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 (