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