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

@@ -14,32 +14,44 @@ import dynamic from "next/dynamic";
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
// Lazy load heavy components
const ProductTable = dynamic(() => import("@/components/tables/product-table"), {
// Lazy load heavy components with error handling
const ProductTable = dynamic(() => import("@/components/tables/product-table").catch((err) => {
console.error("Failed to load ProductTable:", err);
throw err;
}), {
loading: () => <ProductTableSkeleton />
});
const ProductModal = dynamic(() => import("@/components/modals/product-modal").then(mod => ({ default: mod.ProductModal })), {
loading: () => <div>Loading...</div>
const ProductModal = dynamic(() => import("@/components/modals/product-modal").then(mod => ({ default: mod.ProductModal })).catch((err) => {
console.error("Failed to load ProductModal:", err);
throw err;
}), {
loading: () => <ModalSkeleton />
});
const ImportProductsModal = dynamic(() => import("@/components/modals/import-products-modal"), {
loading: () => <div>Loading...</div>
const ImportProductsModal = dynamic(() => import("@/components/modals/import-products-modal").catch((err) => {
console.error("Failed to load ImportProductsModal:", err);
throw err;
}), {
loading: () => <ModalSkeleton />
});
const ProfitAnalysisModal = dynamic(() => import("@/components/modals/profit-analysis-modal").then(mod => ({ default: mod.ProfitAnalysisModal })), {
loading: () => <div>Loading...</div>
const ProfitAnalysisModal = dynamic(() => import("@/components/modals/profit-analysis-modal").then(mod => ({ default: mod.ProfitAnalysisModal })).catch((err) => {
console.error("Failed to load ProfitAnalysisModal:", err);
throw err;
}), {
loading: () => <ModalSkeleton />
});
function ProductTableSkeleton() {
return (
<Card>
<Card className="animate-in fade-in duration-500">
<CardHeader>
<div className="flex items-center justify-between">
<Skeleton className="h-6 w-32" />
<div className="flex gap-2">
<Skeleton className="h-9 w-24" />
<Skeleton className="h-9 w-32" />
<Skeleton className="h-9 w-24 rounded-md" />
<Skeleton className="h-9 w-32 rounded-md" />
</div>
</div>
</CardHeader>
@@ -48,13 +60,29 @@ function ProductTableSkeleton() {
<div className="border-b p-4">
<div className="flex items-center gap-4">
{['Product', 'Category', 'Price', 'Stock', 'Status', 'Actions'].map((header, i) => (
<Skeleton key={i} className="h-4 w-20 flex-1" />
<Skeleton
key={i}
className="h-4 w-20 flex-1 animate-in fade-in"
style={{
animationDelay: `${i * 50}ms`,
animationDuration: '300ms',
animationFillMode: 'both',
}}
/>
))}
</div>
</div>
{[...Array(8)].map((_, i) => (
<div key={i} className="border-b last:border-b-0 p-4">
<div
key={i}
className="border-b last:border-b-0 p-4 animate-in fade-in"
style={{
animationDelay: `${300 + i * 50}ms`,
animationDuration: '300ms',
animationFillMode: 'both',
}}
>
<div className="flex items-center gap-4">
<div className="flex items-center gap-3 flex-1">
<Skeleton className="h-12 w-12 rounded-md" />
@@ -66,10 +94,10 @@ function ProductTableSkeleton() {
<Skeleton className="h-4 w-24 flex-1" />
<Skeleton className="h-4 w-16 flex-1" />
<Skeleton className="h-4 w-16 flex-1" />
<Skeleton className="h-6 w-20 flex-1" />
<Skeleton className="h-6 w-20 flex-1 rounded-full" />
<div className="flex gap-2 flex-1">
<Skeleton className="h-8 w-16" />
<Skeleton className="h-8 w-16" />
<Skeleton className="h-8 w-16 rounded-md" />
<Skeleton className="h-8 w-16 rounded-md" />
</div>
</div>
</div>
@@ -80,6 +108,29 @@ function ProductTableSkeleton() {
);
}
function ModalSkeleton() {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
<Card className="w-full max-w-2xl m-4 animate-in fade-in zoom-in-95 duration-300">
<CardHeader>
<Skeleton className="h-6 w-48" />
<Skeleton className="h-4 w-64 mt-2" />
</CardHeader>
<CardContent>
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="space-y-2">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-10 w-full rounded-md" />
</div>
))}
</div>
</CardContent>
</Card>
</div>
);
}
export default function ProductsPage() {
const router = useRouter();
const [products, setProducts] = useState<Product[]>([]);