import React, { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react'; import type { User } from '../types'; import { authApi, usersApi, getToken, setToken, clearToken } from '../utils/api'; // ── Types ────────────────────────────────────────────────────────────────────── interface AuthContextValue { user: User | null; isAuthenticated: boolean; isStaff: boolean; isAdmin: boolean; isLoading: boolean; login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>; register: (username: string, email: string, password: string) => Promise<{ success: boolean; error?: string }>; logout: () => void; updateUsername: (username: string) => Promise<{ success: boolean; error?: string }>; } // ── Context ──────────────────────────────────────────────────────────────────── const AuthContext = createContext(null); // ── Provider ─────────────────────────────────────────────────────────────────── const STORAGE_KEY = 'crowmate_auth_user'; function saveUserToStorage(user: User | null): void { if (user) { localStorage.setItem(STORAGE_KEY, JSON.stringify(user)); } else { localStorage.removeItem(STORAGE_KEY); } } export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); // On mount: validate stored token and restore session useEffect(() => { const token = getToken(); if (!token) { setIsLoading(false); return; } authApi.me() .then((freshUser) => { setUser(freshUser); saveUserToStorage(freshUser); }) .catch(() => { clearToken(); saveUserToStorage(null); }) .finally(() => setIsLoading(false)); }, []); const isAuthenticated = user !== null; const isStaff = user?.role === 'dev' || user?.role === 'com'; const isAdmin = user?.isAdmin === true; const login = useCallback( async (email: string, password: string): Promise<{ success: boolean; error?: string }> => { try { const { token, user: loggedInUser } = await authApi.login(email, password); setToken(token); setUser(loggedInUser); saveUserToStorage(loggedInUser); return { success: true }; } catch (err) { const message = err instanceof Error ? err.message : 'Login failed.'; return { success: false, error: message }; } }, [] ); const register = useCallback( async (username: string, email: string, password: string): Promise<{ success: boolean; error?: string }> => { try { const { token, user: newUser } = await authApi.register(username, email, password); setToken(token); setUser(newUser); saveUserToStorage(newUser); return { success: true }; } catch (err) { const message = err instanceof Error ? err.message : 'Registration failed.'; return { success: false, error: message }; } }, [] ); const logout = useCallback(() => { clearToken(); setUser(null); saveUserToStorage(null); }, []); const updateUsername = useCallback( async (username: string): Promise<{ success: boolean; error?: string }> => { try { const updatedUser = await usersApi.updateUsername(username); setUser(updatedUser); saveUserToStorage(updatedUser); return { success: true }; } catch (err) { const message = err instanceof Error ? err.message : 'Failed to update username.'; return { success: false, error: message }; } }, [] ); const value = useMemo( () => ({ user, isAuthenticated, isStaff, isAdmin, isLoading, login, register, logout, updateUsername }), [user, isAuthenticated, isStaff, isAdmin, isLoading, login, register, logout, updateUsername] ); return {children}; } // ── Hook ─────────────────────────────────────────────────────────────────────── export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) { throw new Error('useAuth must be used inside '); } return ctx; }