- Add lazyWithRetry wrapper for all lazy-loaded routes to auto-reload on stale chunk errors after deploys (prevents ErrorBoundary flash) - Show toast notification when image paste/upload fails due to storage not configured (503), instead of silent tiny error thumbnails - Remove failed uploads from thumbnail strip on 503 (was showing confusing retry icon) - Pass completed upload IDs in navigation state from dashboard input - Suppress Sentry dialog for chunk load errors (deploy artifacts) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
import * as Sentry from '@sentry/react'
|
|
import { type ReactNode, useEffect, useRef } from 'react'
|
|
import { Button } from '@/components/ui/Button'
|
|
|
|
interface FallbackProps {
|
|
error: Error
|
|
resetError: () => void
|
|
}
|
|
|
|
function isChunkLoadError(error: Error): boolean {
|
|
const msg = error.message || ''
|
|
return (
|
|
msg.includes('dynamically imported module') ||
|
|
msg.includes('Loading chunk') ||
|
|
msg.includes('Failed to fetch') ||
|
|
error.name === 'ChunkLoadError'
|
|
)
|
|
}
|
|
|
|
function DefaultFallback({ error, resetError }: FallbackProps) {
|
|
const reloadingRef = useRef(false)
|
|
|
|
// Auto-reload on stale chunk errors (happens after deployments)
|
|
useEffect(() => {
|
|
if (!isChunkLoadError(error)) return
|
|
const key = 'rf_boundary_chunk_reload'
|
|
const lastReload = sessionStorage.getItem(key)
|
|
const now = Date.now()
|
|
if (!lastReload || now - Number(lastReload) > 10_000) {
|
|
sessionStorage.setItem(key, String(now))
|
|
reloadingRef.current = true
|
|
window.location.reload()
|
|
}
|
|
}, [error])
|
|
|
|
return (
|
|
<div className="flex min-h-[400px] flex-col items-center justify-center p-8">
|
|
<div className="max-w-md text-center">
|
|
<h2 className="mb-2 text-xl font-semibold text-red-400">
|
|
Something went wrong
|
|
</h2>
|
|
<p className="mb-4 text-muted-foreground">
|
|
An unexpected error occurred. Please try refreshing the page.
|
|
</p>
|
|
<pre className="mb-4 overflow-auto rounded-xl bg-white/5 border border-border p-3 text-left text-xs text-red-400">
|
|
{error.message}
|
|
</pre>
|
|
<div className="flex justify-center gap-3">
|
|
<Button variant="secondary" onClick={resetError}>
|
|
Try Again
|
|
</Button>
|
|
<Button onClick={() => window.location.reload()}>
|
|
Refresh Page
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
interface Props {
|
|
children: ReactNode
|
|
fallback?: ReactNode
|
|
}
|
|
|
|
export function ErrorBoundary({ children, fallback }: Props) {
|
|
return (
|
|
<Sentry.ErrorBoundary
|
|
fallback={({ error, resetError }) => {
|
|
if (fallback) return fallback as React.ReactElement
|
|
return <DefaultFallback error={error as Error} resetError={resetError} />
|
|
}}
|
|
beforeCapture={(scope, error) => {
|
|
// Don't report chunk load errors to Sentry — they're deploy artifacts, not bugs
|
|
if (error && isChunkLoadError(error as Error)) {
|
|
scope.setLevel('info')
|
|
scope.setTag('chunk_load_error', 'true')
|
|
}
|
|
}}
|
|
showDialog={false}
|
|
>
|
|
{children}
|
|
</Sentry.ErrorBoundary>
|
|
)
|
|
}
|
|
|
|
export default ErrorBoundary
|