Files
supabase/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx
Ali Waseem 21df8d02e6 Rename best available region option to Auto (#50873)
The region selector's "Best available region" option now reads "Auto"
and carries the Recommended badge. Also removes the "Select the region
closest to your users for the best performance." description, which was
misleading for the auto selection.

Fixes FE-4468


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Updates**
* The recommended region is now labeled “Auto” in both the selected
value and the options list; its “Recommended” badge remains.
* Removed general advice to choose a region closest to users.
High-availability and local/staging notices are unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-24 11:29:31 -06:00

482 lines
20 KiB
TypeScript

import { useFeatureFlags, 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 { getRegionRestrictionCopy, 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<CreateProjectForm>
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
// 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<string, Array<string>> = {
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'
const BestAvailableRegionIcon = () => (
<div className="w-5 border border-brand-500 h-4.5 rounded-[3px] bg-brand-300 flex items-center justify-center">
<ThumbsUp size={10} className="text-brand" />
</div>
)
export const RegionSelector = ({
form,
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 { getRegionRestriction } = useRegionRestriction()
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 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<string | undefined>(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 <AlertError subject="Error loading available regions" error={errorAvailableRegions} />
}
return (
<Panel.Content>
<FormField
control={form.control}
name="dbRegion"
render={({ field }) => {
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
// 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 (
<>
<FormItemLayout
id="region"
layout={layout}
label="Region"
description={
restrictHighAvailabilityRegion ? (
<div className="text-warning">
High Availability projects are currently limited to{' '}
{regionOptions[0]?.name ?? highAvailabilityRegionCode}.
</div>
) : showNonProdFields ? (
<div className="text-warning">
<p>Only these regions are supported for local/staging projects:</p>
<ul className="list-disc list-inside mt-1">
<li>East US (North Virginia)</li>
<li>Central EU (Frankfurt)</li>
<li>Southeast Asia (Singapore)</li>
</ul>
{isLocalEnvironment && (
<p className="mt-1">
Use Central EU (Frankfurt) unless you're on a personal dev stack.
</p>
)}
</div>
) : undefined
}
>
<FormControl>
<Select
value={isBestAvailableActive ? 'best_available' : dbRegion}
onValueChange={(value) => {
if (value === '') return
hasUserSelectedRegionRef.current = true
if (value === 'best_available') {
if (!recommendedSmartRegion) return
onBestAvailableSelectedChange(true)
field.onChange(recommendedSmartRegion.name)
} else {
onBestAvailableSelectedChange(false)
field.onChange(value)
}
}}
disabled={isLoading}
>
<SelectTrigger
ref={field.ref}
id="region"
className="[&>:nth-child(1)]:w-full [&>:nth-child(1)]:flex [&>:nth-child(1)]:items-start"
>
<SelectValue
placeholder={
isLoading
? 'Loading available regions...'
: 'Select a region for your project..'
}
>
{dbRegion !== undefined && (
<div className="flex items-center gap-x-3">
{isLoading && <Loader2 size={14} className="animate-spin" />}
{isBestAvailableActive ? (
<BestAvailableRegionIcon />
) : (
selectedRegion?.code && (
<RegionFlag className="w-5" region={selectedRegion.code} />
)
)}
<span className="text-foreground">{triggerLabel}</span>
</div>
)}
</SelectValue>
</SelectTrigger>
<SelectContent>
{isBestAvailableOptionShown && (
<>
<SelectGroup>
<SelectLabel>Recommendation</SelectLabel>
<SelectItem
value="best_available"
className="w-full [&>:nth-child(2)]:w-full"
>
<div className="flex flex-row items-center justify-between w-full">
<div className="flex items-center gap-x-3">
<BestAvailableRegionIcon />
<span className="text-foreground">Auto</span>
</div>
<Badge variant="success" className="mr-1">
Recommended
</Badge>
</div>
</SelectItem>
</SelectGroup>
<SelectSeparator />
</>
)}
{smartRegionEnabled && !highAvailability && (
<>
<SelectGroup>
<SelectLabel>General regions</SelectLabel>
{smartRegions.map((value) => {
return (
<SelectItem
key={value.code}
value={value.name}
className="w-full [&>:nth-child(2)]:w-full"
>
<div className="flex flex-row items-center justify-between w-full">
<div className="flex items-center gap-x-3">
<RegionFlag className="w-5" region={value.code} />
<span className="text-foreground">
{getDisplayNameForSmartRegion(value.name)}
</span>
</div>
<div>
{!isBestAvailableOptionShown &&
recommendedSmartRegions.has(value.code) && (
<Badge variant="success" className="mr-1">
Recommended
</Badge>
)}
</div>
</div>
</SelectItem>
)
})}
</SelectGroup>
<SelectSeparator />
</>
)}
<SelectGroup>
<SelectLabel>
{highAvailability ? 'High Availability Regions' : 'Specific regions'}
</SelectLabel>
{regionOptionsWithRestriction.map((value) => {
const restrictionCopy =
value.restriction !== undefined
? getRegionRestrictionCopy(value.restriction)
: undefined
return (
<SelectItem
key={value.code}
value={value.name}
className={cn(
'w-full [&>:nth-child(2)]:w-full',
restrictionCopy !== undefined && 'pointer-events-auto!'
)}
>
<div className="flex flex-row items-center justify-between w-full gap-x-2">
<div className="flex items-center gap-x-3">
<RegionFlag className="w-5" region={value.code} />
<div className="flex items-center gap-x-2">
<span className="text-foreground">{value.name}</span>
<span className="text-xs text-foreground-lighter font-mono">
{value.code}
</span>
</div>
</div>
{!isBestAvailableOptionShown &&
recommendedSpecificRegions.has(value.code) && (
<Badge variant="success" className="mr-1">
Recommended
</Badge>
)}
{restrictionCopy !== undefined && (
<Tooltip>
<TooltipTrigger>
<Badge variant="warning" className="mr-1">
{restrictionCopy.badge}
</Badge>
</TooltipTrigger>
<TooltipContent>{restrictionCopy.tooltip}</TooltipContent>
</Tooltip>
)}
</div>
</SelectItem>
)
})}
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
</FormItemLayout>
{affectingIncidents.length > 0 && (
<FormItemLayout layout="horizontal">
<Admonition
type="warning"
title="Incident in progress for this region"
description={
<>
We're currently investigating an issue that may impact projects in this
region. Follow updates on{' '}
<InlineLink href="https://status.supabase.com">
status.supabase.com
</InlineLink>
.
</>
}
className="mt-3"
/>
</FormItemLayout>
)}
{selectedRestrictionCopy !== undefined && (
<FormItemLayout layout="horizontal" isReactForm={false}>
<Admonition
type="warning"
title={selectedRestrictionCopy.title}
description={`${selectedRestrictionCopy.notice} ${SELECT_DIFFERENT_REGION}`}
className="mt-3"
/>
</FormItemLayout>
)}
</>
)
}}
/>
</Panel.Content>
)
}