// Authentication context for sharing auth state across the app import { createContext, useContext, useState, useCallback, useEffect, ReactNode } from 'react' import * as authApi from '@/api/auth' import apiClient from '@/api/client' import type { AuthTokens, User } from '@/types' const ACCESS_TOKEN_KEY = 'smartbotic_access_token' const REFRESH_TOKEN_KEY = 'smartbotic_refresh_token' const TOKEN_EXPIRY_KEY = 'smartbotic_token_expiry' interface AuthContextValue { isAuthenticated: boolean isLoading: boolean user: User | null error: string | null login: (email: string, password: string) => Promise logout: () => Promise clearError: () => void } const AuthContext = createContext(null) interface AuthProviderProps { children: ReactNode } export function AuthProvider({ children }: AuthProviderProps) { const [isAuthenticated, setIsAuthenticated] = useState(() => { return !!localStorage.getItem(ACCESS_TOKEN_KEY) }) const [isLoading, setIsLoading] = useState(true) const [user, setUser] = useState(null) const [error, setError] = useState(null) const clearError = useCallback(() => { setError(null) }, []) const saveTokens = useCallback((tokens: AuthTokens) => { localStorage.setItem(ACCESS_TOKEN_KEY, tokens.access_token) localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refresh_token) // Calculate expiry time (subtract 60 seconds for buffer) const expiryTime = Date.now() + (tokens.expires_in - 60) * 1000 localStorage.setItem(TOKEN_EXPIRY_KEY, expiryTime.toString()) apiClient.setAccessToken(tokens.access_token) }, []) const clearTokens = useCallback(() => { localStorage.removeItem(ACCESS_TOKEN_KEY) localStorage.removeItem(REFRESH_TOKEN_KEY) localStorage.removeItem(TOKEN_EXPIRY_KEY) apiClient.setAccessToken(null) }, []) const refreshTokens = useCallback(async (): Promise => { const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY) if (!refreshToken) { return false } try { const tokens = await authApi.refreshToken(refreshToken) saveTokens(tokens) return true } catch { clearTokens() setIsAuthenticated(false) setUser(null) return false } }, [saveTokens, clearTokens]) const login = useCallback( async (email: string, password: string) => { setIsLoading(true) setError(null) try { const tokens = await authApi.login({ email, password }) saveTokens(tokens) setIsAuthenticated(true) // Fetch user info after successful login try { const userInfo = await apiClient.get('/auth/me') setUser(userInfo) } catch { // User info fetch failed but login succeeded } } catch (err) { const message = err instanceof Error ? err.message : 'Login failed' setError(message) throw err } finally { setIsLoading(false) } }, [saveTokens] ) const logout = useCallback(async () => { setIsLoading(true) try { const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY) if (refreshToken) { await authApi.logout(refreshToken).catch(() => { // Ignore logout API errors }) } } finally { clearTokens() setIsAuthenticated(false) setUser(null) setIsLoading(false) } }, [clearTokens]) // Initialize auth state on mount useEffect(() => { const initAuth = async () => { const accessToken = localStorage.getItem(ACCESS_TOKEN_KEY) if (!accessToken) { setIsLoading(false) return } apiClient.setAccessToken(accessToken) // Check if token is expired or about to expire const expiryStr = localStorage.getItem(TOKEN_EXPIRY_KEY) const expiry = expiryStr ? parseInt(expiryStr, 10) : 0 if (Date.now() >= expiry) { // Token expired, try to refresh const refreshed = await refreshTokens() if (!refreshed) { setIsLoading(false) return } } // Fetch user info try { const userInfo = await apiClient.get('/auth/me') setUser(userInfo) setIsAuthenticated(true) } catch { // Token invalid, try to refresh const refreshed = await refreshTokens() if (refreshed) { try { const userInfo = await apiClient.get('/auth/me') setUser(userInfo) setIsAuthenticated(true) } catch { clearTokens() setIsAuthenticated(false) } } } finally { setIsLoading(false) } } initAuth() }, [refreshTokens, clearTokens]) // Auto-refresh token before expiry useEffect(() => { if (!isAuthenticated) { return } const checkAndRefresh = () => { const expiryStr = localStorage.getItem(TOKEN_EXPIRY_KEY) const expiry = expiryStr ? parseInt(expiryStr, 10) : 0 // Refresh if token expires in less than 2 minutes if (Date.now() >= expiry - 120000) { refreshTokens() } } // Check every minute const interval = setInterval(checkAndRefresh, 60000) return () => clearInterval(interval) }, [isAuthenticated, refreshTokens]) return ( {children} ) } export function useAuth() { const context = useContext(AuthContext) if (!context) { throw new Error('useAuth must be used within an AuthProvider') } return context } export default AuthContext