BillingBreakdown.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import { PermissionAction } from '@supabase/shared-types/out/constants'
  2. import { useParams } from 'common'
  3. import dayjs from 'dayjs'
  4. import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
  5. import { UpcomingInvoice } from './UpcomingInvoice'
  6. import {
  7. ScaffoldSection,
  8. ScaffoldSectionContent,
  9. ScaffoldSectionDetail,
  10. } from '@/components/layouts/Scaffold'
  11. import AlertError from '@/components/ui/AlertError'
  12. import { InlineLink } from '@/components/ui/InlineLink'
  13. import NoPermission from '@/components/ui/NoPermission'
  14. import SparkBar from '@/components/ui/SparkBar'
  15. import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-query'
  16. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  17. import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
  18. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  19. import { MANAGED_BY } from '@/lib/constants/infrastructure'
  20. const BillingBreakdown = () => {
  21. const { slug: orgSlug } = useParams()
  22. const { data: selectedOrganization } = useSelectedOrganizationQuery()
  23. const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = useAsyncCheckPermissions(
  24. PermissionAction.BILLING_READ,
  25. 'stripe.subscriptions'
  26. )
  27. const {
  28. data: subscription,
  29. error: subscriptionError,
  30. isPending: isLoadingSubscription,
  31. isError: isErrorSubscription,
  32. } = useOrgSubscriptionQuery({ orgSlug }, { enabled: canReadSubscriptions })
  33. const invoiceFeatureEnabled = useIsFeatureEnabled('billing:invoices')
  34. const billingCycleStart = dayjs.unix(subscription?.current_period_start ?? 0).utc()
  35. const billingCycleEnd = dayjs.unix(subscription?.current_period_end ?? 0).utc()
  36. const daysToCycleEnd = billingCycleEnd.diff(dayjs(), 'days')
  37. const daysWithinCycle = billingCycleEnd.diff(billingCycleStart, 'days')
  38. return (
  39. <ScaffoldSection>
  40. <ScaffoldSectionDetail>
  41. <div className="sticky space-y-2 top-12 pr-6">
  42. <p className="text-foreground text-base m-0">Upcoming Invoice</p>
  43. <div className="py-2">
  44. {selectedOrganization?.managed_by !== MANAGED_BY.AWS_MARKETPLACE && (
  45. <SparkBar
  46. type="horizontal"
  47. value={daysWithinCycle - daysToCycleEnd}
  48. max={daysWithinCycle}
  49. barClass="bg-foreground"
  50. labelBottom={`${billingCycleStart.format('MMMM DD')} - ${billingCycleEnd.format('MMMM DD')}`}
  51. bgClass="bg-surface-300"
  52. labelBottomClass="text-foreground-light! p-1 m-0"
  53. labelTop={
  54. subscription
  55. ? `${daysToCycleEnd} ${daysToCycleEnd === 1 ? 'day' : 'days'} left`
  56. : ''
  57. }
  58. labelTopClass="p-1 m-0"
  59. />
  60. )}
  61. </div>
  62. <p className="prose text-sm">
  63. {selectedOrganization?.managed_by === MANAGED_BY.AWS_MARKETPLACE ? (
  64. <>
  65. <p>
  66. AWS Marketplace sends two invoices each month: one for your fixed subscription
  67. fee, billed on the day you subscribed, and one for your usage charges from the
  68. previous month, billed by the 3rd.
  69. </p>
  70. <p>
  71. For a more detailed breakdown, visit the{' '}
  72. <InlineLink href={`/org/${orgSlug}/usage`}>usage page.</InlineLink>
  73. </p>
  74. </>
  75. ) : (
  76. <>
  77. Your upcoming invoice will continue to update until the end of your billing cycle on{' '}
  78. {billingCycleEnd.format('MMMM DD')}. For a more detailed breakdown, visit the{' '}
  79. <InlineLink href={`/org/${orgSlug}/usage`}>usage page.</InlineLink>
  80. </>
  81. )}
  82. </p>
  83. <br />
  84. <p className="text-sm text-foreground-light mt-4">
  85. Add-on changes or new projects may take up to an hour to appear.
  86. </p>
  87. </div>
  88. </ScaffoldSectionDetail>
  89. <ScaffoldSectionContent>
  90. {isPermissionsLoaded && !canReadSubscriptions ? (
  91. <NoPermission resourceText="view this organization's upcoming invoice" />
  92. ) : (
  93. <>
  94. {isLoadingSubscription && (
  95. <div className="space-y-2">
  96. <ShimmeringLoader />
  97. <ShimmeringLoader className="w-3/4" />
  98. <ShimmeringLoader className="w-1/2" />
  99. </div>
  100. )}
  101. {isErrorSubscription && (
  102. <AlertError subject="Failed to retrieve subscription" error={subscriptionError} />
  103. )}
  104. {invoiceFeatureEnabled && <UpcomingInvoice slug={orgSlug} />}
  105. </>
  106. )}
  107. </ScaffoldSectionContent>
  108. </ScaffoldSection>
  109. )
  110. }
  111. export default BillingBreakdown