ErrorBoundary.tsx95 lines · main
1import * as Sentry from '@sentry/nextjs'
2import { AlertCircle } from 'lucide-react'
3import { ErrorInfo } from 'react'
4import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary'
5import { Alert, AlertDescription, AlertTitle, Button } from 'ui'
6
7interface ErrorFallbackProps {
8 error: Error
9 resetErrorBoundary: () => void
10 message?: string
11 actions?: {
12 label: string
13 onClick: () => void
14 }[]
15 sentryContext?: Record<string, any>
16}
17
18const ErrorFallback = ({
19 error: _error,
20 resetErrorBoundary,
21 message = 'Something went wrong',
22 actions = [],
23}: ErrorFallbackProps) => {
24 return (
25 <div className="p-4 bg-destructive-foreground h-full flex flex-col justify-center items-center">
26 <Alert variant="destructive">
27 <AlertCircle />
28 <AlertTitle>{message}</AlertTitle>
29 <AlertDescription>We've been notified and will review and fix this issue.</AlertDescription>
30 <div className="mt-4 flex gap-2">
31 <Button type="default" onClick={resetErrorBoundary} className="text-sm">
32 Try again
33 </Button>
34 {actions?.map((action, index) => (
35 <Button key={index} type="default" onClick={action.onClick} className="text-sm">
36 {action.label}
37 </Button>
38 ))}
39 </div>
40 </Alert>
41 </div>
42 )
43}
44
45interface ErrorBoundaryProps {
46 children: React.ReactNode
47 message?: string
48 actions?: {
49 label: string
50 onClick: () => void
51 }[]
52 sentryContext?: Record<string, any>
53 onReset?: () => void
54}
55
56export const ErrorBoundary = ({
57 children,
58 message,
59 actions,
60 sentryContext,
61 onReset,
62}: ErrorBoundaryProps) => {
63 const handleError = (error: Error, info: ErrorInfo) => {
64 Sentry.withScope((scope) => {
65 scope.setExtra('componentStack', info.componentStack)
66 if (sentryContext) {
67 Object.entries(sentryContext).forEach(([key, value]) => {
68 scope.setExtra(key, value)
69 })
70 }
71 Sentry.captureException(error)
72 })
73 }
74
75 const handleReset = () => {
76 onReset?.()
77 }
78
79 return (
80 <ReactErrorBoundary
81 fallbackRender={({ error, resetErrorBoundary }) => (
82 <ErrorFallback
83 error={error}
84 resetErrorBoundary={resetErrorBoundary}
85 message={message}
86 actions={actions}
87 />
88 )}
89 onError={handleError}
90 onReset={handleReset}
91 >
92 {children}
93 </ReactErrorBoundary>
94 )
95}