Files
Joshen Lim fcfb0f0222 Refactor all usage of form.watch to either useWatch or subscribe (#48436)
## Context

Replaces all usage of `form.watch()` to use `useWatch` instead + follows
the "name what you watch" convention as specified in the react-hook-form
skills.

There's also a small refactor in `SmtpForm.tsx` which removes the
unnecessary use of a `useState` to track if SMTP is enabled or not

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* Updated many Studio forms to watch specific fields more precisely,
improving live UI updates for previews, warnings, conditional sections,
and validation messages.
* Enhanced responsiveness across settings, authentication, billing,
storage, integrations, and support flows while keeping save/update
behavior the same.
* **Refined Experiences**
* Improved the analytics table creation flow with tighter, enum-based
column type validation and structured, type-specific column options.
* **Preserved Behavior**
* Maintained existing permission checks, submission flows, and
account-management workflows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-30 11:45:40 +08:00

181 lines
6.0 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { Link2 } from 'lucide-react'
import { useEffect } from 'react'
import type { SubmitHandler } from 'react-hook-form'
import { useForm, useWatch } from 'react-hook-form'
import { toast } from 'sonner'
import { Button, DialogSectionSeparator, Form, FormControl, FormField, Input } from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CategoryAndSeverityInfo } from './CategoryAndSeverityInfo'
import {
LinkSupportTicketFormSchema,
type LinkSupportTicketFormValues,
} from './LinkSupportTicketForm.schema'
import { OrganizationSelector } from './OrganizationSelector'
import { ProjectAndPlanInfo } from './ProjectAndPlanInfo'
import { SupportAccessToggle } from './SupportAccessToggle'
import {
canAllowSupportAccess,
getOrgSubscriptionPlan,
NO_ORG_MARKER,
NO_PROJECT_MARKER,
} from './SupportForm.utils'
import { useLinkSupportTicketMutation } from '@/data/feedback/link-support-ticket-mutation'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
interface LinkSupportTicketFormProps {
conversationId: string
onSuccess: () => void
}
// [Joshen] Am reusing the SupportFormV2 components here but am not sure how to type things properly
// hence marking all the `form` as `any` when passing as props to the components for now
export const LinkSupportTicketForm = ({
conversationId,
onSuccess,
}: LinkSupportTicketFormProps) => {
const { data: organizations, isSuccess } = useOrganizationsQuery()
const form = useForm<LinkSupportTicketFormValues>({
resolver: zodResolver(LinkSupportTicketFormSchema),
defaultValues: {
conversation_id: conversationId,
organizationSlug: NO_ORG_MARKER,
projectRef: NO_PROJECT_MARKER,
category: '' as any,
allowSupportAccess: true,
},
mode: 'onSubmit',
reValidateMode: 'onBlur',
})
const [category, organizationSlug, projectRef] = useWatch({
control: form.control,
name: ['category', 'organizationSlug', 'projectRef'],
})
const selectedOrgSlug = organizationSlug === NO_ORG_MARKER ? null : organizationSlug
const selectedProjectRef = projectRef === NO_PROJECT_MARKER ? null : projectRef
const subscriptionPlanId = getOrgSubscriptionPlan(organizations, selectedOrgSlug)
const { mutate: linkSupportTicket, isPending } = useLinkSupportTicketMutation({
onSuccess: () => {
toast.success('Support ticket linked successfully!')
onSuccess()
},
onError: (error) => {
let errorMessage = error.message
try {
const parsed = JSON.parse(error.message)
errorMessage = parsed?._error?.message || parsed?.message || error.message
} catch {}
// If parsing fails, use the original message
toast.error(`Failed to link support ticket: ${errorMessage}`)
},
})
const onSubmit: SubmitHandler<LinkSupportTicketFormValues> = (values) => {
if (!organizations) return toast.error('Organizations not loaded. Please try again.')
const selectedOrg = organizations.find((org) => org.slug === values.organizationSlug)
if (!selectedOrg) return toast.error('Selected organization not found. Please try again.')
linkSupportTicket({
conversation_id: values.conversation_id,
org_id: selectedOrg.id,
project_ref:
values.projectRef && values.projectRef !== NO_PROJECT_MARKER
? values.projectRef
: undefined,
category: values.category,
allow_support_access: canAllowSupportAccess(values.category, values.projectRef)
? values.allowSupportAccess
: false,
})
}
useEffect(() => {
if (!!organizations && organizations.length > 0) {
form.reset({
conversation_id: conversationId,
organizationSlug: organizations[0].slug,
projectRef: NO_PROJECT_MARKER,
category: '' as any,
allowSupportAccess: true,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSuccess])
return (
<Form {...form}>
<form
id="link-support-ticket-form"
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col"
>
<div className="flex flex-col py-6 gap-y-6">
<h3 className="px-6 text-xl">Link support ticket to account</h3>
<div className="px-6 flex flex-col gap-y-8">
<FormField
control={form.control}
name="conversation_id"
render={({ field }) => (
<FormItemLayout hideMessage layout="vertical" label="Conversation ID">
<FormControl>
<Input {...field} readOnly />
</FormControl>
</FormItemLayout>
)}
/>
<OrganizationSelector form={form as any} orgSlug={organizationSlug} />
{organizationSlug !== NO_ORG_MARKER && (
<ProjectAndPlanInfo
form={form as any}
orgSlug={selectedOrgSlug}
projectRef={selectedProjectRef}
subscriptionPlanId={subscriptionPlanId}
category={category}
/>
)}
<CategoryAndSeverityInfo
showSeverity={false}
showIssueSuggestion={false}
form={form as any}
category={category}
projectRef={projectRef}
/>
</div>
</div>
<DialogSectionSeparator />
{canAllowSupportAccess(category, projectRef) && (
<>
<div className="py-4">
<SupportAccessToggle form={form as any} />
</div>
<DialogSectionSeparator />
</>
)}
<div className="px-6 py-8">
<Button
block
variant="primary"
type="submit"
size="large"
icon={<Link2 />}
loading={isPending}
>
Link support ticket to account
</Button>
</div>
</form>
</Form>
)
}