import React, { createContext, useState, useContext, useEffect } from 'react'; import axios from 'axios'; import api from '../utils/api'; import logger from '../utils/logger'; const AuthContext = createContext(); const API_URL = process.env.REACT_APP_BACKEND_URL || window.location.origin; // Log environment on module load for debugging logger.log('[AuthContext] Module initialized with:', { REACT_APP_BACKEND_URL: process.env.REACT_APP_BACKEND_URL, REACT_APP_BASENAME: process.env.REACT_APP_BASENAME, API_URL: API_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) { logger.error('Failed to fetch permissions:', error); setPermissions([]); } }; const login = async (email, password) => { try { logger.log('[AuthContext] Starting login request...', { API_URL: API_URL, envBackendUrl: process.env.REACT_APP_BACKEND_URL, fullUrl: `${API_URL}/api/auth/login` }); // Use api instance for retry logic const response = await api.post( '/auth/login', { email, password }, { headers: { 'Content-Type': 'application/json' } } ); logger.log('[AuthContext] Login response received:', { status: response.status, hasToken: !!response.data?.access_token, hasUser: !!response.data?.user }); const { access_token, user: userData } = response.data; if (!access_token || !userData) { throw new Error('Invalid response from server - missing token or user data'); } // Store token FIRST and verify it was stored localStorage.setItem('token', access_token); const storedToken = localStorage.getItem('token'); if (storedToken !== access_token) { throw new Error('Failed to store token in localStorage'); } logger.log('[AuthContext] Token stored and verified in localStorage'); // Update state in correct order setToken(access_token); setUser(userData); logger.log('[AuthContext] User state updated:', { email: userData.email, role: userData.role }); // Fetch permissions immediately and WAIT for it (but don't fail login if it fails) try { logger.log('[AuthContext] Fetching permissions...'); await fetchPermissions(access_token); logger.log('[AuthContext] Permissions fetched successfully'); } catch (permError) { logger.error('[AuthContext] Failed to fetch permissions (non-critical):', { message: permError.message, response: permError.response?.data, status: permError.response?.status }); // Set empty permissions array so hasPermission doesn't break setPermissions([]); // Don't throw - login succeeded even if permissions failed } return userData; } catch (error) { // Enhanced error logging logger.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 } }); // Clear any partial state localStorage.removeItem('token'); setToken(null); setUser(null); setPermissions([]); // 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) { logger.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);