From 7313901a4977378ff29fd12096102d74563dbcf8 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Fri, 24 Oct 2025 08:48:42 -0400 Subject: [PATCH] [PM-26019] Pre-Launch Payment Dialog (#16859) --- .../premium/premium-vnext.component.ts | 31 ++++++++++++++++++- .../upgrade-payment.component.ts | 11 ++++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/billing/individual/premium/premium-vnext.component.ts b/apps/web/src/app/billing/individual/premium/premium-vnext.component.ts index 32c8061b10..d25e035d1b 100644 --- a/apps/web/src/app/billing/individual/premium/premium-vnext.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium-vnext.component.ts @@ -42,6 +42,13 @@ import { UnifiedUpgradeDialogStep, } from "../upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component"; +const RouteParams = { + callToAction: "callToAction", +} as const; +const RouteParamValues = { + upgradeToPremium: "upgradeToPremium", +} as const; + // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -61,6 +68,7 @@ export class PremiumVNextComponent { protected hasPremiumFromAnyOrganization$: Observable; protected hasPremiumPersonally$: Observable; protected shouldShowNewDesign$: Observable; + protected shouldShowUpgradeDialogOnInit$: Observable; protected personalPricingTiers$: Observable; protected premiumCardData$: Observable<{ tier: PersonalSubscriptionPricingTier | undefined; @@ -72,7 +80,6 @@ export class PremiumVNextComponent { price: number; features: string[]; }>; - protected subscriber!: BitwardenSubscriber; protected isSelfHost = false; private destroyRef = inject(DestroyRef); @@ -134,6 +141,17 @@ export class PremiumVNextComponent { ) .subscribe(); + this.shouldShowUpgradeDialogOnInit$ = combineLatest([ + this.hasPremiumFromAnyOrganization$, + this.hasPremiumPersonally$, + this.activatedRoute.queryParams, + ]).pipe( + map(([hasOrgPremium, hasPersonalPremium, queryParams]) => { + const cta = queryParams[RouteParams.callToAction]; + return !hasOrgPremium && !hasPersonalPremium && cta === RouteParamValues.upgradeToPremium; + }), + ); + this.personalPricingTiers$ = this.subscriptionPricingService.getPersonalSubscriptionPricingTiers$(); @@ -166,6 +184,17 @@ export class PremiumVNextComponent { }), shareReplay({ bufferSize: 1, refCount: true }), ); + + this.shouldShowUpgradeDialogOnInit$ + .pipe( + switchMap(async (shouldShowUpgradeDialogOnInit) => { + if (shouldShowUpgradeDialogOnInit) { + from(this.openUpgradeDialog("Premium")); + } + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(); } private navigateToSubscriptionPage = (): Promise => diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts index 5ad465455f..f168672f23 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts @@ -1,5 +1,5 @@ import { - AfterViewInit, + AfterViewChecked, Component, DestroyRef, input, @@ -96,7 +96,7 @@ export type UpgradePaymentParams = { providers: [UpgradePaymentService], templateUrl: "./upgrade-payment.component.html", }) -export class UpgradePaymentComponent implements OnInit, AfterViewInit { +export class UpgradePaymentComponent implements OnInit, AfterViewChecked { protected readonly selectedPlanId = input.required(); protected readonly account = input.required(); protected goBack = output(); @@ -118,6 +118,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { }); protected readonly loading = signal(true); + private cartSummaryConfigured = false; private pricingTiers$!: Observable; // Cart Summary data @@ -201,9 +202,11 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { this.loading.set(false); } - ngAfterViewInit(): void { - if (this.cartSummaryComponent) { + ngAfterViewChecked(): void { + // Configure cart summary only once when it becomes available + if (this.cartSummaryComponent && !this.cartSummaryConfigured) { this.cartSummaryComponent.isExpanded.set(false); + this.cartSummaryConfigured = true; } }