Add route loading and error states

This commit is contained in:
Janpeter Visser 2026-04-19 10:22:03 +02:00
parent fe35676e8e
commit 42d88ce8d6
7 changed files with 284 additions and 0 deletions

27
app/check-in/error.tsx Normal file
View file

@ -0,0 +1,27 @@
"use client";
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
export default function CheckInError({
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<main className="flex min-h-svh items-center justify-center p-6">
<div className="w-full max-w-md space-y-4">
<Alert variant="destructive">
<AlertTitle>Er is iets misgegaan</AlertTitle>
<AlertDescription>
De check-in pagina kon niet worden geladen. Probeer het opnieuw of
kom later terug.
</AlertDescription>
</Alert>
<Button onClick={reset} variant="outline" className="w-full">
Opnieuw proberen
</Button>
</div>
</main>
);
}

65
app/check-in/loading.tsx Normal file
View file

@ -0,0 +1,65 @@
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
export default function CheckInLoading() {
return (
<main className="app-page">
<div className="mx-auto flex min-h-screen w-full max-w-6xl flex-col gap-8 px-4 sm:px-6">
<div className="flex h-16 shrink-0 items-center justify-between border-b border-border/50">
<Skeleton className="h-5 w-40" />
<div className="flex items-center gap-2">
<Skeleton className="h-7 w-7 rounded-full" />
<Skeleton className="h-7 w-7 rounded-full" />
</div>
</div>
<div className="flex-1 space-y-8">
<div className="space-y-2">
<Skeleton className="h-3 w-20" />
<Skeleton className="h-7 w-56" />
<Skeleton className="h-4 w-80 max-w-full" />
</div>
<div className="grid gap-5 lg:grid-cols-[1.1fr_0.9fr]">
<Card className="py-0">
<CardContent className="space-y-5 pb-6 pt-4">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-8 w-full" />
<Skeleton className="h-4 w-40" />
<div className="flex gap-2">
{[0, 1, 2].map((i) => (
<Skeleton key={i} className="h-10 flex-1" />
))}
</div>
<Skeleton className="h-10 w-full" />
</CardContent>
</Card>
<div className="space-y-5">
<Card className="py-0">
<CardHeader className="pb-0">
<Skeleton className="h-3 w-16" />
<Skeleton className="mt-1 h-5 w-48" />
</CardHeader>
<CardContent className="pb-6">
<Skeleton className="mt-2 h-4 w-full" />
<Skeleton className="mt-1.5 h-4 w-3/4" />
</CardContent>
</Card>
<Card className="py-0">
<CardHeader className="pb-0">
<Skeleton className="h-3 w-24" />
</CardHeader>
<CardContent className="space-y-2 pb-6">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-5/6" />
<Skeleton className="h-4 w-4/6" />
</CardContent>
</Card>
</div>
</div>
</div>
</div>
</main>
);
}

27
app/dashboard/error.tsx Normal file
View file

@ -0,0 +1,27 @@
"use client";
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
export default function DashboardError({
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<main className="flex min-h-svh items-center justify-center p-6">
<div className="w-full max-w-md space-y-4">
<Alert variant="destructive">
<AlertTitle>Er is iets misgegaan</AlertTitle>
<AlertDescription>
Het dashboard kon niet worden geladen. Probeer het opnieuw of kom
later terug.
</AlertDescription>
</Alert>
<Button onClick={reset} variant="outline" className="w-full">
Opnieuw proberen
</Button>
</div>
</main>
);
}

41
app/dashboard/loading.tsx Normal file
View file

@ -0,0 +1,41 @@
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
export default function DashboardLoading() {
return (
<main className="app-page">
<div className="mx-auto flex min-h-screen w-full max-w-6xl flex-col gap-8 px-4 sm:px-6">
<div className="flex h-16 shrink-0 items-center justify-between border-b border-border/50">
<Skeleton className="h-5 w-40" />
<div className="flex items-center gap-2">
<Skeleton className="h-7 w-7 rounded-full" />
<Skeleton className="h-7 w-7 rounded-full" />
</div>
</div>
<div className="flex-1 space-y-8">
<div className="space-y-2">
<Skeleton className="h-3 w-20" />
<Skeleton className="h-7 w-64" />
<Skeleton className="h-4 w-96 max-w-full" />
</div>
<div className="grid gap-5 md:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => (
<Card key={i} className="py-0">
<CardHeader className="pb-0">
<Skeleton className="h-3 w-14" />
<Skeleton className="mt-1 h-5 w-40" />
</CardHeader>
<CardContent className="pb-6">
<Skeleton className="mt-2 h-4 w-full" />
<Skeleton className="mt-1.5 h-4 w-3/4" />
</CardContent>
</Card>
))}
</div>
</div>
</div>
</main>
);
}

27
app/planning/error.tsx Normal file
View file

@ -0,0 +1,27 @@
"use client";
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
export default function PlanningError({
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<main className="flex min-h-svh items-center justify-center p-6">
<div className="w-full max-w-md space-y-4">
<Alert variant="destructive">
<AlertTitle>Er is iets misgegaan</AlertTitle>
<AlertDescription>
De planningpagina kon niet worden geladen. Probeer het opnieuw of
kom later terug.
</AlertDescription>
</Alert>
<Button onClick={reset} variant="outline" className="w-full">
Opnieuw proberen
</Button>
</div>
</main>
);
}

87
app/planning/loading.tsx Normal file
View file

@ -0,0 +1,87 @@
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
export default function PlanningLoading() {
return (
<main className="app-page">
<div className="mx-auto flex min-h-screen w-full max-w-6xl flex-col gap-8 px-4 sm:px-6">
<div className="flex h-16 shrink-0 items-center justify-between border-b border-border/50">
<Skeleton className="h-5 w-40" />
<div className="flex items-center gap-2">
<Skeleton className="h-7 w-7 rounded-full" />
<Skeleton className="h-7 w-7 rounded-full" />
</div>
</div>
<div className="flex-1 space-y-8">
<div className="space-y-2">
<Skeleton className="h-3 w-20" />
<Skeleton className="h-7 w-56" />
<Skeleton className="h-4 w-80 max-w-full" />
</div>
<div className="grid gap-5 lg:grid-cols-[1.1fr_0.9fr]">
<Card className="py-0">
<CardContent className="space-y-4 pb-6 pt-4">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-10 w-full" />
<div className="flex gap-2">
<Skeleton className="h-10 flex-1" />
<Skeleton className="h-10 w-24" />
</div>
<Skeleton className="h-4 w-28" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
</CardContent>
</Card>
<div className="space-y-5">
<Card className="py-0">
<CardHeader className="pb-0">
<Skeleton className="h-3 w-16" />
<Skeleton className="mt-1 h-5 w-48" />
</CardHeader>
<CardContent className="pb-6">
<Skeleton className="mt-2 h-4 w-full" />
<Skeleton className="mt-1.5 h-4 w-3/4" />
</CardContent>
</Card>
<Card className="py-0">
<CardContent className="space-y-3 pb-6 pt-4">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-5 w-full rounded-full" />
<Skeleton className="h-3 w-24" />
</CardContent>
</Card>
<Card className="py-0">
<CardHeader className="pb-0">
<Skeleton className="h-3 w-24" />
</CardHeader>
<CardContent className="space-y-2 pb-6">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-5/6" />
<Skeleton className="h-4 w-4/6" />
</CardContent>
</Card>
</div>
</div>
<div className="space-y-3">
<Skeleton className="h-5 w-48" />
{[0, 1, 2].map((i) => (
<Card key={i} className="py-0">
<CardContent className="flex items-center justify-between px-4 py-3">
<div className="space-y-1.5">
<Skeleton className="h-4 w-48" />
<Skeleton className="h-3 w-32" />
</div>
<Skeleton className="h-7 w-16" />
</CardContent>
</Card>
))}
</div>
</div>
</div>
</main>
);
}

View file

@ -0,0 +1,10 @@
import { cn } from "@/lib/utils";
export function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
}