Polish public error and 404 screens with brand atmosphere.
Local Build and Deploy / deploy (push) Successful in 1m31s
Local Build and Deploy / deploy (push) Successful in 1m31s
Shared ErrorScreen composition, i18n for en/nl/it, Sentry on route errors, and a self-contained global-error fallback that matches the hotel gold theme. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
b22725d3a9
commit
1f552c2822
8 files changed
+348
-80
No files matched your search
+32
-42
@@ -1,12 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { Home, RefreshCw } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect } from "react";
|
||||
import { ErrorScreen } from "@/components/error-screen";
|
||||
|
||||
/**
|
||||
* Route-segment error boundary. Renders inside the root layout (so the shell
|
||||
* stays), shows a friendly card, and offers a reset. The raw error is logged to
|
||||
* the console, never shown to the user.
|
||||
* stays). Never shows the raw error to the user.
|
||||
*/
|
||||
export default function ErrorPage({
|
||||
error,
|
||||
@@ -15,51 +18,38 @@ export default function ErrorPage({
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
const t = useTranslations("pages.error");
|
||||
|
||||
useEffect(() => {
|
||||
console.error(error);
|
||||
Sentry.captureException(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<div className="content-card">
|
||||
<div className="content-card-head">
|
||||
<span className="content-card-icon" aria-hidden>
|
||||
⚠️
|
||||
</span>
|
||||
<div className="content-card-head-text">
|
||||
<p className="content-card-title">Something went wrong</p>
|
||||
<p className="content-card-subtitle">
|
||||
An unexpected error occurred
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content-card-body">
|
||||
<p className="muted" style={{ marginTop: 0 }}>
|
||||
Try again — if the problem persists, please contact a member of
|
||||
staff.
|
||||
<ErrorScreen
|
||||
code={t("code")}
|
||||
title={t("title")}
|
||||
subtitle={t("subtitle")}
|
||||
body={t("body")}
|
||||
actions={
|
||||
<>
|
||||
<button type="button" className="btn btn-primary" onClick={() => reset()}>
|
||||
<RefreshCw className="error-screen-btn-icon" aria-hidden />
|
||||
{t("tryAgain")}
|
||||
</button>
|
||||
<Link className="btn btn-outline" href="/">
|
||||
<Home className="error-screen-btn-icon" aria-hidden />
|
||||
{t("backHome")}
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
error.digest ? (
|
||||
<p className="error-screen-digest">
|
||||
{t("reference", { digest: error.digest })}
|
||||
</p>
|
||||
<div style={{ display: "flex", gap: "0.6rem", flexWrap: "wrap" }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={() => reset()}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
<Link className="btn btn-outline" href="/">
|
||||
Back home
|
||||
</Link>
|
||||
</div>
|
||||
{error.digest ? (
|
||||
<p
|
||||
className="muted"
|
||||
style={{ marginTop: "0.75rem", fontSize: "0.75rem" }}
|
||||
>
|
||||
Reference: {error.digest}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+81
-25
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+20
-13
@@ -1,22 +1,29 @@
|
||||
import { Home, Newspaper } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { ContentCard } from "@/components/public/ui";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { ErrorScreen } from "@/components/error-screen";
|
||||
|
||||
export default async function NotFound() {
|
||||
const t = await getTranslations("pages.notFound");
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<ContentCard icon="🧭" title="Page not found" subtitle="Error 404">
|
||||
<p className="muted" style={{ marginTop: 0 }}>
|
||||
The page you’re looking for doesn’t exist or has moved.
|
||||
</p>
|
||||
<div style={{ display: "flex", gap: "0.6rem", flexWrap: "wrap" }}>
|
||||
<ErrorScreen
|
||||
code={t("code")}
|
||||
title={t("title")}
|
||||
subtitle={t("subtitle")}
|
||||
body={t("body")}
|
||||
actions={
|
||||
<>
|
||||
<Link className="btn btn-primary" href="/">
|
||||
Back home
|
||||
<Home className="error-screen-btn-icon" aria-hidden />
|
||||
{t("backHome")}
|
||||
</Link>
|
||||
<Link className="btn btn-outline" href="/news">
|
||||
Latest news
|
||||
<Newspaper className="error-screen-btn-icon" aria-hidden />
|
||||
{t("latestNews")}
|
||||
</Link>
|
||||
</div>
|
||||
</ContentCard>
|
||||
</main>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
/**
|
||||
* Shared public error / empty-route composition.
|
||||
* Used by route error.tsx and not-found.tsx (inside the app shell).
|
||||
*/
|
||||
export function ErrorScreen({
|
||||
code,
|
||||
title,
|
||||
subtitle,
|
||||
body,
|
||||
actions,
|
||||
footer,
|
||||
}: {
|
||||
code: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
body: string;
|
||||
actions: ReactNode;
|
||||
footer?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<main className="error-screen">
|
||||
<div className="error-screen-glow" aria-hidden />
|
||||
<section className="error-screen-panel" aria-labelledby="error-screen-title">
|
||||
<p className="error-screen-code">{code}</p>
|
||||
<h1 id="error-screen-title" className="error-screen-title">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="error-screen-subtitle">{subtitle}</p>
|
||||
<p className="error-screen-body">{body}</p>
|
||||
<div className="error-screen-actions">{actions}</div>
|
||||
{footer ? <div className="error-screen-footer">{footer}</div> : null}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -40,6 +40,23 @@
|
||||
"darkMode": "Dark mode"
|
||||
},
|
||||
"pages": {
|
||||
"error": {
|
||||
"code": "Error",
|
||||
"title": "Something went wrong",
|
||||
"subtitle": "The hotel hit an unexpected snag.",
|
||||
"body": "Try again in a moment. If it keeps happening, contact staff with the reference below.",
|
||||
"tryAgain": "Try again",
|
||||
"backHome": "Back home",
|
||||
"reference": "Reference: {digest}"
|
||||
},
|
||||
"notFound": {
|
||||
"code": "404",
|
||||
"title": "Page not found",
|
||||
"subtitle": "This room doesn’t exist — or the furniture was moved.",
|
||||
"body": "Check the link, or head back to the lobby.",
|
||||
"backHome": "Back home",
|
||||
"latestNews": "Latest news"
|
||||
},
|
||||
"staff": {
|
||||
"title": "Staff",
|
||||
"subtitle": "The team behind the hotel",
|
||||
|
||||
@@ -40,6 +40,23 @@
|
||||
"darkMode": "Tema scuro"
|
||||
},
|
||||
"pages": {
|
||||
"error": {
|
||||
"code": "Errore",
|
||||
"title": "Qualcosa è andato storto",
|
||||
"subtitle": "L'hotel ha incontrato un imprevisto.",
|
||||
"body": "Riprova tra poco. Se continua, contatta lo staff indicando il riferimento qui sotto.",
|
||||
"tryAgain": "Riprova",
|
||||
"backHome": "Torna alla home",
|
||||
"reference": "Riferimento: {digest}"
|
||||
},
|
||||
"notFound": {
|
||||
"code": "404",
|
||||
"title": "Pagina non trovata",
|
||||
"subtitle": "Questa stanza non esiste — o i mobili sono stati spostati.",
|
||||
"body": "Controlla il link, oppure torna alla lobby.",
|
||||
"backHome": "Torna alla home",
|
||||
"latestNews": "Ultime notizie"
|
||||
},
|
||||
"staff": {
|
||||
"title": "Staff",
|
||||
"subtitle": "Il team dietro l'hotel",
|
||||
|
||||
@@ -40,6 +40,23 @@
|
||||
"darkMode": "Donkere modus"
|
||||
},
|
||||
"pages": {
|
||||
"error": {
|
||||
"code": "Fout",
|
||||
"title": "Er ging iets mis",
|
||||
"subtitle": "Het hotel liep tegen een onverwachte snag aan.",
|
||||
"body": "Probeer het zo opnieuw. Blijft het probleem? Neem contact op met de staff en noem de referentie hieronder.",
|
||||
"tryAgain": "Opnieuw proberen",
|
||||
"backHome": "Terug naar home",
|
||||
"reference": "Referentie: {digest}"
|
||||
},
|
||||
"notFound": {
|
||||
"code": "404",
|
||||
"title": "Pagina niet gevonden",
|
||||
"subtitle": "Deze kamer bestaat niet — of de meubels zijn verplaatst.",
|
||||
"body": "Controleer de link, of ga terug naar de lobby.",
|
||||
"backHome": "Terug naar home",
|
||||
"latestNews": "Laatste nieuws"
|
||||
},
|
||||
"staff": {
|
||||
"title": "Team",
|
||||
"subtitle": "Het team achter het hotel",
|
||||
|
||||
Reference in new issue
Block a user