Files
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

44 lines
1.9 KiB
TypeScript

export const INFINITE_PROJECTS_KEY_PREFIX = 'all-projects-infinite'
export const projectKeys = {
infiniteList: (params?: {
limit: number
sort?: 'name_asc' | 'name_desc' | 'created_asc' | 'created_desc'
search?: string
}) => [INFINITE_PROJECTS_KEY_PREFIX, params].filter(Boolean),
infiniteListByOrg: (
slug: string | undefined,
params?: {
limit: number
sort?: 'name_asc' | 'name_desc' | 'created_asc' | 'created_desc'
search?: string
statuses?: string[]
}
) => [INFINITE_PROJECTS_KEY_PREFIX, slug, params].filter(Boolean),
status: (projectRef: string | undefined) => ['project', projectRef, 'status'] as const,
types: (projectRef: string | undefined) => ['project', projectRef, 'types'] as const,
detail: (projectRef: string | undefined) => ['project', projectRef, 'detail'] as const,
serviceVersions: (projectRef: string | undefined) =>
['project', projectRef, 'service-versions'] as const,
readonlyStatus: (projectRef: string | undefined) =>
['projects', projectRef, 'readonly-status'] as const,
projectTransfer: (projectRef: string | undefined, targetOrganizationSlug: string | undefined) =>
['projects', 'transfer', projectRef, targetOrganizationSlug] as const,
projectTransferPreview: (
projectRef: string | undefined,
targetOrganizationSlug: string | undefined
) => ['projects', 'transfer', projectRef, targetOrganizationSlug, 'preview'] as const,
pauseStatus: (projectRef: string | undefined) =>
['projects', projectRef, 'pause-status'] as const,
projectMembers: (projectRef: string | undefined) => ['project', projectRef, 'members'] as const,
// Clone to new project
listCloneBackups: (projectRef: string | undefined) =>
['projects', projectRef, 'clone-backups'] as const,
listCloneStatus: (projectRef: string | undefined) =>
['projects', projectRef, 'clone-status'] as const,
bannerProjectsByOrg: (slug: string) => ['banner', 'org-projects', slug] as const,
}