Files
supabase/apps/studio/components/layouts/AppLayout/useStatusPageBannerVisibility.ts
Charis d22702e907 refactor(studio): extract user project regions + emergency override hooks (#51033)
## Summary
- Extracts `useUserProjectRegions` (org + project region aggregation,
fail-open on fetch errors) and `useEmergencyIncidentOverride` (the
`ongoingIncident` flag / env var check) out of
`useStatusPageBannerVisibility`, so the upcoming incident.io status-page
banner can reuse both without duplicating the org/project fan-out logic.
- No behavior change for the legacy banner except one intentional fix:
`StatusPageBanner.utils.ts` compared the incident's affected regions
(lowercased) against the user's regions (not normalized), so a
mixed-case region on either side could silently fail to match. Both
sides now go through the same `normalizeRegion` helper.
- Part of the Linear FE-4057 stack (PR 5a of 6). Base branch is PR 4
(`charis/fe-4057-pr4-project-creation-status-admonition`), not master.

Linear: FE-4057

## 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` on the touched test files
and the `hooks/misc/` and `components/layouts/AppLayout/` directories
(all passing, no regressions)

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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved status banner targeting by matching incidents against
normalized regions across the user’s projects.
* Updated banner visibility checks to handle incomplete project or
region data, including when project information fails to load.
* Improved loading behavior so the banner can be evaluated based on
user-region data rather than waiting for separate organization and
project requests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-29 17:49:06 -04:00

75 lines
2.7 KiB
TypeScript

import { useQuery } from '@tanstack/react-query'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useCallback } from 'react'
import { getRelevantIncidentIds, shouldShowBanner } from './StatusPageBanner.utils'
import { incidentBannerQueryOptions } from '@/data/platform/incident-banner-query'
import { useEmergencyIncidentOverride } from '@/hooks/misc/useEmergencyIncidentOverride'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useUserProjectRegions } from '@/hooks/misc/useUserProjectRegions'
export type StatusPageBannerData = { title: string; dismiss?: () => void }
const EMPTY_REGIONS: ReadonlySet<string> = new Set()
export function useStatusPageBannerVisibility(): StatusPageBannerData | null {
const showIncidentBannerOverride = useEmergencyIncidentOverride()
const { data: incidentBannerData } = useQuery(incidentBannerQueryOptions())
const bannerItems = incidentBannerData?.incidents ?? []
const incidents = bannerItems.map((i) => ({ id: i.id, cache: i.metadata }))
const hasActiveIncidents = incidents.length > 0
const userProjectRegions = useUserProjectRegions({
enabled: !showIncidentBannerOverride && hasActiveIncidents,
})
const userContext =
userProjectRegions.status === 'resolved' ? userProjectRegions.context : undefined
const hasProjects = userContext?.hasProjects ?? false
const userRegions = userContext?.regions ?? EMPTY_REGIONS
const hasUnknownRegions = userContext !== undefined && !userContext.isComplete
const [dismissedIds, setDismissedIds, { isSuccess: isDismissedLoaded }] = useLocalStorageQuery<
Array<string>
>(LOCAL_STORAGE_KEYS.INCIDENT_BANNER_DISMISSED_IDS, [])
const dismiss = useCallback(() => {
const activeIncidentIds = new Set(incidents.map((i) => i.id))
const relevantIds = getRelevantIncidentIds({
incidents,
hasProjects,
userRegions,
hasUnknownRegions,
})
setDismissedIds((prev) => [
...new Set([...prev.filter((id) => activeIncidentIds.has(id)), ...relevantIds]),
])
}, [incidents, hasProjects, userRegions, hasUnknownRegions, setDismissedIds])
if (showIncidentBannerOverride) return { title: 'We are investigating a technical issue' }
if (!hasActiveIncidents || userProjectRegions.status === 'loading') return null
const dismissedIdSet = new Set(dismissedIds)
const undismissedIncidents = incidents.filter((i) => !dismissedIdSet.has(i.id))
if (
!isDismissedLoaded ||
!shouldShowBanner({
incidents: undismissedIncidents,
hasProjects,
userRegions,
hasUnknownRegions,
})
)
return null
const title = hasProjects
? 'We are investigating a technical issue'
: 'Project creation may be impacted in some regions'
return { title, dismiss }
}