diff --git a/apps/studio/components/layouts/ProjectLayout/index.tsx b/apps/studio/components/layouts/ProjectLayout/index.tsx index eff94d559a0..73f3e2b38d1 100644 --- a/apps/studio/components/layouts/ProjectLayout/index.tsx +++ b/apps/studio/components/layouts/ProjectLayout/index.tsx @@ -38,11 +38,16 @@ import { RestoringState } from './RestoringState' import { UpgradingState } from './UpgradingState' import { CreateBranchModal } from '@/components/interfaces/BranchManagement/CreateBranchModal' import { ProjectAPIDocs } from '@/components/interfaces/ProjectAPIDocs/ProjectAPIDocs' +import { BannerFreeMicroUpgrade } from '@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade' +import { useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider' import { ResourceExhaustionWarningBanner } from '@/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner' import { useCustomContent } from '@/hooks/custom-content/useCustomContent' +import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' +import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { withAuth } from '@/hooks/misc/withAuth' +import { LOCAL_STORAGE_KEYS } from 'common' import { PROJECT_STATUS } from '@/lib/constants' import { buildStudioPageTitle } from '@/lib/page-title' import { getPathnameWithoutQuery } from '@/lib/pathname.utils' @@ -110,6 +115,16 @@ export const ProjectLayout = forwardRef { + if (isEligibleForFreeUpgrade && !isFreeMicroUpgradeBannerDismissed) { + addBanner({ + id: 'free-micro-upgrade-banner', + isDismissed: false, + content: , + priority: 2, + }) + } else { + dismissBanner('free-micro-upgrade-banner') + } + }, [isEligibleForFreeUpgrade, isFreeMicroUpgradeBannerDismissed, addBanner, dismissBanner]) + useLayoutEffect(() => { const unregister = registerOpenMenu(() => { setMobileSheetContent( diff --git a/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx b/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx index 86b27f9a729..8ec5aaf054b 100644 --- a/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx +++ b/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx @@ -5,6 +5,7 @@ const BANNER_ID = { INDEX_ADVISOR: 'index-advisor-banner', TABLE_EDITOR_QUEUE_OPERATIONS: 'table-editor-queue-operations-banner', RLS_EVENT_TRIGGER: 'rls-event-trigger-banner', + FREE_MICRO_UPGRADE: 'free-micro-upgrade-banner', } as const export type BannerId = (typeof BANNER_ID)[keyof typeof BANNER_ID] diff --git a/apps/studio/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade.tsx b/apps/studio/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade.tsx new file mode 100644 index 00000000000..5930c4bf4b1 --- /dev/null +++ b/apps/studio/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade.tsx @@ -0,0 +1,92 @@ +import { LOCAL_STORAGE_KEYS } from 'common' +import { useParams } from 'common/hooks' +import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' +import { useTrack } from 'lib/telemetry/track' +import Link from 'next/link' +import { Button } from 'ui' +import { ComputeBadge } from 'ui-patterns/ComputeBadge' + +import { BannerCard } from '../BannerCard' +import { useBannerStack } from '../BannerStackProvider' + +const ChevronsUpAnimated = () => ( + + + + +) + +export const BannerFreeMicroUpgrade = () => { + const { ref } = useParams() + const track = useTrack() + const { dismissBanner } = useBannerStack() + const [, setIsDismissed] = useLocalStorageQuery( + LOCAL_STORAGE_KEYS.FREE_MICRO_UPGRADE_BANNER_DISMISSED(ref ?? ''), + false + ) + + return ( + { + setIsDismissed(true) + dismissBanner('free-micro-upgrade-banner') + track('free_micro_upgrade_banner_dismissed') + }} + > +
+
+
+
+ } + className="text-brand-600 border-brand-500 bg-brand/10 gap-1" + /> + +
+
+
+
+

Free upgrade to Micro available

+

+ Your Pro plan includes a free upgrade from Nano to Micro — double the memory at no extra + cost. +

+
+
+ +
+
+
+ ) +} diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts index 13be858ebf0..235cffb2443 100644 --- a/packages/common/constants/local-storage.ts +++ b/packages/common/constants/local-storage.ts @@ -116,6 +116,8 @@ export const LOCAL_STORAGE_KEYS = { TABLE_EDITOR_QUEUE_OPERATIONS_BANNER_DISMISSED: (ref: string) => `table-editor-queue-operations-banner-dismissed-${ref}`, + FREE_MICRO_UPGRADE_BANNER_DISMISSED: (ref: string) => + `free-micro-upgrade-banner-dismissed-${ref}`, PRIVACY_NOTICE_ACKNOWLEDGED: 'privacy-notice-acknowledged-2026-03', /** diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index b8cb22bbe4b..2b46ced7ad9 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -2946,6 +2946,28 @@ export interface ComputeBadgeUpgradeClickedEvent { groups: TelemetryGroups } +/** + * User dismissed the free Micro upgrade banner. + * + * @group Events + * @source studio + */ +export interface FreeMicroUpgradeBannerDismissedEvent { + action: 'free_micro_upgrade_banner_dismissed' + groups: TelemetryGroups +} + +/** + * User clicked the CTA on the free Micro upgrade banner. + * + * @group Events + * @source studio + */ +export interface FreeMicroUpgradeBannerCtaClickedEvent { + action: 'free_micro_upgrade_banner_cta_clicked' + groups: TelemetryGroups +} + /** * User clicked the Navigate action in the storage explorer header. * @@ -3136,3 +3158,5 @@ export type TelemetryEvent = | OrgMenuBackClickedEvent | OrgMenuItemClickedEvent | ComputeBadgeUpgradeClickedEvent + | FreeMicroUpgradeBannerDismissedEvent + | FreeMicroUpgradeBannerCtaClickedEvent