Add robust error boundaries and improved skeletons to dashboard

Introduces reusable error boundary and suspense timeout components across dashboard pages for better error handling and user feedback. Enhances loading skeletons with subtle progress indicators, animation, and slow-loading warnings. All dynamic imports now include error handling and improved fallback skeletons, and a shared DashboardContentWrapper is added for consistent dashboard content loading experience.
This commit is contained in:
g
2025-12-31 05:20:44 +00:00
parent 96638f968f
commit 0062aa2dfe
10 changed files with 1166 additions and 118 deletions

View File

@@ -8,26 +8,46 @@ import { Suspense } from 'react';
import dynamic from 'next/dynamic';
import { Skeleton } from '@/components/ui/skeleton';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
// Lazy load the Content component
const Content = dynamic(() => import("@/components/dashboard/content"), {
loading: () => <DashboardContentSkeleton />
});
import DashboardContentWrapper from './dashboard-content-wrapper';
// Loading skeleton for the dashboard content
function DashboardContentSkeleton() {
return (
<div className="space-y-6">
<div
className="space-y-6 animate-in fade-in duration-500 relative"
role="status"
aria-label="Loading dashboard content"
aria-live="polite"
>
{/* Subtle loading indicator */}
<div className="absolute top-0 left-0 right-0 h-1 bg-muted overflow-hidden rounded-full">
<div className="h-full bg-primary w-1/3"
style={{
background: 'linear-gradient(90deg, transparent, hsl(var(--primary)), transparent)',
backgroundSize: '200% 100%',
animation: 'shimmer 2s ease-in-out infinite',
}}
/>
</div>
{/* Header skeleton */}
<div>
<Skeleton className="h-8 w-64 mb-2" />
<Skeleton className="h-4 w-96" />
<Skeleton className="h-4 w-96 max-w-full" />
</div>
{/* Stats cards skeleton */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 lg:gap-6">
{[...Array(4)].map((_, i) => (
<Card key={i}>
<Card
key={i}
className="animate-in fade-in slide-in-from-bottom-4"
style={{
animationDelay: `${i * 75}ms`,
animationDuration: '400ms',
animationFillMode: 'both',
}}
>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-5 w-5 rounded" />
@@ -41,15 +61,29 @@ function DashboardContentSkeleton() {
</div>
{/* Best selling products skeleton */}
<Card>
<Card className="animate-in fade-in slide-in-from-bottom-4"
style={{
animationDelay: '300ms',
animationDuration: '400ms',
animationFillMode: 'both',
}}
>
<CardHeader>
<Skeleton className="h-6 w-48" />
<Skeleton className="h-4 w-72" />
<Skeleton className="h-4 w-72 max-w-full" />
</CardHeader>
<CardContent>
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="flex items-center gap-4">
<div
key={i}
className="flex items-center gap-4 animate-in fade-in"
style={{
animationDelay: `${400 + i * 50}ms`,
animationDuration: '300ms',
animationFillMode: 'both',
}}
>
<Skeleton className="h-12 w-12 rounded-md" />
<div className="space-y-2">
<Skeleton className="h-4 w-40" />
@@ -68,6 +102,14 @@ function DashboardContentSkeleton() {
);
}
// Lazy load the Content component with error handling
const Content = dynamic(() => import("@/components/dashboard/content").catch((err) => {
console.error("Failed to load dashboard content:", err);
throw err;
}), {
loading: () => <DashboardContentSkeleton />
});
// ✅ Corrected Vendor Type
interface Vendor {
_id: string;
@@ -108,9 +150,11 @@ export default async function DashboardPage() {
return (
<Dashboard>
<Suspense fallback={<DashboardContentSkeleton />}>
<Content username={vendor.username} orderStats={orderStats} />
</Suspense>
<DashboardContentWrapper>
<Suspense fallback={<DashboardContentSkeleton />}>
<Content username={vendor.username} orderStats={orderStats} />
</Suspense>
</DashboardContentWrapper>
<div className="fixed bottom-2 right-2 text-xs text-muted-foreground bg-background/80 backdrop-blur-sm px-2 py-1 rounded border border-border/50 z-50 flex items-center space-x-2">
<div className="flex items-center gap-1">
<Info size={12} className="text-muted-foreground/80" />