import { useEffect, useState } from 'react'; import { useAuthStore } from '@/stores/auth'; import { useUIStore } from '@/stores/ui'; import { ErrorBoundary } from '@/components/ErrorBoundary'; import { PWAInstallBanner } from '@/components/pwa/PWAInstallBanner'; import { ToastProvider } from '@/components/feedback/ToastProvider'; import { AppRouter } from '@/router'; import { csrfService } from '@/services/csrf'; import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts'; import { KeyboardShortcutsHelp } from '@/components/keyboard/KeyboardShortcutsHelp'; import { useStateHydration } from '@/utils/stateHydration'; import { useQueryInvalidation } from '@/hooks/useQueryInvalidation'; export function App() { const { refreshUser } = useAuthStore(); const { theme, setTheme, language, setLanguage } = useUIStore(); const [showKeyboardHelp, setShowKeyboardHelp] = useState(false); // FE-COMP-022: Enable global keyboard shortcuts useGlobalKeyboardShortcuts({ enabled: true, onHelpOpen: () => setShowKeyboardHelp(true), }); // FE-STATE-003: Hydrate state from server on app load const { isHydrating } = 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(); // Initialiser l'application useEffect(() => { // Note: refreshUser is now called by useStateHydration, but we keep it here // as a fallback in case hydration is disabled if (!isHydrating) { refreshUser(); } // 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) => { console.warn('Failed to fetch CSRF token on app init:', error); }); } }; checkAndFetchCSRF(); // Appliquer le thème au chargement (le store persist le fait déjà, mais on s'assure qu'il est appliqué) 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'); } } }, [refreshUser, setTheme, theme, language, setLanguage]); // É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]); return ( {/* PWA Install Banner */} {/* Keyboard Shortcuts Help */} setShowKeyboardHelp(false)} /> ); }