From 234ce35ad8c9586a86ce544ef70094f095e97735 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 17 Jun 2025 15:28:18 +0300
Subject: [PATCH 01/34] wip

---
 packages/clerk-js/src/core/clerk.ts           |  37 ++
 .../core/modules/commerce/CommerceBilling.ts  |   9 +
 packages/clerk-js/src/ui/Components.tsx       |  28 +-
 .../src/ui/components/Plans/PlanDetails.tsx   | 295 ++--------
 .../src/ui/components/Plans/index.tsx         |   1 -
 .../ui/components/Plans/old_PlanDetails.tsx   | 522 +++++++++++++++++
 .../PricingTable/PricingTableDefault.tsx      |   5 +-
 .../components/SubscriptionDetails/index.tsx  | 524 ++++++++++++++++++
 .../src/ui/contexts/components/Plans.tsx      |   5 +-
 .../src/ui/elements/contexts/index.tsx        |   3 +-
 .../lazyModules/MountedPlanDetailDrawer.tsx   |   7 +-
 .../MountedSubscriptionDetailDrawer.tsx       |  42 ++
 .../clerk-js/src/ui/lazyModules/components.ts |   8 +-
 .../clerk-js/src/ui/lazyModules/drawers.tsx   |   6 +
 packages/react/src/isomorphicClerk.ts         |  43 +-
 packages/types/src/clerk.ts                   |  39 ++
 packages/types/src/commerce.ts                |   1 +
 17 files changed, 1305 insertions(+), 270 deletions(-)
 delete mode 100644 packages/clerk-js/src/ui/components/Plans/index.tsx
 create mode 100644 packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx
 create mode 100644 packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
 create mode 100644 packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx

diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index d67438da176..aa09557c6c1 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -15,6 +15,8 @@ import {
 import { addClerkPrefix, isAbsoluteUrl, stripScheme } from '@clerk/shared/url';
 import { allSettled, handleValueOrFn, noop } from '@clerk/shared/utils';
 import type {
+  __experimental_PlanDetailsProps,
+  __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
   __internal_ComponentNavigationContext,
   __internal_OAuthConsentProps,
@@ -609,6 +611,41 @@ export class Clerk implements ClerkInterface {
     void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('planDetails'));
   };
 
+  public __experimental_openPlanDetails = (props?: __experimental_PlanDetailsProps): void => {
+    this.assertComponentsReady(this.#componentControls);
+    if (disabledBillingFeature(this, this.environment)) {
+      if (this.#instanceType === 'development') {
+        throw new ClerkRuntimeError(warnings.cannotRenderAnyCommerceComponent('PlanDetails'), {
+          code: CANNOT_RENDER_BILLING_DISABLED_ERROR_CODE,
+        });
+      }
+      return;
+    }
+    void this.#componentControls
+      .ensureMounted({ preloadHint: 'PlanDetails' })
+      .then(controls => controls.openDrawer('planDetails', props || {}));
+
+    this.telemetry?.record(eventPrebuiltComponentOpened(`PlanDetails`, props));
+  };
+
+  public __experimental_closePlanDetails = (): void => {
+    this.assertComponentsReady(this.#componentControls);
+    void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('planDetails'));
+  };
+
+  public __experimental_openSubscriptionDetails = (props?: __experimental_SubscriptionDetailsProps): void => {
+    this.assertComponentsReady(this.#componentControls);
+    console.log('__experimental_openSubscriptionDetails', props);
+    void this.#componentControls
+      .ensureMounted({ preloadHint: 'SubscriptionDetails' })
+      .then(controls => controls.openDrawer('subscriptionDetails', props || {}));
+  };
+
+  public __experimental_closeSubscriptionDetails = (): void => {
+    this.assertComponentsReady(this.#componentControls);
+    void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('subscriptionDetails'));
+  };
+
   public __internal_openReverification = (props?: __internal_UserVerificationModalProps): void => {
     this.assertComponentsReady(this.#componentControls);
     if (noUserExists(this)) {
diff --git a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
index b28e6454137..cfa90076df7 100644
--- a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
+++ b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
@@ -4,6 +4,7 @@ import type {
   CommerceCheckoutJSON,
   CommercePaymentJSON,
   CommercePaymentResource,
+  CommercePlanJSON,
   CommercePlanResource,
   CommerceProductJSON,
   CommerceStatementJSON,
@@ -39,6 +40,14 @@ export class CommerceBilling implements CommerceBillingNamespace {
     return defaultProduct?.plans.map(plan => new CommercePlan(plan)) || [];
   };
 
+  getPlan = async (params: { id: string }): Promise<CommercePlanResource> => {
+    const plan = (await BaseResource._fetch({
+      path: `/commerce/plans/${params.id}`,
+      method: 'GET',
+    })) as unknown as CommercePlanJSON;
+    return new CommercePlan(plan);
+  };
+
   getSubscriptions = async (
     params: GetSubscriptionsParams,
   ): Promise<ClerkPaginatedResponse<CommerceSubscriptionResource>> => {
diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx
index 5d3cca0b7dd..ee5d4dbd96d 100644
--- a/packages/clerk-js/src/ui/Components.tsx
+++ b/packages/clerk-js/src/ui/Components.tsx
@@ -1,5 +1,7 @@
 import { createDeferredPromise } from '@clerk/shared/utils';
 import type {
+  __experimental_PlanDetailsProps,
+  __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
   __internal_PlanDetailsProps,
   __internal_UserVerificationProps,
@@ -37,6 +39,7 @@ import {
   WaitlistModal,
 } from './lazyModules/components';
 import { MountedCheckoutDrawer, MountedPlanDetailDrawer } from './lazyModules/drawers';
+import { MountedSubscriptionDetailDrawer } from './lazyModules/MountedSubscriptionDetailDrawer';
 import {
   LazyComponentRenderer,
   LazyImpersonationFabProvider,
@@ -106,16 +109,18 @@ export type ComponentControls = {
       notify?: boolean;
     },
   ) => void;
-  openDrawer: <T extends 'checkout' | 'planDetails'>(
+  openDrawer: <T extends 'checkout' | 'planDetails' | 'subscriptionDetails'>(
     drawer: T,
     props: T extends 'checkout'
       ? __internal_CheckoutProps
       : T extends 'planDetails'
         ? __internal_PlanDetailsProps
-        : never,
+        : T extends 'subscriptionDetails'
+          ? __internal_PlanDetailsProps
+          : never,
   ) => void;
   closeDrawer: (
-    drawer: 'checkout' | 'planDetails',
+    drawer: 'checkout' | 'planDetails' | 'subscriptionDetails',
     options?: {
       notify?: boolean;
     },
@@ -158,7 +163,11 @@ interface ComponentsState {
   };
   planDetailsDrawer: {
     open: false;
-    props: null | __internal_PlanDetailsProps;
+    props: null | __experimental_PlanDetailsProps;
+  };
+  subscriptionDetailsDrawer: {
+    open: false;
+    props: null | __experimental_SubscriptionDetailsProps;
   };
   nodes: Map<HTMLDivElement, HtmlNodeOptions>;
   impersonationFab: boolean;
@@ -249,6 +258,10 @@ const Components = (props: ComponentsProps) => {
       open: false,
       props: null,
     },
+    subscriptionDetailsDrawer: {
+      open: false,
+      props: null,
+    },
     nodes: new Map(),
     impersonationFab: false,
   });
@@ -265,6 +278,7 @@ const Components = (props: ComponentsProps) => {
     blankCaptchaModal,
     checkoutDrawer,
     planDetailsDrawer,
+    subscriptionDetailsDrawer,
     nodes,
   } = state;
 
@@ -588,6 +602,12 @@ const Components = (props: ComponentsProps) => {
           onOpenChange={() => componentsControls.closeDrawer('planDetails')}
         />
 
+        <MountedSubscriptionDetailDrawer
+          appearance={state.appearance}
+          subscriptionDetailsDrawer={subscriptionDetailsDrawer}
+          onOpenChange={() => componentsControls.closeDrawer('subscriptionDetails')}
+        />
+
         {state.impersonationFab && (
           <LazyImpersonationFabProvider globalAppearance={state.appearance}>
             <ImpersonationFab />
diff --git a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
index 4bfb535d1df..7c30cd7f980 100644
--- a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
+++ b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
@@ -1,121 +1,69 @@
-import { useClerk, useOrganization } from '@clerk/shared/react';
+import { useClerk } from '@clerk/shared/react';
 import type {
-  __internal_PlanDetailsProps,
-  ClerkAPIError,
-  ClerkRuntimeError,
+  __experimental_PlanDetailsProps,
   CommercePlanResource,
   CommerceSubscriptionPlanPeriod,
-  CommerceSubscriptionResource,
 } from '@clerk/types';
 import * as React from 'react';
 import { useMemo, useState } from 'react';
+import useSWR from 'swr';
 
-import { Alert } from '@/ui/elements/Alert';
 import { Avatar } from '@/ui/elements/Avatar';
-import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
+import { Drawer } from '@/ui/elements/Drawer';
 import { Switch } from '@/ui/elements/Switch';
 
-import { useProtect } from '../../common';
-import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
-import { Badge, Box, Button, Col, descriptors, Flex, Heading, localizationKeys, Span, Text } from '../../customizables';
-import { handleError } from '../../utils';
+import { SubscriberTypeContext } from '../../contexts';
+import { Box, Col, descriptors, Flex, Heading, localizationKeys, Span, Spinner, Text } from '../../customizables';
 
-export const PlanDetails = (props: __internal_PlanDetailsProps) => {
+export const PlanDetails = (props: __experimental_PlanDetailsProps) => {
   return (
-    <SubscriberTypeContext.Provider value={props.subscriberType || 'user'}>
-      <Drawer.Content>
-        <PlanDetailsInternal {...props} />
-      </Drawer.Content>
-    </SubscriberTypeContext.Provider>
+    <Drawer.Content>
+      <PlanDetailsInternal {...props} />
+    </Drawer.Content>
   );
 };
 
 const PlanDetailsInternal = ({
-  plan,
-  onSubscriptionCancel,
-  portalRoot,
+  planId,
+  plan: initialPlan,
   initialPlanPeriod = 'month',
-}: __internal_PlanDetailsProps) => {
+}: __experimental_PlanDetailsProps) => {
   const clerk = useClerk();
-  const { organization } = useOrganization();
-  const [showConfirmation, setShowConfirmation] = useState(false);
-  const [isSubmitting, setIsSubmitting] = useState(false);
-  const [cancelError, setCancelError] = useState<ClerkRuntimeError | ClerkAPIError | string | undefined>();
   const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(initialPlanPeriod);
 
-  const { setIsOpen } = useDrawerContext();
-  const {
-    activeOrUpcomingSubscriptionBasedOnPlanPeriod,
-    revalidateAll,
-    buttonPropsForPlan,
-    isDefaultPlanImplicitlyActiveOrUpcoming,
-  } = usePlansContext();
-  const subscriberType = useSubscriberTypeContext();
-  const canManageBilling = useProtect(
-    has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
+  const { data: plan, isLoading } = useSWR(
+    planId || initialPlan ? { type: 'plan', id: planId || initialPlan?.id } : null,
+    // @ts-expect-error
+    () => clerk.billing.getPlan({ id: planId || initialPlan?.id }),
+    {
+      fallbackData: initialPlan,
+    },
   );
 
+  if (isLoading && !initialPlan) {
+    return (
+      <Flex
+        justify='center'
+        align='center'
+        sx={{
+          height: '100%',
+        }}
+      >
+        <Spinner />
+      </Flex>
+    );
+  }
+
   if (!plan) {
     return null;
   }
 
-  const subscription = activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod);
-
-  const handleClose = () => {
-    if (setIsOpen) {
-      setIsOpen(false);
-    }
-  };
-
   const features = plan.features;
   const hasFeatures = features.length > 0;
-  const cancelSubscription = async () => {
-    if (!subscription) {
-      return;
-    }
-
-    setCancelError(undefined);
-    setIsSubmitting(true);
-
-    await subscription
-      .cancel({ orgId: subscriberType === 'org' ? organization?.id : undefined })
-      .then(() => {
-        setIsSubmitting(false);
-        onSubscriptionCancel?.();
-        handleClose();
-      })
-      .catch(error => {
-        handleError(error, [], setCancelError);
-        setIsSubmitting(false);
-      });
-  };
-
-  type Open__internal_CheckoutProps = {
-    planPeriod?: CommerceSubscriptionPlanPeriod;
-  };
-
-  const openCheckout = (props?: Open__internal_CheckoutProps) => {
-    handleClose();
-
-    // if the plan doesn't support annual, use monthly
-    let _planPeriod = props?.planPeriod || planPeriod;
-    if (_planPeriod === 'annual' && plan.annualMonthlyAmount === 0) {
-      _planPeriod = 'month';
-    }
-
-    clerk.__internal_openCheckout({
-      planId: plan.id,
-      planPeriod: _planPeriod,
-      subscriberType: subscriberType,
-      onSubscriptionComplete: () => {
-        void revalidateAll();
-      },
-      portalRoot,
-    });
-  };
 
   return (
-    <>
+    <SubscriberTypeContext.Provider value={plan.payerType[0] as 'user' | 'org'}>
+      {/* TODO: type assertion is a hack, make FAPI stricter */}
       <Drawer.Header
         sx={t =>
           !hasFeatures
@@ -129,7 +77,6 @@ const PlanDetailsInternal = ({
       >
         <Header
           plan={plan}
-          subscription={subscription}
           planPeriod={planPeriod}
           setPlanPeriod={setPlanPeriod}
           closeSlot={<Drawer.Close />}
@@ -207,127 +154,17 @@ const PlanDetailsInternal = ({
         </Drawer.Body>
       ) : null}
 
-      {(!plan.isDefault && !isDefaultPlanImplicitlyActiveOrUpcoming) || !subscription ? (
+      {/* {!plan.isDefault && !isDefaultPlanImplicitlyActiveOrUpcoming ? (
         <Drawer.Footer>
-          {subscription ? (
-            subscription.canceledAt ? (
-              <Button
-                block
-                textVariant='buttonLarge'
-                {...buttonPropsForPlan({ plan })}
-                onClick={() => openCheckout()}
-              />
-            ) : (
-              <Col gap={4}>
-                {!!subscription &&
-                subscription.planPeriod === 'month' &&
-                plan.annualMonthlyAmount > 0 &&
-                planPeriod === 'annual' ? (
-                  <Button
-                    block
-                    variant='bordered'
-                    colorScheme='secondary'
-                    textVariant='buttonLarge'
-                    isDisabled={!canManageBilling}
-                    onClick={() => openCheckout({ planPeriod: 'annual' })}
-                    localizationKey={localizationKeys('commerce.switchToAnnual')}
-                  />
-                ) : null}
-                {!!subscription && subscription.planPeriod === 'annual' && planPeriod === 'month' ? (
-                  <Button
-                    block
-                    variant='bordered'
-                    colorScheme='secondary'
-                    textVariant='buttonLarge'
-                    isDisabled={!canManageBilling}
-                    onClick={() => openCheckout({ planPeriod: 'month' })}
-                    localizationKey={localizationKeys('commerce.switchToMonthly')}
-                  />
-                ) : null}
-                <Button
-                  block
-                  variant='bordered'
-                  colorScheme='danger'
-                  textVariant='buttonLarge'
-                  isDisabled={!canManageBilling}
-                  onClick={() => setShowConfirmation(true)}
-                  localizationKey={localizationKeys('commerce.cancelSubscription')}
-                />
-              </Col>
-            )
-          ) : (
-            <Button
-              block
-              textVariant='buttonLarge'
-              {...buttonPropsForPlan({ plan })}
-              onClick={() => openCheckout()}
-            />
-          )}
-        </Drawer.Footer>
-      ) : null}
-
-      {subscription ? (
-        <Drawer.Confirmation
-          open={showConfirmation}
-          onOpenChange={setShowConfirmation}
-          actionsSlot={
-            <>
-              {!isSubmitting && (
-                <Button
-                  variant='ghost'
-                  size='sm'
-                  textVariant='buttonLarge'
-                  isDisabled={!canManageBilling}
-                  onClick={() => {
-                    setCancelError(undefined);
-                    setShowConfirmation(false);
-                  }}
-                  localizationKey={localizationKeys('commerce.keepSubscription')}
-                />
-              )}
-              <Button
-                variant='solid'
-                colorScheme='danger'
-                size='sm'
-                textVariant='buttonLarge'
-                isLoading={isSubmitting}
-                isDisabled={!canManageBilling}
-                onClick={() => {
-                  setCancelError(undefined);
-                  setShowConfirmation(false);
-                  void cancelSubscription();
-                }}
-                localizationKey={localizationKeys('commerce.cancelSubscription')}
-              />
-            </>
-          }
-        >
-          <Heading
-            elementDescriptor={descriptors.drawerConfirmationTitle}
-            as='h2'
-            textVariant='h3'
-            localizationKey={localizationKeys('commerce.cancelSubscriptionTitle', {
-              plan: `${subscription.status === 'upcoming' ? 'upcoming ' : ''}${subscription.plan.name}`,
-            })}
-          />
-          <Text
-            elementDescriptor={descriptors.drawerConfirmationDescription}
-            colorScheme='secondary'
-            localizationKey={
-              subscription.status === 'upcoming'
-                ? localizationKeys('commerce.cancelSubscriptionNoCharge')
-                : localizationKeys('commerce.cancelSubscriptionAccessUntil', {
-                    plan: subscription.plan.name,
-                    date: subscription.periodEnd,
-                  })
-            }
+          <Button
+            block
+            textVariant='buttonLarge'
+            {...buttonPropsForPlan({ plan })}
+            onClick={() => openCheckout()}
           />
-          {cancelError && (
-            <Alert colorScheme='danger'>{typeof cancelError === 'string' ? cancelError : cancelError.message}</Alert>
-          )}
-        </Drawer.Confirmation>
-      ) : null}
-    </>
+        </Drawer.Footer>
+      ) : null} */}
+    </SubscriberTypeContext.Provider>
   );
 };
 
@@ -337,21 +174,13 @@ const PlanDetailsInternal = ({
 
 interface HeaderProps {
   plan: CommercePlanResource;
-  subscription?: CommerceSubscriptionResource;
   planPeriod: CommerceSubscriptionPlanPeriod;
   setPlanPeriod: (val: CommerceSubscriptionPlanPeriod) => void;
   closeSlot?: React.ReactNode;
 }
 
 const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
-  const { plan, subscription, closeSlot, planPeriod, setPlanPeriod } = props;
-
-  const { captionForSubscription, isDefaultPlanImplicitlyActiveOrUpcoming } = usePlansContext();
-  const { data: subscriptions } = useSubscriptions();
-
-  const isImplicitlyActiveOrUpcoming = isDefaultPlanImplicitlyActiveOrUpcoming && plan.isDefault;
-
-  const showBadge = !!subscription;
+  const { plan, closeSlot, planPeriod, setPlanPeriod } = props;
 
   const getPlanFee = useMemo(() => {
     if (plan.annualMonthlyAmount <= 0) {
@@ -386,38 +215,6 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
         gap={3}
         elementDescriptor={descriptors.planDetailBadgeAvatarTitleDescriptionContainer}
       >
-        {showBadge ? (
-          <Flex
-            align='center'
-            gap={3}
-            elementDescriptor={descriptors.planDetailBadgeContainer}
-            sx={t => ({
-              paddingInlineEnd: t.space.$10,
-            })}
-          >
-            {subscription?.status === 'active' || (isImplicitlyActiveOrUpcoming && subscriptions.length === 0) ? (
-              <Badge
-                elementDescriptor={descriptors.planDetailBadge}
-                localizationKey={localizationKeys('badge__activePlan')}
-                colorScheme={'secondary'}
-              />
-            ) : (
-              <Badge
-                elementDescriptor={descriptors.planDetailBadge}
-                localizationKey={localizationKeys('badge__upcomingPlan')}
-                colorScheme={'primary'}
-              />
-            )}
-            {!!subscription && (
-              <Text
-                elementDescriptor={descriptors.planDetailCaption}
-                variant={'caption'}
-                localizationKey={captionForSubscription(subscription)}
-                colorScheme='secondary'
-              />
-            )}
-          </Flex>
-        ) : null}
         {plan.avatarUrl ? (
           <Avatar
             boxElementDescriptor={descriptors.planDetailAvatar}
diff --git a/packages/clerk-js/src/ui/components/Plans/index.tsx b/packages/clerk-js/src/ui/components/Plans/index.tsx
deleted file mode 100644
index 612088fd9aa..00000000000
--- a/packages/clerk-js/src/ui/components/Plans/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export * from './PlanDetails';
diff --git a/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx b/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx
new file mode 100644
index 00000000000..4bfb535d1df
--- /dev/null
+++ b/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx
@@ -0,0 +1,522 @@
+import { useClerk, useOrganization } from '@clerk/shared/react';
+import type {
+  __internal_PlanDetailsProps,
+  ClerkAPIError,
+  ClerkRuntimeError,
+  CommercePlanResource,
+  CommerceSubscriptionPlanPeriod,
+  CommerceSubscriptionResource,
+} from '@clerk/types';
+import * as React from 'react';
+import { useMemo, useState } from 'react';
+
+import { Alert } from '@/ui/elements/Alert';
+import { Avatar } from '@/ui/elements/Avatar';
+import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
+import { Switch } from '@/ui/elements/Switch';
+
+import { useProtect } from '../../common';
+import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
+import { Badge, Box, Button, Col, descriptors, Flex, Heading, localizationKeys, Span, Text } from '../../customizables';
+import { handleError } from '../../utils';
+
+export const PlanDetails = (props: __internal_PlanDetailsProps) => {
+  return (
+    <SubscriberTypeContext.Provider value={props.subscriberType || 'user'}>
+      <Drawer.Content>
+        <PlanDetailsInternal {...props} />
+      </Drawer.Content>
+    </SubscriberTypeContext.Provider>
+  );
+};
+
+const PlanDetailsInternal = ({
+  plan,
+  onSubscriptionCancel,
+  portalRoot,
+  initialPlanPeriod = 'month',
+}: __internal_PlanDetailsProps) => {
+  const clerk = useClerk();
+  const { organization } = useOrganization();
+  const [showConfirmation, setShowConfirmation] = useState(false);
+  const [isSubmitting, setIsSubmitting] = useState(false);
+  const [cancelError, setCancelError] = useState<ClerkRuntimeError | ClerkAPIError | string | undefined>();
+  const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(initialPlanPeriod);
+
+  const { setIsOpen } = useDrawerContext();
+  const {
+    activeOrUpcomingSubscriptionBasedOnPlanPeriod,
+    revalidateAll,
+    buttonPropsForPlan,
+    isDefaultPlanImplicitlyActiveOrUpcoming,
+  } = usePlansContext();
+  const subscriberType = useSubscriberTypeContext();
+  const canManageBilling = useProtect(
+    has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
+  );
+
+  if (!plan) {
+    return null;
+  }
+
+  const subscription = activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod);
+
+  const handleClose = () => {
+    if (setIsOpen) {
+      setIsOpen(false);
+    }
+  };
+
+  const features = plan.features;
+  const hasFeatures = features.length > 0;
+  const cancelSubscription = async () => {
+    if (!subscription) {
+      return;
+    }
+
+    setCancelError(undefined);
+    setIsSubmitting(true);
+
+    await subscription
+      .cancel({ orgId: subscriberType === 'org' ? organization?.id : undefined })
+      .then(() => {
+        setIsSubmitting(false);
+        onSubscriptionCancel?.();
+        handleClose();
+      })
+      .catch(error => {
+        handleError(error, [], setCancelError);
+        setIsSubmitting(false);
+      });
+  };
+
+  type Open__internal_CheckoutProps = {
+    planPeriod?: CommerceSubscriptionPlanPeriod;
+  };
+
+  const openCheckout = (props?: Open__internal_CheckoutProps) => {
+    handleClose();
+
+    // if the plan doesn't support annual, use monthly
+    let _planPeriod = props?.planPeriod || planPeriod;
+    if (_planPeriod === 'annual' && plan.annualMonthlyAmount === 0) {
+      _planPeriod = 'month';
+    }
+
+    clerk.__internal_openCheckout({
+      planId: plan.id,
+      planPeriod: _planPeriod,
+      subscriberType: subscriberType,
+      onSubscriptionComplete: () => {
+        void revalidateAll();
+      },
+      portalRoot,
+    });
+  };
+
+  return (
+    <>
+      <Drawer.Header
+        sx={t =>
+          !hasFeatures
+            ? {
+                flex: 1,
+                borderBottomWidth: 0,
+                background: t.colors.$colorBackground,
+              }
+            : null
+        }
+      >
+        <Header
+          plan={plan}
+          subscription={subscription}
+          planPeriod={planPeriod}
+          setPlanPeriod={setPlanPeriod}
+          closeSlot={<Drawer.Close />}
+        />
+      </Drawer.Header>
+
+      {hasFeatures ? (
+        <Drawer.Body>
+          <Text
+            elementDescriptor={descriptors.planDetailCaption}
+            variant={'caption'}
+            localizationKey={localizationKeys('commerce.availableFeatures')}
+            colorScheme='secondary'
+            sx={t => ({
+              padding: t.space.$4,
+              paddingBottom: 0,
+            })}
+          />
+          <Box
+            elementDescriptor={descriptors.planDetailFeaturesList}
+            as='ul'
+            role='list'
+            sx={t => ({
+              display: 'grid',
+              rowGap: t.space.$6,
+              padding: t.space.$4,
+              margin: 0,
+            })}
+          >
+            {features.map(feature => (
+              <Box
+                key={feature.id}
+                elementDescriptor={descriptors.planDetailFeaturesListItem}
+                as='li'
+                sx={t => ({
+                  display: 'flex',
+                  alignItems: 'baseline',
+                  gap: t.space.$3,
+                })}
+              >
+                {feature.avatarUrl ? (
+                  <Avatar
+                    size={_ => 24}
+                    title={feature.name}
+                    initials={feature.name[0]}
+                    rounded={false}
+                    imageUrl={feature.avatarUrl}
+                  />
+                ) : null}
+                <Span elementDescriptor={descriptors.planDetailFeaturesListItemContent}>
+                  <Text
+                    elementDescriptor={descriptors.planDetailFeaturesListItemTitle}
+                    colorScheme='body'
+                    sx={t => ({
+                      fontWeight: t.fontWeights.$medium,
+                    })}
+                  >
+                    {feature.name}
+                  </Text>
+                  {feature.description ? (
+                    <Text
+                      elementDescriptor={descriptors.planDetailFeaturesListItemDescription}
+                      colorScheme='secondary'
+                      sx={t => ({
+                        marginBlockStart: t.space.$0x25,
+                      })}
+                    >
+                      {feature.description}
+                    </Text>
+                  ) : null}
+                </Span>
+              </Box>
+            ))}
+          </Box>
+        </Drawer.Body>
+      ) : null}
+
+      {(!plan.isDefault && !isDefaultPlanImplicitlyActiveOrUpcoming) || !subscription ? (
+        <Drawer.Footer>
+          {subscription ? (
+            subscription.canceledAt ? (
+              <Button
+                block
+                textVariant='buttonLarge'
+                {...buttonPropsForPlan({ plan })}
+                onClick={() => openCheckout()}
+              />
+            ) : (
+              <Col gap={4}>
+                {!!subscription &&
+                subscription.planPeriod === 'month' &&
+                plan.annualMonthlyAmount > 0 &&
+                planPeriod === 'annual' ? (
+                  <Button
+                    block
+                    variant='bordered'
+                    colorScheme='secondary'
+                    textVariant='buttonLarge'
+                    isDisabled={!canManageBilling}
+                    onClick={() => openCheckout({ planPeriod: 'annual' })}
+                    localizationKey={localizationKeys('commerce.switchToAnnual')}
+                  />
+                ) : null}
+                {!!subscription && subscription.planPeriod === 'annual' && planPeriod === 'month' ? (
+                  <Button
+                    block
+                    variant='bordered'
+                    colorScheme='secondary'
+                    textVariant='buttonLarge'
+                    isDisabled={!canManageBilling}
+                    onClick={() => openCheckout({ planPeriod: 'month' })}
+                    localizationKey={localizationKeys('commerce.switchToMonthly')}
+                  />
+                ) : null}
+                <Button
+                  block
+                  variant='bordered'
+                  colorScheme='danger'
+                  textVariant='buttonLarge'
+                  isDisabled={!canManageBilling}
+                  onClick={() => setShowConfirmation(true)}
+                  localizationKey={localizationKeys('commerce.cancelSubscription')}
+                />
+              </Col>
+            )
+          ) : (
+            <Button
+              block
+              textVariant='buttonLarge'
+              {...buttonPropsForPlan({ plan })}
+              onClick={() => openCheckout()}
+            />
+          )}
+        </Drawer.Footer>
+      ) : null}
+
+      {subscription ? (
+        <Drawer.Confirmation
+          open={showConfirmation}
+          onOpenChange={setShowConfirmation}
+          actionsSlot={
+            <>
+              {!isSubmitting && (
+                <Button
+                  variant='ghost'
+                  size='sm'
+                  textVariant='buttonLarge'
+                  isDisabled={!canManageBilling}
+                  onClick={() => {
+                    setCancelError(undefined);
+                    setShowConfirmation(false);
+                  }}
+                  localizationKey={localizationKeys('commerce.keepSubscription')}
+                />
+              )}
+              <Button
+                variant='solid'
+                colorScheme='danger'
+                size='sm'
+                textVariant='buttonLarge'
+                isLoading={isSubmitting}
+                isDisabled={!canManageBilling}
+                onClick={() => {
+                  setCancelError(undefined);
+                  setShowConfirmation(false);
+                  void cancelSubscription();
+                }}
+                localizationKey={localizationKeys('commerce.cancelSubscription')}
+              />
+            </>
+          }
+        >
+          <Heading
+            elementDescriptor={descriptors.drawerConfirmationTitle}
+            as='h2'
+            textVariant='h3'
+            localizationKey={localizationKeys('commerce.cancelSubscriptionTitle', {
+              plan: `${subscription.status === 'upcoming' ? 'upcoming ' : ''}${subscription.plan.name}`,
+            })}
+          />
+          <Text
+            elementDescriptor={descriptors.drawerConfirmationDescription}
+            colorScheme='secondary'
+            localizationKey={
+              subscription.status === 'upcoming'
+                ? localizationKeys('commerce.cancelSubscriptionNoCharge')
+                : localizationKeys('commerce.cancelSubscriptionAccessUntil', {
+                    plan: subscription.plan.name,
+                    date: subscription.periodEnd,
+                  })
+            }
+          />
+          {cancelError && (
+            <Alert colorScheme='danger'>{typeof cancelError === 'string' ? cancelError : cancelError.message}</Alert>
+          )}
+        </Drawer.Confirmation>
+      ) : null}
+    </>
+  );
+};
+
+/* -------------------------------------------------------------------------------------------------
+ * Header
+ * -----------------------------------------------------------------------------------------------*/
+
+interface HeaderProps {
+  plan: CommercePlanResource;
+  subscription?: CommerceSubscriptionResource;
+  planPeriod: CommerceSubscriptionPlanPeriod;
+  setPlanPeriod: (val: CommerceSubscriptionPlanPeriod) => void;
+  closeSlot?: React.ReactNode;
+}
+
+const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
+  const { plan, subscription, closeSlot, planPeriod, setPlanPeriod } = props;
+
+  const { captionForSubscription, isDefaultPlanImplicitlyActiveOrUpcoming } = usePlansContext();
+  const { data: subscriptions } = useSubscriptions();
+
+  const isImplicitlyActiveOrUpcoming = isDefaultPlanImplicitlyActiveOrUpcoming && plan.isDefault;
+
+  const showBadge = !!subscription;
+
+  const getPlanFee = useMemo(() => {
+    if (plan.annualMonthlyAmount <= 0) {
+      return plan.amountFormatted;
+    }
+    return planPeriod === 'annual' ? plan.annualMonthlyAmountFormatted : plan.amountFormatted;
+  }, [plan, planPeriod]);
+
+  return (
+    <Box
+      ref={ref}
+      elementDescriptor={descriptors.planDetailHeader}
+      sx={t => ({
+        width: '100%',
+        padding: t.space.$4,
+        position: 'relative',
+      })}
+    >
+      {closeSlot ? (
+        <Box
+          sx={t => ({
+            position: 'absolute',
+            top: t.space.$2,
+            insetInlineEnd: t.space.$2,
+          })}
+        >
+          {closeSlot}
+        </Box>
+      ) : null}
+
+      <Col
+        gap={3}
+        elementDescriptor={descriptors.planDetailBadgeAvatarTitleDescriptionContainer}
+      >
+        {showBadge ? (
+          <Flex
+            align='center'
+            gap={3}
+            elementDescriptor={descriptors.planDetailBadgeContainer}
+            sx={t => ({
+              paddingInlineEnd: t.space.$10,
+            })}
+          >
+            {subscription?.status === 'active' || (isImplicitlyActiveOrUpcoming && subscriptions.length === 0) ? (
+              <Badge
+                elementDescriptor={descriptors.planDetailBadge}
+                localizationKey={localizationKeys('badge__activePlan')}
+                colorScheme={'secondary'}
+              />
+            ) : (
+              <Badge
+                elementDescriptor={descriptors.planDetailBadge}
+                localizationKey={localizationKeys('badge__upcomingPlan')}
+                colorScheme={'primary'}
+              />
+            )}
+            {!!subscription && (
+              <Text
+                elementDescriptor={descriptors.planDetailCaption}
+                variant={'caption'}
+                localizationKey={captionForSubscription(subscription)}
+                colorScheme='secondary'
+              />
+            )}
+          </Flex>
+        ) : null}
+        {plan.avatarUrl ? (
+          <Avatar
+            boxElementDescriptor={descriptors.planDetailAvatar}
+            size={_ => 40}
+            title={plan.name}
+            initials={plan.name[0]}
+            rounded={false}
+            imageUrl={plan.avatarUrl}
+            sx={t => ({
+              marginBlockEnd: t.space.$3,
+            })}
+          />
+        ) : null}
+        <Col
+          gap={1}
+          elementDescriptor={descriptors.planDetailTitleDescriptionContainer}
+        >
+          <Heading
+            elementDescriptor={descriptors.planDetailTitle}
+            as='h2'
+            textVariant='h2'
+          >
+            {plan.name}
+          </Heading>
+          {plan.description ? (
+            <Text
+              elementDescriptor={descriptors.planDetailDescription}
+              variant='subtitle'
+              colorScheme='secondary'
+            >
+              {plan.description}
+            </Text>
+          ) : null}
+        </Col>
+      </Col>
+
+      <Flex
+        elementDescriptor={descriptors.planDetailFeeContainer}
+        align='center'
+        wrap='wrap'
+        sx={t => ({
+          marginTop: t.space.$3,
+          columnGap: t.space.$1x5,
+        })}
+      >
+        <>
+          <Text
+            elementDescriptor={descriptors.planDetailFee}
+            variant='h1'
+            colorScheme='body'
+          >
+            {plan.currencySymbol}
+            {getPlanFee}
+          </Text>
+          <Text
+            elementDescriptor={descriptors.planDetailFeePeriod}
+            variant='caption'
+            colorScheme='secondary'
+            sx={t => ({
+              textTransform: 'lowercase',
+              ':before': {
+                content: '"/"',
+                marginInlineEnd: t.space.$1,
+              },
+            })}
+            localizationKey={localizationKeys('commerce.month')}
+          />
+        </>
+      </Flex>
+
+      {plan.annualMonthlyAmount > 0 ? (
+        <Box
+          elementDescriptor={descriptors.planDetailPeriodToggle}
+          sx={t => ({
+            display: 'flex',
+            marginTop: t.space.$3,
+          })}
+        >
+          <Switch
+            isChecked={planPeriod === 'annual'}
+            onChange={(checked: boolean) => setPlanPeriod(checked ? 'annual' : 'month')}
+            label={localizationKeys('commerce.billedAnnually')}
+          />
+        </Box>
+      ) : (
+        <Text
+          elementDescriptor={descriptors.pricingTableCardFeePeriodNotice}
+          variant='caption'
+          colorScheme='secondary'
+          localizationKey={
+            plan.isDefault ? localizationKeys('commerce.alwaysFree') : localizationKeys('commerce.billedMonthlyOnly')
+          }
+          sx={t => ({
+            justifySelf: 'flex-start',
+            alignSelf: 'center',
+            marginTop: t.space.$3,
+          })}
+        />
+      )}
+    </Box>
+  );
+});
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
index fc47b2de578..792c3eb0cdf 100644
--- a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
+++ b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
@@ -116,9 +116,10 @@ function Card(props: CardProps) {
   const showPlanDetails = (event?: React.MouseEvent<HTMLElement>) => {
     const portalRoot = getClosestProfileScrollBox(mode, event);
 
-    clerk.__internal_openPlanDetails({
+    clerk.__experimental_openPlanDetails({
       plan,
-      subscriberType,
+      // planId: plan.id,
+      // subscriberType,
       initialPlanPeriod: planPeriod,
       portalRoot,
     });
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
new file mode 100644
index 00000000000..66cdc9a8d7b
--- /dev/null
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -0,0 +1,524 @@
+import { useClerk, useOrganization } from '@clerk/shared/react';
+import type {
+  __experimental_SubscriptionDetailsProps,
+  __internal_PlanDetailsProps,
+  ClerkAPIError,
+  ClerkRuntimeError,
+  CommercePlanResource,
+  CommerceSubscriptionPlanPeriod,
+  CommerceSubscriptionResource,
+} from '@clerk/types';
+import * as React from 'react';
+import { useMemo, useState } from 'react';
+
+import { Alert } from '@/ui/elements/Alert';
+import { Avatar } from '@/ui/elements/Avatar';
+import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
+import { Switch } from '@/ui/elements/Switch';
+
+import { useProtect } from '../../common';
+import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
+import { Badge, Box, Button, Col, descriptors, Flex, Heading, localizationKeys, Span, Text } from '../../customizables';
+import { handleError } from '../../utils';
+
+export const SubscriptionDetails = (props: __experimental_SubscriptionDetailsProps) => {
+  console.log('SubscriptionDetails', props);
+  return (
+    <SubscriberTypeContext.Provider value={'user'}>
+      <Drawer.Content>
+        <PlanDetailsInternal {...props} />
+      </Drawer.Content>
+    </SubscriberTypeContext.Provider>
+  );
+};
+
+const PlanDetailsInternal = ({
+  plan,
+  onSubscriptionCancel,
+  portalRoot,
+  initialPlanPeriod = 'month',
+}: __internal_PlanDetailsProps) => {
+  const clerk = useClerk();
+  const { organization } = useOrganization();
+  const [showConfirmation, setShowConfirmation] = useState(false);
+  const [isSubmitting, setIsSubmitting] = useState(false);
+  const [cancelError, setCancelError] = useState<ClerkRuntimeError | ClerkAPIError | string | undefined>();
+  const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(initialPlanPeriod);
+
+  const { setIsOpen } = useDrawerContext();
+  const {
+    activeOrUpcomingSubscriptionBasedOnPlanPeriod,
+    revalidateAll,
+    buttonPropsForPlan,
+    isDefaultPlanImplicitlyActiveOrUpcoming,
+  } = usePlansContext();
+  const subscriberType = useSubscriberTypeContext();
+  const canManageBilling = useProtect(
+    has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
+  );
+
+  if (!plan) {
+    return null;
+  }
+
+  const subscription = activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod);
+
+  const handleClose = () => {
+    if (setIsOpen) {
+      setIsOpen(false);
+    }
+  };
+
+  const features = plan.features;
+  const hasFeatures = features.length > 0;
+  const cancelSubscription = async () => {
+    if (!subscription) {
+      return;
+    }
+
+    setCancelError(undefined);
+    setIsSubmitting(true);
+
+    await subscription
+      .cancel({ orgId: subscriberType === 'org' ? organization?.id : undefined })
+      .then(() => {
+        setIsSubmitting(false);
+        onSubscriptionCancel?.();
+        handleClose();
+      })
+      .catch(error => {
+        handleError(error, [], setCancelError);
+        setIsSubmitting(false);
+      });
+  };
+
+  type Open__internal_CheckoutProps = {
+    planPeriod?: CommerceSubscriptionPlanPeriod;
+  };
+
+  const openCheckout = (props?: Open__internal_CheckoutProps) => {
+    handleClose();
+
+    // if the plan doesn't support annual, use monthly
+    let _planPeriod = props?.planPeriod || planPeriod;
+    if (_planPeriod === 'annual' && plan.annualMonthlyAmount === 0) {
+      _planPeriod = 'month';
+    }
+
+    clerk.__internal_openCheckout({
+      planId: plan.id,
+      planPeriod: _planPeriod,
+      subscriberType: subscriberType,
+      onSubscriptionComplete: () => {
+        void revalidateAll();
+      },
+      portalRoot,
+    });
+  };
+
+  return (
+    <>
+      <Drawer.Header
+        sx={t =>
+          !hasFeatures
+            ? {
+                flex: 1,
+                borderBottomWidth: 0,
+                background: t.colors.$colorBackground,
+              }
+            : null
+        }
+      >
+        <Header
+          plan={plan}
+          subscription={subscription}
+          planPeriod={planPeriod}
+          setPlanPeriod={setPlanPeriod}
+          closeSlot={<Drawer.Close />}
+        />
+      </Drawer.Header>
+
+      {hasFeatures ? (
+        <Drawer.Body>
+          <Text
+            elementDescriptor={descriptors.planDetailCaption}
+            variant={'caption'}
+            localizationKey={localizationKeys('commerce.availableFeatures')}
+            colorScheme='secondary'
+            sx={t => ({
+              padding: t.space.$4,
+              paddingBottom: 0,
+            })}
+          />
+          <Box
+            elementDescriptor={descriptors.planDetailFeaturesList}
+            as='ul'
+            role='list'
+            sx={t => ({
+              display: 'grid',
+              rowGap: t.space.$6,
+              padding: t.space.$4,
+              margin: 0,
+            })}
+          >
+            {features.map(feature => (
+              <Box
+                key={feature.id}
+                elementDescriptor={descriptors.planDetailFeaturesListItem}
+                as='li'
+                sx={t => ({
+                  display: 'flex',
+                  alignItems: 'baseline',
+                  gap: t.space.$3,
+                })}
+              >
+                {feature.avatarUrl ? (
+                  <Avatar
+                    size={_ => 24}
+                    title={feature.name}
+                    initials={feature.name[0]}
+                    rounded={false}
+                    imageUrl={feature.avatarUrl}
+                  />
+                ) : null}
+                <Span elementDescriptor={descriptors.planDetailFeaturesListItemContent}>
+                  <Text
+                    elementDescriptor={descriptors.planDetailFeaturesListItemTitle}
+                    colorScheme='body'
+                    sx={t => ({
+                      fontWeight: t.fontWeights.$medium,
+                    })}
+                  >
+                    {feature.name}
+                  </Text>
+                  {feature.description ? (
+                    <Text
+                      elementDescriptor={descriptors.planDetailFeaturesListItemDescription}
+                      colorScheme='secondary'
+                      sx={t => ({
+                        marginBlockStart: t.space.$0x25,
+                      })}
+                    >
+                      {feature.description}
+                    </Text>
+                  ) : null}
+                </Span>
+              </Box>
+            ))}
+          </Box>
+        </Drawer.Body>
+      ) : null}
+
+      {(!plan.isDefault && !isDefaultPlanImplicitlyActiveOrUpcoming) || !subscription ? (
+        <Drawer.Footer>
+          {subscription ? (
+            subscription.canceledAt ? (
+              <Button
+                block
+                textVariant='buttonLarge'
+                {...buttonPropsForPlan({ plan })}
+                onClick={() => openCheckout()}
+              />
+            ) : (
+              <Col gap={4}>
+                {!!subscription &&
+                subscription.planPeriod === 'month' &&
+                plan.annualMonthlyAmount > 0 &&
+                planPeriod === 'annual' ? (
+                  <Button
+                    block
+                    variant='bordered'
+                    colorScheme='secondary'
+                    textVariant='buttonLarge'
+                    isDisabled={!canManageBilling}
+                    onClick={() => openCheckout({ planPeriod: 'annual' })}
+                    localizationKey={localizationKeys('commerce.switchToAnnual')}
+                  />
+                ) : null}
+                {!!subscription && subscription.planPeriod === 'annual' && planPeriod === 'month' ? (
+                  <Button
+                    block
+                    variant='bordered'
+                    colorScheme='secondary'
+                    textVariant='buttonLarge'
+                    isDisabled={!canManageBilling}
+                    onClick={() => openCheckout({ planPeriod: 'month' })}
+                    localizationKey={localizationKeys('commerce.switchToMonthly')}
+                  />
+                ) : null}
+                <Button
+                  block
+                  variant='bordered'
+                  colorScheme='danger'
+                  textVariant='buttonLarge'
+                  isDisabled={!canManageBilling}
+                  onClick={() => setShowConfirmation(true)}
+                  localizationKey={localizationKeys('commerce.cancelSubscription')}
+                />
+              </Col>
+            )
+          ) : (
+            <Button
+              block
+              textVariant='buttonLarge'
+              {...buttonPropsForPlan({ plan })}
+              onClick={() => openCheckout()}
+            />
+          )}
+        </Drawer.Footer>
+      ) : null}
+
+      {subscription ? (
+        <Drawer.Confirmation
+          open={showConfirmation}
+          onOpenChange={setShowConfirmation}
+          actionsSlot={
+            <>
+              {!isSubmitting && (
+                <Button
+                  variant='ghost'
+                  size='sm'
+                  textVariant='buttonLarge'
+                  isDisabled={!canManageBilling}
+                  onClick={() => {
+                    setCancelError(undefined);
+                    setShowConfirmation(false);
+                  }}
+                  localizationKey={localizationKeys('commerce.keepSubscription')}
+                />
+              )}
+              <Button
+                variant='solid'
+                colorScheme='danger'
+                size='sm'
+                textVariant='buttonLarge'
+                isLoading={isSubmitting}
+                isDisabled={!canManageBilling}
+                onClick={() => {
+                  setCancelError(undefined);
+                  setShowConfirmation(false);
+                  void cancelSubscription();
+                }}
+                localizationKey={localizationKeys('commerce.cancelSubscription')}
+              />
+            </>
+          }
+        >
+          <Heading
+            elementDescriptor={descriptors.drawerConfirmationTitle}
+            as='h2'
+            textVariant='h3'
+            localizationKey={localizationKeys('commerce.cancelSubscriptionTitle', {
+              plan: `${subscription.status === 'upcoming' ? 'upcoming ' : ''}${subscription.plan.name}`,
+            })}
+          />
+          <Text
+            elementDescriptor={descriptors.drawerConfirmationDescription}
+            colorScheme='secondary'
+            localizationKey={
+              subscription.status === 'upcoming'
+                ? localizationKeys('commerce.cancelSubscriptionNoCharge')
+                : localizationKeys('commerce.cancelSubscriptionAccessUntil', {
+                    plan: subscription.plan.name,
+                    date: subscription.periodEnd,
+                  })
+            }
+          />
+          {cancelError && (
+            <Alert colorScheme='danger'>{typeof cancelError === 'string' ? cancelError : cancelError.message}</Alert>
+          )}
+        </Drawer.Confirmation>
+      ) : null}
+    </>
+  );
+};
+
+/* -------------------------------------------------------------------------------------------------
+ * Header
+ * -----------------------------------------------------------------------------------------------*/
+
+interface HeaderProps {
+  plan: CommercePlanResource;
+  subscription?: CommerceSubscriptionResource;
+  planPeriod: CommerceSubscriptionPlanPeriod;
+  setPlanPeriod: (val: CommerceSubscriptionPlanPeriod) => void;
+  closeSlot?: React.ReactNode;
+}
+
+const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
+  const { plan, subscription, closeSlot, planPeriod, setPlanPeriod } = props;
+
+  const { captionForSubscription, isDefaultPlanImplicitlyActiveOrUpcoming } = usePlansContext();
+  const { data: subscriptions } = useSubscriptions();
+
+  const isImplicitlyActiveOrUpcoming = isDefaultPlanImplicitlyActiveOrUpcoming && plan.isDefault;
+
+  const showBadge = !!subscription;
+
+  const getPlanFee = useMemo(() => {
+    if (plan.annualMonthlyAmount <= 0) {
+      return plan.amountFormatted;
+    }
+    return planPeriod === 'annual' ? plan.annualMonthlyAmountFormatted : plan.amountFormatted;
+  }, [plan, planPeriod]);
+
+  return (
+    <Box
+      ref={ref}
+      elementDescriptor={descriptors.planDetailHeader}
+      sx={t => ({
+        width: '100%',
+        padding: t.space.$4,
+        position: 'relative',
+      })}
+    >
+      {closeSlot ? (
+        <Box
+          sx={t => ({
+            position: 'absolute',
+            top: t.space.$2,
+            insetInlineEnd: t.space.$2,
+          })}
+        >
+          {closeSlot}
+        </Box>
+      ) : null}
+
+      <Col
+        gap={3}
+        elementDescriptor={descriptors.planDetailBadgeAvatarTitleDescriptionContainer}
+      >
+        {showBadge ? (
+          <Flex
+            align='center'
+            gap={3}
+            elementDescriptor={descriptors.planDetailBadgeContainer}
+            sx={t => ({
+              paddingInlineEnd: t.space.$10,
+            })}
+          >
+            {subscription?.status === 'active' || (isImplicitlyActiveOrUpcoming && subscriptions.length === 0) ? (
+              <Badge
+                elementDescriptor={descriptors.planDetailBadge}
+                localizationKey={localizationKeys('badge__activePlan')}
+                colorScheme={'secondary'}
+              />
+            ) : (
+              <Badge
+                elementDescriptor={descriptors.planDetailBadge}
+                localizationKey={localizationKeys('badge__upcomingPlan')}
+                colorScheme={'primary'}
+              />
+            )}
+            {!!subscription && (
+              <Text
+                elementDescriptor={descriptors.planDetailCaption}
+                variant={'caption'}
+                localizationKey={captionForSubscription(subscription)}
+                colorScheme='secondary'
+              />
+            )}
+          </Flex>
+        ) : null}
+        {plan.avatarUrl ? (
+          <Avatar
+            boxElementDescriptor={descriptors.planDetailAvatar}
+            size={_ => 40}
+            title={plan.name}
+            initials={plan.name[0]}
+            rounded={false}
+            imageUrl={plan.avatarUrl}
+            sx={t => ({
+              marginBlockEnd: t.space.$3,
+            })}
+          />
+        ) : null}
+        <Col
+          gap={1}
+          elementDescriptor={descriptors.planDetailTitleDescriptionContainer}
+        >
+          <Heading
+            elementDescriptor={descriptors.planDetailTitle}
+            as='h2'
+            textVariant='h2'
+          >
+            {plan.name}
+          </Heading>
+          {plan.description ? (
+            <Text
+              elementDescriptor={descriptors.planDetailDescription}
+              variant='subtitle'
+              colorScheme='secondary'
+            >
+              {plan.description}
+            </Text>
+          ) : null}
+        </Col>
+      </Col>
+
+      <Flex
+        elementDescriptor={descriptors.planDetailFeeContainer}
+        align='center'
+        wrap='wrap'
+        sx={t => ({
+          marginTop: t.space.$3,
+          columnGap: t.space.$1x5,
+        })}
+      >
+        <>
+          <Text
+            elementDescriptor={descriptors.planDetailFee}
+            variant='h1'
+            colorScheme='body'
+          >
+            {plan.currencySymbol}
+            {getPlanFee}
+          </Text>
+          <Text
+            elementDescriptor={descriptors.planDetailFeePeriod}
+            variant='caption'
+            colorScheme='secondary'
+            sx={t => ({
+              textTransform: 'lowercase',
+              ':before': {
+                content: '"/"',
+                marginInlineEnd: t.space.$1,
+              },
+            })}
+            localizationKey={localizationKeys('commerce.month')}
+          />
+        </>
+      </Flex>
+
+      {plan.annualMonthlyAmount > 0 ? (
+        <Box
+          elementDescriptor={descriptors.planDetailPeriodToggle}
+          sx={t => ({
+            display: 'flex',
+            marginTop: t.space.$3,
+          })}
+        >
+          <Switch
+            isChecked={planPeriod === 'annual'}
+            onChange={(checked: boolean) => setPlanPeriod(checked ? 'annual' : 'month')}
+            label={localizationKeys('commerce.billedAnnually')}
+          />
+        </Box>
+      ) : (
+        <Text
+          elementDescriptor={descriptors.pricingTableCardFeePeriodNotice}
+          variant='caption'
+          colorScheme='secondary'
+          localizationKey={
+            plan.isDefault ? localizationKeys('commerce.alwaysFree') : localizationKeys('commerce.billedMonthlyOnly')
+          }
+          sx={t => ({
+            justifySelf: 'flex-start',
+            alignSelf: 'center',
+            marginTop: t.space.$3,
+          })}
+        />
+      )}
+    </Box>
+  );
+});
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index 0ec0005d9b5..7e35dcca5c7 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -363,10 +363,7 @@ export const usePlansContext = () => {
       const portalRoot = getClosestProfileScrollBox(mode, event);
 
       if (subscription && subscription.planPeriod === planPeriod && !subscription.canceledAt) {
-        clerk.__internal_openPlanDetails({
-          plan,
-          initialPlanPeriod: planPeriod,
-          subscriberType,
+        clerk.__experimental_openSubscriptionDetails({
           onSubscriptionCancel: () => {
             revalidateAll();
             onSubscriptionChange?.();
diff --git a/packages/clerk-js/src/ui/elements/contexts/index.tsx b/packages/clerk-js/src/ui/elements/contexts/index.tsx
index efa927e5593..c91fb670ff1 100644
--- a/packages/clerk-js/src/ui/elements/contexts/index.tsx
+++ b/packages/clerk-js/src/ui/elements/contexts/index.tsx
@@ -88,7 +88,8 @@ export type FlowMetadata = {
     | 'planDetails'
     | 'pricingTable'
     | 'apiKeys'
-    | 'oauthConsent';
+    | 'oauthConsent'
+    | 'subscriptionDetails';
   part?:
     | 'start'
     | 'emailCode'
diff --git a/packages/clerk-js/src/ui/lazyModules/MountedPlanDetailDrawer.tsx b/packages/clerk-js/src/ui/lazyModules/MountedPlanDetailDrawer.tsx
index 2d16fd8e4d9..3b703f1c4f2 100644
--- a/packages/clerk-js/src/ui/lazyModules/MountedPlanDetailDrawer.tsx
+++ b/packages/clerk-js/src/ui/lazyModules/MountedPlanDetailDrawer.tsx
@@ -36,12 +36,7 @@ export function MountedPlanDetailDrawer({
       portalId={planDetailsDrawer.props.portalId}
       portalRoot={planDetailsDrawer.props.portalRoot as HTMLElement | null | undefined}
     >
-      <PlanDetails
-        {...planDetailsDrawer.props}
-        subscriberType={planDetailsDrawer.props.subscriberType || 'user'}
-        onSubscriptionCancel={planDetailsDrawer.props.onSubscriptionCancel || (() => {})}
-        appearance={planDetailsDrawer.props.appearance}
-      />
+      <PlanDetails {...planDetailsDrawer.props} />
     </LazyDrawerRenderer>
   );
 }
diff --git a/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx b/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx
new file mode 100644
index 00000000000..4a6186c4701
--- /dev/null
+++ b/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx
@@ -0,0 +1,42 @@
+import { useUser } from '@clerk/shared/react';
+import type { __experimental_SubscriptionDetailsProps, Appearance } from '@clerk/types';
+
+import { SubscriptionDetails } from '../components/SubscriptionDetails';
+import { LazyDrawerRenderer } from './providers';
+
+export function MountedSubscriptionDetailDrawer({
+  appearance,
+  subscriptionDetailsDrawer,
+  onOpenChange,
+}: {
+  appearance?: Appearance;
+  onOpenChange: (open: boolean) => void;
+  subscriptionDetailsDrawer: {
+    open: false;
+    props: null | __experimental_SubscriptionDetailsProps;
+  };
+}) {
+  const { user } = useUser();
+  if (!subscriptionDetailsDrawer.props) {
+    return null;
+  }
+
+  return (
+    <LazyDrawerRenderer
+      // We set `key` to be the user id to "reset" floating ui portals on session switch.
+      // Without this, the drawer would not be rendered after a session switch.
+      key={user?.id}
+      globalAppearance={appearance}
+      appearanceKey={'planDetails' as any}
+      componentAppearance={subscriptionDetailsDrawer.props.appearance || {}}
+      flowName={'subscriptionDetails'}
+      open={subscriptionDetailsDrawer.open}
+      onOpenChange={onOpenChange}
+      componentName={'SubscriptionDetails'}
+      portalId={subscriptionDetailsDrawer.props.portalId}
+      portalRoot={subscriptionDetailsDrawer.props.portalRoot as HTMLElement | null | undefined}
+    >
+      <SubscriptionDetails {...subscriptionDetailsDrawer.props} />
+    </LazyDrawerRenderer>
+  );
+}
diff --git a/packages/clerk-js/src/ui/lazyModules/components.ts b/packages/clerk-js/src/ui/lazyModules/components.ts
index d0c38843dc2..daa94dc0368 100644
--- a/packages/clerk-js/src/ui/lazyModules/components.ts
+++ b/packages/clerk-js/src/ui/lazyModules/components.ts
@@ -20,7 +20,8 @@ const componentImportPaths = {
   PricingTable: () => import(/* webpackChunkName: "pricingTable" */ '../components/PricingTable'),
   Checkout: () => import(/* webpackChunkName: "checkout" */ '../components/Checkout'),
   SessionTasks: () => import(/* webpackChunkName: "sessionTasks" */ '../components/SessionTasks'),
-  PlanDetails: () => import(/* webpackChunkName: "planDetails" */ '../components/Plans'),
+  PlanDetails: () => import(/* webpackChunkName: "planDetails" */ '../components/Plans/PlanDetails'),
+  SubscriptionDetails: () => import(/* webpackChunkName: "subscriptionDetails" */ '../components/SubscriptionDetails'),
   APIKeys: () => import(/* webpackChunkName: "apiKeys" */ '../components/ApiKeys/ApiKeys'),
   OAuthConsent: () => import(/* webpackChunkName: "oauthConsent" */ '../components/OAuthConsent/OAuthConsent'),
 } as const;
@@ -106,6 +107,10 @@ export const PlanDetails = lazy(() =>
   componentImportPaths.PlanDetails().then(module => ({ default: module.PlanDetails })),
 );
 
+export const SubscriptionDetails = lazy(() =>
+  componentImportPaths.SubscriptionDetails().then(module => ({ default: module.SubscriptionDetails })),
+);
+
 export const OAuthConsent = lazy(() =>
   componentImportPaths.OAuthConsent().then(module => ({ default: module.OAuthConsent })),
 );
@@ -143,6 +148,7 @@ export const ClerkComponents = {
   PlanDetails,
   APIKeys,
   OAuthConsent,
+  SubscriptionDetails,
 };
 
 export type ClerkComponentName = keyof typeof ClerkComponents;
diff --git a/packages/clerk-js/src/ui/lazyModules/drawers.tsx b/packages/clerk-js/src/ui/lazyModules/drawers.tsx
index 3e05ccdddef..7022e169c1d 100644
--- a/packages/clerk-js/src/ui/lazyModules/drawers.tsx
+++ b/packages/clerk-js/src/ui/lazyModules/drawers.tsx
@@ -6,3 +6,9 @@ export const MountedCheckoutDrawer = lazy(() =>
 export const MountedPlanDetailDrawer = lazy(() =>
   import('./MountedPlanDetailDrawer').then(module => ({ default: module.MountedPlanDetailDrawer })),
 );
+
+export const MountedSubscriptionDetailDrawer = lazy(() =>
+  import('./MountedSubscriptionDetailDrawer').then(module => ({
+    default: module.MountedSubscriptionDetailDrawer,
+  })),
+);
diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts
index c92f3d57692..d2f0894733c 100644
--- a/packages/react/src/isomorphicClerk.ts
+++ b/packages/react/src/isomorphicClerk.ts
@@ -3,6 +3,8 @@ import { clerkEvents, createClerkEventBus } from '@clerk/shared/clerkEventBus';
 import { loadClerkJsScript } from '@clerk/shared/loadClerkJsScript';
 import { handleValueOrFn } from '@clerk/shared/utils';
 import type {
+  __experimental_PlanDetailsProps,
+  __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
   __internal_OAuthConsentProps,
   __internal_PlanDetailsProps,
@@ -119,7 +121,8 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
   private preopenUserVerification?: null | __internal_UserVerificationProps = null;
   private preopenSignIn?: null | SignInProps = null;
   private preopenCheckout?: null | __internal_CheckoutProps = null;
-  private preopenPlanDetails?: null | __internal_PlanDetailsProps = null;
+  private preopenPlanDetails?: null | __experimental_PlanDetailsProps = null;
+  private preopenSubscriptionDetails?: null | __experimental_SubscriptionDetailsProps = null;
   private preopenSignUp?: null | SignUpProps = null;
   private preopenUserProfile?: null | UserProfileProps = null;
   private preopenOrganizationProfile?: null | OrganizationProfileProps = null;
@@ -557,7 +560,11 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
     }
 
     if (this.preopenPlanDetails !== null) {
-      clerkjs.__internal_openPlanDetails(this.preopenPlanDetails);
+      clerkjs.__experimental_openPlanDetails(this.preopenPlanDetails);
+    }
+
+    if (this.preopenSubscriptionDetails !== null) {
+      clerkjs.__experimental_openSubscriptionDetails(this.preopenSubscriptionDetails);
     }
 
     if (this.preopenSignUp !== null) {
@@ -788,6 +795,38 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
     }
   };
 
+  __experimental_openPlanDetails = (props?: __experimental_PlanDetailsProps) => {
+    if (this.clerkjs && this.loaded) {
+      this.clerkjs.__experimental_openPlanDetails(props);
+    } else {
+      this.preopenPlanDetails = props;
+    }
+  };
+
+  __experimental_closePlanDetails = () => {
+    if (this.clerkjs && this.loaded) {
+      this.clerkjs.__experimental_closePlanDetails();
+    } else {
+      this.preopenPlanDetails = null;
+    }
+  };
+
+  __experimental_openSubscriptionDetails = (props?: __experimental_SubscriptionDetailsProps) => {
+    if (this.clerkjs && this.loaded) {
+      this.clerkjs.__experimental_openSubscriptionDetails(props);
+    } else {
+      this.preopenSubscriptionDetails = props;
+    }
+  };
+
+  __experimental_closeSubscriptionDetails = () => {
+    if (this.clerkjs && this.loaded) {
+      this.clerkjs.__experimental_closeSubscriptionDetails();
+    } else {
+      this.preopenSubscriptionDetails = null;
+    }
+  };
+
   __internal_openReverification = (props?: __internal_UserVerificationModalProps) => {
     if (this.clerkjs && this.loaded) {
       this.clerkjs.__internal_openReverification(props);
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index e25115150a9..a5a9f89fbda 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -232,6 +232,29 @@ export interface Clerk {
    */
   __internal_closePlanDetails: () => void;
 
+  /**
+   * Opens the Clerk PlanDetails drawer component in a drawer.
+   * @param props Optional subscription details drawer configuration parameters.
+   */
+  __experimental_openPlanDetails: (props?: __experimental_PlanDetailsProps) => void;
+
+  /**
+   * Closes the Clerk PlanDetails drawer.
+   */
+  __experimental_closePlanDetails: () => void;
+
+  /**
+   * Opens the Clerk PlanDetails drawer component in a drawer.
+   * @param props Optional subscription details drawer configuration parameters.
+   */
+  __experimental_openSubscriptionDetails: (props?: __experimental_SubscriptionDetailsProps) => void;
+
+  /**
+   * Closes the Clerk PlanDetails drawer.
+   */
+  __experimental_closeSubscriptionDetails: () => void;
+
+  /**
   /** Opens the Clerk UserVerification component in a modal.
    * @param props Optional user verification configuration parameters.
    */
@@ -1728,6 +1751,22 @@ export type __internal_PlanDetailsProps = {
   portalRoot?: PortalRoot;
 };
 
+export type __experimental_PlanDetailsProps = {
+  appearance?: PlanDetailTheme;
+  plan?: CommercePlanResource;
+  planId?: string;
+  initialPlanPeriod?: CommerceSubscriptionPlanPeriod;
+  portalId?: string;
+  portalRoot?: PortalRoot;
+};
+
+export type __experimental_SubscriptionDetailsProps = {
+  appearance?: PlanDetailTheme;
+  onSubscriptionCancel?: () => void;
+  portalId?: string;
+  portalRoot?: PortalRoot;
+};
+
 export type __internal_OAuthConsentProps = {
   appearance?: OAuthConsentTheme;
   /**
diff --git a/packages/types/src/commerce.ts b/packages/types/src/commerce.ts
index 8b27f61d081..3caf459548d 100644
--- a/packages/types/src/commerce.ts
+++ b/packages/types/src/commerce.ts
@@ -10,6 +10,7 @@ type WithOptionalOrgType<T> = T & {
 export interface CommerceBillingNamespace {
   getPaymentAttempts: (params: GetPaymentAttemptsParams) => Promise<ClerkPaginatedResponse<CommercePaymentResource>>;
   getPlans: (params?: GetPlansParams) => Promise<CommercePlanResource[]>;
+  getPlan: (params: { id: string }) => Promise<CommercePlanResource>;
   getSubscriptions: (params: GetSubscriptionsParams) => Promise<ClerkPaginatedResponse<CommerceSubscriptionResource>>;
   getStatements: (params: GetStatementsParams) => Promise<ClerkPaginatedResponse<CommerceStatementResource>>;
   startCheckout: (params: CreateCheckoutParams) => Promise<CommerceCheckoutResource>;

From 2041111d6adcae5b872a66c0e7c61ac1f9fbd89e Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 17 Jun 2025 18:58:04 +0300
Subject: [PATCH 02/34] wip 2

---
 packages/clerk-js/src/ui/Components.tsx       |   3 +-
 .../components/SubscriptionDetails/index.tsx  | 262 +++++++++++++-----
 2 files changed, 200 insertions(+), 65 deletions(-)

diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx
index ee5d4dbd96d..59b124f1fb9 100644
--- a/packages/clerk-js/src/ui/Components.tsx
+++ b/packages/clerk-js/src/ui/Components.tsx
@@ -38,8 +38,7 @@ import {
   UserVerificationModal,
   WaitlistModal,
 } from './lazyModules/components';
-import { MountedCheckoutDrawer, MountedPlanDetailDrawer } from './lazyModules/drawers';
-import { MountedSubscriptionDetailDrawer } from './lazyModules/MountedSubscriptionDetailDrawer';
+import { MountedCheckoutDrawer, MountedPlanDetailDrawer, MountedSubscriptionDetailDrawer } from './lazyModules/drawers';
 import {
   LazyComponentRenderer,
   LazyImpersonationFabProvider,
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 66cdc9a8d7b..d0d7168f975 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -11,28 +11,37 @@ import type {
 import * as React from 'react';
 import { useMemo, useState } from 'react';
 
-import { Alert } from '@/ui/elements/Alert';
 import { Avatar } from '@/ui/elements/Avatar';
 import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
 import { Switch } from '@/ui/elements/Switch';
+import { Icon } from '@/ui/primitives/Icon';
+import { truncateWithEndVisible } from '@/ui/utils/truncateTextWithEndVisible';
 
 import { useProtect } from '../../common';
-import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
-import { Badge, Box, Button, Col, descriptors, Flex, Heading, localizationKeys, Span, Text } from '../../customizables';
-import { handleError } from '../../utils';
+import { usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
+import type { LocalizationKey } from '../../customizables';
+import {
+  Badge,
+  Box,
+  Col,
+  descriptors,
+  Flex,
+  Heading,
+  localizationKeys,
+  Span,
+  Spinner,
+  Text,
+} from '../../customizables';
 
 export const SubscriptionDetails = (props: __experimental_SubscriptionDetailsProps) => {
-  console.log('SubscriptionDetails', props);
   return (
-    <SubscriberTypeContext.Provider value={'user'}>
-      <Drawer.Content>
-        <PlanDetailsInternal {...props} />
-      </Drawer.Content>
-    </SubscriberTypeContext.Provider>
+    <Drawer.Content>
+      <SubscriptionDetailsInternal {...props} />
+    </Drawer.Content>
   );
 };
 
-const PlanDetailsInternal = ({
+const SubscriptionDetailsInternal = ({
   plan,
   onSubscriptionCancel,
   portalRoot,
@@ -46,22 +55,23 @@ const PlanDetailsInternal = ({
   const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(initialPlanPeriod);
 
   const { setIsOpen } = useDrawerContext();
-  const {
-    activeOrUpcomingSubscriptionBasedOnPlanPeriod,
-    revalidateAll,
-    buttonPropsForPlan,
-    isDefaultPlanImplicitlyActiveOrUpcoming,
-  } = usePlansContext();
+  const { revalidateAll, buttonPropsForPlan, isDefaultPlanImplicitlyActiveOrUpcoming } = usePlansContext();
   const subscriberType = useSubscriberTypeContext();
   const canManageBilling = useProtect(
     has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
   );
 
-  if (!plan) {
-    return null;
-  }
+  const { data: subscriptions, isLoading } = useSubscriptions();
 
-  const subscription = activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod);
+  if (isLoading) {
+    return (
+      <Spinner
+        sx={{
+          margin: 'auto',
+        }}
+      />
+    );
+  }
 
   const handleClose = () => {
     if (setIsOpen) {
@@ -69,27 +79,20 @@ const PlanDetailsInternal = ({
     }
   };
 
-  const features = plan.features;
-  const hasFeatures = features.length > 0;
   const cancelSubscription = async () => {
-    if (!subscription) {
-      return;
-    }
-
-    setCancelError(undefined);
-    setIsSubmitting(true);
-
-    await subscription
-      .cancel({ orgId: subscriberType === 'org' ? organization?.id : undefined })
-      .then(() => {
-        setIsSubmitting(false);
-        onSubscriptionCancel?.();
-        handleClose();
-      })
-      .catch(error => {
-        handleError(error, [], setCancelError);
-        setIsSubmitting(false);
-      });
+    // setCancelError(undefined);
+    // setIsSubmitting(true);
+    // await subscription
+    //   .cancel({ orgId: subscriberType === 'org' ? organization?.id : undefined })
+    //   .then(() => {
+    //     setIsSubmitting(false);
+    //     onSubscriptionCancel?.();
+    //     handleClose();
+    //   })
+    //   .catch(error => {
+    //     handleError(error, [], setCancelError);
+    //     setIsSubmitting(false);
+    //   });
   };
 
   type Open__internal_CheckoutProps = {
@@ -119,26 +122,75 @@ const PlanDetailsInternal = ({
   return (
     <>
       <Drawer.Header
-        sx={t =>
-          !hasFeatures
-            ? {
-                flex: 1,
-                borderBottomWidth: 0,
-                background: t.colors.$colorBackground,
-              }
-            : null
+        title={
+          'Subscription'
+          // localizationKeys('commerce.checkout.title')
         }
+      />
+
+      <Col
+        gap={4}
+        sx={t => ({
+          padding: t.space.$4,
+        })}
       >
-        <Header
-          plan={plan}
-          subscription={subscription}
-          planPeriod={planPeriod}
-          setPlanPeriod={setPlanPeriod}
-          closeSlot={<Drawer.Close />}
-        />
-      </Drawer.Header>
+        {subscriptions?.map(subscriptionItem => (
+          <Col
+            gap={3}
+            key={subscriptionItem.id}
+            sx={t => ({
+              padding: t.space.$3,
+              borderWidth: t.borderWidths.$normal,
+              borderStyle: t.borderStyles.$solid,
+              borderColor: t.colors.$neutralAlpha100,
+              borderRadius: t.radii.$md,
+            })}
+          >
+            <Flex>
+              <Text
+                sx={{
+                  marginRight: 'auto',
+                }}
+              >
+                {subscriptionItem.plan.name}
+              </Text>
+              <Badge
+                colorScheme={subscriptionItem.status === 'active' ? 'secondary' : 'primary'}
+                localizationKey={
+                  subscriptionItem.status === 'active'
+                    ? localizationKeys('badge__activePlan')
+                    : localizationKeys('badge__upcomingPlan')
+                }
+              />
+            </Flex>
+
+            <Box
+              elementDescriptor={descriptors.statementSectionContentDetailsList}
+              as='ul'
+              sx={t => ({
+                margin: 0,
+                padding: 0,
+                borderWidth: t.borderWidths.$normal,
+                borderStyle: t.borderStyles.$solid,
+                borderColor: t.colors.$neutralAlpha100,
+                borderRadius: t.radii.$md,
+                overflow: 'hidden',
+              })}
+            >
+              <PriceItem
+                label={'Monthly price'}
+                value={`${subscriptionItem.plan.currencySymbol}${subscriptionItem.plan.amountFormatted} / mo`}
+              />
+              <PriceItem
+                label={'Annual discount'}
+                value={`${subscriptionItem.plan.currencySymbol}${subscriptionItem.plan.annualMonthlyAmountFormatted} / mo`}
+              />
+            </Box>
+          </Col>
+        ))}
+      </Col>
 
-      {hasFeatures ? (
+      {/* {hasFeatures ? (
         <Drawer.Body>
           <Text
             elementDescriptor={descriptors.planDetailCaption}
@@ -207,9 +259,9 @@ const PlanDetailsInternal = ({
             ))}
           </Box>
         </Drawer.Body>
-      ) : null}
+      ) : null} */}
 
-      {(!plan.isDefault && !isDefaultPlanImplicitlyActiveOrUpcoming) || !subscription ? (
+      {/* {(!plan.isDefault && !isDefaultPlanImplicitlyActiveOrUpcoming) || !subscription ? (
         <Drawer.Footer>
           {subscription ? (
             subscription.canceledAt ? (
@@ -266,9 +318,9 @@ const PlanDetailsInternal = ({
             />
           )}
         </Drawer.Footer>
-      ) : null}
+      ) : null} */}
 
-      {subscription ? (
+      {/* {subscription ? (
         <Drawer.Confirmation
           open={showConfirmation}
           onOpenChange={setShowConfirmation}
@@ -328,11 +380,95 @@ const PlanDetailsInternal = ({
             <Alert colorScheme='danger'>{typeof cancelError === 'string' ? cancelError : cancelError.message}</Alert>
           )}
         </Drawer.Confirmation>
-      ) : null}
+      ) : null} */}
     </>
   );
 };
 
+function PriceItem({
+  labelIcon,
+  label,
+  valueCopyable = false,
+  value,
+  valueTruncated = false,
+}: {
+  icon?: React.ReactNode;
+  label: string | LocalizationKey;
+  labelIcon?: React.ComponentType;
+  value: string | LocalizationKey;
+  valueTruncated?: boolean;
+  valueCopyable?: boolean;
+}) {
+  return (
+    <Box
+      elementDescriptor={descriptors.statementSectionContentDetailsListItem}
+      as='li'
+      sx={t => ({
+        margin: 0,
+        paddingInline: t.space.$2,
+        paddingBlock: t.space.$1x5,
+        display: 'flex',
+        justifyContent: 'space-between',
+        flexWrap: 'wrap',
+        columnGap: t.space.$2,
+        rowGap: t.space.$0x5,
+        '&:not(:first-child)': {
+          borderBlockStartWidth: t.borderWidths.$normal,
+          borderBlockStartStyle: t.borderStyles.$solid,
+          borderBlockStartColor: t.colors.$neutralAlpha100,
+        },
+      })}
+    >
+      <Span
+        elementDescriptor={descriptors.statementSectionContentDetailsListItemLabelContainer}
+        sx={t => ({
+          display: 'flex',
+          alignItems: 'center',
+          gap: t.space.$1x5,
+        })}
+      >
+        {labelIcon ? (
+          <Icon
+            icon={labelIcon}
+            colorScheme='neutral'
+          />
+        ) : null}
+        <Text
+          variant='caption'
+          colorScheme='secondary'
+          elementDescriptor={descriptors.statementSectionContentDetailsListItemLabel}
+          localizationKey={label}
+        />
+      </Span>
+      <Span
+        sx={t => ({
+          display: 'flex',
+          alignItems: 'center',
+          gap: t.space.$0x25,
+          color: t.colors.$colorTextSecondary,
+        })}
+      >
+        {typeof value === 'string' ? (
+          <Text
+            colorScheme='secondary'
+            variant='caption'
+            elementDescriptor={descriptors.statementSectionContentDetailsListItemValue}
+          >
+            {valueTruncated ? truncateWithEndVisible(value) : value}
+          </Text>
+        ) : (
+          <Text
+            elementDescriptor={descriptors.statementSectionContentDetailsListItemValue}
+            colorScheme='secondary'
+            variant='caption'
+            localizationKey={value}
+          />
+        )}
+      </Span>
+    </Box>
+  );
+}
+
 /* -------------------------------------------------------------------------------------------------
  * Header
  * -----------------------------------------------------------------------------------------------*/

From e7a3f26d7f62e5aa964801fec26bae70c5c33a54 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 18 Jun 2025 15:17:11 +0300
Subject: [PATCH 03/34] create the layout and functionality

---
 packages/clerk-js/src/core/clerk.ts           |   1 -
 .../core/resources/CommerceSubscription.ts    |  15 +-
 .../components/SubscriptionDetails/index.tsx  | 899 ++++++++----------
 .../src/ui/contexts/components/Plans.tsx      |   3 +-
 packages/types/src/commerce.ts                |   7 +-
 packages/types/src/json.ts                    |   1 +
 6 files changed, 418 insertions(+), 508 deletions(-)

diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index aa09557c6c1..e48ac39362d 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -635,7 +635,6 @@ export class Clerk implements ClerkInterface {
 
   public __experimental_openSubscriptionDetails = (props?: __experimental_SubscriptionDetailsProps): void => {
     this.assertComponentsReady(this.#componentControls);
-    console.log('__experimental_openSubscriptionDetails', props);
     void this.#componentControls
       .ensureMounted({ preloadHint: 'SubscriptionDetails' })
       .then(controls => controls.openDrawer('subscriptionDetails', props || {}));
diff --git a/packages/clerk-js/src/core/resources/CommerceSubscription.ts b/packages/clerk-js/src/core/resources/CommerceSubscription.ts
index aa45426968a..26ad8a2f74d 100644
--- a/packages/clerk-js/src/core/resources/CommerceSubscription.ts
+++ b/packages/clerk-js/src/core/resources/CommerceSubscription.ts
@@ -9,6 +9,7 @@ import type {
 } from '@clerk/types';
 
 import { commerceMoneyFromJSON } from '../../utils';
+import { unixEpochToDate } from '../../utils/date';
 import { BaseResource, CommercePlan, DeletedObject } from './internal';
 
 export class CommerceSubscription extends BaseResource implements CommerceSubscriptionResource {
@@ -17,9 +18,10 @@ export class CommerceSubscription extends BaseResource implements CommerceSubscr
   plan!: CommercePlan;
   planPeriod!: CommerceSubscriptionPlanPeriod;
   status!: CommerceSubscriptionStatus;
-  periodStart!: number;
-  periodEnd!: number;
-  canceledAt!: number | null;
+  createdAt!: Date;
+  periodStart!: Date;
+  periodEnd!: Date;
+  canceledAt!: Date | null;
   amount?: CommerceMoney;
   credit?: {
     amount: CommerceMoney;
@@ -39,9 +41,10 @@ export class CommerceSubscription extends BaseResource implements CommerceSubscr
     this.plan = new CommercePlan(data.plan);
     this.planPeriod = data.plan_period;
     this.status = data.status;
-    this.periodStart = data.period_start;
-    this.periodEnd = data.period_end;
-    this.canceledAt = data.canceled_at;
+    this.createdAt = unixEpochToDate(data.created_at);
+    this.periodStart = unixEpochToDate(data.period_start);
+    this.periodEnd = unixEpochToDate(data.period_end);
+    this.canceledAt = data.canceled_at ? unixEpochToDate(data.canceled_at) : null;
     this.amount = data.amount ? commerceMoneyFromJSON(data.amount) : undefined;
     this.credit = data.credit && data.credit.amount ? { amount: commerceMoneyFromJSON(data.credit.amount) } : undefined;
     return this;
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index d0d7168f975..d93a88b03b0 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -1,32 +1,35 @@
 import { useClerk, useOrganization } from '@clerk/shared/react';
 import type {
   __experimental_SubscriptionDetailsProps,
-  __internal_PlanDetailsProps,
+  __internal_CheckoutProps,
   ClerkAPIError,
   ClerkRuntimeError,
-  CommercePlanResource,
-  CommerceSubscriptionPlanPeriod,
   CommerceSubscriptionResource,
 } from '@clerk/types';
 import * as React from 'react';
-import { useMemo, useState } from 'react';
+import { useState } from 'react';
 
-import { Avatar } from '@/ui/elements/Avatar';
 import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
-import { Switch } from '@/ui/elements/Switch';
-import { Icon } from '@/ui/primitives/Icon';
+import { Check } from '@/ui/icons';
+import { common } from '@/ui/styledSystem/common';
+import { colors } from '@/ui/utils/colors';
+import { handleError } from '@/ui/utils/errorHandler';
+import { formatDate } from '@/ui/utils/formatDate';
 import { truncateWithEndVisible } from '@/ui/utils/truncateTextWithEndVisible';
 
 import { useProtect } from '../../common';
 import { usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
 import type { LocalizationKey } from '../../customizables';
 import {
+  Alert,
   Badge,
   Box,
+  Button,
   Col,
   descriptors,
   Flex,
   Heading,
+  Icon,
   localizationKeys,
   Span,
   Spinner,
@@ -41,21 +44,19 @@ export const SubscriptionDetails = (props: __experimental_SubscriptionDetailsPro
   );
 };
 
-const SubscriptionDetailsInternal = ({
-  plan,
-  onSubscriptionCancel,
-  portalRoot,
-  initialPlanPeriod = 'month',
-}: __internal_PlanDetailsProps) => {
+const SubscriptionDetailsInternal = ({ onSubscriptionCancel, portalRoot }: __experimental_SubscriptionDetailsProps) => {
   const clerk = useClerk();
-  const { organization } = useOrganization();
+  const { organization: _organization } = useOrganization();
   const [showConfirmation, setShowConfirmation] = useState(false);
   const [isSubmitting, setIsSubmitting] = useState(false);
   const [cancelError, setCancelError] = useState<ClerkRuntimeError | ClerkAPIError | string | undefined>();
-  const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(initialPlanPeriod);
 
   const { setIsOpen } = useDrawerContext();
-  const { revalidateAll, buttonPropsForPlan, isDefaultPlanImplicitlyActiveOrUpcoming } = usePlansContext();
+  const {
+    revalidateAll,
+    buttonPropsForPlan: _buttonPropsForPlan,
+    isDefaultPlanImplicitlyActiveOrUpcoming: _isDefaultPlanImplicitlyActiveOrUpcoming,
+  } = usePlansContext();
   const subscriberType = useSubscriberTypeContext();
   const canManageBilling = useProtect(
     has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
@@ -79,39 +80,31 @@ const SubscriptionDetailsInternal = ({
     }
   };
 
-  const cancelSubscription = async () => {
-    // setCancelError(undefined);
-    // setIsSubmitting(true);
-    // await subscription
-    //   .cancel({ orgId: subscriberType === 'org' ? organization?.id : undefined })
-    //   .then(() => {
-    //     setIsSubmitting(false);
-    //     onSubscriptionCancel?.();
-    //     handleClose();
-    //   })
-    //   .catch(error => {
-    //     handleError(error, [], setCancelError);
-    //     setIsSubmitting(false);
-    //   });
-  };
+  const cancelSubscription = async (subscription: CommerceSubscriptionResource) => {
+    setCancelError(undefined);
+    setIsSubmitting(true);
 
-  type Open__internal_CheckoutProps = {
-    planPeriod?: CommerceSubscriptionPlanPeriod;
+    await subscription
+      .cancel(
+        // { orgId: subscriberType === 'org' ? organization?.id : undefined }
+        {},
+      )
+      .then(() => {
+        setIsSubmitting(false);
+        onSubscriptionCancel?.();
+        handleClose();
+      })
+      .catch(error => {
+        handleError(error, [], setCancelError);
+        setIsSubmitting(false);
+      });
   };
 
-  const openCheckout = (props?: Open__internal_CheckoutProps) => {
+  const openCheckout = (params?: __internal_CheckoutProps) => {
     handleClose();
 
-    // if the plan doesn't support annual, use monthly
-    let _planPeriod = props?.planPeriod || planPeriod;
-    if (_planPeriod === 'annual' && plan.annualMonthlyAmount === 0) {
-      _planPeriod = 'month';
-    }
-
     clerk.__internal_openCheckout({
-      planId: plan.id,
-      planPeriod: _planPeriod,
-      subscriberType: subscriberType,
+      ...params,
       onSubscriptionComplete: () => {
         void revalidateAll();
       },
@@ -119,276 +112,297 @@ const SubscriptionDetailsInternal = ({
     });
   };
 
+  // Mock data for demonstration - in real implementation this would come from the subscriptions data
+  const activeSubscription = subscriptions?.find(sub => sub.status === 'active');
+  const upcomingSubscription = subscriptions?.find(sub => sub.status === 'upcoming');
+
+  if (!activeSubscription) {
+    // Should never happen, but just in case
+    return null;
+  }
+
+  const subscription = upcomingSubscription || activeSubscription;
+
   return (
     <>
-      <Drawer.Header
-        title={
-          'Subscription'
-          // localizationKeys('commerce.checkout.title')
-        }
-      />
+      <Drawer.Header title='Subscription' />
 
-      <Col
-        gap={4}
-        sx={t => ({
-          padding: t.space.$4,
-        })}
-      >
-        {subscriptions?.map(subscriptionItem => (
-          <Col
-            gap={3}
-            key={subscriptionItem.id}
-            sx={t => ({
-              padding: t.space.$3,
-              borderWidth: t.borderWidths.$normal,
-              borderStyle: t.borderStyles.$solid,
-              borderColor: t.colors.$neutralAlpha100,
-              borderRadius: t.radii.$md,
-            })}
-          >
-            <Flex>
-              <Text
-                sx={{
-                  marginRight: 'auto',
-                }}
-              >
-                {subscriptionItem.plan.name}
+      <Drawer.Body>
+        <Col
+          gap={4}
+          sx={t => ({
+            padding: t.space.$4,
+            overflowY: 'auto',
+          })}
+        >
+          {/* Subscription Cards */}
+          {subscriptions?.map(subscriptionItem => (
+            <SubscriptionCard
+              key={subscriptionItem.id}
+              subscription={subscriptionItem}
+            />
+          ))}
+        </Col>
+
+        {/* Billing Information */}
+
+        <Col
+          gap={3}
+          as='ul'
+          sx={t => ({
+            marginTop: 'auto',
+            paddingBlock: t.space.$4,
+            borderTopWidth: t.borderWidths.$normal,
+            borderTopStyle: t.borderStyles.$solid,
+            borderTopColor: t.colors.$neutralAlpha100,
+          })}
+        >
+          <SummaryItem>
+            <SummmaryItemLabel>
+              <Text colorScheme='secondary'>Current billing cycle</Text>
+            </SummmaryItemLabel>
+            <SummmaryItemValue>
+              <Text colorScheme='secondary'>{activeSubscription.planPeriod === 'month' ? 'Monthly' : 'Annually'}</Text>
+            </SummmaryItemValue>
+          </SummaryItem>
+          <SummaryItem>
+            <SummmaryItemLabel>
+              <Text colorScheme='secondary'>Next payment on</Text>
+            </SummmaryItemLabel>
+            <SummmaryItemValue>
+              <Text colorScheme='secondary'>
+                {upcomingSubscription
+                  ? formatDate(upcomingSubscription.periodStart)
+                  : formatDate(subscription.periodEnd)}
               </Text>
-              <Badge
-                colorScheme={subscriptionItem.status === 'active' ? 'secondary' : 'primary'}
-                localizationKey={
-                  subscriptionItem.status === 'active'
-                    ? localizationKeys('badge__activePlan')
-                    : localizationKeys('badge__upcomingPlan')
-                }
-              />
-            </Flex>
-
-            <Box
-              elementDescriptor={descriptors.statementSectionContentDetailsList}
-              as='ul'
-              sx={t => ({
-                margin: 0,
-                padding: 0,
-                borderWidth: t.borderWidths.$normal,
-                borderStyle: t.borderStyles.$solid,
-                borderColor: t.colors.$neutralAlpha100,
-                borderRadius: t.radii.$md,
-                overflow: 'hidden',
-              })}
-            >
-              <PriceItem
-                label={'Monthly price'}
-                value={`${subscriptionItem.plan.currencySymbol}${subscriptionItem.plan.amountFormatted} / mo`}
-              />
-              <PriceItem
-                label={'Annual discount'}
-                value={`${subscriptionItem.plan.currencySymbol}${subscriptionItem.plan.annualMonthlyAmountFormatted} / mo`}
-              />
-            </Box>
-          </Col>
-        ))}
-      </Col>
+            </SummmaryItemValue>
+          </SummaryItem>
+          <SummaryItem>
+            <SummmaryItemLabel>
+              <Text>Next payment amount</Text>
+            </SummmaryItemLabel>
+            <SummmaryItemValue>
+              <Text>
+                {`${subscription.plan.currencySymbol}${subscription.planPeriod === 'month' ? subscription.plan.amountFormatted : subscription.plan.annualAmountFormatted}`}
+              </Text>
+            </SummmaryItemValue>
+          </SummaryItem>
+        </Col>
+      </Drawer.Body>
 
-      {/* {hasFeatures ? (
-        <Drawer.Body>
-          <Text
-            elementDescriptor={descriptors.planDetailCaption}
-            variant={'caption'}
-            localizationKey={localizationKeys('commerce.availableFeatures')}
-            colorScheme='secondary'
-            sx={t => ({
-              padding: t.space.$4,
-              paddingBottom: 0,
-            })}
-          />
-          <Box
-            elementDescriptor={descriptors.planDetailFeaturesList}
-            as='ul'
-            role='list'
-            sx={t => ({
-              display: 'grid',
-              rowGap: t.space.$6,
-              padding: t.space.$4,
-              margin: 0,
-            })}
-          >
-            {features.map(feature => (
-              <Box
-                key={feature.id}
-                elementDescriptor={descriptors.planDetailFeaturesListItem}
-                as='li'
-                sx={t => ({
-                  display: 'flex',
-                  alignItems: 'baseline',
-                  gap: t.space.$3,
-                })}
-              >
-                {feature.avatarUrl ? (
-                  <Avatar
-                    size={_ => 24}
-                    title={feature.name}
-                    initials={feature.name[0]}
-                    rounded={false}
-                    imageUrl={feature.avatarUrl}
-                  />
-                ) : null}
-                <Span elementDescriptor={descriptors.planDetailFeaturesListItemContent}>
-                  <Text
-                    elementDescriptor={descriptors.planDetailFeaturesListItemTitle}
-                    colorScheme='body'
-                    sx={t => ({
-                      fontWeight: t.fontWeights.$medium,
-                    })}
-                  >
-                    {feature.name}
-                  </Text>
-                  {feature.description ? (
-                    <Text
-                      elementDescriptor={descriptors.planDetailFeaturesListItemDescription}
-                      colorScheme='secondary'
-                      sx={t => ({
-                        marginBlockStart: t.space.$0x25,
-                      })}
-                    >
-                      {feature.description}
-                    </Text>
-                  ) : null}
-                </Span>
-              </Box>
-            ))}
-          </Box>
-        </Drawer.Body>
-      ) : null} */}
-
-      {/* {(!plan.isDefault && !isDefaultPlanImplicitlyActiveOrUpcoming) || !subscription ? (
+      {/* If either the active or upcoming subscription is the free plan, then a C1 cannot switch to a different period or cancel the plan */}
+      {!subscription?.plan.isDefault ? (
         <Drawer.Footer>
-          {subscription ? (
-            subscription.canceledAt ? (
+          <Col gap={4}>
+            {subscription.planPeriod === 'month' && subscription.plan.annualMonthlyAmount > 0 && (
               <Button
                 block
+                variant='bordered'
+                colorScheme='secondary'
                 textVariant='buttonLarge'
-                {...buttonPropsForPlan({ plan })}
-                onClick={() => openCheckout()}
+                isDisabled={!canManageBilling}
+                // onClick={() => openCheckout({ planPeriod: 'annual' })}
+                onClick={() => {
+                  openCheckout({
+                    planId: subscription.plan.id,
+                    planPeriod: subscription.planPeriod === 'month' ? 'annual' : 'month',
+                    subscriberType: subscriberType,
+                  });
+                }}
+                localizationKey={
+                  subscription.planPeriod === 'month'
+                    ? localizationKeys('commerce.switchToAnnual')
+                    : localizationKeys('commerce.switchToMonthly')
+                }
               />
-            ) : (
-              <Col gap={4}>
-                {!!subscription &&
-                subscription.planPeriod === 'month' &&
-                plan.annualMonthlyAmount > 0 &&
-                planPeriod === 'annual' ? (
-                  <Button
-                    block
-                    variant='bordered'
-                    colorScheme='secondary'
-                    textVariant='buttonLarge'
-                    isDisabled={!canManageBilling}
-                    onClick={() => openCheckout({ planPeriod: 'annual' })}
-                    localizationKey={localizationKeys('commerce.switchToAnnual')}
-                  />
-                ) : null}
-                {!!subscription && subscription.planPeriod === 'annual' && planPeriod === 'month' ? (
+            )}
+
+            <Button
+              block
+              variant='bordered'
+              colorScheme='danger'
+              textVariant='buttonLarge'
+              isDisabled={!canManageBilling}
+              onClick={() => setShowConfirmation(true)}
+              localizationKey={localizationKeys('commerce.cancelSubscription')}
+            />
+          </Col>
+
+          <Drawer.Confirmation
+            open={showConfirmation}
+            onOpenChange={setShowConfirmation}
+            actionsSlot={
+              <>
+                {!isSubmitting && (
                   <Button
-                    block
-                    variant='bordered'
-                    colorScheme='secondary'
+                    variant='ghost'
+                    size='sm'
                     textVariant='buttonLarge'
                     isDisabled={!canManageBilling}
-                    onClick={() => openCheckout({ planPeriod: 'month' })}
-                    localizationKey={localizationKeys('commerce.switchToMonthly')}
+                    onClick={() => {
+                      setCancelError(undefined);
+                      setShowConfirmation(false);
+                    }}
+                    localizationKey={localizationKeys('commerce.keepSubscription')}
                   />
-                ) : null}
+                )}
                 <Button
-                  block
-                  variant='bordered'
+                  variant='solid'
                   colorScheme='danger'
-                  textVariant='buttonLarge'
-                  isDisabled={!canManageBilling}
-                  onClick={() => setShowConfirmation(true)}
-                  localizationKey={localizationKeys('commerce.cancelSubscription')}
-                />
-              </Col>
-            )
-          ) : (
-            <Button
-              block
-              textVariant='buttonLarge'
-              {...buttonPropsForPlan({ plan })}
-              onClick={() => openCheckout()}
-            />
-          )}
-        </Drawer.Footer>
-      ) : null} */}
-
-      {/* {subscription ? (
-        <Drawer.Confirmation
-          open={showConfirmation}
-          onOpenChange={setShowConfirmation}
-          actionsSlot={
-            <>
-              {!isSubmitting && (
-                <Button
-                  variant='ghost'
                   size='sm'
                   textVariant='buttonLarge'
+                  isLoading={isSubmitting}
                   isDisabled={!canManageBilling}
                   onClick={() => {
                     setCancelError(undefined);
                     setShowConfirmation(false);
+                    void cancelSubscription(subscription);
                   }}
-                  localizationKey={localizationKeys('commerce.keepSubscription')}
+                  localizationKey={localizationKeys('commerce.cancelSubscription')}
                 />
-              )}
-              <Button
-                variant='solid'
-                colorScheme='danger'
-                size='sm'
-                textVariant='buttonLarge'
-                isLoading={isSubmitting}
-                isDisabled={!canManageBilling}
-                onClick={() => {
-                  setCancelError(undefined);
-                  setShowConfirmation(false);
-                  void cancelSubscription();
-                }}
-                localizationKey={localizationKeys('commerce.cancelSubscription')}
-              />
-            </>
-          }
+              </>
+            }
+          >
+            <Heading
+              elementDescriptor={descriptors.drawerConfirmationTitle}
+              as='h2'
+              textVariant='h3'
+              localizationKey={localizationKeys('commerce.cancelSubscriptionTitle', {
+                plan: `${subscription.status === 'upcoming' ? 'upcoming ' : ''}${subscription.plan.name}`,
+              })}
+            />
+            <Text
+              elementDescriptor={descriptors.drawerConfirmationDescription}
+              colorScheme='secondary'
+              localizationKey={
+                subscription.status === 'upcoming'
+                  ? localizationKeys('commerce.cancelSubscriptionNoCharge')
+                  : localizationKeys('commerce.cancelSubscriptionAccessUntil', {
+                      plan: subscription.plan.name,
+                      date: subscription.periodEnd,
+                    })
+              }
+            />
+            {cancelError && (
+              <Alert colorScheme='danger'>{typeof cancelError === 'string' ? cancelError : cancelError.message}</Alert>
+            )}
+          </Drawer.Confirmation>
+        </Drawer.Footer>
+      ) : null}
+    </>
+  );
+};
+
+// New component for individual subscription cards
+const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscriptionResource }) => {
+  const isActive = subscription.status === 'active';
+
+  return (
+    <Col
+      sx={t => ({
+        borderWidth: t.borderWidths.$normal,
+        borderStyle: t.borderStyles.$solid,
+        borderColor: t.colors.$neutralAlpha100,
+        borderRadius: t.radii.$md,
+      })}
+    >
+      <Col
+        gap={3}
+        sx={t => ({
+          padding: t.space.$3,
+        })}
+      >
+        {/* Header with name and badge */}
+        <Flex
+          justify='between'
+          align='center'
         >
-          <Heading
-            elementDescriptor={descriptors.drawerConfirmationTitle}
-            as='h2'
-            textVariant='h3'
-            localizationKey={localizationKeys('commerce.cancelSubscriptionTitle', {
-              plan: `${subscription.status === 'upcoming' ? 'upcoming ' : ''}${subscription.plan.name}`,
-            })}
-          />
           <Text
-            elementDescriptor={descriptors.drawerConfirmationDescription}
-            colorScheme='secondary'
-            localizationKey={
-              subscription.status === 'upcoming'
-                ? localizationKeys('commerce.cancelSubscriptionNoCharge')
-                : localizationKeys('commerce.cancelSubscriptionAccessUntil', {
-                    plan: subscription.plan.name,
-                    date: subscription.periodEnd,
-                  })
-            }
+            sx={{
+              fontSize: '16px',
+              fontWeight: '600',
+              color: '#333',
+            }}
+          >
+            {subscription.plan.name}
+          </Text>
+          <Badge
+            colorScheme={isActive ? 'secondary' : 'primary'}
+            localizationKey={isActive ? localizationKeys('badge__activePlan') : localizationKeys('badge__upcomingPlan')}
           />
-          {cancelError && (
-            <Alert colorScheme='danger'>{typeof cancelError === 'string' ? cancelError : cancelError.message}</Alert>
-          )}
-        </Drawer.Confirmation>
-      ) : null} */}
-    </>
+        </Flex>
+
+        {/* Pricing details */}
+        <Box
+          elementDescriptor={descriptors.statementSectionContentDetailsList}
+          as='ul'
+          sx={t => ({
+            margin: 0,
+            padding: 0,
+            borderWidth: t.borderWidths.$normal,
+            borderStyle: t.borderStyles.$solid,
+            borderColor: t.colors.$neutralAlpha100,
+            borderRadius: t.radii.$md,
+            overflow: 'hidden',
+          })}
+        >
+          <PriceItem
+            labelIcon={subscription.planPeriod === 'month' ? Check : undefined}
+            label='Monthly price'
+            value={`${subscription.plan.currencySymbol}${subscription.plan.amountFormatted} / mo`}
+          />
+          <PriceItem
+            labelIcon={subscription.planPeriod === 'annual' ? Check : undefined}
+            label='Annual discount'
+            value={`${subscription.plan.currencySymbol}${subscription.plan.annualMonthlyAmountFormatted} / mo`}
+          />
+        </Box>
+      </Col>
+
+      {isActive ? (
+        <>
+          <DetailRow
+            label='Subscribed on'
+            // TODO: Use localization for dates
+            value={formatDate(subscription.createdAt)}
+          />
+          <DetailRow
+            label={subscription.canceledAt ? 'Ends on' : 'Renews at'}
+            value={formatDate(subscription.periodEnd)}
+          />
+        </>
+      ) : (
+        <DetailRow
+          label='Begins on'
+          value={formatDate(subscription.periodStart)}
+        />
+      )}
+    </Col>
   );
 };
 
+// Helper component for detail rows
+const DetailRow = ({ label, value }: { label: string; value: string }) => (
+  <Flex
+    justify='between'
+    align='center'
+    sx={t => ({
+      paddingInline: t.space.$3,
+      paddingBlock: t.space.$3,
+      borderBlockStartWidth: t.borderWidths.$normal,
+      borderBlockStartStyle: t.borderStyles.$solid,
+      borderBlockStartColor: t.colors.$neutralAlpha100,
+    })}
+  >
+    <Text>{label}</Text>
+    <Text colorScheme='secondary'>{value}</Text>
+  </Flex>
+);
+
 function PriceItem({
   labelIcon,
   label,
-  valueCopyable = false,
+  valueCopyable: _valueCopyable = false,
   value,
   valueTruncated = false,
 }: {
@@ -405,256 +419,147 @@ function PriceItem({
       as='li'
       sx={t => ({
         margin: 0,
-        paddingInline: t.space.$2,
-        paddingBlock: t.space.$1x5,
+        background: common.mergedColorsBackground(
+          colors.setAlpha(t.colors.$colorBackground, 1),
+          t.colors.$neutralAlpha50,
+        ),
         display: 'flex',
-        justifyContent: 'space-between',
-        flexWrap: 'wrap',
-        columnGap: t.space.$2,
-        rowGap: t.space.$0x5,
-        '&:not(:first-child)': {
+        '&:not(:first-of-type)': {
           borderBlockStartWidth: t.borderWidths.$normal,
           borderBlockStartStyle: t.borderStyles.$solid,
           borderBlockStartColor: t.colors.$neutralAlpha100,
         },
+        '&:first-of-type #test': {
+          borderTopLeftRadius: t.radii.$md,
+          borderTopRightRadius: t.radii.$md,
+        },
+        '&:last-of-type #test': {
+          borderBottomLeftRadius: t.radii.$md,
+          borderBottomRightRadius: t.radii.$md,
+        },
       })}
     >
-      <Span
-        elementDescriptor={descriptors.statementSectionContentDetailsListItemLabelContainer}
+      <Flex
+        justify='center'
+        align='center'
         sx={t => ({
-          display: 'flex',
-          alignItems: 'center',
-          gap: t.space.$1x5,
+          width: t.space.$8,
+          paddingInline: t.space.$2,
+          paddingBlock: t.space.$1x5,
         })}
       >
         {labelIcon ? (
           <Icon
             icon={labelIcon}
+            size='xs'
             colorScheme='neutral'
           />
         ) : null}
-        <Text
-          variant='caption'
-          colorScheme='secondary'
-          elementDescriptor={descriptors.statementSectionContentDetailsListItemLabel}
-          localizationKey={label}
-        />
-      </Span>
-      <Span
+      </Flex>
+
+      <Box
+        id='test'
         sx={t => ({
+          flex: 1,
           display: 'flex',
-          alignItems: 'center',
-          gap: t.space.$0x25,
-          color: t.colors.$colorTextSecondary,
+          justifyContent: 'space-between',
+          flexWrap: 'wrap',
+          background: t.colors.$colorBackground,
+          paddingInline: t.space.$2,
+          paddingBlock: t.space.$1x5,
+          marginBlock: -1,
+          marginInline: -1,
+          boxShadow: `inset 0px 0px 0px ${t.borderWidths.$normal} ${t.colors.$neutralAlpha100}`,
         })}
       >
-        {typeof value === 'string' ? (
+        <Span
+          elementDescriptor={descriptors.statementSectionContentDetailsListItemLabelContainer}
+          sx={t => ({
+            display: 'flex',
+            alignItems: 'center',
+            gap: t.space.$1x5,
+          })}
+        >
           <Text
-            colorScheme='secondary'
             variant='caption'
-            elementDescriptor={descriptors.statementSectionContentDetailsListItemValue}
-          >
-            {valueTruncated ? truncateWithEndVisible(value) : value}
-          </Text>
-        ) : (
-          <Text
-            elementDescriptor={descriptors.statementSectionContentDetailsListItemValue}
             colorScheme='secondary'
-            variant='caption'
-            localizationKey={value}
+            elementDescriptor={descriptors.statementSectionContentDetailsListItemLabel}
+            localizationKey={label}
           />
-        )}
-      </Span>
+        </Span>
+        <Span
+          sx={t => ({
+            display: 'flex',
+            alignItems: 'center',
+            gap: t.space.$0x25,
+            color: t.colors.$colorTextSecondary,
+          })}
+        >
+          {typeof value === 'string' ? (
+            <Text
+              colorScheme='secondary'
+              variant='caption'
+              elementDescriptor={descriptors.statementSectionContentDetailsListItemValue}
+            >
+              {valueTruncated ? truncateWithEndVisible(value) : value}
+            </Text>
+          ) : (
+            <Text
+              elementDescriptor={descriptors.statementSectionContentDetailsListItemValue}
+              colorScheme='secondary'
+              variant='caption'
+              localizationKey={value}
+            />
+          )}
+        </Span>
+      </Box>
     </Box>
   );
 }
 
-/* -------------------------------------------------------------------------------------------------
- * Header
- * -----------------------------------------------------------------------------------------------*/
-
-interface HeaderProps {
-  plan: CommercePlanResource;
-  subscription?: CommerceSubscriptionResource;
-  planPeriod: CommerceSubscriptionPlanPeriod;
-  setPlanPeriod: (val: CommerceSubscriptionPlanPeriod) => void;
-  closeSlot?: React.ReactNode;
-}
-
-const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
-  const { plan, subscription, closeSlot, planPeriod, setPlanPeriod } = props;
-
-  const { captionForSubscription, isDefaultPlanImplicitlyActiveOrUpcoming } = usePlansContext();
-  const { data: subscriptions } = useSubscriptions();
-
-  const isImplicitlyActiveOrUpcoming = isDefaultPlanImplicitlyActiveOrUpcoming && plan.isDefault;
-
-  const showBadge = !!subscription;
-
-  const getPlanFee = useMemo(() => {
-    if (plan.annualMonthlyAmount <= 0) {
-      return plan.amountFormatted;
-    }
-    return planPeriod === 'annual' ? plan.annualMonthlyAmountFormatted : plan.amountFormatted;
-  }, [plan, planPeriod]);
-
+function SummaryItem(props: React.PropsWithChildren) {
   return (
     <Box
-      ref={ref}
-      elementDescriptor={descriptors.planDetailHeader}
+      elementDescriptor={descriptors.statementSectionContentDetailsListItem}
+      as='li'
       sx={t => ({
-        width: '100%',
-        padding: t.space.$4,
-        position: 'relative',
+        paddingInline: t.space.$4,
+        display: 'flex',
+        justifyContent: 'space-between',
+        flexWrap: 'wrap',
       })}
     >
-      {closeSlot ? (
-        <Box
-          sx={t => ({
-            position: 'absolute',
-            top: t.space.$2,
-            insetInlineEnd: t.space.$2,
-          })}
-        >
-          {closeSlot}
-        </Box>
-      ) : null}
-
-      <Col
-        gap={3}
-        elementDescriptor={descriptors.planDetailBadgeAvatarTitleDescriptionContainer}
-      >
-        {showBadge ? (
-          <Flex
-            align='center'
-            gap={3}
-            elementDescriptor={descriptors.planDetailBadgeContainer}
-            sx={t => ({
-              paddingInlineEnd: t.space.$10,
-            })}
-          >
-            {subscription?.status === 'active' || (isImplicitlyActiveOrUpcoming && subscriptions.length === 0) ? (
-              <Badge
-                elementDescriptor={descriptors.planDetailBadge}
-                localizationKey={localizationKeys('badge__activePlan')}
-                colorScheme={'secondary'}
-              />
-            ) : (
-              <Badge
-                elementDescriptor={descriptors.planDetailBadge}
-                localizationKey={localizationKeys('badge__upcomingPlan')}
-                colorScheme={'primary'}
-              />
-            )}
-            {!!subscription && (
-              <Text
-                elementDescriptor={descriptors.planDetailCaption}
-                variant={'caption'}
-                localizationKey={captionForSubscription(subscription)}
-                colorScheme='secondary'
-              />
-            )}
-          </Flex>
-        ) : null}
-        {plan.avatarUrl ? (
-          <Avatar
-            boxElementDescriptor={descriptors.planDetailAvatar}
-            size={_ => 40}
-            title={plan.name}
-            initials={plan.name[0]}
-            rounded={false}
-            imageUrl={plan.avatarUrl}
-            sx={t => ({
-              marginBlockEnd: t.space.$3,
-            })}
-          />
-        ) : null}
-        <Col
-          gap={1}
-          elementDescriptor={descriptors.planDetailTitleDescriptionContainer}
-        >
-          <Heading
-            elementDescriptor={descriptors.planDetailTitle}
-            as='h2'
-            textVariant='h2'
-          >
-            {plan.name}
-          </Heading>
-          {plan.description ? (
-            <Text
-              elementDescriptor={descriptors.planDetailDescription}
-              variant='subtitle'
-              colorScheme='secondary'
-            >
-              {plan.description}
-            </Text>
-          ) : null}
-        </Col>
-      </Col>
+      {props.children}
+    </Box>
+  );
+}
 
-      <Flex
-        elementDescriptor={descriptors.planDetailFeeContainer}
-        align='center'
-        wrap='wrap'
-        sx={t => ({
-          marginTop: t.space.$3,
-          columnGap: t.space.$1x5,
-        })}
-      >
-        <>
-          <Text
-            elementDescriptor={descriptors.planDetailFee}
-            variant='h1'
-            colorScheme='body'
-          >
-            {plan.currencySymbol}
-            {getPlanFee}
-          </Text>
-          <Text
-            elementDescriptor={descriptors.planDetailFeePeriod}
-            variant='caption'
-            colorScheme='secondary'
-            sx={t => ({
-              textTransform: 'lowercase',
-              ':before': {
-                content: '"/"',
-                marginInlineEnd: t.space.$1,
-              },
-            })}
-            localizationKey={localizationKeys('commerce.month')}
-          />
-        </>
-      </Flex>
+function SummmaryItemLabel(props: React.PropsWithChildren) {
+  return (
+    <Span
+      elementDescriptor={descriptors.statementSectionContentDetailsListItemLabelContainer}
+      sx={t => ({
+        display: 'flex',
+        alignItems: 'center',
+        gap: t.space.$1x5,
+      })}
+    >
+      {props.children}
+    </Span>
+  );
+}
 
-      {plan.annualMonthlyAmount > 0 ? (
-        <Box
-          elementDescriptor={descriptors.planDetailPeriodToggle}
-          sx={t => ({
-            display: 'flex',
-            marginTop: t.space.$3,
-          })}
-        >
-          <Switch
-            isChecked={planPeriod === 'annual'}
-            onChange={(checked: boolean) => setPlanPeriod(checked ? 'annual' : 'month')}
-            label={localizationKeys('commerce.billedAnnually')}
-          />
-        </Box>
-      ) : (
-        <Text
-          elementDescriptor={descriptors.pricingTableCardFeePeriodNotice}
-          variant='caption'
-          colorScheme='secondary'
-          localizationKey={
-            plan.isDefault ? localizationKeys('commerce.alwaysFree') : localizationKeys('commerce.billedMonthlyOnly')
-          }
-          sx={t => ({
-            justifySelf: 'flex-start',
-            alignSelf: 'center',
-            marginTop: t.space.$3,
-          })}
-        />
-      )}
-    </Box>
+function SummmaryItemValue(props: React.PropsWithChildren) {
+  return (
+    <Span
+      elementDescriptor={descriptors.statementSectionContentDetailsListItemLabelContainer}
+      sx={t => ({
+        display: 'flex',
+        alignItems: 'center',
+        gap: t.space.$0x25,
+      })}
+    >
+      {props.children}
+    </Span>
   );
-});
+}
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index 7e35dcca5c7..6743234858a 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -117,8 +117,9 @@ export const useSubscriptions = () => {
           plan_period: 'month',
           canceled_at: null,
           status: _subscriptions.data.length === 0 ? 'active' : 'upcoming',
-          period_start: canceledSubscription?.periodEnd || 0,
+          period_start: canceledSubscription?.periodEnd?.getTime() || 0,
           period_end: 0,
+          created_at: canceledSubscription?.periodEnd?.getTime() || 0,
         }),
       ];
     }
diff --git a/packages/types/src/commerce.ts b/packages/types/src/commerce.ts
index 3caf459548d..685a0305abc 100644
--- a/packages/types/src/commerce.ts
+++ b/packages/types/src/commerce.ts
@@ -155,9 +155,10 @@ export interface CommerceSubscriptionResource extends ClerkResource {
   plan: CommercePlanResource;
   planPeriod: CommerceSubscriptionPlanPeriod;
   status: CommerceSubscriptionStatus;
-  periodStart: number;
-  periodEnd: number;
-  canceledAt: number | null;
+  createdAt: Date;
+  periodStart: Date;
+  periodEnd: Date;
+  canceledAt: Date | null;
   amount?: CommerceMoney;
   credit?: {
     amount: CommerceMoney;
diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts
index dbf38dcb0c7..202d5d0a2bb 100644
--- a/packages/types/src/json.ts
+++ b/packages/types/src/json.ts
@@ -697,6 +697,7 @@ export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
   credit?: {
     amount: CommerceMoneyJSON;
   };
+  created_at: number;
   payment_source_id: string;
   plan: CommercePlanJSON;
   plan_period: CommerceSubscriptionPlanPeriod;

From 7af1561a5457728eead21cb360ef5548dff0fd6c Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 18 Jun 2025 18:32:05 +0300
Subject: [PATCH 04/34] display button for annual to switch to monthly as well

---
 .../clerk-js/src/ui/components/SubscriptionDetails/index.tsx   | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index d93a88b03b0..9aaf8188b94 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -194,7 +194,8 @@ const SubscriptionDetailsInternal = ({ onSubscriptionCancel, portalRoot }: __exp
       {!subscription?.plan.isDefault ? (
         <Drawer.Footer>
           <Col gap={4}>
-            {subscription.planPeriod === 'month' && subscription.plan.annualMonthlyAmount > 0 && (
+            {((subscription.planPeriod === 'month' && subscription.plan.annualMonthlyAmount > 0) ||
+              subscription.planPeriod === 'annual') && (
               <Button
                 block
                 variant='bordered'

From 99607d7f001200e75981e61d115e6f78a2156daa Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 1 Jul 2025 12:06:49 +0300
Subject: [PATCH 05/34] implement new UI

---
 .../src/ui/components/Plans/PlanDetails.tsx   |   7 +-
 .../ui/components/Plans/old_PlanDetails.tsx   |   2 +-
 .../components/SubscriptionDetails/index.tsx  | 705 ++++++++++--------
 .../components/SubscriptionDetails.ts         |  20 +
 .../src/ui/elements/ThreeDotsMenu.tsx         |  39 +-
 packages/clerk-js/src/ui/types.ts             |   5 +
 packages/localizations/src/en-US.ts           |   2 +
 packages/types/src/localization.ts            |   2 +
 8 files changed, 432 insertions(+), 350 deletions(-)
 create mode 100644 packages/clerk-js/src/ui/contexts/components/SubscriptionDetails.ts

diff --git a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
index d2a8ad23b02..7c30cd7f980 100644
--- a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
+++ b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
@@ -11,12 +11,9 @@ import useSWR from 'swr';
 import { Avatar } from '@/ui/elements/Avatar';
 import { Drawer } from '@/ui/elements/Drawer';
 import { Switch } from '@/ui/elements/Switch';
-import { handleError } from '@/ui/utils/errorHandler';
 
-import { useProtect } from '../../common';
-import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
-import { Badge, Box, Button, Col, descriptors, Flex, Heading, localizationKeys, Span, Text } from '../../customizables';
-import { handleError } from '../../utils';
+import { SubscriberTypeContext } from '../../contexts';
+import { Box, Col, descriptors, Flex, Heading, localizationKeys, Span, Spinner, Text } from '../../customizables';
 
 export const PlanDetails = (props: __experimental_PlanDetailsProps) => {
   return (
diff --git a/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx b/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx
index 4bfb535d1df..3a967e241ea 100644
--- a/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx
+++ b/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx
@@ -14,11 +14,11 @@ import { Alert } from '@/ui/elements/Alert';
 import { Avatar } from '@/ui/elements/Avatar';
 import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
 import { Switch } from '@/ui/elements/Switch';
+import { handleError } from '@/ui/utils/errorHandler';
 
 import { useProtect } from '../../common';
 import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
 import { Badge, Box, Button, Col, descriptors, Flex, Heading, localizationKeys, Span, Text } from '../../customizables';
-import { handleError } from '../../utils';
 
 export const PlanDetails = (props: __internal_PlanDetailsProps) => {
   return (
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 9aaf8188b94..72e2d29b444 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -2,26 +2,27 @@ import { useClerk, useOrganization } from '@clerk/shared/react';
 import type {
   __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
-  ClerkAPIError,
-  ClerkRuntimeError,
   CommerceSubscriptionResource,
 } from '@clerk/types';
 import * as React from 'react';
-import { useState } from 'react';
+import { useCallback, useContext, useState } from 'react';
 
+import { useProtect } from '@/ui/common/Gate';
+import {
+  SubscriptionDetailsContext,
+  useSubscriptionDetailsContext,
+} from '@/ui/contexts/components/SubscriptionDetails';
+import { Avatar } from '@/ui/elements/Avatar';
+import { CardAlert } from '@/ui/elements/Card/CardAlert';
+import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
 import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
-import { Check } from '@/ui/icons';
-import { common } from '@/ui/styledSystem/common';
-import { colors } from '@/ui/utils/colors';
+import { ThreeDotsMenu } from '@/ui/elements/ThreeDotsMenu';
+import { ThreeDots } from '@/ui/icons';
 import { handleError } from '@/ui/utils/errorHandler';
 import { formatDate } from '@/ui/utils/formatDate';
-import { truncateWithEndVisible } from '@/ui/utils/truncateTextWithEndVisible';
 
-import { useProtect } from '../../common';
 import { usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
-import type { LocalizationKey } from '../../customizables';
 import {
-  Alert,
   Badge,
   Box,
   Button,
@@ -34,35 +35,73 @@ import {
   Span,
   Spinner,
   Text,
+  useLocalizations,
 } from '../../customizables';
 
+const SubscriptionForCancellationContext = React.createContext<{
+  subscription: CommerceSubscriptionResource | null;
+  setSubscription: (subscription: CommerceSubscriptionResource | null) => void;
+}>({
+  subscription: null,
+  setSubscription: () => {},
+});
+
 export const SubscriptionDetails = (props: __experimental_SubscriptionDetailsProps) => {
   return (
     <Drawer.Content>
-      <SubscriptionDetailsInternal {...props} />
+      <SubscriptionDetailsContext.Provider value={{ componentName: 'SubscriptionDetails', ...props }}>
+        <SubscriptionDetailsInternal {...props} />
+      </SubscriptionDetailsContext.Provider>
     </Drawer.Content>
   );
 };
 
-const SubscriptionDetailsInternal = ({ onSubscriptionCancel, portalRoot }: __experimental_SubscriptionDetailsProps) => {
-  const clerk = useClerk();
+type UseGuessableSubscriptionResult<Or extends 'throw' | undefined = undefined> = Or extends 'throw'
+  ? {
+      upcomingSubscription?: CommerceSubscriptionResource;
+      activeSubscription: CommerceSubscriptionResource;
+      anySubscription: CommerceSubscriptionResource;
+      isLoading: boolean;
+    }
+  : {
+      upcomingSubscription?: CommerceSubscriptionResource;
+      activeSubscription?: CommerceSubscriptionResource;
+      anySubscription?: CommerceSubscriptionResource;
+      isLoading: boolean;
+    };
+
+function useGuessableSubscription<Or extends 'throw' | undefined = undefined>(options?: {
+  or?: Or;
+}): UseGuessableSubscriptionResult<Or> {
+  const { data: subscriptions, isLoading } = useSubscriptions();
+  const activeSubscription = subscriptions?.find(sub => sub.status === 'active');
+  const upcomingSubscription = subscriptions?.find(sub => sub.status === 'upcoming');
+
+  if (options?.or === 'throw' && !activeSubscription) {
+    throw new Error('No active subscription found');
+  }
+
+  return {
+    upcomingSubscription,
+    activeSubscription: activeSubscription as any, // Type is correct due to the throw above
+    anySubscription: (upcomingSubscription || activeSubscription) as any,
+    isLoading,
+  };
+}
+
+const SubscriptionDetailsInternal = (props: __experimental_SubscriptionDetailsProps) => {
   const { organization: _organization } = useOrganization();
-  const [showConfirmation, setShowConfirmation] = useState(false);
-  const [isSubmitting, setIsSubmitting] = useState(false);
-  const [cancelError, setCancelError] = useState<ClerkRuntimeError | ClerkAPIError | string | undefined>();
+  const [subscriptionForCancellation, setSubscriptionForCancellation] = useState<CommerceSubscriptionResource | null>(
+    null,
+  );
 
-  const { setIsOpen } = useDrawerContext();
   const {
-    revalidateAll,
     buttonPropsForPlan: _buttonPropsForPlan,
     isDefaultPlanImplicitlyActiveOrUpcoming: _isDefaultPlanImplicitlyActiveOrUpcoming,
   } = usePlansContext();
-  const subscriberType = useSubscriberTypeContext();
-  const canManageBilling = useProtect(
-    has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
-  );
 
   const { data: subscriptions, isLoading } = useSubscriptions();
+  const { activeSubscription } = useGuessableSubscription();
 
   if (isLoading) {
     return (
@@ -74,57 +113,15 @@ const SubscriptionDetailsInternal = ({ onSubscriptionCancel, portalRoot }: __exp
     );
   }
 
-  const handleClose = () => {
-    if (setIsOpen) {
-      setIsOpen(false);
-    }
-  };
-
-  const cancelSubscription = async (subscription: CommerceSubscriptionResource) => {
-    setCancelError(undefined);
-    setIsSubmitting(true);
-
-    await subscription
-      .cancel(
-        // { orgId: subscriberType === 'org' ? organization?.id : undefined }
-        {},
-      )
-      .then(() => {
-        setIsSubmitting(false);
-        onSubscriptionCancel?.();
-        handleClose();
-      })
-      .catch(error => {
-        handleError(error, [], setCancelError);
-        setIsSubmitting(false);
-      });
-  };
-
-  const openCheckout = (params?: __internal_CheckoutProps) => {
-    handleClose();
-
-    clerk.__internal_openCheckout({
-      ...params,
-      onSubscriptionComplete: () => {
-        void revalidateAll();
-      },
-      portalRoot,
-    });
-  };
-
-  // Mock data for demonstration - in real implementation this would come from the subscriptions data
-  const activeSubscription = subscriptions?.find(sub => sub.status === 'active');
-  const upcomingSubscription = subscriptions?.find(sub => sub.status === 'upcoming');
-
   if (!activeSubscription) {
-    // Should never happen, but just in case
+    // Should never happen, since Free will always be active
     return null;
   }
 
-  const subscription = upcomingSubscription || activeSubscription;
-
   return (
-    <>
+    <SubscriptionForCancellationContext.Provider
+      value={{ subscription: subscriptionForCancellation, setSubscription: setSubscriptionForCancellation }}
+    >
       <Drawer.Header title='Subscription' />
 
       <Drawer.Body>
@@ -140,130 +137,96 @@ const SubscriptionDetailsInternal = ({ onSubscriptionCancel, portalRoot }: __exp
             <SubscriptionCard
               key={subscriptionItem.id}
               subscription={subscriptionItem}
+              {...props}
             />
           ))}
         </Col>
+      </Drawer.Body>
 
-        {/* Billing Information */}
+      <SubscriptionDetailsFooter />
+    </SubscriptionForCancellationContext.Provider>
+  );
+};
 
-        <Col
-          gap={3}
-          as='ul'
-          sx={t => ({
-            marginTop: 'auto',
-            paddingBlock: t.space.$4,
-            borderTopWidth: t.borderWidths.$normal,
-            borderTopStyle: t.borderStyles.$solid,
-            borderTopColor: t.colors.$neutralAlpha100,
-          })}
-        >
-          <SummaryItem>
-            <SummmaryItemLabel>
-              <Text colorScheme='secondary'>Current billing cycle</Text>
-            </SummmaryItemLabel>
-            <SummmaryItemValue>
-              <Text colorScheme='secondary'>{activeSubscription.planPeriod === 'month' ? 'Monthly' : 'Annually'}</Text>
-            </SummmaryItemValue>
-          </SummaryItem>
-          <SummaryItem>
-            <SummmaryItemLabel>
-              <Text colorScheme='secondary'>Next payment on</Text>
-            </SummmaryItemLabel>
-            <SummmaryItemValue>
-              <Text colorScheme='secondary'>
-                {upcomingSubscription
-                  ? formatDate(upcomingSubscription.periodStart)
-                  : formatDate(subscription.periodEnd)}
-              </Text>
-            </SummmaryItemValue>
-          </SummaryItem>
-          <SummaryItem>
-            <SummmaryItemLabel>
-              <Text>Next payment amount</Text>
-            </SummmaryItemLabel>
-            <SummmaryItemValue>
-              <Text>
-                {`${subscription.plan.currencySymbol}${subscription.planPeriod === 'month' ? subscription.plan.amountFormatted : subscription.plan.annualAmountFormatted}`}
-              </Text>
-            </SummmaryItemValue>
-          </SummaryItem>
-        </Col>
-      </Drawer.Body>
+const SubscriptionDetailsFooter = withCardStateProvider(() => {
+  const subscriberType = useSubscriberTypeContext();
+  const { organization } = useOrganization();
+  const { isLoading, error, setError, setLoading, setIdle } = useCardState();
+  const { subscription, setSubscription } = useContext(SubscriptionForCancellationContext);
+  const { anySubscription } = useGuessableSubscription({ or: 'throw' });
+  const { setIsOpen } = useDrawerContext();
+  const { onSubscriptionCancel } = useSubscriptionDetailsContext();
+
+  const onOpenChange = useCallback(
+    (open: boolean) => setSubscription(open ? subscription : null),
+    [subscription, setSubscription],
+  );
+
+  const cancelSubscription = useCallback(async () => {
+    if (!subscription) {
+      return;
+    }
+
+    setError(undefined);
+    setLoading();
+
+    await subscription
+      .cancel({ orgId: subscriberType === 'org' ? organization?.id : undefined })
+      .then(() => {
+        onSubscriptionCancel?.();
+        if (setIsOpen) {
+          setIsOpen(false);
+        }
+      })
+      .catch(error => {
+        handleError(error, [], setError);
+      })
+      .finally(() => {
+        setIdle();
+      });
+  }, [subscription, setError, setLoading, subscriberType, organization?.id, onSubscriptionCancel, setIsOpen, setIdle]);
 
-      {/* If either the active or upcoming subscription is the free plan, then a C1 cannot switch to a different period or cancel the plan */}
-      {!subscription?.plan.isDefault ? (
-        <Drawer.Footer>
-          <Col gap={4}>
-            {((subscription.planPeriod === 'month' && subscription.plan.annualMonthlyAmount > 0) ||
-              subscription.planPeriod === 'annual') && (
+  // If either the active or upcoming subscription is the free plan, then a C1 cannot switch to a different period or cancel the plan
+  if (anySubscription.plan.isDefault) {
+    return null;
+  }
+
+  return (
+    <Drawer.Footer>
+      <SubscriptionDetailsSummary />
+
+      <Drawer.Confirmation
+        open={!!subscription}
+        onOpenChange={onOpenChange}
+        actionsSlot={
+          <>
+            {!isLoading && (
               <Button
-                block
-                variant='bordered'
-                colorScheme='secondary'
+                variant='ghost'
+                size='sm'
                 textVariant='buttonLarge'
-                isDisabled={!canManageBilling}
-                // onClick={() => openCheckout({ planPeriod: 'annual' })}
                 onClick={() => {
-                  openCheckout({
-                    planId: subscription.plan.id,
-                    planPeriod: subscription.planPeriod === 'month' ? 'annual' : 'month',
-                    subscriberType: subscriberType,
-                  });
+                  setIdle();
+                  setError(undefined);
+                  onOpenChange(false);
                 }}
-                localizationKey={
-                  subscription.planPeriod === 'month'
-                    ? localizationKeys('commerce.switchToAnnual')
-                    : localizationKeys('commerce.switchToMonthly')
-                }
+                localizationKey={localizationKeys('commerce.keepSubscription')}
               />
             )}
-
             <Button
-              block
-              variant='bordered'
+              variant='solid'
               colorScheme='danger'
+              size='sm'
               textVariant='buttonLarge'
-              isDisabled={!canManageBilling}
-              onClick={() => setShowConfirmation(true)}
+              isLoading={isLoading}
+              onClick={() => void cancelSubscription()}
               localizationKey={localizationKeys('commerce.cancelSubscription')}
             />
-          </Col>
-
-          <Drawer.Confirmation
-            open={showConfirmation}
-            onOpenChange={setShowConfirmation}
-            actionsSlot={
-              <>
-                {!isSubmitting && (
-                  <Button
-                    variant='ghost'
-                    size='sm'
-                    textVariant='buttonLarge'
-                    isDisabled={!canManageBilling}
-                    onClick={() => {
-                      setCancelError(undefined);
-                      setShowConfirmation(false);
-                    }}
-                    localizationKey={localizationKeys('commerce.keepSubscription')}
-                  />
-                )}
-                <Button
-                  variant='solid'
-                  colorScheme='danger'
-                  size='sm'
-                  textVariant='buttonLarge'
-                  isLoading={isSubmitting}
-                  isDisabled={!canManageBilling}
-                  onClick={() => {
-                    setCancelError(undefined);
-                    setShowConfirmation(false);
-                    void cancelSubscription(subscription);
-                  }}
-                  localizationKey={localizationKeys('commerce.cancelSubscription')}
-                />
-              </>
-            }
-          >
+          </>
+        }
+      >
+        {subscription ? (
+          <>
             <Heading
               elementDescriptor={descriptors.drawerConfirmationTitle}
               as='h2'
@@ -284,19 +247,216 @@ const SubscriptionDetailsInternal = ({ onSubscriptionCancel, portalRoot }: __exp
                     })
               }
             />
-            {cancelError && (
-              <Alert colorScheme='danger'>{typeof cancelError === 'string' ? cancelError : cancelError.message}</Alert>
-            )}
-          </Drawer.Confirmation>
-        </Drawer.Footer>
-      ) : null}
-    </>
+            <CardAlert>{error}</CardAlert>
+          </>
+        ) : null}
+      </Drawer.Confirmation>
+    </Drawer.Footer>
+  );
+});
+
+function SubscriptionDetailsSummary() {
+  const { anySubscription, activeSubscription, upcomingSubscription } = useGuessableSubscription({ or: 'throw' });
+
+  if (!activeSubscription) {
+    return null;
+  }
+
+  return (
+    <Col
+      gap={3}
+      as='ul'
+      sx={t => ({
+        paddingBlock: t.space.$1,
+      })}
+    >
+      <SummaryItem>
+        <SummmaryItemLabel>
+          <Text colorScheme='secondary'>Current billing cycle</Text>
+        </SummmaryItemLabel>
+        <SummmaryItemValue>
+          <Text colorScheme='secondary'>{activeSubscription.planPeriod === 'month' ? 'Monthly' : 'Annually'}</Text>
+        </SummmaryItemValue>
+      </SummaryItem>
+      <SummaryItem>
+        <SummmaryItemLabel>
+          <Text colorScheme='secondary'>Next payment on</Text>
+        </SummmaryItemLabel>
+        <SummmaryItemValue>
+          <Text colorScheme='secondary'>
+            {upcomingSubscription
+              ? formatDate(upcomingSubscription.periodStart)
+              : formatDate(anySubscription.periodEnd)}
+          </Text>
+        </SummmaryItemValue>
+      </SummaryItem>
+      <SummaryItem>
+        <SummmaryItemLabel>
+          <Text>Next payment amount</Text>
+        </SummmaryItemLabel>
+        <SummmaryItemValue>
+          <Span
+            // elementDescriptor={descriptors.paymentAttemptFooterValueContainer}
+            sx={t => ({
+              display: 'flex',
+              alignItems: 'center',
+              gap: t.space.$1,
+            })}
+          >
+            <Text
+              variant='caption'
+              colorScheme='secondary'
+              // elementDescriptor={descriptors.paymentAttemptFooterCurrency}
+              sx={{ textTransform: 'uppercase' }}
+            >
+              {anySubscription.plan.currency}
+            </Text>
+            <Text
+            // variant='h3'
+            // elementDescriptor={descriptors.paymentAttemptFooterValue}
+            >
+              {anySubscription.plan.currencySymbol}
+              {anySubscription.planPeriod === 'month'
+                ? anySubscription.plan.amountFormatted
+                : anySubscription.plan.annualAmountFormatted}
+            </Text>
+          </Span>
+        </SummmaryItemValue>
+      </SummaryItem>
+    </Col>
+  );
+}
+
+const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubscriptionResource }) => {
+  const { portalRoot } = useSubscriptionDetailsContext();
+  const { __internal_openCheckout } = useClerk();
+  const subscriberType = useSubscriberTypeContext();
+  const { setIsOpen } = useDrawerContext();
+  const { revalidateAll } = usePlansContext();
+  const { setSubscription } = useContext(SubscriptionForCancellationContext);
+  const canOrgManageBilling = useProtect(has => has({ permission: 'org:sys_billing:manage' }));
+  const canManageBilling = subscriberType === 'user' || canOrgManageBilling;
+
+  const isSwitchable =
+    (subscription.planPeriod === 'month' && subscription.plan.annualMonthlyAmount > 0) ||
+    subscription.planPeriod === 'annual';
+  const isFreePlan = subscription.plan.isDefault;
+  const isCancellable = subscription.canceledAt === null && !isFreePlan;
+  const isReSubscribable = subscription.canceledAt !== null && !isFreePlan;
+
+  const openCheckout = useCallback(
+    (params?: __internal_CheckoutProps) => {
+      if (setIsOpen) {
+        setIsOpen(false);
+      }
+
+      __internal_openCheckout({
+        ...params,
+        onSubscriptionComplete: () => {
+          void revalidateAll();
+        },
+        portalRoot,
+      });
+    },
+    [__internal_openCheckout, revalidateAll, portalRoot, setIsOpen],
+  );
+
+  const actions = React.useMemo(() => {
+    if (!canManageBilling) {
+      return [];
+    }
+
+    return [
+      isSwitchable
+        ? {
+            label:
+              subscription.planPeriod === 'month'
+                ? localizationKeys('commerce.switchToAnnualWithAnnualPrice', {
+                    price: subscription.plan.annualAmountFormatted,
+                    currency: subscription.plan.currencySymbol,
+                  })
+                : localizationKeys('commerce.switchToMonthlyWithPrice', {
+                    price: subscription.plan.amountFormatted,
+                    currency: subscription.plan.currencySymbol,
+                  }),
+            onClick: () => {
+              openCheckout({
+                planId: subscription.plan.id,
+                planPeriod: subscription.planPeriod === 'month' ? 'annual' : 'month',
+                subscriberType,
+              });
+            },
+          }
+        : null,
+      isCancellable
+        ? {
+            isDestructive: true,
+            label: localizationKeys('commerce.cancelSubscription'),
+            onClick: () => {
+              setSubscription(subscription);
+            },
+          }
+        : null,
+      isReSubscribable
+        ? {
+            label: localizationKeys('commerce.reSubscribe'),
+            onClick: () => {
+              openCheckout({
+                planId: subscription.plan.id,
+                planPeriod: subscription.planPeriod,
+                subscriberType,
+              });
+            },
+          }
+        : null,
+    ].filter(a => a !== null);
+  }, [
+    isSwitchable,
+    subscription,
+    isCancellable,
+    openCheckout,
+    subscriberType,
+    setSubscription,
+    canManageBilling,
+    isReSubscribable,
+  ]);
+
+  if (actions.length === 0) {
+    return null;
+  }
+
+  return (
+    <ThreeDotsMenu
+      trigger={
+        <Button
+          aria-label='Manage subscription'
+          variant='bordered'
+          colorScheme='secondary'
+          sx={t => ({
+            width: t.sizes.$6,
+            height: t.sizes.$6,
+          })}
+          elementDescriptor={[descriptors.menuButton, descriptors.menuButtonEllipsis]}
+        >
+          <Icon
+            icon={ThreeDots}
+            sx={t => ({
+              width: t.sizes.$4,
+              height: t.sizes.$4,
+              opacity: t.opacity.$inactive,
+            })}
+          />
+        </Button>
+      }
+      actions={actions}
+    />
   );
 };
 
 // New component for individual subscription cards
 const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscriptionResource }) => {
   const isActive = subscription.status === 'active';
+  const { t } = useLocalizations();
 
   return (
     <Col
@@ -315,14 +475,30 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
       >
         {/* Header with name and badge */}
         <Flex
-          justify='between'
           align='center'
+          gap={2}
         >
+          <Avatar
+            boxElementDescriptor={descriptors.planDetailAvatar}
+            // TODO: Use size prop
+            size={_ => 40}
+            title={subscription.plan.name}
+            initials={subscription.plan.name[0]}
+            rounded={false}
+            // TODO: remove hardcoded image
+            imageUrl={subscription.plan.avatarUrl || 'https://i.ibb.co/s9GqfwtK/Frame-106.png'}
+            // TODO: remove hardcoded background
+            sx={{
+              background: 'unset',
+            }}
+          />
+
           <Text
             sx={{
               fontSize: '16px',
               fontWeight: '600',
               color: '#333',
+              marginInlineEnd: 'auto',
             }}
           >
             {subscription.plan.name}
@@ -334,30 +510,26 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
         </Flex>
 
         {/* Pricing details */}
-        <Box
+        <Flex
           elementDescriptor={descriptors.statementSectionContentDetailsList}
-          as='ul'
-          sx={t => ({
-            margin: 0,
-            padding: 0,
-            borderWidth: t.borderWidths.$normal,
-            borderStyle: t.borderStyles.$solid,
-            borderColor: t.colors.$neutralAlpha100,
-            borderRadius: t.radii.$md,
-            overflow: 'hidden',
-          })}
+          justify='between'
+          align='center'
         >
-          <PriceItem
-            labelIcon={subscription.planPeriod === 'month' ? Check : undefined}
-            label='Monthly price'
-            value={`${subscription.plan.currencySymbol}${subscription.plan.amountFormatted} / mo`}
-          />
-          <PriceItem
-            labelIcon={subscription.planPeriod === 'annual' ? Check : undefined}
-            label='Annual discount'
-            value={`${subscription.plan.currencySymbol}${subscription.plan.annualMonthlyAmountFormatted} / mo`}
-          />
-        </Box>
+          <Text
+            variant='body'
+            colorScheme='secondary'
+            sx={t => ({
+              fontWeight: t.fontWeights.$medium,
+              textTransform: 'lowercase',
+            })}
+          >
+            {subscription.planPeriod === 'month'
+              ? `${subscription.plan.currencySymbol}${subscription.plan.amountFormatted} / ${t(localizationKeys('commerce.month'))}`
+              : `${subscription.plan.currencySymbol}${subscription.plan.annualAmountFormatted} / ${t(localizationKeys('commerce.year'))}`}
+          </Text>
+
+          <SubscriptionCardActions subscription={subscription} />
+        </Flex>
       </Col>
 
       {isActive ? (
@@ -400,135 +572,16 @@ const DetailRow = ({ label, value }: { label: string; value: string }) => (
   </Flex>
 );
 
-function PriceItem({
-  labelIcon,
-  label,
-  valueCopyable: _valueCopyable = false,
-  value,
-  valueTruncated = false,
-}: {
-  icon?: React.ReactNode;
-  label: string | LocalizationKey;
-  labelIcon?: React.ComponentType;
-  value: string | LocalizationKey;
-  valueTruncated?: boolean;
-  valueCopyable?: boolean;
-}) {
-  return (
-    <Box
-      elementDescriptor={descriptors.statementSectionContentDetailsListItem}
-      as='li'
-      sx={t => ({
-        margin: 0,
-        background: common.mergedColorsBackground(
-          colors.setAlpha(t.colors.$colorBackground, 1),
-          t.colors.$neutralAlpha50,
-        ),
-        display: 'flex',
-        '&:not(:first-of-type)': {
-          borderBlockStartWidth: t.borderWidths.$normal,
-          borderBlockStartStyle: t.borderStyles.$solid,
-          borderBlockStartColor: t.colors.$neutralAlpha100,
-        },
-        '&:first-of-type #test': {
-          borderTopLeftRadius: t.radii.$md,
-          borderTopRightRadius: t.radii.$md,
-        },
-        '&:last-of-type #test': {
-          borderBottomLeftRadius: t.radii.$md,
-          borderBottomRightRadius: t.radii.$md,
-        },
-      })}
-    >
-      <Flex
-        justify='center'
-        align='center'
-        sx={t => ({
-          width: t.space.$8,
-          paddingInline: t.space.$2,
-          paddingBlock: t.space.$1x5,
-        })}
-      >
-        {labelIcon ? (
-          <Icon
-            icon={labelIcon}
-            size='xs'
-            colorScheme='neutral'
-          />
-        ) : null}
-      </Flex>
-
-      <Box
-        id='test'
-        sx={t => ({
-          flex: 1,
-          display: 'flex',
-          justifyContent: 'space-between',
-          flexWrap: 'wrap',
-          background: t.colors.$colorBackground,
-          paddingInline: t.space.$2,
-          paddingBlock: t.space.$1x5,
-          marginBlock: -1,
-          marginInline: -1,
-          boxShadow: `inset 0px 0px 0px ${t.borderWidths.$normal} ${t.colors.$neutralAlpha100}`,
-        })}
-      >
-        <Span
-          elementDescriptor={descriptors.statementSectionContentDetailsListItemLabelContainer}
-          sx={t => ({
-            display: 'flex',
-            alignItems: 'center',
-            gap: t.space.$1x5,
-          })}
-        >
-          <Text
-            variant='caption'
-            colorScheme='secondary'
-            elementDescriptor={descriptors.statementSectionContentDetailsListItemLabel}
-            localizationKey={label}
-          />
-        </Span>
-        <Span
-          sx={t => ({
-            display: 'flex',
-            alignItems: 'center',
-            gap: t.space.$0x25,
-            color: t.colors.$colorTextSecondary,
-          })}
-        >
-          {typeof value === 'string' ? (
-            <Text
-              colorScheme='secondary'
-              variant='caption'
-              elementDescriptor={descriptors.statementSectionContentDetailsListItemValue}
-            >
-              {valueTruncated ? truncateWithEndVisible(value) : value}
-            </Text>
-          ) : (
-            <Text
-              elementDescriptor={descriptors.statementSectionContentDetailsListItemValue}
-              colorScheme='secondary'
-              variant='caption'
-              localizationKey={value}
-            />
-          )}
-        </Span>
-      </Box>
-    </Box>
-  );
-}
-
 function SummaryItem(props: React.PropsWithChildren) {
   return (
     <Box
       elementDescriptor={descriptors.statementSectionContentDetailsListItem}
       as='li'
-      sx={t => ({
-        paddingInline: t.space.$4,
+      sx={{
         display: 'flex',
         justifyContent: 'space-between',
         flexWrap: 'wrap',
-      })}
+      }}
     >
       {props.children}
     </Box>
diff --git a/packages/clerk-js/src/ui/contexts/components/SubscriptionDetails.ts b/packages/clerk-js/src/ui/contexts/components/SubscriptionDetails.ts
new file mode 100644
index 00000000000..1121a0f7b15
--- /dev/null
+++ b/packages/clerk-js/src/ui/contexts/components/SubscriptionDetails.ts
@@ -0,0 +1,20 @@
+import { createContext, useContext } from 'react';
+
+import type { SubscriptionDetailsCtx } from '@/ui/types';
+
+export const SubscriptionDetailsContext = createContext<SubscriptionDetailsCtx | null>(null);
+
+export const useSubscriptionDetailsContext = () => {
+  const context = useContext(SubscriptionDetailsContext);
+
+  if (!context || context.componentName !== 'SubscriptionDetails') {
+    throw new Error('Clerk: useSubscriptionDetailsContext called outside SubscriptionDetails.');
+  }
+
+  const { componentName, ...ctx } = context;
+
+  return {
+    ...ctx,
+    componentName,
+  };
+};
diff --git a/packages/clerk-js/src/ui/elements/ThreeDotsMenu.tsx b/packages/clerk-js/src/ui/elements/ThreeDotsMenu.tsx
index d6e5d411319..66b476b45e6 100644
--- a/packages/clerk-js/src/ui/elements/ThreeDotsMenu.tsx
+++ b/packages/clerk-js/src/ui/elements/ThreeDotsMenu.tsx
@@ -13,6 +13,7 @@ type Action = {
 };
 
 type ThreeDotsMenuProps = {
+  trigger?: React.ReactNode;
   actions: Action[];
   elementId?: MenuId;
 };
@@ -22,24 +23,26 @@ export const ThreeDotsMenu = (props: ThreeDotsMenuProps) => {
   return (
     <Menu elementId={elementId}>
       <MenuTrigger arialLabel={isOpen => `${isOpen ? 'Close' : 'Open'} menu`}>
-        <Button
-          sx={t => ({
-            padding: t.space.$0x5,
-            boxSizing: 'content-box',
-            opacity: t.opacity.$inactive,
-            ':hover': {
-              opacity: 1,
-            },
-          })}
-          variant='ghost'
-          colorScheme='neutral'
-          elementDescriptor={[descriptors.menuButton, descriptors.menuButtonEllipsis]}
-        >
-          <Icon
-            icon={ThreeDots}
-            sx={t => ({ width: 'auto', height: t.sizes.$5 })}
-          />
-        </Button>
+        {props.trigger || (
+          <Button
+            sx={t => ({
+              padding: t.space.$0x5,
+              boxSizing: 'content-box',
+              opacity: t.opacity.$inactive,
+              ':hover': {
+                opacity: 1,
+              },
+            })}
+            variant='ghost'
+            colorScheme='neutral'
+            elementDescriptor={[descriptors.menuButton, descriptors.menuButtonEllipsis]}
+          >
+            <Icon
+              icon={ThreeDots}
+              sx={t => ({ width: 'auto', height: t.sizes.$5 })}
+            />
+          </Button>
+        )}
       </MenuTrigger>
       <MenuList>
         {actions.map((a, index) => (
diff --git a/packages/clerk-js/src/ui/types.ts b/packages/clerk-js/src/ui/types.ts
index 04cf9ee6366..b3df0b937e4 100644
--- a/packages/clerk-js/src/ui/types.ts
+++ b/packages/clerk-js/src/ui/types.ts
@@ -1,4 +1,5 @@
 import type {
+  __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
   __internal_OAuthConsentProps,
   __internal_PlanDetailsProps,
@@ -138,6 +139,10 @@ export type OAuthConsentCtx = __internal_OAuthConsentProps & {
   componentName: 'OAuthConsent';
 };
 
+export type SubscriptionDetailsCtx = __experimental_SubscriptionDetailsProps & {
+  componentName: 'SubscriptionDetails';
+};
+
 export type AvailableComponentCtx =
   | SignInCtx
   | SignUpCtx
diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index 0729f17a402..308fb5162f5 100644
--- a/packages/localizations/src/en-US.ts
+++ b/packages/localizations/src/en-US.ts
@@ -127,6 +127,8 @@ export const enUS: LocalizationResource = {
     switchPlan: 'Switch to this plan',
     switchToAnnual: 'Switch to annual',
     switchToMonthly: 'Switch to monthly',
+    switchToMonthlyWithPrice: 'Switch to monthly {{currency}}{{price}} per month',
+    switchToAnnualWithAnnualPrice: 'Switch to annual {{currency}}{{price}} per year',
     totalDue: 'Total due',
     totalDueToday: 'Total Due Today',
     viewFeatures: 'View features',
diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts
index 16a1002f4b6..185b278a541 100644
--- a/packages/types/src/localization.ts
+++ b/packages/types/src/localization.ts
@@ -175,6 +175,8 @@ export type __internal_LocalizationResource = {
     switchPlan: LocalizationValue;
     switchToMonthly: LocalizationValue;
     switchToAnnual: LocalizationValue;
+    switchToMonthlyWithPrice: LocalizationValue<'price' | 'currency'>;
+    switchToAnnualWithAnnualPrice: LocalizationValue<'price' | 'currency'>;
     billedAnnually: LocalizationValue;
     billedMonthlyOnly: LocalizationValue;
     alwaysFree: LocalizationValue;

From 0af7fb9e18a88180023d735d467610fe83a4cb68 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 1 Jul 2025 14:15:44 +0300
Subject: [PATCH 06/34] add unit tests

---
 .../__tests__/SubscriptionDetails.test.tsx    | 493 ++++++++++++++++++
 .../components/SubscriptionDetails/index.tsx  |  22 +-
 .../ui/contexts/ClerkUIComponentsContext.tsx  |  10 +
 packages/clerk-js/src/ui/types.ts             |   3 +-
 .../src/ui/utils/test/createFixtures.tsx      |   1 +
 .../clerk-js/src/ui/utils/test/mockHelpers.ts |   1 +
 6 files changed, 521 insertions(+), 9 deletions(-)
 create mode 100644 packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
new file mode 100644
index 00000000000..ca0fa83ceed
--- /dev/null
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
@@ -0,0 +1,493 @@
+import { Drawer } from '@/ui/elements/Drawer';
+
+import { render, waitFor } from '../../../../testUtils';
+import { bindCreateFixtures } from '../../../utils/test/createFixtures';
+import { SubscriptionDetails } from '..';
+
+const { createFixtures } = bindCreateFixtures('SubscriptionDetails');
+
+describe('SubscriptionDetails', () => {
+  it('Displays spinner when init loading', async () => {
+    const { wrapper } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const { baseElement } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <SubscriptionDetails />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      const spinner = baseElement.querySelector('span[aria-live="polite"]');
+      expect(spinner).toBeVisible();
+    });
+  });
+
+  it('single active monthly subscription', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    fixtures.clerk.billing.getSubscriptions.mockResolvedValue({
+      data: [
+        {
+          id: 'sub_123',
+          plan: {
+            id: 'plan_123',
+            name: 'Test Plan',
+            amount: 1000,
+            amountFormatted: '10.00',
+            annualAmount: 10000,
+            annualAmountFormatted: '100.00',
+            annualMonthlyAmount: 8333,
+            annualMonthlyAmountFormatted: '83.33',
+            currencySymbol: '$',
+            description: 'Test Plan',
+            hasBaseFee: true,
+            isRecurring: true,
+            currency: 'USD',
+            isDefault: false,
+          },
+          createdAt: new Date('2021-01-01'),
+          periodStart: new Date('2021-01-01'),
+          periodEnd: new Date('2021-02-01'),
+          canceledAt: null,
+          paymentSourceId: 'src_123',
+          planPeriod: 'month',
+          status: 'active',
+        },
+      ],
+      total_count: 1,
+    });
+
+    const { getByRole, getByText, queryByText, getAllByText, userEvent } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <SubscriptionDetails />
+      </Drawer.Root>,
+      { wrapper },
+    );
+    await waitFor(() => {
+      expect(getByRole('heading', { name: /Subscription/i })).toBeVisible();
+
+      expect(getByText('Test Plan')).toBeVisible();
+      expect(getByText('Active')).toBeVisible();
+
+      expect(getByText('$10.00 / Month')).toBeVisible();
+
+      expect(getByText('Subscribed on')).toBeVisible();
+      expect(getByText('January 1, 2021')).toBeVisible();
+      expect(getByText('Renews at')).toBeVisible();
+
+      expect(getByText('Current billing cycle')).toBeVisible();
+      expect(getByText('Monthly')).toBeVisible();
+
+      expect(getByText('Next payment on')).toBeVisible();
+      const nextPaymentElements = getAllByText('February 1, 2021');
+      expect(nextPaymentElements.length).toBe(2);
+
+      expect(getByText('Next payment amount')).toBeVisible();
+      expect(getByText('$10.00')).toBeVisible();
+      expect(queryByText('Ends on')).toBeNull();
+    });
+
+    const menuButton = getByRole('button', { name: /Open menu/i });
+    expect(menuButton).toBeVisible();
+    await userEvent.click(menuButton);
+
+    await waitFor(() => {
+      expect(getByText('Switch to annual $100.00 per year')).toBeVisible();
+      expect(getByText('Cancel subscription')).toBeVisible();
+    });
+  });
+
+  it('single active annual subscription', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    fixtures.clerk.billing.getSubscriptions.mockResolvedValue({
+      data: [
+        {
+          id: 'sub_123',
+          plan: {
+            id: 'plan_123',
+            name: 'Test Plan',
+            amount: 1000,
+            amountFormatted: '10.00',
+            annualAmount: 10000,
+            annualAmountFormatted: '100.00',
+            annualMonthlyAmount: 8333,
+            annualMonthlyAmountFormatted: '83.33',
+            currencySymbol: '$',
+            description: 'Test Plan',
+            hasBaseFee: true,
+            isRecurring: true,
+            currency: 'USD',
+            isDefault: false,
+          },
+          createdAt: new Date('2021-01-01'),
+          periodStart: new Date('2021-01-01'),
+          periodEnd: new Date('2022-01-01'),
+          canceledAt: null,
+          paymentSourceId: 'src_123',
+          planPeriod: 'annual',
+          status: 'active',
+        },
+      ],
+      total_count: 1,
+    });
+
+    const { getByRole, getByText, queryByText, getAllByText, userEvent } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <SubscriptionDetails />
+      </Drawer.Root>,
+      { wrapper },
+    );
+    await waitFor(() => {
+      expect(getByRole('heading', { name: /Subscription/i })).toBeVisible();
+
+      expect(getByText('Test Plan')).toBeVisible();
+      expect(getByText('Active')).toBeVisible();
+
+      expect(getByText('$100.00 / Year')).toBeVisible();
+
+      expect(getByText('Subscribed on')).toBeVisible();
+      expect(getByText('January 1, 2021')).toBeVisible();
+      expect(getByText('Renews at')).toBeVisible();
+
+      expect(getByText('Current billing cycle')).toBeVisible();
+      expect(getByText('Annually')).toBeVisible();
+
+      expect(getByText('Next payment on')).toBeVisible();
+      const nextPaymentElements = getAllByText('January 1, 2022');
+      expect(nextPaymentElements.length).toBe(2);
+
+      expect(getByText('Next payment amount')).toBeVisible();
+      expect(getByText('$100.00')).toBeVisible();
+      expect(queryByText('Ends on')).toBeNull();
+    });
+
+    const menuButton = getByRole('button', { name: /Open menu/i });
+    expect(menuButton).toBeVisible();
+    await userEvent.click(menuButton);
+
+    await waitFor(() => {
+      expect(getByText('Switch to monthly $10.00 per month')).toBeVisible();
+      expect(getByText('Cancel subscription')).toBeVisible();
+    });
+  });
+
+  it('active free subscription', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    fixtures.clerk.billing.getSubscriptions.mockResolvedValue({
+      data: [
+        {
+          id: 'sub_123',
+          plan: {
+            id: 'plan_123',
+            name: 'Free Plan',
+            amount: 0,
+            amountFormatted: '0.00',
+            annualAmount: 0,
+            annualAmountFormatted: '0.00',
+            annualMonthlyAmount: 0,
+            annualMonthlyAmountFormatted: '0.00',
+            currencySymbol: '$',
+            description: 'Free Plan description',
+            hasBaseFee: false,
+            isRecurring: true,
+            currency: 'USD',
+            isDefault: true,
+          },
+          createdAt: new Date('2021-01-01'),
+          periodStart: new Date('2021-01-01'),
+          periodEnd: new Date('2021-02-01'),
+          canceledAt: null,
+          paymentSourceId: 'src_123',
+          planPeriod: 'month',
+          status: 'active',
+        },
+      ],
+      total_count: 1,
+    });
+
+    const { getByRole, getByText, queryByText, queryByRole } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <SubscriptionDetails />
+      </Drawer.Root>,
+      { wrapper },
+    );
+    await waitFor(() => {
+      expect(getByRole('heading', { name: /Subscription/i })).toBeVisible();
+
+      expect(getByText('Free Plan')).toBeVisible();
+      expect(getByText('Active')).toBeVisible();
+
+      expect(getByText('$0.00 / Month')).toBeVisible();
+
+      expect(getByText('Subscribed on')).toBeVisible();
+      expect(getByText('January 1, 2021')).toBeVisible();
+
+      expect(queryByText('Renews at')).toBeNull();
+      expect(queryByText('Ends on')).toBeNull();
+      expect(queryByText('Current billing cycle')).toBeNull();
+      expect(queryByText('Monthly')).toBeNull();
+      expect(queryByText('Next payment on')).toBeNull();
+      expect(queryByText('Next payment amount')).toBeNull();
+      expect(queryByRole('button', { name: /Open menu/i })).toBeNull();
+    });
+  });
+
+  it('one active annual and one upcoming monthly subscription', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const planAnnual = {
+      id: 'plan_annual',
+      name: 'Annual Plan',
+      amount: 1300,
+      amountFormatted: '13.00',
+      annualAmount: 12000,
+      annualAmountFormatted: '120.00',
+      annualMonthlyAmount: 1000,
+      annualMonthlyAmountFormatted: '10.00',
+      currencySymbol: '$',
+      description: 'Annual Plan',
+      hasBaseFee: true,
+      isRecurring: true,
+      currency: 'USD',
+      isDefault: false,
+      payerType: ['user'],
+      publiclyVisible: true,
+      slug: 'annual-plan',
+      avatarUrl: '',
+      features: [],
+    };
+    const planMonthly = {
+      id: 'plan_monthly',
+      name: 'Monthly Plan',
+      amount: 1000,
+      amountFormatted: '10.00',
+      annualAmount: 9000,
+      annualAmountFormatted: '90.00',
+      annualMonthlyAmount: 750,
+      annualMonthlyAmountFormatted: '7.50',
+      currencySymbol: '$',
+      description: 'Monthly Plan',
+      hasBaseFee: true,
+      isRecurring: true,
+      currency: 'USD',
+      isDefault: false,
+      payerType: ['user'],
+      publiclyVisible: true,
+      slug: 'monthly-plan',
+      avatarUrl: '',
+      features: [],
+    };
+
+    fixtures.clerk.billing.getSubscriptions.mockResolvedValue({
+      data: [
+        {
+          id: 'sub_annual',
+          plan: planAnnual,
+          createdAt: new Date('2021-01-01'),
+          periodStart: new Date('2021-01-01'),
+          periodEnd: new Date('2022-01-01'),
+          canceledAt: new Date('2021-04-01'),
+          paymentSourceId: 'src_annual',
+          planPeriod: 'annual',
+          status: 'active',
+        },
+        {
+          id: 'sub_monthly',
+          plan: planMonthly,
+          createdAt: new Date('2022-01-01'),
+          periodStart: new Date('2022-02-01'),
+          periodEnd: new Date('2022-03-01'),
+          canceledAt: null,
+          paymentSourceId: 'src_monthly',
+          planPeriod: 'month',
+          status: 'upcoming',
+        },
+      ],
+      total_count: 2,
+    });
+
+    const { getByRole, getByText, getAllByText, queryByText, getAllByRole, userEvent } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <SubscriptionDetails />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(getByRole('heading', { name: /Subscription/i })).toBeVisible();
+      expect(getByText('Annual Plan')).toBeVisible();
+      expect(getByText('Active')).toBeVisible();
+      expect(getByText('$120.00 / Year')).toBeVisible();
+      expect(getByText('Subscribed on')).toBeVisible();
+      expect(getByText('January 1, 2021')).toBeVisible();
+      expect(getByText('Ends on')).toBeVisible();
+      expect(getByText('January 1, 2022')).toBeVisible();
+      expect(queryByText('Renews at')).toBeNull();
+      expect(getByText('Current billing cycle')).toBeVisible();
+      expect(getByText('Annually')).toBeVisible();
+      expect(getByText('Next payment on')).toBeVisible();
+      expect(getAllByText('January 1, 2022').length).toBeGreaterThan(0);
+      expect(getByText('Next payment amount')).toBeVisible();
+      expect(getByText('$10.00')).toBeVisible();
+      // Check for the upcoming subscription details
+      expect(getByText('Upcoming')).toBeVisible();
+      expect(getByText('Monthly Plan')).toBeVisible();
+      expect(getByText('$10.00 / Month')).toBeVisible();
+      expect(getByText('Begins on')).toBeVisible();
+    });
+
+    const [menuButton, upcomingMenuButton] = getAllByRole('button', { name: /Open menu/i });
+    await userEvent.click(menuButton);
+
+    await waitFor(() => {
+      expect(getByText('Switch to monthly $13.00 per month')).toBeVisible();
+      expect(getByText('Resubscribe')).toBeVisible();
+      expect(queryByText('Cancel subscription')).toBeNull();
+    });
+
+    await userEvent.click(upcomingMenuButton);
+
+    await waitFor(() => {
+      expect(getByText('Switch to annual $90.00 per year')).toBeVisible();
+      expect(getByText('Cancel subscription')).toBeVisible();
+    });
+  });
+
+  it('one active and one upcoming FREE subscription', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const planMonthly = {
+      id: 'plan_monthly',
+      name: 'Monthly Plan',
+      amount: 1000,
+      amountFormatted: '10.00',
+      annualAmount: 9000,
+      annualAmountFormatted: '90.00',
+      annualMonthlyAmount: 750,
+      annualMonthlyAmountFormatted: '7.50',
+      currencySymbol: '$',
+      description: 'Monthly Plan',
+      hasBaseFee: true,
+      isRecurring: true,
+      currency: 'USD',
+      isDefault: false,
+      payerType: ['user'],
+      publiclyVisible: true,
+      slug: 'monthly-plan',
+      avatarUrl: '',
+      features: [],
+    };
+
+    const planFreeUpcoming = {
+      id: 'plan_free_upcoming',
+      name: 'Free Plan (Upcoming)',
+      amount: 0,
+      amountFormatted: '0.00',
+      annualAmount: 0,
+      annualAmountFormatted: '0.00',
+      annualMonthlyAmount: 0,
+      annualMonthlyAmountFormatted: '0.00',
+      currencySymbol: '$',
+      description: 'Upcoming Free Plan',
+      hasBaseFee: false,
+      isRecurring: true,
+      currency: 'USD',
+      isDefault: false,
+      payerType: ['user'],
+      publiclyVisible: true,
+      slug: 'free-plan-upcoming',
+      avatarUrl: '',
+      features: [],
+    };
+
+    fixtures.clerk.billing.getSubscriptions.mockResolvedValue({
+      data: [
+        {
+          id: 'test_active',
+          plan: planMonthly,
+          createdAt: new Date('2021-01-01'),
+          periodStart: new Date('2021-01-01'),
+          periodEnd: new Date('2021-02-01'),
+          canceledAt: new Date('2021-01-03'),
+          paymentSourceId: 'src_free_active',
+          planPeriod: 'month',
+          status: 'active',
+        },
+        {
+          id: 'sub_free_upcoming',
+          plan: planFreeUpcoming,
+          createdAt: new Date('2021-01-03'),
+          periodStart: new Date('2021-02-01'),
+          canceledAt: null,
+          paymentSourceId: 'src_free_upcoming',
+          planPeriod: 'month',
+          status: 'upcoming',
+        },
+      ],
+      total_count: 2,
+    });
+
+    const { getByRole, getByText, queryByText, getAllByText } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <SubscriptionDetails />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(getByRole('heading', { name: /Subscription/i })).toBeVisible();
+      expect(getByText('Monthly Plan')).toBeVisible();
+      expect(getByText('Active')).toBeVisible();
+      expect(getByText('$10.00 / Month')).toBeVisible();
+      expect(getByText('Subscribed on')).toBeVisible();
+      expect(getByText('January 1, 2021')).toBeVisible();
+      expect(getByText('Ends on')).toBeVisible();
+
+      expect(queryByText('Renews at')).toBeNull();
+      expect(queryByText('Current billing cycle')).toBeNull();
+      expect(queryByText('Next payment on')).toBeNull();
+      expect(queryByText('Next payment amount')).toBeNull();
+      // Check for the upcoming free subscription details
+      expect(getByText('Upcoming')).toBeVisible();
+      expect(getByText('Free Plan (Upcoming)')).toBeVisible();
+      expect(getByText('$0.00 / Month')).toBeVisible();
+      expect(getByText('Begins on')).toBeVisible();
+
+      const nextPaymentElements = getAllByText('February 1, 2021');
+      expect(nextPaymentElements.length).toBe(2);
+    });
+  });
+});
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 72e2d29b444..071b4f334d8 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -2,6 +2,7 @@ import { useClerk, useOrganization } from '@clerk/shared/react';
 import type {
   __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
+  CommercePlanResource,
   CommerceSubscriptionResource,
 } from '@clerk/types';
 import * as React from 'react';
@@ -21,6 +22,8 @@ import { ThreeDots } from '@/ui/icons';
 import { handleError } from '@/ui/utils/errorHandler';
 import { formatDate } from '@/ui/utils/formatDate';
 
+const isFreePlan = (plan: CommercePlanResource) => !plan.hasBaseFee;
+
 import { usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
 import {
   Badge,
@@ -187,7 +190,7 @@ const SubscriptionDetailsFooter = withCardStateProvider(() => {
   }, [subscription, setError, setLoading, subscriberType, organization?.id, onSubscriptionCancel, setIsOpen, setIdle]);
 
   // If either the active or upcoming subscription is the free plan, then a C1 cannot switch to a different period or cancel the plan
-  if (anySubscription.plan.isDefault) {
+  if (isFreePlan(anySubscription.plan)) {
     return null;
   }
 
@@ -340,9 +343,9 @@ const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubsc
   const isSwitchable =
     (subscription.planPeriod === 'month' && subscription.plan.annualMonthlyAmount > 0) ||
     subscription.planPeriod === 'annual';
-  const isFreePlan = subscription.plan.isDefault;
-  const isCancellable = subscription.canceledAt === null && !isFreePlan;
-  const isReSubscribable = subscription.canceledAt !== null && !isFreePlan;
+  const isFree = isFreePlan(subscription.plan);
+  const isCancellable = subscription.canceledAt === null && !isFree;
+  const isReSubscribable = subscription.canceledAt !== null && !isFree;
 
   const openCheckout = useCallback(
     (params?: __internal_CheckoutProps) => {
@@ -456,6 +459,7 @@ const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubsc
 // New component for individual subscription cards
 const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscriptionResource }) => {
   const isActive = subscription.status === 'active';
+  const isFree = isFreePlan(subscription.plan);
   const { t } = useLocalizations();
 
   return (
@@ -539,10 +543,12 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
             // TODO: Use localization for dates
             value={formatDate(subscription.createdAt)}
           />
-          <DetailRow
-            label={subscription.canceledAt ? 'Ends on' : 'Renews at'}
-            value={formatDate(subscription.periodEnd)}
-          />
+          {!isFree && (
+            <DetailRow
+              label={subscription.canceledAt ? 'Ends on' : 'Renews at'}
+              value={formatDate(subscription.periodEnd)}
+            />
+          )}
         </>
       ) : (
         <DetailRow
diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
index d09ba7c7e1f..8575569232d 100644
--- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
+++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
@@ -1,4 +1,5 @@
 import type {
+  __experimental_SubscriptionDetailsProps,
   __internal_OAuthConsentProps,
   APIKeysProps,
   PricingTableProps,
@@ -25,6 +26,7 @@ import {
   UserVerificationContext,
   WaitlistContext,
 } from './components';
+import { SubscriptionDetailsContext } from './components/SubscriptionDetails';
 
 export function ComponentContextProvider({
   componentName,
@@ -108,6 +110,14 @@ export function ComponentContextProvider({
           {children}
         </OAuthConsentContext.Provider>
       );
+    case 'SubscriptionDetails':
+      return (
+        <SubscriptionDetailsContext.Provider
+          value={{ componentName, ...(props as __experimental_SubscriptionDetailsProps) }}
+        >
+          {children}
+        </SubscriptionDetailsContext.Provider>
+      );
 
     default:
       throw new Error(`Unknown component context: ${componentName}`);
diff --git a/packages/clerk-js/src/ui/types.ts b/packages/clerk-js/src/ui/types.ts
index b3df0b937e4..7fbd4bd04e1 100644
--- a/packages/clerk-js/src/ui/types.ts
+++ b/packages/clerk-js/src/ui/types.ts
@@ -158,5 +158,6 @@ export type AvailableComponentCtx =
   | PricingTableCtx
   | CheckoutCtx
   | APIKeysCtx
-  | OAuthConsentCtx;
+  | OAuthConsentCtx
+  | SubscriptionDetailsCtx;
 export type AvailableComponentName = AvailableComponentCtx['componentName'];
diff --git a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
index 4187dfdda57..d3491841d03 100644
--- a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
+++ b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
@@ -74,6 +74,7 @@ const unboundCreateFixtures = (
       session: clerkMock.session,
       signIn: clerkMock.client.signIn,
       signUp: clerkMock.client.signUp,
+      billing: clerkMock.billing,
       environment: environmentMock,
       router: routerMock,
       options: optionsMock,
diff --git a/packages/clerk-js/src/ui/utils/test/mockHelpers.ts b/packages/clerk-js/src/ui/utils/test/mockHelpers.ts
index 8e7cf2a3265..82eee90827d 100644
--- a/packages/clerk-js/src/ui/utils/test/mockHelpers.ts
+++ b/packages/clerk-js/src/ui/utils/test/mockHelpers.ts
@@ -46,6 +46,7 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepJestMocked<LoadedClerk
   mockMethodsOf(clerk);
   mockMethodsOf(clerk.client.signIn);
   mockMethodsOf(clerk.client.signUp);
+  mockMethodsOf(clerk.billing);
   clerk.client.sessions.forEach(session => {
     mockMethodsOf(session, {
       exclude: ['checkAuthorization'],

From a2303b6377bd6ac74b1a2592a11c1a57f3fde8aa Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 1 Jul 2025 14:36:46 +0300
Subject: [PATCH 07/34] address issue with animation

---
 .../components/SubscriptionDetails/index.tsx  | 26 +++++++++++++------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 071b4f334d8..bd3a2c8a25e 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -41,10 +41,15 @@ import {
   useLocalizations,
 } from '../../customizables';
 
+// We cannot derive the state of confrimation modal from the existance subscription, as it will make the animation laggy when the confimation closes.
 const SubscriptionForCancellationContext = React.createContext<{
   subscription: CommerceSubscriptionResource | null;
   setSubscription: (subscription: CommerceSubscriptionResource | null) => void;
+  confirmationOpen: boolean;
+  setConfirmationOpen: (confirmationOpen: boolean) => void;
 }>({
+  confirmationOpen: false,
+  setConfirmationOpen: () => {},
   subscription: null,
   setSubscription: () => {},
 });
@@ -97,6 +102,7 @@ const SubscriptionDetailsInternal = (props: __experimental_SubscriptionDetailsPr
   const [subscriptionForCancellation, setSubscriptionForCancellation] = useState<CommerceSubscriptionResource | null>(
     null,
   );
+  const [confirmationOpen, setConfirmationOpen] = useState(false);
 
   const {
     buttonPropsForPlan: _buttonPropsForPlan,
@@ -123,7 +129,12 @@ const SubscriptionDetailsInternal = (props: __experimental_SubscriptionDetailsPr
 
   return (
     <SubscriptionForCancellationContext.Provider
-      value={{ subscription: subscriptionForCancellation, setSubscription: setSubscriptionForCancellation }}
+      value={{
+        subscription: subscriptionForCancellation,
+        setSubscription: setSubscriptionForCancellation,
+        confirmationOpen,
+        setConfirmationOpen,
+      }}
     >
       <Drawer.Header title='Subscription' />
 
@@ -155,15 +166,12 @@ const SubscriptionDetailsFooter = withCardStateProvider(() => {
   const subscriberType = useSubscriberTypeContext();
   const { organization } = useOrganization();
   const { isLoading, error, setError, setLoading, setIdle } = useCardState();
-  const { subscription, setSubscription } = useContext(SubscriptionForCancellationContext);
+  const { subscription, confirmationOpen, setConfirmationOpen } = useContext(SubscriptionForCancellationContext);
   const { anySubscription } = useGuessableSubscription({ or: 'throw' });
   const { setIsOpen } = useDrawerContext();
   const { onSubscriptionCancel } = useSubscriptionDetailsContext();
 
-  const onOpenChange = useCallback(
-    (open: boolean) => setSubscription(open ? subscription : null),
-    [subscription, setSubscription],
-  );
+  const onOpenChange = useCallback((open: boolean) => setConfirmationOpen(open), [setConfirmationOpen]);
 
   const cancelSubscription = useCallback(async () => {
     if (!subscription) {
@@ -199,7 +207,7 @@ const SubscriptionDetailsFooter = withCardStateProvider(() => {
       <SubscriptionDetailsSummary />
 
       <Drawer.Confirmation
-        open={!!subscription}
+        open={confirmationOpen}
         onOpenChange={onOpenChange}
         actionsSlot={
           <>
@@ -336,7 +344,7 @@ const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubsc
   const subscriberType = useSubscriberTypeContext();
   const { setIsOpen } = useDrawerContext();
   const { revalidateAll } = usePlansContext();
-  const { setSubscription } = useContext(SubscriptionForCancellationContext);
+  const { setSubscription, setConfirmationOpen } = useContext(SubscriptionForCancellationContext);
   const canOrgManageBilling = useProtect(has => has({ permission: 'org:sys_billing:manage' }));
   const canManageBilling = subscriberType === 'user' || canOrgManageBilling;
 
@@ -397,6 +405,7 @@ const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubsc
             label: localizationKeys('commerce.cancelSubscription'),
             onClick: () => {
               setSubscription(subscription);
+              setConfirmationOpen(true);
             },
           }
         : null,
@@ -422,6 +431,7 @@ const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubsc
     setSubscription,
     canManageBilling,
     isReSubscribable,
+    setConfirmationOpen,
   ]);
 
   if (actions.length === 0) {

From e0f8e1aa7e8a55dedf53ff03c903eb49a187f3ed Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 1 Jul 2025 15:16:17 +0300
Subject: [PATCH 08/34] add more test cases

---
 .../__tests__/SubscriptionDetails.test.tsx    | 264 +++++++++++++++++-
 1 file changed, 252 insertions(+), 12 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
index ca0fa83ceed..55d4a848615 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
@@ -138,8 +138,8 @@ describe('SubscriptionDetails', () => {
           periodEnd: new Date('2022-01-01'),
           canceledAt: null,
           paymentSourceId: 'src_123',
-          planPeriod: 'annual',
-          status: 'active',
+          planPeriod: 'annual' as const,
+          status: 'active' as const,
         },
       ],
       total_count: 1,
@@ -218,8 +218,8 @@ describe('SubscriptionDetails', () => {
           periodEnd: new Date('2021-02-01'),
           canceledAt: null,
           paymentSourceId: 'src_123',
-          planPeriod: 'month',
-          status: 'active',
+          planPeriod: 'month' as const,
+          status: 'active' as const,
         },
       ],
       total_count: 1,
@@ -313,8 +313,8 @@ describe('SubscriptionDetails', () => {
           periodEnd: new Date('2022-01-01'),
           canceledAt: new Date('2021-04-01'),
           paymentSourceId: 'src_annual',
-          planPeriod: 'annual',
-          status: 'active',
+          planPeriod: 'annual' as const,
+          status: 'active' as const,
         },
         {
           id: 'sub_monthly',
@@ -324,8 +324,8 @@ describe('SubscriptionDetails', () => {
           periodEnd: new Date('2022-03-01'),
           canceledAt: null,
           paymentSourceId: 'src_monthly',
-          planPeriod: 'month',
-          status: 'upcoming',
+          planPeriod: 'month' as const,
+          status: 'upcoming' as const,
         },
       ],
       total_count: 2,
@@ -440,8 +440,8 @@ describe('SubscriptionDetails', () => {
           periodEnd: new Date('2021-02-01'),
           canceledAt: new Date('2021-01-03'),
           paymentSourceId: 'src_free_active',
-          planPeriod: 'month',
-          status: 'active',
+          planPeriod: 'month' as const,
+          status: 'active' as const,
         },
         {
           id: 'sub_free_upcoming',
@@ -450,8 +450,8 @@ describe('SubscriptionDetails', () => {
           periodStart: new Date('2021-02-01'),
           canceledAt: null,
           paymentSourceId: 'src_free_upcoming',
-          planPeriod: 'month',
-          status: 'upcoming',
+          planPeriod: 'month' as const,
+          status: 'upcoming' as const,
         },
       ],
       total_count: 2,
@@ -490,4 +490,244 @@ describe('SubscriptionDetails', () => {
       expect(nextPaymentElements.length).toBe(2);
     });
   });
+
+  it('allows cancelling a subscription of a monthly plan', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const cancelSubscriptionMock = jest.fn().mockResolvedValue({});
+
+    fixtures.clerk.billing.getSubscriptions.mockResolvedValue({
+      data: [
+        {
+          id: 'sub_123',
+          plan: {
+            id: 'plan_123',
+            name: 'Monthly Plan',
+            amount: 1000,
+            amountFormatted: '10.00',
+            annualAmount: 10000,
+            annualAmountFormatted: '100.00',
+            annualMonthlyAmount: 8333,
+            annualMonthlyAmountFormatted: '83.33',
+            currencySymbol: '$',
+            description: 'Monthly Plan',
+            hasBaseFee: true,
+            isRecurring: true,
+            currency: 'USD',
+            isDefault: false,
+          },
+          createdAt: new Date('2021-01-01'),
+          periodStart: new Date('2021-01-01'),
+          periodEnd: new Date('2021-02-01'),
+          canceledAt: null,
+          paymentSourceId: 'src_123',
+          planPeriod: 'month' as const,
+          status: 'active' as const,
+          cancel: cancelSubscriptionMock,
+        },
+      ],
+      total_count: 1,
+    });
+
+    const { getByRole, getByText, userEvent } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <SubscriptionDetails />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    // Wait for the subscription details to render
+    await waitFor(() => {
+      expect(getByText('Monthly Plan')).toBeVisible();
+      expect(getByText('Active')).toBeVisible();
+    });
+
+    // Open the menu
+    const menuButton = getByRole('button', { name: /Open menu/i });
+    await userEvent.click(menuButton);
+
+    // Wait for the cancel option to appear and click it
+    await userEvent.click(getByText('Cancel subscription'));
+
+    await waitFor(() => {
+      expect(getByText('Cancel Monthly Plan Subscription?')).toBeVisible();
+      expect(
+        getByText(
+          "You can keep using 'Monthly Plan' features until February 1, 2021, after which you will no longer have access.",
+        ),
+      ).toBeVisible();
+      expect(getByText('Keep subscription')).toBeVisible();
+    });
+
+    await userEvent.click(getByText('Cancel subscription'));
+
+    // Assert that the cancelSubscription method was called
+    await waitFor(() => {
+      expect(cancelSubscriptionMock).toHaveBeenCalled();
+    });
+  });
+
+  it('calls resubscribe when the user clicks Resubscribe for a canceled subscription', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const plan = {
+      id: 'plan_annual',
+      name: 'Annual Plan',
+      amount: 12000,
+      amountFormatted: '120.00',
+      annualAmount: 12000,
+      annualAmountFormatted: '120.00',
+      annualMonthlyAmount: 1000,
+      annualMonthlyAmountFormatted: '10.00',
+      currencySymbol: '$',
+      description: 'Annual Plan',
+      hasBaseFee: true,
+      isRecurring: true,
+      currency: 'USD',
+      isDefault: false,
+      payerType: ['user'],
+      publiclyVisible: true,
+      slug: 'annual-plan',
+      avatarUrl: '',
+      features: [],
+      __internal_toSnapshot: jest.fn(),
+      pathRoot: '',
+      reload: jest.fn(),
+    };
+
+    const subscription = {
+      id: 'sub_annual',
+      plan,
+      createdAt: new Date('2021-01-01'),
+      periodStart: new Date('2021-01-01'),
+      periodEnd: new Date('2022-01-01'),
+      canceledAt: new Date('2021-04-01'),
+      paymentSourceId: 'src_annual',
+      planPeriod: 'annual' as const,
+      status: 'active' as const,
+      cancel: jest.fn(),
+      pathRoot: '',
+      reload: jest.fn(),
+    };
+
+    // Mock getSubscriptions to return the canceled subscription
+    fixtures.clerk.billing.getSubscriptions.mockResolvedValue({
+      data: [subscription],
+      total_count: 1,
+    });
+
+    const { getByRole, getByText, userEvent } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <SubscriptionDetails />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(getByText('Annual Plan')).toBeVisible();
+    });
+
+    // Open the menu
+    const menuButton = getByRole('button', { name: /Open menu/i });
+    await userEvent.click(menuButton);
+
+    // Wait for the Resubscribe option and click it
+    await userEvent.click(getByText('Resubscribe'));
+
+    // Assert resubscribe was called
+    await waitFor(() => {
+      expect(fixtures.clerk.__internal_openCheckout).toHaveBeenCalled();
+    });
+  });
+
+  it('calls switchToMonthly when the user clicks Switch to monthly for an annual subscription', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const switchToMonthlyMock = jest.fn().mockResolvedValue({});
+    const plan = {
+      id: 'plan_annual',
+      name: 'Annual Plan',
+      amount: 12000,
+      amountFormatted: '120.00',
+      annualAmount: 12000,
+      annualAmountFormatted: '120.00',
+      annualMonthlyAmount: 1000,
+      annualMonthlyAmountFormatted: '10.00',
+      currencySymbol: '$',
+      description: 'Annual Plan',
+      hasBaseFee: true,
+      isRecurring: true,
+      currency: 'USD',
+      isDefault: false,
+      payerType: ['user'],
+      publiclyVisible: true,
+      slug: 'annual-plan',
+      avatarUrl: '',
+      features: [],
+    };
+
+    const subscription = {
+      id: 'sub_annual',
+      plan,
+      createdAt: new Date('2021-01-01'),
+      periodStart: new Date('2021-01-01'),
+      periodEnd: new Date('2022-01-01'),
+      canceledAt: null,
+      paymentSourceId: 'src_annual',
+      planPeriod: 'annual' as const,
+      status: 'active' as const,
+      cancel: jest.fn(),
+      pathRoot: '',
+      reload: jest.fn(),
+    };
+
+    // Mock getSubscriptions to return the annual subscription
+    fixtures.clerk.billing.getSubscriptions.mockResolvedValue({
+      data: [subscription],
+      total_count: 1,
+    });
+
+    const { getByRole, getByText, userEvent } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <SubscriptionDetails />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(getByText('Annual Plan')).toBeVisible();
+    });
+
+    // Open the menu
+    const menuButton = getByRole('button', { name: /Open menu/i });
+    await userEvent.click(menuButton);
+
+    // Wait for the Switch to monthly option and click it
+    await userEvent.click(getByText(/Switch to monthly/i));
+
+    // Assert switchToMonthly was called
+    await waitFor(() => {
+      expect(fixtures.clerk.__internal_openCheckout).toHaveBeenCalledWith(
+        expect.objectContaining({
+          planId: subscription.plan.id,
+          planPeriod: 'month' as const,
+        }),
+      );
+    });
+  });
 });

From 1b75fe1f1c4e3f2db749d189a6287b4298c51028 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 1 Jul 2025 15:52:19 +0300
Subject: [PATCH 09/34] use box shadow instead of border

---
 .../clerk-js/src/ui/components/SubscriptionDetails/index.tsx  | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index bd3a2c8a25e..9bbc148390b 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -475,10 +475,8 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
   return (
     <Col
       sx={t => ({
-        borderWidth: t.borderWidths.$normal,
-        borderStyle: t.borderStyles.$solid,
-        borderColor: t.colors.$neutralAlpha100,
         borderRadius: t.radii.$md,
+        boxShadow: t.shadows.$tableBodyShadow,
       })}
     >
       <Col

From 3a0907c3805803d41769b78fb612437abc225fb2 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 1 Jul 2025 17:12:54 +0300
Subject: [PATCH 10/34] remove unnecessary context

---
 .../src/ui/contexts/ClerkUIComponentsContext.tsx      | 11 -----------
 .../clerk-js/src/ui/utils/test/createFixtures.tsx     |  2 +-
 2 files changed, 1 insertion(+), 12 deletions(-)

diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
index 8575569232d..2cada4a7da1 100644
--- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
+++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx
@@ -1,5 +1,4 @@
 import type {
-  __experimental_SubscriptionDetailsProps,
   __internal_OAuthConsentProps,
   APIKeysProps,
   PricingTableProps,
@@ -26,7 +25,6 @@ import {
   UserVerificationContext,
   WaitlistContext,
 } from './components';
-import { SubscriptionDetailsContext } from './components/SubscriptionDetails';
 
 export function ComponentContextProvider({
   componentName,
@@ -110,15 +108,6 @@ export function ComponentContextProvider({
           {children}
         </OAuthConsentContext.Provider>
       );
-    case 'SubscriptionDetails':
-      return (
-        <SubscriptionDetailsContext.Provider
-          value={{ componentName, ...(props as __experimental_SubscriptionDetailsProps) }}
-        >
-          {children}
-        </SubscriptionDetailsContext.Provider>
-      );
-
     default:
       throw new Error(`Unknown component context: ${componentName}`);
   }
diff --git a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
index d3491841d03..bc6532a706a 100644
--- a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
+++ b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
@@ -90,7 +90,7 @@ const unboundCreateFixtures = (
     const MockClerkProvider = (props: any) => {
       const { children } = props;
 
-      const componentsWithoutContext = ['UsernameSection', 'UserProfileSection'];
+      const componentsWithoutContext = ['UsernameSection', 'UserProfileSection', 'SubscriptionDetails'];
       const contextWrappedChildren = !componentsWithoutContext.includes(componentName) ? (
         <ComponentContextProvider
           componentName={componentName}

From 1ddd06b5d1668a21f12b3be851e65444b4dc25f8 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 1 Jul 2025 17:19:03 +0300
Subject: [PATCH 11/34] handle missing avatar url

---
 .../components/SubscriptionDetails/index.tsx  | 24 ++++++++-----------
 1 file changed, 10 insertions(+), 14 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 9bbc148390b..65462a48b67 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -490,20 +490,16 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
           align='center'
           gap={2}
         >
-          <Avatar
-            boxElementDescriptor={descriptors.planDetailAvatar}
-            // TODO: Use size prop
-            size={_ => 40}
-            title={subscription.plan.name}
-            initials={subscription.plan.name[0]}
-            rounded={false}
-            // TODO: remove hardcoded image
-            imageUrl={subscription.plan.avatarUrl || 'https://i.ibb.co/s9GqfwtK/Frame-106.png'}
-            // TODO: remove hardcoded background
-            sx={{
-              background: 'unset',
-            }}
-          />
+          {subscription.plan.avatarUrl ? (
+            <Avatar
+              // TODO(@commerce): Add correct descriptor
+              boxElementDescriptor={descriptors.planDetailAvatar}
+              size={_ => 40}
+              title={subscription.plan.name}
+              rounded={false}
+              imageUrl={subscription.plan.avatarUrl}
+            />
+          ) : null}
 
           <Text
             sx={{

From d84f755de4f51048d2acd97f2b41661dd23f32d9 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Tue, 1 Jul 2025 20:50:40 +0300
Subject: [PATCH 12/34] add descriptors

---
 .../components/SubscriptionDetails/index.tsx  | 22 +++++++++----------
 .../ui/customizables/elementDescriptors.ts    | 12 ++++++++++
 packages/types/src/appearance.ts              | 12 ++++++++++
 3 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 65462a48b67..ac57bc4f3a7 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -275,6 +275,7 @@ function SubscriptionDetailsSummary() {
 
   return (
     <Col
+      elementDescriptor={descriptors.subscriptionDetailsSummaryItems}
       gap={3}
       as='ul'
       sx={t => ({
@@ -307,7 +308,6 @@ function SubscriptionDetailsSummary() {
         </SummmaryItemLabel>
         <SummmaryItemValue>
           <Span
-            // elementDescriptor={descriptors.paymentAttemptFooterValueContainer}
             sx={t => ({
               display: 'flex',
               alignItems: 'center',
@@ -317,15 +317,11 @@ function SubscriptionDetailsSummary() {
             <Text
               variant='caption'
               colorScheme='secondary'
-              // elementDescriptor={descriptors.paymentAttemptFooterCurrency}
               sx={{ textTransform: 'uppercase' }}
             >
               {anySubscription.plan.currency}
             </Text>
-            <Text
-            // variant='h3'
-            // elementDescriptor={descriptors.paymentAttemptFooterValue}
-            >
+            <Text>
               {anySubscription.plan.currencySymbol}
               {anySubscription.planPeriod === 'month'
                 ? anySubscription.plan.amountFormatted
@@ -474,12 +470,14 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
 
   return (
     <Col
+      elementDescriptor={descriptors.subscriptionDetailsCard}
       sx={t => ({
         borderRadius: t.radii.$md,
         boxShadow: t.shadows.$tableBodyShadow,
       })}
     >
       <Col
+        elementDescriptor={descriptors.subscriptionDetailsCardBody}
         gap={3}
         sx={t => ({
           padding: t.space.$3,
@@ -487,12 +485,12 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
       >
         {/* Header with name and badge */}
         <Flex
+          elementDescriptor={descriptors.subscriptionDetailsCardHeader}
           align='center'
           gap={2}
         >
           {subscription.plan.avatarUrl ? (
             <Avatar
-              // TODO(@commerce): Add correct descriptor
               boxElementDescriptor={descriptors.planDetailAvatar}
               size={_ => 40}
               title={subscription.plan.name}
@@ -502,6 +500,7 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
           ) : null}
 
           <Text
+            elementDescriptor={descriptors.subscriptionDetailsCardTitle}
             sx={{
               fontSize: '16px',
               fontWeight: '600',
@@ -512,6 +511,7 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
             {subscription.plan.name}
           </Text>
           <Badge
+            elementDescriptor={descriptors.subscriptionDetailsCardBadge}
             colorScheme={isActive ? 'secondary' : 'primary'}
             localizationKey={isActive ? localizationKeys('badge__activePlan') : localizationKeys('badge__upcomingPlan')}
           />
@@ -519,7 +519,7 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
 
         {/* Pricing details */}
         <Flex
-          elementDescriptor={descriptors.statementSectionContentDetailsList}
+          elementDescriptor={descriptors.subscriptionDetailsCardActions}
           justify='between'
           align='center'
         >
@@ -585,7 +585,7 @@ const DetailRow = ({ label, value }: { label: string; value: string }) => (
 function SummaryItem(props: React.PropsWithChildren) {
   return (
     <Box
-      elementDescriptor={descriptors.statementSectionContentDetailsListItem}
+      elementDescriptor={descriptors.subscriptionDetailsSummaryItem}
       as='li'
       sx={{
         display: 'flex',
@@ -601,7 +601,7 @@ function SummaryItem(props: React.PropsWithChildren) {
 function SummmaryItemLabel(props: React.PropsWithChildren) {
   return (
     <Span
-      elementDescriptor={descriptors.statementSectionContentDetailsListItemLabelContainer}
+      elementDescriptor={descriptors.subscriptionDetailsSummaryLabel}
       sx={t => ({
         display: 'flex',
         alignItems: 'center',
@@ -616,7 +616,7 @@ function SummmaryItemLabel(props: React.PropsWithChildren) {
 function SummmaryItemValue(props: React.PropsWithChildren) {
   return (
     <Span
-      elementDescriptor={descriptors.statementSectionContentDetailsListItemLabelContainer}
+      elementDescriptor={descriptors.subscriptionDetailsSummaryValue}
       sx={t => ({
         display: 'flex',
         alignItems: 'center',
diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
index 690efe896a6..13f49d7b81c 100644
--- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
+++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
@@ -481,6 +481,18 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
   'apiKeysRevokeModal',
   'apiKeysRevokeModalInput',
   'apiKeysRevokeModalSubmitButton',
+
+  'subscriptionDetailsCard',
+  'subscriptionDetailsCardHeader',
+  'subscriptionDetailsCardBadge',
+  'subscriptionDetailsCardTitle',
+  'subscriptionDetailsCardBody',
+  'subscriptionDetailsCardFooter',
+  'subscriptionDetailsCardActions',
+  'subscriptionDetailsSummaryItems',
+  'subscriptionDetailsSummaryItem',
+  'subscriptionDetailsSummaryLabel',
+  'subscriptionDetailsSummaryValue',
 ] as const).map(camelize) as (keyof ElementsConfig)[];
 
 type TargettableClassname<K extends keyof ElementsConfig> = `${typeof CLASS_PREFIX}${K}`;
diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts
index dc647795bee..ea202356f13 100644
--- a/packages/types/src/appearance.ts
+++ b/packages/types/src/appearance.ts
@@ -608,6 +608,18 @@ export type ElementsConfig = {
   apiKeysRevokeModal: WithOptions;
   apiKeysRevokeModalInput: WithOptions;
   apiKeysRevokeModalSubmitButton: WithOptions;
+
+  subscriptionDetailsCard: WithOptions;
+  subscriptionDetailsCardHeader: WithOptions;
+  subscriptionDetailsCardBadge: WithOptions;
+  subscriptionDetailsCardTitle: WithOptions;
+  subscriptionDetailsCardBody: WithOptions;
+  subscriptionDetailsCardFooter: WithOptions;
+  subscriptionDetailsCardActions: WithOptions;
+  subscriptionDetailsSummaryItems: WithOptions;
+  subscriptionDetailsSummaryItem: WithOptions;
+  subscriptionDetailsSummaryLabel: WithOptions;
+  subscriptionDetailsSummaryValue: WithOptions;
 };
 
 export type Elements = {

From aaf814ceee38d0dfeb19b4e01d0416d23980d3e1 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 2 Jul 2025 12:11:05 +0300
Subject: [PATCH 13/34] add more descriptors

---
 .../components/SubscriptionDetails/index.tsx  | 82 +++++++++++--------
 .../ui/customizables/elementDescriptors.ts    |  3 +
 packages/types/src/appearance.ts              |  3 +
 3 files changed, 52 insertions(+), 36 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index ac57bc4f3a7..59675704d58 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -297,8 +297,10 @@ function SubscriptionDetailsSummary() {
         <SummmaryItemValue>
           <Text colorScheme='secondary'>
             {upcomingSubscription
-              ? formatDate(upcomingSubscription.periodStart)
-              : formatDate(anySubscription.periodEnd)}
+              ? formatDate(upcomingSubscription.periodStartDate)
+              : anySubscription.periodEndDate
+                ? formatDate(anySubscription.periodEndDate)
+                : '-'}
           </Text>
         </SummmaryItemValue>
       </SummaryItem>
@@ -306,28 +308,26 @@ function SubscriptionDetailsSummary() {
         <SummmaryItemLabel>
           <Text>Next payment amount</Text>
         </SummmaryItemLabel>
-        <SummmaryItemValue>
-          <Span
-            sx={t => ({
-              display: 'flex',
-              alignItems: 'center',
-              gap: t.space.$1,
-            })}
+        <SummmaryItemValue
+          sx={t => ({
+            display: 'flex',
+            alignItems: 'center',
+            gap: t.space.$1,
+          })}
+        >
+          <Text
+            variant='caption'
+            colorScheme='secondary'
+            sx={{ textTransform: 'uppercase' }}
           >
-            <Text
-              variant='caption'
-              colorScheme='secondary'
-              sx={{ textTransform: 'uppercase' }}
-            >
-              {anySubscription.plan.currency}
-            </Text>
-            <Text>
-              {anySubscription.plan.currencySymbol}
-              {anySubscription.planPeriod === 'month'
-                ? anySubscription.plan.amountFormatted
-                : anySubscription.plan.annualAmountFormatted}
-            </Text>
-          </Span>
+            {anySubscription.plan.currency}
+          </Text>
+          <Text>
+            {anySubscription.plan.currencySymbol}
+            {anySubscription.planPeriod === 'month'
+              ? anySubscription.plan.amountFormatted
+              : anySubscription.plan.annualAmountFormatted}
+          </Text>
         </SummmaryItemValue>
       </SummaryItem>
     </Col>
@@ -465,7 +465,6 @@ const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubsc
 // New component for individual subscription cards
 const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscriptionResource }) => {
   const isActive = subscription.status === 'active';
-  const isFree = isFreePlan(subscription.plan);
   const { t } = useLocalizations();
 
   return (
@@ -547,17 +546,18 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
             // TODO: Use localization for dates
             value={formatDate(subscription.createdAt)}
           />
-          {!isFree && (
+          {/* The free plan does not have a period end date */}
+          {subscription.periodEndDate && (
             <DetailRow
-              label={subscription.canceledAt ? 'Ends on' : 'Renews at'}
-              value={formatDate(subscription.periodEnd)}
+              label={subscription.canceledAtDate ? 'Ends on' : 'Renews at'}
+              value={formatDate(subscription.periodEndDate)}
             />
           )}
         </>
       ) : (
         <DetailRow
           label='Begins on'
-          value={formatDate(subscription.periodStart)}
+          value={formatDate(subscription.periodStartDate)}
         />
       )}
     </Col>
@@ -567,6 +567,7 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
 // Helper component for detail rows
 const DetailRow = ({ label, value }: { label: string; value: string }) => (
   <Flex
+    elementDescriptor={descriptors.subscriptionDetailsDetailRow}
     justify='between'
     align='center'
     sx={t => ({
@@ -577,8 +578,13 @@ const DetailRow = ({ label, value }: { label: string; value: string }) => (
       borderBlockStartColor: t.colors.$neutralAlpha100,
     })}
   >
-    <Text>{label}</Text>
-    <Text colorScheme='secondary'>{value}</Text>
+    <Text elementDescriptor={descriptors.subscriptionDetailsDetailRowLabel}>{label}</Text>
+    <Text
+      elementDescriptor={descriptors.subscriptionDetailsDetailRowValue}
+      colorScheme='secondary'
+    >
+      {value}
+    </Text>
   </Flex>
 );
 
@@ -613,15 +619,19 @@ function SummmaryItemLabel(props: React.PropsWithChildren) {
   );
 }
 
-function SummmaryItemValue(props: React.PropsWithChildren) {
+function SummmaryItemValue(props: Parameters<typeof Span>[0]) {
   return (
     <Span
       elementDescriptor={descriptors.subscriptionDetailsSummaryValue}
-      sx={t => ({
-        display: 'flex',
-        alignItems: 'center',
-        gap: t.space.$0x25,
-      })}
+      {...props}
+      sx={[
+        t => ({
+          display: 'flex',
+          alignItems: 'center',
+          gap: t.space.$0x25,
+        }),
+        props.sx,
+      ]}
     >
       {props.children}
     </Span>
diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
index 13f49d7b81c..6f8307a8a8f 100644
--- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
+++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
@@ -493,6 +493,9 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
   'subscriptionDetailsSummaryItem',
   'subscriptionDetailsSummaryLabel',
   'subscriptionDetailsSummaryValue',
+  'subscriptionDetailsDetailRow',
+  'subscriptionDetailsDetailRowLabel',
+  'subscriptionDetailsDetailRowValue',
 ] as const).map(camelize) as (keyof ElementsConfig)[];
 
 type TargettableClassname<K extends keyof ElementsConfig> = `${typeof CLASS_PREFIX}${K}`;
diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts
index ea202356f13..e9404d8fcdf 100644
--- a/packages/types/src/appearance.ts
+++ b/packages/types/src/appearance.ts
@@ -620,6 +620,9 @@ export type ElementsConfig = {
   subscriptionDetailsSummaryItem: WithOptions;
   subscriptionDetailsSummaryLabel: WithOptions;
   subscriptionDetailsSummaryValue: WithOptions;
+  subscriptionDetailsDetailRow: WithOptions;
+  subscriptionDetailsDetailRowLabel: WithOptions;
+  subscriptionDetailsDetailRowValue: WithOptions;
 };
 
 export type Elements = {

From 55f1ba3d8398865b876569feaef50b43a574f8e0 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 2 Jul 2025 13:58:40 +0300
Subject: [PATCH 14/34] handle localizations

---
 .../components/SubscriptionDetails/index.tsx  | 55 ++++++++++++++-----
 packages/localizations/src/en-US.ts           | 10 ++++
 packages/types/src/localization.ts            | 10 ++++
 3 files changed, 60 insertions(+), 15 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 59675704d58..6d749c40518 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -25,6 +25,7 @@ import { formatDate } from '@/ui/utils/formatDate';
 const isFreePlan = (plan: CommercePlanResource) => !plan.hasBaseFee;
 
 import { usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
+import type { LocalizationKey } from '../../customizables';
 import {
   Badge,
   Box,
@@ -136,7 +137,7 @@ const SubscriptionDetailsInternal = (props: __experimental_SubscriptionDetailsPr
         setConfirmationOpen,
       }}
     >
-      <Drawer.Header title='Subscription' />
+      <Drawer.Header title={localizationKeys('commerce.subscriptionDetails.title')} />
 
       <Drawer.Body>
         <Col
@@ -254,7 +255,8 @@ const SubscriptionDetailsFooter = withCardStateProvider(() => {
                   ? localizationKeys('commerce.cancelSubscriptionNoCharge')
                   : localizationKeys('commerce.cancelSubscriptionAccessUntil', {
                       plan: subscription.plan.name,
-                      date: subscription.periodEnd,
+                      // @ts-expect-error this will always be defined in this state
+                      date: subscription.periodEndDate,
                     })
               }
             />
@@ -268,6 +270,7 @@ const SubscriptionDetailsFooter = withCardStateProvider(() => {
 
 function SubscriptionDetailsSummary() {
   const { anySubscription, activeSubscription, upcomingSubscription } = useGuessableSubscription({ or: 'throw' });
+  const { t } = useLocalizations();
 
   if (!activeSubscription) {
     return null;
@@ -284,15 +287,25 @@ function SubscriptionDetailsSummary() {
     >
       <SummaryItem>
         <SummmaryItemLabel>
-          <Text colorScheme='secondary'>Current billing cycle</Text>
+          <Text
+            colorScheme='secondary'
+            localizationKey={localizationKeys('commerce.subscriptionDetails.currentBillingCycle')}
+          />
         </SummmaryItemLabel>
         <SummmaryItemValue>
-          <Text colorScheme='secondary'>{activeSubscription.planPeriod === 'month' ? 'Monthly' : 'Annually'}</Text>
+          <Text
+            colorScheme='secondary'
+            localizationKey={
+              activeSubscription.planPeriod === 'month'
+                ? localizationKeys('commerce.monthly')
+                : localizationKeys('commerce.annually')
+            }
+          />
         </SummmaryItemValue>
       </SummaryItem>
       <SummaryItem>
         <SummmaryItemLabel>
-          <Text colorScheme='secondary'>Next payment on</Text>
+          <Text colorScheme='secondary'>{t(localizationKeys('commerce.subscriptionDetails.nextPaymentOn'))}</Text>
         </SummmaryItemLabel>
         <SummmaryItemValue>
           <Text colorScheme='secondary'>
@@ -306,7 +319,10 @@ function SubscriptionDetailsSummary() {
       </SummaryItem>
       <SummaryItem>
         <SummmaryItemLabel>
-          <Text>Next payment amount</Text>
+          <Text
+            colorScheme='secondary'
+            localizationKey={localizationKeys('commerce.subscriptionDetails.nextPaymentAmount')}
+          />
         </SummmaryItemLabel>
         <SummmaryItemValue
           sx={t => ({
@@ -337,6 +353,7 @@ function SubscriptionDetailsSummary() {
 const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubscriptionResource }) => {
   const { portalRoot } = useSubscriptionDetailsContext();
   const { __internal_openCheckout } = useClerk();
+  const { t } = useLocalizations();
   const subscriberType = useSubscriberTypeContext();
   const { setIsOpen } = useDrawerContext();
   const { revalidateAll } = usePlansContext();
@@ -348,8 +365,8 @@ const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubsc
     (subscription.planPeriod === 'month' && subscription.plan.annualMonthlyAmount > 0) ||
     subscription.planPeriod === 'annual';
   const isFree = isFreePlan(subscription.plan);
-  const isCancellable = subscription.canceledAt === null && !isFree;
-  const isReSubscribable = subscription.canceledAt !== null && !isFree;
+  const isCancellable = subscription.canceledAtDate === null && !isFree;
+  const isReSubscribable = subscription.canceledAtDate !== null && !isFree;
 
   const openCheckout = useCallback(
     (params?: __internal_CheckoutProps) => {
@@ -438,7 +455,7 @@ const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubsc
     <ThreeDotsMenu
       trigger={
         <Button
-          aria-label='Manage subscription'
+          aria-label={t(localizationKeys('commerce.manageSubscription'))}
           variant='bordered'
           colorScheme='secondary'
           sx={t => ({
@@ -467,6 +484,8 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
   const isActive = subscription.status === 'active';
   const { t } = useLocalizations();
 
+  console.log(subscription.periodEndDate, subscription.canceledAtDate, subscription);
+
   return (
     <Col
       elementDescriptor={descriptors.subscriptionDetailsCard}
@@ -542,21 +561,24 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
       {isActive ? (
         <>
           <DetailRow
-            label='Subscribed on'
-            // TODO: Use localization for dates
+            label={localizationKeys('commerce.subscriptionDetails.subscribedOn')}
             value={formatDate(subscription.createdAt)}
           />
           {/* The free plan does not have a period end date */}
           {subscription.periodEndDate && (
             <DetailRow
-              label={subscription.canceledAtDate ? 'Ends on' : 'Renews at'}
+              label={
+                subscription.canceledAtDate
+                  ? localizationKeys('commerce.subscriptionDetails.endsOn')
+                  : localizationKeys('commerce.subscriptionDetails.renewsAt')
+              }
               value={formatDate(subscription.periodEndDate)}
             />
           )}
         </>
       ) : (
         <DetailRow
-          label='Begins on'
+          label={localizationKeys('commerce.subscriptionDetails.beginsOn')}
           value={formatDate(subscription.periodStartDate)}
         />
       )}
@@ -565,7 +587,7 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
 };
 
 // Helper component for detail rows
-const DetailRow = ({ label, value }: { label: string; value: string }) => (
+const DetailRow = ({ label, value }: { label: LocalizationKey; value: string }) => (
   <Flex
     elementDescriptor={descriptors.subscriptionDetailsDetailRow}
     justify='between'
@@ -578,7 +600,10 @@ const DetailRow = ({ label, value }: { label: string; value: string }) => (
       borderBlockStartColor: t.colors.$neutralAlpha100,
     })}
   >
-    <Text elementDescriptor={descriptors.subscriptionDetailsDetailRowLabel}>{label}</Text>
+    <Text
+      elementDescriptor={descriptors.subscriptionDetailsDetailRowLabel}
+      localizationKey={label}
+    />
     <Text
       elementDescriptor={descriptors.subscriptionDetailsDetailRowValue}
       colorScheme='secondary'
diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index ef9c0f1871f..9054b1d041a 100644
--- a/packages/localizations/src/en-US.ts
+++ b/packages/localizations/src/en-US.ts
@@ -120,6 +120,16 @@ export const enUS: LocalizationResource = {
       billingCycle: 'Billing cycle',
       included: 'Included',
     },
+    subscriptionDetails: {
+      title: 'Subscription',
+      currentBillingCycle: 'Current billing cycle',
+      nextPaymentOn: 'Next payment on',
+      nextPaymentAmount: 'Next payment amount',
+      subscribedOn: 'Subscribed on',
+      endsOn: 'Ends on',
+      renewsAt: 'Renews at',
+      beginsOn: 'Begins on',
+    },
     reSubscribe: 'Resubscribe',
     seeAllFeatures: 'See all features',
     subscribe: 'Subscribe',
diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts
index 185b278a541..cf1ff595d1c 100644
--- a/packages/types/src/localization.ts
+++ b/packages/types/src/localization.ts
@@ -198,6 +198,16 @@ export type __internal_LocalizationResource = {
     cancelSubscriptionNoCharge: LocalizationValue;
     cancelSubscriptionAccessUntil: LocalizationValue<'plan' | 'date'>;
     popular: LocalizationValue;
+    subscriptionDetails: {
+      title: LocalizationValue;
+      currentBillingCycle: LocalizationValue;
+      nextPaymentOn: LocalizationValue;
+      nextPaymentAmount: LocalizationValue;
+      subscribedOn: LocalizationValue;
+      endsOn: LocalizationValue;
+      renewsAt: LocalizationValue;
+      beginsOn: LocalizationValue;
+    };
     monthly: LocalizationValue;
     annually: LocalizationValue;
     cannotSubscribeMonthly: LocalizationValue;

From 962e8d5e8eb89d2105a43c57a3a51d29ffc1b1fd Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 2 Jul 2025 13:58:52 +0300
Subject: [PATCH 15/34] fix issues from conflicts

---
 .../__tests__/SubscriptionDetails.test.tsx    | 60 +++++++++----------
 packages/types/src/json.ts                    |  1 -
 2 files changed, 30 insertions(+), 31 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
index 55d4a848615..15e3c15af3c 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
@@ -54,9 +54,9 @@ describe('SubscriptionDetails', () => {
             isDefault: false,
           },
           createdAt: new Date('2021-01-01'),
-          periodStart: new Date('2021-01-01'),
-          periodEnd: new Date('2021-02-01'),
-          canceledAt: null,
+          periodStartDate: new Date('2021-01-01'),
+          periodEndDate: new Date('2021-02-01'),
+          canceledAtDate: null,
           paymentSourceId: 'src_123',
           planPeriod: 'month',
           status: 'active',
@@ -134,9 +134,9 @@ describe('SubscriptionDetails', () => {
             isDefault: false,
           },
           createdAt: new Date('2021-01-01'),
-          periodStart: new Date('2021-01-01'),
-          periodEnd: new Date('2022-01-01'),
-          canceledAt: null,
+          periodStartDate: new Date('2021-01-01'),
+          periodEndDate: new Date('2022-01-01'),
+          canceledAtDate: null,
           paymentSourceId: 'src_123',
           planPeriod: 'annual' as const,
           status: 'active' as const,
@@ -214,9 +214,9 @@ describe('SubscriptionDetails', () => {
             isDefault: true,
           },
           createdAt: new Date('2021-01-01'),
-          periodStart: new Date('2021-01-01'),
-          periodEnd: new Date('2021-02-01'),
-          canceledAt: null,
+          periodStartDate: new Date('2021-01-01'),
+          periodEndDate: new Date('2021-02-01'),
+          canceledAtDate: null,
           paymentSourceId: 'src_123',
           planPeriod: 'month' as const,
           status: 'active' as const,
@@ -309,9 +309,9 @@ describe('SubscriptionDetails', () => {
           id: 'sub_annual',
           plan: planAnnual,
           createdAt: new Date('2021-01-01'),
-          periodStart: new Date('2021-01-01'),
-          periodEnd: new Date('2022-01-01'),
-          canceledAt: new Date('2021-04-01'),
+          periodStartDate: new Date('2021-01-01'),
+          periodEndDate: new Date('2022-01-01'),
+          canceledAtDate: new Date('2021-04-01'),
           paymentSourceId: 'src_annual',
           planPeriod: 'annual' as const,
           status: 'active' as const,
@@ -320,9 +320,9 @@ describe('SubscriptionDetails', () => {
           id: 'sub_monthly',
           plan: planMonthly,
           createdAt: new Date('2022-01-01'),
-          periodStart: new Date('2022-02-01'),
-          periodEnd: new Date('2022-03-01'),
-          canceledAt: null,
+          periodStartDate: new Date('2022-02-01'),
+          periodEndDate: new Date('2022-03-01'),
+          canceledAtDate: null,
           paymentSourceId: 'src_monthly',
           planPeriod: 'month' as const,
           status: 'upcoming' as const,
@@ -436,9 +436,9 @@ describe('SubscriptionDetails', () => {
           id: 'test_active',
           plan: planMonthly,
           createdAt: new Date('2021-01-01'),
-          periodStart: new Date('2021-01-01'),
-          periodEnd: new Date('2021-02-01'),
-          canceledAt: new Date('2021-01-03'),
+          periodStartDate: new Date('2021-01-01'),
+          periodEndDate: new Date('2021-02-01'),
+          canceledAtDate: new Date('2021-01-03'),
           paymentSourceId: 'src_free_active',
           planPeriod: 'month' as const,
           status: 'active' as const,
@@ -447,8 +447,8 @@ describe('SubscriptionDetails', () => {
           id: 'sub_free_upcoming',
           plan: planFreeUpcoming,
           createdAt: new Date('2021-01-03'),
-          periodStart: new Date('2021-02-01'),
-          canceledAt: null,
+          periodStartDate: new Date('2021-02-01'),
+          canceledAtDate: null,
           paymentSourceId: 'src_free_upcoming',
           planPeriod: 'month' as const,
           status: 'upcoming' as const,
@@ -491,7 +491,7 @@ describe('SubscriptionDetails', () => {
     });
   });
 
-  it('allows cancelling a subscription of a monthly plan', async () => {
+  it.only('allows cancelling a subscription of a monthly plan', async () => {
     const { wrapper, fixtures } = await createFixtures(f => {
       f.withUser({ email_addresses: ['test@clerk.com'] });
     });
@@ -519,9 +519,9 @@ describe('SubscriptionDetails', () => {
             isDefault: false,
           },
           createdAt: new Date('2021-01-01'),
-          periodStart: new Date('2021-01-01'),
-          periodEnd: new Date('2021-02-01'),
-          canceledAt: null,
+          periodStartDate: new Date('2021-01-01'),
+          periodEndDate: new Date('2021-02-01'),
+          canceledAtDate: null,
           paymentSourceId: 'src_123',
           planPeriod: 'month' as const,
           status: 'active' as const,
@@ -606,9 +606,9 @@ describe('SubscriptionDetails', () => {
       id: 'sub_annual',
       plan,
       createdAt: new Date('2021-01-01'),
-      periodStart: new Date('2021-01-01'),
-      periodEnd: new Date('2022-01-01'),
-      canceledAt: new Date('2021-04-01'),
+      periodStartDate: new Date('2021-01-01'),
+      periodEndDate: new Date('2022-01-01'),
+      canceledAtDate: new Date('2021-04-01'),
       paymentSourceId: 'src_annual',
       planPeriod: 'annual' as const,
       status: 'active' as const,
@@ -682,9 +682,9 @@ describe('SubscriptionDetails', () => {
       id: 'sub_annual',
       plan,
       createdAt: new Date('2021-01-01'),
-      periodStart: new Date('2021-01-01'),
-      periodEnd: new Date('2022-01-01'),
-      canceledAt: null,
+      periodStartDate: new Date('2021-01-01'),
+      periodEndDate: new Date('2022-01-01'),
+      canceledAtDate: null,
       paymentSourceId: 'src_annual',
       planPeriod: 'annual' as const,
       status: 'active' as const,
diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts
index f2cf9313f68..779d0807f0c 100644
--- a/packages/types/src/json.ts
+++ b/packages/types/src/json.ts
@@ -697,7 +697,6 @@ export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
   credit?: {
     amount: CommerceMoneyJSON;
   };
-  created_at: number;
   payment_source_id: string;
   plan: CommercePlanJSON;
   plan_period: CommerceSubscriptionPlanPeriod;

From 84528d0de73b1331200346d349179c00aa0df257 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 2 Jul 2025 14:39:47 +0300
Subject: [PATCH 16/34] finishing touches on subscription details

---
 packages/clerk-js/src/ui/Components.tsx                     | 5 ++---
 .../src/ui/components/SubscriptionDetails/index.tsx         | 6 ++++--
 packages/clerk-js/src/ui/contexts/components/Plans.tsx      | 1 +
 .../clerk-js/src/ui/contexts/components/SubscriberType.ts   | 2 +-
 .../clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx   | 2 +-
 .../src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx  | 2 +-
 packages/types/src/appearance.ts                            | 1 +
 packages/types/src/clerk.ts                                 | 4 +++-
 8 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx
index 59b124f1fb9..5a45a2e8790 100644
--- a/packages/clerk-js/src/ui/Components.tsx
+++ b/packages/clerk-js/src/ui/Components.tsx
@@ -3,7 +3,6 @@ import type {
   __experimental_PlanDetailsProps,
   __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
-  __internal_PlanDetailsProps,
   __internal_UserVerificationProps,
   Appearance,
   Clerk,
@@ -113,9 +112,9 @@ export type ComponentControls = {
     props: T extends 'checkout'
       ? __internal_CheckoutProps
       : T extends 'planDetails'
-        ? __internal_PlanDetailsProps
+        ? __experimental_PlanDetailsProps
         : T extends 'subscriptionDetails'
-          ? __internal_PlanDetailsProps
+          ? __experimental_SubscriptionDetailsProps
           : never,
   ) => void;
   closeDrawer: (
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 6d749c40518..2f7e59c34a2 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -24,7 +24,7 @@ import { formatDate } from '@/ui/utils/formatDate';
 
 const isFreePlan = (plan: CommercePlanResource) => !plan.hasBaseFee;
 
-import { usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
+import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
 import type { LocalizationKey } from '../../customizables';
 import {
   Badge,
@@ -59,7 +59,9 @@ export const SubscriptionDetails = (props: __experimental_SubscriptionDetailsPro
   return (
     <Drawer.Content>
       <SubscriptionDetailsContext.Provider value={{ componentName: 'SubscriptionDetails', ...props }}>
-        <SubscriptionDetailsInternal {...props} />
+        <SubscriberTypeContext.Provider value={props.for}>
+          <SubscriptionDetailsInternal {...props} />
+        </SubscriberTypeContext.Provider>
       </SubscriptionDetailsContext.Provider>
     </Drawer.Content>
   );
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index bc1a3642c16..7419d98b74b 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -362,6 +362,7 @@ export const usePlansContext = () => {
 
       if (subscription && subscription.planPeriod === planPeriod && !subscription.canceledAtDate) {
         clerk.__experimental_openSubscriptionDetails({
+          for: subscriberType,
           onSubscriptionCancel: () => {
             revalidateAll();
             onSubscriptionChange?.();
diff --git a/packages/clerk-js/src/ui/contexts/components/SubscriberType.ts b/packages/clerk-js/src/ui/contexts/components/SubscriberType.ts
index 0653ece4067..3ce377b836a 100644
--- a/packages/clerk-js/src/ui/contexts/components/SubscriberType.ts
+++ b/packages/clerk-js/src/ui/contexts/components/SubscriberType.ts
@@ -1,7 +1,7 @@
 import { createContext, useContext } from 'react';
 
 const DEFAUlT = 'user';
-export const SubscriberTypeContext = createContext<'user' | 'org'>(DEFAUlT);
+export const SubscriberTypeContext = createContext<'user' | 'org' | undefined>(DEFAUlT);
 
 export const useSubscriberTypeContext = () => useContext(SubscriberTypeContext) || DEFAUlT;
 
diff --git a/packages/clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx b/packages/clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx
index 3ed00fc63f3..edb88bc9ddb 100644
--- a/packages/clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx
+++ b/packages/clerk-js/src/ui/lazyModules/MountedCheckoutDrawer.tsx
@@ -27,7 +27,7 @@ export function MountedCheckoutDrawer({
       // Without this, the drawer would not be rendered after a session switch.
       key={user?.id}
       globalAppearance={appearance}
-      appearanceKey={'checkout' as any}
+      appearanceKey={'checkout'}
       componentAppearance={checkoutDrawer.props.appearance || {}}
       flowName={'checkout'}
       open={checkoutDrawer.open}
diff --git a/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx b/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx
index 4a6186c4701..7bdec53ecda 100644
--- a/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx
+++ b/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx
@@ -27,7 +27,7 @@ export function MountedSubscriptionDetailDrawer({
       // Without this, the drawer would not be rendered after a session switch.
       key={user?.id}
       globalAppearance={appearance}
-      appearanceKey={'planDetails' as any}
+      appearanceKey={'subscriptionDetails' as any}
       componentAppearance={subscriptionDetailsDrawer.props.appearance || {}}
       flowName={'subscriptionDetails'}
       open={subscriptionDetailsDrawer.open}
diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts
index e9404d8fcdf..4a9142bc3c6 100644
--- a/packages/types/src/appearance.ts
+++ b/packages/types/src/appearance.ts
@@ -884,6 +884,7 @@ export type WaitlistTheme = Theme;
 export type PricingTableTheme = Theme;
 export type CheckoutTheme = Theme;
 export type PlanDetailTheme = Theme;
+export type SubscriptionDetailsTheme = Theme;
 export type APIKeysTheme = Theme;
 export type OAuthConsentTheme = Theme;
 
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 9e2d811346b..5aa5c3799e3 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -12,6 +12,7 @@ import type {
   PricingTableTheme,
   SignInTheme,
   SignUpTheme,
+  SubscriptionDetailsTheme,
   UserButtonTheme,
   UserProfileTheme,
   UserVerificationTheme,
@@ -1777,7 +1778,8 @@ export type __experimental_PlanDetailsProps = {
 };
 
 export type __experimental_SubscriptionDetailsProps = {
-  appearance?: PlanDetailTheme;
+  for?: CommerceSubscriberType;
+  appearance?: SubscriptionDetailsTheme;
   onSubscriptionCancel?: () => void;
   portalId?: string;
   portalRoot?: PortalRoot;

From dc926ab01178e4ad27e804d21b72cfc2dc1c8d3f Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 2 Jul 2025 14:58:35 +0300
Subject: [PATCH 17/34] add unit tests for PlanDetails

---
 .../src/ui/components/Plans/PlanDetails.tsx   |  13 +-
 .../Plans/__tests__/PlanDetails.test.tsx      | 281 ++++++++++++++++++
 .../components/SubscriptionDetails/index.tsx  |   2 -
 packages/clerk-js/src/ui/types.ts             |   9 +-
 .../src/ui/utils/test/createFixtures.tsx      |   2 +-
 5 files changed, 290 insertions(+), 17 deletions(-)
 create mode 100644 packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx

diff --git a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
index 7c30cd7f980..8e1512b0bb4 100644
--- a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
+++ b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
@@ -33,7 +33,7 @@ const PlanDetailsInternal = ({
 
   const { data: plan, isLoading } = useSWR(
     planId || initialPlan ? { type: 'plan', id: planId || initialPlan?.id } : null,
-    // @ts-expect-error
+    // @ts-expect-error we are handling it above
     () => clerk.billing.getPlan({ id: planId || initialPlan?.id }),
     {
       fallbackData: initialPlan,
@@ -153,17 +153,6 @@ const PlanDetailsInternal = ({
           </Box>
         </Drawer.Body>
       ) : null}
-
-      {/* {!plan.isDefault && !isDefaultPlanImplicitlyActiveOrUpcoming ? (
-        <Drawer.Footer>
-          <Button
-            block
-            textVariant='buttonLarge'
-            {...buttonPropsForPlan({ plan })}
-            onClick={() => openCheckout()}
-          />
-        </Drawer.Footer>
-      ) : null} */}
     </SubscriberTypeContext.Provider>
   );
 };
diff --git a/packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx b/packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
new file mode 100644
index 00000000000..3b4b4c7cc16
--- /dev/null
+++ b/packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx
@@ -0,0 +1,281 @@
+import { Drawer } from '@/ui/elements/Drawer';
+
+import { render, waitFor } from '../../../../testUtils';
+import { bindCreateFixtures } from '../../../utils/test/createFixtures';
+import { PlanDetails } from '../PlanDetails';
+
+const { createFixtures } = bindCreateFixtures('UserProfile');
+
+describe('PlanDetails', () => {
+  const mockFeature = {
+    id: 'feature_1',
+    name: 'Feature 1',
+    description: 'Feature 1 Description',
+    avatarUrl: 'https://example.com/feature1.png',
+    slug: 'feature-1',
+    __internal_toSnapshot: jest.fn(),
+    pathRoot: '',
+    reload: jest.fn(),
+  };
+
+  const mockFeature2 = {
+    id: 'feature_2',
+    name: 'Feature 2',
+    description: 'Feature 2 Description',
+    avatarUrl: 'https://example.com/feature2.png',
+    slug: 'feature-2',
+    __internal_toSnapshot: jest.fn(),
+    pathRoot: '',
+    reload: jest.fn(),
+  };
+
+  const mockPlan = {
+    id: 'plan_123',
+    name: 'Test Plan',
+    amount: 1000,
+    amountFormatted: '10.00',
+    annualAmount: 10000,
+    annualAmountFormatted: '100.00',
+    annualMonthlyAmount: 833,
+    annualMonthlyAmountFormatted: '8.33',
+    currencySymbol: '$',
+    description: 'Test Plan Description',
+    hasBaseFee: true,
+    isRecurring: true,
+    currency: 'USD',
+    isDefault: false,
+    payerType: ['user'],
+    publiclyVisible: true,
+    slug: 'test-plan',
+    avatarUrl: 'https://example.com/avatar.png',
+    features: [mockFeature, mockFeature2],
+    __internal_toSnapshot: jest.fn(),
+    pathRoot: '',
+    reload: jest.fn(),
+  };
+
+  it('displays spinner when loading with planId', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    fixtures.clerk.billing.getPlan.mockImplementation(() => new Promise(() => {}));
+
+    const { baseElement, queryByRole } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <PlanDetails planId='plan_123' />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    const spinner = baseElement.querySelector('span[aria-live="polite"]');
+    expect(spinner).toBeVisible();
+    expect(queryByRole('heading', { name: 'Test Plan' })).toBeNull();
+  });
+
+  it('renders plan details when plan is provided directly', async () => {
+    const { wrapper } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const { getByText, getByRole, baseElement } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <PlanDetails plan={mockPlan} />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    const spinner = baseElement.querySelector('span[aria-live="polite"]');
+    expect(spinner).toBeNull();
+    expect(getByRole('heading', { name: 'Test Plan' })).toBeVisible();
+    expect(getByText('Test Plan Description')).toBeVisible();
+    expect(getByText('$10.00')).toBeVisible();
+    expect(getByText('Feature 1')).toBeVisible();
+    expect(getByText('Feature 1 Description')).toBeVisible();
+    expect(getByText('Feature 2')).toBeVisible();
+    expect(getByText('Feature 2 Description')).toBeVisible();
+  });
+
+  it('fetches and renders plan details when planId is provided', async () => {
+    const { wrapper, fixtures } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    fixtures.clerk.billing.getPlan.mockResolvedValue(mockPlan);
+
+    const { getByText, getByRole } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <PlanDetails planId='plan_123' />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(fixtures.clerk.billing.getPlan).toHaveBeenCalledWith({ id: 'plan_123' });
+      expect(getByRole('heading', { name: 'Test Plan' })).toBeVisible();
+      expect(getByText('Test Plan Description')).toBeVisible();
+      expect(getByText('$10.00')).toBeVisible();
+    });
+  });
+
+  it('uses default monthly plan period when not specified', async () => {
+    const { wrapper } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const { getByText, queryByText } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <PlanDetails plan={mockPlan} />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(getByText('$10.00')).toBeVisible();
+      expect(queryByText('$8.33')).toBeNull();
+    });
+  });
+
+  it('respects initialPlanPeriod when provided as annual', async () => {
+    const { wrapper } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const { getByText, queryByText } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <PlanDetails
+          plan={mockPlan}
+          initialPlanPeriod='annual'
+        />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(getByText('$8.33')).toBeVisible();
+      expect(queryByText('$10.00')).toBeNull();
+    });
+  });
+
+  it('toggles between monthly and annual pricing when switch is clicked', async () => {
+    const { wrapper } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const { getByText, getByRole, userEvent } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <PlanDetails plan={mockPlan} />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(getByText('$10.00')).toBeVisible();
+    });
+
+    const switchButton = getByRole('switch', { name: /billed annually/i });
+    await userEvent.click(switchButton);
+
+    await waitFor(() => {
+      expect(getByText('$8.33')).toBeVisible();
+    });
+  });
+
+  it('does not show period toggle for plans with no annual pricing', async () => {
+    const planWithoutAnnual = {
+      ...mockPlan,
+      annualMonthlyAmount: 0,
+    };
+
+    const { wrapper } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const { queryByRole, getByText } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <PlanDetails plan={planWithoutAnnual} />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(queryByRole('switch')).toBeNull();
+      expect(getByText(/only billed monthly/i)).toBeVisible();
+    });
+  });
+
+  it('shows "Always free" notice for default free plans', async () => {
+    const freePlan = {
+      ...mockPlan,
+      amount: 0,
+      amountFormatted: '0.00',
+      annualMonthlyAmount: 0,
+      isDefault: true,
+    };
+
+    const { wrapper } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const { getByText } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <PlanDetails plan={freePlan} />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(getByText(/always free/i)).toBeVisible();
+    });
+  });
+
+  it('renders plan without features correctly', async () => {
+    const planWithoutFeatures = {
+      ...mockPlan,
+      features: [],
+    };
+
+    const { wrapper } = await createFixtures(f => {
+      f.withUser({ email_addresses: ['test@clerk.com'] });
+    });
+
+    const { queryByText, getByRole } = render(
+      <Drawer.Root
+        open
+        onOpenChange={() => {}}
+      >
+        <PlanDetails plan={planWithoutFeatures} />
+      </Drawer.Root>,
+      { wrapper },
+    );
+
+    await waitFor(() => {
+      expect(getByRole('heading', { name: 'Test Plan' })).toBeVisible();
+      expect(queryByText(/available features/i)).toBeNull();
+    });
+  });
+});
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 2f7e59c34a2..109c92fb2e1 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -486,8 +486,6 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
   const isActive = subscription.status === 'active';
   const { t } = useLocalizations();
 
-  console.log(subscription.periodEndDate, subscription.canceledAtDate, subscription);
-
   return (
     <Col
       elementDescriptor={descriptors.subscriptionDetailsCard}
diff --git a/packages/clerk-js/src/ui/types.ts b/packages/clerk-js/src/ui/types.ts
index 7fbd4bd04e1..9b70500231b 100644
--- a/packages/clerk-js/src/ui/types.ts
+++ b/packages/clerk-js/src/ui/types.ts
@@ -1,8 +1,8 @@
 import type {
+  __experimental_PlanDetailsProps,
   __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
   __internal_OAuthConsentProps,
-  __internal_PlanDetailsProps,
   __internal_UserVerificationProps,
   APIKeysProps,
   CreateOrganizationProps,
@@ -51,7 +51,8 @@ export type AvailableComponentProps =
   | PricingTableProps
   | __internal_CheckoutProps
   | __internal_UserVerificationProps
-  | __internal_PlanDetailsProps
+  | __experimental_SubscriptionDetailsProps
+  | __experimental_PlanDetailsProps
   | APIKeysProps;
 
 type ComponentMode = 'modal' | 'mounted';
@@ -143,6 +144,10 @@ export type SubscriptionDetailsCtx = __experimental_SubscriptionDetailsProps & {
   componentName: 'SubscriptionDetails';
 };
 
+export type PlanDetailsCtx = __experimental_PlanDetailsProps & {
+  componentName: 'PlanDetails';
+};
+
 export type AvailableComponentCtx =
   | SignInCtx
   | SignUpCtx
diff --git a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
index bc6532a706a..2b82dbaea6e 100644
--- a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
+++ b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx
@@ -90,7 +90,7 @@ const unboundCreateFixtures = (
     const MockClerkProvider = (props: any) => {
       const { children } = props;
 
-      const componentsWithoutContext = ['UsernameSection', 'UserProfileSection', 'SubscriptionDetails'];
+      const componentsWithoutContext = ['UsernameSection', 'UserProfileSection', 'SubscriptionDetails', 'PlanDetails'];
       const contextWrappedChildren = !componentsWithoutContext.includes(componentName) ? (
         <ComponentContextProvider
           componentName={componentName}

From 79b3c79e6447c6aac88dac8fb3a4f5a28cb49d9a Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 2 Jul 2025 15:39:28 +0300
Subject: [PATCH 18/34] remove old file

---
 .../ui/components/Plans/old_PlanDetails.tsx   | 522 ------------------
 1 file changed, 522 deletions(-)
 delete mode 100644 packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx

diff --git a/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx b/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx
deleted file mode 100644
index 3a967e241ea..00000000000
--- a/packages/clerk-js/src/ui/components/Plans/old_PlanDetails.tsx
+++ /dev/null
@@ -1,522 +0,0 @@
-import { useClerk, useOrganization } from '@clerk/shared/react';
-import type {
-  __internal_PlanDetailsProps,
-  ClerkAPIError,
-  ClerkRuntimeError,
-  CommercePlanResource,
-  CommerceSubscriptionPlanPeriod,
-  CommerceSubscriptionResource,
-} from '@clerk/types';
-import * as React from 'react';
-import { useMemo, useState } from 'react';
-
-import { Alert } from '@/ui/elements/Alert';
-import { Avatar } from '@/ui/elements/Avatar';
-import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
-import { Switch } from '@/ui/elements/Switch';
-import { handleError } from '@/ui/utils/errorHandler';
-
-import { useProtect } from '../../common';
-import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
-import { Badge, Box, Button, Col, descriptors, Flex, Heading, localizationKeys, Span, Text } from '../../customizables';
-
-export const PlanDetails = (props: __internal_PlanDetailsProps) => {
-  return (
-    <SubscriberTypeContext.Provider value={props.subscriberType || 'user'}>
-      <Drawer.Content>
-        <PlanDetailsInternal {...props} />
-      </Drawer.Content>
-    </SubscriberTypeContext.Provider>
-  );
-};
-
-const PlanDetailsInternal = ({
-  plan,
-  onSubscriptionCancel,
-  portalRoot,
-  initialPlanPeriod = 'month',
-}: __internal_PlanDetailsProps) => {
-  const clerk = useClerk();
-  const { organization } = useOrganization();
-  const [showConfirmation, setShowConfirmation] = useState(false);
-  const [isSubmitting, setIsSubmitting] = useState(false);
-  const [cancelError, setCancelError] = useState<ClerkRuntimeError | ClerkAPIError | string | undefined>();
-  const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(initialPlanPeriod);
-
-  const { setIsOpen } = useDrawerContext();
-  const {
-    activeOrUpcomingSubscriptionBasedOnPlanPeriod,
-    revalidateAll,
-    buttonPropsForPlan,
-    isDefaultPlanImplicitlyActiveOrUpcoming,
-  } = usePlansContext();
-  const subscriberType = useSubscriberTypeContext();
-  const canManageBilling = useProtect(
-    has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
-  );
-
-  if (!plan) {
-    return null;
-  }
-
-  const subscription = activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod);
-
-  const handleClose = () => {
-    if (setIsOpen) {
-      setIsOpen(false);
-    }
-  };
-
-  const features = plan.features;
-  const hasFeatures = features.length > 0;
-  const cancelSubscription = async () => {
-    if (!subscription) {
-      return;
-    }
-
-    setCancelError(undefined);
-    setIsSubmitting(true);
-
-    await subscription
-      .cancel({ orgId: subscriberType === 'org' ? organization?.id : undefined })
-      .then(() => {
-        setIsSubmitting(false);
-        onSubscriptionCancel?.();
-        handleClose();
-      })
-      .catch(error => {
-        handleError(error, [], setCancelError);
-        setIsSubmitting(false);
-      });
-  };
-
-  type Open__internal_CheckoutProps = {
-    planPeriod?: CommerceSubscriptionPlanPeriod;
-  };
-
-  const openCheckout = (props?: Open__internal_CheckoutProps) => {
-    handleClose();
-
-    // if the plan doesn't support annual, use monthly
-    let _planPeriod = props?.planPeriod || planPeriod;
-    if (_planPeriod === 'annual' && plan.annualMonthlyAmount === 0) {
-      _planPeriod = 'month';
-    }
-
-    clerk.__internal_openCheckout({
-      planId: plan.id,
-      planPeriod: _planPeriod,
-      subscriberType: subscriberType,
-      onSubscriptionComplete: () => {
-        void revalidateAll();
-      },
-      portalRoot,
-    });
-  };
-
-  return (
-    <>
-      <Drawer.Header
-        sx={t =>
-          !hasFeatures
-            ? {
-                flex: 1,
-                borderBottomWidth: 0,
-                background: t.colors.$colorBackground,
-              }
-            : null
-        }
-      >
-        <Header
-          plan={plan}
-          subscription={subscription}
-          planPeriod={planPeriod}
-          setPlanPeriod={setPlanPeriod}
-          closeSlot={<Drawer.Close />}
-        />
-      </Drawer.Header>
-
-      {hasFeatures ? (
-        <Drawer.Body>
-          <Text
-            elementDescriptor={descriptors.planDetailCaption}
-            variant={'caption'}
-            localizationKey={localizationKeys('commerce.availableFeatures')}
-            colorScheme='secondary'
-            sx={t => ({
-              padding: t.space.$4,
-              paddingBottom: 0,
-            })}
-          />
-          <Box
-            elementDescriptor={descriptors.planDetailFeaturesList}
-            as='ul'
-            role='list'
-            sx={t => ({
-              display: 'grid',
-              rowGap: t.space.$6,
-              padding: t.space.$4,
-              margin: 0,
-            })}
-          >
-            {features.map(feature => (
-              <Box
-                key={feature.id}
-                elementDescriptor={descriptors.planDetailFeaturesListItem}
-                as='li'
-                sx={t => ({
-                  display: 'flex',
-                  alignItems: 'baseline',
-                  gap: t.space.$3,
-                })}
-              >
-                {feature.avatarUrl ? (
-                  <Avatar
-                    size={_ => 24}
-                    title={feature.name}
-                    initials={feature.name[0]}
-                    rounded={false}
-                    imageUrl={feature.avatarUrl}
-                  />
-                ) : null}
-                <Span elementDescriptor={descriptors.planDetailFeaturesListItemContent}>
-                  <Text
-                    elementDescriptor={descriptors.planDetailFeaturesListItemTitle}
-                    colorScheme='body'
-                    sx={t => ({
-                      fontWeight: t.fontWeights.$medium,
-                    })}
-                  >
-                    {feature.name}
-                  </Text>
-                  {feature.description ? (
-                    <Text
-                      elementDescriptor={descriptors.planDetailFeaturesListItemDescription}
-                      colorScheme='secondary'
-                      sx={t => ({
-                        marginBlockStart: t.space.$0x25,
-                      })}
-                    >
-                      {feature.description}
-                    </Text>
-                  ) : null}
-                </Span>
-              </Box>
-            ))}
-          </Box>
-        </Drawer.Body>
-      ) : null}
-
-      {(!plan.isDefault && !isDefaultPlanImplicitlyActiveOrUpcoming) || !subscription ? (
-        <Drawer.Footer>
-          {subscription ? (
-            subscription.canceledAt ? (
-              <Button
-                block
-                textVariant='buttonLarge'
-                {...buttonPropsForPlan({ plan })}
-                onClick={() => openCheckout()}
-              />
-            ) : (
-              <Col gap={4}>
-                {!!subscription &&
-                subscription.planPeriod === 'month' &&
-                plan.annualMonthlyAmount > 0 &&
-                planPeriod === 'annual' ? (
-                  <Button
-                    block
-                    variant='bordered'
-                    colorScheme='secondary'
-                    textVariant='buttonLarge'
-                    isDisabled={!canManageBilling}
-                    onClick={() => openCheckout({ planPeriod: 'annual' })}
-                    localizationKey={localizationKeys('commerce.switchToAnnual')}
-                  />
-                ) : null}
-                {!!subscription && subscription.planPeriod === 'annual' && planPeriod === 'month' ? (
-                  <Button
-                    block
-                    variant='bordered'
-                    colorScheme='secondary'
-                    textVariant='buttonLarge'
-                    isDisabled={!canManageBilling}
-                    onClick={() => openCheckout({ planPeriod: 'month' })}
-                    localizationKey={localizationKeys('commerce.switchToMonthly')}
-                  />
-                ) : null}
-                <Button
-                  block
-                  variant='bordered'
-                  colorScheme='danger'
-                  textVariant='buttonLarge'
-                  isDisabled={!canManageBilling}
-                  onClick={() => setShowConfirmation(true)}
-                  localizationKey={localizationKeys('commerce.cancelSubscription')}
-                />
-              </Col>
-            )
-          ) : (
-            <Button
-              block
-              textVariant='buttonLarge'
-              {...buttonPropsForPlan({ plan })}
-              onClick={() => openCheckout()}
-            />
-          )}
-        </Drawer.Footer>
-      ) : null}
-
-      {subscription ? (
-        <Drawer.Confirmation
-          open={showConfirmation}
-          onOpenChange={setShowConfirmation}
-          actionsSlot={
-            <>
-              {!isSubmitting && (
-                <Button
-                  variant='ghost'
-                  size='sm'
-                  textVariant='buttonLarge'
-                  isDisabled={!canManageBilling}
-                  onClick={() => {
-                    setCancelError(undefined);
-                    setShowConfirmation(false);
-                  }}
-                  localizationKey={localizationKeys('commerce.keepSubscription')}
-                />
-              )}
-              <Button
-                variant='solid'
-                colorScheme='danger'
-                size='sm'
-                textVariant='buttonLarge'
-                isLoading={isSubmitting}
-                isDisabled={!canManageBilling}
-                onClick={() => {
-                  setCancelError(undefined);
-                  setShowConfirmation(false);
-                  void cancelSubscription();
-                }}
-                localizationKey={localizationKeys('commerce.cancelSubscription')}
-              />
-            </>
-          }
-        >
-          <Heading
-            elementDescriptor={descriptors.drawerConfirmationTitle}
-            as='h2'
-            textVariant='h3'
-            localizationKey={localizationKeys('commerce.cancelSubscriptionTitle', {
-              plan: `${subscription.status === 'upcoming' ? 'upcoming ' : ''}${subscription.plan.name}`,
-            })}
-          />
-          <Text
-            elementDescriptor={descriptors.drawerConfirmationDescription}
-            colorScheme='secondary'
-            localizationKey={
-              subscription.status === 'upcoming'
-                ? localizationKeys('commerce.cancelSubscriptionNoCharge')
-                : localizationKeys('commerce.cancelSubscriptionAccessUntil', {
-                    plan: subscription.plan.name,
-                    date: subscription.periodEnd,
-                  })
-            }
-          />
-          {cancelError && (
-            <Alert colorScheme='danger'>{typeof cancelError === 'string' ? cancelError : cancelError.message}</Alert>
-          )}
-        </Drawer.Confirmation>
-      ) : null}
-    </>
-  );
-};
-
-/* -------------------------------------------------------------------------------------------------
- * Header
- * -----------------------------------------------------------------------------------------------*/
-
-interface HeaderProps {
-  plan: CommercePlanResource;
-  subscription?: CommerceSubscriptionResource;
-  planPeriod: CommerceSubscriptionPlanPeriod;
-  setPlanPeriod: (val: CommerceSubscriptionPlanPeriod) => void;
-  closeSlot?: React.ReactNode;
-}
-
-const Header = React.forwardRef<HTMLDivElement, HeaderProps>((props, ref) => {
-  const { plan, subscription, closeSlot, planPeriod, setPlanPeriod } = props;
-
-  const { captionForSubscription, isDefaultPlanImplicitlyActiveOrUpcoming } = usePlansContext();
-  const { data: subscriptions } = useSubscriptions();
-
-  const isImplicitlyActiveOrUpcoming = isDefaultPlanImplicitlyActiveOrUpcoming && plan.isDefault;
-
-  const showBadge = !!subscription;
-
-  const getPlanFee = useMemo(() => {
-    if (plan.annualMonthlyAmount <= 0) {
-      return plan.amountFormatted;
-    }
-    return planPeriod === 'annual' ? plan.annualMonthlyAmountFormatted : plan.amountFormatted;
-  }, [plan, planPeriod]);
-
-  return (
-    <Box
-      ref={ref}
-      elementDescriptor={descriptors.planDetailHeader}
-      sx={t => ({
-        width: '100%',
-        padding: t.space.$4,
-        position: 'relative',
-      })}
-    >
-      {closeSlot ? (
-        <Box
-          sx={t => ({
-            position: 'absolute',
-            top: t.space.$2,
-            insetInlineEnd: t.space.$2,
-          })}
-        >
-          {closeSlot}
-        </Box>
-      ) : null}
-
-      <Col
-        gap={3}
-        elementDescriptor={descriptors.planDetailBadgeAvatarTitleDescriptionContainer}
-      >
-        {showBadge ? (
-          <Flex
-            align='center'
-            gap={3}
-            elementDescriptor={descriptors.planDetailBadgeContainer}
-            sx={t => ({
-              paddingInlineEnd: t.space.$10,
-            })}
-          >
-            {subscription?.status === 'active' || (isImplicitlyActiveOrUpcoming && subscriptions.length === 0) ? (
-              <Badge
-                elementDescriptor={descriptors.planDetailBadge}
-                localizationKey={localizationKeys('badge__activePlan')}
-                colorScheme={'secondary'}
-              />
-            ) : (
-              <Badge
-                elementDescriptor={descriptors.planDetailBadge}
-                localizationKey={localizationKeys('badge__upcomingPlan')}
-                colorScheme={'primary'}
-              />
-            )}
-            {!!subscription && (
-              <Text
-                elementDescriptor={descriptors.planDetailCaption}
-                variant={'caption'}
-                localizationKey={captionForSubscription(subscription)}
-                colorScheme='secondary'
-              />
-            )}
-          </Flex>
-        ) : null}
-        {plan.avatarUrl ? (
-          <Avatar
-            boxElementDescriptor={descriptors.planDetailAvatar}
-            size={_ => 40}
-            title={plan.name}
-            initials={plan.name[0]}
-            rounded={false}
-            imageUrl={plan.avatarUrl}
-            sx={t => ({
-              marginBlockEnd: t.space.$3,
-            })}
-          />
-        ) : null}
-        <Col
-          gap={1}
-          elementDescriptor={descriptors.planDetailTitleDescriptionContainer}
-        >
-          <Heading
-            elementDescriptor={descriptors.planDetailTitle}
-            as='h2'
-            textVariant='h2'
-          >
-            {plan.name}
-          </Heading>
-          {plan.description ? (
-            <Text
-              elementDescriptor={descriptors.planDetailDescription}
-              variant='subtitle'
-              colorScheme='secondary'
-            >
-              {plan.description}
-            </Text>
-          ) : null}
-        </Col>
-      </Col>
-
-      <Flex
-        elementDescriptor={descriptors.planDetailFeeContainer}
-        align='center'
-        wrap='wrap'
-        sx={t => ({
-          marginTop: t.space.$3,
-          columnGap: t.space.$1x5,
-        })}
-      >
-        <>
-          <Text
-            elementDescriptor={descriptors.planDetailFee}
-            variant='h1'
-            colorScheme='body'
-          >
-            {plan.currencySymbol}
-            {getPlanFee}
-          </Text>
-          <Text
-            elementDescriptor={descriptors.planDetailFeePeriod}
-            variant='caption'
-            colorScheme='secondary'
-            sx={t => ({
-              textTransform: 'lowercase',
-              ':before': {
-                content: '"/"',
-                marginInlineEnd: t.space.$1,
-              },
-            })}
-            localizationKey={localizationKeys('commerce.month')}
-          />
-        </>
-      </Flex>
-
-      {plan.annualMonthlyAmount > 0 ? (
-        <Box
-          elementDescriptor={descriptors.planDetailPeriodToggle}
-          sx={t => ({
-            display: 'flex',
-            marginTop: t.space.$3,
-          })}
-        >
-          <Switch
-            isChecked={planPeriod === 'annual'}
-            onChange={(checked: boolean) => setPlanPeriod(checked ? 'annual' : 'month')}
-            label={localizationKeys('commerce.billedAnnually')}
-          />
-        </Box>
-      ) : (
-        <Text
-          elementDescriptor={descriptors.pricingTableCardFeePeriodNotice}
-          variant='caption'
-          colorScheme='secondary'
-          localizationKey={
-            plan.isDefault ? localizationKeys('commerce.alwaysFree') : localizationKeys('commerce.billedMonthlyOnly')
-          }
-          sx={t => ({
-            justifySelf: 'flex-start',
-            alignSelf: 'center',
-            marginTop: t.space.$3,
-          })}
-        />
-      )}
-    </Box>
-  );
-});

From 958fdd90764665fe421f7452eaef2753a2947177 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 2 Jul 2025 16:00:07 +0300
Subject: [PATCH 19/34] replace experimental with internal apis

---
 packages/clerk-js/src/core/clerk.ts           | 31 +++-----------
 packages/clerk-js/src/ui/Components.tsx       | 12 +++---
 .../src/ui/components/Plans/PlanDetails.tsx   | 10 ++---
 .../PricingTable/PricingTableDefault.tsx      |  4 +-
 .../components/SubscriptionDetails/index.tsx  |  6 +--
 .../src/ui/contexts/components/Plans.tsx      |  2 +-
 .../MountedSubscriptionDetailDrawer.tsx       |  4 +-
 packages/clerk-js/src/ui/types.ts             | 15 +++----
 packages/react/src/isomorphicClerk.ts         | 37 +++++------------
 packages/types/src/clerk.ts                   | 40 ++++++-------------
 10 files changed, 51 insertions(+), 110 deletions(-)

diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index ed31c4c3625..d1bfc3a0761 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -15,12 +15,11 @@ import {
 import { addClerkPrefix, isAbsoluteUrl, stripScheme } from '@clerk/shared/url';
 import { allSettled, handleValueOrFn, noop } from '@clerk/shared/utils';
 import type {
-  __experimental_PlanDetailsProps,
-  __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
   __internal_ComponentNavigationContext,
   __internal_OAuthConsentProps,
   __internal_PlanDetailsProps,
+  __internal_SubscriptionDetailsProps,
   __internal_UserVerificationModalProps,
   APIKeysNamespace,
   APIKeysProps,
@@ -591,27 +590,7 @@ export class Clerk implements ClerkInterface {
     void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('checkout'));
   };
 
-  public __internal_openPlanDetails = (props?: __internal_PlanDetailsProps): void => {
-    this.assertComponentsReady(this.#componentControls);
-    if (disabledBillingFeature(this, this.environment)) {
-      if (this.#instanceType === 'development') {
-        throw new ClerkRuntimeError(warnings.cannotRenderAnyCommerceComponent('PlanDetails'), {
-          code: CANNOT_RENDER_BILLING_DISABLED_ERROR_CODE,
-        });
-      }
-      return;
-    }
-    void this.#componentControls
-      .ensureMounted({ preloadHint: 'PlanDetails' })
-      .then(controls => controls.openDrawer('planDetails', props || {}));
-  };
-
-  public __internal_closePlanDetails = (): void => {
-    this.assertComponentsReady(this.#componentControls);
-    void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('planDetails'));
-  };
-
-  public __experimental_openPlanDetails = (props?: __experimental_PlanDetailsProps): void => {
+  public __internal_openPlanDetails = (props: __internal_PlanDetailsProps): void => {
     this.assertComponentsReady(this.#componentControls);
     if (disabledBillingFeature(this, this.environment)) {
       if (this.#instanceType === 'development') {
@@ -628,19 +607,19 @@ export class Clerk implements ClerkInterface {
     this.telemetry?.record(eventPrebuiltComponentOpened(`PlanDetails`, props));
   };
 
-  public __experimental_closePlanDetails = (): void => {
+  public __internal_closePlanDetails = (): void => {
     this.assertComponentsReady(this.#componentControls);
     void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('planDetails'));
   };
 
-  public __experimental_openSubscriptionDetails = (props?: __experimental_SubscriptionDetailsProps): void => {
+  public __internal_openSubscriptionDetails = (props?: __internal_SubscriptionDetailsProps): void => {
     this.assertComponentsReady(this.#componentControls);
     void this.#componentControls
       .ensureMounted({ preloadHint: 'SubscriptionDetails' })
       .then(controls => controls.openDrawer('subscriptionDetails', props || {}));
   };
 
-  public __experimental_closeSubscriptionDetails = (): void => {
+  public __internal_closeSubscriptionDetails = (): void => {
     this.assertComponentsReady(this.#componentControls);
     void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('subscriptionDetails'));
   };
diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx
index 5a45a2e8790..45dfd1cb763 100644
--- a/packages/clerk-js/src/ui/Components.tsx
+++ b/packages/clerk-js/src/ui/Components.tsx
@@ -1,8 +1,8 @@
 import { createDeferredPromise } from '@clerk/shared/utils';
 import type {
-  __experimental_PlanDetailsProps,
-  __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
+  __internal_PlanDetailsProps,
+  __internal_SubscriptionDetailsProps,
   __internal_UserVerificationProps,
   Appearance,
   Clerk,
@@ -112,9 +112,9 @@ export type ComponentControls = {
     props: T extends 'checkout'
       ? __internal_CheckoutProps
       : T extends 'planDetails'
-        ? __experimental_PlanDetailsProps
+        ? __internal_PlanDetailsProps
         : T extends 'subscriptionDetails'
-          ? __experimental_SubscriptionDetailsProps
+          ? __internal_SubscriptionDetailsProps
           : never,
   ) => void;
   closeDrawer: (
@@ -161,11 +161,11 @@ interface ComponentsState {
   };
   planDetailsDrawer: {
     open: false;
-    props: null | __experimental_PlanDetailsProps;
+    props: null | __internal_PlanDetailsProps;
   };
   subscriptionDetailsDrawer: {
     open: false;
-    props: null | __experimental_SubscriptionDetailsProps;
+    props: null | __internal_SubscriptionDetailsProps;
   };
   nodes: Map<HTMLDivElement, HtmlNodeOptions>;
   impersonationFab: boolean;
diff --git a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
index 8e1512b0bb4..0836fb70727 100644
--- a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
+++ b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
@@ -1,9 +1,5 @@
 import { useClerk } from '@clerk/shared/react';
-import type {
-  __experimental_PlanDetailsProps,
-  CommercePlanResource,
-  CommerceSubscriptionPlanPeriod,
-} from '@clerk/types';
+import type { __internal_PlanDetailsProps, CommercePlanResource, CommerceSubscriptionPlanPeriod } from '@clerk/types';
 import * as React from 'react';
 import { useMemo, useState } from 'react';
 import useSWR from 'swr';
@@ -15,7 +11,7 @@ import { Switch } from '@/ui/elements/Switch';
 import { SubscriberTypeContext } from '../../contexts';
 import { Box, Col, descriptors, Flex, Heading, localizationKeys, Span, Spinner, Text } from '../../customizables';
 
-export const PlanDetails = (props: __experimental_PlanDetailsProps) => {
+export const PlanDetails = (props: __internal_PlanDetailsProps) => {
   return (
     <Drawer.Content>
       <PlanDetailsInternal {...props} />
@@ -27,7 +23,7 @@ const PlanDetailsInternal = ({
   planId,
   plan: initialPlan,
   initialPlanPeriod = 'month',
-}: __experimental_PlanDetailsProps) => {
+}: __internal_PlanDetailsProps) => {
   const clerk = useClerk();
   const [planPeriod, setPlanPeriod] = useState<CommerceSubscriptionPlanPeriod>(initialPlanPeriod);
 
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
index a6dd6360a43..ab99e7fe3bc 100644
--- a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
+++ b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
@@ -117,10 +117,8 @@ function Card(props: CardProps) {
   const showPlanDetails = (event?: React.MouseEvent<HTMLElement>) => {
     const portalRoot = getClosestProfileScrollBox(mode, event);
 
-    clerk.__experimental_openPlanDetails({
+    clerk.__internal_openPlanDetails({
       plan,
-      // planId: plan.id,
-      // subscriberType,
       initialPlanPeriod: planPeriod,
       portalRoot,
     });
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 109c92fb2e1..3e36abd05a7 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -1,7 +1,7 @@
 import { useClerk, useOrganization } from '@clerk/shared/react';
 import type {
-  __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
+  __internal_SubscriptionDetailsProps,
   CommercePlanResource,
   CommerceSubscriptionResource,
 } from '@clerk/types';
@@ -55,7 +55,7 @@ const SubscriptionForCancellationContext = React.createContext<{
   setSubscription: () => {},
 });
 
-export const SubscriptionDetails = (props: __experimental_SubscriptionDetailsProps) => {
+export const SubscriptionDetails = (props: __internal_SubscriptionDetailsProps) => {
   return (
     <Drawer.Content>
       <SubscriptionDetailsContext.Provider value={{ componentName: 'SubscriptionDetails', ...props }}>
@@ -100,7 +100,7 @@ function useGuessableSubscription<Or extends 'throw' | undefined = undefined>(op
   };
 }
 
-const SubscriptionDetailsInternal = (props: __experimental_SubscriptionDetailsProps) => {
+const SubscriptionDetailsInternal = (props: __internal_SubscriptionDetailsProps) => {
   const { organization: _organization } = useOrganization();
   const [subscriptionForCancellation, setSubscriptionForCancellation] = useState<CommerceSubscriptionResource | null>(
     null,
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index 7419d98b74b..a5a99f2a6fc 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -361,7 +361,7 @@ export const usePlansContext = () => {
       const portalRoot = getClosestProfileScrollBox(mode, event);
 
       if (subscription && subscription.planPeriod === planPeriod && !subscription.canceledAtDate) {
-        clerk.__experimental_openSubscriptionDetails({
+        clerk.__internal_openSubscriptionDetails({
           for: subscriberType,
           onSubscriptionCancel: () => {
             revalidateAll();
diff --git a/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx b/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx
index 7bdec53ecda..53835c691f7 100644
--- a/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx
+++ b/packages/clerk-js/src/ui/lazyModules/MountedSubscriptionDetailDrawer.tsx
@@ -1,5 +1,5 @@
 import { useUser } from '@clerk/shared/react';
-import type { __experimental_SubscriptionDetailsProps, Appearance } from '@clerk/types';
+import type { __internal_SubscriptionDetailsProps, Appearance } from '@clerk/types';
 
 import { SubscriptionDetails } from '../components/SubscriptionDetails';
 import { LazyDrawerRenderer } from './providers';
@@ -13,7 +13,7 @@ export function MountedSubscriptionDetailDrawer({
   onOpenChange: (open: boolean) => void;
   subscriptionDetailsDrawer: {
     open: false;
-    props: null | __experimental_SubscriptionDetailsProps;
+    props: null | __internal_SubscriptionDetailsProps;
   };
 }) {
   const { user } = useUser();
diff --git a/packages/clerk-js/src/ui/types.ts b/packages/clerk-js/src/ui/types.ts
index 9b70500231b..3f776335b94 100644
--- a/packages/clerk-js/src/ui/types.ts
+++ b/packages/clerk-js/src/ui/types.ts
@@ -1,8 +1,8 @@
 import type {
-  __experimental_PlanDetailsProps,
-  __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
   __internal_OAuthConsentProps,
+  __internal_PlanDetailsProps,
+  __internal_SubscriptionDetailsProps,
   __internal_UserVerificationProps,
   APIKeysProps,
   CreateOrganizationProps,
@@ -51,8 +51,8 @@ export type AvailableComponentProps =
   | PricingTableProps
   | __internal_CheckoutProps
   | __internal_UserVerificationProps
-  | __experimental_SubscriptionDetailsProps
-  | __experimental_PlanDetailsProps
+  | __internal_SubscriptionDetailsProps
+  | __internal_PlanDetailsProps
   | APIKeysProps;
 
 type ComponentMode = 'modal' | 'mounted';
@@ -140,11 +140,11 @@ export type OAuthConsentCtx = __internal_OAuthConsentProps & {
   componentName: 'OAuthConsent';
 };
 
-export type SubscriptionDetailsCtx = __experimental_SubscriptionDetailsProps & {
+export type SubscriptionDetailsCtx = __internal_SubscriptionDetailsProps & {
   componentName: 'SubscriptionDetails';
 };
 
-export type PlanDetailsCtx = __experimental_PlanDetailsProps & {
+export type PlanDetailsCtx = __internal_PlanDetailsProps & {
   componentName: 'PlanDetails';
 };
 
@@ -164,5 +164,6 @@ export type AvailableComponentCtx =
   | CheckoutCtx
   | APIKeysCtx
   | OAuthConsentCtx
-  | SubscriptionDetailsCtx;
+  | SubscriptionDetailsCtx
+  | PlanDetailsCtx;
 export type AvailableComponentName = AvailableComponentCtx['componentName'];
diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts
index d2f0894733c..43425068470 100644
--- a/packages/react/src/isomorphicClerk.ts
+++ b/packages/react/src/isomorphicClerk.ts
@@ -3,11 +3,10 @@ import { clerkEvents, createClerkEventBus } from '@clerk/shared/clerkEventBus';
 import { loadClerkJsScript } from '@clerk/shared/loadClerkJsScript';
 import { handleValueOrFn } from '@clerk/shared/utils';
 import type {
-  __experimental_PlanDetailsProps,
-  __experimental_SubscriptionDetailsProps,
   __internal_CheckoutProps,
   __internal_OAuthConsentProps,
   __internal_PlanDetailsProps,
+  __internal_SubscriptionDetailsProps,
   __internal_UserVerificationModalProps,
   __internal_UserVerificationProps,
   APIKeysNamespace,
@@ -121,8 +120,8 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
   private preopenUserVerification?: null | __internal_UserVerificationProps = null;
   private preopenSignIn?: null | SignInProps = null;
   private preopenCheckout?: null | __internal_CheckoutProps = null;
-  private preopenPlanDetails?: null | __experimental_PlanDetailsProps = null;
-  private preopenSubscriptionDetails?: null | __experimental_SubscriptionDetailsProps = null;
+  private preopenPlanDetails: null | __internal_PlanDetailsProps = null;
+  private preopenSubscriptionDetails: null | __internal_SubscriptionDetailsProps = null;
   private preopenSignUp?: null | SignUpProps = null;
   private preopenUserProfile?: null | UserProfileProps = null;
   private preopenOrganizationProfile?: null | OrganizationProfileProps = null;
@@ -560,11 +559,11 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
     }
 
     if (this.preopenPlanDetails !== null) {
-      clerkjs.__experimental_openPlanDetails(this.preopenPlanDetails);
+      clerkjs.__internal_openPlanDetails(this.preopenPlanDetails);
     }
 
     if (this.preopenSubscriptionDetails !== null) {
-      clerkjs.__experimental_openSubscriptionDetails(this.preopenSubscriptionDetails);
+      clerkjs.__internal_openSubscriptionDetails(this.preopenSubscriptionDetails);
     }
 
     if (this.preopenSignUp !== null) {
@@ -779,7 +778,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
     }
   };
 
-  __internal_openPlanDetails = (props?: __internal_PlanDetailsProps) => {
+  __internal_openPlanDetails = (props: __internal_PlanDetailsProps) => {
     if (this.clerkjs && this.loaded) {
       this.clerkjs.__internal_openPlanDetails(props);
     } else {
@@ -795,33 +794,17 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
     }
   };
 
-  __experimental_openPlanDetails = (props?: __experimental_PlanDetailsProps) => {
+  __internal_openSubscriptionDetails = (props?: __internal_SubscriptionDetailsProps) => {
     if (this.clerkjs && this.loaded) {
-      this.clerkjs.__experimental_openPlanDetails(props);
-    } else {
-      this.preopenPlanDetails = props;
-    }
-  };
-
-  __experimental_closePlanDetails = () => {
-    if (this.clerkjs && this.loaded) {
-      this.clerkjs.__experimental_closePlanDetails();
-    } else {
-      this.preopenPlanDetails = null;
-    }
-  };
-
-  __experimental_openSubscriptionDetails = (props?: __experimental_SubscriptionDetailsProps) => {
-    if (this.clerkjs && this.loaded) {
-      this.clerkjs.__experimental_openSubscriptionDetails(props);
+      this.clerkjs.__internal_openSubscriptionDetails(props);
     } else {
       this.preopenSubscriptionDetails = props;
     }
   };
 
-  __experimental_closeSubscriptionDetails = () => {
+  __internal_closeSubscriptionDetails = () => {
     if (this.clerkjs && this.loaded) {
-      this.clerkjs.__experimental_closeSubscriptionDetails();
+      this.clerkjs.__internal_closeSubscriptionDetails();
     } else {
       this.preopenSubscriptionDetails = null;
     }
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 5aa5c3799e3..5bdb23fe518 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -224,9 +224,9 @@ export interface Clerk {
 
   /**
    * Opens the Clerk PlanDetails drawer component in a drawer.
-   * @param props Optional subscription details drawer configuration parameters.
+   * @param props `plan` or `planId` parameters are required.
    */
-  __internal_openPlanDetails: (props?: __internal_PlanDetailsProps) => void;
+  __internal_openPlanDetails: (props: __internal_PlanDetailsProps) => void;
 
   /**
    * Closes the Clerk PlanDetails drawer.
@@ -234,26 +234,15 @@ export interface Clerk {
   __internal_closePlanDetails: () => void;
 
   /**
-   * Opens the Clerk PlanDetails drawer component in a drawer.
-   * @param props Optional subscription details drawer configuration parameters.
-   */
-  __experimental_openPlanDetails: (props?: __experimental_PlanDetailsProps) => void;
-
-  /**
-   * Closes the Clerk PlanDetails drawer.
+   * Opens the Clerk SubscriptionDetails drawer component in a drawer.
+   * @param props Optional configuration parameters.
    */
-  __experimental_closePlanDetails: () => void;
-
-  /**
-   * Opens the Clerk PlanDetails drawer component in a drawer.
-   * @param props Optional subscription details drawer configuration parameters.
-   */
-  __experimental_openSubscriptionDetails: (props?: __experimental_SubscriptionDetailsProps) => void;
+  __internal_openSubscriptionDetails: (props?: __internal_SubscriptionDetailsProps) => void;
 
   /**
    * Closes the Clerk PlanDetails drawer.
    */
-  __experimental_closeSubscriptionDetails: () => void;
+  __internal_closeSubscriptionDetails: () => void;
 
   /**
   /** Opens the Clerk UserVerification component in a modal.
@@ -1759,16 +1748,6 @@ export type __internal_CheckoutProps = {
 };
 
 export type __internal_PlanDetailsProps = {
-  appearance?: PlanDetailTheme;
-  plan?: CommercePlanResource;
-  subscriberType?: CommerceSubscriberType;
-  initialPlanPeriod?: CommerceSubscriptionPlanPeriod;
-  onSubscriptionCancel?: () => void;
-  portalId?: string;
-  portalRoot?: PortalRoot;
-};
-
-export type __experimental_PlanDetailsProps = {
   appearance?: PlanDetailTheme;
   plan?: CommercePlanResource;
   planId?: string;
@@ -1777,7 +1756,12 @@ export type __experimental_PlanDetailsProps = {
   portalRoot?: PortalRoot;
 };
 
-export type __experimental_SubscriptionDetailsProps = {
+export type __internal_SubscriptionDetailsProps = {
+  /**
+   * The subscriber type to display the subscription details for.
+   * If `org` is provided, the subscription details will be displayed for the active organization.
+   * @default 'user'
+   */
   for?: CommerceSubscriberType;
   appearance?: SubscriptionDetailsTheme;
   onSubscriptionCancel?: () => void;

From c5dafc435e19f36edaaca86ec9a53e1127876faf Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Wed, 2 Jul 2025 16:26:09 +0300
Subject: [PATCH 20/34] Update packages/types/src/clerk.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
 packages/types/src/clerk.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 5bdb23fe518..5b32fe26d1b 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -240,7 +240,7 @@ export interface Clerk {
   __internal_openSubscriptionDetails: (props?: __internal_SubscriptionDetailsProps) => void;
 
   /**
-   * Closes the Clerk PlanDetails drawer.
+   * Closes the Clerk SubscriptionDetails drawer.
    */
   __internal_closeSubscriptionDetails: () => void;
 

From bd9984f4a5a63440eace1efdd1244db619bdab06 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Thu, 10 Jul 2025 15:57:13 +0300
Subject: [PATCH 21/34] address pr comments

---
 .../Subscriptions/SubscriptionsList.tsx       | 53 +++++--------
 .../src/ui/contexts/components/Plans.tsx      | 77 ++++++++-----------
 packages/localizations/src/en-US.ts           |  4 +-
 3 files changed, 55 insertions(+), 79 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
index 9c08b9f1258..17d6eaff437 100644
--- a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
+++ b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
@@ -1,5 +1,3 @@
-import type { CommerceSubscriptionResource } from '@clerk/types';
-
 import { ProfileSection } from '@/ui/elements/Section';
 
 import { useProtect } from '../../common';
@@ -38,7 +36,7 @@ export function SubscriptionsList({
   arrowButtonText: LocalizationKey;
   arrowButtonEmptyText: LocalizationKey;
 }) {
-  const { handleSelectPlan, captionForSubscription, canManageSubscription } = usePlansContext();
+  const { captionForSubscription, openSubscriptionDetails } = usePlansContext();
   const localizationRoot = useSubscriberTypeLocalizationRoot();
   const subscriberType = useSubscriberTypeContext();
   const { data: subscriptions } = useSubscriptions();
@@ -46,17 +44,6 @@ export function SubscriptionsList({
     has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
   );
   const { navigate } = useRouter();
-  const handleSelectSubscription = (
-    subscription: CommerceSubscriptionResource,
-    event?: React.MouseEvent<HTMLElement>,
-  ) => {
-    handleSelectPlan({
-      mode: 'modal', // always modal for now
-      plan: subscription.plan,
-      planPeriod: subscription.planPeriod,
-      event,
-    });
-  };
 
   const sortedSubscriptions = subscriptions.sort((a, b) => {
     // alway put active subscriptions first
@@ -179,28 +166,26 @@ export function SubscriptionsList({
                     textAlign: 'right',
                   })}
                 >
-                  {canManageSubscription({ subscription }) && subscription.id && !subscription.plan.isDefault && (
-                    <Button
-                      aria-label='Manage subscription'
-                      onClick={event => handleSelectSubscription(subscription, event)}
-                      variant='bordered'
-                      colorScheme='secondary'
-                      isDisabled={!canManageBilling}
+                  <Button
+                    aria-label='Manage subscription'
+                    onClick={event => openSubscriptionDetails(event)}
+                    variant='bordered'
+                    colorScheme='secondary'
+                    isDisabled={!canManageBilling}
+                    sx={t => ({
+                      width: t.sizes.$6,
+                      height: t.sizes.$6,
+                    })}
+                  >
+                    <Icon
+                      icon={CogFilled}
                       sx={t => ({
-                        width: t.sizes.$6,
-                        height: t.sizes.$6,
+                        width: t.sizes.$4,
+                        height: t.sizes.$4,
+                        opacity: t.opacity.$inactive,
                       })}
-                    >
-                      <Icon
-                        icon={CogFilled}
-                        sx={t => ({
-                          width: t.sizes.$4,
-                          height: t.sizes.$4,
-                          opacity: t.opacity.$inactive,
-                        })}
-                      />
-                    </Button>
-                  )}
+                    />
+                  </Button>
                 </Td>
               </Tr>
             ))}
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index a5a99f2a6fc..b216c9db257 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -143,7 +143,6 @@ export const usePlans = () => {
 type HandleSelectPlanProps = {
   plan: CommercePlanResource;
   planPeriod: CommerceSubscriptionPlanPeriod;
-  onSubscriptionChange?: () => void;
   mode?: 'modal' | 'mounted';
   event?: React.MouseEvent<HTMLElement>;
   appearance?: Appearance;
@@ -345,53 +344,44 @@ export const usePlansContext = () => {
     return;
   }, []);
 
+  const openSubscriptionDetails = useCallback(
+    (event?: React.MouseEvent<HTMLElement>) => {
+      const portalRoot = getClosestProfileScrollBox('modal', event);
+      clerk.__internal_openSubscriptionDetails({
+        for: subscriberType,
+        onSubscriptionCancel: () => {
+          revalidateAll();
+        },
+        portalRoot,
+      });
+    },
+    [clerk, subscriberType, revalidateAll],
+  );
+
   // handle the selection of a plan, either by opening the subscription details or checkout
   const handleSelectPlan = useCallback(
-    ({
-      plan,
-      planPeriod,
-      onSubscriptionChange,
-      mode = 'mounted',
-      event,
-      appearance,
-      newSubscriptionRedirectUrl,
-    }: HandleSelectPlanProps) => {
-      const subscription = activeOrUpcomingSubscriptionWithPlanPeriod(plan, planPeriod);
-
+    ({ plan, planPeriod, mode = 'mounted', event, appearance, newSubscriptionRedirectUrl }: HandleSelectPlanProps) => {
       const portalRoot = getClosestProfileScrollBox(mode, event);
 
-      if (subscription && subscription.planPeriod === planPeriod && !subscription.canceledAtDate) {
-        clerk.__internal_openSubscriptionDetails({
-          for: subscriberType,
-          onSubscriptionCancel: () => {
-            revalidateAll();
-            onSubscriptionChange?.();
-          },
-          appearance,
-          portalRoot,
-        });
-      } else {
-        clerk.__internal_openCheckout({
-          planId: plan.id,
-          // if the plan doesn't support annual, use monthly
-          planPeriod: planPeriod === 'annual' && plan.annualMonthlyAmount === 0 ? 'month' : planPeriod,
-          subscriberType,
-          onSubscriptionComplete: () => {
-            revalidateAll();
-            onSubscriptionChange?.();
-          },
-          onClose: () => {
-            if (session?.id) {
-              void clerk.setActive({ session: session.id });
-            }
-          },
-          appearance,
-          portalRoot,
-          newSubscriptionRedirectUrl,
-        });
-      }
+      clerk.__internal_openCheckout({
+        planId: plan.id,
+        // if the plan doesn't support annual, use monthly
+        planPeriod: planPeriod === 'annual' && plan.annualMonthlyAmount === 0 ? 'month' : planPeriod,
+        subscriberType,
+        onSubscriptionComplete: () => {
+          revalidateAll();
+        },
+        onClose: () => {
+          if (session?.id) {
+            void clerk.setActive({ session: session.id });
+          }
+        },
+        appearance,
+        portalRoot,
+        newSubscriptionRedirectUrl,
+      });
     },
-    [clerk, revalidateAll, activeOrUpcomingSubscription, subscriberType, session?.id],
+    [clerk, revalidateAll, subscriberType, session?.id],
   );
 
   const defaultFreePlan = useMemo(() => {
@@ -404,6 +394,7 @@ export const usePlansContext = () => {
     activeOrUpcomingSubscriptionBasedOnPlanPeriod: activeOrUpcomingSubscriptionWithPlanPeriod,
     isDefaultPlanImplicitlyActiveOrUpcoming,
     handleSelectPlan,
+    openSubscriptionDetails,
     buttonPropsForPlan,
     canManageSubscription,
     captionForSubscription,
diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index 9054b1d041a..fa721a53773 100644
--- a/packages/localizations/src/en-US.ts
+++ b/packages/localizations/src/en-US.ts
@@ -137,8 +137,8 @@ export const enUS: LocalizationResource = {
     switchPlan: 'Switch to this plan',
     switchToAnnual: 'Switch to annual',
     switchToMonthly: 'Switch to monthly',
-    switchToMonthlyWithPrice: 'Switch to monthly {{currency}}{{price}} per month',
-    switchToAnnualWithAnnualPrice: 'Switch to annual {{currency}}{{price}} per year',
+    switchToMonthlyWithPrice: 'Switch to monthly {{currency}}{{price}} / month',
+    switchToAnnualWithAnnualPrice: 'Switch to annual {{currency}}{{price}} / year',
     totalDue: 'Total due',
     totalDueToday: 'Total Due Today',
     viewFeatures: 'View features',

From 587f2b857330337cbb0811cf8c090fec1e9ae3f2 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Thu, 10 Jul 2025 17:51:13 +0300
Subject: [PATCH 22/34] wip

---
 packages/clerk-js/src/core/resources/CommerceSubscription.ts   | 3 +++
 .../src/ui/components/PricingTable/PricingTableDefault.tsx     | 1 +
 .../clerk-js/src/ui/components/SubscriptionDetails/index.tsx   | 1 +
 .../src/ui/components/Subscriptions/SubscriptionsList.tsx      | 3 +++
 packages/clerk-js/src/ui/contexts/components/Plans.tsx         | 1 +
 packages/types/src/commerce.ts                                 | 3 ++-
 packages/types/src/json.ts                                     | 1 +
 7 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/packages/clerk-js/src/core/resources/CommerceSubscription.ts b/packages/clerk-js/src/core/resources/CommerceSubscription.ts
index ac4d949fdf4..d2f8ae66219 100644
--- a/packages/clerk-js/src/core/resources/CommerceSubscription.ts
+++ b/packages/clerk-js/src/core/resources/CommerceSubscription.ts
@@ -20,6 +20,7 @@ export class CommerceSubscription extends BaseResource implements CommerceSubscr
   planPeriod!: CommerceSubscriptionPlanPeriod;
   status!: CommerceSubscriptionStatus;
   createdAt!: Date;
+  pastDueAt!: Date | null;
   periodStartDate!: Date;
   periodEndDate!: Date | null;
   canceledAtDate!: Date | null;
@@ -51,6 +52,8 @@ export class CommerceSubscription extends BaseResource implements CommerceSubscr
     this.canceledAt = data.canceled_at;
 
     this.createdAt = unixEpochToDate(data.created_at);
+    this.pastDueAt = data.past_due_at ? unixEpochToDate(data.past_due_at) : null;
+
     this.periodStartDate = unixEpochToDate(data.period_start);
     this.periodEndDate = data.period_end ? unixEpochToDate(data.period_end) : null;
     this.canceledAtDate = data.canceled_at ? unixEpochToDate(data.canceled_at) : null;
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
index ab99e7fe3bc..8bc1a1c4291 100644
--- a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
+++ b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
@@ -191,6 +191,7 @@ function Card(props: CardProps) {
             isPlanActive ? (
               <Badge
                 colorScheme='secondary'
+                // here
                 localizationKey={localizationKeys('badge__activePlan')}
               />
             ) : (
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 3e36abd05a7..d76750b7752 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -531,6 +531,7 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
           <Badge
             elementDescriptor={descriptors.subscriptionDetailsCardBadge}
             colorScheme={isActive ? 'secondary' : 'primary'}
+            // here
             localizationKey={isActive ? localizationKeys('badge__activePlan') : localizationKeys('badge__upcomingPlan')}
           />
         </Flex>
diff --git a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
index 17d6eaff437..e1e4d600dad 100644
--- a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
+++ b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
@@ -113,6 +113,7 @@ export function SubscriptionsList({
                         {subscription.plan.name}
                       </Text>
                       {sortedSubscriptions.length > 1 || !!subscription.canceledAtDate ? (
+                        // here
                         <Badge
                           colorScheme={subscription.status === 'active' ? 'secondary' : 'primary'}
                           localizationKey={
@@ -123,7 +124,9 @@ export function SubscriptionsList({
                         />
                       ) : null}
                     </Flex>
+
                     {(!subscription.plan.isDefault || subscription.status === 'upcoming') && (
+                      // here
                       <Text
                         variant='caption'
                         colorScheme='secondary'
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index b216c9db257..9a267c13fa8 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -114,6 +114,7 @@ export const useSubscriptions = () => {
           created_at: canceledSubscription?.periodEndDate?.getTime() || 0,
           period_start: canceledSubscription?.periodEndDate?.getTime() || 0,
           period_end: 0,
+          past_due_at: null,
         }),
       ];
     }
diff --git a/packages/types/src/commerce.ts b/packages/types/src/commerce.ts
index ea041db90d9..70ba49b9333 100644
--- a/packages/types/src/commerce.ts
+++ b/packages/types/src/commerce.ts
@@ -17,7 +17,7 @@ export interface CommerceBillingNamespace {
 }
 
 export type CommerceSubscriberType = 'org' | 'user';
-export type CommerceSubscriptionStatus = 'active' | 'ended' | 'upcoming';
+export type CommerceSubscriptionStatus = 'active' | 'ended' | 'upcoming' | 'past_due';
 export type CommerceSubscriptionPlanPeriod = 'month' | 'annual';
 
 export interface CommercePaymentSourceMethods {
@@ -156,6 +156,7 @@ export interface CommerceSubscriptionResource extends ClerkResource {
   planPeriod: CommerceSubscriptionPlanPeriod;
   status: CommerceSubscriptionStatus;
   createdAt: Date;
+  pastDueAt: Date | null;
   periodStartDate: Date;
   periodEndDate: Date | null;
   canceledAtDate: Date | null;
diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts
index 779d0807f0c..28989135c66 100644
--- a/packages/types/src/json.ts
+++ b/packages/types/src/json.ts
@@ -705,6 +705,7 @@ export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
   period_start: number;
   period_end: number;
   canceled_at: number | null;
+  past_due_at: number | null;
 }
 
 export interface CommerceMoneyJSON {

From af6a28baccca874cf55c41e42aa1c901073e1f4d Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Sun, 13 Jul 2025 20:14:33 +0300
Subject: [PATCH 23/34] Revert "wip"

This reverts commit 587f2b857330337cbb0811cf8c090fec1e9ae3f2.
---
 packages/clerk-js/src/core/resources/CommerceSubscription.ts   | 3 ---
 .../src/ui/components/PricingTable/PricingTableDefault.tsx     | 1 -
 .../clerk-js/src/ui/components/SubscriptionDetails/index.tsx   | 1 -
 .../src/ui/components/Subscriptions/SubscriptionsList.tsx      | 3 ---
 packages/clerk-js/src/ui/contexts/components/Plans.tsx         | 1 -
 packages/types/src/commerce.ts                                 | 3 +--
 packages/types/src/json.ts                                     | 1 -
 7 files changed, 1 insertion(+), 12 deletions(-)

diff --git a/packages/clerk-js/src/core/resources/CommerceSubscription.ts b/packages/clerk-js/src/core/resources/CommerceSubscription.ts
index d2f8ae66219..ac4d949fdf4 100644
--- a/packages/clerk-js/src/core/resources/CommerceSubscription.ts
+++ b/packages/clerk-js/src/core/resources/CommerceSubscription.ts
@@ -20,7 +20,6 @@ export class CommerceSubscription extends BaseResource implements CommerceSubscr
   planPeriod!: CommerceSubscriptionPlanPeriod;
   status!: CommerceSubscriptionStatus;
   createdAt!: Date;
-  pastDueAt!: Date | null;
   periodStartDate!: Date;
   periodEndDate!: Date | null;
   canceledAtDate!: Date | null;
@@ -52,8 +51,6 @@ export class CommerceSubscription extends BaseResource implements CommerceSubscr
     this.canceledAt = data.canceled_at;
 
     this.createdAt = unixEpochToDate(data.created_at);
-    this.pastDueAt = data.past_due_at ? unixEpochToDate(data.past_due_at) : null;
-
     this.periodStartDate = unixEpochToDate(data.period_start);
     this.periodEndDate = data.period_end ? unixEpochToDate(data.period_end) : null;
     this.canceledAtDate = data.canceled_at ? unixEpochToDate(data.canceled_at) : null;
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
index 8bc1a1c4291..ab99e7fe3bc 100644
--- a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
+++ b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
@@ -191,7 +191,6 @@ function Card(props: CardProps) {
             isPlanActive ? (
               <Badge
                 colorScheme='secondary'
-                // here
                 localizationKey={localizationKeys('badge__activePlan')}
               />
             ) : (
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index d76750b7752..3e36abd05a7 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -531,7 +531,6 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
           <Badge
             elementDescriptor={descriptors.subscriptionDetailsCardBadge}
             colorScheme={isActive ? 'secondary' : 'primary'}
-            // here
             localizationKey={isActive ? localizationKeys('badge__activePlan') : localizationKeys('badge__upcomingPlan')}
           />
         </Flex>
diff --git a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
index e1e4d600dad..17d6eaff437 100644
--- a/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
+++ b/packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx
@@ -113,7 +113,6 @@ export function SubscriptionsList({
                         {subscription.plan.name}
                       </Text>
                       {sortedSubscriptions.length > 1 || !!subscription.canceledAtDate ? (
-                        // here
                         <Badge
                           colorScheme={subscription.status === 'active' ? 'secondary' : 'primary'}
                           localizationKey={
@@ -124,9 +123,7 @@ export function SubscriptionsList({
                         />
                       ) : null}
                     </Flex>
-
                     {(!subscription.plan.isDefault || subscription.status === 'upcoming') && (
-                      // here
                       <Text
                         variant='caption'
                         colorScheme='secondary'
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index 9a267c13fa8..b216c9db257 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -114,7 +114,6 @@ export const useSubscriptions = () => {
           created_at: canceledSubscription?.periodEndDate?.getTime() || 0,
           period_start: canceledSubscription?.periodEndDate?.getTime() || 0,
           period_end: 0,
-          past_due_at: null,
         }),
       ];
     }
diff --git a/packages/types/src/commerce.ts b/packages/types/src/commerce.ts
index 70ba49b9333..ea041db90d9 100644
--- a/packages/types/src/commerce.ts
+++ b/packages/types/src/commerce.ts
@@ -17,7 +17,7 @@ export interface CommerceBillingNamespace {
 }
 
 export type CommerceSubscriberType = 'org' | 'user';
-export type CommerceSubscriptionStatus = 'active' | 'ended' | 'upcoming' | 'past_due';
+export type CommerceSubscriptionStatus = 'active' | 'ended' | 'upcoming';
 export type CommerceSubscriptionPlanPeriod = 'month' | 'annual';
 
 export interface CommercePaymentSourceMethods {
@@ -156,7 +156,6 @@ export interface CommerceSubscriptionResource extends ClerkResource {
   planPeriod: CommerceSubscriptionPlanPeriod;
   status: CommerceSubscriptionStatus;
   createdAt: Date;
-  pastDueAt: Date | null;
   periodStartDate: Date;
   periodEndDate: Date | null;
   canceledAtDate: Date | null;
diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts
index 28989135c66..779d0807f0c 100644
--- a/packages/types/src/json.ts
+++ b/packages/types/src/json.ts
@@ -705,7 +705,6 @@ export interface CommerceSubscriptionJSON extends ClerkResourceJSON {
   period_start: number;
   period_end: number;
   canceled_at: number | null;
-  past_due_at: number | null;
 }
 
 export interface CommerceMoneyJSON {

From 1c161ccbb55131e225407f88547cfbc540161536 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Sun, 13 Jul 2025 21:16:46 +0300
Subject: [PATCH 24/34] address pr feedback

---
 .../components/SubscriptionDetails/index.tsx  | 71 +++++++------------
 .../ui/customizables/elementDescriptors.ts    |  1 +
 packages/clerk-js/src/ui/elements/Drawer.tsx  | 18 +++--
 .../src/ui/elements/ThreeDotsMenu.tsx         | 63 ++++++++++------
 packages/types/src/appearance.ts              |  1 +
 5 files changed, 79 insertions(+), 75 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 3e36abd05a7..96c8430f2d8 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -18,7 +18,6 @@ import { CardAlert } from '@/ui/elements/Card/CardAlert';
 import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
 import { Drawer, useDrawerContext } from '@/ui/elements/Drawer';
 import { ThreeDotsMenu } from '@/ui/elements/ThreeDotsMenu';
-import { ThreeDots } from '@/ui/icons';
 import { handleError } from '@/ui/utils/errorHandler';
 import { formatDate } from '@/ui/utils/formatDate';
 
@@ -34,7 +33,6 @@ import {
   descriptors,
   Flex,
   Heading,
-  Icon,
   localizationKeys,
   Span,
   Spinner,
@@ -141,23 +139,24 @@ const SubscriptionDetailsInternal = (props: __internal_SubscriptionDetailsProps)
     >
       <Drawer.Header title={localizationKeys('commerce.subscriptionDetails.title')} />
 
-      <Drawer.Body>
-        <Col
-          gap={4}
-          sx={t => ({
-            padding: t.space.$4,
-            overflowY: 'auto',
-          })}
-        >
-          {/* Subscription Cards */}
-          {subscriptions?.map(subscriptionItem => (
-            <SubscriptionCard
-              key={subscriptionItem.id}
-              subscription={subscriptionItem}
-              {...props}
-            />
-          ))}
-        </Col>
+      <Drawer.Body
+        sx={t => ({
+          display: 'flex',
+          flexDirection: 'column',
+          flex: 1,
+          overflowY: 'auto',
+          padding: t.space.$4,
+          gap: t.space.$4,
+        })}
+      >
+        {/* Subscription Cards */}
+        {subscriptions?.map(subscriptionItem => (
+          <SubscriptionCard
+            key={subscriptionItem.id}
+            subscription={subscriptionItem}
+            {...props}
+          />
+        ))}
       </Drawer.Body>
 
       <SubscriptionDetailsFooter />
@@ -355,7 +354,6 @@ function SubscriptionDetailsSummary() {
 const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubscriptionResource }) => {
   const { portalRoot } = useSubscriptionDetailsContext();
   const { __internal_openCheckout } = useClerk();
-  const { t } = useLocalizations();
   const subscriberType = useSubscriberTypeContext();
   const { setIsOpen } = useDrawerContext();
   const { revalidateAll } = usePlansContext();
@@ -455,27 +453,7 @@ const SubscriptionCardActions = ({ subscription }: { subscription: CommerceSubsc
 
   return (
     <ThreeDotsMenu
-      trigger={
-        <Button
-          aria-label={t(localizationKeys('commerce.manageSubscription'))}
-          variant='bordered'
-          colorScheme='secondary'
-          sx={t => ({
-            width: t.sizes.$6,
-            height: t.sizes.$6,
-          })}
-          elementDescriptor={[descriptors.menuButton, descriptors.menuButtonEllipsis]}
-        >
-          <Icon
-            icon={ThreeDots}
-            sx={t => ({
-              width: t.sizes.$4,
-              height: t.sizes.$4,
-              opacity: t.opacity.$inactive,
-            })}
-          />
-        </Button>
-      }
+      variant='bordered'
       actions={actions}
     />
   );
@@ -519,12 +497,13 @@ const SubscriptionCard = ({ subscription }: { subscription: CommerceSubscription
 
           <Text
             elementDescriptor={descriptors.subscriptionDetailsCardTitle}
-            sx={{
-              fontSize: '16px',
-              fontWeight: '600',
-              color: '#333',
+            variant='h2'
+            sx={t => ({
+              fontSize: t.fontSizes.$lg,
+              fontWeight: t.fontWeights.$semibold,
+              color: t.colors.$colorText,
               marginInlineEnd: 'auto',
-            }}
+            })}
           >
             {subscription.plan.name}
           </Text>
diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
index 6f8307a8a8f..72d44689226 100644
--- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
+++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
@@ -387,6 +387,7 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
   'statementCopyButton',
   'menuButton',
   'menuButtonEllipsis',
+  'menuButtonEllipsisBordered',
   'menuList',
   'menuItem',
 
diff --git a/packages/clerk-js/src/ui/elements/Drawer.tsx b/packages/clerk-js/src/ui/elements/Drawer.tsx
index 49622abc644..f20e4c44d93 100644
--- a/packages/clerk-js/src/ui/elements/Drawer.tsx
+++ b/packages/clerk-js/src/ui/elements/Drawer.tsx
@@ -339,6 +339,7 @@ const Header = React.forwardRef<HTMLDivElement, HeaderProps>(({ title, children,
 
 interface BodyProps extends React.HTMLAttributes<HTMLDivElement> {
   children: React.ReactNode;
+  sx?: ThemableCssProp;
 }
 
 const Body = React.forwardRef<HTMLDivElement, BodyProps>(({ children, ...props }, ref) => {
@@ -346,13 +347,16 @@ const Body = React.forwardRef<HTMLDivElement, BodyProps>(({ children, ...props }
     <Box
       ref={ref}
       elementDescriptor={descriptors.drawerBody}
-      sx={{
-        display: 'flex',
-        flexDirection: 'column',
-        flex: 1,
-        overflowY: 'auto',
-        overflowX: 'hidden',
-      }}
+      sx={[
+        () => ({
+          display: 'flex',
+          flexDirection: 'column',
+          flex: 1,
+          overflowY: 'auto',
+          overflowX: 'hidden',
+        }),
+        props.sx,
+      ]}
       {...props}
     >
       {children}
diff --git a/packages/clerk-js/src/ui/elements/ThreeDotsMenu.tsx b/packages/clerk-js/src/ui/elements/ThreeDotsMenu.tsx
index 66b476b45e6..9cc826d1114 100644
--- a/packages/clerk-js/src/ui/elements/ThreeDotsMenu.tsx
+++ b/packages/clerk-js/src/ui/elements/ThreeDotsMenu.tsx
@@ -2,6 +2,7 @@ import type { MenuId } from '@clerk/types';
 
 import type { LocalizationKey } from '../customizables';
 import { Button, descriptors, Icon } from '../customizables';
+import type { InternalTheme } from '../foundations';
 import { ThreeDots } from '../icons';
 import { Menu, MenuItem, MenuList, MenuTrigger } from './Menu';
 
@@ -13,36 +14,54 @@ type Action = {
 };
 
 type ThreeDotsMenuProps = {
-  trigger?: React.ReactNode;
+  variant?: 'bordered';
   actions: Action[];
   elementId?: MenuId;
 };
 
 export const ThreeDotsMenu = (props: ThreeDotsMenuProps) => {
-  const { actions, elementId } = props;
+  const { actions, elementId, variant } = props;
+  const isBordered = variant === 'bordered';
+
+  const iconSx = (t: InternalTheme) =>
+    !isBordered
+      ? { width: 'auto', height: t.sizes.$5 }
+      : { width: t.sizes.$4, height: t.sizes.$4, opacity: t.opacity.$inactive };
+
+  const buttonVariant = isBordered ? 'bordered' : 'ghost';
+  const colorScheme = isBordered ? 'secondary' : 'neutral';
+
   return (
     <Menu elementId={elementId}>
       <MenuTrigger arialLabel={isOpen => `${isOpen ? 'Close' : 'Open'} menu`}>
-        {props.trigger || (
-          <Button
-            sx={t => ({
-              padding: t.space.$0x5,
-              boxSizing: 'content-box',
-              opacity: t.opacity.$inactive,
-              ':hover': {
-                opacity: 1,
-              },
-            })}
-            variant='ghost'
-            colorScheme='neutral'
-            elementDescriptor={[descriptors.menuButton, descriptors.menuButtonEllipsis]}
-          >
-            <Icon
-              icon={ThreeDots}
-              sx={t => ({ width: 'auto', height: t.sizes.$5 })}
-            />
-          </Button>
-        )}
+        <Button
+          sx={t =>
+            !isBordered
+              ? {
+                  padding: t.space.$0x5,
+                  boxSizing: 'content-box',
+                  opacity: t.opacity.$inactive,
+                  ':hover': {
+                    opacity: 1,
+                  },
+                }
+              : {
+                  width: t.sizes.$6,
+                  height: t.sizes.$6,
+                }
+          }
+          variant={buttonVariant}
+          colorScheme={colorScheme}
+          elementDescriptor={[
+            descriptors.menuButton,
+            isBordered ? descriptors.menuButtonEllipsisBordered : descriptors.menuButtonEllipsis,
+          ]}
+        >
+          <Icon
+            icon={ThreeDots}
+            sx={iconSx}
+          />
+        </Button>
       </MenuTrigger>
       <MenuList>
         {actions.map((a, index) => (
diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts
index 4a9142bc3c6..3c5f7158aa0 100644
--- a/packages/types/src/appearance.ts
+++ b/packages/types/src/appearance.ts
@@ -512,6 +512,7 @@ export type ElementsConfig = {
   statementCopyButton: WithOptions;
   menuButton: WithOptions<MenuId>;
   menuButtonEllipsis: WithOptions;
+  menuButtonEllipsisBordered: WithOptions;
   menuList: WithOptions<MenuId>;
   menuItem: WithOptions<MenuId>;
 

From 0ed99f14f6e917b6628694e410b69adaefc36f74 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Sun, 13 Jul 2025 21:19:05 +0300
Subject: [PATCH 25/34] bump

---
 packages/clerk-js/bundlewatch.config.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json
index 57ab2fbe893..71de67ae931 100644
--- a/packages/clerk-js/bundlewatch.config.json
+++ b/packages/clerk-js/bundlewatch.config.json
@@ -1,6 +1,6 @@
 {
   "files": [
-    { "path": "./dist/clerk.js", "maxSize": "612kB" },
+    { "path": "./dist/clerk.js", "maxSize": "614kB" },
     { "path": "./dist/clerk.browser.js", "maxSize": "72.2KB" },
     { "path": "./dist/clerk.legacy.browser.js", "maxSize": "115KB" },
     { "path": "./dist/clerk.headless*.js", "maxSize": "55KB" },

From 39100a922c756c158bd5e274b414a939d4ec861f Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Sun, 13 Jul 2025 21:43:34 +0300
Subject: [PATCH 26/34] wip subscription items

---
 .../core/modules/commerce/CommerceBilling.ts  |  9 +++++++
 .../src/ui/contexts/components/Plans.tsx      |  2 ++
 packages/shared/src/react/hooks/index.ts      |  1 +
 .../src/react/hooks/useSubscriptionItems.tsx  | 25 ++++++++++++++++++-
 packages/types/src/commerce.ts                |  4 +++
 5 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
index f454e9ff848..d5e71157b46 100644
--- a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
+++ b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
@@ -48,6 +48,15 @@ export class CommerceBilling implements CommerceBillingNamespace {
     return new CommercePlan(plan);
   };
 
+  getSubscription = async (params: GetSubscriptionsParams): Promise<any> => {
+    return await BaseResource._fetch({
+      path: params.orgId ? `/organizations/${params.orgId}/commerce/subscription` : `/me/commerce/subscription`,
+      method: 'GET',
+    }).then(res => {
+      return res;
+    });
+  };
+
   getSubscriptions = async (
     params: GetSubscriptionsParams,
   ): Promise<ClerkPaginatedResponse<CommerceSubscriptionResource>> => {
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index b216c9db257..309b8c35915 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -5,6 +5,7 @@ import {
   useClerk,
   useOrganization,
   useSession,
+  useSubscription,
   useUser,
 } from '@clerk/shared/react';
 import type {
@@ -72,6 +73,7 @@ export const useStatements = (params?: { mode: 'cache' }) => {
 };
 
 export const useSubscriptions = () => {
+  useSubscription();
   const { billing } = useClerk();
   const { organization } = useOrganization();
   const { user, isSignedIn } = useUser();
diff --git a/packages/shared/src/react/hooks/index.ts b/packages/shared/src/react/hooks/index.ts
index e801745a0b7..1d689ce2501 100644
--- a/packages/shared/src/react/hooks/index.ts
+++ b/packages/shared/src/react/hooks/index.ts
@@ -12,3 +12,4 @@ export { useStatements as __experimental_useStatements } from './useStatements';
 export { usePaymentAttempts as __experimental_usePaymentAttempts } from './usePaymentAttempts';
 export { usePaymentMethods as __experimental_usePaymentMethods } from './usePaymentMethods';
 export { useSubscriptionItems as __experimental_useSubscriptionItems } from './useSubscriptionItems';
+export { useSubscription } from './useSubscriptionItems';
diff --git a/packages/shared/src/react/hooks/useSubscriptionItems.tsx b/packages/shared/src/react/hooks/useSubscriptionItems.tsx
index db2db7eb889..0c0e73b9ecb 100644
--- a/packages/shared/src/react/hooks/useSubscriptionItems.tsx
+++ b/packages/shared/src/react/hooks/useSubscriptionItems.tsx
@@ -1,6 +1,8 @@
 import type { CommerceSubscriptionResource, GetSubscriptionsParams } from '@clerk/types';
 
-import { useClerkInstanceContext } from '../contexts';
+import { eventMethodCalled } from '../../telemetry/events';
+import { useSWR } from '../clerk-swr';
+import { useClerkInstanceContext, useOrganizationContext, useUserContext } from '../contexts';
 import { createCommerceHook } from './createCommerceHook';
 
 /**
@@ -14,3 +16,24 @@ export const useSubscriptionItems = createCommerceHook<CommerceSubscriptionResou
     return clerk.billing.getSubscriptions;
   },
 });
+
+const dedupeOptions = {
+  dedupingInterval: 1_000 * 60, // 1 minute,
+  keepPreviousData: true,
+};
+
+export const useSubscription = (params?: { for: 'organization' | 'user' }) => {
+  const clerk = useClerkInstanceContext();
+  const user = useUserContext();
+  const { organization } = useOrganizationContext();
+  clerk.telemetry?.record(eventMethodCalled('useSubscription'));
+  return useSWR(
+    {
+      type: 'commerce-subscription',
+      userId: user?.id,
+      args: { orgId: params?.for === 'organization' ? organization?.id : undefined },
+    },
+    ({ args, userId }) => (userId ? clerk.billing.getSubscription(args) : undefined),
+    dedupeOptions,
+  );
+};
diff --git a/packages/types/src/commerce.ts b/packages/types/src/commerce.ts
index ea041db90d9..d907469875a 100644
--- a/packages/types/src/commerce.ts
+++ b/packages/types/src/commerce.ts
@@ -11,6 +11,10 @@ export interface CommerceBillingNamespace {
   getPaymentAttempts: (params: GetPaymentAttemptsParams) => Promise<ClerkPaginatedResponse<CommercePaymentResource>>;
   getPlans: (params?: GetPlansParams) => Promise<CommercePlanResource[]>;
   getPlan: (params: { id: string }) => Promise<CommercePlanResource>;
+  getSubscription: (params: GetSubscriptionsParams) => Promise<any>;
+  /**
+   * @deprecated Use `getSubscription`
+   */
   getSubscriptions: (params: GetSubscriptionsParams) => Promise<ClerkPaginatedResponse<CommerceSubscriptionResource>>;
   getStatements: (params: GetStatementsParams) => Promise<ClerkPaginatedResponse<CommerceStatementResource>>;
   startCheckout: (params: CreateCheckoutParams) => Promise<CommerceCheckoutResource>;

From 7db88a9606cbce3b8225b7c38545eceb8841fd70 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Sun, 13 Jul 2025 21:43:40 +0300
Subject: [PATCH 27/34] Revert "wip subscription items"

This reverts commit 39100a922c756c158bd5e274b414a939d4ec861f.
---
 .../core/modules/commerce/CommerceBilling.ts  |  9 -------
 .../src/ui/contexts/components/Plans.tsx      |  2 --
 packages/shared/src/react/hooks/index.ts      |  1 -
 .../src/react/hooks/useSubscriptionItems.tsx  | 25 +------------------
 packages/types/src/commerce.ts                |  4 ---
 5 files changed, 1 insertion(+), 40 deletions(-)

diff --git a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
index d5e71157b46..f454e9ff848 100644
--- a/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
+++ b/packages/clerk-js/src/core/modules/commerce/CommerceBilling.ts
@@ -48,15 +48,6 @@ export class CommerceBilling implements CommerceBillingNamespace {
     return new CommercePlan(plan);
   };
 
-  getSubscription = async (params: GetSubscriptionsParams): Promise<any> => {
-    return await BaseResource._fetch({
-      path: params.orgId ? `/organizations/${params.orgId}/commerce/subscription` : `/me/commerce/subscription`,
-      method: 'GET',
-    }).then(res => {
-      return res;
-    });
-  };
-
   getSubscriptions = async (
     params: GetSubscriptionsParams,
   ): Promise<ClerkPaginatedResponse<CommerceSubscriptionResource>> => {
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index 309b8c35915..b216c9db257 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -5,7 +5,6 @@ import {
   useClerk,
   useOrganization,
   useSession,
-  useSubscription,
   useUser,
 } from '@clerk/shared/react';
 import type {
@@ -73,7 +72,6 @@ export const useStatements = (params?: { mode: 'cache' }) => {
 };
 
 export const useSubscriptions = () => {
-  useSubscription();
   const { billing } = useClerk();
   const { organization } = useOrganization();
   const { user, isSignedIn } = useUser();
diff --git a/packages/shared/src/react/hooks/index.ts b/packages/shared/src/react/hooks/index.ts
index 1d689ce2501..e801745a0b7 100644
--- a/packages/shared/src/react/hooks/index.ts
+++ b/packages/shared/src/react/hooks/index.ts
@@ -12,4 +12,3 @@ export { useStatements as __experimental_useStatements } from './useStatements';
 export { usePaymentAttempts as __experimental_usePaymentAttempts } from './usePaymentAttempts';
 export { usePaymentMethods as __experimental_usePaymentMethods } from './usePaymentMethods';
 export { useSubscriptionItems as __experimental_useSubscriptionItems } from './useSubscriptionItems';
-export { useSubscription } from './useSubscriptionItems';
diff --git a/packages/shared/src/react/hooks/useSubscriptionItems.tsx b/packages/shared/src/react/hooks/useSubscriptionItems.tsx
index 0c0e73b9ecb..db2db7eb889 100644
--- a/packages/shared/src/react/hooks/useSubscriptionItems.tsx
+++ b/packages/shared/src/react/hooks/useSubscriptionItems.tsx
@@ -1,8 +1,6 @@
 import type { CommerceSubscriptionResource, GetSubscriptionsParams } from '@clerk/types';
 
-import { eventMethodCalled } from '../../telemetry/events';
-import { useSWR } from '../clerk-swr';
-import { useClerkInstanceContext, useOrganizationContext, useUserContext } from '../contexts';
+import { useClerkInstanceContext } from '../contexts';
 import { createCommerceHook } from './createCommerceHook';
 
 /**
@@ -16,24 +14,3 @@ export const useSubscriptionItems = createCommerceHook<CommerceSubscriptionResou
     return clerk.billing.getSubscriptions;
   },
 });
-
-const dedupeOptions = {
-  dedupingInterval: 1_000 * 60, // 1 minute,
-  keepPreviousData: true,
-};
-
-export const useSubscription = (params?: { for: 'organization' | 'user' }) => {
-  const clerk = useClerkInstanceContext();
-  const user = useUserContext();
-  const { organization } = useOrganizationContext();
-  clerk.telemetry?.record(eventMethodCalled('useSubscription'));
-  return useSWR(
-    {
-      type: 'commerce-subscription',
-      userId: user?.id,
-      args: { orgId: params?.for === 'organization' ? organization?.id : undefined },
-    },
-    ({ args, userId }) => (userId ? clerk.billing.getSubscription(args) : undefined),
-    dedupeOptions,
-  );
-};
diff --git a/packages/types/src/commerce.ts b/packages/types/src/commerce.ts
index d907469875a..ea041db90d9 100644
--- a/packages/types/src/commerce.ts
+++ b/packages/types/src/commerce.ts
@@ -11,10 +11,6 @@ export interface CommerceBillingNamespace {
   getPaymentAttempts: (params: GetPaymentAttemptsParams) => Promise<ClerkPaginatedResponse<CommercePaymentResource>>;
   getPlans: (params?: GetPlansParams) => Promise<CommercePlanResource[]>;
   getPlan: (params: { id: string }) => Promise<CommercePlanResource>;
-  getSubscription: (params: GetSubscriptionsParams) => Promise<any>;
-  /**
-   * @deprecated Use `getSubscription`
-   */
   getSubscriptions: (params: GetSubscriptionsParams) => Promise<ClerkPaginatedResponse<CommerceSubscriptionResource>>;
   getStatements: (params: GetStatementsParams) => Promise<ClerkPaginatedResponse<CommerceStatementResource>>;
   startCheckout: (params: CreateCheckoutParams) => Promise<CommerceCheckoutResource>;

From 257a3cc5254bcc1c3e2f544ae0a4816b89407ddb Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Sun, 13 Jul 2025 21:47:13 +0300
Subject: [PATCH 28/34] wip changeset

---
 .changeset/lovely-lands-smell.md | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 .changeset/lovely-lands-smell.md

diff --git a/.changeset/lovely-lands-smell.md b/.changeset/lovely-lands-smell.md
new file mode 100644
index 00000000000..9f791bdc28a
--- /dev/null
+++ b/.changeset/lovely-lands-smell.md
@@ -0,0 +1,8 @@
+---
+'@clerk/localizations': minor
+'@clerk/clerk-js': minor
+'@clerk/clerk-react': minor
+'@clerk/types': minor
+---
+
+wip

From 3753deeb607149cb8ea3fe9192e1f8b023731179 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Sun, 13 Jul 2025 21:50:06 +0300
Subject: [PATCH 29/34] bundlewatch.config.json

---
 packages/clerk-js/bundlewatch.config.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json
index 71de67ae931..f8f54577f6c 100644
--- a/packages/clerk-js/bundlewatch.config.json
+++ b/packages/clerk-js/bundlewatch.config.json
@@ -1,10 +1,10 @@
 {
   "files": [
-    { "path": "./dist/clerk.js", "maxSize": "614kB" },
+    { "path": "./dist/clerk.js", "maxSize": "615kB" },
     { "path": "./dist/clerk.browser.js", "maxSize": "72.2KB" },
     { "path": "./dist/clerk.legacy.browser.js", "maxSize": "115KB" },
     { "path": "./dist/clerk.headless*.js", "maxSize": "55KB" },
-    { "path": "./dist/ui-common*.js", "maxSize": "110KB" },
+    { "path": "./dist/ui-common*.js", "maxSize": "115KB" },
     { "path": "./dist/vendors*.js", "maxSize": "40.2KB" },
     { "path": "./dist/coinbase*.js", "maxSize": "38KB" },
     { "path": "./dist/createorganization*.js", "maxSize": "5KB" },

From 28d86b4bedce99e20ae630c26943034ed9d49b37 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Mon, 14 Jul 2025 10:49:15 +0300
Subject: [PATCH 30/34] fix lint

---
 .../SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx   | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
index 15e3c15af3c..0a4bfed164e 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
@@ -655,7 +655,6 @@ describe('SubscriptionDetails', () => {
       f.withUser({ email_addresses: ['test@clerk.com'] });
     });
 
-    const switchToMonthlyMock = jest.fn().mockResolvedValue({});
     const plan = {
       id: 'plan_annual',
       name: 'Annual Plan',

From 38fa5f6baa4e350afd20f99514124b0743c9a7c2 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Mon, 14 Jul 2025 11:30:02 +0300
Subject: [PATCH 31/34] fix build issue

---
 packages/react/src/isomorphicClerk.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts
index 0da47ee7598..09dd59fa1b2 100644
--- a/packages/react/src/isomorphicClerk.ts
+++ b/packages/react/src/isomorphicClerk.ts
@@ -802,7 +802,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
     if (this.clerkjs && this.loaded) {
       this.clerkjs.__internal_openSubscriptionDetails(props);
     } else {
-      this.preopenSubscriptionDetails = props;
+      this.preopenSubscriptionDetails = props ?? null;
     }
   };
 

From 54214037ae85fd1193efc4022e8d7ffee7b6c567 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Mon, 14 Jul 2025 13:18:40 +0300
Subject: [PATCH 32/34] patch tests

---
 .../__tests__/SubscriptionDetails.test.tsx           | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
index 0a4bfed164e..191a8534e90 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx
@@ -103,7 +103,7 @@ describe('SubscriptionDetails', () => {
     await userEvent.click(menuButton);
 
     await waitFor(() => {
-      expect(getByText('Switch to annual $100.00 per year')).toBeVisible();
+      expect(getByText('Switch to annual $100.00 / year')).toBeVisible();
       expect(getByText('Cancel subscription')).toBeVisible();
     });
   });
@@ -183,7 +183,7 @@ describe('SubscriptionDetails', () => {
     await userEvent.click(menuButton);
 
     await waitFor(() => {
-      expect(getByText('Switch to monthly $10.00 per month')).toBeVisible();
+      expect(getByText('Switch to monthly $10.00 / month')).toBeVisible();
       expect(getByText('Cancel subscription')).toBeVisible();
     });
   });
@@ -245,7 +245,7 @@ describe('SubscriptionDetails', () => {
       expect(getByText('Subscribed on')).toBeVisible();
       expect(getByText('January 1, 2021')).toBeVisible();
 
-      expect(queryByText('Renews at')).toBeNull();
+      expect(getByText('Renews at')).toBeVisible();
       expect(queryByText('Ends on')).toBeNull();
       expect(queryByText('Current billing cycle')).toBeNull();
       expect(queryByText('Monthly')).toBeNull();
@@ -368,7 +368,7 @@ describe('SubscriptionDetails', () => {
     await userEvent.click(menuButton);
 
     await waitFor(() => {
-      expect(getByText('Switch to monthly $13.00 per month')).toBeVisible();
+      expect(getByText('Switch to monthly $13.00 / month')).toBeVisible();
       expect(getByText('Resubscribe')).toBeVisible();
       expect(queryByText('Cancel subscription')).toBeNull();
     });
@@ -376,7 +376,7 @@ describe('SubscriptionDetails', () => {
     await userEvent.click(upcomingMenuButton);
 
     await waitFor(() => {
-      expect(getByText('Switch to annual $90.00 per year')).toBeVisible();
+      expect(getByText('Switch to annual $90.00 / year')).toBeVisible();
       expect(getByText('Cancel subscription')).toBeVisible();
     });
   });
@@ -491,7 +491,7 @@ describe('SubscriptionDetails', () => {
     });
   });
 
-  it.only('allows cancelling a subscription of a monthly plan', async () => {
+  it('allows cancelling a subscription of a monthly plan', async () => {
     const { wrapper, fixtures } = await createFixtures(f => {
       f.withUser({ email_addresses: ['test@clerk.com'] });
     });

From 9c5beacd3dd5a87003418e1ca29fda64bc9ebce8 Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Mon, 14 Jul 2025 17:24:10 +0300
Subject: [PATCH 33/34] fix changeset

---
 .changeset/lovely-lands-smell.md | 8 --------
 .changeset/tangy-toes-dress.md   | 8 ++++++++
 2 files changed, 8 insertions(+), 8 deletions(-)
 delete mode 100644 .changeset/lovely-lands-smell.md
 create mode 100644 .changeset/tangy-toes-dress.md

diff --git a/.changeset/lovely-lands-smell.md b/.changeset/lovely-lands-smell.md
deleted file mode 100644
index 9f791bdc28a..00000000000
--- a/.changeset/lovely-lands-smell.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-'@clerk/localizations': minor
-'@clerk/clerk-js': minor
-'@clerk/clerk-react': minor
-'@clerk/types': minor
----
-
-wip
diff --git a/.changeset/tangy-toes-dress.md b/.changeset/tangy-toes-dress.md
new file mode 100644
index 00000000000..76cfd7a4bff
--- /dev/null
+++ b/.changeset/tangy-toes-dress.md
@@ -0,0 +1,8 @@
+---
+'@clerk/localizations': minor
+'@clerk/clerk-js': minor
+'@clerk/clerk-react': minor
+'@clerk/types': minor
+---
+
+Extract `SubscriptionDetails`, into its own internal component, out of existing (also internal) `PlanDetails` component.

From 86de6849076aeb3f088ba39759f5a2b48a0e208a Mon Sep 17 00:00:00 2001
From: panteliselef <panteliselef@outlook.com>
Date: Mon, 14 Jul 2025 19:16:54 +0300
Subject: [PATCH 34/34] use line items

---
 .../src/ui/components/Plans/PlanDetails.tsx   |   1 -
 .../components/SubscriptionDetails/index.tsx  | 152 ++++--------------
 .../ui/customizables/elementDescriptors.ts    |   4 -
 .../clerk-js/src/ui/elements/LineItems.tsx    |  36 +++--
 packages/types/src/appearance.ts              |   4 -
 5 files changed, 52 insertions(+), 145 deletions(-)

diff --git a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
index 0836fb70727..5f570143e2a 100644
--- a/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
+++ b/packages/clerk-js/src/ui/components/Plans/PlanDetails.tsx
@@ -59,7 +59,6 @@ const PlanDetailsInternal = ({
 
   return (
     <SubscriberTypeContext.Provider value={plan.payerType[0] as 'user' | 'org'}>
-      {/* TODO: type assertion is a hack, make FAPI stricter */}
       <Drawer.Header
         sx={t =>
           !hasFeatures
diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
index 96c8430f2d8..2774763171f 100644
--- a/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
+++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx
@@ -23,18 +23,18 @@ import { formatDate } from '@/ui/utils/formatDate';
 
 const isFreePlan = (plan: CommercePlanResource) => !plan.hasBaseFee;
 
+import { LineItems } from '@/ui/elements/LineItems';
+
 import { SubscriberTypeContext, usePlansContext, useSubscriberTypeContext, useSubscriptions } from '../../contexts';
 import type { LocalizationKey } from '../../customizables';
 import {
   Badge,
-  Box,
   Button,
   Col,
   descriptors,
   Flex,
   Heading,
   localizationKeys,
-  Span,
   Spinner,
   Text,
   useLocalizations,
@@ -271,83 +271,47 @@ const SubscriptionDetailsFooter = withCardStateProvider(() => {
 
 function SubscriptionDetailsSummary() {
   const { anySubscription, activeSubscription, upcomingSubscription } = useGuessableSubscription({ or: 'throw' });
-  const { t } = useLocalizations();
 
   if (!activeSubscription) {
     return null;
   }
 
   return (
-    <Col
-      elementDescriptor={descriptors.subscriptionDetailsSummaryItems}
-      gap={3}
-      as='ul'
-      sx={t => ({
-        paddingBlock: t.space.$1,
-      })}
-    >
-      <SummaryItem>
-        <SummmaryItemLabel>
-          <Text
-            colorScheme='secondary'
-            localizationKey={localizationKeys('commerce.subscriptionDetails.currentBillingCycle')}
-          />
-        </SummmaryItemLabel>
-        <SummmaryItemValue>
-          <Text
-            colorScheme='secondary'
-            localizationKey={
-              activeSubscription.planPeriod === 'month'
-                ? localizationKeys('commerce.monthly')
-                : localizationKeys('commerce.annually')
-            }
-          />
-        </SummmaryItemValue>
-      </SummaryItem>
-      <SummaryItem>
-        <SummmaryItemLabel>
-          <Text colorScheme='secondary'>{t(localizationKeys('commerce.subscriptionDetails.nextPaymentOn'))}</Text>
-        </SummmaryItemLabel>
-        <SummmaryItemValue>
-          <Text colorScheme='secondary'>
-            {upcomingSubscription
+    <LineItems.Root>
+      <LineItems.Group>
+        <LineItems.Title description={localizationKeys('commerce.subscriptionDetails.currentBillingCycle')} />
+        <LineItems.Description
+          text={
+            activeSubscription.planPeriod === 'month'
+              ? localizationKeys('commerce.monthly')
+              : localizationKeys('commerce.annually')
+          }
+        />
+      </LineItems.Group>
+      <LineItems.Group>
+        <LineItems.Title description={localizationKeys('commerce.subscriptionDetails.nextPaymentOn')} />
+        <LineItems.Description
+          text={
+            upcomingSubscription
               ? formatDate(upcomingSubscription.periodStartDate)
               : anySubscription.periodEndDate
                 ? formatDate(anySubscription.periodEndDate)
-                : '-'}
-          </Text>
-        </SummmaryItemValue>
-      </SummaryItem>
-      <SummaryItem>
-        <SummmaryItemLabel>
-          <Text
-            colorScheme='secondary'
-            localizationKey={localizationKeys('commerce.subscriptionDetails.nextPaymentAmount')}
-          />
-        </SummmaryItemLabel>
-        <SummmaryItemValue
-          sx={t => ({
-            display: 'flex',
-            alignItems: 'center',
-            gap: t.space.$1,
-          })}
-        >
-          <Text
-            variant='caption'
-            colorScheme='secondary'
-            sx={{ textTransform: 'uppercase' }}
-          >
-            {anySubscription.plan.currency}
-          </Text>
-          <Text>
-            {anySubscription.plan.currencySymbol}
-            {anySubscription.planPeriod === 'month'
+                : '-'
+          }
+        />
+      </LineItems.Group>
+      <LineItems.Group>
+        <LineItems.Title description={localizationKeys('commerce.subscriptionDetails.nextPaymentAmount')} />
+        <LineItems.Description
+          prefix={anySubscription.plan.currency}
+          text={`${anySubscription.plan.currencySymbol}${
+            anySubscription.planPeriod === 'month'
               ? anySubscription.plan.amountFormatted
-              : anySubscription.plan.annualAmountFormatted}
-          </Text>
-        </SummmaryItemValue>
-      </SummaryItem>
-    </Col>
+              : anySubscription.plan.annualAmountFormatted
+          }`}
+        />
+      </LineItems.Group>
+    </LineItems.Root>
   );
 }
 
@@ -591,53 +555,3 @@ const DetailRow = ({ label, value }: { label: LocalizationKey; value: string })
     </Text>
   </Flex>
 );
-
-function SummaryItem(props: React.PropsWithChildren) {
-  return (
-    <Box
-      elementDescriptor={descriptors.subscriptionDetailsSummaryItem}
-      as='li'
-      sx={{
-        display: 'flex',
-        justifyContent: 'space-between',
-        flexWrap: 'wrap',
-      }}
-    >
-      {props.children}
-    </Box>
-  );
-}
-
-function SummmaryItemLabel(props: React.PropsWithChildren) {
-  return (
-    <Span
-      elementDescriptor={descriptors.subscriptionDetailsSummaryLabel}
-      sx={t => ({
-        display: 'flex',
-        alignItems: 'center',
-        gap: t.space.$1x5,
-      })}
-    >
-      {props.children}
-    </Span>
-  );
-}
-
-function SummmaryItemValue(props: Parameters<typeof Span>[0]) {
-  return (
-    <Span
-      elementDescriptor={descriptors.subscriptionDetailsSummaryValue}
-      {...props}
-      sx={[
-        t => ({
-          display: 'flex',
-          alignItems: 'center',
-          gap: t.space.$0x25,
-        }),
-        props.sx,
-      ]}
-    >
-      {props.children}
-    </Span>
-  );
-}
diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
index 90b01d9b3d3..cd7ffe647ae 100644
--- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
+++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts
@@ -491,10 +491,6 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
   'subscriptionDetailsCardBody',
   'subscriptionDetailsCardFooter',
   'subscriptionDetailsCardActions',
-  'subscriptionDetailsSummaryItems',
-  'subscriptionDetailsSummaryItem',
-  'subscriptionDetailsSummaryLabel',
-  'subscriptionDetailsSummaryValue',
   'subscriptionDetailsDetailRow',
   'subscriptionDetailsDetailRowLabel',
   'subscriptionDetailsDetailRowValue',
diff --git a/packages/clerk-js/src/ui/elements/LineItems.tsx b/packages/clerk-js/src/ui/elements/LineItems.tsx
index 461806babf4..71bf56673fa 100644
--- a/packages/clerk-js/src/ui/elements/LineItems.tsx
+++ b/packages/clerk-js/src/ui/elements/LineItems.tsx
@@ -81,7 +81,7 @@ function Group({ children, borderTop = false, variant = 'primary' }: GroupProps)
  * -----------------------------------------------------------------------------------------------*/
 
 interface TitleProps {
-  title: string | LocalizationKey;
+  title?: string | LocalizationKey;
   description?: string | LocalizationKey;
   icon?: React.ComponentType;
 }
@@ -104,22 +104,24 @@ const Title = React.forwardRef<HTMLTableCellElement, TitleProps>(({ title, descr
         ...common.textVariants(t)[textVariant],
       })}
     >
-      <Span
-        sx={t => ({
-          display: 'inline-flex',
-          alignItems: 'center',
-          gap: t.space.$1,
-        })}
-      >
-        {icon ? (
-          <Icon
-            size='md'
-            icon={icon}
-            aria-hidden
-          />
-        ) : null}
-        <Span localizationKey={title} />
-      </Span>
+      {title ? (
+        <Span
+          sx={t => ({
+            display: 'inline-flex',
+            alignItems: 'center',
+            gap: t.space.$1,
+          })}
+        >
+          {icon ? (
+            <Icon
+              size='md'
+              icon={icon}
+              aria-hidden
+            />
+          ) : null}
+          <Span localizationKey={title} />
+        </Span>
+      ) : null}
       {description ? (
         <Span
           localizationKey={description}
diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts
index a401cd9ce29..17c872df239 100644
--- a/packages/types/src/appearance.ts
+++ b/packages/types/src/appearance.ts
@@ -618,10 +618,6 @@ export type ElementsConfig = {
   subscriptionDetailsCardBody: WithOptions;
   subscriptionDetailsCardFooter: WithOptions;
   subscriptionDetailsCardActions: WithOptions;
-  subscriptionDetailsSummaryItems: WithOptions;
-  subscriptionDetailsSummaryItem: WithOptions;
-  subscriptionDetailsSummaryLabel: WithOptions;
-  subscriptionDetailsSummaryValue: WithOptions;
   subscriptionDetailsDetailRow: WithOptions;
   subscriptionDetailsDetailRowLabel: WithOptions;
   subscriptionDetailsDetailRowValue: WithOptions;