| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- // 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 { Form, Separator } 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 { PlanExpectationInfoContent, 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 { SupportFormDirectEmailContent } from './SupportFormDirectEmailInfo'
- 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 SupportFormV3Props {
- form: UseFormReturn<SupportFormValues>
- initialError: string | null
- state: SupportFormState
- dispatch: Dispatch<SupportFormActions>
- selectedProjectRef?: string | null
- }
- export const SupportFormV3 = ({
- form,
- initialError,
- state,
- dispatch,
- selectedProjectRef,
- }: SupportFormV3Props) => {
- 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 currentProjectRef = 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,
- })
- 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) => {
- 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 {
- // Nice-to-have only
- }
- }
- submitSupportTicket(payload)
- }
- const handleFormSubmit = form.handleSubmit(onSubmit)
- const handleSubmitButtonClick: MouseEventHandler<HTMLButtonElement> = (event) => {
- handleFormSubmit(event)
- }
- const showPlanExpectationInfo =
- !!selectedOrgSlug &&
- subscriptionPlanId !== 'enterprise' &&
- subscriptionPlanId !== 'platform' &&
- category !== 'Login_issues'
- const showDirectEmailInfo = state.type !== 'success' && selectedProjectRef !== undefined
- return (
- <Form {...form}>
- <form id="support-form" className="flex min-h-full flex-col">
- <div className="flex flex-col gap-y-6">
- <OrganizationSelector form={form} orgSlug={organizationSlug} />
- <ProjectAndPlanInfo
- form={form}
- orgSlug={selectedOrgSlug}
- projectRef={currentProjectRef}
- subscriptionPlanId={subscriptionPlanId}
- category={category}
- />
- <CategoryAndSeverityInfo
- form={form}
- category={category}
- severity={severity}
- projectRef={projectRef}
- />
- </div>
- <div className="flex flex-col gap-y-6 py-6">
- <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>
- {(DASHBOARD_LOG_CATEGORIES.includes(category) ||
- (!!category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(category)) ||
- showPlanExpectationInfo ||
- showDirectEmailInfo) && (
- <div className="flex flex-col gap-y-6">
- <Separator />
- {DASHBOARD_LOG_CATEGORIES.includes(category) && (
- <DashboardLogsToggle form={form} sanitizedLog={sanitizedLogSnapshot} align="right" />
- )}
- {!!category && !DISABLE_SUPPORT_ACCESS_CATEGORIES.includes(category) && (
- <SupportAccessToggle form={form} align="right" />
- )}
- {(showPlanExpectationInfo || showDirectEmailInfo) && (
- <SupportFormV3AdditionalInfoSection
- orgSlug={selectedOrgSlug}
- subscriptionPlanId={subscriptionPlanId}
- projectRef={currentProjectRef}
- showPlanExpectationInfo={showPlanExpectationInfo}
- showDirectEmailInfo={showDirectEmailInfo}
- />
- )}
- </div>
- )}
- <div className="sticky bottom-0 z-10 -mx-5 mt-6 border-t bg-panel-footer-light px-5 py-4">
- <SubmitButton
- isSubmitting={state.type === 'submitting'}
- userEmail={respondToEmail}
- onClick={handleSubmitButtonClick}
- descriptionClassName="pr-0"
- />
- </div>
- </form>
- </Form>
- )
- }
- interface SupportFormV3AdditionalInfoSectionProps {
- orgSlug: string | null
- subscriptionPlanId?: OrganizationPlanID
- projectRef: string | null
- showPlanExpectationInfo: boolean
- showDirectEmailInfo: boolean
- }
- function SupportFormV3AdditionalInfoSection({
- orgSlug,
- subscriptionPlanId,
- projectRef,
- showPlanExpectationInfo,
- showDirectEmailInfo,
- }: SupportFormV3AdditionalInfoSectionProps) {
- return (
- <div className="flex flex-col gap-y-5">
- {showPlanExpectationInfo && orgSlug && (
- <div className="flex flex-col gap-y-2">
- <h5 className="text-foreground">Support varies by plan</h5>
- <PlanExpectationInfoContent orgSlug={orgSlug} planId={subscriptionPlanId} />
- </div>
- )}
- {showDirectEmailInfo && (
- <div className="flex flex-col gap-y-2">
- <h5 className="text-foreground">Having trouble submitting the form?</h5>
- <SupportFormDirectEmailContent projectRef={projectRef} />
- </div>
- )}
- </div>
- )
- }
|