Skip to content

Commit 0ae0403

Browse files
feat(clerk-js,types): Add subscription listing to UP and OP (#5658)
Co-authored-by: Keiran Flanigan <[email protected]>
1 parent 6f4cf71 commit 0ae0403

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2343
-531
lines changed

.changeset/red-peaches-fold.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@clerk/localizations': patch
3+
'@clerk/clerk-js': patch
4+
'@clerk/clerk-react': patch
5+
'@clerk/types': patch
6+
---
7+
8+
Add `<SubscriptionsList />` to both UserProfile and OrgProfile components.
9+
10+
Introduce experimental method for opening `<SubscriptionDetails />` component.
11+
12+
```tsx
13+
clerk.__experimental_openSubscriptionDetails(...)
14+
```

packages/clerk-js/bundlewatch.config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"files": [
3-
{ "path": "./dist/clerk.js", "maxSize": "593kB" },
4-
{ "path": "./dist/clerk.browser.js", "maxSize": "74.2KB" },
3+
{ "path": "./dist/clerk.js", "maxSize": "594kB" },
4+
{ "path": "./dist/clerk.browser.js", "maxSize": "75KB" },
55
{ "path": "./dist/clerk.headless*.js", "maxSize": "55KB" },
66
{ "path": "./dist/ui-common*.js", "maxSize": "102KB" },
77
{ "path": "./dist/vendors*.js", "maxSize": "39KB" },

packages/clerk-js/src/core/clerk.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
__experimental_CheckoutProps,
1818
__experimental_CommerceNamespace,
1919
__experimental_PricingTableProps,
20+
__experimental_SubscriptionDetailsProps,
2021
__internal_ComponentNavigationContext,
2122
__internal_UserVerificationModalProps,
2223
AuthenticateWithCoinbaseWalletParams,
@@ -550,6 +551,18 @@ export class Clerk implements ClerkInterface {
550551
void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('checkout'));
551552
};
552553

554+
public __internal_openSubscriptionDetails = (props?: __experimental_SubscriptionDetailsProps): void => {
555+
this.assertComponentsReady(this.#componentControls);
556+
void this.#componentControls
557+
.ensureMounted({ preloadHint: 'SubscriptionDetails' })
558+
.then(controls => controls.openDrawer('subscriptionDetails', props || {}));
559+
};
560+
561+
public __internal_closeSubscriptionDetails = (): void => {
562+
this.assertComponentsReady(this.#componentControls);
563+
void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('subscriptionDetails'));
564+
};
565+
553566
public __internal_openReverification = (props?: __internal_UserVerificationModalProps): void => {
554567
this.assertComponentsReady(this.#componentControls);
555568
if (noUserExists(this)) {

packages/clerk-js/src/ui/Components.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createDeferredPromise } from '@clerk/shared/utils';
22
import type {
33
__experimental_CheckoutProps,
4+
__experimental_SubscriptionDetailsProps,
45
__internal_UserVerificationProps,
56
Appearance,
67
Clerk,
@@ -33,6 +34,7 @@ import {
3334
preloadComponent,
3435
SignInModal,
3536
SignUpModal,
37+
SubscriptionDetails,
3638
UserProfileModal,
3739
UserVerificationModal,
3840
WaitlistModal,
@@ -107,12 +109,16 @@ export type ComponentControls = {
107109
notify?: boolean;
108110
},
109111
) => void;
110-
openDrawer: <T extends 'checkout'>(
112+
openDrawer: <T extends 'checkout' | 'subscriptionDetails'>(
111113
drawer: T,
112-
props: T extends 'checkout' ? __experimental_CheckoutProps : never,
114+
props: T extends 'checkout'
115+
? __experimental_CheckoutProps
116+
: T extends 'subscriptionDetails'
117+
? __experimental_SubscriptionDetailsProps
118+
: never,
113119
) => void;
114120
closeDrawer: (
115-
drawer: 'checkout',
121+
drawer: 'checkout' | 'subscriptionDetails',
116122
options?: {
117123
notify?: boolean;
118124
},
@@ -153,6 +159,10 @@ interface ComponentsState {
153159
open: false;
154160
props: null | __experimental_CheckoutProps;
155161
};
162+
subscriptionDetailsDrawer: {
163+
open: false;
164+
props: null | __experimental_SubscriptionDetailsProps;
165+
};
156166
nodes: Map<HTMLDivElement, HtmlNodeOptions>;
157167
impersonationFab: boolean;
158168
}
@@ -238,6 +248,10 @@ const Components = (props: ComponentsProps) => {
238248
open: false,
239249
props: null,
240250
},
251+
subscriptionDetailsDrawer: {
252+
open: false,
253+
props: null,
254+
},
241255
nodes: new Map(),
242256
impersonationFab: false,
243257
});
@@ -253,6 +267,7 @@ const Components = (props: ComponentsProps) => {
253267
waitlistModal,
254268
blankCaptchaModal,
255269
checkoutDrawer,
270+
subscriptionDetailsDrawer,
256271
nodes,
257272
} = state;
258273

@@ -548,6 +563,25 @@ const Components = (props: ComponentsProps) => {
548563
</LazyDrawerRenderer>
549564
);
550565

566+
const mountedSubscriptionDetailDrawer = subscriptionDetailsDrawer.props && (
567+
<LazyDrawerRenderer
568+
globalAppearance={state.appearance}
569+
appearanceKey={'subscriptionDetails' as any}
570+
componentAppearance={{}}
571+
flowName={'subscriptionDetails'}
572+
open={subscriptionDetailsDrawer.open}
573+
onOpenChange={() => componentsControls.closeDrawer('subscriptionDetails')}
574+
componentName={'SubscriptionDetails'}
575+
portalId={subscriptionDetailsDrawer.props.portalId}
576+
>
577+
<SubscriptionDetails
578+
{...subscriptionDetailsDrawer.props}
579+
subscriberType={subscriptionDetailsDrawer.props.subscriberType || 'user'}
580+
onSubscriptionCancel={subscriptionDetailsDrawer.props.onSubscriptionCancel || (() => {})}
581+
/>
582+
</LazyDrawerRenderer>
583+
);
584+
551585
return (
552586
<Suspense fallback={''}>
553587
<LazyProviders
@@ -579,7 +613,7 @@ const Components = (props: ComponentsProps) => {
579613
{waitlistModal && mountedWaitlistModal}
580614
{blankCaptchaModal && mountedBlankCaptchaModal}
581615
{mountedCheckoutDrawer}
582-
616+
{mountedSubscriptionDetailDrawer}
583617
{state.impersonationFab && (
584618
<LazyImpersonationFabProvider globalAppearance={state.appearance}>
585619
<ImpersonationFab />

packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationBillingPage.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
usePlansContext,
66
withPlans,
77
} from '../../contexts';
8-
import { Col, descriptors, localizationKeys } from '../../customizables';
8+
import { Col, descriptors, Heading, Hr, localizationKeys } from '../../customizables';
99
import {
1010
Card,
1111
Header,
@@ -21,6 +21,7 @@ import { useTabState } from '../../hooks/useTabState';
2121
import { InvoicesList } from '../Invoices';
2222
import { __experimental_PaymentSources } from '../PaymentSources/PaymentSources';
2323
import { __experimental_PricingTable } from '../PricingTable';
24+
import { SubscriptionsList } from '../Subscriptions';
2425

2526
const orgTabMap = {
2627
0: 'plans',
@@ -75,7 +76,19 @@ export const OrganizationBillingPage = withPlans(
7576
/>
7677
</TabsList>
7778
<TabPanels>
78-
<TabPanel sx={{ width: '100%' }}>
79+
<TabPanel sx={{ width: '100%', flexDirection: 'column' }}>
80+
{subscriptions.length > 0 && (
81+
<>
82+
<SubscriptionsList />
83+
<Hr sx={t => ({ marginBlock: t.space.$6 })} />
84+
<Heading
85+
textVariant='h3'
86+
as='h2'
87+
localizationKey='Available Plans'
88+
sx={t => ({ marginBlockEnd: t.space.$4, fontWeight: t.fontWeights.$medium })}
89+
/>
90+
</>
91+
)}
7992
<__experimental_PricingTableContext.Provider
8093
value={{ componentName: 'PricingTable', mode: 'modal', subscriberType: 'org' }}
8194
>

packages/clerk-js/src/ui/components/PaymentSources/PaymentSources.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ const PaymentSourceMenu = ({
161161
label: localizationKeys('userProfile.__experimental_billingPage.paymentSourcesSection.actionLabel__remove'),
162162
isDestructive: true,
163163
onClick: () => open(`remove-${paymentSource.id}`),
164+
isDisabled: paymentSource.isDefault,
164165
},
165166
];
166167

@@ -176,6 +177,7 @@ const PaymentSourceMenu = ({
176177
handleError(error, [], card.setError);
177178
});
178179
},
180+
isDisabled: false,
179181
});
180182
}
181183

packages/clerk-js/src/ui/components/PricingTable/PricingTable.tsx

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@ import { useClerk } from '@clerk/shared/react';
22
import type {
33
__experimental_CommercePlanResource,
44
__experimental_CommerceSubscriptionPlanPeriod,
5-
__experimental_CommerceSubscriptionResource,
65
__experimental_PricingTableProps,
76
} from '@clerk/types';
87
import { useState } from 'react';
98

109
import { PROFILE_CARD_SCROLLBOX_ID } from '../../constants';
1110
import { usePlansContext, usePricingTableContext } from '../../contexts';
12-
import { AppearanceProvider } from '../../customizables';
1311
import { PricingTableDefault } from './PricingTableDefault';
1412
import { PricingTableMatrix } from './PricingTableMatrix';
15-
import { SubscriptionDetailDrawer } from './SubscriptionDetailDrawer';
1613

1714
const PricingTable = (props: __experimental_PricingTableProps) => {
1815
const clerk = useClerk();
@@ -22,9 +19,6 @@ const PricingTable = (props: __experimental_PricingTableProps) => {
2219
const { plans, revalidate, activeOrUpcomingSubscription } = usePlansContext();
2320

2421
const [planPeriod, setPlanPeriod] = useState<__experimental_CommerceSubscriptionPlanPeriod>('month');
25-
const [detailSubscription, setDetailSubscription] = useState<__experimental_CommerceSubscriptionResource>();
26-
27-
const [showSubscriptionDetailDrawer, setShowSubscriptionDetailDrawer] = useState(false);
2822

2923
const selectPlan = (plan: __experimental_CommercePlanResource) => {
3024
if (!clerk.isSignedIn) {
@@ -34,8 +28,12 @@ const PricingTable = (props: __experimental_PricingTableProps) => {
3428
const subscription = activeOrUpcomingSubscription(plan);
3529

3630
if (subscription && !subscription.canceledAt) {
37-
setDetailSubscription(subscription);
38-
setShowSubscriptionDetailDrawer(true);
31+
clerk.__internal_openSubscriptionDetails({
32+
subscription,
33+
subscriberType,
34+
onSubscriptionCancel: onSubscriptionChange,
35+
portalId: mode === 'modal' ? PROFILE_CARD_SCROLLBOX_ID : undefined,
36+
});
3937
} else {
4038
clerk.__internal_openCheckout({
4139
planId: plan.id,
@@ -71,24 +69,6 @@ const PricingTable = (props: __experimental_PricingTableProps) => {
7169
props={props}
7270
/>
7371
)}
74-
75-
<AppearanceProvider
76-
appearanceKey='checkout'
77-
appearance={props.checkoutProps?.appearance}
78-
>
79-
<SubscriptionDetailDrawer
80-
isOpen={showSubscriptionDetailDrawer}
81-
setIsOpen={setShowSubscriptionDetailDrawer}
82-
subscription={detailSubscription}
83-
subscriberType={subscriberType}
84-
setPlanPeriod={setPlanPeriod}
85-
strategy={mode === 'mounted' ? 'fixed' : 'absolute'}
86-
portalProps={{
87-
id: mode === 'modal' ? PROFILE_CARD_SCROLLBOX_ID : undefined,
88-
}}
89-
onSubscriptionCancel={onSubscriptionChange}
90-
/>
91-
</AppearanceProvider>
9272
</>
9373
);
9474
};

0 commit comments

Comments
 (0)