Merge pull request #15666 from supabase/chore/adjust-copy-org-billing

chore: adjust copy around org level billing
This commit is contained in:
Kevin Grüneberg authored and GitHub committed 2023-07-11 10:49:40 +02:00
commit d6098090fa
11 files changed
+180 -70

No files matched your search

+20
View File
@@ -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',
@@ -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>
@@ -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>
)}
@@ -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`
@@ -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>
@@ -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
View File
@@ -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 && (