mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Merge pull request #15666 from supabase/chore/adjust-copy-org-billing
chore: adjust copy around org level billing
This commit is contained in:
11 files changed
+180
-70
No files matched your search
@@ -3,6 +3,7 @@ export interface PricingInformation {
|
||||
name: string
|
||||
nameBadge?: string
|
||||
costUnit?: string
|
||||
costUnitOrg?: string
|
||||
href: string
|
||||
priceLabel?: string
|
||||
priceMonthly: number | string
|
||||
@@ -10,6 +11,7 @@ export interface PricingInformation {
|
||||
description: string
|
||||
preface: string
|
||||
features: string[]
|
||||
featuresOrg?: string[]
|
||||
footer?: string
|
||||
cta: string
|
||||
}
|
||||
@@ -20,6 +22,7 @@ export const plans: PricingInformation[] = [
|
||||
name: 'Free',
|
||||
nameBadge: '',
|
||||
costUnit: '/ month / project',
|
||||
costUnitOrg: '/ month',
|
||||
href: 'https://supabase.com/dashboard/new/new-project',
|
||||
priceLabel: '',
|
||||
priceMonthly: 0,
|
||||
@@ -48,11 +51,27 @@ export const plans: PricingInformation[] = [
|
||||
name: 'Pro',
|
||||
nameBadge: '',
|
||||
costUnit: '/ month / project',
|
||||
costUnitOrg: '/ month',
|
||||
href: 'https://supabase.com/dashboard/new/new-project',
|
||||
priceLabel: 'From',
|
||||
warning: 'Usage-based plan',
|
||||
priceMonthly: 25,
|
||||
description: 'For production applications with the option to scale.',
|
||||
featuresOrg: [
|
||||
'$10 Compute Credits',
|
||||
'No project pausing',
|
||||
'Daily backups stored for 7 days',
|
||||
'8GB database space included',
|
||||
'100GB file storage included',
|
||||
'50GB bandwidth included',
|
||||
'5GB file uploads included',
|
||||
'100,000 monthly active users included',
|
||||
'2M Edge Function invocations included',
|
||||
'500 concurrent Realtime connections included',
|
||||
'5 million Realtime messages included',
|
||||
'7-day log retention',
|
||||
'Email support',
|
||||
],
|
||||
features: [
|
||||
'No project pausing',
|
||||
'Daily backups stored for 7 days',
|
||||
@@ -77,6 +96,7 @@ export const plans: PricingInformation[] = [
|
||||
name: 'Team',
|
||||
nameBadge: 'New',
|
||||
costUnit: '/ month',
|
||||
costUnitOrg: '/ month',
|
||||
href: 'https://forms.supabase.com/team',
|
||||
priceLabel: 'From',
|
||||
warning: 'Usage-based plan',
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ const BillingAddress = () => {
|
||||
<ScaffoldSectionDetail>
|
||||
<div className="sticky top-16">
|
||||
<p className="text-base">Billing Address</p>
|
||||
<p className="text-sm text-scale-1000">This will be reflected in every invoice</p>
|
||||
<p className="text-sm text-scale-1000">This will be reflected in every upcoming invoice, past invoices are not affected</p>
|
||||
</div>
|
||||
</ScaffoldSectionDetail>
|
||||
<ScaffoldSectionContent>
|
||||
|
||||
+6
-6
@@ -75,15 +75,15 @@ const BillingBreakdown = () => {
|
||||
<p className="text-sm">Included usage summary</p>
|
||||
{isUsageBillingEnabled ? (
|
||||
<p className="text-sm text-scale-1000">
|
||||
Your plan includes a limited amount of usage. If the usage on your project exceeds
|
||||
these quotas, your subscription will be charged for the overages. It may take up to
|
||||
24 hours for usage stats to update.
|
||||
Your plan includes a limited amount of usage. If the usage on your organization
|
||||
exceeds these quotas, your subscription will be charged for the overages. It may
|
||||
take up to 24 hours for usage stats to update.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-sm text-scale-1000">
|
||||
Your plan includes a limited amount of usage. If the usage on your project exceeds
|
||||
these quotas, you may experience restrictions, as you are currently not billed for
|
||||
overages. It may take up to 24 hours for usage stats to update.
|
||||
Your plan includes a limited amount of usage. If the usage on your organization
|
||||
exceeds these quotas, you may experience restrictions, as you are currently not
|
||||
billed for overages. It may take up to 24 hours for usage stats to update.
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ const BillingMetric = ({ idx, slug, metric, usage, subscription }: BillingMetric
|
||||
const isExceededLimit = hasLimit && usageRatio >= 1
|
||||
|
||||
const usageCurrentLabel =
|
||||
metric.units === 'bytes' ? `${usageMeta?.usage ?? 0} GB` : usageMeta?.usage?.toLocaleString()
|
||||
metric.units === 'bytes' ? `${usageMeta?.usage?.toLocaleString() ?? 0} GB` : usageMeta?.usage?.toLocaleString()
|
||||
const usageLimitLabel =
|
||||
metric.units === 'bytes'
|
||||
? `${usageMeta?.pricing_free_units ?? 0} GB`
|
||||
|
||||
+3
-4
@@ -57,10 +57,9 @@ const PaymentMethods = () => {
|
||||
<div className="sticky space-y-6 top-16">
|
||||
<div>
|
||||
<p className="text-base">Payment methods</p>
|
||||
<p className="text-sm text-scale-1000 mb-2">
|
||||
This copy might need to change, when adding a new payment method, either remove the
|
||||
old one or go to your projects' subscription to explicitly update the payment
|
||||
method.
|
||||
<p className="text-sm text-scale-1000 mb-2 pr-4">
|
||||
After adding a payment method, make sure to mark it as active to use it for billing.
|
||||
You can remove unused cards.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -183,7 +183,7 @@ const PlanUpdateSidePanel = () => {
|
||||
) : (
|
||||
<p className="text-scale-1200 text-lg">${price}</p>
|
||||
)}
|
||||
<p className="text-scale-1000 text-sm">{tierMeta?.costUnit}</p>
|
||||
<p className="text-scale-1000 text-sm">{tierMeta?.costUnitOrg}</p>
|
||||
</div>
|
||||
<div className={clsx('flex mt-1 mb-4', !tierMeta?.warning && 'opacity-0')}>
|
||||
<div className="bg-scale-200 text-brand-1100 border shadow-sm rounded-md bg-opacity-30 py-0.5 px-2 text-xs">
|
||||
@@ -252,7 +252,7 @@ const PlanUpdateSidePanel = () => {
|
||||
<div className="border-t my-6" />
|
||||
|
||||
<ul role="list">
|
||||
{plan.features.map((feature) => (
|
||||
{(plan.featuresOrg || plan.features).map((feature) => (
|
||||
<li key={feature} className="flex py-2">
|
||||
<div className="w-[12px]">
|
||||
<IconCheck
|
||||
|
||||
@@ -128,8 +128,9 @@ const TaxID = () => {
|
||||
<div className="sticky space-y-6 top-16">
|
||||
<div>
|
||||
<p className="text-base">Tax ID</p>
|
||||
<p className="text-sm text-scale-1000">
|
||||
If you would like to include specific tax ID(s) to your invoices.
|
||||
<p className="text-sm text-scale-1000 pr-4">
|
||||
If you would like to include specific tax ID(s) to your invoices. Old invoices are not
|
||||
affected.
|
||||
</p>
|
||||
<p className="text-sm text-scale-1000">
|
||||
Make sure the tax ID looks exactly like the placeholder text.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { Button, IconEdit2, IconInfo, Input, Listbox } from 'ui'
|
||||
import { Button, IconEdit2, IconExternalLink, IconInfo, Input, Listbox } from 'ui'
|
||||
|
||||
import { PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js'
|
||||
import type { PaymentMethod } from '@stripe/stripe-js'
|
||||
@@ -12,6 +12,7 @@ import { useStore } from 'hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL, BASE_PATH, PRICING_TIER_LABELS_ORG } from 'lib/constants'
|
||||
import { getURL } from 'lib/helpers'
|
||||
import Link from 'next/link'
|
||||
|
||||
const ORG_KIND_TYPES = {
|
||||
PERSONAL: 'Personal',
|
||||
@@ -185,25 +186,6 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Panel.Content>
|
||||
<InformationBox
|
||||
icon={<IconInfo size="large" strokeWidth={1.5} />}
|
||||
defaultVisibility={true}
|
||||
hideCollapse
|
||||
title="Billed via organization"
|
||||
description={
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm leading-normal">
|
||||
This is heavy Work-In-Progress and not customer facing yet, use with caution!
|
||||
This organization will use the new org level billing, instead of having
|
||||
individual subscriptions per project. There are still a lot of open ends that
|
||||
may be restrictive for you, follow #team-billing for updates.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Panel.Content>
|
||||
|
||||
<Panel.Content className="pt-0">
|
||||
<p className="text-sm">This is your organization within Supabase.</p>
|
||||
<p className="text-sm text-scale-1100">
|
||||
@@ -331,6 +313,33 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => {
|
||||
)}
|
||||
</Panel.Content>
|
||||
)}
|
||||
|
||||
<Panel.Content>
|
||||
<InformationBox
|
||||
icon={<IconInfo size="large" strokeWidth={1.5} />}
|
||||
defaultVisibility={true}
|
||||
hideCollapse
|
||||
title="Billed via organization"
|
||||
description={
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm leading-normal">
|
||||
This organization will use the new organization level billing, which gives you a
|
||||
single subscription for your entire organization, instead of having individual
|
||||
subscriptions per project.{' '}
|
||||
</p>
|
||||
<div>
|
||||
<Link href="https://www.notion.so/supabase/Organization-Level-Billing-9c159d69375b4af095f0b67881276582?pvs=4">
|
||||
<a target="_blank" rel="noreferrer">
|
||||
<Button type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
Documentation
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
</form>
|
||||
</>
|
||||
|
||||
@@ -190,7 +190,7 @@ const Addons = () => {
|
||||
) : null}
|
||||
|
||||
<div className="mt-2 w-full flex items-center justify-between border-b py-2">
|
||||
<Link href={`/project/${projectRef}/settings/billing/usage#ram`}>
|
||||
<Link href={`/project/${projectRef}/settings/infrastructure#ram`}>
|
||||
<a>
|
||||
<div className="group flex items-center space-x-2">
|
||||
<p className="text-sm text-scale-1100 group-hover:text-scale-1200 transition cursor-pointer">
|
||||
@@ -207,7 +207,7 @@ const Addons = () => {
|
||||
<p className="text-sm">{computeInstance?.variant?.meta?.memory_gb ?? 1} GB</p>
|
||||
</div>
|
||||
<div className="w-full flex items-center justify-between border-b py-2">
|
||||
<Link href={`/project/${projectRef}/settings/billing/usage#cpu`}>
|
||||
<Link href={`/project/${projectRef}/settings/infrastructure#cpu`}>
|
||||
<a>
|
||||
<div className="group flex items-center space-x-2">
|
||||
<p className="text-sm text-scale-1100 group-hover:text-scale-1200 transition cursor-pointer">
|
||||
@@ -239,7 +239,7 @@ const Addons = () => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex items-center justify-between border-b py-2">
|
||||
<Link href={`/project/${projectRef}/settings/billing/usage#disk_io`}>
|
||||
<Link href={`/project/${projectRef}/settings/infrastructure#disk_io`}>
|
||||
<a>
|
||||
<div className="group flex items-center space-x-2">
|
||||
<p className="text-sm text-scale-1100 group-hover:text-scale-1200 transition cursor-pointer">
|
||||
@@ -260,7 +260,7 @@ const Addons = () => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex items-center justify-between py-2">
|
||||
<Link href={`/project/${projectRef}/settings/billing/usage#disk_io`}>
|
||||
<Link href={`/project/${projectRef}/settings/infrastructure#disk_io`}>
|
||||
<a>
|
||||
<div className="group flex items-center space-x-2">
|
||||
<p className="text-sm text-scale-1100 group-hover:text-scale-1200 transition cursor-pointer">
|
||||
|
||||
@@ -16,7 +16,9 @@ import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
|
||||
import { BASE_PATH, PROJECT_STATUS } from 'lib/constants'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useSubscriptionPageStateSnapshot } from 'state/subscription-page'
|
||||
import { Alert, Button, IconExternalLink, Modal, Radio, SidePanel } from 'ui'
|
||||
import { Alert, Button, IconExternalLink, IconInfo, Modal, Radio, SidePanel } from 'ui'
|
||||
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
|
||||
const COMPUTE_CATEGORY_OPTIONS: {
|
||||
id: 'micro' | 'optimized'
|
||||
@@ -287,14 +289,47 @@ const ComputeInstanceSidePanel = () => {
|
||||
{option.meta?.cpu_cores ?? 0}-core ARM CPU (
|
||||
{option.meta?.cpu_dedicated ? 'Dedicated' : 'Shared'})
|
||||
</p>
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<p className="text-scale-1200 text-sm">
|
||||
${option.price.toLocaleString()}
|
||||
</p>
|
||||
<p className="text-scale-1000 translate-y-[1px]">
|
||||
{' '}
|
||||
/ {option.price_interval === 'monthly' ? 'month' : 'hour'}
|
||||
</p>
|
||||
<div className="flex justify-between items-center mt-2">
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-scale-1200 text-sm">
|
||||
${option.price.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-scale-1000 translate-y-[1px]">
|
||||
{' '}
|
||||
/ {option.price_interval === 'monthly' ? 'month' : 'hour'}
|
||||
</span>
|
||||
</div>
|
||||
{option.price_interval === 'hourly' && (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<div className="flex items-center">
|
||||
<IconInfo
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
className="hover:text-scale-1000"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex items-center space-x-1">
|
||||
<p className="text-scale-1200 text-sm">
|
||||
${Number(option.price * 672).toFixed(0)} - $
|
||||
{Number(option.price * 744).toFixed(0)} per month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -312,12 +347,13 @@ const ComputeInstanceSidePanel = () => {
|
||||
)}
|
||||
|
||||
{hasChanges &&
|
||||
(selectedCategory === 'micro' ? (
|
||||
(selectedCategory === 'micro' && !isOrgBilling ? (
|
||||
<p className="text-sm text-scale-1100">
|
||||
Upon clicking confirm, the amount of that's unused during the current billing
|
||||
cycle will be returned as credits that can be used for subsequent billing cycles
|
||||
</p>
|
||||
) : (
|
||||
) : selectedCategory !== 'micro' && selectedCompute?.price_interval === 'monthly' ? (
|
||||
// Monthly payment with project-level subscription
|
||||
<p className="text-sm text-scale-1100">
|
||||
Upon clicking confirm, the amount of{' '}
|
||||
<span className="text-scale-1200">
|
||||
@@ -328,6 +364,21 @@ const ComputeInstanceSidePanel = () => {
|
||||
is prepaid per month and in case of a downgrade, you get credits for the remaining
|
||||
time.
|
||||
</p>
|
||||
) : selectedCategory !== 'micro' ? (
|
||||
// Hourly usage-billing with org-level subscription
|
||||
<p className="text-sm text-scale-1100">
|
||||
There are no immediate charges when changing compute. Compute Hours are a
|
||||
usage-based item and you're billed at the end of your billing cycle based on your
|
||||
compute usage. Read more about{' '}
|
||||
<Link href="https://www.notion.so/supabase/Organization-Level-Billing-9c159d69375b4af095f0b67881276582?pvs=4">
|
||||
<a target="_blank" rel="noreferrer" className="underline">
|
||||
Compute Billing
|
||||
</a>
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
) : (
|
||||
<></>
|
||||
))}
|
||||
|
||||
{hasChanges && !blockMicroDowngradeDueToPitr && (
|
||||
|
||||
+49
-19
@@ -38,7 +38,18 @@ import {
|
||||
} from 'lib/constants'
|
||||
import { passwordStrength, pluckObjectFields } from 'lib/helpers'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { Alert, Button, IconHelpCircle, IconInfo, IconUsers, Input, Listbox, Toggle } from 'ui'
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
IconExternalLink,
|
||||
IconHelpCircle,
|
||||
IconInfo,
|
||||
IconUsers,
|
||||
Input,
|
||||
Listbox,
|
||||
Toggle,
|
||||
} from 'ui'
|
||||
import Link from 'next/link'
|
||||
|
||||
const Wizard: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
@@ -320,24 +331,6 @@ const Wizard: NextPageWithLayout = () => {
|
||||
</Listbox>
|
||||
)}
|
||||
|
||||
{billedViaOrg && (
|
||||
<InformationBox
|
||||
icon={<IconInfo size="large" strokeWidth={1.5} />}
|
||||
defaultVisibility={true}
|
||||
hideCollapse
|
||||
title="Billed via organization"
|
||||
description={
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm leading-normal">
|
||||
This is heavy Work-In-Progress and not customer facing yet, use with
|
||||
caution! This organization uses the new org level billing, instead of having
|
||||
individual subscriptions per project.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isAdmin && <NotOrganizationOwnerWarning />}
|
||||
</Panel.Content>
|
||||
|
||||
@@ -473,6 +466,43 @@ const Wizard: NextPageWithLayout = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{billedViaOrg && (
|
||||
<Panel.Content>
|
||||
<InformationBox
|
||||
icon={<IconInfo size="large" strokeWidth={1.5} />}
|
||||
defaultVisibility={true}
|
||||
hideCollapse
|
||||
title="Billed via organization"
|
||||
description={
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm leading-normal">
|
||||
This organization uses organization level billing and is on the{' '}
|
||||
<span className="text-brand-900">{orgSubscription?.plan?.name} plan</span>.
|
||||
</p>
|
||||
|
||||
{orgSubscription?.plan?.id !== 'free' && (
|
||||
<p>
|
||||
Your plan comes with $10 of Compute Credits. Launching another project
|
||||
incurs additional compute costs - if you exhaust your Compute Credits, the
|
||||
additional compute hours result in additional usage charges.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Link href="https://www.notion.so/supabase/Organization-Level-Billing-9c159d69375b4af095f0b67881276582?pvs=4">
|
||||
<a target="_blank" rel="noreferrer">
|
||||
<Button type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
Documentation
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Panel.Content>
|
||||
)}
|
||||
|
||||
{isAdmin && (
|
||||
<Panel.Content>
|
||||
{!billedViaOrg && (
|
||||
|
||||
Reference in new issue
Block a user