272 lines
9.5 KiB
TypeScript
272 lines
9.5 KiB
TypeScript
// CRITICAL: React must be imported first before any Zustand/react imports
|
|
import React from 'react';
|
|
import ReactDOM from 'react-dom/client';
|
|
|
|
// Global error handlers (debug logs removed)
|
|
if (typeof window !== 'undefined') {
|
|
window.addEventListener('error', (_event) => {
|
|
// Error handling without debug logs
|
|
}, true);
|
|
|
|
window.addEventListener('unhandledrejection', (_event) => {
|
|
// Unhandled rejection handling without debug logs
|
|
});
|
|
}
|
|
|
|
import { BrowserRouter } from 'react-router-dom';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
// Action 4.6.1.5: Import QueryClient singleton setter
|
|
import { setQueryClient } from './utils/queryClientSingleton';
|
|
// CRITICAL FIX: Import lazy de react-hot-toast pour éviter les collisions de noms de variables après minification
|
|
import { LazyToaster } from './components/feedback/LazyToaster';
|
|
import { App } from './app/App';
|
|
import { logger } from './utils/logger';
|
|
import './index.css';
|
|
// Initialize i18next before React renders
|
|
import './lib/i18n';
|
|
// FIX #20: Initialize Sentry for error tracking
|
|
import { initSentry } from './lib/sentry';
|
|
// FE-API-019: Initialize MSW for development if enabled
|
|
import { env } from './config/env';
|
|
// Action 11.2.1.4: Initialize grid overlay utility (dev only)
|
|
import { initGridOverlay } from './utils/gridOverlay';
|
|
// Fix display issues (dev only) - KEPT for diagnostic tools only
|
|
import './utils/fixDisplayIssues';
|
|
// Fix input focus (dev only) - Détecte clavier vs souris
|
|
import { fixInputFocus } from './utils/fixInputFocus';
|
|
|
|
// HMR Force Update: 1765126900
|
|
|
|
// FIX #20: Initialize Sentry before React renders
|
|
initSentry();
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
retry: false,
|
|
refetchOnWindowFocus: false, // Keep disabled (intentional - avoid unnecessary refetches)
|
|
// Edge 6.1: Handle stale data in cache - Ensure stale data refreshed appropriately
|
|
staleTime: 1 * 60 * 1000, // Default: Consider data fresh for 1 minute (individual hooks can override)
|
|
gcTime: 5 * 60 * 1000, // Default: Keep in cache for 5 minutes (formerly cacheTime)
|
|
refetchOnMount: true, // Refetch stale data when component mounts (ensures fresh data)
|
|
refetchOnReconnect: true, // Refetch stale data when network reconnects
|
|
},
|
|
},
|
|
});
|
|
|
|
// Action 4.6.1.5: Set QueryClient singleton for state invalidation
|
|
setQueryClient(queryClient);
|
|
|
|
// FE-API-019: Initialize MSW worker for development
|
|
async function enableMocking() {
|
|
// FIX: Désactiver MSW par défaut pour éviter les erreurs "module is not defined"
|
|
// MSW doit être explicitement activé avec VITE_USE_MSW=1
|
|
if (!env.USE_MSW) {
|
|
return;
|
|
}
|
|
|
|
if (import.meta.env.DEV) {
|
|
try {
|
|
const { worker } = await import('./mocks/browser');
|
|
|
|
// Start the worker
|
|
await worker.start({
|
|
onUnhandledRequest: 'bypass', // Don't warn about unhandled requests
|
|
serviceWorker: {
|
|
url: '/mockServiceWorker.js',
|
|
options: {
|
|
// FIX: Désactiver le service worker MSW si problème de module
|
|
// Le service worker peut causer des erreurs "module is not defined"
|
|
scope: '/',
|
|
},
|
|
},
|
|
});
|
|
|
|
// FIX #18: Utiliser logger structuré
|
|
const { logger } = await import('./utils/logger');
|
|
logger.info('[MSW] Mock Service Worker started', { component: 'MSW' });
|
|
} catch (error) {
|
|
// FIX: Ignorer les erreurs MSW pour ne pas bloquer l'app
|
|
logger.warn('[MSW] Failed to start mock service worker', { error });
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for all stylesheets to be loaded before rendering
|
|
* This prevents "Layout was forced before the page was fully loaded" warning
|
|
* FIX: Version améliorée avec vérification plus robuste
|
|
*/
|
|
const waitForStylesheets = (): Promise<void> => {
|
|
return new Promise((resolve) => {
|
|
// Function to check if stylesheets are ready
|
|
const checkStylesheets = (): boolean => {
|
|
try {
|
|
// Check if document is ready
|
|
if (document.readyState !== 'complete' && document.readyState !== 'interactive') {
|
|
return false;
|
|
}
|
|
|
|
// Check all stylesheets
|
|
const stylesheets = Array.from(document.styleSheets);
|
|
if (stylesheets.length === 0) {
|
|
// No stylesheets yet, wait a bit
|
|
return false;
|
|
}
|
|
|
|
// Check if all stylesheets are loaded
|
|
let loadedCount = 0;
|
|
for (const sheet of stylesheets) {
|
|
try {
|
|
// Try to access sheet.cssRules to check if it's loaded
|
|
// This will throw if the stylesheet is not loaded yet
|
|
if (sheet.cssRules !== null || sheet.href === null) {
|
|
loadedCount++;
|
|
}
|
|
} catch (e) {
|
|
// Cross-origin stylesheets will throw, but they're usually loaded
|
|
// Check if it's a cross-origin error (CORS) or a loading error
|
|
if (sheet.href !== null) {
|
|
// Has href but can't access rules - likely cross-origin, assume loaded
|
|
loadedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// All stylesheets must be loaded
|
|
return loadedCount === stylesheets.length;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// If already complete and stylesheets are ready, wait a bit more to ensure processing
|
|
if (document.readyState === 'complete' && checkStylesheets()) {
|
|
// Use multiple requestAnimationFrame calls to ensure stylesheets are processed
|
|
// Add extra delay to prevent "Layout was forced" warning
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
// Additional delay to ensure all CSS is applied and layout is calculated
|
|
// This prevents the "Layout was forced before the page was fully loaded" warning
|
|
setTimeout(() => {
|
|
// One more check to ensure stylesheets are still ready
|
|
if (checkStylesheets()) {
|
|
resolve();
|
|
} else {
|
|
// If not ready, wait a bit more
|
|
setTimeout(() => resolve(), 100);
|
|
}
|
|
}, 100); // Increased delay for better reliability
|
|
});
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Wait for load event which fires after all resources (including stylesheets) are loaded
|
|
if (document.readyState === 'loading') {
|
|
window.addEventListener('load', () => {
|
|
// Wait a bit more to ensure all stylesheets are processed
|
|
let attempts = 0;
|
|
const maxAttempts = 20; // Increased attempts
|
|
const checkInterval = setInterval(() => {
|
|
attempts++;
|
|
if (checkStylesheets() || attempts >= maxAttempts) {
|
|
clearInterval(checkInterval);
|
|
// Use multiple requestAnimationFrame to ensure DOM is ready
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, 50);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}, 50); // Check every 50ms
|
|
}, { once: true });
|
|
} else {
|
|
// Interactive state - check and wait
|
|
let attempts = 0;
|
|
const maxAttempts = 20; // Increased attempts
|
|
const checkInterval = setInterval(() => {
|
|
attempts++;
|
|
if (checkStylesheets() || attempts >= maxAttempts) {
|
|
clearInterval(checkInterval);
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
setTimeout(() => {
|
|
resolve();
|
|
}, 50);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}, 50);
|
|
}
|
|
});
|
|
};
|
|
|
|
import { ThemeProvider } from './components/theme/ThemeProvider';
|
|
|
|
const renderApp = () => {
|
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
<React.StrictMode>
|
|
<QueryClientProvider client={queryClient}>
|
|
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
|
<BrowserRouter
|
|
future={{
|
|
v7_startTransition: true,
|
|
v7_relativeSplatPath: true,
|
|
}}
|
|
>
|
|
<App />
|
|
<LazyToaster position="top-right" />
|
|
</BrowserRouter>
|
|
</ThemeProvider>
|
|
</QueryClientProvider>
|
|
</React.StrictMode>,
|
|
);
|
|
};
|
|
|
|
// CRITICAL FIX: Précharger react-hot-toast AVANT de rendre l'app
|
|
// pour éviter les collisions de noms de variables (ie) lors de la minification
|
|
// Le module sera chargé dans un chunk séparé, évitant les conflits
|
|
const preloadToast = import('react-hot-toast')
|
|
.then((mod) => {
|
|
return mod;
|
|
})
|
|
.catch((_err) => {
|
|
// Ignorer les erreurs de préchargement
|
|
});
|
|
|
|
// Start MSW and preload toast before rendering the app
|
|
Promise.all([enableMocking(), preloadToast])
|
|
.then(() => {
|
|
// Initialization completed
|
|
})
|
|
.catch((error) => {
|
|
logger.error('[Init] Failed to initialize; continuing', {
|
|
error: error instanceof Error ? error.message : String(error),
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
});
|
|
})
|
|
.then(() => {
|
|
// Wait for stylesheets to be loaded before rendering
|
|
// This prevents "Layout was forced before the page was fully loaded" warning
|
|
return waitForStylesheets();
|
|
})
|
|
.finally(() => {
|
|
// Action 11.2.1.4: Initialize grid overlay utility (dev only)
|
|
initGridOverlay();
|
|
renderApp();
|
|
// Fix input focus après rendu
|
|
if (import.meta.env.DEV) {
|
|
setTimeout(() => {
|
|
fixInputFocus();
|
|
}, 500);
|
|
}
|
|
});
|