ClientSideExceptionHandler.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { SupportCategories } from '@supabase/shared-types/out/constants'
  2. import { ExternalLink } from 'lucide-react'
  3. import { useRouter } from 'next/router'
  4. import { Button, cn } from 'ui'
  5. import { Admonition } from 'ui-patterns'
  6. import CopyButton from '../CopyButton'
  7. import { InlineLinkClassName } from '../InlineLink'
  8. import { SupportLink } from '@/components/interfaces/Support/SupportLink'
  9. interface ClientSideExceptionHandlerProps {
  10. message: string
  11. sentryIssueId: string
  12. urlMessage: string
  13. resetErrorBoundary: () => void
  14. }
  15. export const ClientSideExceptionHandler = ({
  16. message,
  17. sentryIssueId,
  18. urlMessage,
  19. resetErrorBoundary,
  20. }: ClientSideExceptionHandlerProps) => {
  21. const router = useRouter()
  22. const isProduction = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod'
  23. const handleClearStorage = () => {
  24. try {
  25. localStorage.clear()
  26. sessionStorage.clear()
  27. } catch (e) {
  28. // ignore
  29. }
  30. window.location.reload()
  31. }
  32. return (
  33. <>
  34. <div className="flex flex-col gap-y-1 text-left py-2 w-full">
  35. <div className="flex items-center justify-between mb-3">
  36. <p className="text-lg font-bold">Sorry! An unexpected error occurred.</p>
  37. <CopyButton type="outline" text={message} copyLabel="Copy error" />
  38. </div>
  39. <p className="text-sm">
  40. Application error: a client-side exception has occurred (see browser console for more
  41. information)
  42. </p>
  43. <p className="text-foreground-light text-sm">{message}</p>
  44. </div>
  45. <Admonition type="note" showIcon={false} title="We recommend trying the following:">
  46. <ul className="list-disc mt-1.5 pl-2 list-inside text-sm space-y-1">
  47. <li>
  48. <span
  49. className={cn(InlineLinkClassName, 'cursor-pointer')}
  50. onClick={() => window.location.reload()}
  51. >
  52. Refresh
  53. </span>{' '}
  54. the page
  55. </li>
  56. <li>
  57. <span
  58. className={cn(InlineLinkClassName, 'cursor-pointer')}
  59. onClick={() => router.push('/logout')}
  60. >
  61. Sign out
  62. </span>{' '}
  63. and sign back in
  64. </li>
  65. <li>
  66. <span
  67. className={cn(InlineLinkClassName, 'cursor-pointer')}
  68. onClick={handleClearStorage}
  69. >
  70. Clear your browser storage
  71. </span>{' '}
  72. to clean potentially outdated data
  73. </li>
  74. <li>Disable browser extensions that might modify page content (e.g. Google Translate)</li>
  75. <li>If the problem persists, please contact support for assistance</li>
  76. </ul>
  77. </Admonition>
  78. <div className={cn('w-full mx-auto grid gap-2', 'grid-cols-2 sm:w-1/2')}>
  79. <Button asChild type="default" icon={<ExternalLink />}>
  80. <SupportLink
  81. queryParams={{
  82. category: SupportCategories.DASHBOARD_BUG,
  83. subject: 'Client side exception occurred on dashboard',
  84. sid: sentryIssueId,
  85. error: urlMessage,
  86. }}
  87. >
  88. Contact support
  89. </SupportLink>
  90. </Button>
  91. {/* [Joshen] For local and staging, allow us to escape the error boundary */}
  92. {/* We could actually investigate how to make this available on prod, but without being able to reliably test this, I'm not keen to do it now */}
  93. {isProduction ? (
  94. <Button type="outline" onClick={() => router.reload()}>
  95. Reload dashboard
  96. </Button>
  97. ) : (
  98. <Button type="outline" onClick={() => resetErrorBoundary()}>
  99. Return to dashboard
  100. </Button>
  101. )}
  102. </div>
  103. </>
  104. )
  105. }