import { useFeatureFlags, useParams } from 'common' import { Loader2 } 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 { 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 instanceSize?: DesiredInstanceSize layout?: 'vertical' | 'horizontal' } // [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 // Maps smart region group codes to the specific-region code prefixes they contain. // Used to check whether an incident affecting specific regions also affects a smart region selection. const SMART_REGION_PREFIXES: Record> = { americas: ['us-', 'ca-', 'sa-'], emea: ['eu-', 'me-', 'af-'], apac: ['ap-'], } function smartRegionMatchesSpecific(smartCode: string, specificCode: string): boolean { return (SMART_REGION_PREFIXES[smartCode] ?? []).some((prefix) => specificCode.startsWith(prefix)) } // 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' export const RegionSelector = ({ form, instanceSize, layout = 'horizontal', }: 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 { data: statusData } = useIncidentStatusQuery() 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 }, { enabled: smartRegionEnabled, staleTime: 1000 * 60 * 5 } // 5 minutes ) 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 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 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 triggerLabel = isLoading ? 'Loading available regions...' : selectedRegionLabel const affectingIncidents = incidents.filter((incident) => { const affectedRegions = incident.cache?.affected_regions ?? [] if (affectedRegions.length === 0 || selectedRegion?.code === undefined) return false // Specific region: direct code match if (affectedRegions.includes(selectedRegion.code)) return true // Smart region: match if any affected region falls within the smart group return affectedRegions.some((specificCode) => smartRegionMatchesSpecific(selectedRegion.code, specificCode) ) }) return ( <>

Select the region closest to your users for the best performance.

{restrictHighAvailabilityRegion ? (
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.

)}
) )} } >
{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" /> )} ) }} />
) }