From ec02a89b69cc964772cc76c4e8362548545fca72 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:29:48 +1100 Subject: [PATCH] polish aws marketplace purchase surfaces Move the Studio callout into the plan cards grid, quiet Billing to a More information link, and tighten the www ambient mask. --- .../AwsMarketplacePurchaseCallout.tsx | 48 +++++++++---------- .../Subscription/CancellationFlow.tsx | 2 +- .../Subscription/PlanCards.tsx | 10 +++- .../Subscription/PlanUpdatePanelShell.tsx | 6 +-- .../Subscription/PlanUpdateSidePanel.test.tsx | 14 +++--- .../Subscription/PlanUpdateSidePanel.tsx | 8 +--- .../Subscription/Subscription.test.tsx | 18 +++++-- .../Subscription/Subscription.tsx | 28 +++++++++-- .../components/Partners/BackgroundPattern.tsx | 2 +- packages/common/telemetry-constants.ts | 2 +- packages/shared-data/plans.ts | 2 +- 11 files changed, 85 insertions(+), 55 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/AwsMarketplacePurchaseCallout.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/AwsMarketplacePurchaseCallout.tsx index a01278ebbf9..9967ada127f 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/AwsMarketplacePurchaseCallout.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/AwsMarketplacePurchaseCallout.tsx @@ -1,34 +1,34 @@ import { ExternalLink } from 'lucide-react' import { Button } from 'ui' +import { Admonition } from 'ui-patterns/Admonition' -import { BASE_PATH } from '@/lib/constants' +import PartnerIcon from '@/components/ui/PartnerIcon' +import { MANAGED_BY } from '@/lib/constants/infrastructure' const AWS_MARKETPLACE_URL = 'https://supabase.com/aws-marketplace' export function AwsMarketplacePurchaseCallout() { return ( -
-
-
- -
-
-

Purchase through AWS Marketplace

-

- Apply Supabase spend toward an AWS commitment and manage billing through AWS. -

-
-
- -
+ + } + title="Prefer purchasing through AWS Marketplace?" + description="Apply Supabase spend toward an AWS commitment and manage billing through AWS." + actions={ + + } + /> ) } diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/CancellationFlow.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/CancellationFlow.tsx index c1b4288de28..10cc79b0e77 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/CancellationFlow.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/CancellationFlow.tsx @@ -160,7 +160,7 @@ export const InitiateCancellationFlowButton = (props: InitiateCancellationFlowBu setVisible(true) }} > - {props.children ?? 'Cancel Subscription'} + {props.children ?? 'Cancel subscription'} void } @@ -37,6 +39,7 @@ export function PlanCards({ selectedOrganization, variant, entryDelay, + showAwsMarketplacePurchase = false, onSelectTier, }: PlanCardsProps) { const router = useRouter() @@ -45,7 +48,7 @@ export function PlanCards({ const entryAnimation = getPlanCardsEntryAnimation(entryDelay) return ( - + {subscriptionsPlans.map((plan) => { const planMeta = availablePlans.find((p) => p.id === plan.id.split('tier_')[1]) const price = planMeta?.price ?? 0 @@ -95,6 +98,11 @@ export function PlanCards({ /> ) })} + {showAwsMarketplacePurchase && ( + + + + )} ) } diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdatePanelShell.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdatePanelShell.tsx index eef5b0ee34c..32fc245b90d 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdatePanelShell.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdatePanelShell.tsx @@ -62,7 +62,7 @@ export function PlanUpdateFullScreenShell({ onClick={onClose} className="fixed top-4 left-4 z-10" > - Go back to Studio + Back
-

Change subscription plan for {organizationName}

+

Change subscription plan for {organizationName}

{notice} {children}
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.test.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.test.tsx index 06eb154eb34..61c40181376 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.test.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.test.tsx @@ -279,7 +279,7 @@ describe('PlanUpdateSidePanel', () => { it('renders the full-screen shell instead of the sheet', () => { render() - expect(screen.getByRole('button', { name: /Go back to Studio/ })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /Back/ })).toBeInTheDocument() expect(screen.getByRole('link', { name: /Compare plans/ })).toBeInTheDocument() expect(screen.queryByRole('link', { name: 'Pricing' })).not.toBeInTheDocument() }) @@ -296,8 +296,8 @@ describe('PlanUpdateSidePanel', () => { it('offers AWS Marketplace as a separate purchasing route', () => { render() - expect(screen.getByText('Purchase through AWS Marketplace')).toBeInTheDocument() - expect(screen.getByRole('link', { name: 'View purchase options' })).toHaveAttribute( + expect(screen.getByText('Prefer purchasing through AWS Marketplace?')).toBeInTheDocument() + expect(screen.getByRole('link', { name: 'View options' })).toHaveAttribute( 'href', 'https://supabase.com/aws-marketplace' ) @@ -331,7 +331,7 @@ describe('PlanUpdateSidePanel', () => { it('renders gap rows inside the full-screen shell', () => { render() - expect(screen.getByRole('button', { name: /Go back to Studio/ })).toBeInTheDocument() + expect(screen.getByRole('button', { name: /Back/ })).toBeInTheDocument() expect(screen.getByText('Daily backups')).toBeInTheDocument() expect(screen.getByText('1-day log retention')).toBeInTheDocument() // Free has a `lesser` item → "Plan limits"; Pro has only `missing` items → "Not included" @@ -352,7 +352,7 @@ describe('PlanUpdateSidePanel', () => { it('opens neither shell, so the fullscreen variant cannot flash the sheet', () => { render() - expect(screen.queryByRole('button', { name: /Go back to Studio/ })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: /Back/ })).not.toBeInTheDocument() expect(screen.queryByRole('button', { name: 'Upgrade to Pro' })).not.toBeInTheDocument() }) @@ -377,8 +377,8 @@ describe('PlanUpdateSidePanel', () => { render() expect(screen.getByRole('link', { name: 'Pricing' })).toBeInTheDocument() - expect(screen.queryByRole('button', { name: /Go back to Studio/ })).not.toBeInTheDocument() - expect(screen.getByText('Purchase through AWS Marketplace')).toBeInTheDocument() + expect(screen.queryByRole('button', { name: /Back/ })).not.toBeInTheDocument() + expect(screen.getByText('Prefer purchasing through AWS Marketplace?')).toBeInTheDocument() }) }) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx index dda572973e2..11fa707629c 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx @@ -6,7 +6,6 @@ import { useRouter } from 'next/router' import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react' import { plans as subscriptionsPlans } from 'shared-data/plans' -import { AwsMarketplacePurchaseCallout } from './AwsMarketplacePurchaseCallout' import { CancellationFlow } from './CancellationFlow' import { isFullScreenPresentation, @@ -268,14 +267,11 @@ export const PlanUpdateSidePanel = () => { selectedOrganization={selectedOrganization} variant={presentation} entryDelay={isFullScreen ? contentDelay : undefined} + showAwsMarketplacePurchase={isPlanPresentationEligible} onSelectTier={setSelectedTier} /> ) - const awsMarketplaceCallout = isPlanPresentationEligible ? ( - - ) : null - return ( <> {isFullScreen && isPanelOpen && ( @@ -286,7 +282,6 @@ export const PlanUpdateSidePanel = () => { contentDelay={contentDelay} onClose={onClose} > - {awsMarketplaceCallout} {planCards} )} @@ -298,7 +293,6 @@ export const PlanUpdateSidePanel = () => { notice={notice} onClose={onClose} > - {awsMarketplaceCallout} {planCards} )} diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.test.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.test.tsx index 7a29ec41f0c..94a5fdc92b6 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.test.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.test.tsx @@ -5,9 +5,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import Subscription from './Subscription' import { render } from '@/tests/helpers' -const { mockSubscription, mockSetPanelKey } = vi.hoisted(() => ({ +const { mockSubscription, mockSetPanelKey, mockSelectedOrganization } = vi.hoisted(() => ({ mockSubscription: vi.fn(), mockSetPanelKey: vi.fn(), + mockSelectedOrganization: vi.fn(), })) vi.mock('common', async (importOriginal) => { @@ -29,7 +30,7 @@ vi.mock('@/hooks/misc/useCheckPermissions', () => ({ vi.mock('@/hooks/misc/useSelectedOrganization', () => ({ useSelectedOrganizationQuery: () => ({ - data: { managed_by: 'supabase' }, + data: mockSelectedOrganization(), }), })) @@ -53,6 +54,7 @@ vi.mock('./PlanUpdateSidePanel', () => ({ describe('Subscription', () => { beforeEach(() => { + mockSelectedOrganization.mockReturnValue({ managed_by: 'supabase' }) mockSubscription.mockReturnValue({ data: { plan: { id: 'free', name: 'Free' }, @@ -76,11 +78,15 @@ describe('Subscription', () => { expect(mockSetPanelKey).toHaveBeenCalledWith('subscriptionPlan') }) - it('shows AWS Marketplace as an alternative purchasing route', () => { + it('links to Compare plans and AWS Marketplace under More information', () => { render() - expect(screen.getByText('Purchase through AWS Marketplace')).toBeInTheDocument() - expect(screen.getByRole('link', { name: 'View purchase options' })).toHaveAttribute( + expect(screen.getByText('More information')).toBeInTheDocument() + expect(screen.getByRole('link', { name: /Compare plans/ })).toHaveAttribute( + 'href', + 'https://supabase.com/pricing#compare-plans' + ) + expect(screen.getByRole('link', { name: /AWS Marketplace/ })).toHaveAttribute( 'href', 'https://supabase.com/aws-marketplace' ) @@ -104,5 +110,7 @@ describe('Subscription', () => { screen.queryByRole('button', { name: 'Change subscription plan' }) ).not.toBeInTheDocument() expect(screen.getByText('Unable to update plan from Enterprise')).toBeInTheDocument() + expect(screen.getByRole('link', { name: /Compare plans/ })).toBeInTheDocument() + expect(screen.queryByRole('link', { name: /AWS Marketplace/ })).not.toBeInTheDocument() }) }) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx index 5c2a008396e..8cd831184d2 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx @@ -1,12 +1,12 @@ import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants' import { useFlag, useParams } from 'common' +import { ExternalLink } from 'lucide-react' import Link from 'next/link' import { Button } from 'ui' import { Admonition } from 'ui-patterns/Admonition' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { Restriction } from '../Restriction' -import { AwsMarketplacePurchaseCallout } from './AwsMarketplacePurchaseCallout' import { InitiateCancellationFlowButton } from './CancellationFlow' import { PlanUpdateSidePanel } from './PlanUpdateSidePanel' import { SupportLink } from '@/components/interfaces/Support/SupportLink' @@ -23,6 +23,9 @@ import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganizati import { MANAGED_BY } from '@/lib/constants/infrastructure' import { useOrgSettingsPageStateSnapshot } from '@/state/organization-settings' +const COMPARE_PLANS_URL = 'https://supabase.com/pricing#compare-plans' +const AWS_MARKETPLACE_URL = 'https://supabase.com/aws-marketplace' + const Subscription = () => { const { slug } = useParams() const snap = useOrgSettingsPageStateSnapshot() @@ -66,6 +69,25 @@ const Subscription = () => { usage quotas.

+
+

More information

+
+ +
+

Compare plans

+ +
+ + {canPurchaseThroughAws && ( + +
+

AWS Marketplace

+ +
+ + )} +
+
@@ -100,15 +122,13 @@ const Subscription = () => { {currentPlan && currentPlan.id !== 'free' && ( - Cancel Subscription + Cancel subscription )} )} - {canPurchaseThroughAws && } - {!canChangeTier && (
{projectUpdateDisabled ? ( diff --git a/apps/www/components/Partners/BackgroundPattern.tsx b/apps/www/components/Partners/BackgroundPattern.tsx index e5182a25bb5..16138750ed9 100644 --- a/apps/www/components/Partners/BackgroundPattern.tsx +++ b/apps/www/components/Partners/BackgroundPattern.tsx @@ -35,7 +35,7 @@ const BackgroundPattern = ({ className, variant = 'center' }: BackgroundPatternP {variant === 'edges' && (
)} {/* Soft brand-green radial glow from the top */} diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index bf8bed7b580..c03ecdb533b 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -1436,7 +1436,7 @@ export interface SqlEditorQueryRunButtonClickedEvent { } /** - * User clicked on the "Cancel Subscription" Button on the billing settings page. + * User clicked on the "Cancel subscription" Button on the billing settings page. * * @group Events * @source studio diff --git a/packages/shared-data/plans.ts b/packages/shared-data/plans.ts index bc2f7cfd893..742d52526db 100644 --- a/packages/shared-data/plans.ts +++ b/packages/shared-data/plans.ts @@ -107,6 +107,6 @@ export const plans: PricingInformation[] = [ priceLabel: '', priceMonthly: 'Custom', preface: '', - cta: 'Contact Us', + cta: 'Contact us', }, ] as const