| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- // End of third-party imports
- import { useParams } from 'common'
- import { AnimatePresence, motion } from 'framer-motion'
- import { Check, ChevronsUpDown, ExternalLink } from 'lucide-react'
- import Link from 'next/link'
- import type { UseFormReturn } from 'react-hook-form'
- import { toast } from 'sonner'
- import { Button, cn, CommandGroup, CommandItem, FormControl, FormField } from 'ui'
- import { Admonition } from 'ui-patterns'
- import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
- import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
- import type { ExtendedSupportCategories } from './Support.constants'
- import type { SupportFormValues } from './SupportForm.schema'
- import { NO_ORG_MARKER, NO_PROJECT_MARKER } from './SupportForm.utils'
- import CopyButton from '@/components/ui/CopyButton'
- import { OrganizationProjectSelector } from '@/components/ui/OrganizationProjectSelector'
- import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
- interface ProjectAndPlanProps {
- form: UseFormReturn<SupportFormValues>
- orgSlug: string | null
- projectRef: string | null
- category: ExtendedSupportCategories
- subscriptionPlanId: string | undefined
- }
- export function ProjectAndPlanInfo({
- form,
- orgSlug,
- projectRef,
- category: _category,
- subscriptionPlanId: _subscriptionPlanId,
- }: ProjectAndPlanProps) {
- const hasProjectSelected = projectRef && projectRef !== NO_PROJECT_MARKER
- return (
- <div className="flex flex-col gap-y-2">
- <ProjectSelector form={form} orgSlug={orgSlug} projectRef={projectRef} />
- <ProjectRefHighlighted projectRef={projectRef} />
- {!hasProjectSelected && (
- <Admonition type="default" description="No project has been selected." />
- )}
- </div>
- )
- }
- interface ProjectSelectorProps {
- form: UseFormReturn<SupportFormValues>
- orgSlug: string | null
- projectRef: string | null
- }
- function ProjectSelector({ form, orgSlug, projectRef }: ProjectSelectorProps) {
- const { projectRef: urlProjectRef } = useParams()
- return (
- <FormField
- name="projectRef"
- control={form.control}
- render={({ field }) => (
- <FormItemLayout hideMessage layout="vertical" label="Which project is affected?">
- <FormControl>
- <OrganizationProjectSelector
- key={orgSlug}
- sameWidthAsTrigger
- fetchOnMount
- checkPosition="left"
- slug={!orgSlug || orgSlug === NO_ORG_MARKER ? undefined : orgSlug}
- selectedRef={field.value}
- onInitialLoad={(projects) => {
- if (!urlProjectRef && (!projectRef || projectRef === NO_PROJECT_MARKER))
- field.onChange(projects[0]?.ref ?? NO_PROJECT_MARKER)
- }}
- onSelect={(project) => field.onChange(project.ref)}
- renderTrigger={({ isLoading, project, listboxId, open }) => {
- return (
- <Button
- block
- type="default"
- role="combobox"
- aria-label="Select a project"
- aria-expanded={open}
- aria-controls={listboxId}
- size="small"
- className="justify-between"
- iconRight={<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />}
- >
- {!!orgSlug && isLoading ? (
- <ShimmeringLoader className="w-44 py-2" />
- ) : !field.value || field.value === NO_PROJECT_MARKER ? (
- 'No specific project'
- ) : (
- (project?.name ?? 'Unknown project')
- )}
- </Button>
- )
- }}
- renderActions={(setOpen) => (
- <CommandGroup>
- <CommandItem
- className="w-full gap-x-2"
- onSelect={() => {
- field.onChange(NO_PROJECT_MARKER)
- setOpen(false)
- }}
- >
- {field.value === NO_PROJECT_MARKER && <Check size={16} />}
- <p className={cn(field.value !== NO_PROJECT_MARKER && 'ml-6')}>
- No specific project
- </p>
- </CommandItem>
- </CommandGroup>
- )}
- />
- </FormControl>
- </FormItemLayout>
- )}
- />
- )
- }
- interface ProjectRefHighlightedProps {
- projectRef: string | null
- }
- function ProjectRefHighlighted({ projectRef }: ProjectRefHighlightedProps) {
- const isVisible = !!projectRef && projectRef !== NO_PROJECT_MARKER
- return (
- <AnimatePresence>
- {isVisible && (
- <motion.div
- initial={{ opacity: 0, height: 0 }}
- animate={{ opacity: 1, height: 'auto' }}
- exit={{ opacity: 0, height: 0 }}
- transition={{ duration: 0.3 }}
- className="flex items-center gap-x-1"
- >
- <p className="text-sm transition text-foreground-lighter">
- Project ID:{' '}
- <code className="text-code-inline text-foreground-light!">{projectRef}</code>
- </p>
- <CopyButton
- iconOnly
- type="text"
- text={projectRef}
- onClick={() => toast.success('Copied project ID to clipboard')}
- />
- </motion.div>
- )}
- </AnimatePresence>
- )
- }
- interface PlanExpectationInfoContentProps {
- orgSlug: string
- planId?: string
- }
- export const PlanExpectationInfoContent = ({
- orgSlug,
- planId,
- }: PlanExpectationInfoContentProps) => {
- const { billingAll } = useIsFeatureEnabled(['billing:all'])
- const shouldShowUpgradeActions = billingAll && planId !== 'enterprise'
- return (
- <div className="flex flex-col gap-y-3 text-sm text-foreground-light">
- {planId === 'free' && (
- <p>
- Support on the Free plan is provided through the community and by the team on a
- best-effort basis. For a guaranteed response time, we recommend upgrading to the Pro plan.
- Enhanced support SLAs are available on the Enterprise plan.
- </p>
- )}
- {planId === 'pro' && (
- <p>
- Pro includes email support with typical 1-business-day responses; upgrade to Team for
- prioritized ticketing and engineering escalation, or Enterprise for enhanced SLAs.
- </p>
- )}
- {planId === 'team' && (
- <p>
- The Team plan includes email support with prioritized ticketing and escalation to product
- engineering. Low, normal, and high-severity tickets are typically handled within 1
- business day. Urgent issues are handled within 1 day, 365 days a year. Enhanced support
- SLAs are available on the Enterprise plan.
- </p>
- )}
- {shouldShowUpgradeActions && (
- <div className="flex flex-wrap gap-2 pt-1">
- <Button asChild size="tiny">
- <Link
- href={`/org/${orgSlug}/billing?panel=subscriptionPlan&source=planSupportExpectationInfoBox`}
- >
- Upgrade plan
- </Link>
- </Button>
- <Button asChild type="default" size="tiny" icon={<ExternalLink />}>
- <Link href="https://supabase.com/contact/enterprise" target="_blank" rel="noreferrer">
- Enquire about Enterprise
- </Link>
- </Button>
- </div>
- )}
- </div>
- )
- }
|