Fix organization setup flow: redirect to onboarding for incomplete setup
This commit is contained in:
234
contexts/AuthContext.tsx
Normal file
234
contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
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<void>;
|
||||
signOutUser: () => Promise<void>;
|
||||
signInWithEmail: (email: string, password: string) => Promise<void>;
|
||||
signUpWithEmail: (email: string, password: string, displayName?: string) => Promise<void>;
|
||||
sendOTP: (email: string, inviteCode?: string) => Promise<any>;
|
||||
verifyOTP: (email: string, otp: string, inviteCode?: string) => Promise<void>;
|
||||
signInWithOTP: (token: string, userData: any) => Promise<void>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [user, setUser] = useState<User | null>(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 (
|
||||
<AuthContext.Provider value={{
|
||||
user,
|
||||
loading,
|
||||
signInWithGoogle,
|
||||
signOutUser,
|
||||
signInWithEmail,
|
||||
signUpWithEmail,
|
||||
sendOTP,
|
||||
verifyOTP,
|
||||
signInWithOTP
|
||||
}}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAuth = () => {
|
||||
const ctx = useContext(AuthContext);
|
||||
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
|
||||
return ctx;
|
||||
};
|
||||
Reference in New Issue
Block a user