import React, { createContext, useContext, ReactNode, useState, useCallback, useEffect } from 'react'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { login, silentLogin as reduxSilentLogin, SignOutResult, getGetConnectedUser, getLoginResult, setAuthState } from '../../store/auth/authSlice'; import { authService } from '../../services/Auth-service'; import { RootState } from '../../store'; import { IUser } from '../../store/userProfile/types'; import { Logger } from '../../utils/Log'; import { eventEmitter, EventType } from '../../services/EventEmitter'; import { IAuthResult } from '../../store/auth/types'; import { getAppConfigurationResult, getAppConfiguration } from '../../store/configuration/appConfigSlice'; const logger = new Logger('AuthContext'); // Memoized selector using createSelector const selectAuthState = createSelector( [(state: RootState) => state.authReducer.user], (user) => ({ user, isAuthenticated: !!user }) ); // Define the shape of the authentication state export interface AuthState { isAuthenticated: boolean; user: IUser | null; login: (credentials: { email: string; password: string }) => Promise; logout: () => Promise; silentLogin: () => Promise; } // Create a default implementation with Redux-based methods export const createDefaultAuthContext = (dispatch: any, authState: { user: any; isAuthenticated: boolean }): AuthState => ({ isAuthenticated: authState.isAuthenticated, user: authState.user, login: async (credentials) => { try { logger.info(`Attempting login ${{ email: credentials.email }}`); await dispatch(login(credentials)); await dispatch(getGetConnectedUser()); return true; } catch (error) { logger.error(`Login failed', ${error}`); return false; } }, logout: async () => { await dispatch(SignOutResult()); }, silentLogin: async () => { try { logger.info('Attempting silent login'); await authService.silentLogin(); await dispatch(getGetConnectedUser()); return true; } catch (error) { logger.error(`Silent login failed, ${error}`); return false; } } }); const defaultAuthContext: AuthState = { isAuthenticated: false, user: null, login: async () => false, logout: async () => {}, silentLogin: async () => false, }; const AuthContext = createContext(defaultAuthContext); // New hook to manage authentication state across components export const useAuthState = () => { const dispatch = useAppDispatch(); const [isLoading, setIsLoading] = useState(true); // Selectors const storeUser = useAppSelector((state) => state.authReducer.user); const isAuthInProgress = useAppSelector((state) => state.authReducer.isAuthInProgress); const initializeAuth = useCallback(async () => { try { // Only attempt silent login if no user exists if (!storeUser ) { logger.info('Attempting initial silent login'); setIsLoading(true); await dispatch(reduxSilentLogin()); } } catch (error) { logger.error(`Initial auth setup failed: ${error}`); } finally { setIsLoading(false); } }, [dispatch, storeUser]); const setupAuthListeners = useCallback(() => { const AuthStateChangelistener = eventEmitter.addListener( EventType.AuthStateChange, async (result: { inProgress: boolean , error?: boolean}) => { logger.info('Auth State Changed'); await dispatch(setAuthState(result)); } ) // Authentication Result Listener const CurrentUserUpdatedListener = eventEmitter.addListener( EventType.AuthenticationResult, async (result: IAuthResult) => { logger.info(`Authentication Result Received', ${result}`); // Dispatch login result and fetch connected user await dispatch(getLoginResult(result)); await dispatch(getGetConnectedUser()); setIsLoading(false); } ); // Sign Out Listener const CurrentUserSignedOutListener = eventEmitter.addListener( EventType.CurrentUserSignedOut, () => { logger.info('User Signed Out'); dispatch(SignOutResult()); setIsLoading(false); } ); // App Configuration Listener const AppConfigurationListener = eventEmitter.addListener( EventType.AppConfiguration, (appConfiguration) => { dispatch(getAppConfigurationResult(appConfiguration)); } ); // Fetch app configuration dispatch(getAppConfiguration()); // Return cleanup function return () => { logger.info('Auth Listeners Cleanup'); CurrentUserUpdatedListener.remove(); CurrentUserSignedOutListener.remove(); AppConfigurationListener.remove(); AuthStateChangelistener.remove(); }; }, [dispatch]); useEffect(() => { const cleanup = setupAuthListeners(); return cleanup; }, [setupAuthListeners]); useEffect(() => { initializeAuth(); }, [initializeAuth]); return { isLoading, setIsLoading, storeUser, isAuthInProgress }; }; // Provider component that wraps the app and provides authentication state export const AuthProvider: React.FC<{ children: ReactNode; }> = ({ children}) => { const dispatch = useAppDispatch(); // Use memoized selector to get auth state const { user, isAuthenticated } = useAppSelector(selectAuthState); // Create default auth context with Redux methods const defaultMethods = createDefaultAuthContext(dispatch, { user, isAuthenticated }); // Merge default implementation with custom implementation const authContextValue: AuthState = { ...defaultMethods, }; return ( {children} ); }; // Custom hook to use the auth context export const useAuth = () => useContext(AuthContext);