Moving between pages sometimes left a white screen until a reload: an error outside the page's own error boundary (the layout, the sign-in gate) unmounted the whole app. An error boundary now wraps the whole app, shows the error's name and message, and tries again when the route changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.8 KiB
TypeScript
70 lines
2.8 KiB
TypeScript
import { StrictMode } from 'react'
|
|
import { createRoot } from 'react-dom/client'
|
|
import { ClerkProvider } from '@clerk/react'
|
|
import { enUS, idID } from '@clerk/localizations'
|
|
import { MutationCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
import { BrowserRouter, useLocation } from 'react-router-dom'
|
|
import { EngineError } from './api/client'
|
|
import App from './App'
|
|
import { ErrorBoundary } from './components/ErrorBoundary'
|
|
import { forgetReads } from './express/results'
|
|
import { useLang } from './i18n'
|
|
import './styles/mockup.css'
|
|
import './styles/app.css'
|
|
import './styles/theme.css'
|
|
|
|
const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
|
|
if (!clerkPubKey) {
|
|
throw new Error('Missing VITE_CLERK_PUBLISHABLE_KEY (set it, or keep ../learning-lab/backend/.env available)')
|
|
}
|
|
|
|
const queryClient: QueryClient = new QueryClient({
|
|
mutationCache: new MutationCache({
|
|
// Runs before the action's own onSuccess, whose invalidations must not get a remembered read.
|
|
onSuccess: forgetReads,
|
|
// Any action may have spent workspace credits, so the topbar balance is read again after each one.
|
|
onSettled: () => queryClient.invalidateQueries({ queryKey: ['credits'] }),
|
|
}),
|
|
defaultOptions: {
|
|
// Retrying a 4xx (auth, credits, validation) only repeats the same answer.
|
|
queries: {
|
|
retry: (n, e) => n < 2 && !(e instanceof EngineError && e.status >= 400 && e.status < 500 && e.status !== 429),
|
|
// A busy engine (rate limited) is asked again only after it has had time to recover; an
|
|
// immediate retry from every query at once is what keeps it over the limit.
|
|
retryDelay: (n, e) => (e instanceof EngineError && (e.status === 503 || e.status === 429) ? 5000 : 1000) * 2 ** n,
|
|
},
|
|
},
|
|
})
|
|
|
|
// Clerk's sign-in and organization screens match the theme in styles/theme.css.
|
|
const clerkAppearance = {
|
|
variables: { colorPrimary: '#1570ef', colorText: '#101828', fontFamily: "'Plus Jakarta Sans', system-ui, sans-serif", borderRadius: '12px' },
|
|
}
|
|
|
|
// A crash outside a page (the layout, the sign-in gate) would otherwise unmount everything and leave
|
|
// a white page until a reload; moving to another page tries again.
|
|
function AppBoundary() {
|
|
const { pathname } = useLocation()
|
|
return <ErrorBoundary resetKey={pathname} className="card app-crash"><App /></ErrorBoundary>
|
|
}
|
|
|
|
// Clerk's own sign-in and organization screens follow the app language.
|
|
function Root() {
|
|
const lang = useLang()
|
|
return (
|
|
<ClerkProvider publishableKey={clerkPubKey} afterSignOutUrl="/" localization={lang === 'id' ? idID : enUS} appearance={clerkAppearance}>
|
|
<QueryClientProvider client={queryClient}>
|
|
<BrowserRouter>
|
|
<AppBoundary />
|
|
</BrowserRouter>
|
|
</QueryClientProvider>
|
|
</ClerkProvider>
|
|
)
|
|
}
|
|
|
|
createRoot(document.getElementById('root')!).render(
|
|
<StrictMode>
|
|
<Root />
|
|
</StrictMode>,
|
|
)
|