Issue #518 - Subscription not updating after checkout: - Fix stale closure in SubscriptionContext checkout polling (use subscriptionRef) - Move checkout success polling from InitialRouteHandler into SubscriptionContext - Remove redundant polling code from InitialRouteHandler - Fix plan label: 'Free' instead of 'No Plan', proper capitalization - Add plan refresh button in UserBadge - Add 'View Costing Details' to UserBadge dropdown - Rename 'ALwrity Podcast Maker' to 'Podcast Creator' across UI - Clean subscription=success URL param after verification Blog Writer WYSIWYG Editor enhancements: - Per-section preview toggle (view/edit icons) - Enhanced hover-based toolbar - Circular SVG progress stats bar with detailed tooltip - Research tool chips in stats bar footer - Per-section TTS with useTextToSpeech hook (browser native) - Full blog preview modal with print/PDF support - PlayAllTTSButton: sequential playback with progress bar - OnThisPageNav: floating sidebar with scroll tracking - Section data attributes for scroll anchoring GSC Brainstorm Topics feature: - Backend: gsc_brainstorm_service.py (rule-based + LLM recommendations) - Backend: POST /gsc/brainstorm endpoint with 3-word minimum validation - Frontend: gscBrainstorm.ts API client - Frontend: useGSCBrainstormConnection hook (popup OAuth, no /onboarding redirect) - Frontend: useGSCBrainstorm hook (connect check + brainstorm call) - Frontend: GSCBrainstormModal (3-tab results: Opportunities, Gaps, AI Recs) - Frontend: BrainstormButton (visible at 3+ words, GSC connect overlay) - Wire BrainstormButton into ManualResearchForm and ResearchAction - Add blog_writer to gsc_auth router features for ALWRITY_ENABLED_FEATURES
208 lines
7.2 KiB
TypeScript
208 lines
7.2 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Button,
|
|
Typography,
|
|
Box,
|
|
CircularProgress,
|
|
Alert
|
|
} from '@mui/material';
|
|
import { usePlatformConnections } from '../../../components/OnboardingWizard/common/usePlatformConnections';
|
|
|
|
interface WixConnectModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onConnectionSuccess?: () => void;
|
|
}
|
|
|
|
export const WixConnectModal: React.FC<WixConnectModalProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
onConnectionSuccess
|
|
}) => {
|
|
const { handleConnect, isLoading } = usePlatformConnections();
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
|
|
// Handle OAuth success via postMessage (same pattern as onboarding)
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
|
|
const handler = (event: MessageEvent) => {
|
|
const ngrokOrigin = process.env.REACT_APP_NGROK_ORIGIN || 'https://littery-sonny-unscrutinisingly.ngrok-free.dev';
|
|
const trusted = [window.location.origin, ngrokOrigin];
|
|
if (!trusted.includes(event.origin)) return;
|
|
if (!event.data || typeof event.data !== 'object') return;
|
|
|
|
if (event.data.type === 'WIX_OAUTH_SUCCESS') {
|
|
console.log('Wix OAuth success in modal');
|
|
setIsConnecting(false);
|
|
setError(null);
|
|
// Close modal and notify parent
|
|
if (onConnectionSuccess) {
|
|
onConnectionSuccess();
|
|
}
|
|
onClose();
|
|
}
|
|
|
|
if (event.data.type === 'WIX_OAUTH_ERROR') {
|
|
console.error('Wix OAuth error in modal:', event.data.error);
|
|
setIsConnecting(false);
|
|
setError(event.data.error || 'Wix connection failed. Please try again.');
|
|
}
|
|
};
|
|
|
|
window.addEventListener('message', handler);
|
|
return () => window.removeEventListener('message', handler);
|
|
}, [isOpen, onClose, onConnectionSuccess]);
|
|
|
|
// Also check for URL param (fallback for same-tab redirect)
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.get('wix_connected') === 'true') {
|
|
setIsConnecting(false);
|
|
setError(null);
|
|
if (onConnectionSuccess) {
|
|
onConnectionSuccess();
|
|
}
|
|
onClose();
|
|
window.history.replaceState({}, document.title, window.location.pathname + window.location.hash);
|
|
}
|
|
}, [isOpen, onClose, onConnectionSuccess]);
|
|
|
|
// Cross-tab: detect localStorage signal from OAuth in new tab
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
|
|
const handler = (e: StorageEvent) => {
|
|
if (e.key === 'wix_connected' && e.newValue === 'true') {
|
|
setIsConnecting(false);
|
|
setError(null);
|
|
if (onConnectionSuccess) {
|
|
onConnectionSuccess();
|
|
}
|
|
onClose();
|
|
}
|
|
};
|
|
window.addEventListener('storage', handler);
|
|
return () => window.removeEventListener('storage', handler);
|
|
}, [isOpen, onClose, onConnectionSuccess]);
|
|
|
|
const handleConnectClick = async () => {
|
|
try {
|
|
setIsConnecting(true);
|
|
setError(null);
|
|
// Store current page URL so we can redirect back after OAuth completes
|
|
// This MUST be stored before calling handleConnect to ensure it's available after redirect
|
|
// We ALWAYS override any existing redirect URL since we know the exact page we're on (Blog Writer)
|
|
// Build the redirect URL to ensure it includes the phase (publish) and works with both localhost and ngrok
|
|
const currentPath = window.location.pathname;
|
|
const currentHash = window.location.hash || '#publish'; // Default to publish phase if no hash
|
|
const currentSearch = window.location.search;
|
|
|
|
// Build redirect URL using the user's ACTUAL origin (where browser data lives).
|
|
// Wix OAuth callback URI uses NGROK_ORIGIN (for Wix to reach us), but after OAuth
|
|
// we must redirect back to the user's real origin so their localStorage data is available.
|
|
const redirectUrl = `${window.location.origin}${currentPath}${currentHash}${currentSearch}`;
|
|
|
|
try {
|
|
// Always override any existing redirect URL when connecting from Blog Writer
|
|
sessionStorage.setItem('wix_oauth_redirect', redirectUrl);
|
|
console.log('[WixConnectModal] Stored redirect URL (overriding any existing):', {
|
|
redirectUrl,
|
|
currentOrigin: window.location.origin,
|
|
});
|
|
} catch (e) {
|
|
console.warn('[WixConnectModal] Failed to store redirect URL:', e);
|
|
}
|
|
await handleConnect('wix');
|
|
// OAuth will redirect, so we don't need to do anything else here
|
|
// The postMessage handler or URL param handler will close the modal
|
|
} catch (err: any) {
|
|
console.error('Error connecting to Wix:', err);
|
|
setIsConnecting(false);
|
|
setError(err?.message || 'Failed to start Wix connection. Please try again.');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog
|
|
open={isOpen}
|
|
onClose={onClose}
|
|
maxWidth="sm"
|
|
fullWidth
|
|
PaperProps={{
|
|
sx: {
|
|
borderRadius: 2,
|
|
boxShadow: '0 4px 20px rgba(0,0,0,0.15)'
|
|
}
|
|
}}
|
|
>
|
|
<DialogTitle sx={{ pb: 1 }}>
|
|
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b' }}>
|
|
Connect Your Wix Account
|
|
</Typography>
|
|
</DialogTitle>
|
|
|
|
<DialogContent>
|
|
<Box sx={{ py: 1 }}>
|
|
<Typography variant="body2" color="text.secondary" paragraph>
|
|
Connect your Wix account to publish blog posts directly to your website.
|
|
</Typography>
|
|
|
|
{error && (
|
|
<Alert severity="error" sx={{ mb: 2 }}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
{isConnecting && (
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, py: 2 }}>
|
|
<CircularProgress size={20} />
|
|
<Typography variant="body2" color="text.secondary">
|
|
Opening Wix authorization page...
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
|
|
<Box sx={{ mt: 2, p: 2, bgcolor: '#f8fafc', borderRadius: 1 }}>
|
|
<Typography variant="caption" color="text.secondary">
|
|
<strong>What happens next:</strong>
|
|
</Typography>
|
|
<Typography variant="caption" component="div" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
|
<ol style={{ margin: '8px 0 0 20px', padding: 0 }}>
|
|
<li>You'll be redirected to Wix to authorize ALwrity</li>
|
|
<li>Grant permissions for blog creation and publishing</li>
|
|
<li>You'll be redirected back to ALwrity</li>
|
|
<li>Your blog post will be published automatically</li>
|
|
</ol>
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</DialogContent>
|
|
|
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
|
<Button onClick={onClose} disabled={isConnecting}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="contained"
|
|
onClick={handleConnectClick}
|
|
disabled={isConnecting || isLoading}
|
|
startIcon={isConnecting ? <CircularProgress size={16} /> : undefined}
|
|
>
|
|
{isConnecting ? 'Connecting...' : 'Connect to Wix'}
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
};
|
|
|
|
export default WixConnectModal;
|
|
|