SupportSidebarForm.tsx154 lines · main
| 1 | // @ts-nocheck |
| 2 | import * as Sentry from '@sentry/nextjs' |
| 3 | import { Loader2 } from 'lucide-react' |
| 4 | import Link from 'next/link' |
| 5 | import { useCallback, useReducer } from 'react' |
| 6 | import { toast } from 'sonner' |
| 7 | import { Button, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui' |
| 8 | |
| 9 | import { IncidentAdmonition } from './IncidentAdmonition' |
| 10 | import { Success } from './Success' |
| 11 | import type { ExtendedSupportCategories } from './Support.constants' |
| 12 | import { createInitialSupportFormState, supportFormReducer } from './SupportForm.state' |
| 13 | import type { SupportFormUrlKeys } from './SupportForm.utils' |
| 14 | import { SupportFormV3 } from './SupportFormV3' |
| 15 | import { useSupportForm } from './useSupportForm' |
| 16 | import { useIncidentStatusQuery } from '@/data/platform/incident-status-query' |
| 17 | import { useSendEventMutation } from '@/data/telemetry/send-event-mutation' |
| 18 | import { useStateTransition } from '@/hooks/misc/useStateTransition' |
| 19 | |
| 20 | function useSupportFormTelemetry() { |
| 21 | const { mutate: sendEvent } = useSendEventMutation() |
| 22 | |
| 23 | return useCallback( |
| 24 | ({ |
| 25 | projectRef, |
| 26 | orgSlug, |
| 27 | category, |
| 28 | }: { |
| 29 | projectRef: string | undefined |
| 30 | orgSlug: string | undefined |
| 31 | category: ExtendedSupportCategories |
| 32 | }) => |
| 33 | sendEvent({ |
| 34 | action: 'support_ticket_submitted', |
| 35 | properties: { |
| 36 | ticketCategory: category, |
| 37 | }, |
| 38 | groups: { |
| 39 | project: projectRef, |
| 40 | organization: orgSlug, |
| 41 | }, |
| 42 | }), |
| 43 | [sendEvent] |
| 44 | ) |
| 45 | } |
| 46 | |
| 47 | interface SupportFormProps { |
| 48 | initialParams?: Partial<SupportFormUrlKeys> |
| 49 | onFinish?: () => void |
| 50 | } |
| 51 | |
| 52 | export function SupportForm({ initialParams, onFinish }: SupportFormProps) { |
| 53 | const [state, dispatch] = useReducer(supportFormReducer, undefined, createInitialSupportFormState) |
| 54 | const { form, initialError, projectRef } = useSupportForm(dispatch, initialParams) |
| 55 | |
| 56 | const { |
| 57 | data: allStatusPageEvents, |
| 58 | isPending: isIncidentsPending, |
| 59 | isError: isIncidentsError, |
| 60 | } = useIncidentStatusQuery() |
| 61 | const { incidents = [] } = allStatusPageEvents ?? {} |
| 62 | const hasActiveIncidents = |
| 63 | !isIncidentsPending && !isIncidentsError && incidents && incidents.length > 0 |
| 64 | |
| 65 | const sendTelemetry = useSupportFormTelemetry() |
| 66 | useStateTransition(state, 'submitting', 'success', (_, curr) => { |
| 67 | toast.success('Support request sent. Thank you!') |
| 68 | sendTelemetry({ |
| 69 | projectRef: curr.sentProjectRef, |
| 70 | orgSlug: curr.sentOrgSlug, |
| 71 | category: curr.sentCategory, |
| 72 | }) |
| 73 | }) |
| 74 | |
| 75 | useStateTransition(state, 'submitting', 'error', (_, curr) => { |
| 76 | toast.error(`Failed to submit support ticket: ${curr.message}`) |
| 77 | Sentry.captureMessage(`Failed to submit Support Form: ${curr.message}`) |
| 78 | dispatch({ type: 'RETURN_TO_EDITING' }) |
| 79 | }) |
| 80 | |
| 81 | const isSuccess = state.type === 'success' |
| 82 | |
| 83 | return ( |
| 84 | <div className="relative h-full overflow-y-auto overflow-x-hidden"> |
| 85 | <IncidentAdmonition |
| 86 | isActive={hasActiveIncidents} |
| 87 | className="rounded-none border-x-0 shadow-none" |
| 88 | /> |
| 89 | <div className="min-h-full px-5 pt-5"> |
| 90 | <div className="flex flex-col gap-y-8"> |
| 91 | {isSuccess ? ( |
| 92 | <div className="pt-2"> |
| 93 | <Success |
| 94 | selectedProject={projectRef ?? undefined} |
| 95 | sentCategory={state.sentCategory} |
| 96 | onFinish={onFinish} |
| 97 | finishLabel={onFinish ? 'Done' : undefined} |
| 98 | /> |
| 99 | </div> |
| 100 | ) : ( |
| 101 | <SupportFormV3 |
| 102 | form={form} |
| 103 | initialError={initialError} |
| 104 | state={state} |
| 105 | dispatch={dispatch} |
| 106 | selectedProjectRef={projectRef} |
| 107 | /> |
| 108 | )} |
| 109 | </div> |
| 110 | </div> |
| 111 | </div> |
| 112 | ) |
| 113 | } |
| 114 | |
| 115 | export function SupportFormStatusButton() { |
| 116 | const { data: allStatusPageEvents, isPending: isLoading, isError } = useIncidentStatusQuery() |
| 117 | const { incidents = [], maintenanceEvents = [] } = allStatusPageEvents ?? {} |
| 118 | const isMaintenance = maintenanceEvents.length > 0 |
| 119 | const isIncident = incidents.length > 0 |
| 120 | |
| 121 | return ( |
| 122 | <Tooltip> |
| 123 | <TooltipTrigger asChild> |
| 124 | <Button |
| 125 | asChild |
| 126 | type="default" |
| 127 | size="tiny" |
| 128 | icon={ |
| 129 | isLoading ? ( |
| 130 | <Loader2 className="animate-spin" /> |
| 131 | ) : ( |
| 132 | <div className={cn('h-2 w-2 rounded-full', isIncident ? 'bg-warning' : 'bg-brand')} /> |
| 133 | ) |
| 134 | } |
| 135 | > |
| 136 | <Link href="https://status.supabase.com/" target="_blank" rel="noreferrer"> |
| 137 | {isLoading |
| 138 | ? 'Checking status' |
| 139 | : isError |
| 140 | ? 'Failed to check status' |
| 141 | : isIncident |
| 142 | ? 'Active incident ongoing' |
| 143 | : isMaintenance |
| 144 | ? 'Scheduled maintenance' |
| 145 | : 'All systems operational'} |
| 146 | </Link> |
| 147 | </Button> |
| 148 | </TooltipTrigger> |
| 149 | <TooltipContent side="bottom" align="center"> |
| 150 | Check the Briven status page |
| 151 | </TooltipContent> |
| 152 | </Tooltip> |
| 153 | ) |
| 154 | } |