CategoryAndSeverityInfo.tsx 7.2 KB

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