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