import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react' import { type User, loginApi, logoutApi, getStoredTokens, getStoredUser, isTokenExpired, refreshTokenApi, clearAuthStorage, changePasswordApi, } from '../services/authApi' import { TOKEN_REFRESH_BUFFER_MS } from '../config/api' interface AuthContextType { user: User | null isAuthenticated: boolean isLoading: boolean login: (email: string, password: string) => Promise<{ success: boolean; error?: string }> logout: () => Promise changePassword: (currentPassword: string, newPassword: string) => Promise<{ success: boolean; error?: string }> } const AuthContext = createContext(undefined) export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null) const [isLoading, setIsLoading] = useState(true) // Refresh token before expiry const scheduleTokenRefresh = useCallback((expiresInMs: number) => { const refreshIn = Math.max(expiresInMs - TOKEN_REFRESH_BUFFER_MS, 10000) const timeoutId = setTimeout(async () => { const tokens = await refreshTokenApi() if (tokens) { // Schedule next refresh scheduleTokenRefresh(tokens.expiresIn * 1000) } else { // Refresh failed - logout setUser(null) } }, refreshIn) return () => clearTimeout(timeoutId) }, []) // Check for existing session on mount useEffect(() => { const initAuth = async () => { const { accessToken, refreshToken } = getStoredTokens() if (!accessToken && !refreshToken) { setIsLoading(false) return } // Check if access token is still valid if (accessToken && !isTokenExpired(accessToken)) { const storedUser = getStoredUser() if (storedUser) { setUser(storedUser) // Schedule token refresh const payload = JSON.parse(atob(accessToken.split('.')[1])) const expiresInMs = (payload.exp * 1000) - Date.now() scheduleTokenRefresh(expiresInMs) } setIsLoading(false) return } // Access token expired - try to refresh if (refreshToken && !isTokenExpired(refreshToken)) { try { const tokens = await refreshTokenApi() if (tokens) { const storedUser = getStoredUser() if (storedUser) { setUser(storedUser) scheduleTokenRefresh(tokens.expiresIn * 1000) } } } catch { clearAuthStorage() } } else { clearAuthStorage() } setIsLoading(false) } initAuth() }, [scheduleTokenRefresh]) const login = async (email: string, password: string): Promise<{ success: boolean; error?: string }> => { try { const response = await loginApi(email, password) setUser(response.user) // Schedule token refresh scheduleTokenRefresh(response.tokens.expiresIn * 1000) return { success: true } } catch (error) { const message = error instanceof Error ? error.message : 'Login failed' return { success: false, error: message } } } const logout = async () => { await logoutApi() setUser(null) } const changePassword = async ( currentPassword: string, newPassword: string ): Promise<{ success: boolean; error?: string }> => { return changePasswordApi(currentPassword, newPassword) } return ( {children} ) } export function useAuth() { const context = useContext(AuthContext) if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider') } return context } // Re-export User type for convenience export type { User } from '../services/authApi'