3,200+ hardcoded color values replaced with CSS variable-backed Tailwind classes (bg-card, text-foreground, border-border, etc.). Enables light mode via CSS variable swap. Only syntax highlighting colors and intentional one-offs remain hardcoded (~15 values). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import { useEffect } from 'react'
|
|
import { useRouteError, isRouteErrorResponse, useNavigate } from 'react-router-dom'
|
|
import * as Sentry from '@sentry/react'
|
|
import { Button } from '@/components/ui/Button'
|
|
|
|
function isChunkLoadError(error: unknown): boolean {
|
|
if (!(error instanceof Error)) return false
|
|
const msg = error.message.toLowerCase()
|
|
return (
|
|
msg.includes('failed to fetch dynamically imported module') ||
|
|
msg.includes('importing a module script failed') ||
|
|
msg.includes('loading chunk') ||
|
|
msg.includes('loading css chunk')
|
|
)
|
|
}
|
|
|
|
const RELOAD_KEY = 'rf_chunk_reload'
|
|
|
|
export function RouteError() {
|
|
const error = useRouteError()
|
|
const navigate = useNavigate()
|
|
|
|
// Report route errors to Sentry (skip chunk load errors — those are deploy artifacts)
|
|
useEffect(() => {
|
|
if (error && !isChunkLoadError(error)) {
|
|
Sentry.captureException(error)
|
|
}
|
|
}, [error])
|
|
|
|
// Auto-reload once on chunk load failures (stale deploy)
|
|
useEffect(() => {
|
|
if (isChunkLoadError(error)) {
|
|
const lastReload = sessionStorage.getItem(RELOAD_KEY)
|
|
const now = Date.now()
|
|
// Only auto-reload if we haven't reloaded in the last 10 seconds (prevent loops)
|
|
if (!lastReload || now - Number(lastReload) > 10_000) {
|
|
sessionStorage.setItem(RELOAD_KEY, String(now))
|
|
window.location.reload()
|
|
}
|
|
}
|
|
}, [error])
|
|
|
|
let errorMessage = 'An unexpected error occurred'
|
|
let errorDetails = ''
|
|
|
|
if (isChunkLoadError(error)) {
|
|
errorMessage = 'App Updated'
|
|
errorDetails = 'A new version was deployed. Please refresh the page.'
|
|
} else if (isRouteErrorResponse(error)) {
|
|
errorMessage = error.status === 404 ? 'Page not found' : `Error ${error.status}`
|
|
errorDetails = error.statusText || ''
|
|
} else if (error instanceof Error) {
|
|
errorMessage = 'Something went wrong'
|
|
errorDetails = error.message
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-screen flex-col items-center justify-center bg-background p-8">
|
|
<div className="max-w-md text-center">
|
|
<h1 className="mb-2 text-4xl font-bold text-foreground">Oops!</h1>
|
|
<h2 className="mb-2 text-xl font-semibold text-red-400">{errorMessage}</h2>
|
|
{errorDetails && (
|
|
<p className="mb-4 text-muted-foreground">{errorDetails}</p>
|
|
)}
|
|
<div className="flex justify-center gap-4">
|
|
<Button variant="secondary" onClick={() => navigate(-1)}>
|
|
Go Back
|
|
</Button>
|
|
<Button onClick={() => navigate('/trees')}>
|
|
Go Home
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default RouteError
|