// ========================================
// 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 = (
);
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)}>;
}