Improve error boundary granularity with page and section level boundaries
Implement three-level error boundary strategy: - Top-level (app shell): catches critical failures - Page-level: preserves navigation when page crashes - Section-level: graceful degradation for charts/tables Create new components: - PageErrorBoundary: wraps page routes - SectionErrorBoundary: wraps data-heavy sections Enhance ErrorBoundary with customizable titles, messages, and reload behavior. Apply page boundaries to all route handlers in App.tsx. Apply section boundaries to: - DashboardPage: server status, ban trend, country charts, ban list - JailsPage: jail overview, ban/unban form, IP lookup - MapPage: world map, ban table - ConfigPage: configuration editor - HistoryPage: history table, IP detail view - BlocklistsPage: sources, schedule, import log Update Web-Development.md with error boundary strategy documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -17,6 +17,11 @@
|
||||
* - `/history` — event history (protected)
|
||||
* - `/blocklists` — blocklist management (protected)
|
||||
* All unmatched paths redirect to `/`.
|
||||
*
|
||||
* Error Boundaries:
|
||||
* - Top-level ErrorBoundary wraps the entire app shell (rare full-page reload).
|
||||
* - Each page route wrapped in PageErrorBoundary (page fails but nav persists).
|
||||
* - Risky sections within pages wrapped in SectionErrorBoundary (graceful degradation).
|
||||
*/
|
||||
|
||||
import { lazy, Suspense } from "react";
|
||||
@@ -29,6 +34,7 @@ import { TimezoneProvider } from "./providers/TimezoneProvider";
|
||||
import { RequireAuth } from "./components/RequireAuth";
|
||||
import { SetupGuard } from "./components/SetupGuard";
|
||||
import { ErrorBoundary } from "./components/ErrorBoundary";
|
||||
import { PageErrorBoundary } from "./components/PageErrorBoundary";
|
||||
import { MainLayout } from "./layouts/MainLayout";
|
||||
|
||||
const SetupPage = lazy(() => import("./pages/SetupPage").then((m) => ({ default: m.SetupPage })));
|
||||
@@ -50,21 +56,34 @@ function AppContents(): React.JSX.Element {
|
||||
|
||||
return (
|
||||
<FluentProvider theme={theme}>
|
||||
<ErrorBoundary>
|
||||
<ErrorBoundary
|
||||
title="Application Error"
|
||||
message="The application encountered a critical error. Reloading may help."
|
||||
isFullPage={true}
|
||||
>
|
||||
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
|
||||
<Suspense fallback={<Spinner size="large" label="Loading…" />}>
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
{/* Setup wizard — always accessible; redirects to /login if already done */}
|
||||
<Route path="/setup" element={<SetupPage />} />
|
||||
<Route
|
||||
path="/setup"
|
||||
element={
|
||||
<PageErrorBoundary pageName="Setup">
|
||||
<SetupPage />
|
||||
</PageErrorBoundary>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Login — requires setup to be complete */}
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<SetupGuard>
|
||||
<LoginPage />
|
||||
</SetupGuard>
|
||||
<PageErrorBoundary pageName="Login">
|
||||
<SetupGuard>
|
||||
<LoginPage />
|
||||
</SetupGuard>
|
||||
</PageErrorBoundary>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -80,13 +99,62 @@ function AppContents(): React.JSX.Element {
|
||||
</SetupGuard>
|
||||
}
|
||||
>
|
||||
<Route index element={<DashboardPage />} />
|
||||
<Route path="/map" element={<MapPage />} />
|
||||
<Route path="/jails" element={<JailsPage />} />
|
||||
<Route path="/jails/:name" element={<JailDetailPage />} />
|
||||
<Route path="/config" element={<ConfigPage />} />
|
||||
<Route path="/history" element={<HistoryPage />} />
|
||||
<Route path="/blocklists" element={<BlocklistsPage />} />
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<PageErrorBoundary pageName="Dashboard">
|
||||
<DashboardPage />
|
||||
</PageErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/map"
|
||||
element={
|
||||
<PageErrorBoundary pageName="Map">
|
||||
<MapPage />
|
||||
</PageErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jails"
|
||||
element={
|
||||
<PageErrorBoundary pageName="Jails">
|
||||
<JailsPage />
|
||||
</PageErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/jails/:name"
|
||||
element={
|
||||
<PageErrorBoundary pageName="Jail Details">
|
||||
<JailDetailPage />
|
||||
</PageErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/config"
|
||||
element={
|
||||
<PageErrorBoundary pageName="Configuration">
|
||||
<ConfigPage />
|
||||
</PageErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/history"
|
||||
element={
|
||||
<PageErrorBoundary pageName="History">
|
||||
<HistoryPage />
|
||||
</PageErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/blocklists"
|
||||
element={
|
||||
<PageErrorBoundary pageName="Blocklists">
|
||||
<BlocklistsPage />
|
||||
</PageErrorBoundary>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
{/* Fallback — redirect unknown paths to dashboard */}
|
||||
|
||||
Reference in New Issue
Block a user