import {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import {request} from '../utils/api.js'; import {hasSession} from '../utils/auth.js'; import useSession from '../hooks/useSession.js'; import {ME_URL, REQUEST_TIMEOUT_MS} from '../utils/settings.js'; const CurrentUserContext = createContext(null); /** * GET /api/users/me for the whole app. `user` is undefined until the first answer and null without a session. * `reload()` loads it again and resolves to the `request()` result (not ok for a 200 without a body). * `updateUser(fn)` changes it locally after a save, and skips it when the user is gone by then. It follows the * session. A save that replaces settings still loads /me itself (see saveSettings), as it must be fresh. */ export function CurrentUserProvider({children}) { const [user, setUser] = useState(() => (hasSession() ? undefined : null)); const latest = useRef(0); const reload = useCallback(async () => { const call = ++latest.current; // After a logout, "no user" becomes "loading" (e.g. the import waits for the favorite platform). setUser((current) => current ?? undefined); const response = await request(ME_URL(), {signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)}); const result = {...response, ok: response.ok && response.data != null}; if (call === latest.current) { // Only a 401 means "no user"; another failure keeps the user shown so far, or ends "loading". if (result.ok) setUser(result.data); else if (result.status === 401) setUser(null); else setUser((current) => current ?? null); } return result; }, []); // A new stamp is a login, a "view as" or a settings save, also in another tab. const {stamp} = useSession(); useEffect(() => { if (stamp) { reload(); } else { // A /me answer still on the way must not bring the user back. latest.current += 1; setUser(null); } }, [stamp, reload]); const updateUser = useCallback((change) => setUser((current) => current && change(current)), []); const value = useMemo(() => ({user, reload, updateUser}), [user, reload, updateUser]); return {children}; } /** * `{user, reload, updateUser}`. */ export function useCurrentUserContext() { const value = useContext(CurrentUserContext); if (!value) throw new Error('useCurrentUserContext must be used inside '); return value; }