♻️ Refactor frontend apps to use @lilith/service-react-bootstrap
Replace boilerplate React bootstrap code with standardized bootstrap helper: - conversation-assistant, feature-flags, landing, marketplace - platform-admin, profile, seo (admin/public), status-dashboard, webmap Benefits: Consistent QueryClient, ErrorBoundary, StrictMode, and provider composition 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bffa25b2e5
commit
e6d274fa06
20 changed files with 146 additions and 230 deletions
|
|
@ -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:*",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<React.StrictMode>
|
||||
<ErrorBoundary>
|
||||
<ThemeProvider defaultTheme="cyberpunk">
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ToastProvider>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ToastProvider>
|
||||
</QueryClientProvider>
|
||||
</ThemeProvider>
|
||||
</ErrorBoundary>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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:*",
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
|||
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 (
|
||||
<div role="alert" style={{ padding: '2rem', textAlign: 'center' }}>
|
||||
<h1>Something went wrong</h1>
|
||||
<pre style={{ color: 'red' }}>{error.message}</pre>
|
||||
</div>
|
||||
)
|
||||
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(
|
||||
<React.StrictMode>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AnalyticsProvider config={analyticsConfig}>
|
||||
{/* Single source of truth for i18n - all components use useTranslation() */}
|
||||
<I18nProvider config={i18nConfig}>
|
||||
<ThemeProvider defaultTheme="cyberpunk">
|
||||
<DevUserProvider>
|
||||
<CartProvider>
|
||||
<App />
|
||||
{/* Dev-only user type switcher for testing different user states */}
|
||||
<DevUserSwitcher />
|
||||
<Toaster position="top-center" />
|
||||
</CartProvider>
|
||||
</DevUserProvider>
|
||||
</ThemeProvider>
|
||||
</I18nProvider>
|
||||
</AnalyticsProvider>
|
||||
</QueryClientProvider>
|
||||
</ErrorBoundary>
|
||||
</React.StrictMode>
|
||||
// App wrapper component to include DevUserSwitcher and Toaster
|
||||
function AppWithExtras() {
|
||||
return (
|
||||
<>
|
||||
<App />
|
||||
{/* Dev-only user type switcher for testing different user states */}
|
||||
<DevUserSwitcher />
|
||||
<Toaster position="top-center" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<StrictMode>
|
||||
<I18nProvider config={i18nConfig}>
|
||||
<App />
|
||||
</I18nProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
bootstrap({
|
||||
App,
|
||||
providers: {
|
||||
custom: [{ Provider: I18nProvider, props: { config: i18nConfig } }],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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:*",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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:*",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
bootstrap({
|
||||
App,
|
||||
queryClient: {
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
retry: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
providers: {
|
||||
router: 'browser',
|
||||
wrappers: [
|
||||
{ Provider: ThemeProvider, props: { theme } },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<I18nProvider config={i18nConfig}>
|
||||
<App />
|
||||
</I18nProvider>
|
||||
</QueryClientProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
bootstrap({
|
||||
App,
|
||||
queryClient: { defaultOptions: { queries: { staleTime: 30_000, retry: 1 } } },
|
||||
providers: {
|
||||
custom: [{ Provider: I18nProvider, props: { config: i18nConfig } }],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
bootstrap({
|
||||
App,
|
||||
providers: {
|
||||
router: 'browser',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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:*",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
);
|
||||
bootstrap({
|
||||
App,
|
||||
providers: {},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue