Files
gridpilot.gg/apps/website/components/shared/state/PageWrapper.tsx
2026-01-24 12:47:49 +01:00

244 lines
5.4 KiB
TypeScript

import { ErrorDisplay } from '@/components/shared/ErrorDisplay';
import { ApiError } from '@/lib/gateways/api/base/ApiError';
import { EmptyState } from '@/ui/EmptyState';
import { LoadingWrapper } from '@/ui/LoadingWrapper';
import { Inbox, List, LucideIcon } from 'lucide-react';
import React, { ReactNode } from 'react';
// ==================== PAGEWRAPPER TYPES ====================
export interface PageWrapperLoadingConfig {
variant?: 'skeleton' | 'full-screen';
message?: string;
}
export interface PageWrapperErrorConfig {
variant?: 'full-screen' | 'card';
card?: {
title?: string;
description?: string;
};
}
export interface PageWrapperEmptyConfig {
icon?: LucideIcon;
title?: string;
description?: string;
action?: {
label: string;
onClick: () => void;
};
}
export interface PageWrapperProps<TData> {
/** Data to be rendered */
data: TData | undefined;
/** Loading state (default: false) */
isLoading?: boolean;
/** Error state (default: null) */
error?: Error | null;
/** Retry function for errors */
retry?: () => void;
/** Template component that receives the data */
Template: React.ComponentType<{ viewData: TData }>;
/** Loading configuration */
loading?: PageWrapperLoadingConfig;
/** Error configuration */
errorConfig?: PageWrapperErrorConfig;
/** Empty configuration */
empty?: PageWrapperEmptyConfig;
/** Children for flexible content rendering */
children?: ReactNode;
}
/**
* PageWrapper Component
*
* A comprehensive wrapper component that handles all page states:
* - Loading states (skeleton or full-screen)
* - Error states (full-screen or card)
* - Empty states (with icon, title, description, and action)
* - Success state (renders Template component with data)
* - Flexible children support for custom content
*/
export function PageWrapper<TData>({
data,
isLoading = false,
error = null,
retry,
Template,
loading,
errorConfig,
empty,
children,
}: PageWrapperProps<TData>) {
// Priority order: Loading > Error > Empty > Success
// 1. Loading State
if (isLoading) {
const loadingVariant = loading?.variant || 'skeleton';
const loadingMessage = loading?.message || 'Loading...';
if (loadingVariant === 'full-screen') {
return (
<LoadingWrapper
variant="full-screen"
message={loadingMessage}
/>
);
}
// Default to skeleton
return (
<React.Fragment>
<LoadingWrapper
variant="skeleton"
message={loadingMessage}
skeletonCount={3}
/>
{children}
</React.Fragment>
);
}
// 2. Error State
if (error) {
const errorVariant = errorConfig?.variant || 'full-screen';
if (errorVariant === 'card') {
return (
<React.Fragment>
<ErrorDisplay
error={error as ApiError}
onRetry={retry}
variant="card"
/>
{children}
</React.Fragment>
);
}
// Default to full-screen
return (
<ErrorDisplay
error={error as ApiError}
onRetry={retry}
variant="full-screen"
/>
);
}
// 3. Empty State
if (!data || (Array.isArray(data) && data.length === 0)) {
if (empty) {
const Icon = empty.icon;
const hasAction = empty.action && retry;
return (
<React.Fragment>
<EmptyState
icon={Icon || Inbox}
title={empty.title || 'No data available'}
description={empty.description}
action={hasAction ? {
label: empty.action!.label,
onClick: empty.action!.onClick,
} : undefined}
variant="default"
/>
{children}
</React.Fragment>
);
}
// If no empty config provided but data is empty, show nothing
return (
<React.Fragment>
{children}
</React.Fragment>
);
}
// 4. Success State - Render Template with data
return (
<React.Fragment>
<Template viewData={data} />
{children}
</React.Fragment>
);
}
/**
* Convenience component for list data with automatic empty state handling
*/
export function ListPageWrapper<TData extends unknown[]>({
data,
isLoading = false,
error = null,
retry,
Template,
loading,
errorConfig,
empty,
children,
}: PageWrapperProps<TData>) {
const listEmpty = empty || {
icon: List,
title: 'No items found',
description: 'This list is currently empty',
};
return (
<PageWrapper
data={data}
isLoading={isLoading}
error={error}
retry={retry}
Template={Template}
loading={loading}
errorConfig={errorConfig}
empty={listEmpty}
>
{children}
</PageWrapper>
);
}
/**
* Convenience component for detail pages with enhanced error handling
*/
export function DetailPageWrapper<TData>({
data,
isLoading = false,
error = null,
retry,
Template,
loading,
errorConfig,
empty,
children,
}: PageWrapperProps<TData> & {
onBack?: () => void;
onRefresh?: () => void;
}) {
// Create enhanced error config with additional actions
const enhancedErrorConfig: PageWrapperErrorConfig = {
...errorConfig,
};
return (
<PageWrapper
data={data}
isLoading={isLoading}
error={error}
retry={retry}
Template={Template}
loading={loading}
errorConfig={enhancedErrorConfig}
empty={empty}
>
{children}
</PageWrapper>
);
}