diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.html b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.html
index ab14fbbee6c..2cac1c202b5 100644
--- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.html
+++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.html
@@ -1,33 +1,16 @@
-
-@if (showPremiumOrgFlow()) {
- @if (step() == PlanSelectionStep) {
-
- } @else if (step() == PaymentStep && selectedPlan() !== null && account() !== null) {
-
- }
-} @else {
-
- @if (step() == PlanSelectionStep) {
-
- } @else if (step() == PaymentStep && selectedPersonalPlanId() !== null && account() !== null) {
-
- }
+
+@if (step() == PlanSelectionStep) {
+
+} @else if (step() == PaymentStep && selectedPlan() !== null && account() !== null) {
+
}
diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts
index ab9e962bd0a..b3b8dbb170d 100644
--- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts
+++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts
@@ -3,27 +3,17 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { Router } from "@angular/router";
import { mock } from "jest-mock-extended";
-import { of } from "rxjs";
import { PremiumInterestStateService } from "@bitwarden/angular/billing/services/premium-interest/premium-interest-state.service.abstraction";
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
-import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
import {
PersonalSubscriptionPricingTierId,
PersonalSubscriptionPricingTierIds,
- BusinessSubscriptionPricingTierId,
} from "@bitwarden/common/billing/types/subscription-pricing-tier";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { mockAccountInfoWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { DIALOG_DATA, DialogRef } from "@bitwarden/components";
-import { PremiumOrgUpgradeComponent } from "../premium-org-upgrade/premium-org-upgrade.component";
-import {
- PremiumOrgUpgradePaymentComponent,
- PremiumOrgUpgradePaymentResult,
- PremiumOrgUpgradePaymentStatus,
-} from "../premium-org-upgrade-payment/premium-org-upgrade-payment.component";
import {
UpgradeAccountComponent,
UpgradeAccountStatus,
@@ -67,44 +57,12 @@ class MockUpgradePaymentComponent {
complete = output();
}
-@Component({
- selector: "app-premium-org-upgrade",
- template: "",
- standalone: true,
- providers: [PremiumOrgUpgradeComponent],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-class MockPremiumOrgUpgradeComponent {
- readonly dialogTitleMessageOverride = input(null);
- readonly hideContinueWithoutUpgradingButton = input(false);
- planSelected = output();
- closeClicked = output();
-}
-
-@Component({
- selector: "app-premium-org-upgrade-payment",
- template: "",
- standalone: true,
- providers: [PremiumOrgUpgradePaymentComponent],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-class MockPremiumOrgUpgradePaymentComponent {
- readonly selectedPlanId = input<
- PersonalSubscriptionPricingTierId | BusinessSubscriptionPricingTierId | null
- >(null);
- readonly account = input(null);
- goBack = output();
- complete = output<{ status: PremiumOrgUpgradePaymentStatus; organizationId: string | null }>();
-}
-
describe("UnifiedUpgradeDialogComponent", () => {
let component: UnifiedUpgradeDialogComponent;
let fixture: ComponentFixture;
const mockDialogRef = mock();
const mockRouter = mock();
const mockPremiumInterestStateService = mock();
- const mockBillingAccountProfileStateService = mock();
- const mockConfigService = mock();
const mockAccount: Account = {
id: "user-id" as UserId,
...mockAccountInfoWith({
@@ -140,29 +98,14 @@ describe("UnifiedUpgradeDialogComponent", () => {
{ provide: DIALOG_DATA, useValue: dialogData },
{ provide: Router, useValue: mockRouter },
{ provide: PremiumInterestStateService, useValue: mockPremiumInterestStateService },
- {
- provide: BillingAccountProfileStateService,
- useValue: mockBillingAccountProfileStateService,
- },
- { provide: ConfigService, useValue: mockConfigService },
],
})
.overrideComponent(UnifiedUpgradeDialogComponent, {
remove: {
- imports: [
- UpgradeAccountComponent,
- UpgradePaymentComponent,
- PremiumOrgUpgradeComponent,
- PremiumOrgUpgradePaymentComponent,
- ],
+ imports: [UpgradeAccountComponent, UpgradePaymentComponent],
},
add: {
- imports: [
- MockUpgradeAccountComponent,
- MockUpgradePaymentComponent,
- MockPremiumOrgUpgradeComponent,
- MockPremiumOrgUpgradePaymentComponent,
- ],
+ imports: [MockUpgradeAccountComponent, MockUpgradePaymentComponent],
},
})
.compileComponents();
@@ -184,8 +127,6 @@ describe("UnifiedUpgradeDialogComponent", () => {
// Default mock: no premium interest
mockPremiumInterestStateService.getPremiumInterest.mockResolvedValue(false);
- mockBillingAccountProfileStateService.hasPremiumPersonally$.mockReturnValue(of(true));
- mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
await TestBed.configureTestingModule({
imports: [UnifiedUpgradeDialogComponent],
providers: [
@@ -193,29 +134,14 @@ describe("UnifiedUpgradeDialogComponent", () => {
{ provide: DIALOG_DATA, useValue: defaultDialogData },
{ provide: Router, useValue: mockRouter },
{ provide: PremiumInterestStateService, useValue: mockPremiumInterestStateService },
- {
- provide: BillingAccountProfileStateService,
- useValue: mockBillingAccountProfileStateService,
- },
- { provide: ConfigService, useValue: mockConfigService },
],
})
.overrideComponent(UnifiedUpgradeDialogComponent, {
remove: {
- imports: [
- UpgradeAccountComponent,
- UpgradePaymentComponent,
- PremiumOrgUpgradeComponent,
- PremiumOrgUpgradePaymentComponent,
- ],
+ imports: [UpgradeAccountComponent, UpgradePaymentComponent],
},
add: {
- imports: [
- MockUpgradeAccountComponent,
- MockUpgradePaymentComponent,
- MockPremiumOrgUpgradeComponent,
- MockPremiumOrgUpgradePaymentComponent,
- ],
+ imports: [MockUpgradeAccountComponent, MockUpgradePaymentComponent],
},
})
.compileComponents();
@@ -477,170 +403,30 @@ describe("UnifiedUpgradeDialogComponent", () => {
});
describe("Child Component Display Logic", () => {
- describe("Plan Selection Step", () => {
- it("should display app-upgrade-account when user does not have premium personally", async () => {
- mockBillingAccountProfileStateService.hasPremiumPersonally$.mockReturnValue(of(false));
- mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
+ it("should display app-upgrade-account on plan selection step", async () => {
+ const { fixture } = await createComponentWithDialogData(defaultDialogData);
- const { fixture } = await createComponentWithDialogData(defaultDialogData);
+ const upgradeAccountElement = fixture.nativeElement.querySelector("app-upgrade-account");
- const upgradeAccountElement = fixture.nativeElement.querySelector("app-upgrade-account");
- const premiumOrgUpgradeElement =
- fixture.nativeElement.querySelector("app-premium-org-upgrade");
-
- expect(upgradeAccountElement).toBeTruthy();
- expect(premiumOrgUpgradeElement).toBeFalsy();
- });
-
- it("should display app-upgrade-account when user has premium but feature flag is disabled", async () => {
- mockBillingAccountProfileStateService.hasPremiumPersonally$.mockReturnValue(of(true));
- mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
-
- const { fixture } = await createComponentWithDialogData(defaultDialogData);
-
- const upgradeAccountElement = fixture.nativeElement.querySelector("app-upgrade-account");
- const premiumOrgUpgradeElement =
- fixture.nativeElement.querySelector("app-premium-org-upgrade");
-
- expect(upgradeAccountElement).toBeTruthy();
- expect(premiumOrgUpgradeElement).toBeFalsy();
- });
-
- it("should display app-premium-org-upgrade when user has premium and feature flag is enabled", async () => {
- mockBillingAccountProfileStateService.hasPremiumPersonally$.mockReturnValue(of(true));
- mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
-
- const { fixture } = await createComponentWithDialogData(defaultDialogData);
-
- const upgradeAccountElement = fixture.nativeElement.querySelector("app-upgrade-account");
- const premiumOrgUpgradeElement =
- fixture.nativeElement.querySelector("app-premium-org-upgrade");
-
- expect(upgradeAccountElement).toBeFalsy();
- expect(premiumOrgUpgradeElement).toBeTruthy();
- });
+ expect(upgradeAccountElement).toBeTruthy();
});
- describe("Payment Step", () => {
- it("should display app-upgrade-payment when user does not have premium personally", async () => {
- mockBillingAccountProfileStateService.hasPremiumPersonally$.mockReturnValue(of(false));
+ it("should display app-upgrade-payment on payment step", async () => {
+ const customDialogData: UnifiedUpgradeDialogParams = {
+ account: mockAccount,
+ initialStep: UnifiedUpgradeDialogStep.Payment,
+ selectedPlan: PersonalSubscriptionPricingTierIds.Premium,
+ };
- const customDialogData: UnifiedUpgradeDialogParams = {
- account: mockAccount,
- initialStep: UnifiedUpgradeDialogStep.Payment,
- selectedPlan: PersonalSubscriptionPricingTierIds.Premium,
- };
+ const { fixture } = await createComponentWithDialogData(customDialogData);
- const { fixture } = await createComponentWithDialogData(customDialogData);
+ const upgradePaymentElement = fixture.nativeElement.querySelector("app-upgrade-payment");
- const upgradePaymentElement = fixture.nativeElement.querySelector("app-upgrade-payment");
- const premiumOrgUpgradePaymentElement = fixture.nativeElement.querySelector(
- "app-premium-org-upgrade-payment",
- );
-
- expect(upgradePaymentElement).toBeTruthy();
- expect(premiumOrgUpgradePaymentElement).toBeFalsy();
- });
-
- it("should display app-premium-org-upgrade-payment when user has premium personally", async () => {
- mockBillingAccountProfileStateService.hasPremiumPersonally$.mockReturnValue(of(true));
- mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
-
- const customDialogData: UnifiedUpgradeDialogParams = {
- account: mockAccount,
- initialStep: UnifiedUpgradeDialogStep.Payment,
- selectedPlan: "teams" as BusinessSubscriptionPricingTierId,
- };
-
- const { fixture } = await createComponentWithDialogData(customDialogData);
-
- const upgradePaymentElement = fixture.nativeElement.querySelector("app-upgrade-payment");
- const premiumOrgUpgradePaymentElement = fixture.nativeElement.querySelector(
- "app-premium-org-upgrade-payment",
- );
-
- expect(upgradePaymentElement).toBeFalsy();
- expect(premiumOrgUpgradePaymentElement).toBeTruthy();
- });
+ expect(upgradePaymentElement).toBeTruthy();
});
});
- describe("Premium Org Upgrade", () => {
- it("should handle selecting a business plan (Teams) and move to payment step", async () => {
- const { component } = await createComponentWithDialogData(defaultDialogData);
-
- component["onPlanSelected"]("teams" as BusinessSubscriptionPricingTierId);
-
- expect(component["selectedPlan"]()).toBe("teams");
- expect(component["step"]()).toBe(UnifiedUpgradeDialogStep.Payment);
- });
-
- it("should handle completing premium org upgrade to Teams successfully", async () => {
- const { component } = await createComponentWithDialogData(defaultDialogData);
- mockRouter.navigate.mockResolvedValue(true);
-
- const result = {
- status: "upgradedToTeams" as const,
- organizationId: "org-123",
- };
-
- await component["onComplete"](result);
-
- expect(mockDialogRef.close).toHaveBeenCalledWith({
- status: "upgradedToTeams",
- organizationId: "org-123",
- });
- });
-
- it("should handle completing premium org upgrade to Enterprise successfully", async () => {
- const { component } = await createComponentWithDialogData(defaultDialogData);
- mockRouter.navigate.mockResolvedValue(true);
-
- const result = {
- status: "upgradedToEnterprise" as const,
- organizationId: "org-456",
- };
-
- await component["onComplete"](result);
-
- expect(mockDialogRef.close).toHaveBeenCalledWith({
- status: "upgradedToEnterprise",
- organizationId: "org-456",
- });
- });
-
- it("should handle user closing during premium org plan selection", async () => {
- const { component } = await createComponentWithDialogData(defaultDialogData);
-
- await component["onCloseClicked"]();
-
- expect(mockDialogRef.close).toHaveBeenCalledWith({ status: "closed" });
- });
-
- it("should go back from premium org payment step to plan selection", async () => {
- const { component } = await createComponentWithDialogData(defaultDialogData);
- component["step"].set(UnifiedUpgradeDialogStep.Payment);
- component["selectedPlan"].set("teams" as BusinessSubscriptionPricingTierId);
-
- await component["previousStep"]();
-
- expect(component["step"]()).toBe(UnifiedUpgradeDialogStep.PlanSelection);
- expect(component["selectedPlan"]()).toBeNull();
- });
-
- it("should handle closed status during premium org upgrade", async () => {
- const { component } = await createComponentWithDialogData(defaultDialogData);
-
- const result: PremiumOrgUpgradePaymentResult = { status: "closed", organizationId: null };
-
- await component["onComplete"](result);
-
- expect(mockDialogRef.close).toHaveBeenCalledWith({
- status: "closed",
- organizationId: null,
- });
- });
-
+ describe("redirectOnCompletion", () => {
it("should handle redirectOnCompletion for families upgrade with organization", async () => {
const customDialogData: UnifiedUpgradeDialogParams = {
account: mockAccount,
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 887c9205230..63017760195 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
@@ -1,26 +1,11 @@
import { DIALOG_DATA } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
-import {
- ChangeDetectionStrategy,
- Component,
- computed,
- Inject,
- OnInit,
- signal,
-} from "@angular/core";
-import { toSignal } from "@angular/core/rxjs-interop";
+import { ChangeDetectionStrategy, Component, Inject, OnInit, signal } from "@angular/core";
import { Router } from "@angular/router";
import { PremiumInterestStateService } from "@bitwarden/angular/billing/services/premium-interest/premium-interest-state.service.abstraction";
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
-import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
-import {
- BusinessSubscriptionPricingTierId,
- PersonalSubscriptionPricingTierId,
- PersonalSubscriptionPricingTierIds,
-} from "@bitwarden/common/billing/types/subscription-pricing-tier";
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
+import { PersonalSubscriptionPricingTierId } from "@bitwarden/common/billing/types/subscription-pricing-tier";
import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
import {
ButtonModule,
@@ -32,11 +17,6 @@ import {
import { AccountBillingClient, PreviewInvoiceClient } from "../../../clients";
import { BillingServicesModule } from "../../../services";
-import { PremiumOrgUpgradeComponent } from "../premium-org-upgrade/premium-org-upgrade.component";
-import {
- PremiumOrgUpgradePaymentComponent,
- PremiumOrgUpgradePaymentResult,
-} from "../premium-org-upgrade-payment/premium-org-upgrade-payment.component";
import { UpgradeAccountComponent } from "../upgrade-account/upgrade-account.component";
import { UpgradePaymentService } from "../upgrade-payment/services/upgrade-payment.service";
import {
@@ -48,8 +28,6 @@ export const UnifiedUpgradeDialogStatus = {
Closed: "closed",
UpgradedToPremium: "upgradedToPremium",
UpgradedToFamilies: "upgradedToFamilies",
- UpgradedToTeams: "upgradedToTeams",
- UpgradedToEnterprise: "upgradedToEnterprise",
} as const;
export const UnifiedUpgradeDialogStep = {
@@ -79,7 +57,7 @@ export type UnifiedUpgradeDialogResult = {
export type UnifiedUpgradeDialogParams = {
account: Account;
initialStep?: UnifiedUpgradeDialogStep | null;
- selectedPlan?: PersonalSubscriptionPricingTierId | BusinessSubscriptionPricingTierId | null;
+ selectedPlan?: PersonalSubscriptionPricingTierId | null;
planSelectionStepTitleOverride?: string | null;
hideContinueWithoutUpgradingButton?: boolean;
redirectOnCompletion?: boolean;
@@ -95,8 +73,6 @@ export type UnifiedUpgradeDialogParams = {
UpgradeAccountComponent,
UpgradePaymentComponent,
BillingServicesModule,
- PremiumOrgUpgradeComponent,
- PremiumOrgUpgradePaymentComponent,
],
providers: [UpgradePaymentService, AccountBillingClient, PreviewInvoiceClient],
templateUrl: "./unified-upgrade-dialog.component.html",
@@ -106,43 +82,11 @@ export class UnifiedUpgradeDialogComponent implements OnInit {
protected readonly step = signal(
UnifiedUpgradeDialogStep.PlanSelection,
);
- protected readonly selectedPlan = signal<
- PersonalSubscriptionPricingTierId | BusinessSubscriptionPricingTierId | null
- >(null);
+ protected readonly selectedPlan = signal(null);
protected readonly account = signal(null);
protected readonly planSelectionStepTitleOverride = signal(null);
protected readonly hideContinueWithoutUpgradingButton = signal(false);
protected readonly hasPremiumInterest = signal(false);
- protected readonly hasPremiumPersonally = toSignal(
- this.billingAccountProfileStateService.hasPremiumPersonally$(this.params.account.id),
- { initialValue: false },
- );
- protected readonly premiumToOrganizationUpgradeEnabled = toSignal(
- this.configService.getFeatureFlag$(FeatureFlag.PM29593_PremiumToOrganizationUpgrade),
- { initialValue: false },
- );
- protected readonly showPremiumOrgFlow = computed(
- () => this.hasPremiumPersonally() && this.premiumToOrganizationUpgradeEnabled(),
- );
- /**
- * Type-safe computed signal for app-upgrade-payment component.
- * Returns the selected plan only when it's a personal plan (Premium or Families).
- * When showPremiumOrgFlow is true, this will be null since business plans are handled separately.
- */
- protected readonly selectedPersonalPlanId = computed(
- () => {
- const plan = this.selectedPlan();
- // When showing premium org flow, user is selecting business plans (Teams/Enterprise)
- // Standard flow uses personal plans (Premium/Families)
- if (
- plan === PersonalSubscriptionPricingTierIds.Premium ||
- plan === PersonalSubscriptionPricingTierIds.Families
- ) {
- return plan;
- }
- return null;
- },
- );
protected readonly PaymentStep = UnifiedUpgradeDialogStep.Payment;
protected readonly PlanSelectionStep = UnifiedUpgradeDialogStep.PlanSelection;
@@ -152,8 +96,6 @@ export class UnifiedUpgradeDialogComponent implements OnInit {
@Inject(DIALOG_DATA) private params: UnifiedUpgradeDialogParams,
private router: Router,
private premiumInterestStateService: PremiumInterestStateService,
- private billingAccountProfileStateService: BillingAccountProfileStateService,
- private configService: ConfigService,
) {}
async ngOnInit(): Promise {
@@ -179,9 +121,7 @@ export class UnifiedUpgradeDialogComponent implements OnInit {
}
}
- protected onPlanSelected(
- planId: PersonalSubscriptionPricingTierId | BusinessSubscriptionPricingTierId,
- ): void {
+ protected onPlanSelected(planId: PersonalSubscriptionPricingTierId): void {
this.selectedPlan.set(planId);
this.nextStep();
}
@@ -210,17 +150,9 @@ export class UnifiedUpgradeDialogComponent implements OnInit {
}
}
- protected async onComplete(
- result: UpgradePaymentResult | PremiumOrgUpgradePaymentResult,
- ): Promise {
+ protected async onComplete(result: UpgradePaymentResult): Promise {
let status: UnifiedUpgradeDialogStatus;
switch (result.status) {
- case "upgradedToTeams":
- status = UnifiedUpgradeDialogStatus.UpgradedToTeams;
- break;
- case "upgradedToEnterprise":
- status = UnifiedUpgradeDialogStatus.UpgradedToEnterprise;
- break;
case "upgradedToPremium":
status = UnifiedUpgradeDialogStatus.UpgradedToPremium;
break;
@@ -248,14 +180,10 @@ export class UnifiedUpgradeDialogComponent implements OnInit {
if (
this.params.redirectOnCompletion &&
(status === UnifiedUpgradeDialogStatus.UpgradedToPremium ||
- status === UnifiedUpgradeDialogStatus.UpgradedToFamilies ||
- status === UnifiedUpgradeDialogStatus.UpgradedToEnterprise ||
- status === UnifiedUpgradeDialogStatus.UpgradedToTeams)
+ status === UnifiedUpgradeDialogStatus.UpgradedToFamilies)
) {
const redirectUrl =
- status === UnifiedUpgradeDialogStatus.UpgradedToFamilies ||
- status === UnifiedUpgradeDialogStatus.UpgradedToEnterprise ||
- status === UnifiedUpgradeDialogStatus.UpgradedToTeams
+ status === UnifiedUpgradeDialogStatus.UpgradedToFamilies
? `/organizations/${result.organizationId}/vault`
: "/settings/subscription/user-subscription";
await this.router.navigate([redirectUrl]);