Files
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

99 lines
3.5 KiB
TypeScript

import { Skeleton } from "~/components/ui/skeleton"
import { Card, CardContent } from "~/components/ui/card"
/** Layout-preserving placeholder for a list/table surface. Renders a
* header bar + `rows` row skeletons. Looks far less alarming than a
* single spinner during slow loads and keeps the layout from jumping
* when data lands. */
export function ListSkeleton({ rows = 8 }: { rows?: number }) {
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<Skeleton className="h-9 w-72" />
<Skeleton className="h-8 w-24" />
</div>
<div className="rounded-md border">
<div className="flex items-center gap-4 border-b bg-muted/30 px-4 py-2">
<Skeleton className="h-3 w-16" />
<Skeleton className="h-3 w-24" />
<Skeleton className="h-3 w-20" />
<Skeleton className="ml-auto h-3 w-16" />
</div>
{Array.from({ length: rows }).map((_, i) => (
<div
key={i}
className="flex items-center gap-4 border-b px-4 py-3 last:border-b-0"
>
<Skeleton className="h-4 w-20" />
<Skeleton className="h-4 w-32" />
<Skeleton className="h-4 w-16" />
<Skeleton className="ml-auto h-4 w-24" />
</div>
))}
</div>
</div>
)
}
/** Layout-preserving placeholder for a dashboard/overview surface. Mirrors
* a hero KPI + secondary KPIs, a trend + categories row, a forecast strip
* and a list card so there's no visible reshuffle when data lands. Shape
* it to match whatever your overview renders. */
export function DashboardSkeleton() {
return (
<div className="space-y-6">
{/* Hero KPI row */}
<section className="grid gap-6 lg:grid-cols-3 lg:items-end">
<div className="lg:col-span-2 space-y-3">
<Skeleton className="h-3 w-32" />
<Skeleton className="h-14 w-72" />
<Skeleton className="h-8 w-full max-w-md" />
</div>
<div className="space-y-4">
<Skeleton className="h-7 w-full" />
<Skeleton className="h-7 w-full" />
<Skeleton className="h-3 w-32" />
</div>
</section>
{/* Trend + categories */}
<div className="grid gap-6 lg:grid-cols-3">
<Card className="lg:col-span-2">
<CardContent className="space-y-3 p-5 md:p-7">
<Skeleton className="h-4 w-40" />
<Skeleton className="h-3 w-64" />
<Skeleton className="h-56 w-full" />
</CardContent>
</Card>
<Card>
<CardContent className="space-y-4 p-5 md:p-7">
<Skeleton className="h-4 w-40" />
<Skeleton className="h-8 w-40" />
<Skeleton className="h-2.5 w-full rounded-full" />
<div className="space-y-2">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={i} className="h-4 w-full" />
))}
</div>
</CardContent>
</Card>
</div>
{/* Forecast */}
<Card>
<CardContent className="space-y-4 p-5 md:p-7">
<Skeleton className="h-4 w-32" />
<div className="grid gap-4 sm:grid-cols-3">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="space-y-2">
<Skeleton className="h-3 w-24" />
<Skeleton className="h-7 w-32" />
</div>
))}
</div>
</CardContent>
</Card>
</div>
)
}