import { SupportCategories } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { ChevronRight, CpuIcon, Lock, Microchip } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' import { UseFormReturn } from 'react-hook-form' import { Button, cn, FormField, RadioGroupCard, RadioGroupCardItem, Skeleton, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { ComputeBadge } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { DiskStorageSchemaType } from '../DiskManagement.schema' import { ComputeInstanceAddonVariantId, InfraInstanceSize } from '../DiskManagement.types' import { calculateComputeSizePrice, ComputeAddonVariant, getAvailableComputeOptions, } from '../DiskManagement.utils' import { BillingChangeBadge } from '../ui/BillingChangeBadge' import FormMessage from '../ui/FormMessage' import { NoticeBar } from '../ui/NoticeBar' import { SupportLink } from '@/components/interfaces/Support/SupportLink' import { DocsButton } from '@/components/ui/DocsButton' import { InlineLink } from '@/components/ui/InlineLink' import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { getCloudProviderArchitecture } from '@/lib/cloudprovider-utils' import { DOCS_URL } from '@/lib/constants' const INITIALLY_VISIBLE_COUNT = 6 /** * to do: this could be a type from api-types */ type ComputeSizeFieldProps = { form: UseFormReturn disabled?: boolean } export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { const { ref } = useParams() const { data: org } = useSelectedOrganizationQuery() const { data: project, isPending: isProjectLoading } = useSelectedProjectQuery() const { hasAccess: entitledUpdateCompute, isLoading: isEntitlementLoading } = useCheckEntitlements('instances.compute_update_available_sizes') const showComputePrice = useIsFeatureEnabled('project_addons:show_compute_price') const { computeSize } = form.watch() const { data: addons, isPending: isAddonsLoading, error: addonsError, } = useProjectAddonsQuery({ projectRef: ref }) const isLoading = isProjectLoading || isAddonsLoading || isEntitlementLoading const { control, formState, setValue, trigger } = form const availableAddons = useMemo(() => { return addons?.available_addons ?? [] }, [addons]) const availableOptions = useMemo(() => { /** * Returns the available compute options for the project * Also handles backwards compatibility for older API versions * Also handles a case in which Nano is not available from the API */ return getAvailableComputeOptions(availableAddons, project?.cloud_provider) }, [availableAddons, project?.cloud_provider]) // Expand by default if the project's current compute size is beyond the initial visible set const [showAllSizes, setShowAllSizes] = useState(() => { const idx = availableOptions.findIndex((o) => o.identifier === computeSize) return idx >= INITIALLY_VISIBLE_COUNT }) // Expand whenever the selected size falls outside the visible set — covers both initial data // load (availableOptions starts empty) and computeSize changes after mount (e.g. form reset) useEffect(() => { const idx = availableOptions.findIndex((o) => o.identifier === computeSize) if (idx >= INITIALLY_VISIBLE_COUNT) { setShowAllSizes(true) } }, [computeSize, availableOptions]) const subscriptionPitr = addons?.selected_addons.find((addon) => addon.type === 'pitr') const computeSizePrice = calculateComputeSizePrice({ availableOptions: availableOptions, oldComputeSize: form.formState.defaultValues?.computeSize || 'ci_micro', newComputeSize: form.getValues('computeSize'), plan: org?.plan.id ?? 'free', }) const projectComputeSize = project?.infra_compute_size ?? 'nano' const showUpgradeBadge = entitledUpdateCompute && projectComputeSize === 'nano' const selectedOptionIndex = availableOptions.findIndex((o) => o.identifier === computeSize) const selectedOptionIsHidden = selectedOptionIndex >= INITIALLY_VISIBLE_COUNT // Always show all options if the selected one would be outside the visible slice, // so the active card is never hidden from the user. const visibleOptions = showAllSizes || selectedOptionIsHidden ? availableOptions : availableOptions.slice(0, INITIALLY_VISIBLE_COUNT) const hasHiddenOptions = availableOptions.length > INITIALLY_VISIBLE_COUNT return ( ( { setValue('computeSize', value, { shouldDirty: true, shouldValidate: true, }) trigger('provisionedIOPS') trigger('throughput') }} defaultValue={field.value} disabled={disabled} >

Hardware resources allocated to your Postgres database

} >
{isLoading ? ( Array(INITIALLY_VISIBLE_COUNT) .fill(0) .map((_, i) => ) ) : addonsError ? (

{addonsError?.message}

) : ( <> {visibleOptions.map((compute) => { const cpuArchitecture = getCloudProviderArchitecture(project?.cloud_provider) const lockedMicroDueToPITR = compute.identifier === 'ci_micro' && !!subscriptionPitr const lockedNanoDueToPlan = org?.plan.id !== 'free' && project?.infra_compute_size !== 'nano' && compute.identifier === 'ci_nano' const lockedOption = lockedNanoDueToPlan || lockedMicroDueToPITR const price = org?.plan.id !== 'free' && project?.infra_compute_size === 'nano' && compute.identifier === 'ci_nano' ? availableOptions.find( (option: ComputeAddonVariant) => option.identifier === 'ci_micro' )?.price : compute.price const cpuLabel = (() => { const cpuCores = compute.meta?.cpu_cores if (typeof cpuCores === 'number') { return `${cpuCores}-core ${cpuArchitecture} CPU` } if (cpuCores) { return `${cpuCores} CPU` } return 'CPU' })() return (
{showUpgradeBadge && compute.identifier === 'ci_micro' && (
No additional charge
)}
{lockedOption ? (
) : ( showComputePrice && ( <> ${price} {' '} /{' '} {compute.price_interval === 'monthly' ? 'month' : 'hour'} ) )}
{compute.identifier === 'ci_nano' && 'Up to '} {compute.meta?.memory_gb ?? 0} GB memory
{cpuLabel}
{lockedMicroDueToPITR && ( Project has PITR enabled which requires a minimum of Small compute. Please{' '} disable PITR {' '} first before selecting Micro )} } /> ) })} {showAllSizes && ( e.preventDefault()} className={cn( 'relative text-sm text-left flex flex-col gap-0 px-0 py-3 [&_label]:w-full group w-full h-[110px]' )} label={
Contact Us
Custom memory
Custom CPU
} /> )} )}
{!isLoading && !addonsError && hasHiddenOptions && ( )}
)} /> ) }