import { createContext, useContext, useEffect, useState } from 'react' import type { User, AuthSession } from '../types' import { api, ApiError } from '../lib/api' const BASE = (import.meta.env.VITE_API_URL as string | undefined) ?? 'https://api.hotelsync.ru' interface AuthContextValue { session: AuthSession | null user: User | null login: (email: string, password: string) => Promise logout: () => void } const AuthContext = createContext(null) function loadSession(): AuthSession | null { try { const stored = localStorage.getItem('hotelsync-session') if (!stored) return null const parsed = JSON.parse(stored) as AuthSession // If session is missing token — discard it, we'll refresh below if (!parsed?.user) { localStorage.removeItem('hotelsync-session'); return null } return parsed } catch { localStorage.removeItem('hotelsync-session') return null } } export function AuthProvider({ children }: { children: React.ReactNode }) { const [session, setSession] = useState(loadSession) // On mount: if session exists but token is missing/null, try to refresh via cookie useEffect(() => { if (!session) return if (session.token) return // token present, all good fetch(`${BASE}/api/auth/refresh`, { method: 'POST', credentials: 'include' }) .then(r => r.ok ? r.json() : null) .then((data: { access_token?: string } | null) => { if (data?.access_token) { const tok = data.access_token setSession(cur => { if (!cur) return null const s: AuthSession = { ...cur, token: tok } localStorage.setItem('hotelsync-session', JSON.stringify(s)) return s }) } else { // Guard: if login() set a fresh token while refresh was in-flight — keep it setSession(cur => { if (cur?.token) return cur localStorage.removeItem('hotelsync-session') return null }) } }) .catch(() => { // Guard: same race-condition protection setSession(cur => { if (cur?.token) return cur localStorage.removeItem('hotelsync-session') return null }) }) }, []) // eslint-disable-line react-hooks/exhaustive-deps // Keep React state in sync when api.ts refreshes the token (e.g. after 401 auto-refresh) useEffect(() => { const handler = (e: Event) => { const { token } = (e as CustomEvent<{ token: string }>).detail setSession(cur => { if (!cur) return cur return { ...cur, token } }) } window.addEventListener('hotelsync:token-updated', handler) return () => window.removeEventListener('hotelsync:token-updated', handler) }, []) const login = async (email: string, password: string): Promise => { try { const { access_token, user } = await api.auth.login(email, password) const s: AuthSession = { user, token: access_token } setSession(s) localStorage.setItem('hotelsync-session', JSON.stringify(s)) return user } catch (err) { if (err instanceof ApiError && err.status === 401) return null throw err } } const logout = async () => { try { await api.auth.logout() } catch { /* ignore */ } setSession(null) localStorage.removeItem('hotelsync-session') } return ( {children} ) } export function useAuth() { const ctx = useContext(AuthContext) if (!ctx) throw new Error('useAuth must be used within AuthProvider') return ctx }