Subscription.tsx153 lines · main
1import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
2import { useFlag, useParams } from 'common'
3import Link from 'next/link'
4import { Button } from 'ui'
5import { Admonition } from 'ui-patterns/admonition'
6import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
7
8import { ProjectUpdateDisabledTooltip } from '../ProjectUpdateDisabledTooltip'
9import { Restriction } from '../Restriction'
10import { PlanUpdateSidePanel } from './PlanUpdateSidePanel'
11import { SupportLink } from '@/components/interfaces/Support/SupportLink'
12import {
13 ScaffoldSection,
14 ScaffoldSectionContent,
15 ScaffoldSectionDetail,
16} from '@/components/layouts/Scaffold'
17import AlertError from '@/components/ui/AlertError'
18import NoPermission from '@/components/ui/NoPermission'
19import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-query'
20import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
21import { useOrgSettingsPageStateSnapshot } from '@/state/organization-settings'
22
23const Subscription = () => {
24 const { slug } = useParams()
25 const snap = useOrgSettingsPageStateSnapshot()
26 const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
27
28 const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = useAsyncCheckPermissions(
29 PermissionAction.BILLING_READ,
30 'stripe.subscriptions'
31 )
32
33 const {
34 data: subscription,
35 error,
36 isPending: isLoading,
37 isError,
38 isSuccess,
39 } = useOrgSubscriptionQuery({ orgSlug: slug }, { enabled: canReadSubscriptions })
40
41 const currentPlan = subscription?.plan
42 const planName = currentPlan?.name ?? 'Unknown'
43
44 const canChangeTier =
45 !projectUpdateDisabled && !['enterprise', 'platform'].includes(currentPlan?.id ?? '')
46
47 return (
48 <>
49 <ScaffoldSection>
50 <div className="col-span-12 pb-2">
51 <Restriction />
52 </div>
53 <ScaffoldSectionDetail>
54 <div className="sticky space-y-6 top-12">
55 <div className="space-y-2 mb-4">
56 <p className="text-foreground text-base m-0">Subscription Plan</p>
57 <p className="text-sm text-foreground-light m-0">
58 Each organization has it's own subscription plan, billing cycle, payment methods and
59 usage quotas.
60 </p>
61 </div>
62 </div>
63 </ScaffoldSectionDetail>
64 <ScaffoldSectionContent>
65 {isPermissionsLoaded && !canReadSubscriptions ? (
66 <NoPermission resourceText="view this organization's subscription" />
67 ) : (
68 <>
69 {isLoading && (
70 <div className="space-y-2">
71 <ShimmeringLoader />
72 <ShimmeringLoader className="w-3/4" />
73 <ShimmeringLoader className="w-1/2" />
74 </div>
75 )}
76
77 {isError && <AlertError subject="Failed to retrieve subscription" error={error} />}
78
79 {isSuccess && (
80 <div className="space-y-6">
81 <div>
82 <p className="text-2xl text-brand">{currentPlan?.name ?? 'Unknown'} Plan</p>
83 </div>
84
85 <div>
86 {canChangeTier ? (
87 <ProjectUpdateDisabledTooltip projectUpdateDisabled={projectUpdateDisabled}>
88 <Button
89 type="default"
90 className="pointer-events-auto"
91 disabled={!canChangeTier}
92 onClick={() => snap.setPanelKey('subscriptionPlan')}
93 >
94 Change subscription plan
95 </Button>
96 </ProjectUpdateDisabledTooltip>
97 ) : projectUpdateDisabled ? (
98 <Admonition
99 type="default"
100 layout="horizontal"
101 title={`Unable to update plan from ${planName}`}
102 description="We have temporarily disabled project and subscription changes - our
103 engineers are working on a fix."
104 />
105 ) : (
106 <Admonition
107 type="default"
108 layout="horizontal"
109 title={`Unable to update plan from ${planName}`}
110 description="Please contact us if you'd like to change your plan."
111 actions={
112 <Button asChild key="contact-support" type="default">
113 <SupportLink
114 queryParams={{
115 category: SupportCategories.SALES_ENQUIRY,
116 subject: `Change plan away from ${planName}`,
117 }}
118 >
119 Contact support
120 </SupportLink>
121 </Button>
122 }
123 />
124 )}
125 </div>
126
127 {!subscription?.usage_billing_enabled && (
128 <Admonition
129 type="default"
130 title="This organization is limited by the included usage"
131 >
132 <div className="[&>p]:leading-normal! prose text-sm">
133 Projects may become unresponsive when this organization exceeds its{' '}
134 <Link href={`/org/${slug}/usage`}>included usage quota</Link>. To scale
135 seamlessly,{' '}
136 {currentPlan?.id === 'free'
137 ? 'upgrade to a paid plan.'
138 : 'you can disable Spend Cap under the Cost Control settings.'}
139 </div>
140 </Admonition>
141 )}
142 </div>
143 )}
144 </>
145 )}
146 </ScaffoldSectionContent>
147 </ScaffoldSection>
148 <PlanUpdateSidePanel />
149 </>
150 )
151}
152
153export default Subscription