import { api, setOnUnauthorizedCallback } from '@/services/api'; import type { ApiError, User } from '@/types'; import React, { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react'; interface AuthState { user: User | null; isLoading: boolean; isInitializing: boolean; isAuthenticated: boolean; error: ApiError | null; } type CheckEmailResult = { exists: boolean; name?: string | null }; type OtpResult = { success: boolean; skipOtp: boolean }; type UserProfileUpdate = Partial & { firstName?: string | null; lastName?: string | null; phone?: string | null; email?: string; }; interface AuthContextType extends AuthState { checkEmail: (email: string) => Promise; requestOtp: (email: string) => Promise; verifyOtp: (email: string, code: string) => Promise; logout: () => Promise; clearError: () => void; refreshAuth: () => Promise; updateUser: (updates: UserProfileUpdate) => void; } const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const [state, setState] = useState({ user: null, isLoading: false, isInitializing: true, isAuthenticated: false, error: null, }); const checkAuth = useCallback(async () => { try { console.log(`[AuthContext] checkAuth: Checking token...`); const token = await api.getToken(); console.log(`[AuthContext] checkAuth: Token exists=${!!token}, length=${token?.length || 0}`); const isAuth = await api.isAuthenticated(); console.log(`[AuthContext] checkAuth: isAuth=${isAuth}`); if (isAuth) { console.log(`[AuthContext] checkAuth: Getting stored user...`); const user = await api.getStoredUser(); console.log(`[AuthContext] checkAuth: User found=${!!user}`); setState({ user, isLoading: false, isInitializing: false, isAuthenticated: !!user, error: null, }); } else { console.log(`[AuthContext] checkAuth: No token, setting unauth`); setState({ user: null, isLoading: false, isInitializing: false, isAuthenticated: false, error: null, }); } } catch (error) { console.error(`[AuthContext] checkAuth Error:`, error); setState({ user: null, isLoading: false, isInitializing: false, isAuthenticated: false, error: { message: 'Failed to check authentication' }, }); } finally { console.log(`[AuthContext] checkAuth: Finished`); } }, []); // Auto-logout when WellNuo API returns 401 (token expired) // Token now expires after 365 days, so this should rarely happen useEffect(() => { setOnUnauthorizedCallback(() => { console.log('[AuthContext] Received 401 - session expired, logging out...'); api.logout().then(() => { setState({ user: null, isLoading: false, isInitializing: false, isAuthenticated: false, error: { message: 'Session expired. Please login again.' }, }); }); }); }, []); // Check authentication on mount useEffect(() => { console.log('[AuthContext] checkAuth starting...'); checkAuth(); }, [checkAuth]); const checkEmail = useCallback(async (email: string): Promise => { setState((prev) => ({ ...prev, isLoading: true, error: null })); try { // Dev email - check via API like any other user // (no more bypass - dev user needs to go through OTP flow) // Check email via API const response = await api.checkEmail(email); if (response.ok && response.data) { setState((prev) => ({ ...prev, isLoading: false })); return { exists: response.data.exists, name: response.data.name }; } // API failed - assume new user setState((prev) => ({ ...prev, isLoading: false })); return { exists: false }; } catch (error) { setState((prev) => ({ ...prev, isLoading: false, error: { message: 'Network error. Please check your connection.' }, })); return { exists: false }; } }, []); const requestOtp = useCallback(async (email: string): Promise => { setState((prev) => ({ ...prev, isLoading: true, error: null })); try { // Dev email also goes through normal OTP flow now // (no more bypass - need real OTP for WellNuo token) // Send OTP via Brevo API const response = await api.requestOTP(email); if (response.ok) { setState((prev) => ({ ...prev, isLoading: false })); return { success: true, skipOtp: false }; } // API failed setState((prev) => ({ ...prev, isLoading: false, error: { message: 'Failed to send verification code. Please try again.' }, })); return { success: false, skipOtp: false }; } catch (error) { setState((prev) => ({ ...prev, isLoading: false, error: { message: 'Network error. Please check your connection.' }, })); return { success: false, skipOtp: false }; } }, []); const verifyOtp = useCallback(async (email: string, code: string): Promise => { setState((prev) => ({ ...prev, isLoading: true, error: null })); try { // Verify OTP via WellNuo API (for all users including dev) const verifyResponse = await api.verifyOTP(email, code); if (verifyResponse.ok && verifyResponse.data) { const user: User = { user_id: verifyResponse.data.user.id, email: email, firstName: verifyResponse.data.user.first_name || null, lastName: verifyResponse.data.user.last_name || null, max_role: 'USER', privileges: '', }; setState({ user, isLoading: false, isAuthenticated: true, error: null, }); return true; } // Wrong OTP code setState((prev) => ({ ...prev, isLoading: false, error: { message: 'Invalid verification code. Please try again.' }, })); return false; } catch (error) { setState((prev) => ({ ...prev, isLoading: false, error: { message: error instanceof Error ? error.message : 'Verification failed' }, })); return false; } }, []); const refreshAuth = useCallback(async () => { await checkAuth(); }, [checkAuth]); const updateUser = useCallback((updates: UserProfileUpdate) => { setState((prev) => { if (!prev.user) return prev; return { ...prev, user: { ...prev.user, ...updates } }; }); }, []); const logout = useCallback(async () => { setState((prev) => ({ ...prev, isLoading: true })); try { await api.logout(); } finally { setState({ user: null, isLoading: false, isAuthenticated: false, error: null, }); } }, []); const clearError = useCallback(() => { setState((prev) => ({ ...prev, error: null })); }, []); return ( {children} ); } export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }