mirror of
https://github.com/bitwarden/browser
synced 2026-02-11 14:04:03 +00:00
fix(billing): Rename and upgrade upgrade account component
This commit is contained in:
@@ -1,50 +0,0 @@
|
||||
<bit-dialog dialogSize="large" [loading]="loading">
|
||||
<div bitDialogContent>
|
||||
<header class="tw-flex tw-text-center tw-flex-col !tw-my-0 !tw-pb-0">
|
||||
<h1 class="tw-font-semibold">{{ "individualUpgradeWelcomeMessage" | i18n }}</h1>
|
||||
<p bitTypography="body1" class="tw-text-muted tw-mb-8">
|
||||
{{ "individualUpgradeDescriptionMessage" | i18n }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="tw-flex tw-flex-col lg:tw-flex-row tw-gap-6 tw-mb-4 tw-mx-8">
|
||||
@if (premiumCardDetails) {
|
||||
<billing-pricing-card
|
||||
class="tw-flex-1 tw-basis-0"
|
||||
[tagline]="premiumCardDetails.tagline"
|
||||
[price]="premiumCardDetails.price"
|
||||
[button]="premiumCardDetails.button"
|
||||
[features]="premiumCardDetails.features"
|
||||
(buttonClick)="onProceedClick(premiumPlanType)"
|
||||
>
|
||||
<h3 slot="title" class="tw-m-0" bitTypography="h3">
|
||||
{{ premiumCardDetails.title }}
|
||||
</h3>
|
||||
</billing-pricing-card>
|
||||
}
|
||||
|
||||
@if (familiesCardDetails) {
|
||||
<billing-pricing-card
|
||||
class="tw-flex-1 tw-basis-0"
|
||||
[tagline]="familiesCardDetails.tagline"
|
||||
[price]="familiesCardDetails.price"
|
||||
[button]="familiesCardDetails.button"
|
||||
[features]="familiesCardDetails.features"
|
||||
(buttonClick)="onProceedClick(familiesPlanType)"
|
||||
>
|
||||
<h3 slot="title" class="tw-m-0" bitTypography="h3">
|
||||
{{ familiesCardDetails.title }}
|
||||
</h3>
|
||||
</billing-pricing-card>
|
||||
}
|
||||
</div>
|
||||
<div class="tw-text-center tw-w-full">
|
||||
<p bitTypography="helper" class="tw-text-muted tw-italic">
|
||||
{{ "individualUpgradeTaxInformationMessage" | i18n }}
|
||||
</p>
|
||||
<button bitLink linkType="primary" type="button" (click)="onCloseClick()">
|
||||
{{ "continueWithoutUpgrading" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</bit-dialog>
|
||||
@@ -0,0 +1,68 @@
|
||||
@if (!loading()) {
|
||||
<section
|
||||
class="tw-bg-background tw-rounded-xl tw-shadow-lg tw-max-w-4xl tw-w-[870px]"
|
||||
cdkTrapFocus
|
||||
cdkTrapFocusAutoCapture
|
||||
>
|
||||
<div class="tw-pt-4 tw-px-14 tw-pb-20">
|
||||
<header class="tw-flex tw-items-center tw-justify-end">
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-close"
|
||||
buttonType="main"
|
||||
size="default"
|
||||
[label]="'close' | i18n"
|
||||
(click)="closeClicked.emit(closeStatus)"
|
||||
></button>
|
||||
</header>
|
||||
<div class="tw-flex tw-text-center tw-flex-col">
|
||||
<h1 bitTypography="h1" class="tw-font-semibold">
|
||||
{{ "individualUpgradeWelcomeMessage" | i18n }}
|
||||
</h1>
|
||||
<p bitTypography="body1" class="tw-text-muted tw-mb-8">
|
||||
{{ "individualUpgradeDescriptionMessage" | i18n }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-flex-row tw-gap-6 tw-mb-4">
|
||||
@if (premiumCardDetails) {
|
||||
<billing-pricing-card
|
||||
class="tw-flex-1 tw-basis-0 tw-min-w-0"
|
||||
[tagline]="premiumCardDetails.tagline"
|
||||
[price]="premiumCardDetails.price"
|
||||
[button]="premiumCardDetails.button"
|
||||
[features]="premiumCardDetails.features"
|
||||
(buttonClick)="planSelected.emit(premiumPlanType)"
|
||||
>
|
||||
<h3 slot="title" class="tw-m-0" bitTypography="h3">
|
||||
{{ premiumCardDetails.title }}
|
||||
</h3>
|
||||
</billing-pricing-card>
|
||||
}
|
||||
|
||||
@if (familiesCardDetails) {
|
||||
<billing-pricing-card
|
||||
class="tw-flex-1 tw-basis-0 tw-min-w-0"
|
||||
[tagline]="familiesCardDetails.tagline"
|
||||
[price]="familiesCardDetails.price"
|
||||
[button]="familiesCardDetails.button"
|
||||
[features]="familiesCardDetails.features"
|
||||
(buttonClick)="planSelected.emit(familiesPlanType)"
|
||||
>
|
||||
<h3 slot="title" class="tw-m-0" bitTypography="h3">
|
||||
{{ familiesCardDetails.title }}
|
||||
</h3>
|
||||
</billing-pricing-card>
|
||||
}
|
||||
</div>
|
||||
<div class="tw-text-center tw-w-full">
|
||||
<p bitTypography="helper" class="tw-text-muted tw-italic">
|
||||
{{ "individualUpgradeTaxInformationMessage" | i18n }}
|
||||
</p>
|
||||
<button bitLink linkType="primary" type="button" (click)="closeClicked.emit(closeStatus)">
|
||||
{{ "continueWithoutUpgrading" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { CdkTrapFocus } from "@angular/cdk/a11y";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogRef, DialogService } from "@bitwarden/components";
|
||||
import { PricingCardComponent } from "@bitwarden/pricing";
|
||||
|
||||
import { BillingServicesModule } from "../../../services";
|
||||
@@ -14,19 +14,13 @@ import {
|
||||
PersonalSubscriptionPricingTierIds,
|
||||
} from "../../../types/subscription-pricing-tier";
|
||||
|
||||
import {
|
||||
UpgradeAccountDialogComponent,
|
||||
UpgradeAccountDialogResult,
|
||||
UpgradeAccountDialogStatus,
|
||||
} from "./upgrade-account-dialog.component";
|
||||
import { UpgradeAccountComponent, UpgradeAccountStatus } from "./upgrade-account.component";
|
||||
|
||||
describe("UpgradeAccountDialogComponent", () => {
|
||||
let sut: UpgradeAccountDialogComponent;
|
||||
let fixture: ComponentFixture<UpgradeAccountDialogComponent>;
|
||||
const mockDialogRef = mock<DialogRef<UpgradeAccountDialogResult>>();
|
||||
describe("UpgradeAccountComponent", () => {
|
||||
let sut: UpgradeAccountComponent;
|
||||
let fixture: ComponentFixture<UpgradeAccountComponent>;
|
||||
const mockI18nService = mock<I18nService>();
|
||||
const mockSubscriptionPricingService = mock<SubscriptionPricingService>();
|
||||
const mockDialogService = mock<DialogService>();
|
||||
|
||||
// Mock pricing tiers data
|
||||
const mockPricingTiers: PersonalSubscriptionPricingTier[] = [
|
||||
@@ -60,20 +54,19 @@ describe("UpgradeAccountDialogComponent", () => {
|
||||
);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule, UpgradeAccountDialogComponent, PricingCardComponent],
|
||||
imports: [NoopAnimationsModule, UpgradeAccountComponent, PricingCardComponent, CdkTrapFocus],
|
||||
providers: [
|
||||
{ provide: DialogRef, useValue: mockDialogRef },
|
||||
{ provide: I18nService, useValue: mockI18nService },
|
||||
{ provide: SubscriptionPricingService, useValue: mockSubscriptionPricingService },
|
||||
],
|
||||
})
|
||||
.overrideComponent(UpgradeAccountDialogComponent, {
|
||||
.overrideComponent(UpgradeAccountComponent, {
|
||||
// Remove BillingServicesModule to avoid conflicts with mocking SubscriptionPricingService dependencies
|
||||
remove: { imports: [BillingServicesModule] },
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UpgradeAccountDialogComponent);
|
||||
fixture = TestBed.createComponent(UpgradeAccountComponent);
|
||||
sut = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@@ -109,40 +102,37 @@ describe("UpgradeAccountDialogComponent", () => {
|
||||
expect(sut["familiesCardDetails"].features).toEqual(["Feature A", "Feature B", "Feature C"]);
|
||||
});
|
||||
|
||||
it("should call dialogRef.close with proceeded-to-payment status and premium pricing tier when premium plan is selected", () => {
|
||||
sut["onProceedClick"](PersonalSubscriptionPricingTierIds.Premium);
|
||||
it("should emit planSelected with premium pricing tier when premium plan is selected", () => {
|
||||
// Arrange
|
||||
const emitSpy = jest.spyOn(sut.planSelected, "emit");
|
||||
|
||||
expect(mockDialogRef.close).toHaveBeenCalledWith({
|
||||
status: UpgradeAccountDialogStatus.ProceededToPayment,
|
||||
plan: PersonalSubscriptionPricingTierIds.Premium,
|
||||
});
|
||||
// Act
|
||||
sut.planSelected.emit(PersonalSubscriptionPricingTierIds.Premium);
|
||||
|
||||
// Assert
|
||||
expect(emitSpy).toHaveBeenCalledWith(PersonalSubscriptionPricingTierIds.Premium);
|
||||
});
|
||||
|
||||
it("should call dialogRef.close with proceeded-to-payment status and families pricing tier when families plan is selected", () => {
|
||||
sut["onProceedClick"](PersonalSubscriptionPricingTierIds.Families);
|
||||
it("should emit planSelected with families pricing tier when families plan is selected", () => {
|
||||
// Arrange
|
||||
const emitSpy = jest.spyOn(sut.planSelected, "emit");
|
||||
|
||||
expect(mockDialogRef.close).toHaveBeenCalledWith({
|
||||
status: UpgradeAccountDialogStatus.ProceededToPayment,
|
||||
plan: PersonalSubscriptionPricingTierIds.Families,
|
||||
});
|
||||
// Act
|
||||
sut.planSelected.emit(PersonalSubscriptionPricingTierIds.Families);
|
||||
|
||||
// Assert
|
||||
expect(emitSpy).toHaveBeenCalledWith(PersonalSubscriptionPricingTierIds.Families);
|
||||
});
|
||||
|
||||
it("should call dialogRef.close with closed status when dialog is closed", () => {
|
||||
sut["onCloseClick"]();
|
||||
it("should emit closeClicked with closed status when close button is clicked", () => {
|
||||
// Arrange
|
||||
const emitSpy = jest.spyOn(sut.closeClicked, "emit");
|
||||
|
||||
expect(mockDialogRef.close).toHaveBeenCalledWith({
|
||||
status: UpgradeAccountDialogStatus.Closed,
|
||||
plan: null,
|
||||
});
|
||||
});
|
||||
// Act
|
||||
sut.closeClicked.emit(UpgradeAccountStatus.Closed);
|
||||
|
||||
it("should return a DialogRef when open static method is called", () => {
|
||||
mockDialogService.open.mockReturnValue(mockDialogRef);
|
||||
|
||||
const result = UpgradeAccountDialogComponent.open(mockDialogService);
|
||||
|
||||
expect(mockDialogService.open).toHaveBeenCalledWith(UpgradeAccountDialogComponent);
|
||||
expect(result).toBe(mockDialogRef);
|
||||
// Assert
|
||||
expect(emitSpy).toHaveBeenCalledWith(UpgradeAccountStatus.Closed);
|
||||
});
|
||||
|
||||
describe("isFamiliesPlan", () => {
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Component, DestroyRef, OnInit } from "@angular/core";
|
||||
import { CdkTrapFocus } from "@angular/cdk/a11y";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, DestroyRef, OnInit, output, signal } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
|
||||
import { ButtonType, DialogModule, DialogRef, DialogService } from "@bitwarden/components";
|
||||
import { ButtonType, DialogModule } from "@bitwarden/components";
|
||||
import { PricingCardComponent } from "@bitwarden/pricing";
|
||||
|
||||
import { SharedModule } from "../../../../shared";
|
||||
@@ -17,15 +19,15 @@ import {
|
||||
SubscriptionCadenceIds,
|
||||
} from "../../../types/subscription-pricing-tier";
|
||||
|
||||
export const UpgradeAccountDialogStatus = {
|
||||
export const UpgradeAccountStatus = {
|
||||
Closed: "closed",
|
||||
ProceededToPayment: "proceeded-to-payment",
|
||||
} as const;
|
||||
|
||||
export type UpgradeAccountDialogStatus = UnionOfValues<typeof UpgradeAccountDialogStatus>;
|
||||
export type UpgradeAccountStatus = UnionOfValues<typeof UpgradeAccountStatus>;
|
||||
|
||||
export type UpgradeAccountDialogResult = {
|
||||
status: UpgradeAccountDialogStatus;
|
||||
export type UpgradeAccountResult = {
|
||||
status: UpgradeAccountStatus;
|
||||
plan: PersonalSubscriptionPricingTierId | null;
|
||||
};
|
||||
|
||||
@@ -38,20 +40,29 @@ type CardDetails = {
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: "app-upgrade-account-dialog",
|
||||
imports: [DialogModule, SharedModule, BillingServicesModule, PricingCardComponent],
|
||||
templateUrl: "./upgrade-account-dialog.component.html",
|
||||
selector: "app-upgrade-account",
|
||||
imports: [
|
||||
CommonModule,
|
||||
DialogModule,
|
||||
SharedModule,
|
||||
BillingServicesModule,
|
||||
PricingCardComponent,
|
||||
CdkTrapFocus,
|
||||
],
|
||||
templateUrl: "./upgrade-account.component.html",
|
||||
})
|
||||
export class UpgradeAccountDialogComponent implements OnInit {
|
||||
export class UpgradeAccountComponent implements OnInit {
|
||||
planSelected = output<PersonalSubscriptionPricingTierId>();
|
||||
closeClicked = output<UpgradeAccountStatus>();
|
||||
protected loading = signal(true);
|
||||
protected premiumCardDetails!: CardDetails;
|
||||
protected familiesCardDetails!: CardDetails;
|
||||
|
||||
protected familiesPlanType = PersonalSubscriptionPricingTierIds.Families;
|
||||
protected premiumPlanType = PersonalSubscriptionPricingTierIds.Premium;
|
||||
protected loading = true;
|
||||
protected closeStatus = UpgradeAccountStatus.Closed;
|
||||
|
||||
constructor(
|
||||
private dialogRef: DialogRef<UpgradeAccountDialogResult>,
|
||||
private i18nService: I18nService,
|
||||
private subscriptionPricingService: SubscriptionPricingService,
|
||||
private destroyRef: DestroyRef,
|
||||
@@ -63,7 +74,7 @@ export class UpgradeAccountDialogComponent implements OnInit {
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((plans) => {
|
||||
this.setupCardDetails(plans);
|
||||
this.loading = false;
|
||||
this.loading.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -108,29 +119,7 @@ export class UpgradeAccountDialogComponent implements OnInit {
|
||||
};
|
||||
}
|
||||
|
||||
protected onProceedClick(plan: PersonalSubscriptionPricingTierId): void {
|
||||
this.close({
|
||||
status: UpgradeAccountDialogStatus.ProceededToPayment,
|
||||
plan,
|
||||
});
|
||||
}
|
||||
|
||||
private isFamiliesPlan(plan: PersonalSubscriptionPricingTierId): boolean {
|
||||
return plan === PersonalSubscriptionPricingTierIds.Families;
|
||||
}
|
||||
|
||||
protected onCloseClick(): void {
|
||||
this.close({
|
||||
status: UpgradeAccountDialogStatus.Closed,
|
||||
plan: null,
|
||||
});
|
||||
}
|
||||
|
||||
private close(result: UpgradeAccountDialogResult): void {
|
||||
this.dialogRef.close(result);
|
||||
}
|
||||
|
||||
static open(dialogService: DialogService): DialogRef<UpgradeAccountDialogResult> {
|
||||
return dialogService.open<UpgradeAccountDialogResult>(UpgradeAccountDialogComponent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user