| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- import dayjs from 'dayjs'
- import { ExternalLink } from 'lucide-react'
- import Link from 'next/link'
- import { usePathname } from 'next/navigation'
- import { Alert, AlertDescription, AlertTitle, Button, CriticalIcon, WarningIcon } from 'ui'
- 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'
- 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 variant="destructive">
- <CriticalIcon />
- <AlertTitle>Your organization's usage has exceeded its included quota</AlertTitle>
- <AlertDescription>
- <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>
- </Alert>
- )}
- {shownAlert === 'gracePeriod' && (
- <Alert variant="warning">
- <WarningIcon />
- <AlertTitle>Your grace period has started.</AlertTitle>
- <AlertDescription>
- <p className="leading-tight">
- Your organization went over its quota in the previous billing cycle
- {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>
- </Alert>
- )}
- {shownAlert === 'gracePeriodOver' && (
- <Alert variant="warning">
- <WarningIcon />
- <AlertTitle>Your grace period is over.</AlertTitle>
- <AlertDescription>
- <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 plan's 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>
- </Alert>
- )}
- {shownAlert === 'restricted' && (
- <Alert variant="destructive">
- <CriticalIcon />
- <AlertTitle>All services are restricted.</AlertTitle>
- <AlertDescription>
- <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 plan's
- quota{violationLabels && ` ${violationLabels}`}.{' '}
- {org.plan.id === 'free' ? 'Upgrade your plan' : 'Disable spend cap'} to lift
- restrictions immediately, or wait until your quota refills at the start of your next
- billing period. Note that there may be a short delay after your billing period resets
- before restrictions are fully lifted.
- </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>
- </Alert>
- )}
- </>
- )
- }
|