| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- // @ts-nocheck
- import * as Sentry from '@sentry/nextjs'
- import { Loader2, Wrench } from 'lucide-react'
- import Link from 'next/link'
- import { useCallback, useReducer, type Dispatch, type PropsWithChildren } from 'react'
- import type { UseFormReturn } from 'react-hook-form'
- import SVG from 'react-inlinesvg'
- import { toast } from 'sonner'
- import { Button, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
- import { Admonition } from 'ui-patterns/admonition'
- import { AIAssistantOption } from './AIAssistantOption'
- import { DiscordCTACard } from './DiscordCTACard'
- import { IncidentAdmonition } from './IncidentAdmonition'
- import { Success } from './Success'
- import type { ExtendedSupportCategories } from './Support.constants'
- import type { SupportFormValues } from './SupportForm.schema'
- import {
- createInitialSupportFormState,
- supportFormReducer,
- type SupportFormActions,
- type SupportFormState,
- } from './SupportForm.state'
- import { NO_PROJECT_MARKER } from './SupportForm.utils'
- import { SupportFormV2 } from './SupportFormV2'
- import { useSupportForm } from './useSupportForm'
- import CopyButton from '@/components/ui/CopyButton'
- import { useIncidentStatusQuery } from '@/data/platform/incident-status-query'
- import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
- import { useStateTransition } from '@/hooks/misc/useStateTransition'
- import { BASE_PATH, DOCS_URL } from '@/lib/constants'
- export { SupportForm, SupportFormStatusButton } from './SupportSidebarForm'
- function useSupportFormTelemetry() {
- const { mutate: sendEvent } = useSendEventMutation()
- return useCallback(
- ({
- projectRef,
- orgSlug,
- category,
- }: {
- projectRef: string | undefined
- orgSlug: string | undefined
- category: ExtendedSupportCategories
- }) =>
- sendEvent({
- action: 'support_ticket_submitted',
- properties: {
- ticketCategory: category,
- },
- groups: {
- project: projectRef,
- organization: orgSlug,
- },
- }),
- [sendEvent]
- )
- }
- export function SupportFormPage() {
- return <SupportFormPageContent />
- }
- function SupportFormPageContent() {
- const [state, dispatch] = useReducer(supportFormReducer, undefined, createInitialSupportFormState)
- const { form, initialError, projectRef, orgSlug } = useSupportForm(dispatch)
- const {
- data: allStatusPageEvents,
- isPending: isIncidentsPending,
- isError: isIncidentsError,
- } = useIncidentStatusQuery()
- const { incidents = [] } = allStatusPageEvents ?? {}
- const hasActiveIncidents =
- !isIncidentsPending && !isIncidentsError && incidents && incidents.length > 0
- const sendTelemetry = useSupportFormTelemetry()
- useStateTransition(state, 'submitting', 'success', (_, curr) => {
- toast.success('Support request sent. Thank you!')
- sendTelemetry({
- projectRef: curr.sentProjectRef,
- orgSlug: curr.sentOrgSlug,
- category: curr.sentCategory,
- })
- })
- useStateTransition(state, 'submitting', 'error', (_, curr) => {
- toast.error(`Failed to submit support ticket: ${curr.message}`)
- Sentry.captureMessage(`Failed to submit Support Form: ${curr.message}`)
- dispatch({ type: 'RETURN_TO_EDITING' })
- })
- const isSuccess = state.type === 'success'
- return (
- <SupportFormWrapper>
- <SupportFormHeader />
- <IncidentAdmonition isActive={hasActiveIncidents} />
- {!isSuccess && !hasActiveIncidents && (
- <div className="flex flex-col gap-y-4">
- <AIAssistantOption projectRef={projectRef} organizationSlug={orgSlug} />
- <DiscordCTACard organizationSlug={orgSlug} />
- </div>
- )}
- <SupportFormBody
- form={form}
- state={state}
- dispatch={dispatch}
- initialError={initialError}
- selectedProjectRef={projectRef}
- />
- {!isSuccess && <SupportFormDirectEmailInfo projectRef={projectRef} />}
- </SupportFormWrapper>
- )
- }
- function SupportFormWrapper({ children }: PropsWithChildren) {
- return (
- <div className="relative overflow-y-auto overflow-x-hidden">
- <div className="mx-auto my-16 max-w-2xl w-full px-4 lg:px-6">
- <div className="flex flex-col gap-y-8">{children}</div>
- </div>
- </div>
- )
- }
- function SupportFormHeader() {
- const { data: allStatusPageEvents, isPending: isLoading, isError } = useIncidentStatusQuery()
- const { incidents = [], maintenanceEvents = [] } = allStatusPageEvents ?? {}
- const isMaintenance = maintenanceEvents.length > 0
- const isIncident = incidents.length > 0
- return (
- <div className="flex flex-col items-start justify-between gap-y-2 sm:flex-row sm:items-center">
- <div className="flex items-center space-x-3">
- <SVG src={`${BASE_PATH}/img/briven-logo.svg`} className="h-4 w-4" />
- <h3 className="m-0 text-lg">Briven support</h3>
- </div>
- <div className="flex items-center gap-x-3">
- <Button asChild type="default" icon={<Wrench />}>
- <Link
- href={`${DOCS_URL}/guides/troubleshooting?products=platform`}
- target="_blank"
- rel="noreferrer"
- >
- Troubleshooting
- </Link>
- </Button>
- <Tooltip>
- <TooltipTrigger asChild>
- <Button
- asChild
- type="default"
- icon={
- isLoading ? (
- <Loader2 className="animate-spin" />
- ) : (
- <div
- className={cn('h-2 w-2 rounded-full', isIncident ? 'bg-warning' : 'bg-brand')}
- />
- )
- }
- >
- <Link href="https://status.supabase.com/" target="_blank" rel="noreferrer">
- {isLoading
- ? 'Checking status'
- : isError
- ? 'Failed to check status'
- : isIncident
- ? 'Active incident ongoing'
- : isMaintenance
- ? 'Scheduled maintenance'
- : 'All systems operational'}
- </Link>
- </Button>
- </TooltipTrigger>
- <TooltipContent side="bottom" align="center">
- Check the Briven status page
- </TooltipContent>
- </Tooltip>
- </div>
- </div>
- )
- }
- interface SupportFormDirectEmailInfoProps {
- projectRef: string | null
- }
- function SupportFormDirectEmailInfo({ projectRef }: SupportFormDirectEmailInfoProps) {
- const hasProjectRef = projectRef && projectRef !== NO_PROJECT_MARKER
- return (
- <Admonition
- type="default"
- title="Having trouble submitting the form?"
- description={
- <>
- <p className="mb-2.5!">
- Please email us directly. Include your project ID and as much information as possible.
- </p>
- <p className="flex items-center gap-x-1.5 flex-wrap">
- Email:{' '}
- <span className="inline-flex items-center gap-x-1">
- <a
- href={`mailto:support@supabase.com?subject=${encodeURIComponent('Support Request')}${hasProjectRef ? `${encodeURIComponent(' for Project ID: ')}${encodeURIComponent(projectRef)}` : ''}&body=${encodeURIComponent('Here is a detailed description of the problem I am experiencing and any other information that might be helpful...')}`}
- className="hover:text-foreground transition-colors duration-100"
- >
- <code className="text-code-inline text-foreground-light! underline decoration-foreground-lighter/50 hover:decoration-foreground-lighter/80 transition-colors duration-100">
- support@supabase.com
- </code>
- </a>
- <CopyButton
- type="text"
- text="support@supabase.com"
- iconOnly
- onClick={() => toast.success('Copied email address to clipboard')}
- />
- </span>
- </p>
- {hasProjectRef && (
- <p className="flex items-center gap-x-1.5 flex-wrap">
- Project ID:{' '}
- <span className="inline-flex items-center gap-x-1">
- <code className="text-code-inline text-foreground-light!">{projectRef}</code>
- <CopyButton
- iconOnly
- type="text"
- text={projectRef}
- onClick={() => toast.success('Copied project ID to clipboard')}
- />
- </span>
- </p>
- )}
- </>
- }
- />
- )
- }
- interface SupportFromBodyProps {
- form: UseFormReturn<SupportFormValues>
- state: SupportFormState
- dispatch: Dispatch<SupportFormActions>
- initialError: string | null
- selectedProjectRef: string | null
- }
- function SupportFormBody({
- form,
- state,
- dispatch,
- initialError,
- selectedProjectRef,
- }: SupportFromBodyProps) {
- const isSuccess = state.type === 'success'
- return (
- <div
- className={cn(
- 'min-w-full w-full space-y-12 rounded-sm border bg-panel-body-light shadow-md',
- `${isSuccess ? 'pt-8' : 'py-8'}`,
- 'border-default'
- )}
- >
- {isSuccess ? (
- <Success
- selectedProject={selectedProjectRef ?? undefined}
- sentCategory={state.sentCategory}
- />
- ) : (
- <SupportFormV2 form={form} initialError={initialError} state={state} dispatch={dispatch} />
- )}
- </div>
- )
- }
|