Files
supabase/apps/studio/components/interfaces/Support/SupportFormV2.tsx
Monica Khoury a87387b56e feat: show "Allow support access to your project" toggle for all support categories (#42254)
Previously, the “Allow support access to your project” toggle was only
shown for specific issue categories in our support form. This change
makes the toggle available for all categories.

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

* **Bug Fixes**
* Support access toggle and submission now suppress support access for
Account Deletion, Sales Enquiry, and Refund categories.
* **Refactor**
* Reworked category gating so UI visibility and submitted payload
consistently respect the disabled-category list.
* **UI**
* Category list updated—"Others" removed and category options adjusted
so all available options are shown.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-01-28 18:15:41 +00:00

245 lines
8.5 KiB
TypeScript

// End of third-party imports
import { SupportCategories } from '@supabase/shared-types/out/constants'
import { useConstant, useFlag } from 'common'
import { CLIENT_LIBRARIES } from 'common/constants'
import { getProjectAuthConfig } from 'data/auth/auth-config-query'
import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send'
import { type OrganizationPlanID } from 'data/organizations/organization-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useGenerateAttachmentURLsMutation } from 'data/support/generate-attachment-urls-mutation'
import { useDeploymentCommitQuery } from 'data/utils/deployment-commit-query'
import { detectBrowser } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import { type Dispatch, type MouseEventHandler } from 'react'
import type { SubmitHandler, UseFormReturn } from 'react-hook-form'
import { DialogSectionSeparator, Form_Shadcn_ } from 'ui'
import {
AffectedServicesSelector,
CATEGORIES_WITHOUT_AFFECTED_SERVICES,
} from './AffectedServicesSelector'
import { AttachmentUploadDisplay, useAttachmentUpload } from './AttachmentUpload'
import { CategoryAndSeverityInfo } from './CategoryAndSeverityInfo'
import { ClientLibraryInfo } from './ClientLibraryInfo'
import { DashboardLogsToggle } from './DashboardLogsToggle'
import { MessageField } from './MessageField'
import { OrganizationSelector } from './OrganizationSelector'
import { ProjectAndPlanInfo } from './ProjectAndPlanInfo'
import { SubjectAndSuggestionsInfo } from './SubjectAndSuggestionsInfo'
import { SubmitButton } from './SubmitButton'
import { DISABLE_SUPPORT_ACCESS_CATEGORIES, SupportAccessToggle } from './SupportAccessToggle'
import type { SupportFormValues } from './SupportForm.schema'
import type { SupportFormActions, SupportFormState } from './SupportForm.state'
import {
NO_ORG_MARKER,
NO_PROJECT_MARKER,
formatMessage,
formatStudioVersion,
getOrgSubscriptionPlan,
} from './SupportForm.utils'
import {
DASHBOARD_LOG_CATEGORIES,
getSanitizedBreadcrumbs,
uploadDashboardLog,
} from './dashboard-logs'
const useIsSimplifiedForm = (slug: string, subscriptionPlanId?: OrganizationPlanID) => {
const simplifiedSupportForm = useFlag('simplifiedSupportForm')
if (subscriptionPlanId === 'platform') {
return true
}
if (typeof simplifiedSupportForm === 'string') {
const slugs = (simplifiedSupportForm as string).split(',').map((x) => x.trim())
return slugs.includes(slug)
}
return false
}
interface SupportFormV2Props {
form: UseFormReturn<SupportFormValues>
initialError: string | null
state: SupportFormState
dispatch: Dispatch<SupportFormActions>
}
export const SupportFormV2 = ({ form, initialError, state, dispatch }: SupportFormV2Props) => {
const { profile } = useProfile()
const respondToEmail = profile?.primary_email ?? 'your email'
const { organizationSlug, projectRef, category, severity, subject, library } = form.watch()
const selectedOrgSlug = organizationSlug === NO_ORG_MARKER ? null : organizationSlug
const selectedProjectRef = projectRef === NO_PROJECT_MARKER ? null : projectRef
const { data: organizations } = useOrganizationsQuery()
const subscriptionPlanId = getOrgSubscriptionPlan(organizations, selectedOrgSlug)
const simplifiedSupportForm = useIsSimplifiedForm(organizationSlug, subscriptionPlanId)
const attachmentUpload = useAttachmentUpload()
const { mutateAsync: uploadDashboardLogFn } = useGenerateAttachmentURLsMutation()
const sanitizedLogSnapshot = useConstant(getSanitizedBreadcrumbs)
const { data: commit } = useDeploymentCommitQuery({
staleTime: 1000 * 60 * 10, // 10 minutes
})
const { mutate: submitSupportTicket } = useSendSupportTicketMutation({
onSuccess: (_, variables) => {
dispatch({
type: 'SUCCESS',
sentProjectRef: variables.projectRef,
sentOrgSlug: variables.organizationSlug,
sentCategory: variables.category,
})
},
onError: (error) => {
dispatch({
type: 'ERROR',
message: error.message,
})
},
})
const onSubmit: SubmitHandler<SupportFormValues> = async (formValues) => {
dispatch({ type: 'SUBMIT' })
const { attachDashboardLogs: formAttachDashboardLogs, ...values } = formValues
const attachDashboardLogs =
formAttachDashboardLogs && DASHBOARD_LOG_CATEGORIES.includes(values.category)
const [attachments, dashboardLogUrl] = await Promise.all([
attachmentUpload.createAttachments(),
attachDashboardLogs
? uploadDashboardLog({
userId: profile?.gotrue_id,
sanitizedLogs: sanitizedLogSnapshot,
uploadDashboardLogFn,
})
: undefined,
])
const selectedLibrary = values.library
? CLIENT_LIBRARIES.find((library) => library.language === values.library)
: undefined
const payload = {
...values,
organizationSlug: values.organizationSlug ?? NO_ORG_MARKER,
projectRef: values.projectRef ?? NO_PROJECT_MARKER,
allowSupportAccess:
values.category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(values.category)
? values.allowSupportAccess
: false,
library:
values.category === SupportCategories.PROBLEM && selectedLibrary !== undefined
? selectedLibrary.key
: '',
message: formatMessage({
message: values.message,
attachments,
error: initialError,
}),
verified: true,
tags: ['dashboard-support-form'],
siteUrl: '',
additionalRedirectUrls: '',
affectedServices: CATEGORIES_WITHOUT_AFFECTED_SERVICES.includes(values.category)
? ''
: values.affectedServices
.split(',')
.map((x) => x.trim().replace(/ /g, '_').toLowerCase())
.join(';'),
browserInformation: detectBrowser(),
dashboardLogs: dashboardLogUrl?.[0],
dashboardStudioVersion: commit ? formatStudioVersion(commit) : undefined,
}
if (values.projectRef !== NO_PROJECT_MARKER) {
try {
const authConfig = await getProjectAuthConfig({
projectRef: values.projectRef,
})
payload.siteUrl = authConfig.SITE_URL
payload.additionalRedirectUrls = authConfig.URI_ALLOW_LIST
} catch {
// [Joshen] No error handler required as fetching these info are nice to haves, not necessary
}
}
submitSupportTicket(payload)
}
const handleFormSubmit = form.handleSubmit(onSubmit)
const handleSubmitButtonClick: MouseEventHandler<HTMLButtonElement> = (event) => {
handleFormSubmit(event)
}
return (
<Form_Shadcn_ {...form}>
<form id="support-form" className="flex flex-col gap-y-6">
<h3 className="px-6 text-xl">How can we help?</h3>
<div className="px-6 flex flex-col gap-y-8">
<OrganizationSelector form={form} orgSlug={organizationSlug} />
<ProjectAndPlanInfo
form={form}
orgSlug={selectedOrgSlug}
projectRef={selectedProjectRef}
subscriptionPlanId={subscriptionPlanId}
category={category}
/>
<CategoryAndSeverityInfo
form={form}
category={category}
severity={severity}
projectRef={projectRef}
/>
</div>
<DialogSectionSeparator />
<div className="px-6 flex flex-col gap-y-8">
<SubjectAndSuggestionsInfo form={form} subject={subject} category={category} />
{!simplifiedSupportForm && (
<>
<ClientLibraryInfo form={form} library={library} category={category} />
<AffectedServicesSelector form={form} category={category} />
</>
)}
<MessageField form={form} originalError={initialError} />
<AttachmentUploadDisplay {...attachmentUpload} />
</div>
<DialogSectionSeparator />
{DASHBOARD_LOG_CATEGORIES.includes(category) && (
<>
<DashboardLogsToggle form={form} sanitizedLog={sanitizedLogSnapshot} />
<DialogSectionSeparator />
</>
)}
{!!category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(category) && (
<>
<SupportAccessToggle form={form} />
<DialogSectionSeparator />
</>
)}
<div className="px-6 pt-2">
<SubmitButton
isSubmitting={state.type === 'submitting'}
userEmail={respondToEmail}
onClick={handleSubmitButtonClick}
/>
</div>
</form>
</Form_Shadcn_>
)
}