Files
HealthLog/src/app/global-error.tsx
T
Marc-André Bombeck de0c16338f fix(auth): lift the register submit button and add a branded not-found page
Promote the register page's submit button to the 44 px tap-target
floor (size="lg" + min-h-11 + w-full) so the primary action stays
finger-tap reachable on a narrow viewport.

Add a new branded not-found.tsx page. The previous implicit fallback
was Next.js's bare framework text; this introduces a lighter wrapper
than ErrorDetails (404 is not an application error) — Logo, headline,
explanatory paragraph, and a single "Back to dashboard" link. The
wrapper carries min-h-dvh and safe-area-inset-top so the panel
anchors centred under iOS Safari's animated URL bar and stays clear
of the notch.

Apply the same dvh + safe-area pattern on the root-layout global
error boundary and lift its inline button heights to 44 px. Wrap the
ErrorDetails panel in a min-h-dvh flex column so server-error pages
also centre instead of hugging the notch.
2026-05-15 18:58:35 +02:00

118 lines
3.6 KiB
TypeScript

"use client";
/**
* Root-level error boundary. Renders when even the root layout itself fails,
* so it cannot rely on any providers (i18n, query, auth). Keep it static,
* in English, and self-contained.
*/
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
async function handleCopy() {
// Strip query string — OAuth callback routes carry sensitive tokens.
const loc = typeof window !== "undefined" ? window.location : null;
const payload = {
message: error.message,
digest: error.digest,
name: error.name,
stack: error.stack?.split("\n").slice(0, 10).join("\n"),
urlPath: loc?.pathname ?? null,
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : null,
timestamp: new Date().toISOString(),
};
const text = JSON.stringify(payload, null, 2);
try {
await navigator.clipboard.writeText(text);
} catch {
window.prompt("Copy details", text);
}
}
return (
<html lang="en">
<body
style={{
fontFamily: "system-ui, sans-serif",
backgroundColor: "#1e1f29",
color: "#f8f8f2",
margin: 0,
// v1.4.27 MB6 — `100dvh` follows the dynamic viewport on
// iOS Safari (URL-bar collapse) so the wrapper still
// anchors centred when the browser chrome animates in or
// out. `safe-area-inset-top` keeps the headline out from
// under the notch / Dynamic Island.
minHeight: "100dvh",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding:
"calc(env(safe-area-inset-top, 0px) + 24px) 24px 24px 24px",
}}
>
<div style={{ maxWidth: 560 }}>
<h1 style={{ fontSize: 20, marginBottom: 12 }}>
Something went wrong
</h1>
<p style={{ color: "#d0d0d0", marginBottom: 16, fontSize: 14 }}>
A critical error occurred. You can retry or copy the details for
support.
</p>
<pre
style={{
background: "#282a36",
padding: 12,
borderRadius: 6,
fontSize: 12,
overflow: "auto",
marginBottom: 16,
border: "1px solid #44475a",
}}
>
{error.message || "Unknown error"}
</pre>
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
<button
onClick={reset}
style={{
background: "#bd93f9",
color: "#282a36",
border: "none",
borderRadius: 6,
// v1.4.27 MB6 — 44 px minimum height matches the
// app-wide tap-target floor; the larger inline padding
// keeps the labels comfortably finger-tap-sized.
minHeight: 44,
padding: "10px 16px",
cursor: "pointer",
fontSize: 14,
fontWeight: 500,
}}
>
Retry
</button>
<button
onClick={handleCopy}
style={{
background: "transparent",
color: "#f8f8f2",
border: "1px solid #44475a",
borderRadius: 6,
minHeight: 44,
padding: "10px 16px",
cursor: "pointer",
fontSize: 14,
}}
>
Copy details
</button>
</div>
</div>
</body>
</html>
);
}