# Streamlined Error and Load State Handling Design

## Overview

This document outlines a comprehensive design for standardizing error and loading state handling across all pages in the GridPilot website app using shared, user-friendly components.

## Problem Statement

The current implementation has several inconsistencies:
- Mixed state management approaches (hook-based vs manual useState)
- Inconsistent naming (loading vs isLoading vs pending)
- Different error UIs (full-screen, inline, modal, toast)
- Various loading UIs (spinners, skeletons, text-only, full-screen)
- No shared useDataFetching() hook
- Error boundaries not used everywhere

## Solution Architecture

### 1. Unified State Management Pattern

All pages will follow this consistent pattern:

```typescript
// Standardized state interface
interface PageState<T> {
  data: T | null;
  isLoading: boolean;
  error: ApiError | null;
  retry: () => Promise<void>;
}

// Standardized hook signature
interface UseDataFetchingOptions<T> {
  queryKey: string[];
  queryFn: () => Promise<T>;
  enabled?: boolean;
  retryOnMount?: boolean;
}

// Standardized return type
interface UseDataFetchingResult<T> {
  data: T | null;
  isLoading: boolean;
  isFetching: boolean;
  error: ApiError | null;
  retry: () => Promise<void>;
  refetch: () => Promise<void>;
}
```

### 2. Component Architecture

```
apps/website/components/shared/
├── state/
│   ├── LoadingWrapper.tsx          # Main loading component with variants
│   ├── ErrorDisplay.tsx            # Standardized error display
│   ├── EmptyState.tsx              # Enhanced empty state
│   └── StateContainer.tsx          # Combined wrapper for all states
├── hooks/
│   └── useDataFetching.ts          # Unified data fetching hook
└── types/
    └── state.types.ts              # TypeScript interfaces
```

### 3. Shared Component Design

#### LoadingWrapper Component

**Purpose**: Provides consistent loading states with multiple variants

**Variants**:
- `spinner` - Traditional loading spinner (default)
- `skeleton` - Skeleton screens for better UX
- `full-screen` - Centered in viewport
- `inline` - Compact inline loading
- `card` - Loading card placeholders

**Props Interface**:
```typescript
interface LoadingWrapperProps {
  variant?: 'spinner' | 'skeleton' | 'full-screen' | 'inline' | 'card';
  message?: string;
  className?: string;
  size?: 'sm' | 'md' | 'lg';
  /**
   * For skeleton variant - number of skeleton items to show
   */
  skeletonCount?: number;
  /**
   * For card variant - card layout configuration
   */
  cardConfig?: {
    height?: number;
    count?: number;
    className?: string;
  };
}
```

**Usage Examples**:
```typescript
// Default spinner
<LoadingWrapper />

// Full-screen with custom message
<LoadingWrapper 
  variant="full-screen" 
  message="Loading race details..." 
/>

// Skeleton for list
<LoadingWrapper 
  variant="skeleton" 
  skeletonCount={5} 
/>

// Inline loading
<LoadingWrapper 
  variant="inline" 
  size="sm" 
  message="Saving..." 
/>
```

#### ErrorDisplay Component

**Purpose**: Standardized error display with consistent behavior

**Props Interface**:
```typescript
interface ErrorDisplayProps {
  error: ApiError;
  onRetry?: () => void;
  variant?: 'full-screen' | 'inline' | 'card' | 'toast';
  /**
   * Show retry button (auto-detected from error.isRetryable())
   */
  showRetry?: boolean;
  /**
   * Show navigation buttons
   */
  showNavigation?: boolean;
  /**
   * Additional actions
   */
  actions?: Array<{
    label: string;
    onClick: () => void;
    variant?: 'primary' | 'secondary' | 'danger';
  }>;
  className?: string;
  /**
   * Hide technical details in development
   */
  hideTechnicalDetails?: boolean;
}
```

**Features**:
- Auto-detects retryable errors
- Shows user-friendly messages
- Provides navigation options (back, home)
- Displays technical details in development
- Supports custom actions
- Accessible (ARIA labels, keyboard navigation)

**Usage Examples**:
```typescript
// Full-screen error
<ErrorDisplay 
  error={error} 
  onRetry={retry} 
  variant="full-screen" 
/>

// Inline error with custom actions
<ErrorDisplay 
  error={error}
  variant="inline"
  actions={[
    { label: 'Contact Support', onClick: () => router.push('/support') }
  ]}
/>

// Toast-style error
<ErrorDisplay 
  error={error}
  variant="toast"
  showNavigation={false}
/>
```

#### EmptyState Component

**Purpose**: Consistent empty/placeholder states

**Props Interface**:
```typescript
interface EmptyStateProps {
  icon: LucideIcon;
  title: string;
  description?: string;
  action?: {
    label: string;
    onClick: () => void;
    icon?: LucideIcon;
  };
  variant?: 'default' | 'minimal' | 'full-page';
  className?: string;
  /**
   * Show illustration instead of icon
   */
  illustration?: 'racing' | 'league' | 'team' | 'sponsor';
}
```

**Usage Examples**:
```typescript
// No races
<EmptyState
  icon={Calendar}
  title="No upcoming races"
  description="Join a league to see races here"
  action={{
    label: "Browse Leagues",
    onClick: () => router.push('/leagues')
  }}
/>

// No data with illustration
<EmptyState
  illustration="racing"
  title="No results found"
  description="Try adjusting your filters"
  variant="minimal"
/>
```

### 4. useDataFetching Hook

**Purpose**: Unified data fetching with built-in state management

**Signature**:
```typescript
function useDataFetching<T>(
  options: UseDataFetchingOptions<T>
): UseDataFetchingResult<T>
```

**Implementation Features**:
- Built-in retry logic
- Error classification
- Loading state management
- Refetch capability
- Integration with error boundaries
- Automatic retry on mount for recoverable errors

**Usage Example**:
```typescript
// Dashboard page
function DashboardPage() {
  const { 
    data, 
    isLoading, 
    error, 
    retry,
    refetch 
  } = useDataFetching({
    queryKey: ['dashboardOverview'],
    queryFn: () => dashboardService.getDashboardOverview(),
    retryOnMount: true,
  });

  if (isLoading) {
    return <LoadingWrapper variant="full-screen" />;
  }

  if (error) {
    return (
      <ErrorDisplay 
        error={error} 
        onRetry={retry}
        variant="full-screen"
      />
    );
  }

  if (!data) {
    return (
      <EmptyState
        icon={Activity}
        title="No dashboard data"
        description="Try refreshing the page"
        action={{ label: "Refresh", onClick: refetch }}
      />
    );
  }

  return <DashboardContent data={data} />;
}
```

### 5. StateContainer Component

**Purpose**: Combined wrapper that handles all states automatically

**Props Interface**:
```typescript
interface StateContainerProps<T> {
  data: T | null;
  isLoading: boolean;
  error: ApiError | null;
  retry: () => Promise<void>;
  children: (data: T) => React.ReactNode;
  /**
   * Configuration for each state
   */
  config?: {
    loading?: {
      variant?: LoadingWrapperProps['variant'];
      message?: string;
    };
    error?: {
      variant?: ErrorDisplayProps['variant'];
      actions?: ErrorDisplayProps['actions'];
    };
    empty?: {
      icon: LucideIcon;
      title: string;
      description?: string;
      action?: EmptyStateProps['action'];
    };
  };
  className?: string;
}
```

**Usage Example**:
```typescript
// Simplified page implementation
function LeagueDetailPage() {
  const { data, isLoading, error, retry } = useDataFetching({
    queryKey: ['league', leagueId],
    queryFn: () => leagueService.getLeague(leagueId),
  });

  return (
    <StateContainer
      data={data}
      isLoading={isLoading}
      error={error}
      retry={retry}
      config={{
        loading: { variant: 'skeleton', message: 'Loading league...' },
        error: { variant: 'full-screen' },
        empty: {
          icon: Trophy,
          title: 'League not found',
          description: 'The league may have been deleted',
          action: { label: 'Back to Leagues', onClick: () => router.push('/leagues') }
        }
      }}
    >
      {(leagueData) => <LeagueDetailContent league={leagueData} />}
    </StateContainer>
  );
}
```

## 6. Error Boundary Integration

### EnhancedErrorBoundary Updates

The existing `EnhancedErrorBoundary` will be updated to:
1. Support the new standardized error display
2. Provide automatic retry functionality
3. Support custom fallback components
4. Integrate with the notification system

```typescript
interface EnhancedErrorBoundaryProps {
  children: ReactNode;
  fallback?: ReactNode;
  onError?: (error: Error, errorInfo: ErrorInfo) => void;
  onReset?: () => void;
  enableDevOverlay?: boolean;
  context?: Record<string, unknown>;
  /**
   * NEW: Auto-retry configuration
   */
  autoRetry?: {
    enabled: boolean;
    maxAttempts?: number;
    delay?: number;
  };
  /**
   * NEW: Error display variant
   */
  errorVariant?: ErrorDisplayProps['variant'];
}
```

### ApiErrorBoundary Updates

The `ApiErrorBoundary` will be enhanced to:
1. Handle both API errors and regular errors
2. Support retry functionality
3. Provide consistent UI across all API calls

## 7. Migration Strategy

### Phase 1: Foundation (Week 1)
1. Create new shared components in `components/shared/state/`
2. Create `useDataFetching` hook
3. Update TypeScript interfaces
4. Add comprehensive tests

### Phase 2: Core Pages (Week 2-3)
Update pages in priority order:

**High Priority**:
- `app/dashboard/page.tsx`
- `app/leagues/[id]/LeagueDetailInteractive.tsx`
- `app/races/[id]/RaceDetailInteractive.tsx`

**Medium Priority**:
- `app/teams/[id]/TeamDetailInteractive.tsx`
- `app/leagues/[id]/schedule/page.tsx`
- `app/races/[id]/results/RaceResultsInteractive.tsx`
- `app/races/[id]/stewarding/RaceStewardingInteractive.tsx`

**Lower Priority**:
- Sponsor pages
- Profile pages
- Other interactive components

### Phase 3: Rollout (Week 4)
1. Update remaining pages
2. Add migration codemods for automation
3. Update documentation
4. Team training

### Phase 4: Deprecation (Week 5-6)
1. Remove old components
2. Clean up legacy code
3. Performance optimization

## 8. Migration Examples

### Before (Current State)
```typescript
// Dashboard page - inconsistent patterns
function DashboardPage() {
  const { data: dashboardData, isLoading, error } = useDashboardOverview();

  if (isLoading) {
    return (
      <main className="min-h-screen bg-deep-graphite flex items-center justify-center">
        <div className="text-white">Loading dashboard...</div>
      </main>
    );
  }

  if (error || !dashboardData) {
    return (
      <main className="min-h-screen bg-deep-graphite flex items-center justify-center">
        <div className="text-red-400">Failed to load dashboard</div>
      </main>
    );
  }

  // ... render content
}
```

### After (Standardized)
```typescript
// Dashboard page - unified pattern
function DashboardPage() {
  const { data, isLoading, error, retry } = useDataFetching({
    queryKey: ['dashboardOverview'],
    queryFn: () => dashboardService.getDashboardOverview(),
  });

  if (isLoading) {
    return <LoadingWrapper variant="full-screen" message="Loading dashboard..." />;
  }

  if (error) {
    return <ErrorDisplay error={error} onRetry={retry} variant="full-screen" />;
  }

  if (!data) {
    return (
      <EmptyState
        icon={Activity}
        title="No dashboard data"
        description="Try refreshing the page"
        action={{ label: "Refresh", onClick: retry }}
      />
    );
  }

  return <DashboardContent data={data} />;
}
```

### Before (League Detail - Manual State)
```typescript
function LeagueDetailInteractive() {
  const [viewModel, setViewModel] = useState<LeagueDetailPageViewModel | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const loadLeagueData = async () => {
    try {
      const viewModelData = await leagueService.getLeagueDetailPageData(leagueId);
      if (!viewModelData) {
        setError('League not found');
        setLoading(false);
        return;
      }
      setViewModel(viewModelData);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to load league');
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    loadLeagueData();
  }, [leagueId]);

  if (loading) {
    return <div className="text-center text-gray-400">Loading league...</div>;
  }

  if (error || !viewModel) {
    return <div className="text-center text-warning-amber">{error || 'League not found'}</div>;
  }

  // ... render content
}
```

### After (League Detail - Standardized)
```typescript
function LeagueDetailInteractive() {
  const params = useParams();
  const leagueId = params.id as string;

  const { data: viewModel, isLoading, error, retry } = useDataFetching({
    queryKey: ['leagueDetailPage', leagueId],
    queryFn: () => leagueService.getLeagueDetailPageData(leagueId),
  });

  return (
    <StateContainer
      data={viewModel}
      isLoading={isLoading}
      error={error}
      retry={retry}
      config={{
        loading: { variant: 'skeleton', message: 'Loading league...' },
        error: { variant: 'full-screen' },
        empty: {
          icon: Trophy,
          title: 'League not found',
          description: 'The league may have been deleted or you may not have access',
          action: { label: 'Back to Leagues', onClick: () => router.push('/leagues') }
        }
      }}
    >
      {(leagueData) => <LeagueDetailTemplate viewModel={leagueData} leagueId={leagueId} />}
    </StateContainer>
  );
}
```

## 9. Testing Strategy

### Component Tests
```typescript
// LoadingWrapper tests
describe('LoadingWrapper', () => {
  it('renders spinner by default', () => { /* ... */ });
  it('renders skeleton variant', () => { /* ... */ });
  it('shows custom message', () => { /* ... */ });
  it('is accessible', () => { /* ... */ });
});

// ErrorDisplay tests
describe('ErrorDisplay', () => {
  it('shows retry button for retryable errors', () => { /* ... */ });
  it('hides retry for non-retryable errors', () => { /* ... */ });
  it('provides navigation options', () => { /* ... */ });
  it('shows technical details in dev mode', () => { /* ... */ });
});

// useDataFetching tests
describe('useDataFetching', () => {
  it('manages loading state correctly', () => { /* ... */ });
  it('handles errors properly', () => { /* ... */ });
  it('provides retry functionality', () => { /* ... */ });
  it('integrates with error boundaries', () => { /* ... */ });
});
```

### Integration Tests
```typescript
describe('Page State Handling', () => {
  it('dashboard shows loading, then content', async () => { /* ... */ });
  it('league page handles not found', async () => { /* ... */ });
  it('race page handles network errors', async () => { /* ... */ });
});
```

## 10. Accessibility Considerations

All components will be fully accessible:
- ARIA labels for loading states
- Keyboard navigation support
- Screen reader announcements
- High contrast support
- Focus management

## 11. Performance Considerations

- Lazy loading for heavy components
- Memoization where appropriate
- Optimistic updates support
- Debounced retry logic
- Minimal re-renders

## 12. Documentation and Training

### Developer Documentation
- Component API documentation
- Migration guides
- Best practices
- Troubleshooting guide

### Team Training
- Workshop on new patterns
- Code review guidelines
- Migration walkthroughs

## 13. TypeScript Interfaces Reference

All TypeScript interfaces are defined in `components/shared/state/types.ts`. Here's a comprehensive reference:

### Core State Interfaces

```typescript
// Basic state for any data fetching
interface PageState<T> {
  data: T | null;
  isLoading: boolean;
  error: ApiError | null;
  retry: () => Promise<void>;
}

// Extended state with metadata
interface PageStateWithMeta<T> extends PageState<T> {
  isFetching: boolean;
  refetch: () => Promise<void>;
  lastUpdated: Date | null;
  isStale: boolean;
}
```

### Hook Interfaces

```typescript
// useDataFetching options
interface UseDataFetchingOptions<T> {
  queryKey: string[];
  queryFn: () => Promise<T>;
  enabled?: boolean;
  retryOnMount?: boolean;
  cacheTime?: number;
  staleTime?: number;
  maxRetries?: number;
  retryDelay?: number;
  onSuccess?: (data: T) => void;
  onError?: (error: ApiError) => void;
}

// useDataFetching result
interface UseDataFetchingResult<T> {
  data: T | null;
  isLoading: boolean;
  isFetching: boolean;
  error: ApiError | null;
  retry: () => Promise<void>;
  refetch: () => Promise<void>;
  lastUpdated: Date | null;
  isStale: boolean;
}
```

### Component Props

```typescript
// LoadingWrapper
interface LoadingWrapperProps {
  variant?: 'spinner' | 'skeleton' | 'full-screen' | 'inline' | 'card';
  message?: string;
  className?: string;
  size?: 'sm' | 'md' | 'lg';
  skeletonCount?: number;
  cardConfig?: {
    height?: number;
    count?: number;
    className?: string;
  };
  ariaLabel?: string;
}

// ErrorDisplay
interface ErrorDisplayProps {
  error: ApiError;
  onRetry?: () => void;
  variant?: 'full-screen' | 'inline' | 'card' | 'toast';
  showRetry?: boolean;
  showNavigation?: boolean;
  actions?: ErrorAction[];
  className?: string;
  hideTechnicalDetails?: boolean;
  ariaLabel?: string;
}

interface ErrorAction {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
  icon?: LucideIcon;
  disabled?: boolean;
}

// EmptyState
interface EmptyStateProps {
  icon: LucideIcon;
  title: string;
  description?: string;
  action?: {
    label: string;
    onClick: () => void;
    icon?: LucideIcon;
    variant?: 'primary' | 'secondary';
  };
  variant?: 'default' | 'minimal' | 'full-page';
  className?: string;
  illustration?: 'racing' | 'league' | 'team' | 'sponsor' | 'driver';
  ariaLabel?: string;
}

// StateContainer
interface StateContainerProps<T> {
  data: T | null;
  isLoading: boolean;
  error: ApiError | null;
  retry: () => Promise<void>;
  children: (data: T) => ReactNode;
  config?: StateContainerConfig<T>;
  className?: string;
  showEmpty?: boolean;
  isEmpty?: (data: T) => boolean;
}

interface StateContainerConfig<T> {
  loading?: {
    variant?: LoadingVariant;
    message?: string;
    size?: 'sm' | 'md' | 'lg';
  };
  error?: {
    variant?: ErrorVariant;
    actions?: ErrorAction[];
    showRetry?: boolean;
    showNavigation?: boolean;
  };
  empty?: {
    icon: LucideIcon;
    title: string;
    description?: string;
    action?: {
      label: string;
      onClick: () => void;
    };
  };
  customRender?: {
    loading?: () => ReactNode;
    error?: (error: ApiError) => ReactNode;
    empty?: () => ReactNode;
  };
}
```

### Page Template Interfaces

```typescript
// Generic page template
interface PageTemplateProps<T> {
  data: T | null;
  isLoading: boolean;
  error: ApiError | null;
  retry: () => Promise<void>;
  refetch: () => Promise<void>;
  title?: string;
  description?: string;
  children: (data: T) => ReactNode;
  config?: StateContainerConfig<T>;
}

// List page template
interface ListPageTemplateProps<T> extends PageTemplateProps<T[]> {
  emptyConfig?: {
    icon: LucideIcon;
    title: string;
    description?: string;
    action?: {
      label: string;
      onClick: () => void;
    };
  };
  showSkeleton?: boolean;
  skeletonCount?: number;
}

// Detail page template
interface DetailPageTemplateProps<T> extends PageTemplateProps<T> {
  onBack?: () => void;
  onRefresh?: () => void;
}
```

### Configuration Interfaces

```typescript
// Retry configuration
interface RetryConfig {
  maxAttempts?: number;
  baseDelay?: number;
  backoffMultiplier?: number;
  retryOnMount?: boolean;
}

// Notification configuration
interface NotificationConfig {
  showToastOnSuccess?: boolean;
  showToastOnError?: boolean;
  successMessage?: string;
  errorMessage?: string;
  autoDismissDelay?: number;
}

// Analytics configuration
interface StateAnalytics {
  onStateChange?: (from: string, to: string, data?: unknown) => void;
  onError?: (error: ApiError, context: string) => void;
  onRetry?: (attempt: number, maxAttempts: number) => void;
}

// Performance metrics
interface PerformanceMetrics {
  timeToFirstRender?: number;
  timeToDataLoad?: number;
  retryCount?: number;
  cacheHit?: boolean;
}

// Advanced configuration
interface AdvancedStateConfig<T> extends StateContainerConfig<T> {
  retry?: RetryConfig;
  notifications?: NotificationConfig;
  analytics?: StateAnalytics;
  performance?: PerformanceMetrics;
}
```

### Type Aliases

```typescript
type LoadingVariant = 'spinner' | 'skeleton' | 'full-screen' | 'inline' | 'card';
type ErrorVariant = 'full-screen' | 'inline' | 'card' | 'toast';
type EmptyVariant = 'default' | 'minimal' | 'full-page';
type EmptyCheck<T> = (data: T | null) => boolean;
```

### Default Configuration

```typescript
const DEFAULT_CONFIG = {
  loading: {
    variant: 'spinner',
    message: 'Loading...',
    size: 'md',
  },
  error: {
    variant: 'full-screen',
    showRetry: true,
    showNavigation: true,
  },
  empty: {
    title: 'No data available',
    description: 'There is nothing to display here',
  },
  retry: {
    maxAttempts: 3,
    baseDelay: 1000,
    backoffMultiplier: 2,
    retryOnMount: true,
  },
  notifications: {
    showToastOnSuccess: false,
    showToastOnError: true,
    autoDismissDelay: 5000,
  },
} as const;
```

## 14. Success Metrics

- 100% of pages use standardized components
- Reduced error handling code by 60%
- Consistent user experience across all pages
- Improved error recovery rates
- Faster development velocity

## 15. Rollback Plan

If issues arise:
1. Feature flags to toggle new components
2. Gradual rollout with monitoring
3. Quick revert capability
4. Comprehensive logging for debugging

---

This design provides a complete, user-friendly solution for error and load state handling that will improve both developer experience and user experience across the entire GridPilot website application.