1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-05 11:13:44 +00:00

Refactored changes for the pricing summary

This commit is contained in:
Cy Okeke
2025-07-28 12:12:56 +01:00
parent d55ce6424d
commit 689a7d9f37
3 changed files with 41 additions and 726 deletions

View File

@@ -365,621 +365,11 @@
(taxInformationChanged)="taxInformationChanged($event)"
></app-manage-tax-information>
</ng-container>
<div class="tw-mt-4">
<p class="tw-text-lg tw-mb-1">
<span class="tw-font-semibold"
>{{ "total" | i18n }}:
{{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }} USD</span
>
<span class="tw-text-xs tw-font-light"> / {{ selectedPlanInterval | i18n }}</span>
<button
(click)="toggleTotalOpened()"
type="button"
[bitIconButton]="totalOpened ? 'bwi-angle-down' : 'bwi-angle-up'"
size="small"
aria-hidden="true"
></button>
</p>
</div>
<!-- SM + PM and PM only cost summary -->
<div *ngIf="totalOpened && !isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Annually">
<p class="tw-font-semibold tw-mb-1" *ngIf="organization.useSecretsManager">
{{ "passwordManager" | i18n }}
</p>
<p
class="tw-mb-1 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.basePrice"
>
<span>
{{ passwordManagerSeats }}
{{ "members" | i18n }} &times;
{{
(selectedPlan.isAnnual
? selectedPlan.PasswordManager.basePrice / 12
: selectedPlan.PasswordManager.basePrice
) | currency: "$"
}}
/{{ selectedPlanInterval | i18n }}
</span>
<span>
<ng-container *ngIf="acceptingSponsorship; else notAcceptingSponsorship">
<span class="tw-line-through">{{
selectedPlan.PasswordManager.basePrice | currency: "$"
}}</span>
{{ "freeWithSponsorship" | i18n }}
</ng-container>
<ng-template #notAcceptingSponsorship>
{{ selectedPlan.PasswordManager.basePrice | currency: "$" }}
</ng-template>
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.hasAdditionalSeatsOption"
>
<span>
<span *ngIf="selectedPlan.PasswordManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span
>
{{ passwordManagerSeats || 0 }}&nbsp;
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.hasAdditionalStorageOption && storageGb > 0"
>
<span>
{{ storageGb }}
{{ "additionalStorageGbMessage" | i18n }}
&times;
{{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>{{ additionalStorageTotal(selectedPlan) | currency: "$" }}</span>
</p>
<!--Discount PM Annual-->
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="organization.useSecretsManager && !isSecretsManagerTrial()"
>
<ng-container
*ngIf="selectedInterval == planIntervals.Annually && discountPercentageFromSub > 0"
>
<span class="tw-text-xs">
{{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
</span>
<span class="tw-line-through tw-text-xs">{{
calculateTotalAppliedDiscount(
passwordManagerSeatTotal(selectedPlan) + additionalStorageTotal(selectedPlan)
) | currency: "$"
}}</span>
</ng-container>
</p>
<!-- secrets manager summary for annual -->
<p class="tw-font-semibold tw-mt-3 tw-mb-1" *ngIf="organization.useSecretsManager">
{{ "secretsManager" | i18n }}
</p>
<p
class="tw-mb-1 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan?.SecretsManager?.basePrice && organization.useSecretsManager"
>
<span>
{{ sub?.smSeats }}
{{ "members" | i18n }} &times;
{{
(selectedPlan.isAnnual
? selectedPlan.SecretsManager.basePrice / 12
: selectedPlan.SecretsManager.basePrice
) | currency: "$"
}}
/{{ selectedPlanInterval | i18n }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="
selectedPlan?.SecretsManager?.hasAdditionalSeatsOption &&
organization.useSecretsManager
"
>
<span>
<span *ngIf="selectedPlan.SecretsManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span
>
{{ sub?.smSeats || 0 }}&nbsp;
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ selectedPlan.SecretsManager.seatPrice | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>
{{ secretsManagerSeatTotal(selectedPlan, sub.smSeats) | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="
selectedPlan?.SecretsManager?.hasAdditionalServiceAccountOption &&
additionalServiceAccount > 0
"
>
<span>
{{ additionalServiceAccount }}
{{ "serviceAccounts" | i18n | lowercase }}
&times;
{{ selectedPlan?.SecretsManager?.additionalPricePerServiceAccount | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span>
</p>
<!--Discount SM annual-->
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="organization.useSecretsManager && !isSecretsManagerTrial()"
>
<ng-container
*ngIf="selectedInterval == planIntervals.Annually && discountPercentageFromSub > 0"
>
<span class="tw-text-xs">
{{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
</span>
<span class="tw-line-through tw-text-xs">{{
calculateTotalAppliedDiscount(
additionalServiceAccountTotal(selectedPlan) +
secretsManagerSeatTotal(selectedPlan, sub.smSeats)
) | currency: "$"
}}</span>
</ng-container>
</p>
</bit-hint>
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Monthly">
<p class="tw-font-semibold tw-mb-1" *ngIf="organization.useSecretsManager">
{{ "passwordManager" | i18n }}
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.basePrice"
>
<span>
{{ "basePrice" | i18n }}:
{{ selectedPlan.PasswordManager.basePrice | currency: "$" }}
{{ "monthAbbr" | i18n }}
</span>
<span>
{{ selectedPlan.PasswordManager.basePrice | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.hasAdditionalSeatsOption"
>
<span>
<span *ngIf="selectedPlan.PasswordManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span
>
{{ passwordManagerSeats }}&nbsp;
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.hasAdditionalStorageOption && storageGb > 0"
>
<span>
{{ storageGb }}
{{ "additionalStorageGbMessage" | i18n }}
&times;
{{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>{{
storageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$"
}}</span>
</p>
<!--Discount PM Monthly-->
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="organization.useSecretsManager && !isSecretsManagerTrial()"
>
<ng-container *ngIf="selectedInterval == planIntervals.Monthly">
<span
class="tw-text-xs"
[style.display]="discountPercentageFromSub > 0 ? 'block' : 'none'"
>
{{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
</span>
<span
[style.display]="discountPercentageFromSub > 0 ? 'block' : 'none'"
class="tw-line-through tw-text-xs"
>{{ calculateTotalAppliedDiscount(total) | currency: "$" }}</span
>
</ng-container>
</p>
<!-- secrets manager summary for monthly -->
<p class="tw-font-semibold tw-mt-3 tw-mb-1" *ngIf="organization.useSecretsManager">
{{ "secretsManager" | i18n }}
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.SecretsManager.basePrice && organization.useSecretsManager"
>
<span>
{{ "basePrice" | i18n }}:
{{ selectedPlan.SecretsManager.basePrice | currency: "$" }}
{{ "monthAbbr" | i18n }}
</span>
<span>
{{ selectedPlan.SecretsManager.basePrice | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="
selectedPlan.SecretsManager.hasAdditionalSeatsOption &&
organization.useSecretsManager
"
>
<span>
<span *ngIf="selectedPlan.SecretsManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span
>
{{ sub?.smSeats }}&nbsp;
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ selectedPlan.SecretsManager.seatPrice | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>
{{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="
selectedPlan.SecretsManager.hasAdditionalServiceAccountOption &&
additionalServiceAccount > 0
"
>
<span>
{{ additionalServiceAccount }}
{{ "serviceAccounts" | i18n | lowercase }}
&times;
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span>
</p>
<!--Discount SM Monthly-->
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="organization.useSecretsManager && !isSecretsManagerTrial()"
>
<ng-container *ngIf="selectedInterval == planIntervals.Monthly">
<span
class="tw-text-xs"
[style.display]="discountPercentageFromSub > 0 ? 'block' : 'none'"
>
{{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
</span>
<span
[style.display]="discountPercentageFromSub > 0 ? 'block' : 'none'"
class="tw-line-through tw-text-xs"
>{{
additionalServiceAccountTotal(selectedPlan) +
secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$"
}}</span
>
</ng-container>
</p>
</bit-hint>
</div>
<!-- SM + Free PM cost summary -->
<div *ngIf="totalOpened && isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Annually">
<!-- secrets manager summary for annual -->
<p class="tw-font-semibold tw-mt-2 tw-mb-0" *ngIf="organization.useSecretsManager">
{{ "secretsManager" | i18n }}
</p>
<p
class="tw-mb-1 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.SecretsManager.basePrice && organization.useSecretsManager"
>
<span>
{{ sub?.smSeats }}
{{ "members" | i18n }} &times;
{{
(selectedPlan.isAnnual
? selectedPlan.SecretsManager.basePrice / 12
: selectedPlan.SecretsManager.basePrice
) | currency: "$"
}}
/{{ selectedPlanInterval | i18n }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="
selectedPlan.SecretsManager.hasAdditionalSeatsOption &&
organization.useSecretsManager
"
>
<span>
<span *ngIf="selectedPlan.SecretsManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span
>
{{ sub?.smSeats || 0 }}&nbsp;
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ selectedPlan.SecretsManager.seatPrice | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>
{{ secretsManagerSeatTotal(selectedPlan, sub.smSeats) | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="
selectedPlan.SecretsManager.hasAdditionalServiceAccountOption &&
additionalServiceAccount > 0
"
>
<span>
{{ additionalServiceAccount }}
{{ "serviceAccounts" | i18n }}
&times;
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span>
</p>
<!-- password manager summary for annual -->
<p class="tw-font-semibold tw-mt-3 tw-mb-0" *ngIf="organization.useSecretsManager">
{{ "passwordManager" | i18n }}
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.basePrice"
>
<span>
{{ sub?.seats }}
{{ "members" | i18n }} &times;
{{
(selectedPlan.isAnnual
? selectedPlan.PasswordManager.basePrice / 12
: selectedPlan.PasswordManager.basePrice
) | currency: "$"
}}
/{{ selectedPlanInterval | i18n }}
</span>
<span>
<ng-container *ngIf="acceptingSponsorship; else notAcceptingSponsorship">
<span class="tw-line-through">{{
selectedPlan.PasswordManager.basePrice | currency: "$"
}}</span>
{{ "freeWithSponsorship" | i18n }}
</ng-container>
<ng-template #notAcceptingSponsorship>
{{ selectedPlan.PasswordManager.basePrice | currency: "$" }}
</ng-template>
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.hasAdditionalSeatsOption"
>
<span>
<span *ngIf="selectedPlan.PasswordManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span
>
{{ sub?.seats || 0 }}&nbsp;
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span *ngIf="isSecretsManagerTrial()">
{{ "freeForOneYear" | i18n }}
</span>
<span *ngIf="!isSecretsManagerTrial()">
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
</span>
</p>
</bit-hint>
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Monthly">
<!-- secrets manager summary for monthly -->
<p class="tw-font-semibold tw-mt-2 tw-mb-0" *ngIf="organization.useSecretsManager">
{{ "secretsManager" | i18n }}
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.SecretsManager.basePrice && organization.useSecretsManager"
>
<span>
{{ "basePrice" | i18n }}:
{{ selectedPlan.SecretsManager.basePrice | currency: "$" }}
{{ "monthAbbr" | i18n }}
</span>
<span>
{{ selectedPlan.SecretsManager.basePrice | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="
selectedPlan.SecretsManager.hasAdditionalSeatsOption &&
organization.useSecretsManager
"
>
<span>
<span *ngIf="selectedPlan.SecretsManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span
>
{{ sub?.smSeats }}&nbsp;
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ selectedPlan.SecretsManager.seatPrice | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>
{{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="
selectedPlan.SecretsManager.hasAdditionalServiceAccountOption &&
additionalServiceAccount > 0
"
>
<span>
{{ additionalServiceAccount }}
{{ "serviceAccounts" | i18n }}
&times;
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span>
</p>
<!-- password manager summary for monthly -->
<p class="tw-font-semibold tw-mt-3 tw-mb-0" *ngIf="organization.useSecretsManager">
{{ "passwordManager" | i18n }}
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.basePrice"
>
<span>
{{ "basePrice" | i18n }}:
{{ selectedPlan.PasswordManager.basePrice | currency: "$" }}
{{ "monthAbbr" | i18n }}
</span>
<span>
{{ selectedPlan.PasswordManager.basePrice | currency: "$" }}
</span>
</p>
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="selectedPlan.PasswordManager.hasAdditionalSeatsOption"
>
<span>
<span *ngIf="selectedPlan.PasswordManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span
>
{{ sub?.seats }}&nbsp;
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
/{{ selectedPlanInterval | i18n }}
</span>
<span *ngIf="isSecretsManagerTrial()">
{{ "freeForOneYear" | i18n }}
</span>
<span *ngIf="!isSecretsManagerTrial()">
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
</span>
</p>
</bit-hint>
</div>
<!-- discountPercentage to PM Only -->
<div
*ngIf="totalOpened && discountPercentage && !organization.useSecretsManager"
class="tw-flex tw-flex-wrap tw-gap-4"
>
<bit-hint class="tw-w-1/2">
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="discountPercentageFromSub > 0"
>
<ng-container>
<span class="tw-text-xs">
{{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
</span>
<span class="tw-line-through tw-text-xs">{{
calculateTotalAppliedDiscount(total) | currency: "$"
}}</span>
</ng-container>
</p>
</bit-hint>
</div>
<div *ngIf="totalOpened" class="tw-flex tw-flex-wrap tw-gap-4 tw-mt-4">
<bit-hint class="tw-w-1/2">
<p
class="tw-flex tw-justify-between tw-border-0 tw-border-solid tw-border-t tw-border-secondary-300 tw-pt-2 tw-mb-0"
>
<span class="tw-font-semibold">
{{ "estimatedTax" | i18n }}
</span>
<span>
{{ estimatedTax | currency: "USD" : "$" }}
</span>
</p>
</bit-hint>
</div>
<div *ngIf="totalOpened" class="tw-flex tw-flex-wrap tw-gap-4 tw-mt-4">
<bit-hint class="tw-w-1/2">
<p
class="tw-flex tw-justify-between tw-border-0 tw-border-solid tw-border-t tw-border-secondary-300 tw-pt-2 tw-mb-0"
>
<span class="tw-font-semibold">
{{ "total" | i18n }}
</span>
<span>
{{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }}
<span class="tw-text-xs tw-font-semibold">
/ {{ selectedPlanInterval | i18n }}</span
>
</span>
</p>
</bit-hint>
</div>
<!-- Pricing Summary -->
<app-pricing-summary
*ngIf="pricingSummaryData"
[summaryData]="pricingSummaryData"
></app-pricing-summary>
</ng-container>
</div>
<ng-container bitDialogFooter>

View File

@@ -65,8 +65,10 @@ import {
import { KeyService } from "@bitwarden/key-management";
import { BillingNotificationService } from "../services/billing-notification.service";
import { PricingSummaryService } from "../services/pricing-summary.service";
import { BillingSharedModule } from "../shared/billing-shared.module";
import { PaymentComponent } from "../shared/payment/payment.component";
import { PricingSummaryData } from "../shared/pricing-summary/pricing-summary.component";
type ChangePlanDialogParams = {
organizationId: string;
@@ -130,7 +132,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
this.formGroup?.controls?.productTier?.setValue(product);
}
protected estimatedTax: number = 0;
private _productTier = ProductTierType.Free;
@Input()
@@ -186,7 +187,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
dialogHeaderName: string;
currentPlanName: string;
showPayment: boolean = false;
totalOpened: boolean = false;
currentPlan: PlanResponse;
isCardStateDisabled = false;
focusedIndex: number | null = null;
@@ -195,6 +195,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
plans: ListResponse<PlanResponse>;
isSubscriptionCanceled: boolean = false;
secretsManagerTotal: number;
pricingSummaryData: PricingSummaryData;
private destroy$ = new Subject<void>();
@@ -219,6 +220,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
private accountService: AccountService,
private organizationBillingService: OrganizationBillingService,
private billingNotificationService: BillingNotificationService,
private pricingSummaryService: PricingSummaryService,
) {}
async ngOnInit(): Promise<void> {
@@ -312,6 +314,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
const taxInfo = await this.organizationApiService.getTaxInfo(this.organizationId);
this.taxInformation = TaxInformation.from(taxInfo);
// Initialize pricing summary data
await this.updatePricingSummaryData();
if (!this.isSubscriptionCanceled) {
this.refreshSalesTax();
}
@@ -335,7 +340,12 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
setInitialPlanSelection() {
this.focusedIndex = this.selectableProducts.length - 1;
if (!this.isSubscriptionCanceled) {
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
const enterprisePlan = this.getPlanByType(ProductTierType.Enterprise);
const planToSelect =
enterprisePlan || this.selectableProducts[this.selectableProducts.length - 1];
if (planToSelect) {
this.selectPlan(planToSelect);
}
}
}
@@ -362,6 +372,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
updateInterval(event: number) {
this.selectedInterval = event;
this.planTypeChanged();
void this.updatePricingSummaryData();
}
protected getPlanIntervals() {
@@ -460,6 +471,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
}
protected selectPlan(plan: PlanResponse) {
if (!plan) {
return;
}
if (
this.selectedInterval === PlanInterval.Monthly &&
plan.productTier == ProductTierType.Families
@@ -476,7 +491,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
try {
this.refreshSalesTax();
} catch {
this.estimatedTax = 0;
void this.updatePricingSummaryData();
}
}
@@ -585,83 +600,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
return this.sub?.maxStorageGb ? this.sub?.maxStorageGb - 1 : 0;
}
passwordManagerSeatTotal(plan: PlanResponse): number {
if (!plan.PasswordManager.hasAdditionalSeatsOption || this.isSecretsManagerTrial()) {
return 0;
}
const result = plan.PasswordManager.seatPrice * Math.abs(this.sub?.seats || 0);
return result;
}
secretsManagerSeatTotal(plan: PlanResponse, seats: number): number {
if (!plan.SecretsManager.hasAdditionalSeatsOption) {
return 0;
}
return plan.SecretsManager.seatPrice * Math.abs(seats || 0);
}
additionalStorageTotal(plan: PlanResponse): number {
if (!plan.PasswordManager.hasAdditionalStorageOption) {
return 0;
}
return (
plan.PasswordManager.additionalStoragePricePerGb *
// TODO: Eslint upgrade. Please resolve this since the null check does nothing
// eslint-disable-next-line no-constant-binary-expression
Math.abs(this.sub?.maxStorageGb ? this.sub?.maxStorageGb - 1 : 0 || 0)
);
}
additionalStoragePriceMonthly(selectedPlan: PlanResponse) {
return selectedPlan.PasswordManager.additionalStoragePricePerGb;
}
additionalServiceAccountTotal(plan: PlanResponse): number {
if (
!plan.SecretsManager.hasAdditionalServiceAccountOption ||
this.additionalServiceAccount == 0
) {
return 0;
}
return plan.SecretsManager.additionalPricePerServiceAccount * this.additionalServiceAccount;
}
get passwordManagerSubtotal() {
if (!this.selectedPlan || !this.selectedPlan.PasswordManager) {
return 0;
}
let subTotal = this.selectedPlan.PasswordManager.basePrice;
if (this.selectedPlan.PasswordManager.hasAdditionalSeatsOption) {
subTotal += this.passwordManagerSeatTotal(this.selectedPlan);
}
if (this.selectedPlan.PasswordManager.hasPremiumAccessOption) {
subTotal += this.selectedPlan.PasswordManager.premiumAccessOptionPrice;
}
return subTotal - this.discount;
}
secretsManagerSubtotal() {
const plan = this.selectedPlan;
if (!plan || !plan.SecretsManager) {
return this.secretsManagerTotal || 0;
}
if (this.secretsManagerTotal) {
return this.secretsManagerTotal;
}
this.secretsManagerTotal =
plan.SecretsManager.basePrice +
this.secretsManagerSeatTotal(plan, this.sub?.smSeats) +
this.additionalServiceAccountTotal(plan);
return this.secretsManagerTotal;
}
get passwordManagerSeats() {
if (!this.selectedPlan) {
return 0;
@@ -673,26 +611,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
return this.sub?.seats;
}
get total() {
if (!this.organization || !this.selectedPlan) {
return 0;
}
if (this.organization.useSecretsManager) {
return (
this.passwordManagerSubtotal +
this.additionalStorageTotal(this.selectedPlan) +
this.secretsManagerSubtotal() +
this.estimatedTax
);
}
return (
this.passwordManagerSubtotal +
this.additionalStorageTotal(this.selectedPlan) +
this.estimatedTax
);
}
get teamsStarterPlanIsAvailable() {
return this.selectablePlans.some((plan) => plan.type === PlanType.TeamsStarter);
}
@@ -984,15 +902,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
this.showPayment = true;
}
toggleTotalOpened() {
this.totalOpened = !this.totalOpened;
}
calculateTotalAppliedDiscount(total: number) {
const discountedTotal = total * (this.discountPercentageFromSub / 100);
return discountedTotal;
}
get paymentSourceClasses() {
if (this.paymentSource == null) {
return [];
@@ -1099,7 +1008,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
this.taxService
.previewOrganizationInvoice(request)
.then((invoice) => {
this.estimatedTax = invoice.taxAmount;
void this.updatePricingSummaryData();
})
.catch((error) => {
const translatedMessage = this.i18nService.t(error.message);
@@ -1112,6 +1021,21 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
});
}
private async updatePricingSummaryData(): Promise<void> {
if (!this.selectedPlan || !this.organization || !this.taxInformation) {
return;
}
this.pricingSummaryData = await this.pricingSummaryService.getPricingSummaryData(
this.selectedPlan,
this.sub,
this.organization,
this.selectedInterval,
this.taxInformation,
this.isSecretsManagerTrial(),
);
}
protected canUpdatePaymentInformation(): boolean {
return (
this.upgradeRequiresPaymentMethod ||

View File

@@ -60,6 +60,7 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac
PaymentComponent,
IndividualSelfHostingLicenseUploaderComponent,
OrganizationSelfHostingLicenseUploaderComponent,
PricingSummaryComponent,
],
})
export class BillingSharedModule {}