import { createContext, useContext, useState, useEffect, useRef } from 'react'; import { createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, onAuthStateChanged, updateProfile, sendPasswordResetEmail, GoogleAuthProvider, signInWithPopup, signInWithRedirect, getRedirectResult, sendEmailVerification, } from 'firebase/auth'; import { doc, setDoc, getDoc, serverTimestamp } from 'firebase/firestore'; import { auth, db } from '../services/firebase'; import { COLLECTIONS, ROLES } from '../utils/constants'; const AuthContext = createContext(null); export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context; } export function AuthProvider({ children }) { const [currentUser, setCurrentUser] = useState(null); const [userProfile, setUserProfile] = useState(null); const [loading, setLoading] = useState(true); // Fetch user profile from Firestore async function fetchUserProfile(user) { if (!user) { setUserProfile(null); return null; } try { const docRef = doc(db, COLLECTIONS.USERS, user.uid); const docSnap = await getDoc(docRef); if (docSnap.exists()) { const profile = { uid: user.uid, ...docSnap.data() }; setUserProfile(profile); return profile; } return null; } catch (error) { console.error('Error fetching user profile:', error); return null; } } // Register with email/password async function register(email, password, displayName, phoneNumber = '') { const { user } = await createUserWithEmailAndPassword(auth, email, password); // Update Firebase Auth profile await updateProfile(user, { displayName }); // Create Firestore user document const userDoc = { email, displayName, phoneNumber, role: ROLES.CITIZEN, createdAt: serverTimestamp(), isWhitelisted: false, profileImage: '', status: 'active', }; await setDoc(doc(db, COLLECTIONS.USERS, user.uid), userDoc); setUserProfile({ uid: user.uid, ...userDoc }); // Send verification email await sendEmailVerification(user); return user; } // Login with email/password async function login(email, password) { const { user } = await signInWithEmailAndPassword(auth, email, password); await fetchUserProfile(user); return user; } // Create or fetch Google user profile in Firestore async function handleGoogleUser(user) { const docRef = doc(db, COLLECTIONS.USERS, user.uid); const docSnap = await getDoc(docRef); if (!docSnap.exists()) { // First time Google sign-in — create profile const userDoc = { email: user.email, displayName: user.displayName || '', phoneNumber: user.phoneNumber || '', role: ROLES.CITIZEN, createdAt: serverTimestamp(), isWhitelisted: false, profileImage: user.photoURL || '', status: 'active', }; await setDoc(docRef, userDoc); setUserProfile({ uid: user.uid, ...userDoc }); } else { await fetchUserProfile(user); } return user; } // Google sign-in — try popup first, fall back to redirect async function loginWithGoogle() { const provider = new GoogleAuthProvider(); try { const { user } = await signInWithPopup(auth, provider); return await handleGoogleUser(user); } catch (error) { // If popup blocked by COOP or browser policy, use redirect if ( error.code === 'auth/popup-blocked' || error.code === 'auth/popup-closed-by-user' || error.code === 'auth/cancelled-popup-request' || error.message?.includes('Cross-Origin-Opener-Policy') ) { await signInWithRedirect(auth, provider); return null; // Will be handled by getRedirectResult on reload } throw error; } } // Logout async function logout() { await signOut(auth); setCurrentUser(null); setUserProfile(null); } // Reset password async function resetPassword(email) { await sendPasswordResetEmail(auth, email); } // Resend Verification Email async function resendVerification() { if (currentUser) { await sendEmailVerification(currentUser); } } // Check role access function hasRole(requiredRole) { if (!userProfile) return false; const roleHierarchy = [ ROLES.CITIZEN, ROLES.VOLUNTEER_COORDINATOR, ROLES.MANAGER, ROLES.CONTENT_MANAGER, ROLES.ADMIN, ]; const userRoleIndex = roleHierarchy.indexOf(userProfile.role); const requiredRoleIndex = roleHierarchy.indexOf(requiredRole); return userRoleIndex >= requiredRoleIndex; } // Check specific role (exact match) function isRole(role) { return userProfile?.role === role; } // Handle redirect result (for Google sign-in redirect fallback) const redirectHandled = useRef(false); useEffect(() => { if (redirectHandled.current) return; redirectHandled.current = true; getRedirectResult(auth) .then(async (result) => { if (result?.user) { await handleGoogleUser(result.user); } }) .catch((error) => { console.error('Redirect result error:', error); }); }, []); // Auth state listener useEffect(() => { const unsubscribe = onAuthStateChanged(auth, async (user) => { setCurrentUser(user); if (user) { await fetchUserProfile(user); } else { setUserProfile(null); } setLoading(false); }); return unsubscribe; }, []); const value = { currentUser, userProfile, loading, register, login, loginWithGoogle, logout, resetPassword, resendVerification, hasRole, isRole, fetchUserProfile, }; return ( {children} ); } export default AuthContext;