Skip to content

Commit c9b59fe

Browse files
committed
fix(clerk-js): Hide billing tab when appropriate
1 parent 9796fbf commit c9b59fe

File tree

6 files changed

+55
-11
lines changed

6 files changed

+55
-11
lines changed

.changeset/mighty-lions-cut.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
---
4+
5+
Hide billing tab when no paid plans exist, the user does not have a current or past subscription.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const OrganizationPaymentAttemptPage = lazy(() =>
3838
);
3939

4040
export const OrganizationProfileRoutes = () => {
41-
const { pages, isMembersPageRoot, isGeneralPageRoot, isBillingPageRoot, isApiKeysPageRoot } =
41+
const { pages, isMembersPageRoot, isGeneralPageRoot, isBillingPageRoot, isApiKeysPageRoot, shouldShowBilling } =
4242
useOrganizationProfileContext();
4343
const { apiKeysSettings, commerceSettings } = useEnvironment();
4444

@@ -83,7 +83,7 @@ export const OrganizationProfileRoutes = () => {
8383
</Route>
8484
</Switch>
8585
</Route>
86-
{commerceSettings.billing.organization.enabled ? (
86+
{commerceSettings.billing.organization.enabled && shouldShowBilling ? (
8787
<Protect
8888
condition={has =>
8989
has({ permission: 'org:sys_billing:read' }) || has({ permission: 'org:sys_billing:manage' })

packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const PaymentAttemptPage = lazy(() =>
3838
);
3939

4040
export const UserProfileRoutes = () => {
41-
const { pages } = useUserProfileContext();
41+
const { pages, shouldShowBilling } = useUserProfileContext();
4242
const { apiKeysSettings, commerceSettings } = useEnvironment();
4343

4444
const isAccountPageRoot = pages.routes[0].id === USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT;
@@ -80,7 +80,7 @@ export const UserProfileRoutes = () => {
8080
</Route>
8181
</Switch>
8282
</Route>
83-
{commerceSettings.billing.user.enabled ? (
83+
{commerceSettings.billing.user.enabled && shouldShowBilling ? (
8484
<Route path={isBillingPageRoot ? undefined : 'billing'}>
8585
<Switch>
8686
<Route index>

packages/clerk-js/src/ui/contexts/components/OrganizationProfile.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import type { CustomPageContent } from '@/ui/utils/createCustomPages';
66
import { createOrganizationProfileCustomPages } from '@/ui/utils/createCustomPages';
77

88
import { ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID } from '../../constants';
9-
import { useEnvironment } from '../../contexts';
109
import { useRouter } from '../../router';
1110
import type { OrganizationProfileCtx } from '../../types';
11+
import { useEnvironment } from '../EnvironmentContext';
12+
import { useStatements, useSubscription } from './Plans';
1213

1314
type PagesType = {
1415
routes: NavbarRoute[];
@@ -24,6 +25,7 @@ export type OrganizationProfileContextType = OrganizationProfileCtx & {
2425
isGeneralPageRoot: boolean;
2526
isBillingPageRoot: boolean;
2627
isApiKeysPageRoot: boolean;
28+
shouldShowBilling: boolean;
2729
};
2830

2931
export const OrganizationProfileContext = createContext<OrganizationProfileCtx | null>(null);
@@ -40,9 +42,23 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType
4042

4143
const { componentName, customPages, ...ctx } = context;
4244

45+
const subscription = useSubscription();
46+
const statements = useStatements();
47+
48+
const hasNonFreeSubscription = subscription.data?.subscriptionItems.some(item => item.plan.hasBaseFee);
49+
50+
// TODO(@BILLING): Remove this when C1s can disable user billing seperately from the organization billing.
51+
const shouldShowBilling =
52+
// The instance has at lease one visible plan the C2 can choose
53+
environment.commerceSettings.billing.organization.hasPaidPlans ||
54+
// The C2 has a subscription, it can be active or past due, or scheduled for cancellation.
55+
hasNonFreeSubscription ||
56+
// The C2 had a subscription in the past
57+
Boolean(statements.data.length > 0);
58+
4359
const pages = useMemo(
44-
() => createOrganizationProfileCustomPages(customPages || [], clerk, environment),
45-
[customPages],
60+
() => createOrganizationProfileCustomPages(customPages || [], clerk, shouldShowBilling, environment),
61+
[customPages, shouldShowBilling],
4662
);
4763

4864
const navigateAfterLeaveOrganization = () =>
@@ -65,5 +81,6 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType
6581
isGeneralPageRoot,
6682
isBillingPageRoot,
6783
isApiKeysPageRoot,
84+
shouldShowBilling,
6885
};
6986
};

packages/clerk-js/src/ui/contexts/components/UserProfile.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { ParsedQueryString } from '../../router';
99
import { useRouter } from '../../router';
1010
import type { UserProfileCtx } from '../../types';
1111
import { useEnvironment } from '../EnvironmentContext';
12+
import { useStatements, useSubscription } from './Plans';
1213

1314
type PagesType = {
1415
routes: NavbarRoute[];
@@ -21,6 +22,7 @@ export type UserProfileContextType = UserProfileCtx & {
2122
authQueryString: string | null;
2223
pages: PagesType;
2324
shouldAllowIdentificationCreation: boolean;
25+
shouldShowBilling: boolean;
2426
};
2527

2628
export const UserProfileContext = createContext<UserProfileCtx | null>(null);
@@ -32,15 +34,29 @@ export const useUserProfileContext = (): UserProfileContextType => {
3234
const environment = useEnvironment();
3335
const { user } = useUser();
3436

37+
const subscription = useSubscription();
38+
const statements = useStatements();
39+
40+
const hasNonFreeSubscription = subscription.data?.subscriptionItems.some(item => item.plan.hasBaseFee);
41+
42+
// TODO(@BILLING): Remove this when C1s can disable user billing seperately from the organization billing.
43+
const shouldShowBilling =
44+
// The instance has at lease one visible plan the C2 can choose
45+
environment.commerceSettings.billing.user.hasPaidPlans ||
46+
// The C2 has a subscription, it can be active or past due, or scheduled for cancellation.
47+
hasNonFreeSubscription ||
48+
// The C2 had a subscription in the past
49+
Boolean(statements.data.length > 0);
50+
3551
if (!context || context.componentName !== 'UserProfile') {
3652
throw new Error('Clerk: useUserProfileContext called outside of the mounted UserProfile component.');
3753
}
3854

3955
const { componentName, customPages, ...ctx } = context;
4056

4157
const pages = useMemo(() => {
42-
return createUserProfileCustomPages(customPages || [], clerk, environment);
43-
}, [customPages]);
58+
return createUserProfileCustomPages(customPages || [], clerk, shouldShowBilling, environment);
59+
}, [customPages, shouldShowBilling]);
4460

4561
const shouldAllowIdentificationCreation = useMemo(() => {
4662
const { enterpriseSSO } = environment.userSettings;
@@ -62,5 +78,6 @@ export const useUserProfileContext = (): UserProfileContextType => {
6278
queryParams,
6379
authQueryString: '',
6480
shouldAllowIdentificationCreation,
81+
shouldShowBilling,
6582
};
6683
};

packages/clerk-js/src/ui/utils/createCustomPages.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type CreateCustomPagesParams = {
5757
export const createUserProfileCustomPages = (
5858
customPages: CustomPage[],
5959
clerk: LoadedClerk,
60+
shouldShowBilling: boolean,
6061
environment?: EnvironmentResource,
6162
) => {
6263
return createCustomPages(
@@ -67,13 +68,15 @@ export const createUserProfileCustomPages = (
6768
excludedPathsFromDuplicateWarning: [],
6869
},
6970
clerk,
71+
shouldShowBilling,
7072
environment,
7173
);
7274
};
7375

7476
export const createOrganizationProfileCustomPages = (
7577
customPages: CustomPage[],
7678
clerk: LoadedClerk,
79+
shouldShowBilling: boolean,
7780
environment?: EnvironmentResource,
7881
) => {
7982
return createCustomPages(
@@ -84,6 +87,7 @@ export const createOrganizationProfileCustomPages = (
8487
excludedPathsFromDuplicateWarning: [],
8588
},
8689
clerk,
90+
shouldShowBilling,
8791
environment,
8892
true,
8993
);
@@ -92,13 +96,14 @@ export const createOrganizationProfileCustomPages = (
9296
const createCustomPages = (
9397
{ customPages, getDefaultRoutes, setFirstPathToRoot, excludedPathsFromDuplicateWarning }: CreateCustomPagesParams,
9498
clerk: LoadedClerk,
99+
shouldShowBilling: boolean,
95100
environment?: EnvironmentResource,
96101
organization?: boolean,
97102
) => {
98103
const { INITIAL_ROUTES, pageToRootNavbarRouteMap, validReorderItemLabels } = getDefaultRoutes({
99104
commerce: organization
100-
? !disabledOrganizationBillingFeature(clerk, environment)
101-
: !disabledUserBillingFeature(clerk, environment),
105+
? !disabledOrganizationBillingFeature(clerk, environment) && shouldShowBilling
106+
: !disabledUserBillingFeature(clerk, environment) && shouldShowBilling,
102107
apiKeys: !disabledAPIKeysFeature(clerk, environment) && (organization ? canViewOrManageAPIKeys(clerk) : true),
103108
});
104109

0 commit comments

Comments
 (0)