"use client"; import { createContext, useCallback, useContext, useEffect, useState, ReactNode, } from "react"; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080"; type AuthUser = { email: string; displayName?: string | null; role: string; } | null; type AuthContextValue = { user: AuthUser; token: string | null; loading: boolean; // covers initial hydrate + active auth requests login: (params: { email: string; password: string }) => Promise; register: (params: { email: string; password: string; displayName?: string; }) => Promise; logout: () => void; getAuthHeaders: () => HeadersInit; }; const AuthContext = createContext(undefined); const TOKEN_KEY = "ballistic_auth_token"; const USER_KEY = "ballistic_auth_user"; export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [token, setToken] = useState(null); const [loading, setLoading] = useState(true); // start true while we hydrate + reuse for calls // hydrate from localStorage useEffect(() => { try { if (typeof window === "undefined") return; const storedToken = window.localStorage.getItem(TOKEN_KEY); const storedUser = window.localStorage.getItem(USER_KEY); if (storedToken && storedUser) { setToken(storedToken); setUser(JSON.parse(storedUser)); } } catch { // ignore } finally { setLoading(false); } }, []); const setSession = useCallback((newToken: string, newUser: AuthUser) => { setToken(newToken); setUser(newUser); if (typeof window !== "undefined") { window.localStorage.setItem(TOKEN_KEY, newToken); window.localStorage.setItem(USER_KEY, JSON.stringify(newUser)); } }, []); const clearSession = useCallback(() => { setToken(null); setUser(null); if (typeof window !== "undefined") { window.localStorage.removeItem(TOKEN_KEY); window.localStorage.removeItem(USER_KEY); } }, []); const register = useCallback( async (params: { email: string; password: string; displayName?: string }) => { setLoading(true); try { const res = await fetch(`${API_BASE_URL}/api/auth/register`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email: params.email, password: params.password, displayName: params.displayName, }), }); if (!res.ok) { const text = await res.text(); throw new Error(text || "Failed to create account"); } const data: { token: string; email: string; displayName?: string; role: string; } = await res.json(); setSession(data.token, { email: data.email, displayName: data.displayName ?? null, role: data.role, }); } finally { setLoading(false); } }, [setSession], ); const login = useCallback( async (params: { email: string; password: string }) => { setLoading(true); try { const res = await fetch(`${API_BASE_URL}/api/auth/login`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email: params.email, password: params.password, }), }); if (!res.ok) { const text = await res.text(); throw new Error(text || "Failed to log in"); } const data: { token: string; email: string; displayName?: string; role: string; } = await res.json(); setSession(data.token, { email: data.email, displayName: data.displayName ?? null, role: data.role, }); } finally { setLoading(false); } }, [setSession], ); const logout = useCallback(() => { clearSession(); }, [clearSession]); const getAuthHeaders = useCallback((): HeadersInit => { if (!token) return {}; return { Authorization: `Bearer ${token}`, }; }, [token]); const value: AuthContextValue = { user, token, loading, login, register, logout, getAuthHeaders, }; return {children}; } export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) { throw new Error("useAuth must be used within an AuthProvider"); } return ctx; }