diff --git a/features/conversation-assistant/frontend-dev/package.json b/features/conversation-assistant/frontend-dev/package.json index 44b14f0b6..0435f4c10 100644 --- a/features/conversation-assistant/frontend-dev/package.json +++ b/features/conversation-assistant/frontend-dev/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@lilith/design-tokens": "workspace:*", + "@lilith/service-react-bootstrap": "^1.0.0", "@lilith/react-hooks": "workspace:*", "@lilith/react-query-utils": "workspace:*", "@lilith/types": "workspace:*", diff --git a/features/conversation-assistant/frontend-dev/src/main.tsx b/features/conversation-assistant/frontend-dev/src/main.tsx index d6528314c..65f6af9a2 100644 --- a/features/conversation-assistant/frontend-dev/src/main.tsx +++ b/features/conversation-assistant/frontend-dev/src/main.tsx @@ -1,43 +1,28 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { BrowserRouter } from 'react-router-dom'; +import { bootstrap } from '@lilith/service-react-bootstrap'; import { ThemeProvider } from '@lilith/ui-theme'; import { ToastProvider } from '@lilith/ui-feedback'; import { App } from './App'; -import { ErrorBoundary } from './components/ErrorBoundary'; import './index.css'; -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 30_000, - refetchOnWindowFocus: false, - retry: 1, - }, - mutations: { - retry: 0, +bootstrap({ + App, + queryClient: { + defaultOptions: { + queries: { + staleTime: 30_000, + refetchOnWindowFocus: false, + retry: 1, + }, + mutations: { + retry: 0, + }, }, }, + providers: { + router: 'browser', + wrappers: [ + { Provider: ThemeProvider, props: { defaultTheme: 'cyberpunk' } }, + { Provider: ToastProvider, props: {} }, + ], + }, }); - -const rootElement = document.getElementById('root'); -if (!rootElement) { - throw new Error('Root element not found'); -} - -ReactDOM.createRoot(rootElement).render( - - - - - - - - - - - - - -); diff --git a/features/feature-flags/frontend-admin/package.json b/features/feature-flags/frontend-admin/package.json index b703bb076..dd2381512 100644 --- a/features/feature-flags/frontend-admin/package.json +++ b/features/feature-flags/frontend-admin/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@lilith/feature-flags": "workspace:*", + "@lilith/service-react-bootstrap": "^1.0.0", "@lilith/types": "workspace:*", "@tanstack/react-query": "^5.62.0", "clsx": "^2.1.1", diff --git a/features/feature-flags/frontend-admin/src/main.tsx b/features/feature-flags/frontend-admin/src/main.tsx index 8bc14e294..dde01477c 100644 --- a/features/feature-flags/frontend-admin/src/main.tsx +++ b/features/feature-flags/frontend-admin/src/main.tsx @@ -1,25 +1,18 @@ -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { BrowserRouter } from 'react-router-dom'; +import { bootstrap } from '@lilith/service-react-bootstrap'; import App from './App'; import './index.css'; -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 1000 * 60, // 1 minute - retry: 1, +bootstrap({ + App, + queryClient: { + defaultOptions: { + queries: { + staleTime: 1000 * 60, // 1 minute + retry: 1, + }, }, }, + providers: { + router: 'browser', + }, }); - -createRoot(document.getElementById('root')!).render( - - - - - - - , -); diff --git a/features/landing/frontend-public/package.json b/features/landing/frontend-public/package.json index bbb8d6cd6..8afa466ce 100644 --- a/features/landing/frontend-public/package.json +++ b/features/landing/frontend-public/package.json @@ -34,6 +34,7 @@ }, "dependencies": { "@lilith/age-verification-react": "workspace:*", + "@lilith/service-react-bootstrap": "^1.0.0", "@lilith/analytics-client": "workspace:*", "@lilith/api-client": "workspace:*", "@lilith/auth-provider": "workspace:*", diff --git a/features/landing/frontend-public/src/main.tsx b/features/landing/frontend-public/src/main.tsx index 77c9fd477..b75d4c644 100644 --- a/features/landing/frontend-public/src/main.tsx +++ b/features/landing/frontend-public/src/main.tsx @@ -1,10 +1,7 @@ import { AnalyticsProvider } from '@lilith/analytics-client/react' import { I18nProvider } from '@lilith/i18n' import { ThemeProvider } from '@ui/theme' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import React from 'react' -import ReactDOM from 'react-dom/client' -import { ErrorBoundary } from 'react-error-boundary' +import { bootstrap } from '@lilith/service-react-bootstrap' import { Toaster } from 'react-hot-toast' import App from './App' @@ -13,14 +10,8 @@ import { workers } from './config' import { CartProvider, DevUserProvider } from './contexts' import { bundledResources, useApiMode, LANDING_NAMESPACES } from './locales' -// Glassmorphism tokens imported from @lilith/ui-glassmorphism (imported by Header.css and other components) import './index.css' -// i18n Strategy: -// - Development (default): Use bundled JSON locales for instant loading (0ms) -// - API mode (VITE_I18N_USE_API=true): Use ML translation API for testing -// The bundled approach eliminates network dependency during development - /** * Initialize MSW for development API mocking * @@ -39,40 +30,20 @@ async function initMSW(): Promise { await startMockServiceWorker() } -// Create a QueryClient for React Query -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 1000 * 60 * 5, // 5 minutes - retry: 1, - }, - }, -}) - // Register service worker for translation caching -if ('serviceWorker' in navigator && import.meta.env.PROD) { - window.addEventListener('load', () => { - navigator.serviceWorker - .register(workers.i18n) - .then((registration) => { - console.log('[i18n] Service worker registered:', registration.scope) - }) - .catch((error) => { - console.warn('[i18n] Service worker registration failed:', error) - }) - }) -} - -// Note: ErrorFallback cannot use i18n because it renders outside the I18nProvider -// when the error boundary catches errors during provider initialization. -// This is an acceptable exception - error boundaries need hardcoded fallback text. -function ErrorFallback({ error }: { error: Error }) { - return ( -
-

Something went wrong

-
{error.message}
-
- ) +function registerServiceWorker(): void { + if ('serviceWorker' in navigator && import.meta.env.PROD) { + window.addEventListener('load', () => { + navigator.serviceWorker + .register(workers.i18n) + .then((registration) => { + console.log('[i18n] Service worker registered:', registration.scope) + }) + .catch((error) => { + console.warn('[i18n] Service worker registration failed:', error) + }) + }) + } } // Environment-based analytics configuration @@ -113,33 +84,40 @@ const i18nConfig = { enableMLFallback: useApiMode, } -// Render app - MSW must be ready before render for API mocking to work -async function renderApp() { - await initMSW() - - ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - {/* Single source of truth for i18n - all components use useTranslation() */} - - - - - - {/* Dev-only user type switcher for testing different user states */} - - - - - - - - - - +// App wrapper component to include DevUserSwitcher and Toaster +function AppWithExtras() { + return ( + <> + + {/* Dev-only user type switcher for testing different user states */} + + + ) } -renderApp() +// Register service worker +registerServiceWorker() + +// Bootstrap the application +bootstrap({ + App: AppWithExtras, + queryClient: { + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, // 5 minutes + retry: 1, + }, + }, + }, + providers: { + wrappers: [ + { Provider: AnalyticsProvider, props: { config: analyticsConfig } }, + { Provider: I18nProvider, props: { config: i18nConfig } }, + { Provider: ThemeProvider, props: { defaultTheme: 'cyberpunk' } }, + { Provider: DevUserProvider, props: {} }, + { Provider: CartProvider, props: {} }, + ], + }, + onBeforeMount: initMSW, +}) diff --git a/features/marketplace/frontend-public/package.json b/features/marketplace/frontend-public/package.json index ce1bfbf89..c43a82380 100644 --- a/features/marketplace/frontend-public/package.json +++ b/features/marketplace/frontend-public/package.json @@ -21,6 +21,7 @@ "lint": "eslint src --ext ts,tsx" }, "dependencies": { + "@lilith/service-react-bootstrap": "^1.0.0", "@dnd-kit/core": "^6.1.0", "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/sortable": "^8.0.0", diff --git a/features/marketplace/frontend-public/src/main.tsx b/features/marketplace/frontend-public/src/main.tsx index 34fb25245..f48d9f5ea 100644 --- a/features/marketplace/frontend-public/src/main.tsx +++ b/features/marketplace/frontend-public/src/main.tsx @@ -1,5 +1,4 @@ -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; +import { bootstrap } from '@lilith/service-react-bootstrap'; import { I18nProvider } from '@lilith/i18n'; import { App } from './app/App'; import './index.css'; @@ -41,10 +40,9 @@ const i18nConfig = { enableMLFallback: false, }; -createRoot(document.getElementById('root')!).render( - - - - - -); +bootstrap({ + App, + providers: { + custom: [{ Provider: I18nProvider, props: { config: i18nConfig } }], + }, +}); diff --git a/features/platform-admin/frontend-admin/package.json b/features/platform-admin/frontend-admin/package.json index b721d3515..be7bf5098 100644 --- a/features/platform-admin/frontend-admin/package.json +++ b/features/platform-admin/frontend-admin/package.json @@ -14,6 +14,7 @@ "test:e2e:headed": "playwright test --headed" }, "dependencies": { + "@lilith/service-react-bootstrap": "^1.0.0", "@lilith/analytics-frontend-admin": "workspace:*", "@lilith/attribute-hooks": "workspace:*", "@lilith/attributes-admin": "workspace:*", diff --git a/features/platform-admin/frontend-admin/src/main.tsx b/features/platform-admin/frontend-admin/src/main.tsx index 8886b6661..7e3a28338 100644 --- a/features/platform-admin/frontend-admin/src/main.tsx +++ b/features/platform-admin/frontend-admin/src/main.tsx @@ -1,25 +1,11 @@ -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { BrowserRouter } from 'react-router-dom'; +import { bootstrap } from '@lilith/service-react-bootstrap'; import { App } from './App'; import './index.css'; -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 30_000, - retry: 1, - }, +bootstrap({ + App, + queryClient: { defaultOptions: { queries: { staleTime: 30_000, retry: 1 } } }, + providers: { + router: 'browser', }, }); - -createRoot(document.getElementById('root')!).render( - - - - - - - , -); diff --git a/features/profile/frontend-app/package.json b/features/profile/frontend-app/package.json index 2281ddc6b..ebc3d7475 100644 --- a/features/profile/frontend-app/package.json +++ b/features/profile/frontend-app/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@lilith/attributes-admin": "workspace:*", + "@lilith/service-react-bootstrap": "^1.0.0", "@lilith/auth-provider": "workspace:*", "@lilith/feature-flags": "workspace:*", "@lilith/types": "workspace:*", diff --git a/features/profile/frontend-app/src/main.tsx b/features/profile/frontend-app/src/main.tsx index 6efdf2c7d..e7a7c5d46 100644 --- a/features/profile/frontend-app/src/main.tsx +++ b/features/profile/frontend-app/src/main.tsx @@ -1,19 +1,7 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { BrowserRouter } from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { bootstrap } from '@lilith/service-react-bootstrap'; import { ThemeProvider } from 'styled-components'; import { App } from './App'; -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 1000 * 60 * 5, // 5 minutes - retry: 1, - }, - }, -}); - // Default theme (can be enhanced with @lilith/ui-theme) const theme = { colors: { @@ -131,14 +119,20 @@ const theme = { }, }; -ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - - - -); +bootstrap({ + App, + queryClient: { + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, // 5 minutes + retry: 1, + }, + }, + }, + providers: { + router: 'browser', + wrappers: [ + { Provider: ThemeProvider, props: { theme } }, + ], + }, +}); diff --git a/features/seo/frontend-admin/package.json b/features/seo/frontend-admin/package.json index 2fd0e9d64..a4f86061d 100644 --- a/features/seo/frontend-admin/package.json +++ b/features/seo/frontend-admin/package.json @@ -12,6 +12,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@lilith/service-react-bootstrap": "^1.0.0", "@lilith/seo-shared": "workspace:*", "@tanstack/react-query": "^5.75.7", "react": "^19.0.0", diff --git a/features/seo/frontend-admin/src/main.tsx b/features/seo/frontend-admin/src/main.tsx index 45f50a670..0320efc44 100644 --- a/features/seo/frontend-admin/src/main.tsx +++ b/features/seo/frontend-admin/src/main.tsx @@ -1,21 +1,8 @@ -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { bootstrap } from '@lilith/service-react-bootstrap'; import App from './App'; -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 5 * 60 * 1000, // 5 minutes - retry: 1, - }, - }, +bootstrap({ + App, + queryClient: { defaultOptions: { queries: { staleTime: 5 * 60 * 1000, retry: 1 } } }, + providers: {}, }); - -createRoot(document.getElementById('root')!).render( - - - - - -); diff --git a/features/seo/frontend-public/package.json b/features/seo/frontend-public/package.json index 8f2b485b4..4fd09898b 100644 --- a/features/seo/frontend-public/package.json +++ b/features/seo/frontend-public/package.json @@ -16,6 +16,7 @@ "test:e2e:docker": "docker build -f e2e/Dockerfile -t seo-e2e-test . && docker run --rm seo-e2e-test" }, "dependencies": { + "@lilith/service-react-bootstrap": "^1.0.0", "@lilith/i18n": "workspace:*", "@lilith/seo-shared": "workspace:*", "@lilith/ui-astro": "^1.0.0", diff --git a/features/seo/frontend-public/src/main.tsx b/features/seo/frontend-public/src/main.tsx index c56a0498b..39718069d 100644 --- a/features/seo/frontend-public/src/main.tsx +++ b/features/seo/frontend-public/src/main.tsx @@ -1,6 +1,4 @@ -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { bootstrap } from '@lilith/service-react-bootstrap'; import { I18nProvider } from '@lilith/i18n'; import App from './App'; @@ -9,15 +7,6 @@ import '@lilith/ui-astro/bundles/cyberpunk'; // Apply theme tokens to document (body, headings, links, scrollbars) import './index.css'; -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 30 * 1000, - retry: 1, - }, - }, -}); - // I18n configuration for SEO frontend const i18nConfig = { apiUrl: '/api/translations', @@ -26,12 +15,10 @@ const i18nConfig = { debug: import.meta.env.DEV, }; -createRoot(document.getElementById('root')!).render( - - - - - - - , -); +bootstrap({ + App, + queryClient: { defaultOptions: { queries: { staleTime: 30_000, retry: 1 } } }, + providers: { + custom: [{ Provider: I18nProvider, props: { config: i18nConfig } }], + }, +}); diff --git a/features/status-dashboard/frontend-public/package.json b/features/status-dashboard/frontend-public/package.json index 0802ca23c..6cbdd079e 100644 --- a/features/status-dashboard/frontend-public/package.json +++ b/features/status-dashboard/frontend-public/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@lilith/health-client": "workspace:*", + "@lilith/service-react-bootstrap": "^1.0.0", "@lilith/ui-primitives": "^1.1.1", "@lilith/ui-theme": "^1.0.4", "react": "^18.2.0", diff --git a/features/status-dashboard/frontend-public/src/main.tsx b/features/status-dashboard/frontend-public/src/main.tsx index 6c14464c4..f8ae90b2c 100644 --- a/features/status-dashboard/frontend-public/src/main.tsx +++ b/features/status-dashboard/frontend-public/src/main.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; +import { bootstrap } from '@lilith/service-react-bootstrap'; import { App } from './App'; -ReactDOM.createRoot(document.getElementById('root')!).render( - - - -); +bootstrap({ + App, + providers: { + router: 'browser', + }, +}); diff --git a/features/webmap/frontend-public/package.json b/features/webmap/frontend-public/package.json index 15f808397..9853f172d 100644 --- a/features/webmap/frontend-public/package.json +++ b/features/webmap/frontend-public/package.json @@ -10,6 +10,7 @@ "type-check": "tsc --noEmit" }, "dependencies": { + "@lilith/service-react-bootstrap": "^1.0.0", "@lilith/webmap-shared": "workspace:*", "@lilith/types": "workspace:*", "@lilith/api-client": "workspace:*", diff --git a/features/webmap/frontend-public/src/main.tsx b/features/webmap/frontend-public/src/main.tsx index fec6b56d7..a27145e5c 100644 --- a/features/webmap/frontend-public/src/main.tsx +++ b/features/webmap/frontend-public/src/main.tsx @@ -1,9 +1,7 @@ -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; +import { bootstrap } from '@lilith/service-react-bootstrap'; import { App } from './app/App'; -createRoot(document.getElementById('root')!).render( - - - -); +bootstrap({ + App, + providers: {}, +});