import React, { createContext, useContext, useState, useEffect, useCallback } from "react"; export type User = { id: number; username: string; fullName: string | null; role: "admin" | "user" | "viewer"; }; type AuthContextType = { user: User | null; token: string | null; isAuthenticated: boolean; isLoading: boolean; isAdmin: boolean; login: (username: string, password: string) => Promise; logout: () => void; error: string | null; clearError: () => void; }; const AuthContext = createContext(undefined); const AUTH_STORAGE_KEY = "braceflow_auth"; const API_BASE = import.meta.env.VITE_API_BASE || "http://localhost:3001/api"; export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(null); const [token, setToken] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); // Check for existing session on mount useEffect(() => { const stored = localStorage.getItem(AUTH_STORAGE_KEY); if (stored) { try { const parsed = JSON.parse(stored); if (parsed.user && parsed.token && parsed.expiresAt > Date.now()) { setUser(parsed.user); setToken(parsed.token); } else { localStorage.removeItem(AUTH_STORAGE_KEY); } } catch { localStorage.removeItem(AUTH_STORAGE_KEY); } } setIsLoading(false); }, []); const login = useCallback(async (username: string, password: string) => { setIsLoading(true); setError(null); try { const response = await fetch(`${API_BASE}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || "Invalid username or password"); } const data = await response.json(); const userData: User = { id: data.user.id, username: data.user.username, fullName: data.user.full_name, role: data.user.role, }; // Store auth data const authData = { user: userData, token: data.token, expiresAt: new Date(data.expiresAt).getTime(), }; localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(authData)); setUser(userData); setToken(data.token); } catch (err: any) { setError(err.message || "Login failed. Please try again."); throw err; } finally { setIsLoading(false); } }, []); const logout = useCallback(async () => { // Call logout endpoint if we have a token if (token) { try { await fetch(`${API_BASE}/auth/logout`, { method: "POST", headers: { "Authorization": `Bearer ${token}`, }, }); } catch { // Ignore logout API errors } } localStorage.removeItem(AUTH_STORAGE_KEY); setUser(null); setToken(null); }, [token]); const clearError = useCallback(() => { setError(null); }, []); const value: AuthContextType = { user, token, isAuthenticated: !!user, isLoading, isAdmin: user?.role === "admin", login, logout, error, clearError, }; return {children}; } export function useAuth() { const context = useContext(AuthContext); if (context === undefined) { throw new Error("useAuth must be used within an AuthProvider"); } return context; } /** * Helper function to get auth headers for API calls */ export function getAuthHeaders(): Record { const stored = localStorage.getItem(AUTH_STORAGE_KEY); if (stored) { try { const parsed = JSON.parse(stored); if (parsed.token) { return { "Authorization": `Bearer ${parsed.token}` }; } } catch { // Ignore } } return {}; } /** * Helper function to get the auth token */ export function getAuthToken(): string | null { const stored = localStorage.getItem(AUTH_STORAGE_KEY); if (stored) { try { const parsed = JSON.parse(stored); return parsed.token || null; } catch { // Ignore } } return null; }