diff --git a/apps/web/src/providers/AuthProvider.tsx b/apps/web/src/providers/AuthProvider.tsx new file mode 100644 index 000000000..170ffcd1a --- /dev/null +++ b/apps/web/src/providers/AuthProvider.tsx @@ -0,0 +1,96 @@ +import { ReactNode, useEffect, useState } from 'react'; +import { useAuthStore } from '@/features/auth/store/authStore'; +import { TokenStorage } from '@/services/tokenStorage'; +import { logger } from '@/utils/logger'; + +/** + * P3.1: Centralized Auth Provider + * + * Manages authentication state initialization in a single location, + * eliminating race conditions between App.tsx, router, and API interceptors. + * + * Responsibilities: + * - Check for existing tokens on mount + * - Initialize auth state via refreshUser() if tokens exist + * - Show loading screen while auth is initializing + * - Provide ready state to children only after auth check completes + */ + +interface AuthProviderProps { + children: ReactNode; + /** + * Optional loading component to show while auth is initializing. + * If not provided, uses default loading screen. + */ + loadingComponent?: ReactNode; +} + +export const AuthProvider = ({ children, loadingComponent }: AuthProviderProps) => { + const [isAuthReady, setIsAuthReady] = useState(false); + const { refreshUser } = useAuthStore(); + + useEffect(() => { + const initializeAuth = async () => { + try { + // Check if user has tokens (httpOnly cookies or localStorage) + const hasTokens = TokenStorage.hasTokens(); + + if (hasTokens) { + logger.debug('[AuthProvider] Tokens found, refreshing user state'); + + // Wait for auth check to complete + // This prevents race condition where router renders before auth is ready + await refreshUser(); + + logger.debug('[AuthProvider] User state refreshed successfully'); + } else { + logger.debug('[AuthProvider] No tokens found, user is not authenticated'); + } + } catch (error) { + // Log error but don't block app initialization + // User will be treated as unauthenticated + logger.error('[AuthProvider] Auth initialization failed', { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + }); + } finally { + // Always set ready, even if auth check fails + // This ensures the app doesn't get stuck in loading state + setIsAuthReady(true); + } + }; + + initializeAuth(); + }, [refreshUser]); + + // Show loading screen while auth is initializing + if (!isAuthReady) { + return loadingComponent || ( +
Chargement...
+