'use client' import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react' export interface User { id: string email: string name: string role: 'user' | 'admin' avatar?: string isVerified: boolean provider: string createdAt: string updatedAt: string siliconId?: string balance?: number } interface AuthContextType { user: User | null balance: number | null loading: boolean error: string | null login: (emailOrId: string, password: string, rememberMe?: boolean) => Promise register: (email: string, name: string, password: string) => Promise logout: () => Promise refreshUser: () => Promise checkAuth: () => Promise getBalance: () => Promise addBalance: ( amount: number, currency: 'INR' | 'USD' ) => Promise<{ success: boolean; paymentUrl?: string; formData?: any; error?: string }> refreshBalance: () => Promise } const AuthContext = createContext(undefined) interface AuthProviderProps { children: ReactNode } export function AuthProvider({ children }: AuthProviderProps) { const [user, setUser] = useState(null) const [balance, setBalance] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [hasCheckedAuth, setHasCheckedAuth] = useState(false) // API call helper const apiCall = async (endpoint: string, options: RequestInit = {}) => { const response = await fetch(`/api/auth${endpoint}`, { headers: { 'Content-Type': 'application/json', ...options.headers, }, credentials: 'include', // Include cookies ...options, }) const data = await response.json() if (!data.success) { throw new Error(data.error?.message || 'An error occurred') } return data } // Balance API call helper const balanceApiCall = async (endpoint: string, options: RequestInit = {}) => { const response = await fetch(`/api${endpoint}`, { headers: { 'Content-Type': 'application/json', ...options.headers, }, credentials: 'include', ...options, }) const data = await response.json() return data } // Check if user is authenticated - only when explicitly needed const checkAuth = async () => { // Skip if already checked in this session or if user is already set if (hasCheckedAuth || user) { setLoading(false) return } try { setLoading(true) setError(null) const data = await apiCall('/me') setUser(data.data.user) setBalance(data.data.user.balance || null) setHasCheckedAuth(true) } catch (err) { // If getting user info fails, try to refresh token try { const refreshData = await apiCall('/refresh', { method: 'POST' }) setUser(refreshData.data.user) setBalance(refreshData.data.user.balance || null) setHasCheckedAuth(true) } catch (refreshErr) { // Both failed, user is not authenticated setUser(null) setBalance(null) setHasCheckedAuth(true) } } finally { setLoading(false) } } // Login function const login = async (emailOrId: string, password: string, rememberMe?: boolean) => { try { setLoading(true) setError(null) const data = await apiCall('/login', { method: 'POST', body: JSON.stringify({ emailOrId, password, rememberMe }), }) setUser(data.data.user) setBalance(data.data.user.balance || null) } catch (err) { setError(err instanceof Error ? err.message : 'Login failed') throw err } finally { setLoading(false) } } // Register function const register = async (email: string, name: string, password: string) => { try { setLoading(true) setError(null) const data = await apiCall('/register', { method: 'POST', body: JSON.stringify({ email, name, password }), }) setUser(data.data.user) setBalance(data.data.user.balance || null) } catch (err) { setError(err instanceof Error ? err.message : 'Registration failed') throw err } finally { setLoading(false) } } // Logout function const logout = async () => { try { setLoading(true) await apiCall('/logout', { method: 'POST' }) setUser(null) setBalance(null) } catch (err) { console.error('Logout error:', err) // Even if logout API fails, clear user state setUser(null) setBalance(null) } finally { setLoading(false) } } // Refresh user data const refreshUser = async () => { try { const data = await apiCall('/me') setUser(data.data.user) setBalance(data.data.user.balance || null) } catch (err) { console.error('Refresh user error:', err) setUser(null) setBalance(null) } } // Get user balance const getBalance = async (): Promise => { try { const data = await balanceApiCall('/user/balance') if (data.success) { return data.data.balance } throw new Error(data.error?.message || 'Failed to fetch balance') } catch (err) { console.error('Get balance error:', err) throw err } } // Add balance (initiate payment) const addBalance = async (amount: number, currency: 'INR' | 'USD' = 'INR') => { try { const data = await balanceApiCall('/balance/add', { method: 'POST', body: JSON.stringify({ amount, currency }), }) if (data.success) { return { success: true, paymentUrl: data.payment_url, formData: data.form_data, } } else { return { success: false, error: data.message || 'Failed to initiate payment', } } } catch (err) { console.error('Add balance error:', err) return { success: false, error: err instanceof Error ? err.message : 'Failed to add balance', } } } // Refresh balance and update user state const refreshBalance = async () => { try { const newBalance = await getBalance() setBalance(newBalance) if (user) { setUser({ ...user, balance: newBalance }) } } catch (err) { console.error('Refresh balance error:', err) } } // Check for existing session on mount (restore user state) useEffect(() => { const restoreSession = async () => { try { setLoading(true) const data = await apiCall('/me') setUser(data.data.user) setBalance(data.data.user.balance || null) setHasCheckedAuth(true) } catch (err) { // If /me fails, try refresh token try { const refreshData = await apiCall('/refresh', { method: 'POST' }) setUser(refreshData.data.user) setBalance(refreshData.data.user.balance || null) setHasCheckedAuth(true) } catch (refreshErr) { // No valid session setUser(null) setBalance(null) setHasCheckedAuth(true) } } finally { setLoading(false) } } restoreSession() }, []) // Set up auto-refresh only when user is logged in useEffect(() => { if (!user) return // Auto-refresh user data when tab becomes visible (after 5+ minutes) let lastRefresh = Date.now() const handleVisibilityChange = () => { if (!document.hidden && Date.now() - lastRefresh > 5 * 60 * 1000) { refreshUser() lastRefresh = Date.now() } } document.addEventListener('visibilitychange', handleVisibilityChange) return () => document.removeEventListener('visibilitychange', handleVisibilityChange) }, [user]) // Auto-refresh token every 10 minutes if user is logged in useEffect(() => { if (!user) return const interval = setInterval( async () => { try { await apiCall('/refresh', { method: 'POST' }) } catch (err) { console.error('Auto-refresh failed:', err) setUser(null) // Log out if refresh fails setBalance(null) } }, 10 * 60 * 1000 ) // 10 minutes return () => clearInterval(interval) }, [user]) const value: AuthContextType = { user, balance, loading, error, login, register, logout, refreshUser, checkAuth, getBalance, addBalance, refreshBalance, } return {children} } export function useAuth() { const context = useContext(AuthContext) if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider') } return context }