From cf1e95dcd7217309b18798cfcce34b337ed4e5ce Mon Sep 17 00:00:00 2001
From: Charis <26616127+charislam@users.noreply.github.com>
Date: Fri, 8 May 2026 11:35:32 -0400
Subject: [PATCH] studio: maintenance banner for shared pooler 2026-05-13
(#45695)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add a second notice banner (because we need the first one to show the
current ToS update). Scoped to ap-southeast-1 and sa-east-1.
Haven't linked to the StatusPage maintenance entry yet as it's not up;
the placeholder link is just to the generic StatusPage.
## Summary by CodeRabbit
* **New Features**
* Added a second notice banner that alerts users to upcoming maintenance
for affected databases in specific regions; it appears conditionally
(based on affected projects) and can be dismissed—dismissal prevents it
from reappearing.
* The existing “Updated Terms of Service” notice remains unchanged and
continues to display on non–sign-in routes until acknowledged.
---
.../interfaces/App/AppBannerWrapper.tsx | 4 +-
.../layouts/AppLayout/NoticeBanner.tsx | 78 +++++++++++++++++++
2 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/apps/studio/components/interfaces/App/AppBannerWrapper.tsx b/apps/studio/components/interfaces/App/AppBannerWrapper.tsx
index d6ffc44e191..b59ad529fab 100644
--- a/apps/studio/components/interfaces/App/AppBannerWrapper.tsx
+++ b/apps/studio/components/interfaces/App/AppBannerWrapper.tsx
@@ -3,11 +3,12 @@ import { PropsWithChildren } from 'react'
import { OrganizationResourceBanner } from '../Organization/HeaderBanner'
import { ClockSkewBanner } from '@/components/layouts/AppLayout/ClockSkewBanner'
-import { NoticeBanner } from '@/components/layouts/AppLayout/NoticeBanner'
+import { NoticeBanner, NoticeBanner2 } from '@/components/layouts/AppLayout/NoticeBanner'
import { StatusPageBanner } from '@/components/layouts/AppLayout/StatusPageBanner'
export const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => {
const showNoticeBanner = useFlag('showNoticeBanner')
+ const showNoticeBanner2 = useFlag('showNoticeBanner2')
const clockSkewBanner = useFlag('clockSkewBanner')
return (
@@ -15,6 +16,7 @@ export const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => {
{showNoticeBanner &&
}
+ {showNoticeBanner2 &&
}
{/* Disabled until reintroduced or removed altogether. */}
{/*
*/}
diff --git a/apps/studio/components/layouts/AppLayout/NoticeBanner.tsx b/apps/studio/components/layouts/AppLayout/NoticeBanner.tsx
index ccd6fee5974..2421f7697c4 100644
--- a/apps/studio/components/layouts/AppLayout/NoticeBanner.tsx
+++ b/apps/studio/components/layouts/AppLayout/NoticeBanner.tsx
@@ -1,3 +1,4 @@
+import { useQueries } from '@tanstack/react-query'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useRouter } from 'next/router'
import {
@@ -17,6 +18,12 @@ import {
import { HeaderBanner } from '@/components/interfaces/Organization/HeaderBanner'
import { InlineLink, InlineLinkClassName } from '@/components/ui/InlineLink'
+import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
+import { projectKeys } from '@/data/projects/keys'
+import {
+ getOrganizationProjects,
+ type OrgProject,
+} from '@/data/projects/org-projects-infinite-query'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
// Update this whenever the banner content below changes so old client bundles
@@ -53,6 +60,77 @@ export const NoticeBanner = () => {
)
}
+const MAINTENANCE_REGIONS = new Set(['ap-southeast-1', 'sa-east-1'])
+
+export const NoticeBanner2 = () => {
+ const id = 'maintenance-2026-05-13'
+ const expiry = new Date('2026-05-14T23:59:00Z')
+ const isExpired = new Date() > expiry
+
+ const router = useRouter()
+
+ const [bannerAcknowledged, setBannerAcknowledged, { isSuccess }] = useLocalStorageQuery(
+ LOCAL_STORAGE_KEYS.MAINTENANCE_BANNER_DISMISSED(id),
+ false
+ )
+
+ const shouldEvaluate =
+ !router.pathname.includes('sign-in') && isSuccess && !bannerAcknowledged && !isExpired
+
+ const { data: organizations } = useOrganizationsQuery({ enabled: shouldEvaluate })
+ const orgProjectsQueries = useQueries({
+ queries: (organizations ?? []).map((org) => ({
+ queryKey: projectKeys.bannerProjectsByOrg(org.slug),
+ queryFn: () => getOrganizationProjects({ slug: org.slug, limit: 100 }),
+ staleTime: 30 * 60 * 1000,
+ enabled: shouldEvaluate,
+ })),
+ })
+
+ const isProjectsFetched =
+ organizations !== undefined &&
+ (organizations.length === 0 || orgProjectsQueries.every((q) => q.isFetched))
+
+ const hasMaintenanceRegionProject = orgProjectsQueries
+ .flatMap((q) => q.data?.projects ?? [])
+ .some((project: OrgProject) =>
+ project.databases.some((db) => MAINTENANCE_REGIONS.has(db.region))
+ )
+
+ if (!shouldEvaluate || !isProjectsFetched || !hasMaintenanceRegionProject) {
+ return null
+ }
+
+ return (
+
+ Shared pooler maintenance in{' '}
+
+ ap-southeast-1
+ {' '}
+ and{' '}
+
+ sa-east-1
+ {' '}
+ on May 13-14.
+ >
+ }
+ onDismiss={() => setBannerAcknowledged(true)}
+ />
+ )
+}
+
const UpdatedTermsOfServiceDialog = ({ onDismiss }: { onDismiss: () => void }) => {
return (