mirror of
https://github.com/supabase/supabase.git
synced 2026-06-06 05:17:15 +08:00
Joshen/fe 3432 show maintenance banners only for affected project regions (#46191)
## 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 <img width="342" height="235" alt="image" src="https://github.com/user-attachments/assets/0a10fa53-46a0-4c71-beef-d66e006503fd" /> - Updated NoticeBanner to inform users of the upcoming maintenance - Only projects in specific regions will be affected, so not all projects will see this <img width="658" height="75" alt="image" src="https://github.com/user-attachments/assets/83aabda5-a774-4118-a945-08052b7c6b3e" /> <img width="496" height="152" alt="image" src="https://github.com/user-attachments/assets/a1ccc440-5179-4a4b-919f-208844bb2227" /> - 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 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46191?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -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: <BannerTOSUpdate />,
|
||||
priority: 2,
|
||||
})
|
||||
} else {
|
||||
dismissBanner('tos-update-banner')
|
||||
}
|
||||
}, [TOSUpdateAcknowledged, isSuccess, addBanner, dismissBanner])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="shrink-0">
|
||||
|
||||
@@ -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 (
|
||||
<HeaderBanner
|
||||
variant="note"
|
||||
title="We've updated our Terms of Service"
|
||||
description={<UpdatedTermsOfServiceDialog onDismiss={() => setBannerAcknowledged(true)} />}
|
||||
title="Upcoming maintenance"
|
||||
description={
|
||||
<>
|
||||
Shared pooler maintenance in{' '}
|
||||
<a target="_blank" rel="noopener referrer" href={maintenanceWindow.url}>
|
||||
{project.region}
|
||||
</a>{' '}
|
||||
on{' '}
|
||||
<TimestampInfo
|
||||
className="text-sm"
|
||||
utcTimestamp={maintenanceWindow.start}
|
||||
label={dayjs(maintenanceWindow.start).format('DD MMM, HH:mm')}
|
||||
/>
|
||||
.
|
||||
</>
|
||||
}
|
||||
onDismiss={() => setBannerAcknowledged(true)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const UpdatedTermsOfServiceDialog = ({ onDismiss }: { onDismiss: () => void }) => {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger className={cn(InlineLinkClassName, 'cursor-pointer')}>
|
||||
Learn more
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Terms of Service update</DialogTitle>
|
||||
<DialogDescription>
|
||||
We've updated our Terms of Service to better define the responsibilities of both you and
|
||||
Supabase in the use of AI.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogSectionSeparator />
|
||||
|
||||
<DialogSection className="text-sm flex flex-col gap-y-2">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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{' '}
|
||||
<InlineLink href="https://supabase.com/terms">here</InlineLink>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</DialogSection>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button type="default" className="opacity-100" onClick={onDismiss}>
|
||||
Understood
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 (
|
||||
<BannerCard
|
||||
onDismiss={() => {
|
||||
setTOSUpdateAcknowledged(true)
|
||||
dismissBanner('tos-update-banner')
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<Badge variant="default" className="w-min -ml-0.5 uppercase inline-flex items-center mb-2">
|
||||
Notice
|
||||
</Badge>
|
||||
|
||||
<div className="flex flex-col gap-y-1 mb-2">
|
||||
<p className="text-sm font-medium">We've updated our Terms of Service</p>
|
||||
<p className="text-xs text-foreground-lighter text-balance">
|
||||
Updates define the responsibilities of both you and Supabase in the use of AI.
|
||||
</p>
|
||||
</div>
|
||||
<UpdatedTermsOfServiceDialog />
|
||||
</div>
|
||||
</BannerCard>
|
||||
)
|
||||
}
|
||||
|
||||
const UpdatedTermsOfServiceDialog = () => {
|
||||
const [, setTOSUpdateAcknowledged] = useLocalStorageQuery(
|
||||
LOCAL_STORAGE_KEYS.TERMS_OF_SERVICE_UPDATE,
|
||||
false
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="default" className="w-min">
|
||||
Learn more
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Terms of Service update</DialogTitle>
|
||||
<DialogDescription>
|
||||
We've updated our Terms of Service to better define the responsibilities of both you and
|
||||
Supabase in the use of AI.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogSectionSeparator />
|
||||
|
||||
<DialogSection className="text-sm flex flex-col gap-y-2">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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{' '}
|
||||
<InlineLink href="https://supabase.com/terms">here</InlineLink>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</DialogSection>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
type="default"
|
||||
className="opacity-100"
|
||||
onClick={() => setTOSUpdateAcknowledged(true)}
|
||||
>
|
||||
Understood
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user