import { createContext, useCallback, useContext, useMemo, useSyncExternalStore, } from 'react' import { ApiHttpError, setStoredToken, getStoredToken } from '../api/http' import type { AuthUser } from '../api/types' import { loginRequest } from '../api/session' export type { AuthUser } type AuthState = { token: string | null user: AuthUser | null selectedRole: string | null } const STORAGE_USER = 'alrahma_api_user' const STORAGE_ROLE = 'alrahma_selected_role' function enabledRoles(user: AuthUser | null): string[] { if (!user?.roles) return [] return Object.keys(user.roles).filter((k) => user.roles[k]) } function readUser(): AuthUser | null { try { const raw = localStorage.getItem(STORAGE_USER) if (!raw) return null return JSON.parse(raw) as AuthUser } catch { return null } } function writeUser(user: AuthUser | null): void { if (user) localStorage.setItem(STORAGE_USER, JSON.stringify(user)) else localStorage.removeItem(STORAGE_USER) } function readSelectedRole(): string | null { const role = localStorage.getItem(STORAGE_ROLE) return role && role.trim() ? role : null } function writeSelectedRole(role: string | null): void { if (role) localStorage.setItem(STORAGE_ROLE, role) else localStorage.removeItem(STORAGE_ROLE) } function validSelectedRole(user: AuthUser | null, role: string | null): string | null { if (!role) return null const roles = enabledRoles(user) return roles.includes(role) ? role : null } let memory: AuthState = { token: getStoredToken(), user: readUser(), selectedRole: null, } memory.selectedRole = validSelectedRole(memory.user, readSelectedRole()) const listeners = new Set<() => void>() function emit(): void { listeners.forEach((fn) => fn()) } function setAuth(next: AuthState): void { memory = next emit() } function subscribe(listener: () => void): () => void { listeners.add(listener) return () => listeners.delete(listener) } function getSnapshot(): AuthState { return memory } export function hydrateAuthFromStorage(): void { const user = readUser() memory = { token: getStoredToken(), user, selectedRole: validSelectedRole(user, readSelectedRole()), } emit() } export function AuthProvider({ children }: { children: React.ReactNode }) { const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot) const login = useCallback(async (email: string, password: string) => { let result: Awaited> try { result = await loginRequest(email, password) } catch (e) { const message = e instanceof ApiHttpError ? e.message : e instanceof Error ? e.message : 'Could not reach the server.' return { ok: false as const, message } } if (result.status === false) { return { ok: false as const, message: result.message ?? 'Login failed.', } } const roles = enabledRoles(result.user) const nextRole = roles.length === 1 ? roles[0] : null setStoredToken(result.token) writeUser(result.user) writeSelectedRole(nextRole) setAuth({ token: result.token, user: result.user, selectedRole: nextRole }) return { ok: true as const, requiresRoleSelection: roles.length > 1, roles, selectedRole: nextRole, } }, []) const logout = useCallback(() => { setStoredToken(null) writeUser(null) writeSelectedRole(null) setAuth({ token: null, user: null, selectedRole: null }) }, []) const setSession = useCallback((token: string, user: AuthUser) => { const roles = enabledRoles(user) const nextRole = roles.length === 1 ? roles[0] : null setStoredToken(token) writeUser(user) writeSelectedRole(nextRole) setAuth({ token, user, selectedRole: nextRole }) }, []) const setSelectedRole = useCallback((role: string | null) => { const roles = enabledRoles(memory.user) const nextRole = role && roles.includes(role) ? role : null writeSelectedRole(nextRole) setAuth({ ...memory, selectedRole: nextRole }) }, []) const value = useMemo( () => ({ login, logout, setSession, setSelectedRole, token: state.token, user: state.user, selectedRole: state.selectedRole, roles: enabledRoles(state.user), isAuthenticated: Boolean(state.token && state.user), }), [state.token, state.user, state.selectedRole, login, logout, setSession, setSelectedRole], ) return {children} } type AuthContextValue = { login: ( email: string, password: string, ) => Promise< { ok: true; requiresRoleSelection: boolean; roles: string[]; selectedRole: string | null } | { ok: false; message: string } > logout: () => void setSession: (token: string, user: AuthUser) => void setSelectedRole: (role: string | null) => void token: string | null user: AuthUser | null selectedRole: string | null roles: string[] isAuthenticated: boolean } const AuthContext = createContext(null) export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext) if (!ctx) throw new Error('useAuth must be used within AuthProvider') return ctx }