Files
supabase/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx
T
CharisandJoshen Lim 277441334f feat(studio): Display region-specific incidents in RegionSelector (#43308)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Feature

## What is the current behavior?

Region-specific incidents are only shown in the global StatusPageBanner,
which doesn't clearly indicate that an incident only affects specific
regions during project creation.

## What is the new behavior?

Region-specific incidents are now displayed inline in the RegionSelector
with smart region matching to show which regions are affected. The
StatusPageBanner logic is updated to avoid duplicate incident notices
for region-specific incidents when creating projects.

## Additional context

<img width="1394" height="650" alt="CleanShot 2026-03-02 at 16 32 34@2x"
src="https://github.com/user-attachments/assets/fd3734dc-8049-4c24-82d8-456bfbdbd4fd"
/>

Resolves FE-2652

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-03-03 15:22:55 +08:00

320 lines
13 KiB
TypeScript

import { useFlag, useParams } from 'common'
import { UseFormReturn } from 'react-hook-form'
import type { CloudProvider } from 'shared-data'
import {
Badge,
cn,
FormField_Shadcn_,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectLabel_Shadcn_,
SelectSeparator_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { CreateProjectForm } from './ProjectCreation.schema'
import { getAvailableRegions } from './ProjectCreation.utils'
import AlertError from '@/components/ui/AlertError'
import { InlineLink } from '@/components/ui/InlineLink'
import Panel from '@/components/ui/Panel'
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'
import { BASE_PATH, PROVIDERS } from '@/lib/constants'
interface RegionSelectorProps {
form: UseFormReturn<CreateProjectForm>
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<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
}
export const RegionSelector = ({
form,
instanceSize,
layout = 'horizontal',
}: RegionSelectorProps) => {
const { slug } = useParams()
const cloudProvider = form.getValues('cloudProvider') as CloudProvider
const smartRegionEnabled = useFlag('enableSmartRegion')
const { data: statusData } = useIncidentStatusQuery()
const { incidents = [] } = statusData ?? {}
const { isPending: isLoadingDefaultRegion } = useDefaultRegionQuery(
{ cloudProvider },
{ enabled: !smartRegionEnabled }
)
const {
data: availableRegionsData,
isPending: isLoadingAvailableRegions,
isError: isErrorAvailableRegions,
error: errorAvailableRegions,
} = useOrganizationAvailableRegionsQuery(
{ slug, cloudProvider, desiredInstanceSize: instanceSize },
{ enabled: smartRegionEnabled, staleTime: 1000 * 60 * 5 } // 5 minutes
)
const smartRegions = availableRegionsData?.all.smartGroup ?? []
const allRegions = availableRegionsData?.all.specific ?? []
const recommendedSmartRegions = new Set(
[availableRegionsData?.recommendations.smartGroup.code].filter(Boolean)
)
const recommendedSpecificRegions = new Set(
availableRegionsData?.recommendations.specific.map((region) => region.code)
)
const availableRegions = getAvailableRegions(PROVIDERS[cloudProvider].id)
const regionsArray = Object.entries(availableRegions).map(([_key, value]) => {
return {
code: value.code,
name: value.displayName,
provider: cloudProvider,
status: undefined,
}
})
const regionOptions = smartRegionEnabled ? allRegions : regionsArray
const isLoading = smartRegionEnabled ? isLoadingAvailableRegions : isLoadingDefaultRegion
const showNonProdFields =
process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' ||
process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
const allSelectableRegions = [...smartRegions, ...regionOptions]
if (isErrorAvailableRegions) {
return <AlertError subject="Error loading available regions" error={errorAvailableRegions} />
}
return (
<Panel.Content>
<FormField_Shadcn_
control={form.control}
name="dbRegion"
render={({ field }) => {
const selectedRegion = allSelectableRegions.find((region) => {
return !!region.name && region.name === field.value
})
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
layout={layout}
label="Region"
description={
<>
<p>Select the region closest to your users for the best performance.</p>
{showNonProdFields && (
<div className="mt-2 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>
</div>
)}
</>
}
>
<Select_Shadcn_
value={field.value}
onValueChange={field.onChange}
disabled={isLoading}
>
<SelectTrigger_Shadcn_ className="[&>:nth-child(1)]:w-full [&>:nth-child(1)]:flex [&>:nth-child(1)]:items-start">
<SelectValue_Shadcn_
placeholder={
isLoading
? 'Loading available regions...'
: 'Select a region for your project..'
}
>
{field.value !== undefined && (
<div className="flex items-center gap-x-3">
{selectedRegion?.code && (
<img
alt="region icon"
className="w-5 rounded-sm"
src={`${BASE_PATH}/img/regions/${selectedRegion.code}.svg`}
/>
)}
<span className="text-foreground">
{selectedRegion?.name
? getDisplayNameForSmartRegion(selectedRegion.name)
: field.value}
</span>
</div>
)}
</SelectValue_Shadcn_>
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
{smartRegionEnabled && (
<>
<SelectGroup_Shadcn_>
<SelectLabel_Shadcn_>General regions</SelectLabel_Shadcn_>
{smartRegions.map((value) => {
return (
<SelectItem_Shadcn_
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">
<img
alt="region icon"
className="w-5 rounded-sm"
src={`${BASE_PATH}/img/regions/${value.code}.svg`}
/>
<span className="text-foreground">
{getDisplayNameForSmartRegion(value.name)}
</span>
</div>
<div>
{recommendedSmartRegions.has(value.code) && (
<Badge variant="success" className="mr-1">
Recommended
</Badge>
)}
</div>
</div>
</SelectItem_Shadcn_>
)
})}
</SelectGroup_Shadcn_>
<SelectSeparator_Shadcn_ />
</>
)}
<SelectGroup_Shadcn_>
<SelectLabel_Shadcn_>Specific regions</SelectLabel_Shadcn_>
{regionOptions.map((value) => {
return (
<SelectItem_Shadcn_
key={value.code}
value={value.name}
className={cn(
'w-full [&>:nth-child(2)]:w-full',
value.status !== undefined && '!pointer-events-auto'
)}
disabled={value.status !== undefined}
>
<div className="flex flex-row items-center justify-between w-full gap-x-2">
<div className="flex items-center gap-x-3">
<img
alt="region icon"
className="w-5 rounded-sm"
src={`${BASE_PATH}/img/regions/${value.code}.svg`}
/>
<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>
{recommendedSpecificRegions.has(value.code) && (
<Badge variant="success" className="mr-1">
Recommended
</Badge>
)}
{value.status !== undefined && value.status === 'capacity' && (
<Tooltip>
<TooltipTrigger>
<Badge variant="warning" className="mr-1">
Unavailable
</Badge>
</TooltipTrigger>
<TooltipContent>
Temporarily unavailable due to this region being at capacity.
</TooltipContent>
</Tooltip>
)}
</div>
</SelectItem_Shadcn_>
)
})}
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</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>
)}
</>
)
}}
/>
</Panel.Content>
)
}