SCGR's picture
CI fix
8dd8f8e
raw
history blame
6.12 kB
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 />;
}