diff --git a/.changeset/public-bags-stay.md b/.changeset/public-bags-stay.md new file mode 100644 index 00000000000..8ead721e2f4 --- /dev/null +++ b/.changeset/public-bags-stay.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': patch +'@clerk/types': patch +--- + +Adjust the cases in which the Billing item shows within the `UserProfile` and `OrgProfile` components diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 8d1f5450c43..7e2072600c7 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -94,8 +94,8 @@ import { createAllowedRedirectOrigins, createBeforeUnloadTracker, createPageLifecycle, + disabledAllBillingFeatures, disabledAPIKeysFeature, - disabledBillingFeature, disabledOrganizationsFeature, errorThrower, generateSignatureWithCoinbaseWallet, @@ -576,7 +576,7 @@ export class Clerk implements ClerkInterface { public __internal_openCheckout = (props?: __internal_CheckoutProps): void => { this.assertComponentsReady(this.#componentControls); - if (disabledBillingFeature(this, this.environment)) { + if (disabledAllBillingFeatures(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyCommerceComponent('Checkout'), { code: CANNOT_RENDER_BILLING_DISABLED_ERROR_CODE, @@ -605,7 +605,7 @@ export class Clerk implements ClerkInterface { public __internal_openPlanDetails = (props: __internal_PlanDetailsProps): void => { this.assertComponentsReady(this.#componentControls); - if (disabledBillingFeature(this, this.environment)) { + if (disabledAllBillingFeatures(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyCommerceComponent('PlanDetails'), { code: CANNOT_RENDER_BILLING_DISABLED_ERROR_CODE, @@ -1060,7 +1060,7 @@ export class Clerk implements ClerkInterface { public mountPricingTable = (node: HTMLDivElement, props?: PricingTableProps): void => { this.assertComponentsReady(this.#componentControls); - if (disabledBillingFeature(this, this.environment)) { + if (disabledAllBillingFeatures(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyCommerceComponent('PricingTable'), { code: CANNOT_RENDER_BILLING_DISABLED_ERROR_CODE, diff --git a/packages/clerk-js/src/core/resources/CommerceSettings.ts b/packages/clerk-js/src/core/resources/CommerceSettings.ts index 104ce856c43..26909c9b756 100644 --- a/packages/clerk-js/src/core/resources/CommerceSettings.ts +++ b/packages/clerk-js/src/core/resources/CommerceSettings.ts @@ -11,6 +11,14 @@ export class CommerceSettings extends BaseResource implements CommerceSettingsRe enabled: false, hasPaidUserPlans: false, hasPaidOrgPlans: false, + organization: { + enabled: false, + hasPaidPlans: false, + }, + user: { + enabled: false, + hasPaidPlans: false, + }, }; public constructor(data: CommerceSettingsJSON | CommerceSettingsJSONSnapshot | null = null) { @@ -27,6 +35,10 @@ export class CommerceSettings extends BaseResource implements CommerceSettingsRe this.billing.enabled = data.billing.enabled || false; this.billing.hasPaidUserPlans = data.billing.has_paid_user_plans || false; this.billing.hasPaidOrgPlans = data.billing.has_paid_org_plans || false; + this.billing.organization.enabled = data.billing.organization.enabled || false; + this.billing.organization.hasPaidPlans = data.billing.organization.has_paid_plans || false; + this.billing.user.enabled = data.billing.user.enabled || false; + this.billing.user.hasPaidPlans = data.billing.user.has_paid_plans || false; return this; } @@ -38,6 +50,14 @@ export class CommerceSettings extends BaseResource implements CommerceSettingsRe enabled: this.billing.enabled, has_paid_user_plans: this.billing.hasPaidUserPlans, has_paid_org_plans: this.billing.hasPaidOrgPlans, + organization: { + enabled: this.billing.organization.enabled, + has_paid_plans: this.billing.organization.hasPaidPlans, + }, + user: { + enabled: this.billing.user.enabled, + has_paid_plans: this.billing.user.hasPaidPlans, + }, }, } as unknown as CommerceSettingsJSONSnapshot; } diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx index 863a173afd3..f153a991b93 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx @@ -83,7 +83,7 @@ export const OrganizationProfileRoutes = () => { - {commerceSettings.billing.enabled && commerceSettings.billing.hasPaidOrgPlans && ( + {commerceSettings.billing.organization.enabled ? ( has({ permission: 'org:sys_billing:read' }) || has({ permission: 'org:sys_billing:manage' }) @@ -96,11 +96,13 @@ export const OrganizationProfileRoutes = () => { - - - - - + {commerceSettings.billing.organization.hasPaidPlans ? ( + + + + + + ) : null} @@ -114,7 +116,7 @@ export const OrganizationProfileRoutes = () => { - )} + ) : null} {apiKeysSettings.enabled && ( diff --git a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx index 9d6dcd6ee33..50210e65bfd 100644 --- a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx +++ b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx @@ -2,6 +2,7 @@ import { ProfileSection } from '@/ui/elements/Section'; import { useProtect } from '../../common'; import { + useEnvironment, usePlansContext, useSubscriberTypeContext, useSubscriberTypeLocalizationRoot, @@ -44,6 +45,7 @@ export function SubscriptionsList({ has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user', ); const { navigate } = useRouter(); + const { commerceSettings } = useEnvironment(); const sortedSubscriptions = subscriptions.sort((a, b) => { // alway put active subscriptions first @@ -188,22 +190,25 @@ export function SubscriptionsList({ )} - 0 ? arrowButtonText : arrowButtonEmptyText} - sx={[ - t => ({ - justifyContent: 'start', - height: t.sizes.$8, - }), - ]} - leftIcon={subscriptions.length > 0 ? ArrowsUpDown : Plus} - leftIconSx={t => ({ - width: t.sizes.$4, - height: t.sizes.$4, - })} - onClick={() => void navigate('plans')} - /> + {(commerceSettings.billing.user.hasPaidPlans && subscriberType === 'user') || + (commerceSettings.billing.organization.hasPaidPlans && subscriberType === 'org') ? ( + 0 ? arrowButtonText : arrowButtonEmptyText} + sx={[ + t => ({ + justifyContent: 'start', + height: t.sizes.$8, + }), + ]} + leftIcon={subscriptions.length > 0 ? ArrowsUpDown : Plus} + leftIconSx={t => ({ + width: t.sizes.$4, + height: t.sizes.$4, + })} + onClick={() => void navigate('plans')} + /> + ) : null} ); } diff --git a/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx b/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx index 8455fb688d1..76151e5b809 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx @@ -80,7 +80,7 @@ export const UserProfileRoutes = () => { - {commerceSettings.billing.enabled && commerceSettings.billing.hasPaidUserPlans && ( + {commerceSettings.billing.user.enabled ? ( @@ -88,11 +88,13 @@ export const UserProfileRoutes = () => { - - - - - + {commerceSettings.billing.user.hasPaidPlans ? ( + + + + + + ) : null} @@ -105,7 +107,7 @@ export const UserProfileRoutes = () => { - )} + ) : null} {apiKeysSettings.enabled && ( diff --git a/packages/clerk-js/src/ui/utils/createCustomPages.tsx b/packages/clerk-js/src/ui/utils/createCustomPages.tsx index a856048aeb7..ce9a45d0061 100644 --- a/packages/clerk-js/src/ui/utils/createCustomPages.tsx +++ b/packages/clerk-js/src/ui/utils/createCustomPages.tsx @@ -3,9 +3,8 @@ import type { CustomPage, EnvironmentResource, LoadedClerk } from '@clerk/types' import { canViewOrManageAPIKeys, disabledAPIKeysFeature, - disabledBillingFeature, - hasPaidOrgPlans, - hasPaidUserPlans, + disabledOrganizationBillingFeature, + disabledUserBillingFeature, isValidUrl, } from '../../utils'; import { ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID, USER_PROFILE_NAVBAR_ROUTE_ID } from '../constants'; @@ -97,9 +96,9 @@ const createCustomPages = ( organization?: boolean, ) => { const { INITIAL_ROUTES, pageToRootNavbarRouteMap, validReorderItemLabels } = getDefaultRoutes({ - commerce: - !disabledBillingFeature(clerk, environment) && - (organization ? hasPaidOrgPlans(clerk, environment) : hasPaidUserPlans(clerk, environment)), + commerce: organization + ? !disabledOrganizationBillingFeature(clerk, environment) + : !disabledUserBillingFeature(clerk, environment), apiKeys: !disabledAPIKeysFeature(clerk, environment) && (organization ? canViewOrManageAPIKeys(clerk) : true), }); diff --git a/packages/clerk-js/src/utils/componentGuards.ts b/packages/clerk-js/src/utils/componentGuards.ts index 03770b41936..baf23904b17 100644 --- a/packages/clerk-js/src/utils/componentGuards.ts +++ b/packages/clerk-js/src/utils/componentGuards.ts @@ -22,16 +22,24 @@ export const disabledOrganizationsFeature: ComponentGuard = (_, environment) => return !environment?.organizationSettings.enabled; }; -export const disabledBillingFeature: ComponentGuard = (_, environment) => { - return !environment?.commerceSettings.billing.enabled; +export const disabledUserBillingFeature: ComponentGuard = (_, environment) => { + return !environment?.commerceSettings.billing.user.enabled; +}; + +export const disabledOrganizationBillingFeature: ComponentGuard = (_, environment) => { + return !environment?.commerceSettings.billing.organization.enabled; +}; + +export const disabledAllBillingFeatures: ComponentGuard = (_, environment) => { + return disabledUserBillingFeature(_, environment) && disabledOrganizationBillingFeature(_, environment); }; export const hasPaidOrgPlans: ComponentGuard = (_, environment) => { - return environment?.commerceSettings.billing.hasPaidOrgPlans || false; + return environment?.commerceSettings.billing.organization.hasPaidPlans || false; }; export const hasPaidUserPlans: ComponentGuard = (_, environment) => { - return environment?.commerceSettings.billing.hasPaidUserPlans || false; + return environment?.commerceSettings.billing.user.hasPaidPlans || false; }; export const disabledAPIKeysFeature: ComponentGuard = (_, environment) => { diff --git a/packages/types/src/commerceSettings.ts b/packages/types/src/commerceSettings.ts index 3be9ede7370..d41bf633ff9 100644 --- a/packages/types/src/commerceSettings.ts +++ b/packages/types/src/commerceSettings.ts @@ -9,6 +9,14 @@ export interface CommerceSettingsJSON extends ClerkResourceJSON { stripe_publishable_key: string; has_paid_user_plans: boolean; has_paid_org_plans: boolean; + organization: { + enabled: boolean; + has_paid_plans: boolean; + }; + user: { + enabled: boolean; + has_paid_plans: boolean; + }; }; } @@ -18,6 +26,14 @@ export interface CommerceSettingsResource extends ClerkResource { stripePublishableKey: string; hasPaidUserPlans: boolean; hasPaidOrgPlans: boolean; + organization: { + enabled: boolean; + hasPaidPlans: boolean; + }; + user: { + enabled: boolean; + hasPaidPlans: boolean; + }; }; __internal_toSnapshot: () => CommerceSettingsJSONSnapshot;