CategoryAndSeverityInfo.tsx219 lines · main
1// End of third-party imports
2import { SupportCategories } from '@supabase/shared-types/out/constants'
3import type { UseFormReturn } from 'react-hook-form'
4import {
5 cn,
6 FormControl,
7 FormField,
8 Select,
9 SelectContent,
10 SelectGroup,
11 SelectItem,
12 SelectTrigger,
13 SelectValue,
14} from 'ui'
15import { Admonition } from 'ui-patterns/admonition'
16import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
17
18import {
19 CATEGORY_OPTIONS,
20 SEVERITY_OPTIONS,
21 type ExtendedSupportCategories,
22} from './Support.constants'
23import type { SupportFormValues } from './SupportForm.schema'
24import { NO_PROJECT_MARKER } from './SupportForm.utils'
25import { InlineLink } from '@/components/ui/InlineLink'
26
27interface CategoryAndSeverityInfoProps {
28 form: UseFormReturn<SupportFormValues>
29 category: ExtendedSupportCategories
30 severity?: string
31 projectRef: string
32 showSeverity?: boolean
33 showIssueSuggestion?: boolean
34}
35
36export function CategoryAndSeverityInfo({
37 form,
38 category,
39 severity,
40 projectRef,
41 showSeverity = true,
42 showIssueSuggestion = true,
43}: CategoryAndSeverityInfoProps) {
44 return (
45 <div
46 className={cn(
47 'grid sm:grid-rows-1 gap-4 grid-cols-1 grid-rows-2',
48 showSeverity ? 'sm:grid-cols-2' : 'sm:grid-cols-1'
49 )}
50 >
51 <CategorySelector form={form} />
52 {showSeverity && <SeveritySelector form={form} />}
53 {showIssueSuggestion && <IssueSuggestion category={category} projectRef={projectRef} />}
54 {(severity === 'Urgent' || severity === 'High') && (
55 <Admonition
56 type="default"
57 className="sm:col-span-2"
58 title="We do our best to respond to everyone as quickly as possible"
59 description="Prioritization will be based on production status. We ask that you reserve High and Urgent severity for production-impacting issues only."
60 />
61 )}
62 </div>
63 )
64}
65
66interface CategorySelectorProps {
67 form: UseFormReturn<SupportFormValues>
68}
69
70function CategorySelector({ form }: CategorySelectorProps) {
71 return (
72 <FormField
73 name="category"
74 control={form.control}
75 render={({ field }) => {
76 const { ref: _ref, ...fieldWithoutRef } = field
77 // Radix Select fires `onValueChange('')` when its controlled value
78 // transitions from `undefined` to a defined value whose matching
79 // SelectItem isn't mounted yet (the dropdown is closed, so
80 // SelectContent and the items it portals haven't registered). On
81 // React 19's stricter scheduling this races our `setValue` from
82 // useSupportForm and clobbers the prefilled category. No
83 // SelectItem can have value="" (Radix throws), so `v === ''` is
84 // always the spurious bubble — drop it. See
85 // radix-ui/primitives#3381.
86 const onValueChange = (v: string) => {
87 if (v === '' && field.value) return
88 field.onChange(v)
89 }
90 return (
91 <FormItemLayout hideMessage layout="vertical" label="What are you having issues with?">
92 <FormControl>
93 <Select {...fieldWithoutRef} defaultValue={field.value} onValueChange={onValueChange}>
94 <SelectTrigger aria-label="Select an issue" className="w-full">
95 <SelectValue placeholder="Select an issue">
96 {field.value
97 ? CATEGORY_OPTIONS.find((o) => o.value === field.value)?.label
98 : null}
99 </SelectValue>
100 </SelectTrigger>
101 <SelectContent>
102 <SelectGroup>
103 {CATEGORY_OPTIONS.map((option) => (
104 <SelectItem key={option.value} value={option.value}>
105 {option.label}
106 <span className="block text-xs text-foreground-lighter">
107 {option.description}
108 </span>
109 </SelectItem>
110 ))}
111 </SelectGroup>
112 </SelectContent>
113 </Select>
114 </FormControl>
115 </FormItemLayout>
116 )
117 }}
118 />
119 )
120}
121
122interface SeveritySelectorProps {
123 form: UseFormReturn<SupportFormValues>
124}
125
126function SeveritySelector({ form }: SeveritySelectorProps) {
127 return (
128 <FormField
129 name="severity"
130 control={form.control}
131 render={({ field }) => {
132 const { ref, ...fieldWithoutRef } = field
133 return (
134 <FormItemLayout hideMessage layout="vertical" label="Severity">
135 <FormControl>
136 <Select
137 {...fieldWithoutRef}
138 defaultValue={field.value}
139 onValueChange={field.onChange}
140 >
141 <SelectTrigger aria-label="Select a severity" className="w-full">
142 <SelectValue placeholder="Select a severity">{field.value}</SelectValue>
143 </SelectTrigger>
144 <SelectContent>
145 <SelectGroup>
146 {SEVERITY_OPTIONS.map((option) => (
147 <SelectItem key={option.value} value={option.value}>
148 {option.label}
149 <span className="block text-xs text-foreground-lighter">
150 {option.description}
151 </span>
152 </SelectItem>
153 ))}
154 </SelectGroup>
155 </SelectContent>
156 </Select>
157 </FormControl>
158 </FormItemLayout>
159 )
160 }}
161 />
162 )
163}
164
165const IssueSuggestion = ({ category, projectRef }: { category: string; projectRef?: string }) => {
166 const baseUrl = `/project/${projectRef === NO_PROJECT_MARKER ? '_' : projectRef}`
167
168 const className = 'col-span-2 mb-0'
169
170 if (category === SupportCategories.PROBLEM) {
171 return (
172 <Admonition
173 type="default"
174 className={className}
175 title="Have you checked your project's logs?"
176 >
177 Logs can help you identify errors that you might be running into when using your project's
178 API or client libraries. View logs for each product{' '}
179 <InlineLink href={`${baseUrl}/logs/edge-logs`}>here</InlineLink>.
180 </Admonition>
181 )
182 }
183
184 if (category === SupportCategories.DATABASE_UNRESPONSIVE) {
185 return (
186 <Admonition
187 type="default"
188 className={className}
189 title="Have you checked your project's infrastructure activity?"
190 >
191 High memory or low disk IO bandwidth may be slowing down your database. Verify by checking
192 the infrastructure activity of your project{' '}
193 <InlineLink href={`${baseUrl}/settings/infrastructure#infrastructure-activity`}>
194 here
195 </InlineLink>
196 .
197 </Admonition>
198 )
199 }
200
201 if (category === SupportCategories.PERFORMANCE_ISSUES) {
202 return (
203 <Admonition
204 type="default"
205 className={className}
206 title="Have you checked the Query Performance Advisor?"
207 >
208 Identify slow running queries and get actionable insights on how to optimize them with the
209 Query Performance Advisor{' '}
210 <InlineLink href={`${baseUrl}/settings/infrastructure#infrastructure-activity`}>
211 here
212 </InlineLink>
213 .
214 </Admonition>
215 )
216 }
217
218 return null
219}