| '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; |
| } |
|
|
| |
| interface UserAuthState { |
| user: User | null; |
| isUserLoading: boolean; |
| userError: Error | null; |
| } |
|
|
| |
| export interface FirebaseContextState { |
| areServicesAvailable: boolean; |
| firebaseApp: FirebaseApp | null; |
| firestore: Firestore | null; |
| auth: Auth | null; |
| |
| user: User | null; |
| isUserLoading: boolean; |
| userError: Error | null; |
| } |
|
|
| |
| export interface FirebaseServicesAndUser { |
| firebaseApp: FirebaseApp; |
| firestore: Firestore; |
| auth: Auth; |
| user: User | null; |
| isUserLoading: boolean; |
| userError: Error | null; |
| } |
|
|
| |
| export interface UserHookResult { |
| user: User | null; |
| isUserLoading: boolean; |
| userError: Error | null; |
| } |
|
|
| |
| export const FirebaseContext = createContext<FirebaseContextState | undefined>(undefined); |
|
|
| |
| |
| |
| export const FirebaseProvider: React.FC<FirebaseProviderProps> = ({ |
| children, |
| firebaseApp, |
| firestore, |
| auth, |
| }) => { |
| const [userAuthState, setUserAuthState] = useState<UserAuthState>({ |
| user: null, |
| isUserLoading: true, |
| userError: null, |
| }); |
|
|
| |
| useEffect(() => { |
| if (!auth) { |
| setUserAuthState({ user: null, isUserLoading: false, userError: new Error("Auth service not provided.") }); |
| return; |
| } |
|
|
| setUserAuthState({ user: null, isUserLoading: true, userError: null }); |
|
|
| const unsubscribe = onAuthStateChanged( |
| auth, |
| (firebaseUser) => { |
| setUserAuthState({ user: firebaseUser, isUserLoading: false, userError: null }); |
| }, |
| (error) => { |
| console.error("FirebaseProvider: onAuthStateChanged error:", error); |
| setUserAuthState({ user: null, isUserLoading: false, userError: error }); |
| } |
| ); |
| return () => unsubscribe(); |
| }, [auth]); |
|
|
| |
| 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 ( |
| <FirebaseContext.Provider value={contextValue}> |
| <FirebaseErrorListener /> |
| {children} |
| </FirebaseContext.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, |
| }; |
| }; |
|
|
| |
| export const useAuth = (): Auth => { |
| const { auth } = useFirebase(); |
| return auth; |
| }; |
|
|
| |
| export const useFirestore = (): Firestore => { |
| const { firestore } = useFirebase(); |
| return firestore; |
| }; |
|
|
| |
| export const useFirebaseApp = (): FirebaseApp => { |
| const { firebaseApp } = useFirebase(); |
| return firebaseApp; |
| }; |
|
|
| type MemoFirebase <T> = T & {__memo?: boolean}; |
|
|
| export function useMemoFirebase<T>(factory: () => T, deps: DependencyList): T | (MemoFirebase<T>) { |
| const memoized = useMemo(factory, deps); |
| |
| if(typeof memoized !== 'object' || memoized === null) return memoized; |
| (memoized as MemoFirebase<T>).__memo = true; |
| |
| return memoized; |
| } |
|
|
| |
| |
| |
| |
| |
| export const useUser = (): UserHookResult => { |
| const { user, isUserLoading, userError } = useFirebase(); |
| return { user, isUserLoading, userError }; |
| }; |