Restriction.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import dayjs from 'dayjs'
  2. import { ExternalLink } from 'lucide-react'
  3. import Link from 'next/link'
  4. import { usePathname } from 'next/navigation'
  5. import { Alert, AlertDescription, AlertTitle, Button, CriticalIcon, WarningIcon } from 'ui'
  6. import { PricingMetric } from '@/data/analytics/org-daily-stats-query'
  7. import { VIOLATION_TYPE_LABELS } from '@/data/usage/constants'
  8. import { useOrgUsageQuery } from '@/data/usage/org-usage-query'
  9. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  10. import { DOCS_URL } from '@/lib/constants'
  11. export const Restriction = () => {
  12. const { data: org } = useSelectedOrganizationQuery()
  13. const { data: usage, isSuccess: isSuccessOrgUsage } = useOrgUsageQuery({ orgSlug: org?.slug })
  14. const pathname = usePathname()
  15. const isUsagePage = pathname?.endsWith('/usage')
  16. const hasExceededAnyLimits = Boolean(
  17. usage?.usages.find(
  18. (metric) =>
  19. metric.metric !== PricingMetric.DISK_SIZE_GB_HOURS_GP3 &&
  20. !metric.unlimited &&
  21. metric.capped &&
  22. metric.usage > (metric?.pricing_free_units ?? 0)
  23. )
  24. )
  25. // don't show any alerts until everything has been fetched
  26. if (!isSuccessOrgUsage || !org) {
  27. return null
  28. }
  29. let shownAlert: 'exceededLimits' | 'gracePeriod' | 'gracePeriodOver' | 'restricted' | null = null
  30. if (hasExceededAnyLimits && !org?.restriction_status) {
  31. shownAlert = 'exceededLimits'
  32. } else if (org?.restriction_status === 'grace_period') {
  33. shownAlert = 'gracePeriod'
  34. } else if (org?.restriction_status === 'grace_period_over') {
  35. shownAlert = 'gracePeriodOver'
  36. } else if (org?.restriction_status === 'restricted') {
  37. shownAlert = 'restricted'
  38. }
  39. if (shownAlert === null || !org?.restriction_data) {
  40. return null
  41. }
  42. const violationLabels =
  43. Array.isArray(org.restriction_data['violations']) &&
  44. org.restriction_data['violations'].length > 0
  45. ? `(${org.restriction_data['violations']
  46. .map((violation: string) => VIOLATION_TYPE_LABELS[violation] || violation)
  47. .join(', ')})`
  48. : ''
  49. return (
  50. <>
  51. {shownAlert === 'exceededLimits' && (
  52. <Alert variant="destructive">
  53. <CriticalIcon />
  54. <AlertTitle>Your organization's usage has exceeded its included quota</AlertTitle>
  55. <AlertDescription>
  56. <p>
  57. Your projects can become unresponsive or enter read-only mode.{' '}
  58. {org.plan.id === 'free'
  59. ? 'Please upgrade to the Pro Plan to ensure that your projects remain available.'
  60. : 'Please disable spend cap to ensure that your projects remain available.'}
  61. </p>
  62. <div className="flex items-center gap-x-2 mt-3">
  63. <Button key="upgrade-button" asChild type="default">
  64. <Link
  65. href={`/org/${org?.slug}/billing?panel=${
  66. org.plan.id === 'free' ? 'subscriptionPlan' : 'costControl'
  67. }`}
  68. >
  69. {org.plan.id === 'free' ? 'Upgrade plan' : 'Change spend cap'}
  70. </Link>
  71. </Button>
  72. {!isUsagePage && (
  73. <Button key="view-usage-button" asChild type="default">
  74. <Link href={`/org/${org?.slug}/usage`}>View usage</Link>
  75. </Button>
  76. )}
  77. <Button asChild type="default" icon={<ExternalLink />}>
  78. <a href={`${DOCS_URL}/guides/platform/cost-control#spend-cap`}>About spend cap</a>
  79. </Button>
  80. </div>
  81. </AlertDescription>
  82. </Alert>
  83. )}
  84. {shownAlert === 'gracePeriod' && (
  85. <Alert variant="warning">
  86. <WarningIcon />
  87. <AlertTitle>Your grace period has started.</AlertTitle>
  88. <AlertDescription>
  89. <p className="leading-tight">
  90. Your organization went over its quota in the previous billing cycle
  91. {violationLabels && ` ${violationLabels}`}. You can continue with your projects until
  92. your grace period ends on{' '}
  93. <span className="text-foreground">
  94. {dayjs(org.restriction_data['grace_period_end']).format('DD MMM, YYYY')}
  95. </span>
  96. . After that, the Fair Use Policy will apply. If you plan to maintain this level of
  97. usage, {org.plan.id === 'free' ? 'upgrade your plan' : 'disable spend cap'} to avoid
  98. any restrictions. If restrictions are applied, requests to your projects will return a
  99. 402 status code.
  100. </p>
  101. <div className="flex items-center gap-x-2 mt-3">
  102. <Button asChild key="upgrade-button" type="default">
  103. <Link
  104. href={`/org/${org?.slug}/billing?panel=${
  105. org.plan.id === 'free'
  106. ? 'subscriptionPlan&source=fairUseGracePeriodStarted'
  107. : 'costControl&source=fairUseGracePeriodStarted'
  108. }`}
  109. >
  110. {org.plan.id === 'free' ? 'Upgrade plan' : 'Disable spend cap'}
  111. </Link>
  112. </Button>
  113. {!isUsagePage && (
  114. <Button key="view-usage-button" asChild type="default">
  115. <Link href={`/org/${org?.slug}/usage`}>View usage</Link>
  116. </Button>
  117. )}
  118. <Button asChild type="default" icon={<ExternalLink />}>
  119. <a href={`${DOCS_URL}/guides/platform/billing-faq#fair-use-policy`}>
  120. About Fair Use Policy
  121. </a>
  122. </Button>
  123. </div>
  124. </AlertDescription>
  125. </Alert>
  126. )}
  127. {shownAlert === 'gracePeriodOver' && (
  128. <Alert variant="warning">
  129. <WarningIcon />
  130. <AlertTitle>Your grace period is over.</AlertTitle>
  131. <AlertDescription>
  132. <p>
  133. Your grace period ended on{' '}
  134. <span className="text-foreground">
  135. {dayjs(org.restriction_data['grace_period_end']).format('DD MMM, YYYY')}
  136. </span>
  137. . Fair Use Policy applies now. Stay below your plan's quota or{' '}
  138. {org.plan.id === 'free' ? 'upgrade your plan' : 'disable spend cap'} if you expect to
  139. exceed it. If you exceed your quota, requests will respond with a 402 status code.
  140. </p>
  141. <div className="flex items-center gap-x-2 mt-3">
  142. <Button key="upgrade-button" asChild type="default">
  143. <Link
  144. href={`/org/${org?.slug}/billing?panel=${
  145. org.plan.id === 'free'
  146. ? 'subscriptionPlan&source=fairUseGracePeriodOver'
  147. : 'costControl&source=fairUseGracePeriodOver'
  148. }`}
  149. >
  150. {org.plan.id === 'free' ? 'Upgrade plan' : 'Disable spend cap'}
  151. </Link>
  152. </Button>
  153. {!isUsagePage && (
  154. <Button key="view-usage-button" asChild type="default">
  155. <Link href={`/org/${org?.slug}/usage`}>View usage</Link>
  156. </Button>
  157. )}
  158. <Button asChild type="default" icon={<ExternalLink />}>
  159. <a href={`${DOCS_URL}/guides/platform/billing-faq#fair-use-policy`}>
  160. About Fair Use Policy
  161. </a>
  162. </Button>
  163. </div>
  164. </AlertDescription>
  165. </Alert>
  166. )}
  167. {shownAlert === 'restricted' && (
  168. <Alert variant="destructive">
  169. <CriticalIcon />
  170. <AlertTitle>All services are restricted.</AlertTitle>
  171. <AlertDescription>
  172. <p>
  173. Fair Use Policy applies and your service is restricted. Your projects are not able to
  174. serve requests and will respond with a 402 status code. You have exceeded your plan's
  175. quota{violationLabels && ` ${violationLabels}`}.{' '}
  176. {org.plan.id === 'free' ? 'Upgrade your plan' : 'Disable spend cap'} to lift
  177. restrictions immediately, or wait until your quota refills at the start of your next
  178. billing period. Note that there may be a short delay after your billing period resets
  179. before restrictions are fully lifted.
  180. </p>
  181. <div className="flex items-center gap-x-2 mt-3">
  182. <Button key="upgrade-button" asChild type="default">
  183. <Link
  184. href={`/org/${org?.slug}/billing?panel=${
  185. org.plan.id === 'free'
  186. ? 'subscriptionPlan&source=fairUseRestricted'
  187. : 'costControl&source=fairUseRestricted'
  188. }`}
  189. >
  190. {org.plan.id === 'free' ? 'Upgrade plan' : 'Disable spend cap'}
  191. </Link>
  192. </Button>
  193. {!isUsagePage && (
  194. <Button key="view-usage-button" asChild type="default">
  195. <Link href={`/org/${org?.slug}/usage`}>View usage</Link>
  196. </Button>
  197. )}
  198. <Button asChild type="default" icon={<ExternalLink />}>
  199. <a href={`${DOCS_URL}/guides/platform/billing-faq#fair-use-policy`}>
  200. About Fair Use Policy
  201. </a>
  202. </Button>
  203. </div>
  204. </AlertDescription>
  205. </Alert>
  206. )}
  207. </>
  208. )
  209. }