import { createContext, useCallback, useEffect, useMemo, useState } from 'react' import type { ReactNode } from 'react' import * as authApi from '@/api/auth' import { ApiError } from '@/api/client' import { clearTokens, getRefreshToken, setTokens } from '@/api/tokenStore' import type { User } from '@/api/types' type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated' interface AuthContextValue { status: AuthStatus user: User | null /** null, пока запрос не завершится (успехом или ошибкой). */ error: string | null login: (email: string, password: string) => Promise logout: () => Promise } // oxlint-disable-next-line react/only-export-components -- контекст и провайдер экспортируются вместе намеренно export const AuthContext = createContext(null) export function AuthProvider({ children }: { children: ReactNode }) { // Если refresh-токена нет, статус известен сразу же, без похода в сеть — // вычисляем его в инициализаторе, а не синхронным setState внутри эффекта. const [status, setStatus] = useState(() => getRefreshToken() ? 'loading' : 'unauthenticated', ) const [user, setUser] = useState(null) const [error, setError] = useState(null) // При открытии приложения пытаемся восстановить сессию по refresh-токену // из localStorage — иначе каждая перезагрузка страницы требовала бы входа. useEffect(() => { const existingRefreshToken = getRefreshToken() if (!existingRefreshToken) return let cancelled = false void (async () => { try { const pair = await authApi.refresh(existingRefreshToken) setTokens(pair.access_token, pair.refresh_token) const me = await authApi.fetchMe() if (cancelled) return setUser(me) setStatus('authenticated') } catch { if (cancelled) return clearTokens() setStatus('unauthenticated') } })() return () => { cancelled = true } }, []) const login = useCallback(async (email: string, password: string) => { setError(null) try { const result = await authApi.login(email, password) setTokens(result.access_token, result.refresh_token) setUser(result.user) setStatus('authenticated') } catch (err) { const message = err instanceof ApiError ? err.message : 'Не вдалося підключитися до сервера' setError(message) throw err } }, []) const logout = useCallback(async () => { const refreshToken = getRefreshToken() if (refreshToken) { // Best-effort: даже если сервер недоступен, локально выходим всё равно. try { await authApi.logout(refreshToken) } catch { // Токен и так будет забыт локально ниже. } } clearTokens() setUser(null) setStatus('unauthenticated') }, []) const value = useMemo( () => ({ status, user, error, login, logout }), [status, user, error, login, logout], ) return {children} }