Files
crema-app-aifirst-template/app/root.tsx
jules 675f6f8b35 fix+upstream: hooks-order crash, ConfirmDialog/PageHeader/skeletons, styled error boundary
Hoists hooks above early return (render-time Navigate); ports finance's
ConfirmDialog/PageHeader/loading/states + APC error-copy mapper; removes
window.confirm, dead appbar search, and seeded fake notifications; wires toasts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 13:46:12 +10:00

102 lines
3.9 KiB
TypeScript

import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
isRouteErrorResponse,
} from "react-router"
import { AlertTriangle, Home, RotateCcw } from "lucide-react"
import type { Route } from "./+types/root"
import "./app.css"
import { humanErrorMessage, humanErrorTitle } from "~/lib/errors"
import { Button, buttonVariants } from "~/components/ui/button"
import { ToastProvider } from "@crema/notification-ui"
import { CommandBusProvider } from "@crema/action-bus"
// CREMA:PROVIDERS-IMPORTS
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
<script
dangerouslySetInnerHTML={{
__html: `(function(){try{var t=localStorage.getItem('crema-theme');if(!t)t=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';if(t==='dark')document.documentElement.classList.add('dark');var f=localStorage.getItem('crema-font-scale');if(f&&/^(sm|md|lg|xl)$/.test(f))document.documentElement.dataset.fontScale=f;var b=localStorage.getItem('crema-bg');if(b&&/^(pearl|linen|mist|dawn|seafoam|aurora|sunset|meadow|midnight|blush|noir)$/.test(b)){document.addEventListener('DOMContentLoaded',function(){document.body.dataset.bg=b;});}var s=localStorage.getItem('crema-surface');if(s&&/^(snow|stone|sage|slate)$/.test(s)){document.addEventListener('DOMContentLoaded',function(){document.body.dataset.surface=s;});}}catch(e){}})();`,
}}
/>
</head>
<body suppressHydrationWarning>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}
export default function App() {
return (
/* CREMA:PROVIDERS-WRAP-OPEN */
<ToastProvider>
<CommandBusProvider>
<Outlet />
</CommandBusProvider>
</ToastProvider>
/* CREMA:PROVIDERS-WRAP-CLOSE */
)
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
const title = humanErrorTitle(error)
const message = humanErrorMessage(error)
const is404 = isRouteErrorResponse(error) && error.status === 404
// Stack traces are a developer affordance — never leak them to users in
// production, and even in dev keep them tucked below the human copy.
const stack =
import.meta.env.DEV && error instanceof Error ? error.stack : undefined
return (
<main className="flex min-h-svh items-center justify-center bg-background p-6">
<div className="w-full max-w-md rounded-2xl border bg-card p-8 text-center shadow-e1">
<div className="mx-auto mb-5 flex size-12 items-center justify-center rounded-2xl bg-destructive/10 text-destructive">
<AlertTriangle className="size-6" />
</div>
<h1 className="text-title font-semibold tracking-tight text-card-foreground">
{is404 ? "Page not found" : title}
</h1>
<p className="mx-auto mt-2 max-w-sm text-[15px] leading-relaxed text-muted-foreground">
{message}
</p>
<div className="mt-6 flex flex-wrap items-center justify-center gap-2">
<a
href="/"
data-action="error-go-home"
className={buttonVariants({ variant: "default" })}
>
<Home className="size-4" /> Go home
</a>
<Button
data-action="error-retry"
variant="outline"
onClick={() => window.location.reload()}
>
<RotateCcw className="size-4" /> Try again
</Button>
</div>
{stack ? (
<pre className="mt-6 max-h-64 overflow-auto rounded-lg border bg-muted/40 p-4 text-left font-mono text-xs leading-relaxed text-muted-foreground">
<code>{stack}</code>
</pre>
) : null}
</div>
</main>
)
}