import React, { createContext, useContext, useEffect, useState } from 'react'; import { onAuthStateChanged, signInWithPopup, signOut, User, createUserWithEmailAndPassword, signInWithEmailAndPassword, updateProfile } from 'firebase/auth'; import { auth, googleProvider, isFirebaseConfigured } from '../services/firebase'; import { demoStorage } from '../services/demoStorage'; import { API_URL } from '../constants'; interface AuthContextType { user: User | null; loading: boolean; signInWithGoogle: () => Promise; signOutUser: () => Promise; signInWithEmail: (email: string, password: string) => Promise; signUpWithEmail: (email: string, password: string, displayName?: string) => Promise; sendOTP: (email: string, inviteCode?: string) => Promise; verifyOTP: (email: string, otp: string, inviteCode?: string) => Promise; signInWithOTP: (token: string, userData: any) => Promise; } const AuthContext = createContext(undefined); export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { console.log('AuthContext initializing, isFirebaseConfigured:', isFirebaseConfigured); if (!isFirebaseConfigured) { // Demo mode: check for persisted session console.log('Demo mode: checking for persisted session'); const sessionUser = sessionStorage.getItem('auditly_demo_session'); if (sessionUser) { const parsedUser = JSON.parse(sessionUser); console.log('Restoring demo session for:', parsedUser.email); setUser(parsedUser as User); } setLoading(false); return () => { }; } console.log('Setting up Firebase auth listener'); const unsub = onAuthStateChanged(auth, (u) => { console.log('Auth state changed:', u); setUser(u); setLoading(false); }); return () => unsub(); }, []); const signInWithGoogle = async () => { if (!isFirebaseConfigured) { // No-op in demo mode return; } await signInWithPopup(auth, googleProvider); }; const signOutUser = async () => { if (!isFirebaseConfigured) { // Clear demo session sessionStorage.removeItem('auditly_demo_session'); setUser(null); return; } await signOut(auth); }; const signInWithEmail = async (email: string, password: string) => { console.log('signInWithEmail called, isFirebaseConfigured:', isFirebaseConfigured); if (!isFirebaseConfigured) { console.log('Demo mode: authenticating user', email); const existingUser = demoStorage.getUserByEmail(email); if (existingUser) { // Verify password if (demoStorage.verifyPassword(password, existingUser.passwordHash)) { const mockUser = { uid: existingUser.uid, email: existingUser.email, displayName: existingUser.displayName } as unknown as User; setUser(mockUser); sessionStorage.setItem('auditly_demo_session', JSON.stringify(mockUser)); console.log('Demo login successful for:', email); } else { throw new Error('Invalid password'); } } else { throw new Error('User not found. Please sign up first.'); } return; } try { console.log('Attempting Firebase auth'); await signInWithEmailAndPassword(auth, email, password); } catch (e: any) { const code = e?.code || ''; console.error('Firebase Auth Error:', code, e?.message); if (code === 'auth/configuration-not-found' || code === 'auth/operation-not-allowed') { console.warn('Email/Password provider disabled in Firebase. Falling back to local mock user for development.'); const mock = { uid: `demo-${btoa(email).slice(0, 8)}`, email, displayName: email.split('@')[0] } as unknown as User; setUser(mock); return; } throw e; } }; const signUpWithEmail = async (email: string, password: string, displayName?: string) => { if (!isFirebaseConfigured) { console.log('Demo mode: creating new user', email); // Check if user already exists const existingUser = demoStorage.getUserByEmail(email); if (existingUser) { throw new Error('User already exists with this email'); } // Create new user const uid = `demo-${btoa(email).slice(0, 8)}`; const newUser = { uid, email, displayName: displayName || email.split('@')[0], passwordHash: demoStorage.hashPassword(password) }; demoStorage.saveUser(newUser); const mockUser = { uid: newUser.uid, email: newUser.email, displayName: newUser.displayName } as unknown as User; setUser(mockUser); sessionStorage.setItem('auditly_demo_session', JSON.stringify(mockUser)); console.log('Demo signup successful for:', email); return; } try { const cred = await createUserWithEmailAndPassword(auth, email, password); if (displayName) { try { await updateProfile(cred.user, { displayName }); } catch { } } } catch (e: any) { const code = e?.code || ''; if (code === 'auth/configuration-not-found' || code === 'auth/operation-not-allowed') { console.warn('Email/Password provider disabled in Firebase. Falling back to local mock user for development.'); const mock = { uid: `demo-${btoa(email).slice(0, 8)}`, email, displayName: displayName || email.split('@')[0] } as unknown as User; setUser(mock); return; } throw e; } }; const sendOTP = async (email: string, inviteCode?: string) => { const response = await fetch(`${API_URL}/sendOTP`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, inviteCode }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to send OTP'); } return response.json(); }; const verifyOTP = async (email: string, otp: string, inviteCode?: string) => { const response = await fetch(`${API_URL}/verifyOTP`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, otp, inviteCode }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to verify OTP'); } const data = await response.json(); // Set user in auth context const mockUser = { uid: data.user.uid, email: data.user.email, displayName: data.user.displayName, emailVerified: true } as unknown as User; setUser(mockUser); sessionStorage.setItem('auditly_demo_session', JSON.stringify(mockUser)); sessionStorage.setItem('auditly_auth_token', data.token); return data; }; const signInWithOTP = async (token: string, userData: any) => { const mockUser = { uid: userData.uid, email: userData.email, displayName: userData.displayName, emailVerified: true } as unknown as User; setUser(mockUser); sessionStorage.setItem('auditly_demo_session', JSON.stringify(mockUser)); sessionStorage.setItem('auditly_auth_token', token); }; return ( {children} ); }; export const useAuth = () => { const ctx = useContext(AuthContext); if (!ctx) throw new Error('useAuth must be used within AuthProvider'); return ctx; };