import React, { createContext, useState, useContext, useEffect } from 'react'; import axios from 'axios'; const AuthContext = createContext(); const API_URL = process.env.REACT_APP_BACKEND_URL; export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [token, setToken] = useState(localStorage.getItem('token')); const [permissions, setPermissions] = useState([]); useEffect(() => { const initAuth = async () => { const storedToken = localStorage.getItem('token'); if (storedToken) { try { const response = await axios.get(`${API_URL}/api/auth/me`, { headers: { Authorization: `Bearer ${storedToken}` } }); setUser(response.data); setToken(storedToken); // Fetch user permissions await fetchPermissions(storedToken); } catch (error) { localStorage.removeItem('token'); setToken(null); setPermissions([]); } } setLoading(false); }; initAuth(); }, []); const fetchPermissions = async (authToken) => { try { const tokenToUse = authToken || token || localStorage.getItem('token'); if (!tokenToUse) { setPermissions([]); return; } const response = await axios.get(`${API_URL}/api/auth/permissions`, { headers: { Authorization: `Bearer ${tokenToUse}` } }); setPermissions(response.data.permissions || []); } catch (error) { console.error('Failed to fetch permissions:', error); setPermissions([]); } }; const login = async (email, password) => { try { console.log('[AuthContext] Starting login request...'); const response = await axios.post( `${API_URL}/api/auth/login`, { email, password }, { timeout: 30000, // 30 second timeout headers: { 'Content-Type': 'application/json' } } ); console.log('[AuthContext] Login response received:', { status: response.status, hasToken: !!response.data?.access_token, hasUser: !!response.data?.user }); const { access_token, user: userData } = response.data; // Store token first localStorage.setItem('token', access_token); console.log('[AuthContext] Token stored in localStorage'); // Update state setToken(access_token); setUser(userData); console.log('[AuthContext] User state updated:', { email: userData.email, role: userData.role }); // Fetch user permissions (don't let this fail the login) // Use setTimeout to defer permission fetching slightly setTimeout(async () => { try { console.log('[AuthContext] Fetching permissions...'); await fetchPermissions(access_token); console.log('[AuthContext] Permissions fetched successfully'); } catch (error) { console.error('[AuthContext] Failed to fetch permissions (non-critical):', { message: error.message, response: error.response?.data, status: error.response?.status }); // Don't throw - permissions can be fetched later if needed } }, 100); // Small delay to ensure state is settled return userData; } catch (error) { // Enhanced error logging console.error('[AuthContext] Login failed:', { message: error.message, response: error.response?.data, status: error.response?.status, code: error.code, config: { url: error.config?.url, method: error.config?.method, timeout: error.config?.timeout } }); // Re-throw to let Login component handle the error throw error; } }; const logout = () => { localStorage.removeItem('token'); setToken(null); setUser(null); setPermissions([]); }; const register = async (userData) => { await axios.post(`${API_URL}/api/auth/register`, userData); }; const refreshUser = async () => { try { const currentToken = localStorage.getItem('token'); if (!currentToken) { throw new Error('No token available'); } const response = await axios.get(`${API_URL}/api/auth/me`, { headers: { Authorization: `Bearer ${currentToken}` } }); setUser(response.data); return response.data; } catch (error) { console.error('Failed to refresh user:', error); // If token expired, logout if (error.response?.status === 401) { logout(); } throw error; } }; const forgotPassword = async (email) => { const response = await axios.post(`${API_URL}/api/auth/forgot-password`, { email }); return response.data; }; const resetPassword = async (token, newPassword) => { const response = await axios.post(`${API_URL}/api/auth/reset-password`, { token, new_password: newPassword }); return response.data; }; const changePassword = async (currentPassword, newPassword) => { const currentToken = localStorage.getItem('token'); if (!currentToken) { throw new Error('Not authenticated'); } const response = await axios.put( `${API_URL}/api/users/change-password`, { current_password: currentPassword, new_password: newPassword }, { headers: { Authorization: `Bearer ${currentToken}` } } ); // Refresh user data to clear force_password_change flag if it was set await refreshUser(); return response.data; }; const resendVerificationEmail = async () => { const currentToken = localStorage.getItem('token'); if (!currentToken) { throw new Error('Not authenticated'); } const response = await axios.post( `${API_URL}/api/auth/resend-verification-email`, {}, { headers: { Authorization: `Bearer ${currentToken}` } } ); return response.data; }; const hasPermission = (permissionCode) => { if (!user) return false; // Superadmin always has all permissions if (user.role === 'superadmin') return true; return permissions.includes(permissionCode); }; return ( {children} ); }; export const useAuth = () => useContext(AuthContext);