// contexts/AuthContext.jsx import { createContext, useContext, useState, useCallback, useRef } from 'react' import api from '../utils/api.util' const AuthContext = createContext(null) export const decodeToken = (token) => JSON.parse(atob(token.split('.')[1])) export function AuthProvider({ children }) { const [accessToken, _setAccessToken] = useState(null) const [user, setUser] = useState(null) const [sessionId, setSessionId] = useState(null) const [loading, setLoading] = useState(true) const [sessionRestored, setSessionRestored] = useState(false) const [authError, setAuthError] = useState(null) const isRestoring = useRef(false) const accessTokenRef = useRef(null) const setAccessToken = useCallback((token) => { accessTokenRef.current = token _setAccessToken(token) }, []) // Shared by verifyOTP, restoreSession, and login's trusted-device fast path // — anywhere the backend hands back a fully-authenticated session in one shot. const applySession = useCallback((data) => { setAccessToken(data.accessToken) setUser(data.user) setSessionId(data.session_id ?? null) }, [setAccessToken]) // ── Login ────────────────────────────────────────────────────────────────── // Credentials get you an OTP — unless this device already cleared one // recently and its trust window is still valid, in which case the backend // returns otpRequired:false along with a full session, same shape as // verifyOTP's response. const login = useCallback(async ({ email, password }) => { setAuthError(null) try { const { data } = await api.post('/auth/login', { email, password }) if (data.data.otpRequired === false) { applySession(data.data) return { success: true, otpRequired: false, user: data.data.user } } return { success: true, otpRequired: true, email: data.data.email } } catch (err) { const message = err.response?.data?.message || 'Login failed. Please try again.' const errors = err.response?.data?.errors ?? null setAuthError(message) return { success: false, message, errors } } }, [applySession]) // ── Register ─────────────────────────────────────────────────────────────── const register = useCallback(async (payload) => { setAuthError(null) try { await api.post('/auth/register', payload) return { success: true } } catch (err) { const message = err.response?.data?.message || 'Registration failed. Please try again.' setAuthError(message) return { success: false, message } } }, []) // ── Verify OTP (auto-login) ──────────────────────────────────────────────── const verifyOTP = useCallback(async ({ email, otp }) => { setAuthError(null) try { const { data } = await api.post('/auth/verify-otp', { email, otp }) applySession(data.data) return { success: true, user: data.data.user } } catch (err) { const message = err.response?.data?.message || 'OTP verification failed.' setAuthError(message) return { success: false, message } } }, [applySession]) // ── Resend OTP ───────────────────────────────────────────────────────────── const resendOTP = useCallback(async ({ email }) => { try { await api.post('/auth/resend-otp', { email }) return { success: true } } catch (err) { const message = err.response?.data?.message || 'Could not resend OTP.' return { success: false, message } } }, []) // ── Forgot password (request OTP) ───────────────────────────────────────── const forgotPassword = useCallback(async ({ email }) => { try { const { data } = await api.post('/auth/forgot-password', { email }) return { success: true, email: data.data.email } } catch (err) { const message = err.response?.data?.message || 'Could not process request.' return { success: false, message } } }, []) // ── Reset password (OTP + new password in one step) ─────────────────────── const resetPassword = useCallback(async ({ email, otp, new_password }) => { try { await api.post('/auth/reset-password', { email, otp, new_password }) return { success: true } } catch (err) { const message = err.response?.data?.message || 'Password reset failed.' return { success: false, message } } }, []) // ── Logout ───────────────────────────────────────────────────────────────── const logout = useCallback(async () => { try { await api.post('/auth/logout', { session_id: sessionId }) } catch (_) { // ignore } finally { setAccessToken(null) setUser(null) setSessionId(null) } }, [sessionId]) // ── Restore session ──────────────────────────────────────────────────────── const restoreSession = useCallback(async () => { if (isRestoring.current) return { success: false } isRestoring.current = true try { if (accessTokenRef.current) return { success: true } const { data } = await api.post('/auth/refresh') applySession(data.data) return { success: true, user: data.data.user } } catch (_) { setAccessToken(null) setUser(null) setSessionId(null) return { success: false } } finally { setLoading(false) } }, [applySession]) return ( {children} ) } export const useAuth = () => useContext(AuthContext)