Files
supabase/apps/studio/components/interfaces/Organization/BillingSettings/Restriction.tsx
Alaister Young 5f533247e1 Update docs url to env var (#38772)
* Update Supabase docs URLs to use env variable

Co-authored-by: a <a@alaisteryoung.com>

* Refactor: Use DOCS_URL constant for documentation links

This change centralizes documentation links using a new DOCS_URL constant, improving maintainability and consistency.

Co-authored-by: a <a@alaisteryoung.com>

* Refactor: Use DOCS_URL constant for all documentation links

This change replaces hardcoded documentation URLs with a centralized constant, improving maintainability and consistency.

Co-authored-by: a <a@alaisteryoung.com>

* replace more instances

* ci: Autofix updates from GitHub workflow

* remaining instances

* fix duplicate useRouter

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: alaister <10985857+alaister@users.noreply.github.com>
2025-09-26 10:16:33 +00:00

230 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import dayjs from 'dayjs'
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { PricingMetric } from 'data/analytics/org-daily-stats-query'
import { VIOLATION_TYPE_LABELS } from 'data/usage/constants'
import { useOrgUsageQuery } from 'data/usage/org-usage-query'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { DOCS_URL } from 'lib/constants'
import { usePathname } from 'next/navigation'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
CriticalIcon,
WarningIcon,
} from 'ui'
export const Restriction = () => {
const { data: org } = useSelectedOrganizationQuery()
const { data: usage, isSuccess: isSuccessOrgUsage } = useOrgUsageQuery({ orgSlug: org?.slug })
const pathname = usePathname()
const isUsagePage = pathname?.endsWith('/usage')
const hasExceededAnyLimits = Boolean(
usage?.usages.find(
(metric) =>
metric.metric !== PricingMetric.DISK_SIZE_GB_HOURS_GP3 &&
!metric.unlimited &&
metric.capped &&
metric.usage > (metric?.pricing_free_units ?? 0)
)
)
// don't show any alerts until everything has been fetched
if (!isSuccessOrgUsage || !org) {
return null
}
let shownAlert: 'exceededLimits' | 'gracePeriod' | 'gracePeriodOver' | 'restricted' | null = null
if (hasExceededAnyLimits && !org?.restriction_status) {
shownAlert = 'exceededLimits'
} else if (org?.restriction_status === 'grace_period') {
shownAlert = 'gracePeriod'
} else if (org?.restriction_status === 'grace_period_over') {
shownAlert = 'gracePeriodOver'
} else if (org?.restriction_status === 'restricted') {
shownAlert = 'restricted'
}
if (shownAlert === null || !org?.restriction_data) {
return null
}
const violationLabels =
Array.isArray(org.restriction_data['violations']) &&
org.restriction_data['violations'].length > 0
? `(${org.restriction_data['violations']
.map((violation: string) => VIOLATION_TYPE_LABELS[violation] || violation)
.join(', ')})`
: ''
return (
<>
{shownAlert === 'exceededLimits' && (
<Alert_Shadcn_ variant="destructive">
<CriticalIcon />
<AlertTitle_Shadcn_>
Your organization's usage has exceeded its included quota
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<p>
Your projects can become unresponsive or enter read-only mode.{' '}
{org.plan.id === 'free'
? 'Please upgrade to the Pro Plan to ensure that your projects remain available.'
: 'Please disable spend cap to ensure that your projects remain available.'}
</p>
<div className="flex items-center gap-x-2 mt-3">
<Button key="upgrade-button" asChild type="default">
<Link
href={`/org/${org?.slug}/billing?panel=${
org.plan.id === 'free' ? 'subscriptionPlan' : 'costControl'
}`}
>
{org.plan.id === 'free' ? 'Upgrade plan' : 'Change spend cap'}
</Link>
</Button>
{!isUsagePage && (
<Button key="view-usage-button" asChild type="default">
<Link href={`/org/${org?.slug}/usage`}>View usage</Link>
</Button>
)}
<Button asChild type="default" icon={<ExternalLink />}>
<a href={`${DOCS_URL}/guides/platform/cost-control#spend-cap`}>About spend cap</a>
</Button>
</div>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{shownAlert === 'gracePeriod' && (
<Alert_Shadcn_ variant="warning">
<WarningIcon />
<AlertTitle_Shadcn_>Your grace period has started.</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<p className="leading-tight">
Your organization is over its quota
{violationLabels && ` ${violationLabels}`}. You can continue with your projects until
your grace period ends on{' '}
<span className="text-foreground">
{dayjs(org.restriction_data['grace_period_end']).format('DD MMM, YYYY')}
</span>
. After that, the Fair Use Policy will apply. If you plan to maintain this level of
usage, {org.plan.id === 'free' ? 'upgrade your plan' : 'disable spend cap'} to avoid
any restrictions. If restrictions are applied, requests to your projects will return a
402 status code.
</p>
<div className="flex items-center gap-x-2 mt-3">
<Button asChild key="upgrade-button" type="default">
<Link
href={`/org/${org?.slug}/billing?panel=${
org.plan.id === 'free'
? 'subscriptionPlan&source=fairUseGracePeriodStarted'
: 'costControl&source=fairUseGracePeriodStarted'
}`}
>
{org.plan.id === 'free' ? 'Upgrade plan' : 'Disable spend cap'}
</Link>
</Button>
{!isUsagePage && (
<Button key="view-usage-button" asChild type="default">
<Link href={`/org/${org?.slug}/usage`}>View usage</Link>
</Button>
)}
<Button asChild type="default" icon={<ExternalLink />}>
<a href={`${DOCS_URL}/guides/platform/billing-faq#fair-use-policy`}>
About Fair Use Policy
</a>
</Button>
</div>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{shownAlert === 'gracePeriodOver' && (
<Alert_Shadcn_ variant="warning">
<WarningIcon />
<AlertTitle_Shadcn_>Your grace period is over.</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<p>
Your grace period ended on{' '}
<span className="text-foreground">
{dayjs(org.restriction_data['grace_period_end']).format('DD MMM, YYYY')}
</span>
. Fair Use Policy applies now. Stay below your plans quota or{' '}
{org.plan.id === 'free' ? 'upgrade your plan' : 'disable spend cap'} if you expect to
exceed it. If you exceed your quota, requests will respond with a 402 status code.
</p>
<div className="flex items-center gap-x-2 mt-3">
<Button key="upgrade-button" asChild type="default">
<Link
href={`/org/${org?.slug}/billing?panel=${
org.plan.id === 'free'
? 'subscriptionPlan&source=fairUseGracePeriodOver'
: 'costControl&source=fairUseGracePeriodOver'
}`}
>
{org.plan.id === 'free' ? 'Upgrade plan' : 'Disable spend cap'}
</Link>
</Button>
{!isUsagePage && (
<Button key="view-usage-button" asChild type="default">
<Link href={`/org/${org?.slug}/usage`}>View usage</Link>
</Button>
)}
<Button asChild type="default" icon={<ExternalLink />}>
<a href={`${DOCS_URL}/guides/platform/billing-faq#fair-use-policy`}>
About Fair Use Policy
</a>
</Button>
</div>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{shownAlert === 'restricted' && (
<Alert_Shadcn_ variant="destructive">
<CriticalIcon />
<AlertTitle_Shadcn_>All services are restricted.</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<p>
Fair Use Policy applies and your service is restricted. Your projects are not able to
serve requests and will respond with a 402 status code. You have exceeded your plans
quota{violationLabels && ` ${violationLabels}`}.{' '}
{org.plan.id === 'free' ? 'Upgrade your plan' : 'Disable spend cap'} to lift
restrictions or wait until your quota refills on your next billing period.
</p>
<div className="flex items-center gap-x-2 mt-3">
<Button key="upgrade-button" asChild type="default">
<Link
href={`/org/${org?.slug}/billing?panel=${
org.plan.id === 'free'
? 'subscriptionPlan&source=fairUseRestricted'
: 'costControl&source=fairUseRestricted'
}`}
>
{org.plan.id === 'free' ? 'Upgrade plan' : 'Disable spend cap'}
</Link>
</Button>
{!isUsagePage && (
<Button key="view-usage-button" asChild type="default">
<Link href={`/org/${org?.slug}/usage`}>View usage</Link>
</Button>
)}
<Button asChild type="default" icon={<ExternalLink />}>
<a href={`${DOCS_URL}/guides/platform/billing-faq#fair-use-policy`}>
About Fair Use Policy
</a>
</Button>
</div>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
</>
)
}