From 24f62c4402f80bdc8877696d2a210fe28fa5bbba Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 22 May 2026 17:40:12 +0700 Subject: [PATCH] Joshen/fe 3432 show maintenance banners only for affected project regions (#46191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context There'll be an upcoming shared pooler maintenance for specific regions - so the PR here informs users about that via the following changes - Shift Terms of Service update into a float banner - Same local storage key is used for dismissal so users who already dismissed it won't see this again - This is to prevent 2 notice banners scenario image - Updated NoticeBanner to inform users of the upcoming maintenance - Only projects in specific regions will be affected, so not all projects will see this image image - Cleaned up unused local storage keys ### To test - Can be tested on preview as the notice applies for eu-central-1 and us-east-1 ## Summary by CodeRabbit * **New Features** * Dismissible Terms of Service update banner with “Learn more” dialog so users can review and acknowledge changes. * Enhanced maintenance notices showing region-specific maintenance windows, a status link per region, and formatted UTC timestamps tied to the selected project. [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46191?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) --- .../interfaces/App/AppBannerWrapper.tsx | 31 ++++- .../layouts/AppLayout/NoticeBanner.tsx | 129 +++++++----------- .../ui/BannerStack/BannerStackProvider.tsx | 1 + .../BannerStack/Banners/BannerTOSUpdate.tsx | 123 +++++++++++++++++ packages/common/constants/local-storage.ts | 8 +- 5 files changed, 207 insertions(+), 85 deletions(-) create mode 100644 apps/studio/components/ui/BannerStack/Banners/BannerTOSUpdate.tsx diff --git a/apps/studio/components/interfaces/App/AppBannerWrapper.tsx b/apps/studio/components/interfaces/App/AppBannerWrapper.tsx index a2e00299285..b4a45644668 100644 --- a/apps/studio/components/interfaces/App/AppBannerWrapper.tsx +++ b/apps/studio/components/interfaces/App/AppBannerWrapper.tsx @@ -1,16 +1,43 @@ -import { useFlag } from 'common' -import { PropsWithChildren } from 'react' +import { LOCAL_STORAGE_KEYS, useFlag } from 'common' +import { PropsWithChildren, useEffect } from 'react' import { OrganizationResourceBanner } from '../Organization/HeaderBanner' import { ClockSkewBanner } from '@/components/layouts/AppLayout/ClockSkewBanner' import { FlyDeprecationBanner } from '@/components/layouts/AppLayout/FlyDeprecationBanner' import { NoticeBanner } from '@/components/layouts/AppLayout/NoticeBanner' import { StatusPageBanner } from '@/components/layouts/AppLayout/StatusPageBanner' +import { BannerTOSUpdate } from '@/components/ui/BannerStack/Banners/BannerTOSUpdate' +import { useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider' +import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' + +const TOSUpdateExpiry = new Date('2026-07-04T00:00:00Z') export const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => { const showNoticeBanner = useFlag('showNoticeBanner') const clockSkewBanner = useFlag('clockSkewBanner') + const { addBanner, dismissBanner } = useBannerStack() + + const [TOSUpdateAcknowledged, , { isSuccess }] = useLocalStorageQuery( + LOCAL_STORAGE_KEYS.TERMS_OF_SERVICE_UPDATE, + false + ) + + useEffect(() => { + if (Date.now() >= TOSUpdateExpiry.getTime()) return + + if (isSuccess && !TOSUpdateAcknowledged) { + addBanner({ + id: 'tos-update-banner', + isDismissed: false, + content: , + priority: 2, + }) + } else { + dismissBanner('tos-update-banner') + } + }, [TOSUpdateAcknowledged, isSuccess, addBanner, dismissBanner]) + return (
diff --git a/apps/studio/components/layouts/AppLayout/NoticeBanner.tsx b/apps/studio/components/layouts/AppLayout/NoticeBanner.tsx index ccd6fee5974..d4c42b213e4 100644 --- a/apps/studio/components/layouts/AppLayout/NoticeBanner.tsx +++ b/apps/studio/components/layouts/AppLayout/NoticeBanner.tsx @@ -1,43 +1,62 @@ -import { LOCAL_STORAGE_KEYS } from 'common' +import { LOCAL_STORAGE_KEYS, useParams } from 'common' +import dayjs from 'dayjs' import { useRouter } from 'next/router' -import { - Button, - cn, - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogSection, - DialogSectionSeparator, - DialogTitle, - DialogTrigger, -} from 'ui' +import { TimestampInfo } from 'ui-patterns' import { HeaderBanner } from '@/components/interfaces/Organization/HeaderBanner' -import { InlineLink, InlineLinkClassName } from '@/components/ui/InlineLink' import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' // Update this whenever the banner content below changes so old client bundles // stop displaying outdated notices after the relevant date passes. -const BANNER_EXPIRES_AT = new Date('2026-07-04T00:00:00Z') +const BANNER_EXPIRES_AT = new Date('2026-06-01T15:00:00Z') + +const SUPAVISOR_UPDATE_REGIONS = { + 'eu-central-1': { + start: Date.UTC(2026, 4, 26, 13, 0, 0), + end: Date.UTC(2026, 4, 26, 15, 0, 0), + url: 'https://status.supabase.com/incidents/jy1tm4wfs68t', + }, + 'eu-west-1': { + start: Date.UTC(2026, 4, 27, 13, 0, 0), + end: Date.UTC(2026, 4, 27, 15, 0, 0), + url: 'https://status.supabase.com/incidents/3t293hpd545z', + }, + 'us-west-1': { + start: Date.UTC(2026, 4, 28, 13, 0, 0), + end: Date.UTC(2026, 4, 28, 15, 0, 0), + url: 'https://status.supabase.com/incidents/8f72bnv3xs8r', + }, + 'us-east-1': { + start: Date.UTC(2026, 5, 1, 13, 0, 0), + end: Date.UTC(2026, 5, 1, 15, 0, 0), + url: 'https://status.supabase.com/incidents/7zgmgh2p343n', + }, +} /** * Used to display urgent notices that apply for all users, such as maintenance windows. */ export const NoticeBanner = () => { const router = useRouter() + const { ref } = useParams() + const { data: project } = useSelectedProjectQuery() const [bannerAcknowledged, setBannerAcknowledged, { isSuccess }] = useLocalStorageQuery( - LOCAL_STORAGE_KEYS.TERMS_OF_SERVICE_UPDATE, + LOCAL_STORAGE_KEYS.SUPAVISOR_MAINTENANCE(ref ?? ''), false ) + const region = project?.region ?? '' + const maintenanceWindow = + SUPAVISOR_UPDATE_REGIONS[region as keyof typeof SUPAVISOR_UPDATE_REGIONS] + if ( Date.now() >= BANNER_EXPIRES_AT.getTime() || router.pathname.includes('sign-in') || !isSuccess || + !project || + !maintenanceWindow || bannerAcknowledged ) { return null @@ -46,65 +65,23 @@ export const NoticeBanner = () => { return ( setBannerAcknowledged(true)} />} + title="Upcoming maintenance" + description={ + <> + Shared pooler maintenance in{' '} + + {project.region} + {' '} + on{' '} + + . + + } onDismiss={() => setBannerAcknowledged(true)} /> ) } - -const UpdatedTermsOfServiceDialog = ({ onDismiss }: { onDismiss: () => void }) => { - return ( - - - Learn more - - - - Terms of Service update - - We've updated our Terms of Service to better define the responsibilities of both you and - Supabase in the use of AI. - - - - - - -

- We've clarified how we use AI in our customer support tooling, introduced guidelines for - the responsible use of AI by our users, and updated our indemnification terms to clarify - the allocation of responsibility for claims arising from AI-generated inputs and - outputs. -

- -

- Additionally, we've made an explicit commitment that Supabase will never use the data - you submit to the Supabase services to train or improve any AI without your prior - written consent. -

- -

- The updated Terms (Version 2) will take effect on June 6, 2026. By continuing to use the - Services after that date, you agree to the updated Terms. You can review the changes{' '} - here. -

- -

- This notice applies to users on Supabase's standard Terms of Service only. If you are on - an Enterprise plan or with a separately negotiated agreement, your existing terms - continue to govern your use of the Services. -

-
- - - - - - -
-
- ) -} diff --git a/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx b/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx index 2a9d5526b63..eef631513f0 100644 --- a/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx +++ b/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx @@ -7,6 +7,7 @@ export const BANNER_ID = { RLS_EVENT_TRIGGER: 'rls-event-trigger-banner', RLS_TESTER: 'rls-tester-banner', FREE_MICRO_UPGRADE: 'free-micro-upgrade-banner', + TOS_UPDATE: 'tos-update-banner', } as const export type BannerId = (typeof BANNER_ID)[keyof typeof BANNER_ID] diff --git a/apps/studio/components/ui/BannerStack/Banners/BannerTOSUpdate.tsx b/apps/studio/components/ui/BannerStack/Banners/BannerTOSUpdate.tsx new file mode 100644 index 00000000000..582b07a8a14 --- /dev/null +++ b/apps/studio/components/ui/BannerStack/Banners/BannerTOSUpdate.tsx @@ -0,0 +1,123 @@ +import { LOCAL_STORAGE_KEYS } from 'common' +import { + Badge, + Button, + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogSection, + DialogSectionSeparator, + DialogTitle, + DialogTrigger, +} from 'ui' + +import { InlineLink } from '../../InlineLink' +import { BannerCard } from '../BannerCard' +import { useBannerStack } from '../BannerStackProvider' +import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' + +/** + * [Joshen] TOS update takes place from 6th June onwards, can remove from 4th July onwards as + * previously stated in the NoticeBanner + */ + +export const BannerTOSUpdate = () => { + const { dismissBanner } = useBannerStack() + const [, setTOSUpdateAcknowledged] = useLocalStorageQuery( + LOCAL_STORAGE_KEYS.TERMS_OF_SERVICE_UPDATE, + false + ) + + return ( + { + setTOSUpdateAcknowledged(true) + dismissBanner('tos-update-banner') + }} + > +
+ + Notice + + +
+

We've updated our Terms of Service

+

+ Updates define the responsibilities of both you and Supabase in the use of AI. +

+
+ +
+
+ ) +} + +const UpdatedTermsOfServiceDialog = () => { + const [, setTOSUpdateAcknowledged] = useLocalStorageQuery( + LOCAL_STORAGE_KEYS.TERMS_OF_SERVICE_UPDATE, + false + ) + + return ( + + + + + + + Terms of Service update + + We've updated our Terms of Service to better define the responsibilities of both you and + Supabase in the use of AI. + + + + + + +

+ We've clarified how we use AI in our customer support tooling, introduced guidelines for + the responsible use of AI by our users, and updated our indemnification terms to clarify + the allocation of responsibility for claims arising from AI-generated inputs and + outputs. +

+ +

+ Additionally, we've made an explicit commitment that Supabase will never use the data + you submit to the Supabase services to train or improve any AI without your prior + written consent. +

+ +

+ The updated Terms (Version 2) will take effect on June 6, 2026. By continuing to use the + Services after that date, you agree to the updated Terms. You can review the changes{' '} + here. +

+ +

+ This notice applies to users on Supabase's standard Terms of Service only. If you are on + an Enterprise plan or with a separately negotiated agreement, your existing terms + continue to govern your use of the Services. +

+
+ + + + + + +
+
+ ) +} diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts index 0ba915243d4..2701f03f994 100644 --- a/packages/common/constants/local-storage.ts +++ b/packages/common/constants/local-storage.ts @@ -5,14 +5,12 @@ export const LOCAL_STORAGE_KEYS = { AI_ASSISTANT_STATE: (projectRef: string | undefined) => `supabase-ai-assistant-state-${projectRef}`, SIDEBAR_BEHAVIOR: 'supabase-sidebar-behavior', - EDITOR_PANEL_STATE: 'supabase-editor-panel-state', PROJECTS_VIEW: 'projects-view', PROJECTS_FILTER: 'projects-filter', PROJECTS_SORT: 'projects-sort', FEEDBACK_WIDGET_CONTENT: 'feedback-widget-content', FEEDBACK_WIDGET_SCREENSHOT: 'feedback-widget-screenshot', INCIDENT_BANNER_DISMISSED_IDS: 'incident-banner-dismissed-ids', - MAINTENANCE_BANNER_DISMISSED: (id: string) => `maintenance-banner-dismissed-${id}`, DASHBOARD_PREFERENCES: (ref: string) => `dashboard-preferences-${ref}`, UNIFIED_LOGS_DOCK: 'unified-logs-dock', @@ -20,7 +18,6 @@ export const LOCAL_STORAGE_KEYS = { UI_PREVIEW_CLS: 'supabase-ui-cls', UI_PREVIEW_INLINE_EDITOR: 'supabase-ui-preview-inline-editor', UI_PREVIEW_UNIFIED_LOGS: 'supabase-ui-preview-unified-logs', - UI_ONBOARDING_NEW_PAGE_SHOWN: 'supabase-ui-onboarding-new-page-shown', UI_PREVIEW_ADVISOR_RULES: 'supabase-ui-advisor-rules', UI_PREVIEW_QUEUE_OPERATIONS: 'supabase-ui-queue-operations', UI_PREVIEW_PG_DELTA_DIFF: 'supabase-ui-pg-delta-diff', @@ -29,8 +26,6 @@ export const LOCAL_STORAGE_KEYS = { UI_PREVIEW_RLS_TESTER: 'supabase-ui-rls-tester', UI_PREVIEW_MARKETPLACE: 'supabase-ui-marketplace', - NEW_LAYOUT_NOTICE_ACKNOWLEDGED: 'new-layout-notice-acknowledge', - TABS_INTERFACE_ACKNOWLEDGED: 'tabs-interface-acknowledge', AI_ASSISTANT_MCP_OPT_IN: 'ai-assistant-mcp-opt-in', DASHBOARD_HISTORY: (ref: string) => `dashboard-history-${ref}`, @@ -53,7 +48,6 @@ export const LOCAL_STORAGE_KEYS = { SQL_EDITOR_SORT: (ref: string) => `sql-editor-sort-${ref}`, LOG_EXPLORER_SPLIT_SIZE: 'supabase_log-explorer-split-size', - GRAPHIQL_RLS_BYPASS_WARNING: 'graphiql-rls-bypass-warning-dismissed', GRAPHQL_INTROSPECTION_NOTICE_COLLAPSED: (ref: string) => `graphql-introspection-notice-collapsed-${ref}`, CLS_DIFF_WARNING: 'cls-diff-warning-dismissed', @@ -78,6 +72,7 @@ export const LOCAL_STORAGE_KEYS = { FLY_DEPRECATION_2026_05_31: 'fly-deprecation-2026-05-31-dismissed', API_KEYS_FEEDBACK_DISMISSED: (ref: string) => `supabase-api-keys-feedback-dismissed-${ref}`, TERMS_OF_SERVICE_UPDATE: 'terms-of-service-update-2026-06-06', + SUPAVISOR_MAINTENANCE: (ref: string) => `supavisor-maintenance-2026-05-21-${ref}`, REPORT_DATERANGE: 'supabase-report-daterange', PROJECT_PAUSING_STARTED_AT: (ref: string) => `supabase-project-pausing-started-at-${ref}`, PROJECT_RESTORING_STARTED_AT: (ref: string) => `supabase-project-restoring-started-at-${ref}`, @@ -119,7 +114,6 @@ export const LOCAL_STORAGE_KEYS = { `free-micro-upgrade-banner-dismissed-${ref}`, STORAGE_PUBLIC_BUCKET_SELECT_POLICY_WARNING_DISMISSED: (ref: string, bucketId: string) => `storage-public-bucket-select-policy-warning-dismissed-${ref}-${bucketId}`, - PRIVACY_NOTICE_ACKNOWLEDGED: 'privacy-notice-acknowledged-2026-03', /** * COMMON