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:
2026-04-28 08:33:39 +02:00
parent 42beb9cf3b
commit da6433b2cf
12 changed files with 453 additions and 143 deletions

View File

@@ -4,6 +4,8 @@
* Shows a clickable SVG world map coloured by ban density, a time-range
* selector, and a companion table filtered by the selected country (or all
* bans when no country is selected).
*
* Critical sections wrapped with SectionErrorBoundary for resilience.
*/
import { useState, useMemo, useEffect } from "react";
@@ -23,6 +25,7 @@ import {
DismissRegular,
} from "@fluentui/react-icons";
import { DashboardFilterBar } from "../components/DashboardFilterBar";
import { SectionErrorBoundary } from "../components/SectionErrorBoundary";
import { WorldMap } from "../components/WorldMap";
import { useMapData } from "../hooks/useMapData";
import { useMapColorThresholds } from "../hooks/useMapColorThresholds";
@@ -250,15 +253,17 @@ export function MapPage(): React.JSX.Element {
{/* immediate visual feedback before the filtered data arrives. */}
{/* ---------------------------------------------------------------- */}
{!error && hasLoadedOnce && (
<WorldMap
countries={countries}
countryNames={countryNames}
selectedCountry={selectedCountry}
onSelectCountry={setSelectedCountry}
thresholdLow={thresholdLow}
thresholdMedium={thresholdMedium}
thresholdHigh={thresholdHigh}
/>
<SectionErrorBoundary sectionName="World Map">
<WorldMap
countries={countries}
countryNames={countryNames}
selectedCountry={selectedCountry}
onSelectCountry={setSelectedCountry}
thresholdLow={thresholdLow}
thresholdMedium={thresholdMedium}
thresholdHigh={thresholdHigh}
/>
</SectionErrorBoundary>
)}
{/* ---------------------------------------------------------------- */}
@@ -302,19 +307,21 @@ export function MapPage(): React.JSX.Element {
{/* Companion bans table */}
{/* ---------------------------------------------------------------- */}
{!error && hasLoadedOnce && (
<div className={mergeClasses(styles.tableWrapper, loading && styles.tableWrapperLoading)}>
<MapBansTable
pageBans={pageBans}
visibleCount={visibleBans.length}
page={page}
pageSize={pageSize}
totalPages={totalPages}
hasPrev={hasPrev}
hasNext={hasNext}
onPageChange={setPage}
onPageSizeChange={setPageSize}
/>
</div>
<SectionErrorBoundary sectionName="Map Ban Table">
<div className={mergeClasses(styles.tableWrapper, loading && styles.tableWrapperLoading)}>
<MapBansTable
pageBans={pageBans}
visibleCount={visibleBans.length}
page={page}
pageSize={pageSize}
totalPages={totalPages}
hasPrev={hasPrev}
hasNext={hasNext}
onPageChange={setPage}
onPageSizeChange={setPageSize}
/>
</div>
</SectionErrorBoundary>
)}
</div>
);