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;