import { useFeatureFlags, useFlag, useParams } from 'common' import { Loader2, ThumbsUp } from 'lucide-react' import { useEffect, useRef } from 'react' import { UseFormReturn } from 'react-hook-form' import type { CloudProvider } from 'shared-data' import { Badge, cn, FormControl, FormField, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Tooltip, TooltipContent, TooltipTrigger, useWatch, } from 'ui' import { Admonition } from 'ui-patterns/Admonition' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { CreateProjectForm } from './ProjectCreation.schema' import { filterHighAvailabilityRegions, getAvailableRegions, getHighAvailabilityRegionCode, } from './ProjectCreation.utils' import { ProjectCreationStatusAdmonition } from './ProjectCreationStatusAdmonition' import { getRegionRestrictionCopy, regionMatches, SELECT_DIFFERENT_REGION, } from './RegionSelector.utils' import { useRegionRestriction } from './useRegionRestriction' import { AlertError } from '@/components/ui/AlertError' import { InlineLink } from '@/components/ui/InlineLink' import Panel from '@/components/ui/Panel' import { RegionFlag } from '@/components/ui/RegionFlag' import { useDefaultRegionQuery } from '@/data/misc/get-default-region-query' import { useOrganizationAvailableRegionsQuery } from '@/data/organizations/organization-available-regions-query' import { useIncidentStatusQuery } from '@/data/platform/incident-status-query' import type { DesiredInstanceSize } from '@/data/projects/new-project.constants' interface RegionSelectorProps { form: UseFormReturn hasSelectedOrganization: boolean instanceSize?: DesiredInstanceSize layout?: 'vertical' | 'horizontal' showBestAvailableRegionOption: boolean isBestAvailableSelected: boolean onBestAvailableSelectedChange: (value: boolean) => void } // [Joshen] Let's use a library to maintain the flag SVGs in the future // I tried using https://flagpack.xyz/docs/development/react/ but couldn't get it to render // ^ can try again next time // Map backend region names to user-friendly display names const getDisplayNameForSmartRegion = (name: string): string => { if (name === 'APAC') { return 'Asia-Pacific' } return name } const isLocal = process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' const BestAvailableRegionIcon = () => (
) export const RegionSelector = ({ form, hasSelectedOrganization, instanceSize, layout = 'horizontal', showBestAvailableRegionOption, isBestAvailableSelected, onBestAvailableSelectedChange, }: RegionSelectorProps) => { const { slug } = useParams() const cloudProvider = form.getValues('cloudProvider') as CloudProvider const highAvailability = useWatch({ control: form.control, name: 'highAvailability' }) const dbRegion = useWatch({ control: form.control, name: 'dbRegion' }) const highAvailabilityRegionCode = getHighAvailabilityRegionCode() const { hasLoaded: flagsLoaded } = useFeatureFlags() const smartRegionEnabled = cloudProvider !== 'AWS_NIMBUS' const isStatusPageEnabled = useFlag('incidentIoStatusPage') === true const { getRegionRestriction } = useRegionRestriction() const { data: statusData } = useIncidentStatusQuery({ enabled: !isStatusPageEnabled }) const { incidents = [] } = statusData ?? {} const { isPending: isLoadingDefaultRegion } = useDefaultRegionQuery( { cloudProvider }, { enabled: flagsLoaded && !smartRegionEnabled } ) const { data: availableRegionsData, isPending: isLoadingAvailableRegions, isError: isErrorAvailableRegions, error: errorAvailableRegions, } = useOrganizationAvailableRegionsQuery( { slug, cloudProvider, desiredInstanceSize: instanceSize, highAvailability }, { enabled: smartRegionEnabled && hasSelectedOrganization, staleTime: 1000 * 60 * 5, } ) const allSmartRegions = availableRegionsData?.all.smartGroup ?? [] const allRegions = availableRegionsData?.all.specific ?? [] const restrictHighAvailabilityRegion = highAvailability && !isLocal && highAvailabilityRegionCode !== undefined const smartRegions = highAvailability ? [] : allSmartRegions const recommendedSmartRegions = new Set( [availableRegionsData?.recommendations.smartGroup.code].filter(Boolean) ) const recommendedSmartRegion = smartRegions.find( (x) => x.code === recommendedSmartRegions.values().next().value ) const isBestAvailableOptionShown = showBestAvailableRegionOption && !!recommendedSmartRegion const isBestAvailableActive = isBestAvailableSelected && isBestAvailableOptionShown // Defaults free orgs to the "Best available region" shortcut once the recommendation loads, // unless the user has already interacted with the region field themselves. const hasUserSelectedRegionRef = useRef(false) useEffect(() => { if (hasUserSelectedRegionRef.current) return if (isBestAvailableOptionShown) { onBestAvailableSelectedChange(true) } }, [isBestAvailableOptionShown, onBestAvailableSelectedChange]) // Keeps dbRegion following the recommendation while "Best available" is active, so a // recommendation change (e.g. after a refetch) doesn't leave the form submitting a stale // region behind a label that still reads "Auto". useEffect(() => { if (!isBestAvailableSelected || !recommendedSmartRegion) return if (dbRegion !== recommendedSmartRegion.name) { form.setValue('dbRegion', recommendedSmartRegion.name) } }, [isBestAvailableSelected, recommendedSmartRegion, dbRegion, form]) const recommendedSpecificRegions = new Set( availableRegionsData?.recommendations.specific.map((region) => region.code) ) const availableRegions = getAvailableRegions(cloudProvider) const regionsArray = Object.entries(availableRegions).map(([_key, value]) => { return { code: value.code, name: value.displayName, provider: cloudProvider, status: undefined, } }) const unfilteredRegionOptions = smartRegionEnabled ? allRegions : regionsArray const regionOptions = filterHighAvailabilityRegions( [...unfilteredRegionOptions], highAvailability ) const regionOptionsWithRestriction = regionOptions.map((region) => ({ ...region, restriction: getRegionRestriction(region), })) const isLoading = smartRegionEnabled ? isLoadingAvailableRegions : isLoadingDefaultRegion const isLocalEnvironment = process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' const showNonProdFields = isLocalEnvironment || process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging' const allSelectableRegions = [...smartRegions, ...regionOptions] // react-hook-form intermittently drops this field's value when its Controller // remounts (e.g. a sibling section mounting/unmounting in the same update, such as // toggling high availability), so a one-shot effect isn't enough. Instead this effect // re-asserts off the watched value: a region present in the current list is kept (and // remembered in lastValidRegionRef), and when it's missing or cleared out from under us // it restores the last valid region. allSelectableRegions is intentionally omitted from // deps — it's a new array every render, and comparing it by reference would defeat the // point of reacting to genuine content changes on every render where they occur. const lastValidRegionRef = useRef(undefined) useEffect(() => { if (allSelectableRegions.length === 0) return const isRegionAvailable = (name: string | undefined) => !!name && allSelectableRegions.some((region) => region.name === name) if (isRegionAvailable(dbRegion)) { lastValidRegionRef.current = dbRegion return } const lastValidRegion = lastValidRegionRef.current if (lastValidRegion !== undefined && isRegionAvailable(lastValidRegion)) { form.setValue('dbRegion', lastValidRegion) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [dbRegion, form]) if (isErrorAvailableRegions) { return } return ( { const selectedRegion = allSelectableRegions.find((region) => { return !!region.name && region.name === dbRegion }) const selectedRegionLabel = selectedRegion?.name ? getDisplayNameForSmartRegion(selectedRegion.name) : dbRegion const selectedRestriction = regionOptionsWithRestriction.find( (region) => region.name === dbRegion )?.restriction const selectedRestrictionCopy = selectedRestriction !== undefined ? getRegionRestrictionCopy(selectedRestriction) : undefined const triggerLabel = isLoading ? 'Loading available regions...' : isBestAvailableActive ? 'Auto' : selectedRegionLabel const affectingIncidents = incidents.filter((incident) => { const affectedRegions = incident.cache?.affected_regions ?? [] if (affectedRegions.length === 0 || selectedRegion?.code === undefined) return false return affectedRegions.some((affectedRegion) => regionMatches(selectedRegion.code, affectedRegion) ) }) return ( <> High Availability projects are currently limited to{' '} {regionOptions[0]?.name ?? highAvailabilityRegionCode}. ) : showNonProdFields ? (

Only these regions are supported for local/staging projects:

  • East US (North Virginia)
  • Central EU (Frankfurt)
  • Southeast Asia (Singapore)
{isLocalEnvironment && (

Use Central EU (Frankfurt) unless you're on a personal dev stack.

)}
) : undefined } >
{isStatusPageEnabled ? ( ) : ( affectingIncidents.length > 0 && ( We're currently investigating an issue that may impact projects in this region. Follow updates on{' '} status.supabase.com . } className="mt-3" /> ) )} {selectedRestrictionCopy !== undefined && ( )} ) }} />
) }