feat(01-code-splitting): convert 31+ route components to React.lazy

- Replace all 31+ static route component imports in App.tsx with React.lazy() dynamic imports
- Add LazyLoadingFallback.tsx shared component for Suspense fallback
- Wrap <Routes> with <Suspense> for chunk loading states
- Handle named exports (ImageStudio, VideoStudio, ProductMarketing, StoryProjectList) with .then() wrapper
- Main bundle reduced from 8.42MB to 2.50MB (70% reduction)
- 190+ chunk files created for on-demand loading per route

Closes Phase 1 Plan 01-01
This commit is contained in:
ajaysi
2026-05-08 08:27:15 +05:30
parent 4df1adfbe2
commit 8ee042bd2c
2 changed files with 170 additions and 131 deletions

View File

@@ -1,72 +1,81 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { Box, CircularProgress, Typography } from '@mui/material'; import { Box, CircularProgress, Typography } from '@mui/material';
import { ClerkProvider, useAuth } from '@clerk/clerk-react'; import { ClerkProvider, useAuth } from '@clerk/clerk-react';
import Wizard from './components/OnboardingWizard/Wizard';
import MainDashboard from './components/MainDashboard/MainDashboard';
import SEODashboard from './components/SEODashboard/SEODashboard';
import ContentPlanningDashboard from './components/ContentPlanningDashboard/ContentPlanningDashboard';
import FacebookWriter from './components/FacebookWriter/FacebookWriter';
import LinkedInWriter from './components/LinkedInWriter/LinkedInWriter';
import BlogWriter from './components/BlogWriter/BlogWriter';
import StoryWriter from './components/StoryWriter/StoryWriter';
import { StoryProjectList } from './components/StoryWriter/StoryProjectList';
import YouTubeCreator from './components/YouTubeCreator/YouTubeCreator';
import { CreateStudio, EditStudio, UpscaleStudio, ControlStudio, SocialOptimizer, AssetLibrary, ImageStudioDashboard, FaceSwapStudio, CompressionStudio, ImageProcessingStudio } from './components/ImageStudio';
import {
VideoStudioDashboard,
CreateVideo,
AvatarVideo,
EnhanceVideo,
ExtendVideo,
EditVideo,
TransformVideo,
SocialVideo,
FaceSwap,
VideoTranslate,
VideoBackgroundRemover,
AddAudioToVideo,
LibraryVideo,
} from './components/VideoStudio';
import {
ProductMarketingDashboard,
ProductPhotoshootStudio,
ProductAnimationStudio,
ProductVideoStudio,
ProductAvatarStudio,
} from './components/ProductMarketing';
import PodcastDashboard from './components/PodcastMaker/PodcastDashboard';
import PricingPage from './components/Pricing/PricingPage';
import WixTestPage from './components/WixTestPage/WixTestPage';
import WixCallbackPage from './components/WixCallbackPage/WixCallbackPage';
import WordPressCallbackPage from './components/WordPressCallbackPage/WordPressCallbackPage';
import BingCallbackPage from './components/BingCallbackPage/BingCallbackPage';
import BingAnalyticsStorage from './components/BingAnalyticsStorage/BingAnalyticsStorage';
import ResearchDashboard from './pages/ResearchDashboard';
import IntentResearchTest from './pages/IntentResearchTest';
import SchedulerDashboard from './pages/SchedulerDashboard';
import BillingPage from './pages/BillingPage';
import ApprovalsPage from './pages/ApprovalsPage';
import TeamActivityPage from './pages/TeamActivityPage';
import StripeDisputesDashboard from './pages/StripeDisputesDashboard';
import ProtectedRoute from './components/shared/ProtectedRoute'; import ProtectedRoute from './components/shared/ProtectedRoute';
import GSCAuthCallback from './components/SEODashboard/components/GSCAuthCallback';
import Landing from './components/Landing/Landing';
import ErrorBoundary from './components/shared/ErrorBoundary'; import ErrorBoundary from './components/shared/ErrorBoundary';
import ErrorBoundaryTest from './components/shared/ErrorBoundaryTest';
import { OnboardingProvider } from './contexts/OnboardingContext'; import { OnboardingProvider } from './contexts/OnboardingContext';
import { SubscriptionProvider } from './contexts/SubscriptionContext'; import { SubscriptionProvider } from './contexts/SubscriptionContext';
import InitialRouteHandler from './components/App/InitialRouteHandler'; import InitialRouteHandler from './components/App/InitialRouteHandler';
import TokenInstaller from './components/App/TokenInstaller'; import TokenInstaller from './components/App/TokenInstaller';
import { ConditionalCopilotKit, AuthenticatedCopilotWrapper } from './components/App/CopilotWrappers'; import { ConditionalCopilotKit, AuthenticatedCopilotWrapper } from './components/App/CopilotWrappers';
import Landing from './components/Landing/Landing';
import LazyLoadingFallback from './components/shared/LazyLoadingFallback';
// interface OnboardingStatus { // ─── Lazy loaded route components ───────────────────────────────────────────
// onboarding_required: boolean; // Default exports
// onboarding_complete: boolean; const Wizard = React.lazy(() => import('./components/OnboardingWizard/Wizard'));
// current_step?: number; const MainDashboard = React.lazy(() => import('./components/MainDashboard/MainDashboard'));
// total_steps?: number; const SEODashboard = React.lazy(() => import('./components/SEODashboard/SEODashboard'));
// completion_percentage?: number; 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) // Root route that chooses Landing (signed out) or InitialRouteHandler (signed in)
const RootRoute: React.FC = () => { const RootRoute: React.FC = () => {
@@ -162,78 +171,80 @@ const App: React.FC = () => {
<AuthenticatedCopilotWrapper apiKey={copilotApiKey}> <AuthenticatedCopilotWrapper apiKey={copilotApiKey}>
<ConditionalCopilotKit> <ConditionalCopilotKit>
<TokenInstaller /> <TokenInstaller />
<Routes> <Suspense fallback={<LazyLoadingFallback />}>
<Route path="/" element={<RootRoute />} /> <Routes>
<Route <Route path="/" element={<RootRoute />} />
path="/onboarding" <Route
element={ path="/onboarding"
<ErrorBoundary context="Onboarding Wizard" showDetails> element={
<Wizard /> <ErrorBoundary context="Onboarding Wizard" showDetails>
</ErrorBoundary> <Wizard />
} </ErrorBoundary>
/> }
{/* Error Boundary Testing - Development Only */} />
{process.env.NODE_ENV === 'development' && ( {/* Error Boundary Testing - Development Only */}
<Route path="/error-test" element={<ErrorBoundaryTest />} /> {process.env.NODE_ENV === 'development' && (
)} <Route path="/error-test" element={<ErrorBoundaryTest />} />
<Route path="/dashboard" element={<ProtectedRoute><MainDashboard /></ProtectedRoute>} /> )}
<Route path="/seo" element={<ProtectedRoute><SEODashboard /></ProtectedRoute>} /> <Route path="/dashboard" element={<ProtectedRoute><MainDashboard /></ProtectedRoute>} />
<Route path="/seo-dashboard" element={<ProtectedRoute><SEODashboard /></ProtectedRoute>} /> <Route path="/seo" element={<ProtectedRoute><SEODashboard /></ProtectedRoute>} />
<Route path="/content-planning" element={<ProtectedRoute><ContentPlanningDashboard /></ProtectedRoute>} /> <Route path="/seo-dashboard" element={<ProtectedRoute><SEODashboard /></ProtectedRoute>} />
<Route path="/facebook-writer" element={<ProtectedRoute><FacebookWriter /></ProtectedRoute>} /> <Route path="/content-planning" element={<ProtectedRoute><ContentPlanningDashboard /></ProtectedRoute>} />
<Route path="/linkedin-writer" element={<ProtectedRoute><LinkedInWriter /></ProtectedRoute>} /> <Route path="/facebook-writer" element={<ProtectedRoute><FacebookWriter /></ProtectedRoute>} />
<Route path="/blog-writer" element={<ProtectedRoute><BlogWriter /></ProtectedRoute>} /> <Route path="/linkedin-writer" element={<ProtectedRoute><LinkedInWriter /></ProtectedRoute>} />
<Route path="/story-writer" element={<ProtectedRoute><StoryWriter /></ProtectedRoute>} /> <Route path="/blog-writer" element={<ProtectedRoute><BlogWriter /></ProtectedRoute>} />
<Route path="/story-projects" element={<ProtectedRoute><StoryProjectList /></ProtectedRoute>} /> <Route path="/story-writer" element={<ProtectedRoute><StoryWriter /></ProtectedRoute>} />
<Route path="/youtube-creator" element={<ProtectedRoute><YouTubeCreator /></ProtectedRoute>} /> <Route path="/story-projects" element={<ProtectedRoute><StoryProjectList /></ProtectedRoute>} />
<Route path="/podcast-maker" element={<ProtectedRoute><PodcastDashboard /></ProtectedRoute>} /> <Route path="/youtube-creator" element={<ProtectedRoute><YouTubeCreator /></ProtectedRoute>} />
<Route path="/image-studio" element={<ProtectedRoute><ImageStudioDashboard /></ProtectedRoute>} /> <Route path="/podcast-maker" element={<ProtectedRoute><PodcastDashboard /></ProtectedRoute>} />
<Route path="/video-studio" element={<ProtectedRoute><VideoStudioDashboard /></ProtectedRoute>} /> <Route path="/image-studio" element={<ProtectedRoute><ImageStudioDashboard /></ProtectedRoute>} />
<Route path="/video-studio/create" element={<ProtectedRoute><CreateVideo /></ProtectedRoute>} /> <Route path="/video-studio" element={<ProtectedRoute><VideoStudioDashboard /></ProtectedRoute>} />
<Route path="/video-studio/avatar" element={<ProtectedRoute><AvatarVideo /></ProtectedRoute>} /> <Route path="/video-studio/create" element={<ProtectedRoute><CreateVideo /></ProtectedRoute>} />
<Route path="/video-studio/enhance" element={<ProtectedRoute><EnhanceVideo /></ProtectedRoute>} /> <Route path="/video-studio/avatar" element={<ProtectedRoute><AvatarVideo /></ProtectedRoute>} />
<Route path="/video-studio/extend" element={<ProtectedRoute><ExtendVideo /></ProtectedRoute>} /> <Route path="/video-studio/enhance" element={<ProtectedRoute><EnhanceVideo /></ProtectedRoute>} />
<Route path="/video-studio/edit" element={<ProtectedRoute><EditVideo /></ProtectedRoute>} /> <Route path="/video-studio/extend" element={<ProtectedRoute><ExtendVideo /></ProtectedRoute>} />
<Route path="/video-studio/transform" element={<ProtectedRoute><TransformVideo /></ProtectedRoute>} /> <Route path="/video-studio/edit" element={<ProtectedRoute><EditVideo /></ProtectedRoute>} />
<Route path="/video-studio/social" element={<ProtectedRoute><SocialVideo /></ProtectedRoute>} /> <Route path="/video-studio/transform" element={<ProtectedRoute><TransformVideo /></ProtectedRoute>} />
<Route path="/video-studio/face-swap" element={<ProtectedRoute><FaceSwap /></ProtectedRoute>} /> <Route path="/video-studio/social" element={<ProtectedRoute><SocialVideo /></ProtectedRoute>} />
<Route path="/video-studio/video-translate" element={<ProtectedRoute><VideoTranslate /></ProtectedRoute>} /> <Route path="/video-studio/face-swap" element={<ProtectedRoute><FaceSwap /></ProtectedRoute>} />
<Route path="/video-studio/video-background-remover" element={<ProtectedRoute><VideoBackgroundRemover /></ProtectedRoute>} /> <Route path="/video-studio/video-translate" element={<ProtectedRoute><VideoTranslate /></ProtectedRoute>} />
<Route path="/video-studio/add-audio-to-video" element={<ProtectedRoute><AddAudioToVideo /></ProtectedRoute>} /> <Route path="/video-studio/video-background-remover" element={<ProtectedRoute><VideoBackgroundRemover /></ProtectedRoute>} />
<Route path="/video-studio/library" element={<ProtectedRoute><LibraryVideo /></ProtectedRoute>} /> <Route path="/video-studio/add-audio-to-video" element={<ProtectedRoute><AddAudioToVideo /></ProtectedRoute>} />
<Route path="/image-generator" element={<ProtectedRoute><CreateStudio /></ProtectedRoute>} /> <Route path="/video-studio/library" element={<ProtectedRoute><LibraryVideo /></ProtectedRoute>} />
<Route path="/image-editor" element={<ProtectedRoute><EditStudio /></ProtectedRoute>} /> <Route path="/image-generator" element={<ProtectedRoute><CreateStudio /></ProtectedRoute>} />
<Route path="/image-upscale" element={<ProtectedRoute><UpscaleStudio /></ProtectedRoute>} /> <Route path="/image-editor" element={<ProtectedRoute><EditStudio /></ProtectedRoute>} />
<Route path="/image-control" element={<ProtectedRoute><ControlStudio /></ProtectedRoute>} /> <Route path="/image-upscale" element={<ProtectedRoute><UpscaleStudio /></ProtectedRoute>} />
<Route path="/image-studio/face-swap" element={<ProtectedRoute><FaceSwapStudio /></ProtectedRoute>} /> <Route path="/image-control" element={<ProtectedRoute><ControlStudio /></ProtectedRoute>} />
<Route path="/image-studio/compress" element={<ProtectedRoute><CompressionStudio /></ProtectedRoute>} /> <Route path="/image-studio/face-swap" element={<ProtectedRoute><FaceSwapStudio /></ProtectedRoute>} />
<Route path="/image-studio/processing" element={<ProtectedRoute><ImageProcessingStudio /></ProtectedRoute>} /> <Route path="/image-studio/compress" element={<ProtectedRoute><CompressionStudio /></ProtectedRoute>} />
<Route path="/image-studio/social-optimizer" element={<ProtectedRoute><SocialOptimizer /></ProtectedRoute>} /> <Route path="/image-studio/processing" element={<ProtectedRoute><ImageProcessingStudio /></ProtectedRoute>} />
<Route path="/asset-library" element={<ProtectedRoute><AssetLibrary /></ProtectedRoute>} /> <Route path="/image-studio/social-optimizer" element={<ProtectedRoute><SocialOptimizer /></ProtectedRoute>} />
<Route path="/campaign-creator" element={<ProtectedRoute><ProductMarketingDashboard /></ProtectedRoute>} /> <Route path="/asset-library" element={<ProtectedRoute><AssetLibrary /></ProtectedRoute>} />
<Route path="/campaign-creator/photoshoot" element={<ProtectedRoute><ProductPhotoshootStudio /></ProtectedRoute>} /> <Route path="/campaign-creator" element={<ProtectedRoute><ProductMarketingDashboard /></ProtectedRoute>} />
<Route path="/campaign-creator/animation" element={<ProtectedRoute><ProductAnimationStudio /></ProtectedRoute>} /> <Route path="/campaign-creator/photoshoot" element={<ProtectedRoute><ProductPhotoshootStudio /></ProtectedRoute>} />
<Route path="/campaign-creator/video" element={<ProtectedRoute><ProductVideoStudio /></ProtectedRoute>} /> <Route path="/campaign-creator/animation" element={<ProtectedRoute><ProductAnimationStudio /></ProtectedRoute>} />
<Route path="/campaign-creator/avatar" element={<ProtectedRoute><ProductAvatarStudio /></ProtectedRoute>} /> <Route path="/campaign-creator/video" element={<ProtectedRoute><ProductVideoStudio /></ProtectedRoute>} />
<Route path="/product-marketing" element={<Navigate to="/campaign-creator" replace />} /> <Route path="/campaign-creator/avatar" element={<ProtectedRoute><ProductAvatarStudio /></ProtectedRoute>} />
<Route path="/scheduler-dashboard" element={<ProtectedRoute><SchedulerDashboard /></ProtectedRoute>} /> <Route path="/product-marketing" element={<Navigate to="/campaign-creator" replace />} />
<Route path="/billing" element={<ProtectedRoute><BillingPage /></ProtectedRoute>} /> <Route path="/scheduler-dashboard" element={<ProtectedRoute><SchedulerDashboard /></ProtectedRoute>} />
<Route path="/approvals" element={<ProtectedRoute><ApprovalsPage /></ProtectedRoute>} /> <Route path="/billing" element={<ProtectedRoute><BillingPage /></ProtectedRoute>} />
<Route path="/team-activity" element={<ProtectedRoute><TeamActivityPage /></ProtectedRoute>} /> <Route path="/approvals" element={<ProtectedRoute><ApprovalsPage /></ProtectedRoute>} />
<Route path="/stripe-disputes" element={<ProtectedRoute><StripeDisputesDashboard /></ProtectedRoute>} /> <Route path="/team-activity" element={<ProtectedRoute><TeamActivityPage /></ProtectedRoute>} />
<Route path="/pricing" element={<PricingPage />} /> <Route path="/stripe-disputes" element={<ProtectedRoute><StripeDisputesDashboard /></ProtectedRoute>} />
<Route path="/research-test" element={<ResearchDashboard />} /> <Route path="/pricing" element={<PricingPage />} />
<Route path="/research-dashboard" element={<ResearchDashboard />} /> <Route path="/research-test" element={<ResearchDashboard />} />
<Route path="/alwrity-researcher" element={<ResearchDashboard />} /> <Route path="/research-dashboard" element={<ResearchDashboard />} />
<Route path="/intent-research" element={<IntentResearchTest />} /> <Route path="/alwrity-researcher" element={<ResearchDashboard />} />
<Route path="/wix-test" element={<WixTestPage />} /> <Route path="/intent-research" element={<IntentResearchTest />} />
<Route path="/wix-test-direct" element={<WixTestPage />} /> <Route path="/wix-test" element={<WixTestPage />} />
<Route path="/wix/callback" element={<WixCallbackPage />} /> <Route path="/wix-test-direct" element={<WixTestPage />} />
<Route path="/wp/callback" element={<WordPressCallbackPage />} /> <Route path="/wix/callback" element={<WixCallbackPage />} />
<Route path="/gsc/callback" element={<GSCAuthCallback />} /> <Route path="/wp/callback" element={<WordPressCallbackPage />} />
<Route path="/bing/callback" element={<BingCallbackPage />} /> <Route path="/gsc/callback" element={<GSCAuthCallback />} />
<Route path="/bing-analytics-storage" element={<ProtectedRoute><BingAnalyticsStorage /></ProtectedRoute>} /> <Route path="/bing/callback" element={<BingCallbackPage />} />
</Routes> <Route path="/bing-analytics-storage" element={<ProtectedRoute><BingAnalyticsStorage /></ProtectedRoute>} />
</Routes>
</Suspense>
</ConditionalCopilotKit> </ConditionalCopilotKit>
</AuthenticatedCopilotWrapper> </AuthenticatedCopilotWrapper>
</Router> </Router>
@@ -261,4 +272,4 @@ const App: React.FC = () => {
); );
}; };
export default App; export default App;

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { Box, CircularProgress, Typography } from '@mui/material';
interface LazyLoadingFallbackProps {
message?: string;
}
const LazyLoadingFallback: React.FC<LazyLoadingFallbackProps> = ({
message = 'Loading...'
}) => {
return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
minHeight="60vh"
gap={2}
>
<CircularProgress size={40} />
<Typography variant="body1" color="textSecondary">
{message}
</Typography>
</Box>
);
};
export default LazyLoadingFallback;