| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- // 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 { type Dispatch, type MouseEventHandler } from 'react'
- import type { SubmitHandler, UseFormReturn } from 'react-hook-form'
- import { DialogSectionSeparator, Form } from 'ui'
- import {
- AffectedServicesSelector,
- CATEGORIES_WITHOUT_AFFECTED_SERVICES,
- } from './AffectedServicesSelector'
- import { AttachmentUploadDisplay, useAttachmentUpload } from './AttachmentUpload'
- import { CategoryAndSeverityInfo } from './CategoryAndSeverityInfo'
- import { ClientLibraryInfo } from './ClientLibraryInfo'
- import {
- DASHBOARD_LOG_CATEGORIES,
- getSanitizedBreadcrumbs,
- uploadDashboardLog,
- } from './dashboard-logs'
- 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 {
- formatMessage,
- formatStudioVersion,
- getOrgSubscriptionPlan,
- NO_ORG_MARKER,
- NO_PROJECT_MARKER,
- } from './SupportForm.utils'
- 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 { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
- import { detectBrowser } from '@/lib/helpers'
- import { useProfile } from '@/lib/profile'
- 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 showClientLibraries = useIsFeatureEnabled('support:show_client_libraries')
- 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) => {
- // Library is required when selecting "APIs and Client Libraries" category,
- // but only when the library selector is visible (not in simplified form)
- if (
- !simplifiedSupportForm &&
- showClientLibraries &&
- formValues.category === SupportCategories.PROBLEM &&
- !formValues.library
- ) {
- form.setError('library', {
- type: 'manual',
- message: "Please select the library that you're facing issues with",
- })
- return
- }
- 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 {...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} className="px-6" />
- <DialogSectionSeparator />
- </>
- )}
- {!!category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(category) && (
- <>
- <SupportAccessToggle form={form} className="px-6" />
- <DialogSectionSeparator />
- </>
- )}
- <div className="px-6 pt-2">
- <SubmitButton
- isSubmitting={state.type === 'submitting'}
- userEmail={respondToEmail}
- onClick={handleSubmitButtonClick}
- />
- </div>
- </form>
- </Form>
- )
- }
|