Repository: clerk/javascript
Pull request #5883: refactor(clerk-js): Improve switching between plans flows
Author: login: octoper
Labels: name: clerk-js
Created: 2025-05-08T11:59:49Z | Merged: 2025-05-12T00:21:54Z

## Description

## Resubscribe button improvements

**BEFORE:**

- User is initially on the Free plan
- They subscribe to any paid annual plan
- Then they downgrade to Free
- And finally, they click on resubscribe back the paid plan they were before

Now they are subscribed to the monthly plan not the annual which would be the expected

https://github.com/user-attachments/assets/1107288f-b828-4f7a-8e78-c4d20d70d3d9

**AFTER:**

**New flow**

- User is initially on the Free plan
- They subscribe to any paid annual plan
- Then they downgrade to Free

Now if they move back to switch plans they will only see the resubscribe button only on the annual plan and not the monthly one

https://github.com/user-attachments/assets/74a1f25c-d534-460b-acdc-fee002bff483


---


## Show Active or Upcoming badge based on selected plan period if the plan has subscriptions for both statuses

**Steps to reproduce**

- User is initially on the Free plan
- They subscribe to any paid annual plan
- Then they switch the the same plan but with the **monthly** interval

Now the card on the pricing table is confusing when switching between the plan periods, because on is active and one is upcoming but that's not reflected correctly on the badge

**Before**

https://github.com/user-attachments/assets/247db7ba-cd89-4755-a9fd-123c77033650

**After**

https://github.com/user-attachments/assets/371c499a-d1f4-4957-b5f9-8f4fe60169dc


---


## No subscriptions

![CleanShot 2025-05-08 at 17 05 49@2x](https://github.com/user-attachments/assets/c436de33-e798-4a8d-9546-092aa7814974)

## 1 active subscription 

![CleanShot 2025-05-08 at 17 06 43@2x](https://github.com/user-attachments/assets/01086c02-6a4b-4aef-a277-18fa9268655f)

## 1 active subscription  & 1 upcoming subscription

![CleanShot 2025-05-08 at 17 07 15@2x](https://github.com/user-attachments/assets/864b6376-27aa-4401-83e9-330622c6bc04)

## 1 annual active subscription  & 1 upcoming subscription & trying to switch to monthly period of current's active plan 

![CleanShot 2025-05-08 at 17 08 07@2x](https://github.com/user-attachments/assets/5fb453ea-3829-4930-84dd-4713f114b863)

## 1 active subscription  & 1 upcoming subscription on the same plan

| Monthly plan period (Upcoming) | Annual plan period (Active) |
|--------|--------|
![CleanShot 2025-05-08 at 17 10 19@2x](https://github.com/user-attachments/assets/9427961d-b47c-4970-ac0d-dbeed77bead9) | ![CleanShot 2025-05-08 at 17 11 31@2x](https://github.com/user-attachments/assets/a9e39ad2-4cdb-44fb-9568-aa1b8ddcac0f)


## Checklist

- [ ] `pnpm test` runs as expected.
- [ ] `pnpm build` runs as expected.
- [ ] (If applicable) [JSDoc comments](https://jsdoc.app/about-getting-started.html) have been added or updated for any package exports
- [ ] (If applicable) [Documentation](https://github.com/clerk/clerk-docs) has been updated

## Type of change

- [ ] 🐛 Bug fix
- [ ] 🌟 New feature
- [ ] 🔨 Breaking change
- [ ] 📖 Refactoring / dependency upgrade / documentation
- [ ] other:



Diff:
diff --git a/.changeset/little-cases-knock.md b/.changeset/little-cases-knock.md
new file mode 100644
index 00000000000..b04bc7f52b7
--- /dev/null
+++ b/.changeset/little-cases-knock.md
@@ -0,0 +1,5 @@
+---
+'@clerk/clerk-js': patch
+---
+
+Improvements of flows for switching between plans
diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json
index c29df0179b4..29558e15910 100644
--- a/packages/clerk-js/bundlewatch.config.json
+++ b/packages/clerk-js/bundlewatch.config.json
@@ -4,7 +4,7 @@
     { "path": "./dist/clerk.browser.js", "maxSize": "68.3KB" },
     { "path": "./dist/clerk.legacy.browser.js", "maxSize": "110KB" },
     { "path": "./dist/clerk.headless*.js", "maxSize": "52KB" },
-    { "path": "./dist/ui-common*.js", "maxSize": "104.1KB" },
+    { "path": "./dist/ui-common*.js", "maxSize": "104.20KB" },
     { "path": "./dist/vendors*.js", "maxSize": "39.5KB" },
     { "path": "./dist/coinbase*.js", "maxSize": "38KB" },
     { "path": "./dist/createorganization*.js", "maxSize": "5KB" },
diff --git a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
index a384d16b2d2..556ec7e5a5f 100644
--- a/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
+++ b/packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
@@ -109,8 +109,7 @@ function Card(props: CardProps) {
   const canManageBilling = useProtect(
     has => has({ permission: 'org:sys_billing:manage' }) || subscriberType === 'user',
   );
-
-  const { buttonPropsForPlan, upcomingSubscriptionsExist, activeOrUpcomingSubscription } = usePlansContext();
+  const { buttonPropsForPlan, activeOrUpcomingSubscriptionBasedOnPlanPeriod } = usePlansContext();
 
   const showPlanDetails = (event?: React.MouseEvent<HTMLElement>) => {
     const portalRoot = getClosestProfileScrollBox(mode, event);
@@ -123,21 +122,38 @@ function Card(props: CardProps) {
     });
   };
 
-  const subscription = activeOrUpcomingSubscription(plan);
-  const hasFeatures = plan.features.length > 0;
+  const subscription = React.useMemo(
+    () => activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod),
+    [plan, planPeriod, activeOrUpcomingSubscriptionBasedOnPlanPeriod],
+  );
   const isPlanActive = subscription?.status === 'active';
+  const hasFeatures = plan.features.length > 0;
   const showStatusRow = !!subscription;
-  const isEligibleForSwitchToAnnual = plan.annualMonthlyAmount > 0 && planPeriod === 'annual';
 
-  const shouldShowFooter =
-    !subscription ||
-    subscription?.status === 'upcoming' ||
-    subscription?.canceledAt ||
-    (planPeriod !== subscription?.planPeriod && !plan.isDefault && isEligibleForSwitchToAnnual);
-  const shouldShowFooterNotice =
-    subscription?.status === 'upcoming' && (planPeriod === subscription.planPeriod || plan.isDefault);
+  let shouldShowFooter = false;
+  let shouldShowFooterNotice = false;
 
-  const planPeriodSameAsSelectedPlanPeriod = !upcomingSubscriptionsExist && subscription?.planPeriod === planPeriod;
+  if (!subscription) {
+    shouldShowFooter = true;
+    shouldShowFooterNotice = false;
+  } else if (subscription.status === 'upcoming') {
+    shouldShowFooter = true;
+    shouldShowFooterNotice = true;
+  } else if (subscription.status === 'active') {
+    if (subscription.canceledAt) {
+      shouldShowFooter = true;
+      shouldShowFooterNotice = false;
+    } else if (planPeriod !== subscription.planPeriod && plan.annualMonthlyAmount > 0) {
+      shouldShowFooter = true;
+      shouldShowFooterNotice = false;
+    } else {
+      shouldShowFooter = false;
+      shouldShowFooterNotice = false;
+    }
+  } else {
+    shouldShowFooter = false;
+    shouldShowFooterNotice = false;
+  }
 
   return (
     <Box
@@ -226,7 +242,6 @@ function Card(props: CardProps) {
               borderTopWidth: t.borderWidths.$normal,
               borderTopStyle: t.borderStyles.$solid,
               borderTopColor: t.colors.$neutralAlpha100,
-              background: planPeriodSameAsSelectedPlanPeriod && hasFeatures ? t.colors.$colorBackground : undefined,
               order: ctaPosition === 'top' ? -1 : undefined,
             })}
           >
diff --git a/packages/clerk-js/src/ui/contexts/components/Plans.tsx b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
index 127b9723e78..b9a3b8cea2c 100644
--- a/packages/clerk-js/src/ui/contexts/components/Plans.tsx
+++ b/packages/clerk-js/src/ui/contexts/components/Plans.tsx
@@ -176,6 +176,40 @@ export const usePlansContext = () => {
     [ctx.subscriptions],
   );
 
+  // returns all subscriptions for a plan that are active or upcoming
+  const activeAndUpcomingSubscriptions = useCallback(
+    (plan: CommercePlanResource) => {
+      return ctx.subscriptions.filter(subscription => subscription.plan.id === plan.id);
+    },
+    [ctx.subscriptions],
+  );
+
+  // return the active or upcoming subscription for a plan based on the plan period, if there is no subscription for the plan period, return the first subscription
+  const activeOrUpcomingSubscriptionWithPlanPeriod = useCallback(
+    (plan: CommercePlanResource, planPeriod: CommerceSubscriptionPlanPeriod = 'month') => {
+      const plansSubscriptions = activeAndUpcomingSubscriptions(plan);
+      // Handle multiple subscriptions for the same plan
+      if (plansSubscriptions.length > 1) {
+        const subscriptionBaseOnPanPeriod = plansSubscriptions.find(subscription => {
+          return subscription.planPeriod === planPeriod;
+        });
+
+        if (subscriptionBaseOnPanPeriod) {
+          return subscriptionBaseOnPanPeriod;
+        }
+
+        return plansSubscriptions[0];
+      }
+
+      if (plansSubscriptions.length === 1) {
+        return plansSubscriptions[0];
+      }
+
+      return undefined;
+    },
+    [activeAndUpcomingSubscriptions],
+  );
+
   const canManageSubscription = useCallback(
     ({ plan, subscription: sub }: { plan?: CommercePlanResource; subscription?: CommerceSubscriptionResource }) => {
       const subscription = sub ?? (plan ? activeOrUpcomingSubscription(plan) : undefined);
@@ -212,7 +246,8 @@ export const usePlansContext = () => {
       isDisabled: boolean;
       disabled: boolean;
     } => {
-      const subscription = sub ?? (plan ? activeOrUpcomingSubscription(plan) : undefined);
+      const subscription =
+        sub ?? (plan ? activeOrUpcomingSubscriptionWithPlanPeriod(plan, selectedPlanPeriod) : undefined);
       let _selectedPlanPeriod = selectedPlanPeriod;
       if (_selectedPlanPeriod === 'annual' && sub?.plan.annualMonthlyAmount === 0) {
         _selectedPlanPeriod = 'month';
@@ -220,28 +255,55 @@ export const usePlansContext = () => {
 
       const isEligibleForSwitchToAnnual = (plan?.annualMonthlyAmount ?? 0) > 0;
 
+      const getLocalizationKey = () => {
+        // Handle subscription cases
+        if (subscription) {
+          if (_selectedPlanPeriod !== subscription.planPeriod && subscription.canceledAt) {
+            if (_selectedPlanPeriod === 'month') {
+              return localizationKeys('commerce.switchToMonthly');
+            }
+
+            if (isEligibleForSwitchToAnnual) {
+              return localizationKeys('commerce.switchToAnnual');
+            }
+          }
+
+          if (subscription.canceledAt) {
+            return localizationKeys('commerce.reSubscribe');
+          }
+
+          if (_selectedPlanPeriod !== subscription.planPeriod) {
+            if (_selectedPlanPeriod === 'month') {
+              return localizationKeys('commerce.switchToMonthly');
+            }
+
+            if (isEligibleForSwitchToAnnual) {
+              return localizationKeys('commerce.switchToAnnual');
+            }
+
+            return localizationKeys('commerce.manageSubscription');
+          }
+
+          return localizationKeys('commerce.manageSubscription');
+        }
+
+        // Handle non-subscription cases
+        const hasNonDefaultSubscriptions =
+          ctx.subscriptions.filter(subscription => !subscription.plan.isDefault).length > 0;
+        return hasNonDefaultSubscriptions
+          ? localizationKeys('commerce.switchPlan')
+          : localizationKeys('commerce.subscribe');
+      };
+
       return {
-        localizationKey: subscription
-          ? subscription.canceledAt
-            ? localizationKeys('commerce.reSubscribe')
-            : selectedPlanPeriod !== subscription.planPeriod
-              ? selectedPlanPeriod === 'month'
-                ? localizationKeys('commerce.switchToMonthly')
-                : isEligibleForSwitchToAnnual
-                  ? localizationKeys('commerce.switchToAnnual')
-                  : localizationKeys('commerce.manageSubscription')
-              : localizationKeys('commerce.manageSubscription')
-          : // If there are no active or grace period subscriptions, show the get started button
-            ctx.subscriptions.filter(subscription => !subscription.plan.isDefault).length > 0
-            ? localizationKeys('commerce.switchPlan')
-            : localizationKeys('commerce.subscribe'),
+        localizationKey: getLocalizationKey(),
         variant: isCompact ? 'bordered' : 'solid',
         colorScheme: isCompact ? 'secondary' : 'primary',
         isDisabled: !canManageBilling,
         disabled: !canManageBilling,
       };
     },
-    [activeOrUpcomingSubscription, canManageBilling, ctx.subscriptions],
+    [activeOrUpcomingSubscriptionWithPlanPeriod, canManageBilling, ctx.subscriptions],
   );
 
   const captionForSubscription = useCallback((subscription: CommerceSubscriptionResource) => {
@@ -261,7 +323,7 @@ export const usePlansContext = () => {
 
       const portalRoot = getClosestProfileScrollBox(mode, event);
 
-      if (subscription && !subscription.canceledAt) {
+      if (subscription && subscription.planPeriod === planPeriod && !subscription.canceledAt) {
         clerk.__internal_openPlanDetails({
           plan,
           subscriberType,
@@ -303,6 +365,8 @@ export const usePlansContext = () => {
     ...ctx,
     componentName,
     activeOrUpcomingSubscription,
+    activeAndUpcomingSubscriptions,
+    activeOrUpcomingSubscriptionBasedOnPlanPeriod: activeOrUpcomingSubscriptionWithPlanPeriod,
     isDefaultPlanImplicitlyActiveOrUpcoming,
     handleSelectPlan,
     buttonPropsForPlan,


Reviews:
user: login: octoper
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T14:58:57Z
user: login: alexcarpenter
body: @octoper and I reviewed the code together and handled the documented flows in the PR description, but would love to get both @aeliox and/or @brkalow eyes on this one.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T18:54:36Z
user: login: panteliselef
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T21:31:54Z
user: login: aeliox
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T14:35:29Z
user: login: brkalow
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T16:36:55Z
user: login: octoper
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T19:10:26Z
user: login: aeliox
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-11T15:43:22Z

Review comments:
diff_hunk: @@ -437,7 +437,7 @@
     <script
       type="text/javascript"
       src="/<%= htmlRspackPlugin.files.js[0] %>"
-      data-clerk-publishable-key="pk_test_dG91Y2hlZC1sYWR5YmlyZC0yMy5jbGVyay5hY2NvdW50cy5kZXYk"
+      data-clerk-publishable-key="pk_test_ZGV2b3RlZC15YWstMjkuY2xlcmsuYWNjb3VudHNzdGFnZS5kZXYk"
path: packages/clerk-js/sandbox/template.html
user: login: octoper
body: This will need to be reverted before merging, used it to ease testing on sandbox
created_at: 2025-05-08T14:58:57Z
author_association: MEMBER
diff_hunk: @@ -174,6 +174,42 @@ export const usePlansContext = () => {
     [ctx.subscriptions],
   );
 
+  // returns all subscriptions for a plan that are active or upcoming
+  const activeAndUpcomingSubscriptions = useCallback(
+    (plan: CommercePlanResource) => {
+      return ctx.subscriptions
+        .filter(subscription => subscription.plan.id === plan.id)
+        .filter(subscription => subscription.status === 'active' || subscription.status === 'upcoming');
path: packages/clerk-js/src/ui/contexts/components/Plans.tsx
user: login: panteliselef
body: Doesn't the endpoint already return the active and upcoming only ? If not, then i think filtering in FAPI
created_at: 2025-05-08T21:31:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -174,6 +174,42 @@ export const usePlansContext = () => {
     [ctx.subscriptions],
   );
 
+  // returns all subscriptions for a plan that are active or upcoming
+  const activeAndUpcomingSubscriptions = useCallback(
+    (plan: CommercePlanResource) => {
+      return ctx.subscriptions
+        .filter(subscription => subscription.plan.id === plan.id)
+        .filter(subscription => subscription.status === 'active' || subscription.status === 'upcoming');
path: packages/clerk-js/src/ui/contexts/components/Plans.tsx
user: login: aeliox
body: Yes the endpoint only returns active and upcoming subscriptions
created_at: 2025-05-09T14:35:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -123,21 +122,38 @@ function Card(props: CardProps) {
     });
   };
 
-  const subscription = activeOrUpcomingSubscription(plan);
-  const hasFeatures = plan.features.length > 0;
+  const subscription = React.useMemo(
+    () => activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod),
+    [plan, planPeriod, activeOrUpcomingSubscriptionBasedOnPlanPeriod],
+  );
   const isPlanActive = subscription?.status === 'active';
+  const hasFeatures = plan.features.length > 0;
   const showStatusRow = !!subscription;
-  const isEligibleForSwitchToAnnual = plan.annualMonthlyAmount > 0 && planPeriod === 'annual';
 
-  const shouldShowFooter =
-    !subscription ||
-    subscription?.status === 'upcoming' ||
-    subscription?.canceledAt ||
-    (planPeriod !== subscription?.planPeriod && !plan.isDefault && isEligibleForSwitchToAnnual);
-  const shouldShowFooterNotice =
-    subscription?.status === 'upcoming' && (planPeriod === subscription.planPeriod || plan.isDefault);
+  let shouldShowFooter = false;
+  let shouldShowFooterNotice = false;
 
-  const planPeriodSameAsSelectedPlanPeriod = !upcomingSubscriptionsExist && subscription?.planPeriod === planPeriod;
+  if (!subscription) {
+    shouldShowFooter = true;
+    shouldShowFooterNotice = false;
+  } else if (subscription.status === 'upcoming') {
+    shouldShowFooter = true;
+    shouldShowFooterNotice = true;
+  } else if (subscription.status === 'active') {
+    if (subscription.canceledAt) {
+      shouldShowFooter = true;
+      shouldShowFooterNotice = false;
+    } else if (planPeriod !== subscription.planPeriod && plan.annualMonthlyAmount > 0) {
+      shouldShowFooter = true;
+      shouldShowFooterNotice = false;
+    } else {
+      shouldShowFooter = false;
+      shouldShowFooterNotice = false;
+    }
+  } else {
+    shouldShowFooter = false;
+    shouldShowFooterNotice = false;
+  }
path: packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
user: login: brkalow
body: Much better 👏
created_at: 2025-05-09T16:35:55Z
author_association: MEMBER
diff_hunk: @@ -123,21 +122,38 @@ function Card(props: CardProps) {
     });
   };
 
-  const subscription = activeOrUpcomingSubscription(plan);
-  const hasFeatures = plan.features.length > 0;
+  const subscription = React.useMemo(
+    () => activeOrUpcomingSubscriptionBasedOnPlanPeriod(plan, planPeriod),
+    [plan, planPeriod, activeOrUpcomingSubscriptionBasedOnPlanPeriod],
+  );
   const isPlanActive = subscription?.status === 'active';
+  const hasFeatures = plan.features.length > 0;
   const showStatusRow = !!subscription;
-  const isEligibleForSwitchToAnnual = plan.annualMonthlyAmount > 0 && planPeriod === 'annual';
 
-  const shouldShowFooter =
-    !subscription ||
-    subscription?.status === 'upcoming' ||
-    subscription?.canceledAt ||
-    (planPeriod !== subscription?.planPeriod && !plan.isDefault && isEligibleForSwitchToAnnual);
-  const shouldShowFooterNotice =
-    subscription?.status === 'upcoming' && (planPeriod === subscription.planPeriod || plan.isDefault);
+  let shouldShowFooter = false;
+  let shouldShowFooterNotice = false;
 
-  const planPeriodSameAsSelectedPlanPeriod = !upcomingSubscriptionsExist && subscription?.planPeriod === planPeriod;
+  if (!subscription) {
+    shouldShowFooter = true;
+    shouldShowFooterNotice = false;
+  } else if (subscription.status === 'upcoming') {
+    shouldShowFooter = true;
+    shouldShowFooterNotice = true;
+  } else if (subscription.status === 'active') {
+    if (subscription.canceledAt) {
+      shouldShowFooter = true;
+      shouldShowFooterNotice = false;
+    } else if (planPeriod !== subscription.planPeriod && plan.annualMonthlyAmount > 0) {
+      shouldShowFooter = true;
+      shouldShowFooterNotice = false;
+    } else {
+      shouldShowFooter = false;
+      shouldShowFooterNotice = false;
+    }
+  } else {
+    shouldShowFooter = false;
+    shouldShowFooterNotice = false;
+  }
path: packages/clerk-js/src/ui/components/PricingTable/PricingTableDefault.tsx
user: login: octoper
body: Kudos to @alexcarpenter for this one!
created_at: 2025-05-09T19:10:26Z
author_association: MEMBER

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-08T11:59:52Z
body: ###  🦋  Changeset detected

Latest commit: d5799c08ad52cff8541eee7600973ea026cf749a

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 3 packages</summary>

  | Name                    | Type  |
| ----------------------- | ----- |
| @clerk/clerk-js         | Patch |
| @clerk/chrome-extension | Patch |
| @clerk/clerk-expo       | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/clerk/javascript/new/vaggelis/com-763-cant-switch-to-monthly-subscription-for-the-same-plan-if?filename=.changeset/sour-ducks-move.md&value=---%0A%22%40clerk%2Fclerk-js%22%3A%20patch%0A---%0A%0Arefactor(clerk-js)%3A%20Improve%20switching%20between%20plans%20flows%0A)


author_association: NONE
user: login: vercel[bot]
created_at: 2025-05-08T11:59:53Z
body: [vc]: #2YSoVwl0uwcoyC9rfjl0dlFRo53RegRTqXvto4QUu9Y=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJjbGVyay1qcy1zYW5kYm94IiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2NsZXJrLXByb2R1Y3Rpb24vY2xlcmstanMtc2FuZGJveC9DaTN0SjF6a1Z0TW9tQWtVN29DMUEyMk1IeGdwIiwicHJldmlld1VybCI6ImNsZXJrLWpzLXNhbmRib3gtZ2l0LXZhZ2dlbGlzLWNvbS03NjMtY2FudC1zd2l0Y2gtdG8tbW9uLTM2ZDBiMi5jbGVya3N0YWdlLmRldiIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJjbGVyay1qcy1zYW5kYm94LWdpdC12YWdnZWxpcy1jb20tNzYzLWNhbnQtc3dpdGNoLXRvLW1vbi0zNmQwYjIuY2xlcmtzdGFnZS5kZXYifSwicm9vdERpcmVjdG9yeSI6InBhY2thZ2VzL2NsZXJrLWpzIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **clerk-js-sandbox** | ✅ Ready ([Inspect](https://vercel.com/clerk-production/clerk-js-sandbox/Ci3tJ1zkVtMomAkU7oC1A22MHxgp)) | [Visit Preview](https://clerk-js-sandbox-git-vaggelis-com-763-cant-switch-to-mon-36d0b2.clerkstage.dev) | 💬 [**Add feedback**](https://vercel.live/open-feedback/clerk-js-sandbox-git-vaggelis-com-763-cant-switch-to-mon-36d0b2.clerkstage.dev?via=pr-comment-feedback-link) | May 11, 2025 3:45pm |


author_association: NONE