ErrorBoundary.tsx 2.4 KB

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