From 0f235e417c3cc66a334605462bcd8d6c3cff3776 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Thu, 29 Jan 2026 12:27:42 -0500 Subject: [PATCH] fix(billing): prevents multiple upgrade dialogs from opening Adds a check to prevent multiple upgrade dialogs from opening simultaneously. Ensures correct redirection to the organization vault after upgrading to Teams or Enterprise. --- .../premium-org-upgrade-payment.component.ts | 9 +++++++++ .../upgrade/services/unified-upgrade-prompt.service.ts | 5 +++++ .../unified-upgrade-dialog.component.ts | 8 ++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/premium-org-upgrade-payment.component.ts b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/premium-org-upgrade-payment.component.ts index 380e116911c..59620123084 100644 --- a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/premium-org-upgrade-payment.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-payment/premium-org-upgrade-payment.component.ts @@ -31,6 +31,7 @@ import { BusinessSubscriptionPricingTierId, PersonalSubscriptionPricingTier, PersonalSubscriptionPricingTierId, + PersonalSubscriptionPricingTierIds, } from "@bitwarden/common/billing/types/subscription-pricing-tier"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; @@ -192,6 +193,14 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit ) {} async ngOnInit(): Promise { + // If the selected plan is Personal Premium, no upgrade is needed + if (this.selectedPlanId() == PersonalSubscriptionPricingTierIds.Premium) { + this.complete.emit({ + status: PremiumOrgUpgradePaymentStatus.Closed, + organizationId: null, + }); + } + combineLatest([ this.subscriptionPricingService.getPersonalSubscriptionPricingTiers$(), this.subscriptionPricingService.getBusinessSubscriptionPricingTiers$(), diff --git a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts index f5a32483a4d..82cfa700266 100644 --- a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts +++ b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts @@ -85,6 +85,11 @@ export class UnifiedUpgradePromptService { * @returns A promise that resolves to the dialog result if shown, or null if not shown */ async displayUpgradePromptConditionally(): Promise { + // Prevent opening multiple dialogs if one is already open + if (this.unifiedUpgradeDialogRef) { + return null; + } + const shouldShow = await firstValueFrom(this.shouldShowPrompt$); if (shouldShow) { diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts index 84ef426ffff..887c9205230 100644 --- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts @@ -248,10 +248,14 @@ export class UnifiedUpgradeDialogComponent implements OnInit { if ( this.params.redirectOnCompletion && (status === UnifiedUpgradeDialogStatus.UpgradedToPremium || - status === UnifiedUpgradeDialogStatus.UpgradedToFamilies) + status === UnifiedUpgradeDialogStatus.UpgradedToFamilies || + status === UnifiedUpgradeDialogStatus.UpgradedToEnterprise || + status === UnifiedUpgradeDialogStatus.UpgradedToTeams) ) { const redirectUrl = - status === UnifiedUpgradeDialogStatus.UpgradedToFamilies + status === UnifiedUpgradeDialogStatus.UpgradedToFamilies || + status === UnifiedUpgradeDialogStatus.UpgradedToEnterprise || + status === UnifiedUpgradeDialogStatus.UpgradedToTeams ? `/organizations/${result.organizationId}/vault` : "/settings/subscription/user-subscription"; await this.router.navigate([redirectUrl]);