Files
supabase/apps/www/components/Regions/RegionsList.tsx
T
292c08b7b7 Added new /regions page (#49306)
## 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 and data residency information is split across docs, `/security`,
and legal pages.
[MARKET-1866](https://linear.app/supabase/issue/MARKET-1866/package-and-display-available-regions-better-on-website)

## What is the new behavior?

Adds `/regions`: a catalog of all 17 regions generated from
`regions.ts`, plus what stays in-region, the Europe vs EU caveat, and
links to the DPA, GDPR guide, sub-processor list, and security page.

Regions is in the footer under Security & Compliance. The security page
residency card now links here.

## Test plan

- [ ] Open `/regions` in light and dark mode
- [ ] Confirm the region count and list match
`packages/shared-data/regions.ts`
- [ ] Confirm footer Regions link and `/security` residency link go to
`/regions`


Made with [Cursor](https://cursor.com)

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

- **New Features**
  - Added a Regions page showcasing available AWS regions by geography.
- Added an interactive map and region list with selection, hover states,
keyboard accessibility, and residency badges.
- Included data residency guidance, legal resources, and a
call-to-action for next steps.
  - Added Regions links in the site footer and security documentation.

- **Documentation**
- Updated agent skill resources with expanded troubleshooting and
operational guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Francesco Sansalvadore <f.sansalvadore@gmail.com>
2026-08-27 12:45:28 +01:00

76 lines
2.8 KiB
TypeScript

'use client'
import { Badge, cn } from 'ui'
import { GROUPED_REGIONS } from './Regions.constants'
interface Props {
selectedRegionCode: string | null
hoveredRegionCode?: string
onRegionHover: (code?: string) => void
onRegionSelect: (code: string) => void
}
export const RegionsList = ({
selectedRegionCode,
hoveredRegionCode,
onRegionHover,
onRegionSelect,
}: Props) => (
<div className="grid grid-cols-1 gap-8 md:grid-cols-3 lg:gap-12">
{GROUPED_REGIONS.map(({ group, regions }) => (
<div key={group} className="flex flex-col gap-3">
<h3 className="font-mono text-xs uppercase tracking-widest text-foreground-muted">
{group}
</h3>
<ul className="flex flex-col divide-y divide-border-muted border-t border-border-muted">
{regions.map((region) => {
const isSelected = region.code === selectedRegionCode
return (
<li key={region.code}>
<button
type="button"
tabIndex={0}
aria-pressed={isSelected}
onClick={() => onRegionSelect(region.code)}
onMouseEnter={() => onRegionHover(region.code)}
onMouseLeave={() => onRegionHover(undefined)}
onFocus={() => onRegionHover(region.code)}
onBlur={() => onRegionHover(undefined)}
className={cn(
// Bleeds the hover background 8px each side so row content stays aligned with the heading
'-mx-2 flex w-[calc(100%+1rem)] items-center gap-3 rounded-sm px-2 py-2.5 text-left transition-colors',
'hover:bg-surface-100 focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:outline-none',
isSelected && 'bg-surface-100',
!isSelected && region.code === hoveredRegionCode && 'bg-surface-100'
)}
>
{/* Flags are SVGs, which next/image can't serve while dangerouslyAllowSVG is off */}
<img
alt=""
className="w-4 shrink-0 rounded-xs"
src={`/images/regions/${region.code}.svg`}
/>
<span
className={cn(
'text-xs',
isSelected ? 'text-foreground' : 'text-foreground-light'
)}
>
{region.displayName}
</span>
{region.jurisdiction && <Badge>{region.jurisdiction}</Badge>}
<code className="ml-auto shrink-0 font-mono text-xs text-foreground-lighter">
{region.code}
</code>
</button>
</li>
)
})}
</ul>
</div>
))}
</div>
)