# 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 {/* Public routes */} } /> } /> } /> } /> } /> {/* Admin routes - auth guard applied in page component */} } /> } /> {/* Auth routes */} } /> } /> {/* 404 */} } /> ``` --- ## 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 ; return ( ); } ``` ### Feature Components Domain-specific components with business logic. ```typescript // Example: ModerationQueue.tsx export function ModerationQueue() { const { items, isLoading, filters, pagination, handleAction, } = useModerationQueueManager(); return (
{items.map(item => ( ))}
); } ``` --- ## 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 }, }, }); ``` **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 fields */}
); } ``` ### 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 ( {children} ); } ``` **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 ( {children} ); } ``` **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 {children} ``` **LoadingGate** - Handles loading/error states ```typescript ``` **ProfileBadge** - User display with role badges ```typescript ``` --- ## 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({ banner: null, card: null, uploaded: [] }); ``` --- ## 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')); }> } /> ``` ### 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