From 53b20cad5ea89ed016f685f90f92477d30398daa Mon Sep 17 00:00:00 2001
From: Charis <26616127+charislam@users.noreply.github.com>
Date: Tue, 29 Sep 2026 17:37:01 -0400
Subject: [PATCH] feat(studio): project creation status admonition (#51029)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
- Adds a new project-creation-form incident warning path behind the
`incidentIoStatusPage` ConfigCat flag (default off — no visible behavior
change until the flag is enabled)
- When the flag is on, reads the new `/api/status-page` endpoint (added
earlier in this stack) instead of the legacy `/api/incident-status`
endpoint, and only fetches the legacy endpoint when the flag is off
- Extracts region-matching logic into `RegionSelector.utils.ts`
(`regionMatches`, `getItemsAffectingProjectCreation`) with unit test
coverage
See Linear FE-4057 for full context and design.
## Test plan
- [x] `pnpm --filter studio run typecheck`
- [x] `pnpm --filter studio run lint:ratchet`
- [x] `pnpm knip --workspace apps/studio`
- [x] `pnpm test:prettier`
- [x] `pnpm --filter studio exec vitest run
components/interfaces/ProjectCreation/RegionSelector.utils.test.ts`
(35/35 passing)
## Summary by CodeRabbit
* **New Features**
* Project creation displays a warning and links to the status page when
an incident or maintenance event may affect the selected region.
* Status notices match exact regions and broader smart-region groupings;
global project-creation notices are also shown.
* The AI assistant can report visible, active incidents and in-progress
maintenance, including affected components and update details. When
there are no active events, it reports that status.
---
.../ProjectCreationStatusAdmonition.tsx | 41 +++++++
.../ProjectCreation/RegionSelector.tsx | 72 ++++++------
.../RegionSelector.utils.test.ts | 106 ++++++++++++++++++
.../ProjectCreation/RegionSelector.utils.ts | 33 ++++++
4 files changed, 213 insertions(+), 39 deletions(-)
create mode 100644 apps/studio/components/interfaces/ProjectCreation/ProjectCreationStatusAdmonition.tsx
diff --git a/apps/studio/components/interfaces/ProjectCreation/ProjectCreationStatusAdmonition.tsx b/apps/studio/components/interfaces/ProjectCreation/ProjectCreationStatusAdmonition.tsx
new file mode 100644
index 00000000000..e2ad9aff1f6
--- /dev/null
+++ b/apps/studio/components/interfaces/ProjectCreation/ProjectCreationStatusAdmonition.tsx
@@ -0,0 +1,41 @@
+import { useQuery } from '@tanstack/react-query'
+import { Admonition } from 'ui-patterns/Admonition'
+import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
+
+import { getItemsAffectingProjectCreation } from './RegionSelector.utils'
+import { InlineLink } from '@/components/ui/InlineLink'
+import { statusPageQueryOptions } from '@/data/platform/status-page-query'
+import { normalizeStatusPage } from '@/lib/status-page/status-page.utils'
+
+export const ProjectCreationStatusAdmonition = ({
+ selectedRegionCode,
+}: {
+ selectedRegionCode?: string
+}) => {
+ const { data } = useQuery({ ...statusPageQueryOptions(), select: normalizeStatusPage })
+ if (!data) return null
+
+ const items = getItemsAffectingProjectCreation(data.items, selectedRegionCode)
+ const item = items[0]
+ if (!item) return null
+
+ const isMaintenance = item.kind === 'maintenance'
+ const isRegionSpecific = item.projectCreationScope?.type === 'regions'
+
+ return (
+
+
+ {isMaintenance ? 'Maintenance' : 'An incident'} may affect project creation
+ {isRegionSpecific ? ' in this region' : ''}. Follow updates on{' '}
+ status page.
+ >
+ }
+ className="mt-3"
+ />
+
+ )
+}
diff --git a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx
index 68844b3be40..587b31e247b 100644
--- a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx
+++ b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.tsx
@@ -1,4 +1,4 @@
-import { useFeatureFlags, useParams } from 'common'
+import { useFeatureFlags, useFlag, useParams } from 'common'
import { Loader2, ThumbsUp } from 'lucide-react'
import { useEffect, useRef } from 'react'
import { UseFormReturn } from 'react-hook-form'
@@ -30,7 +30,12 @@ import {
getAvailableRegions,
getHighAvailabilityRegionCode,
} from './ProjectCreation.utils'
-import { getRegionRestrictionCopy, SELECT_DIFFERENT_REGION } from './RegionSelector.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'
@@ -55,18 +60,6 @@ interface RegionSelectorProps {
// 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') {
@@ -100,10 +93,11 @@ export const RegionSelector = ({
const { hasLoaded: flagsLoaded } = useFeatureFlags()
const smartRegionEnabled = cloudProvider !== 'AWS_NIMBUS'
+ const isStatusPageEnabled = useFlag('incidentIoStatusPage') === true
const { getRegionRestriction } = useRegionRestriction()
- const { data: statusData } = useIncidentStatusQuery()
+ const { data: statusData } = useIncidentStatusQuery({ enabled: !isStatusPageEnabled })
const { incidents = [] } = statusData ?? {}
const { isPending: isLoadingDefaultRegion } = useDefaultRegionQuery(
@@ -250,12 +244,8 @@ export const RegionSelector = ({
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 affectedRegions.some((affectedRegion) =>
+ regionMatches(selectedRegion.code, affectedRegion)
)
})
@@ -447,24 +437,28 @@ export const RegionSelector = ({
- {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"
- />
-
+ {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 && (
diff --git a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.utils.test.ts b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.utils.test.ts
index 9f2a1f3d6b6..d3c71228949 100644
--- a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.utils.test.ts
+++ b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.utils.test.ts
@@ -1,13 +1,16 @@
import { describe, expect, it, vi } from 'vitest'
import {
+ getItemsAffectingProjectCreation,
getRegionRestrictionCopy,
getRegionRestrictionMessage,
parseRestrictedRegions,
REGION_RESTRICTION_COPY,
+ regionMatches,
resolveRegionRestriction,
SELECT_DIFFERENT_REGION,
} from './RegionSelector.utils'
+import type { StatusItem } from '@/lib/status-page/status-page.utils'
vi.mock('@/lib/error-reporting', () => ({ captureCriticalError: vi.fn() }))
@@ -103,3 +106,106 @@ describe('getRegionRestrictionCopy', () => {
)
})
})
+
+describe('regionMatches', () => {
+ it('matches an exact region code', () => {
+ expect(regionMatches('us-east-1', 'us-east-1')).toBe(true)
+ })
+
+ it('matches a smart group against a specific region it contains', () => {
+ expect(regionMatches('americas', 'us-east-1')).toBe(true)
+ })
+
+ it('does not match a smart group against a region from a different group', () => {
+ expect(regionMatches('apac', 'us-east-1')).toBe(false)
+ })
+
+ it.each([
+ ['US-EAST-1', 'us-east-1'],
+ ['us-east-1', 'US-EAST-1'],
+ ['AMERICAS', 'us-east-1'],
+ ])('matches case-insensitively (%s vs %s)', (selectedCode, affectedRegion) => {
+ expect(regionMatches(selectedCode, affectedRegion)).toBe(true)
+ })
+
+ it('does not match unrelated specific regions', () => {
+ expect(regionMatches('us-east-1', 'eu-central-1')).toBe(false)
+ })
+})
+
+describe('getItemsAffectingProjectCreation', () => {
+ type IncidentItem = Extract
+ type UpcomingMaintenanceItem = Extract
+
+ function buildItem(overrides: Partial = {}): IncidentItem {
+ return {
+ id: 'incident-1',
+ name: 'Project creation degraded',
+ url: 'https://status.supabase.com/incidents/1',
+ lastUpdateAt: '2026-01-01T00:00:00Z',
+ lastUpdateMessage: null,
+ components: [],
+ scope: { type: 'global' },
+ projectCreationScope: null,
+ isProjectCreationOnly: true,
+ showBanner: true,
+ kind: 'incident',
+ status: 'investigating',
+ impact: 'partial_outage',
+ ...overrides,
+ }
+ }
+
+ function buildUpcomingMaintenanceItem(
+ overrides: Partial = {}
+ ): UpcomingMaintenanceItem {
+ return {
+ id: 'maintenance-1',
+ name: 'Upcoming project creation maintenance',
+ url: 'https://status.supabase.com/incidents/2',
+ lastUpdateAt: '2026-01-01T00:00:00Z',
+ lastUpdateMessage: null,
+ components: [],
+ scope: { type: 'global' },
+ projectCreationScope: null,
+ isProjectCreationOnly: true,
+ showBanner: true,
+ kind: 'upcoming_maintenance',
+ startsAt: '2026-02-01T00:00:00Z',
+ endsAt: '2026-02-02T00:00:00Z',
+ leadDays: 7,
+ ...overrides,
+ }
+ }
+
+ it('includes a global project creation item regardless of the selected region', () => {
+ const item = buildItem({ projectCreationScope: { type: 'global' } })
+ expect(getItemsAffectingProjectCreation([item], undefined)).toEqual([item])
+ expect(getItemsAffectingProjectCreation([item], 'us-east-1')).toEqual([item])
+ })
+
+ it('excludes a region-scoped item when no region is selected', () => {
+ const item = buildItem({ projectCreationScope: { type: 'regions', regions: ['us-east-1'] } })
+ expect(getItemsAffectingProjectCreation([item], undefined)).toEqual([])
+ })
+
+ it('excludes an item that does not affect project creation', () => {
+ const item = buildItem({ projectCreationScope: null })
+ expect(getItemsAffectingProjectCreation([item], 'us-east-1')).toEqual([])
+ })
+
+ it('excludes upcoming maintenance even with a matching project creation scope', () => {
+ const item = buildUpcomingMaintenanceItem({ projectCreationScope: { type: 'global' } })
+ expect(getItemsAffectingProjectCreation([item], undefined)).toEqual([])
+ })
+
+ it('still includes an item with showBanner false', () => {
+ const item = buildItem({ projectCreationScope: { type: 'global' }, showBanner: false })
+ expect(getItemsAffectingProjectCreation([item], undefined)).toEqual([item])
+ })
+
+ it('includes a region-scoped item matching the selected region via a smart group', () => {
+ const item = buildItem({ projectCreationScope: { type: 'regions', regions: ['us-east-1'] } })
+ expect(getItemsAffectingProjectCreation([item], 'americas')).toEqual([item])
+ })
+})
diff --git a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.utils.ts b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.utils.ts
index 877e9887d20..d6984eb8ad5 100644
--- a/apps/studio/components/interfaces/ProjectCreation/RegionSelector.utils.ts
+++ b/apps/studio/components/interfaces/ProjectCreation/RegionSelector.utils.ts
@@ -1,6 +1,7 @@
import { z } from 'zod'
import { captureCriticalError } from '@/lib/error-reporting'
+import { normalizeRegion, type StatusItem } from '@/lib/status-page/status-page.utils'
export const RESTRICTED_REGIONS_FLAG_KEY = 'projectCreationRestrictedRegions'
@@ -93,3 +94,35 @@ export function resolveRegionRestriction({
}: ResolveRegionRestrictionArgs): string | undefined {
return platformStatus ?? flagRestriction
}
+
+// Maps smart region group codes to the specific-region code prefixes they contain.
+// Used to check whether a status item affecting specific regions also affects a smart region selection.
+export const SMART_REGION_PREFIXES: Record> = {
+ americas: ['us-', 'ca-', 'sa-'],
+ emea: ['eu-', 'me-', 'af-'],
+ apac: ['ap-'],
+}
+
+export function regionMatches(selectedCode: string, affectedRegion: string): boolean {
+ const normalizedSelected = normalizeRegion(selectedCode)
+ const normalizedAffected = normalizeRegion(affectedRegion)
+ if (normalizedSelected === normalizedAffected) return true
+ return (SMART_REGION_PREFIXES[normalizedSelected] ?? []).some((prefix) =>
+ normalizedAffected.startsWith(prefix)
+ )
+}
+
+export function getItemsAffectingProjectCreation(
+ items: Array,
+ selectedRegionCode: string | undefined
+): Array {
+ return items.filter((item) => {
+ if (item.kind !== 'incident' && item.kind !== 'maintenance') return false
+ if (item.projectCreationScope === null) return false
+ if (item.projectCreationScope.type === 'global') return true
+ return (
+ selectedRegionCode !== undefined &&
+ item.projectCreationScope.regions.some((region) => regionMatches(selectedRegionCode, region))
+ )
+ })
+}