From d1184f65e2f4323f2812aad9a4908096b0b85756 Mon Sep 17 00:00:00 2001 From: Piyanshu Saini Date: Thu, 20 Nov 2025 20:55:12 +0530 Subject: [PATCH] fix(platform): show loading spinner only for selected plan, not all plans (#25290) (#25292) ## Problem When users clicked on a plan card in the Cal.com Platform pricing UI, the loading spinner would appear on ALL plan cards simultaneously instead of just the selected one. This gave a confusing UX impression that all plans were being processed at the same time. ## Solution Track the currently loading plan using a `loadingPlan` state variable that stores which plan the user clicked on, rather than relying solely on the pending state from the mutation hook. ## Changes - Added `loadingPlan` state to track which specific plan is loading - Update `loadingPlan` when a plan subscription is initiated - Reset `loadingPlan` on error to ensure UI returns to normal state - Modified `isLoading` prop condition to check if the current plan matches the `loadingPlan` before showing the spinner This ensures that only the plan card the user clicked on displays the loading indicator, while other plans remain in their normal state. --- .../platform/pricing/platform-pricing/index.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/web/components/settings/platform/pricing/platform-pricing/index.tsx b/apps/web/components/settings/platform/pricing/platform-pricing/index.tsx index ed46268fae..4804f1b64e 100644 --- a/apps/web/components/settings/platform/pricing/platform-pricing/index.tsx +++ b/apps/web/components/settings/platform/pricing/platform-pricing/index.tsx @@ -1,6 +1,7 @@ import { useRouter } from "next/navigation"; import { usePathname } from "next/navigation"; import type { ReactNode } from "react"; +import { useState } from "react"; import { ErrorCode } from "@calcom/lib/errorCodes"; import { showToast } from "@calcom/ui/components/toast"; @@ -17,6 +18,8 @@ export const PlatformPricing = ({ teamId, teamPlan, heading }: PlatformPricingPr const pathname = usePathname(); const currentPage = pathname?.split("/").pop(); const router = useRouter(); + const [loadingPlan, setLoadingPlan] = useState(null); + const { mutateAsync: createTeamSubscription, isPending: isCreateTeamSubscriptionLoading } = useSubscribeTeamToStripe({ onSuccess: (redirectUrl: string) => { @@ -24,6 +27,7 @@ export const PlatformPricing = ({ teamId, teamPlan, heading }: PlatformPricingPr }, onError: () => { showToast(ErrorCode.UnableToSubscribeToThePlatform, "error"); + setLoadingPlan(null); }, teamId, }); @@ -35,6 +39,7 @@ export const PlatformPricing = ({ teamId, teamPlan, heading }: PlatformPricingPr }, onError: () => { showToast(ErrorCode.UnableToSubscribeToThePlatform, "error"); + setLoadingPlan(null); }, teamId, }); @@ -44,6 +49,8 @@ export const PlatformPricing = ({ teamId, teamPlan, heading }: PlatformPricingPr return router.push("https://go.cal.com/quote"); } + setLoadingPlan(plan); + if (currentPage === "platform") { createTeamSubscription({ plan: plan.toLocaleUpperCase() }); } else { @@ -68,7 +75,10 @@ export const PlatformPricing = ({ teamId, teamPlan, heading }: PlatformPricingPr description={plan.description} pricing={plan.pricing} includes={plan.includes} - isLoading={isCreateTeamSubscriptionLoading || isUpgradeTeamSubscriptionLoading} + isLoading={ + loadingPlan === plan.plan && + (isCreateTeamSubscriptionLoading || isUpgradeTeamSubscriptionLoading) + } currentPlan={plan.plan.toLocaleLowerCase() === teamPlan} handleSubscribe={() => handleStripeSubscription(plan.plan)} />