- AppRouter wraps every route (except /amber-callback and the 404) in ONE
<Route element={<Layout/>}> with an <Outlet/>; the chrome now persists across
navigation instead of being torn down and rebuilt on every page.
- Layout: children optional (falls back to <Outlet/>); the map is full-bleed via
a route check (isFullBleed) instead of a per-page noPadding prop.
- All 59 pages + CraftProfileForm/ErrorState/NewsletterIssueView now render
fragments instead of <Layout> (structure-preserving), imports dropped.
Removes the whole remount class: chrome no longer re-runs its hooks per
navigation, Sasquatch chat state survives navigation, and the header can't
re-open Amber on every page change.
54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
import { AlertTriangle, RefreshCw } from 'lucide-react';
|
||
import { Card, CardContent } from '@/components/ui/card';
|
||
import { Button } from '@/components/ui/button';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
/**
|
||
* Shared failure state for data-driven pages.
|
||
*
|
||
* Previously these pages rendered "404 Not Found" (or an empty state) when a
|
||
* relay query failed, which told users the content had been deleted — false and
|
||
* unactionable. This keeps the failure honest and gives a retry path.
|
||
*/
|
||
export function ErrorState({
|
||
title = 'Couldn’t load this',
|
||
description = 'The network request failed. Check your connection and try again.',
|
||
onRetry,
|
||
className,
|
||
}: {
|
||
title?: string;
|
||
description?: string;
|
||
onRetry?: () => void;
|
||
className?: string;
|
||
}) {
|
||
return (
|
||
<Card className={cn('border-2 border-dashed border-red-500/30 bg-black/50', className)}>
|
||
<CardContent className="py-12 text-center">
|
||
<AlertTriangle className="mx-auto mb-4 h-12 w-12 text-red-500/60" />
|
||
<p className="mb-1 font-bold uppercase tracking-wide text-white">{title}</p>
|
||
<p className="mx-auto mb-5 max-w-sm text-sm text-gray-400">{description}</p>
|
||
{onRetry && (
|
||
<Button onClick={onRetry} className="gap-2 bg-white/10 font-bold uppercase hover:bg-white/20">
|
||
<RefreshCw className="h-4 w-4" />
|
||
Try again
|
||
</Button>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
/** Full-page failure state (with app chrome) for route-level data errors. */
|
||
export function PageError(props: {
|
||
title?: string;
|
||
description?: string;
|
||
onRetry?: () => void;
|
||
}) {
|
||
return (
|
||
<>
|
||
<div className="container mx-auto max-w-4xl px-4 py-8">
|
||
<ErrorState {...props} />
|
||
</div>
|
||
</>
|
||
);
|
||
}
|