import { createContext, useContext, useState, useEffect, useCallback } from 'react' const AuthContext = createContext() // A session can expire while the app is open. Rather than teach every one of // the app's fetch calls to check for it, wrap fetch once: any 401 from /api // drops the whole UI back to the login screen. Cookies ride along on their own // — fetch defaults to same-origin credentials, and the UI is served from the // same origin as the API. function installUnauthorizedHandler(onUnauthorized) { const original = window.fetch window.fetch = async (...args) => { const res = await original(...args) const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || '' if (res.status === 401 && url.includes('/api/') && !url.includes('/api/login')) { onUnauthorized() } return res } return () => { window.fetch = original } } export function AuthProvider({ children }) { const [user, setUser] = useState(null) const [checking, setCheck] = useState(true) useEffect(() => { fetch('/api/me') .then(r => r.ok ? r.json() : null) .then(d => setUser(d?.user || null)) .catch(() => setUser(null)) .finally(() => setCheck(false)) }, []) useEffect(() => installUnauthorizedHandler(() => setUser(null)), []) const login = useCallback(async (username, password) => { const r = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }) const data = await r.json().catch(() => ({})) if (!r.ok) throw new Error(data.error || 'Login failed') setUser(data.user) return data.user }, []) const logout = useCallback(async () => { try { await fetch('/api/logout', { method: 'POST' }) } catch {} setUser(null) }, []) return ( {children} ) } const useAuth = () => useContext(AuthContext) export default useAuth