Skip to content

Commit d30942f

Browse files
panteliselefjacekradko
authored andcommitted
chore(shared,clerk-js): Introduce experimental usePlans (#6327)
1 parent 89a82d2 commit d30942f

File tree

7 files changed

+61
-48
lines changed

7 files changed

+61
-48
lines changed

.changeset/eight-socks-lead.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/shared': minor
4+
'@clerk/types': minor
5+
---
6+
7+
[Billing Beta] Update `clerk.billing.getPlans()` to return paginated data and introduce the `usePlans()` hook.

packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
CommercePaymentResource,
77
CommercePlanJSON,
88
CommercePlanResource,
9-
CommerceProductJSON,
109
CommerceStatementJSON,
1110
CommerceStatementResource,
1211
CommerceSubscriptionJSON,
@@ -29,15 +28,21 @@ import {
2928
} from '../../resources/internal';
3029

3130
export class CommerceBilling implements CommerceBillingNamespace {
32-
getPlans = async (params?: GetPlansParams): Promise<CommercePlanResource[]> => {
33-
const { data: products } = (await BaseResource._fetch({
34-
path: `/commerce/products`,
31+
getPlans = async (params?: GetPlansParams): Promise<ClerkPaginatedResponse<CommercePlanResource>> => {
32+
const { for: forParam, ...safeParams } = params || {};
33+
const searchParams = { ...safeParams, payer_type: forParam || 'user' };
34+
return await BaseResource._fetch({
35+
path: `/commerce/plans`,
3536
method: 'GET',
36-
search: { payerType: params?.subscriberType || '' },
37-
})) as unknown as ClerkPaginatedResponse<CommerceProductJSON>;
37+
search: convertPageToOffsetSearchParams(searchParams),
38+
}).then(res => {
39+
const { data: plans, total_count } = res as unknown as ClerkPaginatedResponse<CommercePlanJSON>;
3840

39-
const defaultProduct = products.find(product => product.is_default);
40-
return defaultProduct?.plans.map(plan => new CommercePlan(plan)) || [];
41+
return {
42+
total_count,
43+
data: plans.map(plan => new CommercePlan(plan)),
44+
};
45+
});
4146
};
4247

4348
getPlan = async (params: { id: string }): Promise<CommercePlanResource> => {

packages/clerk-js/src/ui/contexts/components/Plans.tsx

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import {
22
__experimental_usePaymentAttempts,
33
__experimental_usePaymentMethods,
4+
__experimental_usePlans,
45
__experimental_useStatements,
56
__experimental_useSubscriptionItems,
67
useClerk,
7-
useOrganization,
88
useSession,
9-
useUser,
109
} from '@clerk/shared/react';
1110
import type {
1211
Appearance,
@@ -15,30 +14,13 @@ import type {
1514
CommerceSubscriptionResource,
1615
} from '@clerk/types';
1716
import { useCallback, useMemo } from 'react';
18-
import useSWR from 'swr';
1917

2018
import { getClosestProfileScrollBox } from '@/ui/utils/getClosestProfileScrollBox';
2119

2220
import type { LocalizationKey } from '../../localization';
2321
import { localizationKeys } from '../../localization';
2422
import { useSubscriberTypeContext } from './SubscriberType';
2523

26-
const dedupeOptions = {
27-
dedupingInterval: 1_000 * 60, // 1 minute,
28-
keepPreviousData: true,
29-
};
30-
31-
export const usePaymentSourcesCacheKey = () => {
32-
const { organization } = useOrganization();
33-
const { user } = useUser();
34-
const subscriberType = useSubscriberTypeContext();
35-
36-
return {
37-
key: `commerce-payment-sources`,
38-
resourceId: subscriberType === 'org' ? organization?.id : user?.id,
39-
};
40-
};
41-
4224
// TODO(@COMMERCE): Rename payment sources to payment methods at the API level
4325
export const usePaymentMethods = () => {
4426
const subscriberType = useSubscriberTypeContext();
@@ -82,18 +64,16 @@ export const useSubscriptions = () => {
8264
});
8365
};
8466

85-
export const usePlans = () => {
86-
const { billing } = useClerk();
67+
export const usePlans = (params?: { mode: 'cache' }) => {
8768
const subscriberType = useSubscriberTypeContext();
8869

89-
return useSWR(
90-
{
91-
key: `commerce-plans`,
92-
args: { subscriberType },
93-
},
94-
({ args }) => billing.getPlans(args),
95-
dedupeOptions,
96-
);
70+
return __experimental_usePlans({
71+
for: subscriberType === 'org' ? 'organization' : 'user',
72+
initialPage: 1,
73+
pageSize: 50,
74+
keepPreviousData: true,
75+
__experimental_mode: params?.mode,
76+
});
9777
};
9878

9979
type HandleSelectPlanProps = {
@@ -125,10 +105,7 @@ export const usePlansContext = () => {
125105
const { data: subscriptions, revalidate: revalidateSubscriptions } = useSubscriptions();
126106

127107
// Invalidates cache but does not fetch immediately
128-
const { data: plans, mutate: mutatePlans } = useSWR<Awaited<ReturnType<typeof clerk.billing.getPlans>>>({
129-
key: `commerce-plans`,
130-
args: { subscriberType },
131-
});
108+
const { data: plans, revalidate: revalidatePlans } = usePlans({ mode: 'cache' });
132109

133110
// Invalidates cache but does not fetch immediately
134111
const { revalidate: revalidateStatements } = useStatements({ mode: 'cache' });
@@ -138,10 +115,10 @@ export const usePlansContext = () => {
138115
const revalidateAll = useCallback(() => {
139116
// Revalidate the plans and subscriptions
140117
void revalidateSubscriptions();
141-
void mutatePlans();
118+
void revalidatePlans();
142119
void revalidateStatements();
143120
void revalidatePaymentSources();
144-
}, [revalidateSubscriptions, mutatePlans, revalidateStatements, revalidatePaymentSources]);
121+
}, [revalidateSubscriptions, revalidatePlans, revalidateStatements, revalidatePaymentSources]);
145122

146123
// should the default plan be shown as active
147124
const isDefaultPlanImplicitlyActiveOrUpcoming = useMemo(() => {

packages/shared/src/react/hooks/createCommerceHook.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ type CommerceHookConfig<TResource extends ClerkResource, TParams extends PagesOr
1919
useFetcher: (
2020
param: 'organization' | 'user',
2121
) => ((params: TParams) => Promise<ClerkPaginatedResponse<TResource>>) | undefined;
22+
options?: {
23+
unauthenticated?: boolean;
24+
};
2225
};
2326

2427
/**
@@ -39,6 +42,7 @@ export function createCommerceHook<TResource extends ClerkResource, TParams exte
3942
hookName,
4043
resourceType,
4144
useFetcher,
45+
options,
4246
}: CommerceHookConfig<TResource, TParams>) {
4347
type HookParams = PaginatedHookConfig<PagesOrInfiniteOptions> & {
4448
for: 'organization' | 'user';
@@ -76,7 +80,7 @@ export function createCommerceHook<TResource extends ClerkResource, TParams exte
7680
...(_for === 'organization' ? { orgId: organization?.id } : {}),
7781
} as TParams);
7882

79-
const isClerkLoaded = !!(clerk.loaded && user);
83+
const isClerkLoaded = !!(clerk.loaded && (options?.unauthenticated ? true : user));
8084

8185
const isEnabled = !!hookParams && isClerkLoaded;
8286

packages/shared/src/react/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export { useStatements as __experimental_useStatements } from './useStatements';
1212
export { usePaymentAttempts as __experimental_usePaymentAttempts } from './usePaymentAttempts';
1313
export { usePaymentMethods as __experimental_usePaymentMethods } from './usePaymentMethods';
1414
export { useSubscriptionItems as __experimental_useSubscriptionItems } from './useSubscriptionItems';
15+
export { usePlans as __experimental_usePlans } from './usePlans';
1516
export { useCheckout as __experimental_useCheckout } from './useCheckout';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { CommercePlanResource, GetPlansParams } from '@clerk/types';
2+
3+
import { useClerkInstanceContext } from '../contexts';
4+
import { createCommerceHook } from './createCommerceHook';
5+
6+
/**
7+
* @internal
8+
*/
9+
export const usePlans = createCommerceHook<CommercePlanResource, GetPlansParams>({
10+
hookName: 'usePlans',
11+
resourceType: 'commerce-plans',
12+
useFetcher: _for => {
13+
const clerk = useClerkInstanceContext();
14+
return params => clerk.billing.getPlans({ ...params, for: _for === 'organization' ? 'org' : 'user' });
15+
},
16+
options: {
17+
unauthenticated: true,
18+
},
19+
});

packages/types/src/commerce.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export interface CommerceBillingNamespace {
3434
* <ClerkProvider clerkJsVersion="x.x.x" />
3535
* ```
3636
*/
37-
getPlans: (params?: GetPlansParams) => Promise<CommercePlanResource[]>;
37+
getPlans: (params?: GetPlansParams) => Promise<ClerkPaginatedResponse<CommercePlanResource>>;
3838

3939
/**
4040
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
@@ -205,7 +205,7 @@ export interface CommerceProductResource extends ClerkResource {
205205
* <ClerkProvider clerkJsVersion="x.x.x" />
206206
* ```
207207
*/
208-
export interface GetPlansParams {
208+
export type GetPlansParams = ClerkPaginationParams<{
209209
/**
210210
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.
211211
* It is advised to pin the SDK version and the clerk-js version to a specific version to avoid breaking changes.
@@ -214,8 +214,8 @@ export interface GetPlansParams {
214214
* <ClerkProvider clerkJsVersion="x.x.x" />
215215
* ```
216216
*/
217-
subscriberType?: CommerceSubscriberType;
218-
}
217+
for?: CommerceSubscriberType;
218+
}>;
219219

220220
/**
221221
* @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change.

0 commit comments

Comments
 (0)