fix(layout): add React error boundary to catch render crashes
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled

Previously a JS rendering error produced a blank white screen with no
feedback. PageErrorBoundary now catches the error, shows the error
message + stack trace, and offers a reload button. This will surface
the exact crash message for the inaccessible test page.
This commit is contained in:
kitos
2026-05-29 13:23:28 +02:00
parent 9310652944
commit 6c8a1317fd

View File

@@ -1,8 +1,64 @@
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import { LogOut } from "lucide-react"; import { LogOut, AlertTriangle, RefreshCw } from "lucide-react";
import { useAuth } from "../context/AuthContext"; import { useAuth } from "../context/AuthContext";
import Sidebar from "./Sidebar"; import Sidebar from "./Sidebar";
import NotificationBell from "./NotificationBell"; import NotificationBell from "./NotificationBell";
import React from "react";
/* ── Error Boundary ──────────────────────────────────────────────────
Catches any unhandled rendering error and shows a recoverable error
screen instead of a blank white page.
─────────────────────────────────────────────────────────────────── */
interface EBState { hasError: boolean; error: Error | null }
class PageErrorBoundary extends React.Component<{ children: React.ReactNode }, EBState> {
constructor(props: { children: React.ReactNode }) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): EBState {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error("[PageErrorBoundary]", error, info.componentStack);
}
render() {
if (this.state.hasError) {
return (
<div className="flex h-full flex-col items-center justify-center gap-4 p-8 text-center">
<AlertTriangle className="h-12 w-12 text-red-400" />
<div>
<h2 className="text-xl font-semibold text-white">Something went wrong</h2>
<p className="mt-1 text-sm text-gray-400">
{this.state.error?.message ?? "An unexpected error occurred while rendering this page."}
</p>
{this.state.error?.stack && (
<pre className="mt-3 max-h-40 overflow-y-auto rounded-lg border border-gray-800 bg-gray-900 p-3 text-left text-xs text-gray-500">
{this.state.error.stack}
</pre>
)}
</div>
<button
onClick={() => {
this.setState({ hasError: false, error: null });
window.location.reload();
}}
className="flex items-center gap-2 rounded-lg border border-gray-700 px-4 py-2 text-sm text-gray-300 hover:bg-gray-800"
>
<RefreshCw className="h-4 w-4" />
Reload page
</button>
</div>
);
}
return this.props.children;
}
}
/* ── Layout ─────────────────────────────────────────────────────────── */
export default function Layout() { export default function Layout() {
const { user, logout } = useAuth(); const { user, logout } = useAuth();
@@ -25,9 +81,11 @@ export default function Layout() {
</button> </button>
</header> </header>
{/* Main content */} {/* Main content wrapped in error boundary */}
<main className="flex-1 overflow-y-auto p-6"> <main className="flex-1 overflow-y-auto p-6">
<Outlet /> <PageErrorBoundary>
<Outlet />
</PageErrorBoundary>
</main> </main>
</div> </div>
</div> </div>