diff --git a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-dialog/premium-org-upgrade-dialog.component.html b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-dialog/premium-org-upgrade-dialog.component.html
new file mode 100644
index 00000000000..69ff7a6e5d4
--- /dev/null
+++ b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-dialog/premium-org-upgrade-dialog.component.html
@@ -0,0 +1,14 @@
+
+@if (step() == PlanSelectionStep) {
+
+} @else if (step() == PaymentStep && selectedPlan() !== null && account() !== null) {
+
+}
diff --git a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-dialog/premium-org-upgrade-dialog.component.spec.ts b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-dialog/premium-org-upgrade-dialog.component.spec.ts
new file mode 100644
index 00000000000..a44f2eece7a
--- /dev/null
+++ b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-dialog/premium-org-upgrade-dialog.component.spec.ts
@@ -0,0 +1,449 @@
+import { ChangeDetectionStrategy, Component, input, output } from "@angular/core";
+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 { Account } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import {
+ BusinessSubscriptionPricingTierId,
+ PersonalSubscriptionPricingTierId,
+} 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 {
+ PremiumOrgUpgradeDialogComponent,
+ PremiumOrgUpgradeDialogParams,
+ PremiumOrgUpgradeDialogStep,
+} from "./premium-org-upgrade-dialog.component";
+
+@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<
+ BusinessSubscriptionPricingTierId | PersonalSubscriptionPricingTierId | null
+ >(null);
+ readonly account = input(null);
+ goBack = output();
+ complete = output<{ status: PremiumOrgUpgradePaymentStatus; organizationId: string | null }>();
+}
+
+describe("PremiumOrgUpgradeDialogComponent", () => {
+ let component: PremiumOrgUpgradeDialogComponent;
+ let fixture: ComponentFixture;
+ const mockDialogRef = mock();
+ const mockRouter = mock();
+ const mockBillingAccountProfileStateService = mock();
+ const mockConfigService = mock();
+ const mockAccount: Account = {
+ id: "user-id" as UserId,
+ ...mockAccountInfoWith({
+ email: "test@example.com",
+ name: "Test User",
+ }),
+ };
+
+ const defaultDialogData: PremiumOrgUpgradeDialogParams = {
+ account: mockAccount,
+ initialStep: null,
+ selectedPlan: null,
+ planSelectionStepTitleOverride: null,
+ };
+
+ /**
+ * Helper function to create and configure a fresh component instance with custom dialog data
+ */
+ async function createComponentWithDialogData(
+ dialogData: PremiumOrgUpgradeDialogParams,
+ waitForStable = false,
+ ): Promise<{
+ fixture: ComponentFixture;
+ component: PremiumOrgUpgradeDialogComponent;
+ }> {
+ TestBed.resetTestingModule();
+ jest.clearAllMocks();
+
+ await TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule, PremiumOrgUpgradeDialogComponent],
+ providers: [
+ { provide: DialogRef, useValue: mockDialogRef },
+ { provide: DIALOG_DATA, useValue: dialogData },
+ { provide: Router, useValue: mockRouter },
+ {
+ provide: BillingAccountProfileStateService,
+ useValue: mockBillingAccountProfileStateService,
+ },
+ { provide: ConfigService, useValue: mockConfigService },
+ ],
+ })
+ .overrideComponent(PremiumOrgUpgradeDialogComponent, {
+ remove: {
+ imports: [PremiumOrgUpgradeComponent, PremiumOrgUpgradePaymentComponent],
+ },
+ add: {
+ imports: [MockPremiumOrgUpgradeComponent, MockPremiumOrgUpgradePaymentComponent],
+ },
+ })
+ .compileComponents();
+
+ const newFixture = TestBed.createComponent(PremiumOrgUpgradeDialogComponent);
+ const newComponent = newFixture.componentInstance;
+ newFixture.detectChanges();
+
+ if (waitForStable) {
+ await newFixture.whenStable();
+ }
+
+ return { fixture: newFixture, component: newComponent };
+ }
+
+ beforeEach(async () => {
+ // Reset mocks
+ jest.clearAllMocks();
+
+ mockBillingAccountProfileStateService.hasPremiumPersonally$.mockReturnValue(of(true));
+ mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
+
+ await TestBed.configureTestingModule({
+ imports: [PremiumOrgUpgradeDialogComponent],
+ providers: [
+ { provide: DialogRef, useValue: mockDialogRef },
+ { provide: DIALOG_DATA, useValue: defaultDialogData },
+ { provide: Router, useValue: mockRouter },
+ {
+ provide: BillingAccountProfileStateService,
+ useValue: mockBillingAccountProfileStateService,
+ },
+ { provide: ConfigService, useValue: mockConfigService },
+ ],
+ })
+ .overrideComponent(PremiumOrgUpgradeDialogComponent, {
+ remove: {
+ imports: [PremiumOrgUpgradeComponent, PremiumOrgUpgradePaymentComponent],
+ },
+ add: {
+ imports: [MockPremiumOrgUpgradeComponent, MockPremiumOrgUpgradePaymentComponent],
+ },
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(PremiumOrgUpgradeDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("should initialize with default values", () => {
+ expect(component["step"]()).toBe(PremiumOrgUpgradeDialogStep.PlanSelection);
+ expect(component["selectedPlan"]()).toBeNull();
+ expect(component["account"]()).toEqual(mockAccount);
+ expect(component["planSelectionStepTitleOverride"]()).toBeNull();
+ });
+
+ it("should initialize with custom initial step", async () => {
+ const customDialogData: PremiumOrgUpgradeDialogParams = {
+ account: mockAccount,
+ initialStep: PremiumOrgUpgradeDialogStep.Payment,
+ selectedPlan: "teams" as BusinessSubscriptionPricingTierId,
+ };
+
+ const { component: customComponent } = await createComponentWithDialogData(customDialogData);
+
+ expect(customComponent["step"]()).toBe(PremiumOrgUpgradeDialogStep.Payment);
+ expect(customComponent["selectedPlan"]()).toBe("teams");
+ });
+
+ describe("custom dialog title", () => {
+ it("should use null as default when no override is provided", () => {
+ expect(component["planSelectionStepTitleOverride"]()).toBeNull();
+ });
+
+ it("should use custom title when provided in dialog config", async () => {
+ const customDialogData: PremiumOrgUpgradeDialogParams = {
+ account: mockAccount,
+ initialStep: PremiumOrgUpgradeDialogStep.PlanSelection,
+ selectedPlan: null,
+ planSelectionStepTitleOverride: "upgradeYourPlan",
+ };
+
+ const { component: customComponent } = await createComponentWithDialogData(customDialogData);
+
+ expect(customComponent["planSelectionStepTitleOverride"]()).toBe("upgradeYourPlan");
+ });
+ });
+
+ describe("onPlanSelected", () => {
+ it("should set selected plan and move to payment step", () => {
+ component["onPlanSelected"]("teams" as BusinessSubscriptionPricingTierId);
+
+ expect(component["selectedPlan"]()).toBe("teams");
+ expect(component["step"]()).toBe(PremiumOrgUpgradeDialogStep.Payment);
+ });
+
+ it("should handle selecting Enterprise plan", () => {
+ component["onPlanSelected"]("enterprise" as BusinessSubscriptionPricingTierId);
+
+ expect(component["selectedPlan"]()).toBe("enterprise");
+ expect(component["step"]()).toBe(PremiumOrgUpgradeDialogStep.Payment);
+ });
+ });
+
+ describe("previousStep", () => {
+ it("should go back to plan selection and clear selected plan", async () => {
+ component["step"].set(PremiumOrgUpgradeDialogStep.Payment);
+ component["selectedPlan"].set("teams" as BusinessSubscriptionPricingTierId);
+
+ await component["previousStep"]();
+
+ expect(component["step"]()).toBe(PremiumOrgUpgradeDialogStep.PlanSelection);
+ expect(component["selectedPlan"]()).toBeNull();
+ });
+
+ it("should close dialog when backing out from initial step", async () => {
+ const customDialogData: PremiumOrgUpgradeDialogParams = {
+ account: mockAccount,
+ initialStep: PremiumOrgUpgradeDialogStep.Payment,
+ selectedPlan: "teams" as BusinessSubscriptionPricingTierId,
+ };
+
+ const { component: customComponent } = await createComponentWithDialogData(customDialogData);
+
+ await customComponent["previousStep"]();
+
+ expect(mockDialogRef.close).toHaveBeenCalledWith({ status: "closed" });
+ });
+ });
+
+ describe("onComplete", () => {
+ it("should handle completing upgrade to Families successfully", async () => {
+ const { component: testComponent } = await createComponentWithDialogData(defaultDialogData);
+ mockRouter.navigate.mockResolvedValue(true);
+
+ const result = {
+ status: "upgradedToFamilies" as const,
+ organizationId: "org-111",
+ };
+
+ await testComponent["onComplete"](result);
+
+ expect(mockDialogRef.close).toHaveBeenCalledWith({
+ status: "upgradedToFamilies",
+ organizationId: "org-111",
+ });
+ });
+
+ it("should handle completing upgrade to Teams successfully", async () => {
+ const { component: testComponent } = await createComponentWithDialogData(defaultDialogData);
+ mockRouter.navigate.mockResolvedValue(true);
+
+ const result = {
+ status: "upgradedToTeams" as const,
+ organizationId: "org-123",
+ };
+
+ await testComponent["onComplete"](result);
+
+ expect(mockDialogRef.close).toHaveBeenCalledWith({
+ status: "upgradedToTeams",
+ organizationId: "org-123",
+ });
+ });
+
+ it("should handle completing upgrade to Enterprise successfully", async () => {
+ const { component: testComponent } = await createComponentWithDialogData(defaultDialogData);
+ mockRouter.navigate.mockResolvedValue(true);
+
+ const result = {
+ status: "upgradedToEnterprise" as const,
+ organizationId: "org-456",
+ };
+
+ await testComponent["onComplete"](result);
+
+ expect(mockDialogRef.close).toHaveBeenCalledWith({
+ status: "upgradedToEnterprise",
+ organizationId: "org-456",
+ });
+ });
+
+ it("should redirect to organization vault after Teams upgrade when redirectOnCompletion is true", async () => {
+ const customDialogData: PremiumOrgUpgradeDialogParams = {
+ account: mockAccount,
+ redirectOnCompletion: true,
+ };
+
+ mockRouter.navigate.mockResolvedValue(true);
+
+ const { component: customComponent } = await createComponentWithDialogData(customDialogData);
+
+ const result = {
+ status: "upgradedToTeams" as const,
+ organizationId: "org-123",
+ };
+
+ await customComponent["onComplete"](result);
+
+ expect(mockRouter.navigate).toHaveBeenCalledWith(["/organizations/org-123/vault"]);
+ expect(mockDialogRef.close).toHaveBeenCalledWith({
+ status: "upgradedToTeams",
+ organizationId: "org-123",
+ });
+ });
+
+ it("should redirect to organization vault after Enterprise upgrade when redirectOnCompletion is true", async () => {
+ const customDialogData: PremiumOrgUpgradeDialogParams = {
+ account: mockAccount,
+ redirectOnCompletion: true,
+ };
+
+ mockRouter.navigate.mockResolvedValue(true);
+
+ const { component: customComponent } = await createComponentWithDialogData(customDialogData);
+
+ const result = {
+ status: "upgradedToEnterprise" as const,
+ organizationId: "org-789",
+ };
+
+ await customComponent["onComplete"](result);
+
+ expect(mockRouter.navigate).toHaveBeenCalledWith(["/organizations/org-789/vault"]);
+ expect(mockDialogRef.close).toHaveBeenCalledWith({
+ status: "upgradedToEnterprise",
+ organizationId: "org-789",
+ });
+ });
+
+ it("should redirect to organization vault after Families upgrade when redirectOnCompletion is true", async () => {
+ const customDialogData: PremiumOrgUpgradeDialogParams = {
+ account: mockAccount,
+ redirectOnCompletion: true,
+ };
+
+ mockRouter.navigate.mockResolvedValue(true);
+
+ const { component: customComponent } = await createComponentWithDialogData(customDialogData);
+
+ const result = {
+ status: "upgradedToFamilies" as const,
+ organizationId: "org-999",
+ };
+
+ await customComponent["onComplete"](result);
+
+ expect(mockRouter.navigate).toHaveBeenCalledWith(["/organizations/org-999/vault"]);
+ expect(mockDialogRef.close).toHaveBeenCalledWith({
+ status: "upgradedToFamilies",
+ organizationId: "org-999",
+ });
+ });
+
+ it("should not redirect when redirectOnCompletion is false", async () => {
+ const customDialogData: PremiumOrgUpgradeDialogParams = {
+ account: mockAccount,
+ redirectOnCompletion: false,
+ };
+
+ const { component: customComponent } = await createComponentWithDialogData(customDialogData);
+
+ const result = {
+ status: "upgradedToTeams" as const,
+ organizationId: "org-123",
+ };
+
+ await customComponent["onComplete"](result);
+
+ expect(mockRouter.navigate).not.toHaveBeenCalled();
+ expect(mockDialogRef.close).toHaveBeenCalledWith({
+ status: "upgradedToTeams",
+ organizationId: "org-123",
+ });
+ });
+
+ it("should handle closed status", async () => {
+ const { component: testComponent } = await createComponentWithDialogData(defaultDialogData);
+
+ const result: PremiumOrgUpgradePaymentResult = { status: "closed", organizationId: null };
+
+ await testComponent["onComplete"](result);
+
+ expect(mockDialogRef.close).toHaveBeenCalledWith({
+ status: "closed",
+ organizationId: null,
+ });
+ });
+ });
+
+ describe("onCloseClicked", () => {
+ it("should close dialog", async () => {
+ await component["onCloseClicked"]();
+
+ expect(mockDialogRef.close).toHaveBeenCalledWith({ status: "closed" });
+ });
+ });
+
+ describe("Child Component Display Logic", () => {
+ describe("Plan Selection Step", () => {
+ it("should display app-premium-org-upgrade on plan selection step", async () => {
+ const { fixture } = await createComponentWithDialogData(defaultDialogData);
+
+ const premiumOrgUpgradeElement =
+ fixture.nativeElement.querySelector("app-premium-org-upgrade");
+
+ expect(premiumOrgUpgradeElement).toBeTruthy();
+ });
+ });
+
+ describe("Payment Step", () => {
+ it("should display app-premium-org-upgrade-payment on payment step", async () => {
+ const customDialogData: PremiumOrgUpgradeDialogParams = {
+ account: mockAccount,
+ initialStep: PremiumOrgUpgradeDialogStep.Payment,
+ selectedPlan: "teams" as BusinessSubscriptionPricingTierId,
+ };
+
+ const { fixture } = await createComponentWithDialogData(customDialogData);
+
+ const premiumOrgUpgradePaymentElement = fixture.nativeElement.querySelector(
+ "app-premium-org-upgrade-payment",
+ );
+
+ expect(premiumOrgUpgradePaymentElement).toBeTruthy();
+ });
+ });
+ });
+});
diff --git a/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-dialog/premium-org-upgrade-dialog.component.ts b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-dialog/premium-org-upgrade-dialog.component.ts
new file mode 100644
index 00000000000..0026700b167
--- /dev/null
+++ b/apps/web/src/app/billing/individual/upgrade/premium-org-upgrade-dialog/premium-org-upgrade-dialog.component.ts
@@ -0,0 +1,210 @@
+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 { Router } from "@angular/router";
+
+import { Account } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import {
+ BusinessSubscriptionPricingTierId,
+ PersonalSubscriptionPricingTierId,
+} 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 { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
+import {
+ ButtonModule,
+ DialogConfig,
+ DialogModule,
+ DialogRef,
+ DialogService,
+} from "@bitwarden/components";
+
+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 { UpgradePaymentService } from "../upgrade-payment/services/upgrade-payment.service";
+
+export const PremiumOrgUpgradeDialogStatus = {
+ Closed: "closed",
+ UpgradedToFamilies: "upgradedToFamilies",
+ UpgradedToTeams: "upgradedToTeams",
+ UpgradedToEnterprise: "upgradedToEnterprise",
+} as const;
+
+export const PremiumOrgUpgradeDialogStep = {
+ PlanSelection: "planSelection",
+ Payment: "payment",
+} as const;
+
+export type PremiumOrgUpgradeDialogStatus = UnionOfValues;
+export type PremiumOrgUpgradeDialogStep = UnionOfValues;
+
+export type PremiumOrgUpgradeDialogResult = {
+ status: PremiumOrgUpgradeDialogStatus;
+ organizationId?: string | null;
+};
+
+/**
+ * Parameters for the PremiumOrgUpgradeDialog component.
+ * In order to open the dialog to a specific step, you must provide the `initialStep` parameter and a `selectedPlan` if the step is `Payment`.
+ *
+ * @property {Account} account - The user account information.
+ * @property {PremiumOrgUpgradeDialogStep | null} PersonalSubscriptionPricingTierId | null} [selectedPlan] - Pre-selected subscription plan (Families, Teams, or Enterprise)y.
+ * @property {BusinessSubscriptionPricingTierId | null} [selectedPlan] - Pre-selected subscription plan, if any.
+ * @property {string | null} [dialogTitleMessageOverride] - Optional custom i18n key to override the default dialog title.
+ * @property {boolean} [hideContinueWithoutUpgradingButton] - Whether to hide the "Continue without upgrading" button.
+ * @property {boolean} [redirectOnCompletion] - Whether to redirect after successful upgrade to organization vault.
+ */
+export type PremiumOrgUpgradeDialogParams = {
+ account: Account;
+ initialStep?: PremiumOrgUpgradeDialogStep | null;
+ selectedPlan?: BusinessSubscriptionPricingTierId | PersonalSubscriptionPricingTierId | null;
+ redirectOnCompletion?: boolean;
+};
+
+@Component({
+ selector: "app-premium-org-upgrade-dialog",
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ CommonModule,
+ DialogModule,
+ ButtonModule,
+ BillingServicesModule,
+ PremiumOrgUpgradeComponent,
+ PremiumOrgUpgradePaymentComponent,
+ ],
+ providers: [UpgradePaymentService, AccountBillingClient, PreviewInvoiceClient],
+ templateUrl: "./premium-org-upgrade-dialog.component.html",
+})
+export class PremiumOrgUpgradeDialogComponent implements OnInit {
+ // Use signals for dialog state because inputs depend on parent component
+ protected readonly step = signal(
+ PremiumOrgUpgradeDialogStep.PlanSelection,
+ );
+ protected readonly selectedPlan = signal<
+ BusinessSubscriptionPricingTierId | PersonalSubscriptionPricingTierId | null
+ >(null);
+ protected readonly account = signal(null);
+ 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 isPremiumOrgUpgradeEnabled = computed(
+ () => this.hasPremiumPersonally() && this.premiumToOrganizationUpgradeEnabled(),
+ );
+
+ protected readonly PaymentStep = PremiumOrgUpgradeDialogStep.Payment;
+ protected readonly PlanSelectionStep = PremiumOrgUpgradeDialogStep.PlanSelection;
+
+ constructor(
+ private dialogRef: DialogRef,
+ @Inject(DIALOG_DATA) private params: PremiumOrgUpgradeDialogParams,
+ private router: Router,
+ private billingAccountProfileStateService: BillingAccountProfileStateService,
+ private configService: ConfigService,
+ ) {}
+
+ async ngOnInit(): Promise {
+ this.account.set(this.params.account);
+ this.step.set(this.params.initialStep ?? PremiumOrgUpgradeDialogStep.PlanSelection);
+ this.selectedPlan.set(this.params.selectedPlan ?? null);
+ }
+
+ protected onPlanSelected(
+ planId: BusinessSubscriptionPricingTierId | PersonalSubscriptionPricingTierId,
+ ): void {
+ this.selectedPlan.set(planId);
+ this.nextStep();
+ }
+
+ protected async onCloseClicked(): Promise {
+ this.close({ status: PremiumOrgUpgradeDialogStatus.Closed });
+ }
+
+ private close(result: PremiumOrgUpgradeDialogResult): void {
+ this.dialogRef.close(result);
+ }
+
+ protected nextStep() {
+ if (this.step() === PremiumOrgUpgradeDialogStep.PlanSelection) {
+ this.step.set(PremiumOrgUpgradeDialogStep.Payment);
+ }
+ }
+
+ protected async previousStep(): Promise {
+ // If we are on the payment step and there was no initial step, go back to plan selection this is to prevent
+ // going back to payment step if the dialog was opened directly to payment step
+ if (this.step() === PremiumOrgUpgradeDialogStep.Payment && this.params?.initialStep == null) {
+ this.step.set(PremiumOrgUpgradeDialogStep.PlanSelection);
+ this.selectedPlan.set(null);
+ } else {
+ this.close({ status: PremiumOrgUpgradeDialogStatus.Closed });
+ }
+ }
+
+ protected async onComplete(result: PremiumOrgUpgradePaymentResult): Promise {
+ let status: PremiumOrgUpgradeDialogStatus;
+ switch (result.status) {
+ case "upgradedToFamilies":
+ status = PremiumOrgUpgradeDialogStatus.UpgradedToFamilies;
+ break;
+ case "upgradedToTeams":
+ status = PremiumOrgUpgradeDialogStatus.UpgradedToTeams;
+ break;
+ case "upgradedToEnterprise":
+ status = PremiumOrgUpgradeDialogStatus.UpgradedToEnterprise;
+ break;
+ case "closed":
+ status = PremiumOrgUpgradeDialogStatus.Closed;
+ break;
+ default:
+ status = PremiumOrgUpgradeDialogStatus.Closed;
+ }
+
+ this.close({ status, organizationId: result.organizationId });
+
+ // Redirect to organization vault after successful upgrade
+ if (
+ this.params.redirectOnCompletion &&
+ (status === PremiumOrgUpgradeDialogStatus.UpgradedToFamilies ||
+ status === PremiumOrgUpgradeDialogStatus.UpgradedToEnterprise ||
+ status === PremiumOrgUpgradeDialogStatus.UpgradedToTeams)
+ ) {
+ const redirectUrl = `/organizations/${result.organizationId}/vault`;
+ await this.router.navigate([redirectUrl]);
+ }
+ }
+
+ /**
+ * Opens the premium org upgrade dialog.
+ *
+ * @param dialogService - The dialog service used to open the component
+ * @param dialogConfig - The configuration for the dialog including PremiumOrgUpgradeDialogParams data
+ * @returns A dialog reference object of type DialogRef
+ */
+ static open(
+ dialogService: DialogService,
+ dialogConfig: DialogConfig,
+ ): DialogRef {
+ return dialogService.open(PremiumOrgUpgradeDialogComponent, {
+ data: dialogConfig.data,
+ });
+ }
+}