import React, { useState, useEffect, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { Box, CircularProgress, Typography } from '@mui/material';
import { ClerkProvider, useAuth } from '@clerk/clerk-react';
import ProtectedRoute from './components/shared/ProtectedRoute';
import ErrorBoundary from './components/shared/ErrorBoundary';
import { OnboardingProvider } from './contexts/OnboardingContext';
import { SubscriptionProvider } from './contexts/SubscriptionContext';
import InitialRouteHandler from './components/App/InitialRouteHandler';
import TokenInstaller from './components/App/TokenInstaller';
import { ConditionalCopilotKit, AuthenticatedCopilotWrapper } from './components/App/CopilotWrappers';
import Landing from './components/Landing/Landing';
import LazyLoadingFallback from './components/shared/LazyLoadingFallback';
import FeatureRoute from './components/shared/FeatureRoute';
// ─── Lazy loaded route components ───────────────────────────────────────────
// Default exports
const Wizard = React.lazy(() => import('./components/OnboardingWizard/Wizard'));
const MainDashboard = React.lazy(() => import('./components/MainDashboard/MainDashboard'));
const SEODashboard = React.lazy(() => import('./components/SEODashboard/SEODashboard'));
const ContentPlanningDashboard = React.lazy(() => import('./components/ContentPlanningDashboard/ContentPlanningDashboard'));
const FacebookWriter = React.lazy(() => import('./components/FacebookWriter/FacebookWriter'));
const LinkedInWriter = React.lazy(() => import('./components/LinkedInWriter/LinkedInWriter'));
const BlogWriter = React.lazy(() => import('./components/BlogWriter/BlogWriter'));
const StoryWriter = React.lazy(() => import('./components/StoryWriter/StoryWriter'));
const YouTubeCreator = React.lazy(() => import('./components/YouTubeCreator/YouTubeCreator'));
const PodcastDashboard = React.lazy(() => import('./components/PodcastMaker/PodcastDashboard'));
const PricingPage = React.lazy(() => import('./components/Pricing/PricingPage'));
const WixTestPage = React.lazy(() => import('./components/WixTestPage/WixTestPage'));
const WixCallbackPage = React.lazy(() => import('./components/WixCallbackPage/WixCallbackPage'));
const WordPressCallbackPage = React.lazy(() => import('./components/WordPressCallbackPage/WordPressCallbackPage'));
const BingCallbackPage = React.lazy(() => import('./components/BingCallbackPage/BingCallbackPage'));
const BingAnalyticsStorage = React.lazy(() => import('./components/BingAnalyticsStorage/BingAnalyticsStorage'));
const ResearchDashboard = React.lazy(() => import('./pages/ResearchDashboard'));
const IntentResearchTest = React.lazy(() => import('./pages/IntentResearchTest'));
const SchedulerDashboard = React.lazy(() => import('./pages/SchedulerDashboard'));
const BillingPage = React.lazy(() => import('./pages/BillingPage'));
const ApprovalsPage = React.lazy(() => import('./pages/ApprovalsPage'));
const TeamActivityPage = React.lazy(() => import('./pages/TeamActivityPage'));
const StripeDisputesDashboard = React.lazy(() => import('./pages/StripeDisputesDashboard'));
const GSCAuthCallback = React.lazy(() => import('./components/SEODashboard/components/GSCAuthCallback'));
const ErrorBoundaryTest = React.lazy(() => import('./components/shared/ErrorBoundaryTest'));
// Named exports — need .then() wrapper to resolve default
const StoryProjectList = React.lazy(() => import('./components/StoryWriter/StoryProjectList').then(m => ({ default: m.StoryProjectList })));
// ImageStudio barrel (10 named exports)
const CreateStudio = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.CreateStudio })));
const EditStudio = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.EditStudio })));
const UpscaleStudio = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.UpscaleStudio })));
const ControlStudio = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.ControlStudio })));
const SocialOptimizer = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.SocialOptimizer })));
const AssetLibrary = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.AssetLibrary })));
const ImageStudioDashboard = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.ImageStudioDashboard })));
const FaceSwapStudio = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.FaceSwapStudio })));
const CompressionStudio = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.CompressionStudio })));
const ImageProcessingStudio = React.lazy(() => import('./components/ImageStudio').then(m => ({ default: m.ImageProcessingStudio })));
// VideoStudio barrel (13 named exports)
const VideoStudioDashboard = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.VideoStudioDashboard })));
const CreateVideo = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.CreateVideo })));
const AvatarVideo = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.AvatarVideo })));
const EnhanceVideo = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.EnhanceVideo })));
const ExtendVideo = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.ExtendVideo })));
const EditVideo = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.EditVideo })));
const TransformVideo = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.TransformVideo })));
const SocialVideo = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.SocialVideo })));
const FaceSwap = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.FaceSwap })));
const VideoTranslate = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.VideoTranslate })));
const VideoBackgroundRemover = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.VideoBackgroundRemover })));
const AddAudioToVideo = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.AddAudioToVideo })));
const LibraryVideo = React.lazy(() => import('./components/VideoStudio').then(m => ({ default: m.LibraryVideo })));
// ProductMarketing barrel (5 named exports)
const ProductMarketingDashboard = React.lazy(() => import('./components/ProductMarketing').then(m => ({ default: m.ProductMarketingDashboard })));
const ProductPhotoshootStudio = React.lazy(() => import('./components/ProductMarketing').then(m => ({ default: m.ProductPhotoshootStudio })));
const ProductAnimationStudio = React.lazy(() => import('./components/ProductMarketing').then(m => ({ default: m.ProductAnimationStudio })));
const ProductVideoStudio = React.lazy(() => import('./components/ProductMarketing').then(m => ({ default: m.ProductVideoStudio })));
const ProductAvatarStudio = React.lazy(() => import('./components/ProductMarketing').then(m => ({ default: m.ProductAvatarStudio })));
// Root route that chooses Landing (signed out) or InitialRouteHandler (signed in)
const RootRoute: React.FC = () => {
const { isSignedIn } = useAuth();
if (isSignedIn) {
return ;
}
return ;
};
const App: React.FC = () => {
// React Hooks MUST be at the top before any conditionals
const [loading, setLoading] = useState(true);
// Get CopilotKit key from localStorage or .env
const [copilotApiKey, setCopilotApiKey] = useState(() => {
const savedKey = localStorage.getItem('copilotkit_api_key');
const envKey = process.env.REACT_APP_COPILOTKIT_API_KEY || '';
const key = (savedKey || envKey).trim();
// Validate key format if present
if (key && !key.startsWith('ck_pub_')) {
console.warn('CopilotKit API key format invalid - must start with ck_pub_');
}
return key;
});
// Initialize app - loading state will be managed by InitialRouteHandler
useEffect(() => {
// Remove manual health check - connection errors are handled by ErrorBoundary
setLoading(false);
}, []);
// Listen for CopilotKit key updates
useEffect(() => {
const handleKeyUpdate = (event: CustomEvent) => {
const newKey = event.detail?.apiKey;
if (newKey) {
console.log('App: CopilotKit key updated, reloading...');
setCopilotApiKey(newKey);
setTimeout(() => window.location.reload(), 500);
}
};
window.addEventListener('copilotkit-key-updated', handleKeyUpdate as EventListener);
return () => window.removeEventListener('copilotkit-key-updated', handleKeyUpdate as EventListener);
}, []);
// Token installer must be inside ClerkProvider; see TokenInstaller below
if (loading) {
return (
Connecting to ALwrity...
);
}
// Get environment variables with fallbacks
const clerkPublishableKey = process.env.REACT_APP_CLERK_PUBLISHABLE_KEY || '';
const clerkJSUrl = process.env.REACT_APP_CLERK_JS_URL;
// Show error if required keys are missing
if (!clerkPublishableKey) {
return (
Missing Clerk Publishable Key
Please add REACT_APP_CLERK_PUBLISHABLE_KEY to your .env file
);
}
// Render app with or without CopilotKit based on whether we have a key
const renderApp = () => {
return (
}>
} />
}
/>
{/* Error Boundary Testing - Development Only */}
{process.env.NODE_ENV === 'development' && (
} />
)}
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
{/* Auth callbacks — always accessible (needed for OAuth flow) */}
} />
} />
} />
} />
} />
);
};
return (
{
// Custom error handler - send to analytics/monitoring
console.error('Global error caught:', { error, errorInfo });
// TODO: Send to error tracking service (Sentry, LogRocket, etc.)
}}
>
{renderApp()}
);
};
export default App;