Spaces:
Running
Running
| import { createBrowserRouter, RouterProvider } from 'react-router-dom'; | |
| import { AlertContext, type AlertContextProps, type AlertParams, LanguageContext, type LanguageContextProps } from '@ifrc-go/ui/contexts'; | |
| import { useCallback, useMemo, useState, lazy, Suspense, useEffect, Component } from 'react'; | |
| import type { ReactNode } from 'react'; | |
| import { unique } from '@togglecorp/fujs'; | |
| import RootLayout from './layouts/RootLayout'; | |
| import UploadPage from './pages/UploadPage'; | |
| import HelpPage from './pages/HelpPage'; | |
| // Lazy load heavy pages with prefetching | |
| const AnalyticsPage = lazy(() => import('./pages/AnalyticsPage')); | |
| const ExplorePage = lazy(() => import('./pages/ExplorePage')); | |
| const AdminPage = lazy(() => import('./pages/AdminPage/AdminPage')); | |
| const MapDetailPage = lazy(() => import('./pages/MapDetailsPage')); | |
| import { FilterProvider } from './contexts/FilterContext'; | |
| import { AdminProvider } from './contexts/AdminContext'; | |
| // Simple Error Boundary Component | |
| class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> { | |
| constructor(props: { children: ReactNode }) { | |
| super(props); | |
| this.state = { hasError: false }; | |
| } | |
| static getDerivedStateFromError() { | |
| return { hasError: true }; | |
| } | |
| componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { | |
| console.error('Error caught by boundary:', error, errorInfo); | |
| } | |
| render() { | |
| if (this.state.hasError) { | |
| return ( | |
| <div style={{ padding: '20px', textAlign: 'center' }}> | |
| <h2>Something went wrong</h2> | |
| <p>Please refresh the page to try again.</p> | |
| <button onClick={() => window.location.reload()}> | |
| Refresh Page | |
| </button> | |
| </div> | |
| ); | |
| } | |
| return this.props.children; | |
| } | |
| } | |
| // Prefetch function for better performance | |
| const prefetchPage = (importFn: () => Promise<{ default: React.ComponentType }>) => { | |
| // Start prefetching immediately | |
| const prefetchPromise = importFn(); | |
| // Store the promise so we can reuse it | |
| prefetchPromise.catch(() => { | |
| // Silently handle prefetch errors | |
| }); | |
| return prefetchPromise; | |
| }; | |
| // Prefetch all pages on idle | |
| const prefetchAllPages = () => { | |
| if ('requestIdleCallback' in window) { | |
| requestIdleCallback(() => { | |
| prefetchPage(() => import('./pages/AnalyticsPage')); | |
| prefetchPage(() => import('./pages/ExplorePage')); | |
| prefetchPage(() => import('./pages/AdminPage/AdminPage')); | |
| prefetchPage(() => import('./pages/MapDetailsPage')); | |
| }); | |
| } else { | |
| // Fallback for browsers without requestIdleCallback | |
| setTimeout(() => { | |
| prefetchPage(() => import('./pages/AnalyticsPage')); | |
| prefetchPage(() => import('./pages/ExplorePage')); | |
| prefetchPage(() => import('./pages/AdminPage/AdminPage')); | |
| prefetchPage(() => import('./pages/MapDetailsPage')); | |
| }, 1000); | |
| } | |
| }; | |
| const router = createBrowserRouter([ | |
| { | |
| element: <RootLayout />, | |
| children: [ | |
| { path: '/', element: <UploadPage /> }, | |
| { path: '/upload', element: <UploadPage /> }, | |
| { | |
| path: '/analytics', | |
| element: ( | |
| <Suspense fallback={<div>Loading Analytics...</div>}> | |
| <AnalyticsPage /> | |
| </Suspense> | |
| ) | |
| }, | |
| { | |
| path: '/explore', | |
| element: ( | |
| <Suspense fallback={<div>Loading Explore...</div>}> | |
| <ExplorePage /> | |
| </Suspense> | |
| ) | |
| }, | |
| { path: '/help', element: <HelpPage /> }, | |
| { | |
| path: '/admin', | |
| element: ( | |
| <Suspense fallback={<div>Loading Admin...</div>}> | |
| <AdminPage /> | |
| </Suspense> | |
| ) | |
| }, | |
| { | |
| path: '/map/:mapId', | |
| element: ( | |
| <Suspense fallback={<div>Loading Map Details...</div>}> | |
| <MapDetailPage /> | |
| </Suspense> | |
| ) | |
| }, | |
| ], | |
| }, | |
| ], { | |
| basename: import.meta.env.BASE_URL, // This will be '/' from Vite | |
| }); | |
| function Application() { | |
| const [alerts, setAlerts] = useState<AlertParams[]>([]); | |
| // Prefetch pages on mount | |
| useEffect(() => { | |
| prefetchAllPages(); | |
| }, []); | |
| const addAlert = useCallback((alert: AlertParams) => { | |
| setAlerts((prevAlerts) => unique( | |
| [...prevAlerts, alert], | |
| (a) => a.name, | |
| ) ?? prevAlerts); | |
| }, [setAlerts]); | |
| const removeAlert = useCallback((name: AlertParams['name']) => { | |
| setAlerts((prevAlerts) => { | |
| const i = prevAlerts.findIndex((a) => a.name === name); | |
| if (i === -1) { | |
| return prevAlerts; | |
| } | |
| const newAlerts = [...prevAlerts]; | |
| newAlerts.splice(i, 1); | |
| return newAlerts; | |
| }); | |
| }, [setAlerts]); | |
| const updateAlert = useCallback((name: AlertParams['name'], paramsWithoutName: Omit<AlertParams, 'name'>) => { | |
| setAlerts((prevAlerts) => { | |
| const i = prevAlerts.findIndex((a) => a.name === name); | |
| if (i === -1) { | |
| return prevAlerts; | |
| } | |
| const newAlerts = [...prevAlerts]; | |
| newAlerts[i] = { | |
| ...newAlerts[i], | |
| ...paramsWithoutName, | |
| }; | |
| return newAlerts; | |
| }); | |
| }, [setAlerts]); | |
| const alertContextValue = useMemo<AlertContextProps>( | |
| () => ({ | |
| alerts, | |
| addAlert, | |
| removeAlert, | |
| updateAlert, | |
| }), | |
| [alerts, addAlert, removeAlert, updateAlert], | |
| ); | |
| const languageContextValue = useMemo<LanguageContextProps>( | |
| () => ({ | |
| languageNamespaceStatus: {}, | |
| setLanguageNamespaceStatus: () => {}, | |
| currentLanguage: 'en', | |
| setCurrentLanguage: () => {}, | |
| strings: {}, | |
| setStrings: () => {}, | |
| registerNamespace: () => {}, | |
| }), | |
| [], | |
| ); | |
| return ( | |
| <ErrorBoundary> | |
| <AlertContext.Provider value={alertContextValue}> | |
| <LanguageContext.Provider value={languageContextValue}> | |
| <AdminProvider> | |
| <FilterProvider> | |
| <RouterProvider router={router} /> | |
| </FilterProvider> | |
| </AdminProvider> | |
| </LanguageContext.Provider> | |
| </AlertContext.Provider> | |
| </ErrorBoundary> | |
| ); | |
| } | |
| export default function App() { | |
| return <Application />; | |
| } | |