"use client"; import { createContext, useCallback, useContext, useEffect, useState, ReactNode, } from "react"; import { apiFetch, authLogin, authLogout, authRegister, clearTokens, fetchMe, getAccessToken, getRefreshToken, setTokens, ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, SESSION_ID_KEY, } from "@/lib/api"; export interface AuthUser { user_id: string; email: string; username?: string | null; full_name: string | null; [key: string]: any; } interface AuthState { user: AuthUser | null; isAuthenticated: boolean; isLoading: boolean; } interface AuthContextValue extends AuthState { login: ( email: string, password: string, mfaCode?: string, ) => Promise<{ mfaRequired?: boolean }>; register: (payload: { email: string; password: string; password_confirm: string; full_name?: string; username?: string; }) => Promise; logout: () => Promise; refreshUser: () => Promise; apiFetch: typeof apiFetch; } const AuthContext = createContext(undefined); export function AuthProvider({ children }: { children: ReactNode }) { const [state, setState] = useState({ user: null, isAuthenticated: false, isLoading: true, }); const refreshUser = useCallback(async () => { const user = await fetchMe(); if (user) { setState((prev) => ({ ...prev, user, isAuthenticated: true, isLoading: false, })); } else { clearTokens(); setState({ user: null, isAuthenticated: false, isLoading: false }); } }, []); // Bootstrap: if a token exists in storage, load the current user. useEffect(() => { let active = true; if (typeof window === "undefined") { setState((prev) => ({ ...prev, isLoading: false })); return; } if (getAccessToken()) { fetchMe().then((user) => { if (!active) return; if (user) { setState({ user, isAuthenticated: true, isLoading: false }); } else { clearTokens(); setState({ user: null, isAuthenticated: false, isLoading: false }); } }); } else { setState((prev) => ({ ...prev, isLoading: false })); } return () => { active = false; }; }, []); const login = useCallback( async (email: string, password: string, mfaCode?: string) => { const { ok, data } = await authLogin(email, password, mfaCode); if (!ok) { if (data?.mfa_required) { return { mfaRequired: true }; } const err = new Error(data?.detail || "Login failed") as any; err.status = data?.status; throw err; } setTokens(data.access, data.refresh, data.session_id); const user = await fetchMe(); setState({ user: user ?? null, isAuthenticated: true, isLoading: false, }); return {}; }, [], ); const register = useCallback( async (payload: { email: string; password: string; password_confirm: string; full_name?: string; username?: string; }) => { const { ok, data } = await authRegister(payload); if (!ok) { const err = new Error(data?.detail || "Registration failed") as any; err.errors = data; err.status = data?.status; throw err; } setTokens(data.access, data.refresh, data.session_id); setState({ user: data.user ?? null, isAuthenticated: true, isLoading: false, }); }, [], ); const logout = useCallback(async () => { await authLogout(getRefreshToken()); clearTokens(); setState({ user: null, isAuthenticated: false, isLoading: false }); }, []); const value: AuthContextValue = { ...state, login, register, logout, refreshUser, apiFetch, }; return {children}; } export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) { throw new Error("useAuth must be used within an AuthProvider"); } return ctx; } export { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, SESSION_ID_KEY };