Phase 6: polish — error boundaries, a11y, smoke test doc
Close out the read-only surface before cutover with three families of small fixes that the previous phases left unfinished: - Error boundaries: add src/app/error.tsx (route-segment), global-error.tsx (root crash fallback with its own minimal html/body — no Providers dependency since those may be the thing that crashed), and not-found.tsx for a Hebrew 404 instead of the default Next page. - Accessibility: wire usePathname() into AppShell so the current nav item gets aria-current="page" and a gold underline. Add aria-label + aria-hidden on the icon-only buttons that Phase 5 left text-less (corpus trash, parties-field Plus). Nav gets an aria-label of its own. - Metadata template: title on each route now reads "X · עוזר משפטי" via the layout.tsx title.template. Description localized to Jerusalem. - README: full E2E smoke test checklist covering all 9 screens, plus a backend contract table so future phases know which hook wraps which endpoint. Documents the known Gitea→Coolify webhook issue. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
57
web-ui/src/app/error.tsx
Normal file
57
web-ui/src/app/error.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
/*
|
||||
* Route-segment error boundary. Next 16 App Router convention: this file
|
||||
* catches render-time errors thrown below the root layout. `reset` clears
|
||||
* the error and re-renders the segment; we keep the user's nav in place
|
||||
* (no AppShell here — the shell re-renders from the layout above us).
|
||||
*/
|
||||
export default function ErrorPage({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
console.error("Route error:", error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<main className="flex-1 w-full max-w-[1400px] mx-auto px-10 py-10">
|
||||
<div className="max-w-xl mx-auto text-center space-y-5 py-16">
|
||||
<AlertTriangle className="w-12 h-12 text-danger mx-auto" aria-hidden="true" />
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-navy">משהו השתבש</h1>
|
||||
<p className="text-ink-muted leading-relaxed">
|
||||
נכשלה טעינת המסך. זה עשוי להיות כשל זמני ברשת או באחד מה-endpoints
|
||||
של FastAPI.
|
||||
</p>
|
||||
{error.digest && (
|
||||
<p className="text-[0.72rem] text-ink-light tabular-nums">
|
||||
error id: {error.digest}
|
||||
</p>
|
||||
)}
|
||||
{error.message && (
|
||||
<code className="text-[0.78rem] text-ink-soft bg-rule-soft/60 rounded px-3 py-1 inline-block mt-2">
|
||||
{error.message}
|
||||
</code>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<Button onClick={reset} className="bg-navy hover:bg-navy-soft text-parchment">
|
||||
נסה שוב
|
||||
</Button>
|
||||
<Button variant="outline" asChild>
|
||||
<Link href="/">חזרה לבית</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
60
web-ui/src/app/global-error.tsx
Normal file
60
web-ui/src/app/global-error.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
/*
|
||||
* Root-level error boundary. Renders only when the root layout itself
|
||||
* crashes, so it must include its own <html> / <body>. No AppShell here —
|
||||
* the Providers that wrap AppShell are also above the crash boundary and
|
||||
* may themselves be the thing that failed.
|
||||
*/
|
||||
export default function GlobalError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<html lang="he" dir="rtl">
|
||||
<body
|
||||
style={{
|
||||
fontFamily: "system-ui, sans-serif",
|
||||
background: "#f5f1e8",
|
||||
color: "#1a1a2e",
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "2rem",
|
||||
}}
|
||||
>
|
||||
<div style={{ maxWidth: 520, textAlign: "center" }}>
|
||||
<h1 style={{ color: "#0f172a", fontSize: "2rem", marginBottom: 8 }}>
|
||||
שגיאה חמורה
|
||||
</h1>
|
||||
<p style={{ color: "#6b7280", lineHeight: 1.6, marginBottom: 16 }}>
|
||||
האפליקציה נכשלה לטעון. נסה לרענן את הדף.
|
||||
</p>
|
||||
{error.digest && (
|
||||
<p style={{ fontSize: 12, color: "#9ca3af", marginBottom: 16 }}>
|
||||
error id: {error.digest}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
onClick={reset}
|
||||
style={{
|
||||
background: "#0f172a",
|
||||
color: "#fbf8f0",
|
||||
border: 0,
|
||||
padding: "0.6rem 1.25rem",
|
||||
borderRadius: 6,
|
||||
cursor: "pointer",
|
||||
fontSize: "0.95rem",
|
||||
}}
|
||||
>
|
||||
נסה שוב
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -14,8 +14,11 @@ const heebo = Heebo({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "עוזר משפטי — ניהול תיקים",
|
||||
description: "מערכת סיוע בניסוח החלטות לוועדת ערר לתכנון ובנייה",
|
||||
title: {
|
||||
default: "עוזר משפטי — ניהול תיקים",
|
||||
template: "%s · עוזר משפטי",
|
||||
},
|
||||
description: "מערכת סיוע בניסוח החלטות לוועדת ערר לתכנון ובנייה, ירושלים",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
24
web-ui/src/app/not-found.tsx
Normal file
24
web-ui/src/app/not-found.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import Link from "next/link";
|
||||
import { AppShell } from "@/components/app-shell";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<AppShell>
|
||||
<section className="max-w-xl mx-auto text-center py-16 space-y-5">
|
||||
<div className="font-display text-gold text-6xl leading-none" aria-hidden="true">
|
||||
404
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-navy">הדף לא נמצא</h1>
|
||||
<p className="text-ink-muted leading-relaxed">
|
||||
הכתובת שביקשת אינה קיימת או שהוזזה לדף אחר.
|
||||
</p>
|
||||
</div>
|
||||
<Button asChild className="bg-navy hover:bg-navy-soft text-parchment">
|
||||
<Link href="/">חזרה לבית</Link>
|
||||
</Button>
|
||||
</section>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user