feat: add error boundaries so a crash never blanks the whole app
No error.tsx existed anywhere in the app - an uncaught render error on any page (a beekeeper's most field-critical moment: mid-inspection, no signal, nowhere to go) showed nothing recoverable, not even a message, let alone a way back to a working page. Two boundaries, matching Next.js App Router's error-boundary scoping: - (app)/error.tsx covers every authenticated page. It only replaces the crashed page's content - the sidebar/header/bottom nav stay mounted and usable, so the user can navigate to a different page without a full reload. - app/error.tsx is the outer fallback (login, the public Herkunft page, or a crash in (app)/layout.tsx itself, which only a strictly higher boundary can catch). Its "Zum Dashboard" action is a plain <a> reload rather than client-side navigation, since a crash this high may have left client state corrupted. Both are in German, matching the app's existing card/honeycomb visual language, and explicitly reassure the user that already-saved data is unaffected. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
2e6846285d
commit
8bf9aa467d
2 files changed
+90
No files matched your search
@@ -0,0 +1,42 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { AlertTriangle } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
|
export default function AppError({
|
||||||
|
error,
|
||||||
|
reset,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string };
|
||||||
|
reset: () => void;
|
||||||
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
console.error(error);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex flex-col gap-4 p-4 sm:p-6">
|
||||||
|
<Card className="max-w-lg">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<span className="flex size-9 items-center justify-center rounded-xl bg-destructive/10 text-destructive">
|
||||||
|
<AlertTriangle className="size-5" />
|
||||||
|
</span>
|
||||||
|
<CardTitle className="text-lg">Etwas ist schiefgelaufen</CardTitle>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Diese Seite konnte nicht angezeigt werden. Bereits gespeicherte Daten sind davon nicht
|
||||||
|
betroffen. Über die Navigation kommst du zu jeder anderen Seite.
|
||||||
|
</p>
|
||||||
|
<Button onClick={() => reset()} className="w-fit">
|
||||||
|
Erneut versuchen
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { AlertTriangle } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { HoneycombPattern } from "@/components/honeycomb-pattern";
|
||||||
|
|
||||||
|
export default function RootError({
|
||||||
|
error,
|
||||||
|
reset,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string };
|
||||||
|
reset: () => void;
|
||||||
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
console.error(error);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="relative flex min-h-screen items-center justify-center overflow-hidden bg-background p-4">
|
||||||
|
<HoneycombPattern />
|
||||||
|
<Card className="relative w-full max-w-md shadow-[var(--shadow-elevated)]">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<span className="flex size-9 items-center justify-center rounded-xl bg-destructive/10 text-destructive">
|
||||||
|
<AlertTriangle className="size-5" />
|
||||||
|
</span>
|
||||||
|
<CardTitle className="text-lg">Etwas ist schiefgelaufen</CardTitle>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Es ist ein unerwarteter Fehler aufgetreten. Bereits gespeicherte Daten sind davon nicht
|
||||||
|
betroffen.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Button onClick={() => reset()}>Erneut versuchen</Button>
|
||||||
|
{/* Plain reload, not client-side navigation - a crash here may
|
||||||
|
have left the app's client state corrupted, so this must
|
||||||
|
guarantee a fully clean slate rather than route through it. */}
|
||||||
|
<Button variant="outline" render={<a href="/dashboard">Zum Dashboard</a>} />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user