import { createContext, useContext, useEffect, useMemo, useState, type PropsWithChildren, } from "react"; import { apiGet } from "./api"; import { AUTH_TOKEN_STORAGE_KEY, USER_STORAGE_KEY } from "./storage"; import type { SessionResponse, UserOption } from "./types"; interface SessionContextValue { user: UserOption | null; ready: boolean; setSession: (session: SessionResponse | null) => void; } const SessionContext = createContext({ user: null, ready: false, setSession: () => undefined, }); function loadStoredUser(): UserOption | null { const raw = window.localStorage.getItem(USER_STORAGE_KEY); if (!raw) { return null; } try { return JSON.parse(raw) as UserOption; } catch { return null; } } export function SessionProvider({ children }: PropsWithChildren) { const [user, setUserState] = useState(() => loadStoredUser()); const [ready, setReady] = useState(false); useEffect(() => { const token = window.localStorage.getItem(AUTH_TOKEN_STORAGE_KEY); if (!token) { setReady(true); return; } let cancelled = false; void apiGet("/session/me") .then((currentUser) => { if (!cancelled) { setUserState(currentUser); } }) .catch(() => { if (!cancelled) { window.localStorage.removeItem(AUTH_TOKEN_STORAGE_KEY); window.localStorage.removeItem(USER_STORAGE_KEY); setUserState(null); } }) .finally(() => { if (!cancelled) { setReady(true); } }); return () => { cancelled = true; }; }, []); useEffect(() => { if (user) { window.localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(user)); return; } window.localStorage.removeItem(USER_STORAGE_KEY); }, [user]); function setSession(session: SessionResponse | null) { if (session) { window.localStorage.setItem(AUTH_TOKEN_STORAGE_KEY, session.token); setUserState(session.user); setReady(true); return; } window.localStorage.removeItem(AUTH_TOKEN_STORAGE_KEY); window.localStorage.removeItem(USER_STORAGE_KEY); setUserState(null); setReady(true); } const value = useMemo( () => ({ user, ready, setSession, }), [ready, user], ); return {children}; } export function useSession() { return useContext(SessionContext); }