import { PermissionAction } from '@supabase/shared-types/out/constants' import { useTheme } from 'next-themes' import Image from 'next/image' import Link from 'next/link' import { useEffect, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' import Table from 'components/to-be-cleaned/Table' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { BASE_PATH, DOCS_URL, PRICING_TIER_PRODUCT_IDS } from 'lib/constants' import { ChevronRight, ExternalLink } from 'lucide-react' import { pricing } from 'shared-data/pricing' import { useOrgSettingsPageStateSnapshot } from 'state/organization-settings' import { Alert, Button, Collapsible, SidePanel, cn } from 'ui' const SPEND_CAP_OPTIONS: { name: string value: 'on' | 'off' imageUrl: string imageUrlLight: string }[] = [ { name: 'Spend cap enabled', value: 'on', imageUrl: `${BASE_PATH}/img/spend-cap-on.png`, imageUrlLight: `${BASE_PATH}/img/spend-cap-on--light.png`, }, { name: 'Spend cap disabled', value: 'off', imageUrl: `${BASE_PATH}/img/spend-cap-off.png`, imageUrlLight: `${BASE_PATH}/img/spend-cap-off--light.png`, }, ] const SpendCapSidePanel = () => { const { slug } = useParams() const { resolvedTheme } = useTheme() const [showUsageCosts, setShowUsageCosts] = useState(false) const [selectedOption, setSelectedOption] = useState<'on' | 'off'>() const { can: canUpdateSpendCap } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) const snap = useOrgSettingsPageStateSnapshot() const visible = snap.panelKey === 'costControl' const onClose = () => snap.setPanelKey(undefined) const { data: subscription, isLoading } = useOrgSubscriptionQuery({ orgSlug: slug }) const { mutate: updateOrgSubscription, isLoading: isUpdating } = useOrgSubscriptionUpdateMutation( { onSuccess: () => { toast.success(`Successfully ${isTurningOnCap ? 'enabled' : 'disabled'} spend cap`) onClose() }, onError: (error) => { toast.error(`Failed to toggle spend cap: ${error.message}`) }, } ) const isFreePlan = subscription?.plan?.id === 'free' const isSpendCapOn = !subscription?.usage_billing_enabled const isTurningOnCap = !isSpendCapOn && selectedOption === 'on' const hasChanges = selectedOption !== (isSpendCapOn ? 'on' : 'off') useEffect(() => { if (visible && subscription !== undefined) { setSelectedOption(isSpendCapOn ? 'on' : 'off') } // eslint-disable-next-line react-hooks/exhaustive-deps }, [visible, isLoading, subscription, isSpendCapOn]) const onConfirm = async () => { if (!slug) return console.error('Org slug is required') const tier = ( selectedOption === 'on' ? PRICING_TIER_PRODUCT_IDS.PRO : PRICING_TIER_PRODUCT_IDS.PAYG ) as 'tier_pro' | 'tier_payg' updateOrgSubscription({ slug, tier }) } const billingMetricCategories: (keyof typeof pricing)[] = [ 'database', 'auth', 'storage', 'realtime', 'edge_functions', ] return (

Spend cap

} tooltip={!canUpdateSpendCap ? 'You do not have permission to update spend cap' : undefined} >

Use the spend cap to manage project usage and costs, and control whether the project can exceed the included quota allowance of any billed line item in a billing cycle

How are each resource charged after exceeding the included quota?

Item

Rate

} body={billingMetricCategories.map((categoryId) => { const category = pricing[categoryId] const usageItems = category.features.filter((it: any) => it.usage_based) return ( <>

{category.title}

{null}
{usageItems.map((item: any) => { return (

{item.title}

{item.plans['pro']}

) })} ) })} /> {isFreePlan && ( snap.setPanelKey('subscriptionPlan')}> View available plans } > Upgrade your plan to disable the spend cap )}
{SPEND_CAP_OPTIONS.map((option) => { const isSelected = selectedOption === option.value return (
!isFreePlan && setSelectedOption(option.value)} >

{option.name}

) })}
{selectedOption === 'on' ? ( Exceeding the included quota allowance with spend cap enabled can cause your projects to become unresponsive or enter read only mode. ) : ( Your projects will always remain responsive and active, and charges only apply when exceeding the included quota limit. )} {hasChanges && ( <>

{selectedOption === 'on' ? 'Upon clicking confirm, spend cap will be enabled for your organization and you will no longer be charged any extra for usage.' : 'Upon clicking confirm, spend cap will be disabled for your organization and you will be charged for any usage beyond the included quota.'}

Toggling spend cap triggers an invoice and there might be prorated charges for any usage beyond the Pro Plans quota during this billing cycle.

)} ) } export default SpendCapSidePanel