'use client'; import React, { DependencyList, createContext, useContext, ReactNode, useMemo, useState, useEffect } from 'react'; import { FirebaseApp } from 'firebase/app'; import { Firestore } from 'firebase/firestore'; import { Auth, User, onAuthStateChanged } from 'firebase/auth'; import { FirebaseErrorListener } from '@/components/FirebaseErrorListener' interface FirebaseProviderProps { children: ReactNode; firebaseApp: FirebaseApp; firestore: Firestore; auth: Auth; } // Internal state for user authentication interface UserAuthState { user: User | null; isUserLoading: boolean; userError: Error | null; } // Combined state for the Firebase context export interface FirebaseContextState { areServicesAvailable: boolean; // True if core services (app, firestore, auth instance) are provided firebaseApp: FirebaseApp | null; firestore: Firestore | null; auth: Auth | null; // The Auth service instance // User authentication state user: User | null; isUserLoading: boolean; // True during initial auth check userError: Error | null; // Error from auth listener } // Return type for useFirebase() export interface FirebaseServicesAndUser { firebaseApp: FirebaseApp; firestore: Firestore; auth: Auth; user: User | null; isUserLoading: boolean; userError: Error | null; } // Return type for useUser() - specific to user auth state export interface UserHookResult { // Renamed from UserAuthHookResult for consistency if desired, or keep as UserAuthHookResult user: User | null; isUserLoading: boolean; userError: Error | null; } // React Context export const FirebaseContext = createContext(undefined); /** * FirebaseProvider manages and provides Firebase services and user authentication state. */ export const FirebaseProvider: React.FC = ({ children, firebaseApp, firestore, auth, }) => { const [userAuthState, setUserAuthState] = useState({ user: null, isUserLoading: true, // Start loading until first auth event userError: null, }); // Effect to subscribe to Firebase auth state changes useEffect(() => { if (!auth) { // If no Auth service instance, cannot determine user state setUserAuthState({ user: null, isUserLoading: false, userError: new Error("Auth service not provided.") }); return; } setUserAuthState({ user: null, isUserLoading: true, userError: null }); // Reset on auth instance change const unsubscribe = onAuthStateChanged( auth, (firebaseUser) => { // Auth state determined setUserAuthState({ user: firebaseUser, isUserLoading: false, userError: null }); }, (error) => { // Auth listener error console.error("FirebaseProvider: onAuthStateChanged error:", error); setUserAuthState({ user: null, isUserLoading: false, userError: error }); } ); return () => unsubscribe(); // Cleanup }, [auth]); // Depends on the auth instance // Memoize the context value const contextValue = useMemo((): FirebaseContextState => { const servicesAvailable = !!(firebaseApp && firestore && auth); return { areServicesAvailable: servicesAvailable, firebaseApp: servicesAvailable ? firebaseApp : null, firestore: servicesAvailable ? firestore : null, auth: servicesAvailable ? auth : null, user: userAuthState.user, isUserLoading: userAuthState.isUserLoading, userError: userAuthState.userError, }; }, [firebaseApp, firestore, auth, userAuthState]); return ( {children} ); }; /** * Hook to access core Firebase services and user authentication state. * Throws error if core services are not available or used outside provider. */ export const useFirebase = (): FirebaseServicesAndUser => { const context = useContext(FirebaseContext); if (context === undefined) { throw new Error('useFirebase must be used within a FirebaseProvider.'); } if (!context.areServicesAvailable || !context.firebaseApp || !context.firestore || !context.auth) { throw new Error('Firebase core services not available. Check FirebaseProvider props.'); } return { firebaseApp: context.firebaseApp, firestore: context.firestore, auth: context.auth, user: context.user, isUserLoading: context.isUserLoading, userError: context.userError, }; }; /** Hook to access Firebase Auth instance. */ export const useAuth = (): Auth => { const { auth } = useFirebase(); return auth; }; /** Hook to access Firestore instance. */ export const useFirestore = (): Firestore => { const { firestore } = useFirebase(); return firestore; }; /** Hook to access Firebase App instance. */ export const useFirebaseApp = (): FirebaseApp => { const { firebaseApp } = useFirebase(); return firebaseApp; }; type MemoFirebase = T & {__memo?: boolean}; export function useMemoFirebase(factory: () => T, deps: DependencyList): T | (MemoFirebase) { const memoized = useMemo(factory, deps); if(typeof memoized !== 'object' || memoized === null) return memoized; (memoized as MemoFirebase).__memo = true; return memoized; } /** * Hook specifically for accessing the authenticated user's state. * This provides the User object, loading status, and any auth errors. * @returns {UserHookResult} Object with user, isUserLoading, userError. */ export const useUser = (): UserHookResult => { // Renamed from useAuthUser const { user, isUserLoading, userError } = useFirebase(); // Leverages the main hook return { user, isUserLoading, userError }; };