// ======================================== // Async Component Pattern // ======================================== // Standard pattern for loading/error/empty states import React, { ReactNode } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Loader2, AlertCircle, Inbox, RefreshCw } from 'lucide-react'; // ======================================== // LOADING SKELETON // ======================================== export const CardSkeleton: React.FC = () => { return (
); }; export const ListItemSkeleton: React.FC = () => { return (
); }; export const TableSkeleton: React.FC<{ rows?: number }> = ({ rows = 5 }) => { return (
{Array.from({ length: rows }).map((_, i) => (
))}
); }; // ======================================== // LOADING COMPONENT // ======================================== export const LoadingState: React.FC<{ message?: string; fullScreen?: boolean; }> = ({ message = 'Loading...', fullScreen = false }) => { const content = (

{message}

); if (fullScreen) { return (
{content}
); } return (
{content}
); }; // ======================================== // ERROR STATE // ======================================== export const ErrorState: React.FC<{ message?: string; error?: Error | string; onRetry?: () => void; fullScreen?: boolean; }> = ({ message = 'An error occurred', error, onRetry, fullScreen = false, }) => { const errorMessage = typeof error === 'string' ? error : error?.message; const content = ( {message} {errorMessage && (

{errorMessage}

)} {onRetry && ( )}
); if (fullScreen) { return (
{content}
); } return
{content}
; }; // ======================================== // EMPTY STATE // ======================================== export const EmptyState: React.FC<{ message?: string; description?: string; icon?: ReactNode; action?: { label: string; onClick: () => void; }; fullScreen?: boolean; }> = ({ message = 'No data found', description, icon, action, fullScreen = false, }) => { const content = (
{icon || }

{message}

{description &&

{description}

}
{action && ( )}
); if (fullScreen) { return (
{content}
); } return
{content}
; }; // ======================================== // ASYNC COMPONENT WRAPPER // ======================================== export interface AsyncComponentProps { /** Loading state */ isLoading: boolean; /** Error object */ error?: Error | string | null; /** Data */ data?: T | null; /** Children render function */ children: (data: T) => ReactNode; /** Custom loading component */ LoadingComponent?: React.ComponentType; /** Custom error component */ ErrorComponent?: React.ComponentType<{ error: Error | string; onRetry?: () => void }>; /** Custom empty component */ EmptyComponent?: React.ComponentType; /** Retry callback */ onRetry?: () => void; /** Loading message */ loadingMessage?: string; /** Error message */ errorMessage?: string; /** Empty message */ emptyMessage?: string; /** Full screen mode */ fullScreen?: boolean; } /** * Standard async component pattern * Handles loading, error, empty, and success states * * @example * * {(courses) => } * */ export function AsyncComponent({ isLoading, error, data, children, LoadingComponent, ErrorComponent, EmptyComponent, onRetry, loadingMessage = 'Loading...', errorMessage = 'Failed to load data', emptyMessage = 'No data available', fullScreen = false, }: AsyncComponentProps): JSX.Element { // Loading state if (isLoading) { if (LoadingComponent) { return ; } return ; } // Error state if (error) { if (ErrorComponent) { return ; } return ( ); } // Empty state if (!data || (Array.isArray(data) && data.length === 0)) { if (EmptyComponent) { return ; } return ; } // Success state - render children with data return <>{children(data)}; }