# Frontend Architecture

Complete documentation of ThrillWiki's React frontend architecture, routing, state management, and UI patterns.

---

## Technology Stack

- **Framework**: React 18.3 with TypeScript
- **Build Tool**: Vite
- **Styling**: Tailwind CSS with custom design system
- **UI Components**: shadcn/ui (Radix UI primitives)
- **Routing**: React Router v6
- **Data Fetching**: TanStack Query (React Query)
- **Forms**: React Hook Form with Zod validation
- **State Machines**: Custom TypeScript state machines
- **Icons**: lucide-react

---

## Routing Structure

### URL Pattern Standards

**Parks:**
```
/parks/                         → Global park list
/parks/{parkSlug}/             → Individual park detail
/parks/{parkSlug}/rides/       → Park's ride list
/operators/{operatorSlug}/parks/ → Operator's parks
/owners/{ownerSlug}/parks/      → Owner's parks
```

**Rides:**
```
/rides/                        → Global ride list
/parks/{parkSlug}/rides/{rideSlug}/ → Ride detail (nested under park)
/manufacturers/{manufacturerSlug}/rides/ → Manufacturer's rides
/manufacturers/{manufacturerSlug}/models/ → Manufacturer's models
/designers/{designerSlug}/rides/ → Designer's rides
```

**Admin:**
```
/admin/                        → Admin dashboard
/admin/moderation/             → Moderation queue
/admin/reports/                → Reports queue
/admin/users/                  → User management
/admin/system-log/             → System activity log
/admin/blog/                   → Blog management
/admin/settings/               → Admin settings
```

### Route Configuration

```typescript
// src/App.tsx
<BrowserRouter>
  <Routes>
    {/* Public routes */}
    <Route path="/" element={<Index />} />
    <Route path="/parks" element={<Parks />} />
    <Route path="/parks/:slug" element={<ParkDetail />} />
    <Route path="/parks/:parkSlug/rides" element={<ParkRides />} />
    <Route path="/parks/:parkSlug/rides/:rideSlug" element={<RideDetail />} />
    
    {/* Admin routes - auth guard applied in page component */}
    <Route path="/admin" element={<AdminDashboard />} />
    <Route path="/admin/moderation" element={<AdminModeration />} />
    
    {/* Auth routes */}
    <Route path="/auth" element={<Auth />} />
    <Route path="/auth/callback" element={<AuthCallback />} />
    
    {/* 404 */}
    <Route path="*" element={<NotFound />} />
  </Routes>
</BrowserRouter>
```

---

## Component Architecture

### Component Organization

```
src/components/
├── admin/              # Admin forms and management tools
│   ├── ParkForm.tsx
│   ├── RideForm.tsx
│   ├── UserManagement.tsx
│   └── ...
├── auth/               # Authentication components
│   ├── AuthModal.tsx
│   ├── MFAChallenge.tsx
│   ├── TOTPSetup.tsx
│   └── ...
├── moderation/         # Moderation queue components
│   ├── ModerationQueue.tsx
│   ├── SubmissionReviewManager.tsx
│   ├── QueueFilters.tsx
│   └── ...
├── parks/              # Park display components
│   ├── ParkCard.tsx
│   ├── ParkGrid.tsx
│   ├── ParkFilters.tsx
│   └── ...
├── rides/              # Ride display components
├── upload/             # Image upload components
├── versioning/         # Version history components
├── layout/             # Layout components
│   ├── Header.tsx
│   ├── Footer.tsx
│   └── AdminLayout.tsx
├── common/             # Shared components
│   ├── LoadingGate.tsx
│   ├── ProfileBadge.tsx
│   └── SortControls.tsx
└── ui/                 # shadcn/ui base components
    ├── button.tsx
    ├── dialog.tsx
    └── ...
```

### Page Components

Top-level route components that:
- Handle auth guards (admin pages use `useAdminGuard`)
- Fetch initial data with React Query
- Implement layout structure
- Pass data to feature components

```typescript
// Example: AdminModeration.tsx
export function AdminModeration() {
  const { loading } = useAdminGuard('moderator');
  
  if (loading) return <LoadingGate isLoading />;
  
  return (
    <AdminLayout>
      <AdminPageLayout
        title="Moderation Queue"
        description="Review and approve submissions"
      >
        <ModerationQueue />
      </AdminPageLayout>
    </AdminLayout>
  );
}
```

### Feature Components

Domain-specific components with business logic.

```typescript
// Example: ModerationQueue.tsx
export function ModerationQueue() {
  const {
    items,
    isLoading,
    filters,
    pagination,
    handleAction,
  } = useModerationQueueManager();
  
  return (
    <div>
      <QueueFilters {...filters} />
      <QueueStats items={items} />
      {items.map(item => (
        <QueueItem 
          key={item.id} 
          item={item}
          onAction={handleAction}
        />
      ))}
      <QueuePagination {...pagination} />
    </div>
  );
}
```

---

## State Management

### React Query (TanStack Query)

Used for ALL server state (data fetching, caching, mutations).

**Configuration:**

```typescript
// App.tsx
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      refetchOnMount: true,
      refetchOnReconnect: true,
      retry: 1,
      staleTime: 30000,      // 30s fresh
      gcTime: 5 * 60 * 1000, // 5min cache
    },
  },
});

<QueryClientProvider client={queryClient}>
  <App />
</QueryClientProvider>
```

**Custom Hook Pattern:**

```typescript
// src/hooks/useEntityVersions.ts
export function useEntityVersions(entityType: EntityType, entityId: string) {
  const query = useQuery({
    queryKey: ['versions', entityType, entityId],
    queryFn: () => fetchVersions(entityType, entityId),
    enabled: !!entityId,
  });
  
  const mutation = useMutation({
    mutationFn: rollbackVersion,
    onSuccess: () => {
      queryClient.invalidateQueries(['versions', entityType, entityId]);
    },
  });
  
  return { 
    versions: query.data, 
    isLoading: query.isLoading,
    rollback: mutation.mutate 
  };
}
```

### State Machines

Used for complex workflows with strict transitions.

**1. moderationStateMachine.ts** - Moderation workflow

```typescript
type ModerationState =
  | { status: 'idle' }
  | { status: 'claiming'; itemId: string }
  | { status: 'locked'; itemId: string; lockExpires: string }
  | { status: 'loading_data'; itemId: string; lockExpires: string }
  | { status: 'reviewing'; itemId: string; lockExpires: string; reviewData: SubmissionItemWithDeps[] }
  | { status: 'approving'; itemId: string }
  | { status: 'rejecting'; itemId: string }
  | { status: 'complete'; itemId: string; result: 'approved' | 'rejected' }
  | { status: 'error'; itemId: string; error: string }
  | { status: 'lock_expired'; itemId: string };

type ModerationAction =
  | { type: 'CLAIM_ITEM'; payload: { itemId: string } }
  | { type: 'LOCK_ACQUIRED'; payload: { lockExpires: string } }
  | { type: 'LOAD_DATA' }
  | { type: 'DATA_LOADED'; payload: { reviewData: SubmissionItemWithDeps[] } }
  | { type: 'START_APPROVAL' }
  | { type: 'START_REJECTION' }
  | { type: 'COMPLETE'; payload: { result: 'approved' | 'rejected' } }
  | { type: 'ERROR'; payload: { error: string } }
  | { type: 'LOCK_EXPIRED' }
  | { type: 'RESET' };

function moderationReducer(
  state: ModerationState,
  action: ModerationAction
): ModerationState {
  // ... transition logic with guards
}

// Usage in SubmissionReviewManager.tsx
const [state, dispatch] = useReducer(moderationReducer, { status: 'idle' });
```

**2. deletionDialogMachine.ts** - Account deletion wizard

```typescript
type DeletionStep = 'warning' | 'confirm' | 'code';

type DeletionDialogState = {
  step: DeletionStep;
  confirmationCode: string;
  codeReceived: boolean;
  loading: boolean;
  error: string | null;
};

// Usage in AccountDeletionDialog.tsx
```

### React Hook Form

Used for ALL forms with Zod validation.

```typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const parkFormSchema = z.object({
  name: z.string().min(1, 'Name required').max(255),
  slug: z.string().regex(/^[a-z0-9-]+$/),
  park_type: z.enum(['theme_park', 'amusement_park', 'water_park']),
  opening_date: z.string().optional(),
  // ... all fields validated
});

export function ParkForm() {
  const form = useForm({
    resolver: zodResolver(parkFormSchema),
    defaultValues: initialData,
  });

  const onSubmit = async (data: ParkFormData) => {
    await submitParkCreation(data, user.id);
    // Goes to moderation queue, NOT direct DB write
  };
  
  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        {/* form fields */}
      </form>
    </Form>
  );
}
```

### Context Providers

**1. AuthProvider** - Global auth state

```typescript
// src/hooks/useAuth.tsx
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [session, setSession] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    supabase.auth.getSession().then(({ data: { session } }) => {
      setSession(session);
      setUser(session?.user ?? null);
      setLoading(false);
    });
    
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        setSession(session);
        setUser(session?.user ?? null);
      }
    );
    
    return () => subscription.unsubscribe();
  }, []);
  
  return (
    <AuthContext.Provider value={{ user, session, loading }}>
      {children}
    </AuthContext.Provider>
  );
}
```

**2. ThemeProvider** - Light/dark mode

```typescript
// src/components/theme/ThemeProvider.tsx
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState<'light' | 'dark' | 'system'>('system');
  
  useEffect(() => {
    const root = window.document.documentElement;
    root.classList.remove('light', 'dark');
    
    if (theme === 'system') {
      const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches 
        ? 'dark' 
        : 'light';
      root.classList.add(systemTheme);
    } else {
      root.classList.add(theme);
    }
  }, [theme]);
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}
```

**3. LocationAutoDetectProvider** - Auto-detect measurement system

```typescript
// src/components/providers/LocationAutoDetectProvider.tsx
export function LocationAutoDetectProvider() {
  useEffect(() => {
    const detectLocation = async () => {
      const { data } = await supabase.functions.invoke('detect-location');
      if (data?.country) {
        const system = getMeasurementSystemFromCountry(data.country);
        // Update user preferences
      }
    };
    
    detectLocation();
  }, []);
  
  return null;
}
```

---

## UI/UX Patterns

### Design System

- **Colors**: HSL-based semantic tokens in `index.css`
- **Typography**: Custom font scale with Tailwind classes
- **Spacing**: Consistent spacing scale (4px, 8px, 16px, etc.)
- **Components**: shadcn/ui with custom variants
- **Responsive**: Mobile-first, breakpoints at sm, md, lg, xl

**Color Tokens (index.css):**

```css
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;
  --secondary: 210 40% 96.1%;
  --muted: 210 40% 96.1%;
  --accent: 210 40% 96.1%;
  --destructive: 0 84.2% 60.2%;
  --border: 214.3 31.8% 91.4%;
  --input: 214.3 31.8% 91.4%;
  --ring: 222.2 84% 4.9%;
  --radius: 0.5rem;
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  /* ... dark mode tokens */
}
```

### Reusable UI Components

**AdminPageLayout** - Wraps admin pages

```typescript
<AdminPageLayout
  title="User Management"
  description="Manage users"
  onRefresh={handleRefresh}
  refreshMode="auto"
  pollInterval={30000}
>
  {children}
</AdminPageLayout>
```

**LoadingGate** - Handles loading/error states

```typescript
<LoadingGate 
  isLoading={loading} 
  error={error}
  variant="skeleton"
>
  <Content />
</LoadingGate>
```

**ProfileBadge** - User display with role badges

```typescript
<ProfileBadge
  username="john"
  displayName="John Doe"
  role="moderator"
  showRole
  size="md"
/>
```

---

## Form Patterns

### Entity Forms

All entity forms follow this structure:

1. **Schema Definition** (Zod)
2. **Form Setup** (React Hook Form)
3. **Image Upload** (EntityMultiImageUploader)
4. **Submit Handler** (entitySubmissionHelpers)

```typescript
// 1. Schema
const parkFormSchema = z.object({
  name: z.string().min(1).max(255),
  slug: z.string().regex(/^[a-z0-9-]+$/),
  // ... all fields
});

// 2. Form
const form = useForm({
  resolver: zodResolver(parkFormSchema),
  defaultValues: initialData || defaultValues,
});

// 3. Submit
const onSubmit = async (data: ParkFormData) => {
  if (isEditing) {
    await submitParkUpdate(parkId, data, user.id);
  } else {
    await submitParkCreation(data, user.id);
  }
  toast({ title: "Submitted for review" });
};
```

### Image Upload

```typescript
const [imageAssignments, setImageAssignments] = useState<ImageAssignments>({
  banner: null,
  card: null,
  uploaded: []
});

<EntityMultiImageUploader
  value={imageAssignments}
  onChange={setImageAssignments}
  entityType="park"
  maxImages={20}
/>
```

---

## Custom Hooks

### Data Fetching Hooks

```typescript
// src/hooks/useEntityVersions.ts
export function useEntityVersions(entityType, entityId)

// src/hooks/useModerationQueue.ts
export function useModerationQueue(filters, pagination)

// src/hooks/useProfile.tsx
export function useProfile(userId)

// src/hooks/useUserRole.ts
export function useUserRole()
```

### Utility Hooks

```typescript
// src/hooks/useDebounce.ts
export function useDebounce(value, delay)

// src/hooks/useMobile.tsx
export function useMobile()

// src/hooks/useUnitPreferences.ts
export function useUnitPreferences()
```

### Guard Hooks

```typescript
// src/hooks/useAdminGuard.ts
export function useAdminGuard(requiredRole: AppRole = 'moderator')

// src/hooks/useRequireMFA.ts
export function useRequireMFA()
```

---

## Performance Optimizations

### React Query Caching

```typescript
// Aggressive caching for static data
const { data: parks } = useQuery({
  queryKey: ['parks'],
  queryFn: fetchParks,
  staleTime: 5 * 60 * 1000, // 5 minutes
  gcTime: 10 * 60 * 1000,   // 10 minutes
});

// Shorter caching for dynamic data
const { data: queue } = useQuery({
  queryKey: ['moderation', 'queue', filters],
  queryFn: fetchQueue,
  staleTime: 10000,  // 10 seconds
  gcTime: 60000,     // 1 minute
  refetchInterval: 30000, // Auto-refresh every 30s
});
```

### Code Splitting

```typescript
// Lazy load admin pages
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
const AdminModeration = lazy(() => import('./pages/AdminModeration'));

<Suspense fallback={<LoadingGate isLoading />}>
  <Route path="/admin" element={<AdminDashboard />} />
</Suspense>
```

### Memoization

```typescript
// Expensive computations
const filteredItems = useMemo(() => {
  return items.filter(item => 
    item.status === statusFilter &&
    item.entity_type === typeFilter
  );
}, [items, statusFilter, typeFilter]);

// Callback stability
const handleAction = useCallback((itemId: string, action: string) => {
  performAction(itemId, action);
}, [performAction]);
```

---

**See Also:**
- [DATABASE_ARCHITECTURE.md](./DATABASE_ARCHITECTURE.md) - Database schema
- [SUBMISSION_FLOW.md](./SUBMISSION_FLOW.md) - Submission workflow
- [AUTHENTICATION.md](./AUTHENTICATION.md) - Auth implementation
