import { useEffect, useState } from 'react'; import '@/styles/premium-utilities.css'; import '@/styles/visual-enhancements.css'; import { useQueryClient } from '@tanstack/react-query'; import { useAuthStore } from '@/features/auth/store/authStore'; import { useUIStore } from '@/stores/ui'; import { ErrorBoundary } from '@/components/ErrorBoundary'; import { ToastProvider } from '@/components/feedback/ToastProvider'; import { AstralBackground } from '@/components/ui/AstralBackground'; import { OfflineIndicator } from '@/components/OfflineIndicator'; import { AppRouter } from '@/router'; import { csrfService } from '@/services/csrf'; import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts'; import { KeyboardShortcutsPanel } from '@/components/ui/KeyboardShortcutsPanel'; import { useStateHydration } from '@/utils/stateHydration'; import { useQueryInvalidation } from '@/hooks/useQueryInvalidation'; import { setupReactQuerySync } from '@/utils/reactQuerySync'; import { logger } from '@/utils/logger'; import { AudioProvider } from '@/context/AudioContext'; export function App() { const { refreshUser } = useAuthStore(); const { theme, setTheme, language, setLanguage } = useUIStore(); const [showKeyboardHelp, setShowKeyboardHelp] = useState(false); // P1.2: Auth initialization state to prevent race condition const [isAuthReady, setIsAuthReady] = useState(false); const queryClient = useQueryClient(); // FE-COMP-022: Enable global keyboard shortcuts useGlobalKeyboardShortcuts({ enabled: true, onHelpOpen: () => setShowKeyboardHelp(true), }); // FE-STATE-003: Hydrate state from server on app load useStateHydration({ hydrateAuth: true, hydrateLibrary: false, // Can be enabled if needed hydrateChat: false, // Can be enabled if needed requireAuth: false, // Hydrate auth even if not authenticated (to check status) }); // FE-STATE-004: Listen for query invalidation events useQueryInvalidation(); // Action 2.3.1.2: Initialize React Query cache synchronization across tabs useEffect(() => { const cleanup = setupReactQuerySync(queryClient, { enabled: true, channelName: 'veza-react-query-sync', }); return cleanup; }, [queryClient]); // Initialiser l'application useEffect(() => { // CRITIQUE FIX #18: refreshUser est maintenant appelé par useStateHydration // Ne pas appeler refreshUser ici pour éviter les appels multiples // useStateHydration gère déjà l'hydratation de l'état d'authentification // Ce useEffect ne fait plus qu'initialiser les autres aspects de l'app // Récupérer le token CSRF si l'utilisateur est déjà authentifié // (refreshUser() est asynchrone, donc on vérifie après un court délai) const checkAndFetchCSRF = async () => { // Attendre un peu pour que refreshUser() se termine await new Promise((resolve) => setTimeout(resolve, 100)); const { isAuthenticated } = useAuthStore.getState(); if (isAuthenticated) { csrfService.refreshToken().catch((error) => { logger.warn('Failed to fetch CSRF token on app init', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); }); } }; checkAndFetchCSRF(); // Appliquer le thème au chargement (le store persist le fait déjà, mais on s'assure qu'il est appliqué) // Forcer dark mode par défaut si pas encore défini if (!theme || theme === 'system') { const root = document.documentElement; if ( !root.classList.contains('dark') && !root.classList.contains('light') ) { setTheme('dark'); } else { setTheme(theme); } } else { setTheme(theme); } // Synchroniser la langue avec i18n au chargement if (typeof window !== 'undefined' && window.i18n) { const currentLang = window.i18n.language || language; if (currentLang !== language) { window.i18n.changeLanguage(language); } else if (language !== currentLang) { setLanguage(currentLang as 'en' | 'fr'); } } }, [setTheme, theme, language, setLanguage]); // P1.2: Initialize auth state before rendering app // With httpOnly cookies we cannot read tokens in JS; always call refreshUser() // so getMe() is used to verify auth (cookies sent automatically). useEffect(() => { const initAuth = async () => { try { await refreshUser(); } catch (error) { logger.error('[App] Auth initialization failed', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); } finally { setIsAuthReady(true); } }; initAuth(); }, [refreshUser]); // Écouter les changements de préférence système pour le mode 'system' useEffect(() => { if (theme !== 'system') return; const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleChange = (e: MediaQueryListEvent) => { const root = document.documentElement; if (e.matches) { root.classList.add('dark'); } else { root.classList.remove('dark'); } }; // Écouter les changements if (mediaQuery.addEventListener) { mediaQuery.addEventListener('change', handleChange); } else { // Fallback pour les navigateurs plus anciens mediaQuery.addListener(handleChange); } return () => { if (mediaQuery.removeEventListener) { mediaQuery.removeEventListener('change', handleChange); } else { mediaQuery.removeListener(handleChange); } }; }, [theme]); // P1.2: Show loading screen while auth is initializing if (!isAuthReady) { return (

Chargement...

); } return ( {/* Offline/Online Status Indicator */} {/* PWA Install Banner - Disabled for now as it is too intrusive */} {/* */} {/* Keyboard Shortcuts Panel (Discord-style overlay) */} setShowKeyboardHelp(false)} /> ); }