LinkSupportTicketForm.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { zodResolver } from '@hookform/resolvers/zod'
  2. import { Link2 } from 'lucide-react'
  3. import { useEffect } from 'react'
  4. import type { SubmitHandler } from 'react-hook-form'
  5. import { useForm } from 'react-hook-form'
  6. import { toast } from 'sonner'
  7. import { Button, DialogSectionSeparator, Form, FormControl, FormField, Input } from 'ui'
  8. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  9. import { CategoryAndSeverityInfo } from './CategoryAndSeverityInfo'
  10. import {
  11. LinkSupportTicketFormSchema,
  12. type LinkSupportTicketFormValues,
  13. } from './LinkSupportTicketForm.schema'
  14. import { OrganizationSelector } from './OrganizationSelector'
  15. import { ProjectAndPlanInfo } from './ProjectAndPlanInfo'
  16. import { DISABLE_SUPPORT_ACCESS_CATEGORIES, SupportAccessToggle } from './SupportAccessToggle'
  17. import { getOrgSubscriptionPlan, NO_ORG_MARKER, NO_PROJECT_MARKER } from './SupportForm.utils'
  18. import { useLinkSupportTicketMutation } from '@/data/feedback/link-support-ticket-mutation'
  19. import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
  20. interface LinkSupportTicketFormProps {
  21. conversationId: string
  22. onSuccess: () => void
  23. }
  24. // [Joshen] Am reusing the SupportFormV2 components here but am not sure how to type things properly
  25. // hence marking all the `form` as `any` when passing as props to the components for now
  26. export const LinkSupportTicketForm = ({
  27. conversationId,
  28. onSuccess,
  29. }: LinkSupportTicketFormProps) => {
  30. const { data: organizations, isSuccess } = useOrganizationsQuery()
  31. const form = useForm<LinkSupportTicketFormValues>({
  32. resolver: zodResolver(LinkSupportTicketFormSchema as any),
  33. defaultValues: {
  34. conversation_id: conversationId,
  35. organizationSlug: NO_ORG_MARKER,
  36. projectRef: NO_PROJECT_MARKER,
  37. category: '' as any,
  38. allowSupportAccess: true,
  39. },
  40. mode: 'onSubmit',
  41. reValidateMode: 'onBlur',
  42. })
  43. const { category, organizationSlug, projectRef } = form.watch()
  44. const selectedOrgSlug = organizationSlug === NO_ORG_MARKER ? null : organizationSlug
  45. const selectedProjectRef = projectRef === NO_PROJECT_MARKER ? null : projectRef
  46. const subscriptionPlanId = getOrgSubscriptionPlan(organizations, selectedOrgSlug)
  47. const { mutate: linkSupportTicket, isPending } = useLinkSupportTicketMutation({
  48. onSuccess: () => {
  49. toast.success('Support ticket linked successfully!')
  50. onSuccess()
  51. },
  52. onError: (error) => {
  53. let errorMessage = error.message
  54. try {
  55. const parsed = JSON.parse(error.message)
  56. errorMessage = parsed?._error?.message || parsed?.message || error.message
  57. } catch {}
  58. // If parsing fails, use the original message
  59. toast.error(`Failed to link support ticket: ${errorMessage}`)
  60. },
  61. })
  62. const onSubmit: SubmitHandler<LinkSupportTicketFormValues> = (values) => {
  63. if (!organizations) return toast.error('Organizations not loaded. Please try again.')
  64. const selectedOrg = organizations.find((org) => org.slug === values.organizationSlug)
  65. if (!selectedOrg) return toast.error('Selected organization not found. Please try again.')
  66. linkSupportTicket({
  67. conversation_id: values.conversation_id,
  68. org_id: selectedOrg.id,
  69. project_ref:
  70. values.projectRef && values.projectRef !== NO_PROJECT_MARKER
  71. ? values.projectRef
  72. : undefined,
  73. category: values.category,
  74. allow_support_access:
  75. values.category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(values.category)
  76. ? values.allowSupportAccess
  77. : false,
  78. })
  79. }
  80. useEffect(() => {
  81. if (!!organizations && organizations.length > 0) {
  82. form.reset({
  83. conversation_id: conversationId,
  84. organizationSlug: organizations[0].slug,
  85. projectRef: NO_PROJECT_MARKER,
  86. category: '' as any,
  87. allowSupportAccess: true,
  88. })
  89. }
  90. // eslint-disable-next-line react-hooks/exhaustive-deps
  91. }, [isSuccess])
  92. return (
  93. <Form {...form}>
  94. <form
  95. id="link-support-ticket-form"
  96. onSubmit={form.handleSubmit(onSubmit)}
  97. className="flex flex-col"
  98. >
  99. <div className="flex flex-col py-6 gap-y-6">
  100. <h3 className="px-6 text-xl">Link support ticket to account</h3>
  101. <div className="px-6 flex flex-col gap-y-8">
  102. <FormField
  103. control={form.control}
  104. name="conversation_id"
  105. render={({ field }) => (
  106. <FormItemLayout hideMessage layout="vertical" label="Conversation ID">
  107. <FormControl>
  108. <Input {...field} readOnly />
  109. </FormControl>
  110. </FormItemLayout>
  111. )}
  112. />
  113. <OrganizationSelector form={form as any} orgSlug={organizationSlug} />
  114. {organizationSlug !== NO_ORG_MARKER && (
  115. <ProjectAndPlanInfo
  116. form={form as any}
  117. orgSlug={selectedOrgSlug}
  118. projectRef={selectedProjectRef}
  119. subscriptionPlanId={subscriptionPlanId}
  120. category={category}
  121. />
  122. )}
  123. <CategoryAndSeverityInfo
  124. showSeverity={false}
  125. showIssueSuggestion={false}
  126. form={form as any}
  127. category={category}
  128. projectRef={projectRef}
  129. />
  130. </div>
  131. </div>
  132. <DialogSectionSeparator />
  133. {!!category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(category) && (
  134. <>
  135. <div className="py-4">
  136. <SupportAccessToggle form={form as any} />
  137. </div>
  138. <DialogSectionSeparator />
  139. </>
  140. )}
  141. <div className="px-6 py-8">
  142. <Button
  143. block
  144. type="primary"
  145. htmlType="submit"
  146. size="large"
  147. icon={<Link2 />}
  148. loading={isPending}
  149. >
  150. Link support ticket to account
  151. </Button>
  152. </div>
  153. </form>
  154. </Form>
  155. )
  156. }