1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-20 11:24:07 +00:00

Refactor total summary to another component

This commit is contained in:
Cy Okeke
2025-06-24 14:32:50 +01:00
parent 05b57cdbf6
commit 5b196df1e0
6 changed files with 879 additions and 1201 deletions

View File

@@ -365,619 +365,90 @@
(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>
<div *ngIf="!isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint
class="tw-w-6/12 !tw-text-main"
*ngIf="selectedInterval == planIntervals.Annually"
>
<app-cost-summary
[organization]="organization"
[sub]="sub"
[selectedPlan]="selectedPlan"
[selectedInterval]="selectedInterval"
[discountPercentageFromSub]="discountPercentageFromSub"
[discountPercentage]="discountPercentage"
[acceptingSponsorship]="acceptingSponsorship"
[additionalServiceAccount]="additionalServiceAccount"
[planIntervals]="planIntervals"
[storageGb]="storageGb"
[totalOpened]="totalOpened"
[isSecretsManagerTrial]="isSecretsManagerTrial()"
[estimatedTax]="estimatedTax"
></app-cost-summary>
</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
class="tw-w-6/12 !tw-text-main"
*ngIf="selectedInterval == planIntervals.Monthly"
>
<app-cost-summary
[organization]="organization"
[sub]="sub"
[selectedPlan]="selectedPlan"
[selectedInterval]="selectedInterval"
[discountPercentageFromSub]="discountPercentageFromSub"
[discountPercentage]="discountPercentage"
[acceptingSponsorship]="acceptingSponsorship"
[additionalServiceAccount]="additionalServiceAccount"
[planIntervals]="planIntervals"
[totalOpened]="totalOpened"
[storageGb]="storageGb"
[isSecretsManagerTrial]="isSecretsManagerTrial()"
[estimatedTax]="estimatedTax"
></app-cost-summary>
</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>
<div *ngIf="isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint
class="tw-w-6/12 !tw-text-main"
*ngIf="selectedInterval == planIntervals.Annually"
>
<app-cost-summary
[organization]="organization"
[sub]="sub"
[selectedPlan]="selectedPlan"
[selectedInterval]="selectedInterval"
[discountPercentageFromSub]="discountPercentageFromSub"
[discountPercentage]="discountPercentage"
[acceptingSponsorship]="acceptingSponsorship"
[additionalServiceAccount]="additionalServiceAccount"
[planIntervals]="planIntervals"
[totalOpened]="totalOpened"
[storageGb]="storageGb"
[isSecretsManagerTrial]="isSecretsManagerTrial()"
[estimatedTax]="estimatedTax"
></app-cost-summary>
</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
class="tw-w-6/12 tw-text-main"
*ngIf="selectedInterval == planIntervals.Monthly"
>
<app-cost-summary
[organization]="organization"
[sub]="sub"
[selectedPlan]="selectedPlan"
[selectedInterval]="selectedInterval"
[discountPercentageFromSub]="discountPercentageFromSub"
[discountPercentage]="discountPercentage"
[acceptingSponsorship]="acceptingSponsorship"
[additionalServiceAccount]="additionalServiceAccount"
[planIntervals]="planIntervals"
[totalOpened]="totalOpened"
[storageGb]="storageGb"
[isSecretsManagerTrial]="isSecretsManagerTrial()"
[estimatedTax]="estimatedTax"
></app-cost-summary>
</bit-hint>
</div>
</ng-container>

View File

@@ -67,6 +67,7 @@ import { KeyService } from "@bitwarden/key-management";
import { BillingNotificationService } from "../services/billing-notification.service";
import { BillingSharedModule } from "../shared/billing-shared.module";
import { PaymentComponent } from "../shared/payment/payment.component";
import { CostSummaryComponent } from "../shared/trial-subscription-dialog/cost-summary.component";
type ChangePlanDialogParams = {
organizationId: string;
@@ -109,7 +110,7 @@ interface OnSuccessArgs {
@Component({
templateUrl: "./change-plan-dialog.component.html",
imports: [BillingSharedModule],
imports: [BillingSharedModule, CostSummaryComponent],
})
export class ChangePlanDialogComponent implements OnInit, OnDestroy {
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
@@ -194,7 +195,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
paymentSource?: PaymentSourceResponse;
plans: ListResponse<PlanResponse>;
isSubscriptionCanceled: boolean = false;
secretsManagerTotal: number;
private destroy$ = new Subject<void>();
@@ -585,114 +585,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;
}
if (this.selectedPlan.productTier === ProductTierType.Families) {
return this.selectedPlan.PasswordManager.baseSeats;
}
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 +876,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 [];

View File

@@ -0,0 +1,603 @@
<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-full" *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-full" *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-full" *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-full" *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-full">
<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-full">
<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-full">
<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>
</ng-container>

View File

@@ -0,0 +1,109 @@
import { Component, Input } from "@angular/core";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { PlanInterval } from "@bitwarden/common/billing/enums";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { SharedModule } from "../../../shared/shared.module";
import { PricingCalculationService } from "./pricing-calculation.service";
@Component({
selector: "app-cost-summary",
templateUrl: "./cost-summary.component.html",
standalone: true,
providers: [PricingCalculationService],
imports: [SharedModule],
})
export class CostSummaryComponent {
@Input() organization: Organization;
@Input() sub: OrganizationSubscriptionResponse;
@Input() selectedPlan: PlanResponse;
@Input() selectedInterval: number;
@Input() discountPercentageFromSub: number;
@Input() discountPercentage: number;
@Input() acceptingSponsorship: boolean;
@Input() additionalServiceAccount: number;
@Input() planIntervals = PlanInterval;
@Input() totalOpened: boolean;
@Input() storageGb: number;
@Input() isSecretsManagerTrial: boolean;
@Input() estimatedTax: number;
discount = 0;
secretsManagerTotal: number;
constructor(public pricingCalculationService: PricingCalculationService) {}
get selectedPlanInterval(): string {
return this.selectedPlan?.isAnnual ? "year" : "month";
}
get passwordManagerSeats(): number {
return this.pricingCalculationService.getPasswordManagerSeats(this.selectedPlan, this.sub);
}
passwordManagerSeatTotal(plan: PlanResponse): number {
return this.pricingCalculationService.calculatePasswordManagerSeatTotal(
plan,
this.sub,
this.isSecretsManagerTrial,
);
}
secretsManagerSeatTotal(plan: PlanResponse, seats: number): number {
return this.pricingCalculationService.calculateSecretsManagerSeatTotal(plan, seats);
}
additionalStorageTotal(plan: PlanResponse): number {
return this.pricingCalculationService.calculateAdditionalStorageTotal(plan, this.sub);
}
additionalStoragePriceMonthly(selectedPlan: PlanResponse): number {
return selectedPlan.PasswordManager.additionalStoragePricePerGb;
}
additionalServiceAccountTotal(plan: PlanResponse): number {
return this.pricingCalculationService.calculateAdditionalServiceAccountTotal(
plan,
this.additionalServiceAccount,
);
}
calculateTotalAppliedDiscount(total: number): number {
return this.pricingCalculationService.calculateTotalAppliedDiscount(
total,
this.discountPercentageFromSub,
);
}
toggleTotalOpened(): void {
this.totalOpened = !this.totalOpened;
}
secretsManagerSubtotal() {
return this.pricingCalculationService.calculateSecretsManagerSubtotal(
this.selectedPlan,
this.sub,
this.secretsManagerTotal,
);
}
get passwordManagerSubtotal() {
return this.pricingCalculationService.calculatePasswordManagerSubtotal(
this.selectedPlan,
this.sub,
this.discount,
);
}
get total() {
return this.pricingCalculationService.calculateTotal(
this.organization,
this.selectedPlan,
this.passwordManagerSubtotal,
this.estimatedTax,
this.sub,
);
}
}

View File

@@ -205,108 +205,96 @@
/>
</ng-container>
<!-- Total Summary -->
<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>
<!-- Cost Breakdown -->
<!-- Password Manager + Secrets Manager Cost Summary -->
<div *ngIf="!isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint
class="tw-w-[55%] !tw-text-main"
*ngIf="selectedInterval == planIntervals.Annually"
>
<app-cost-summary
[organization]="organization"
[sub]="sub"
[selectedPlan]="selectedPlan"
[selectedInterval]="selectedInterval"
[discountPercentageFromSub]="discountPercentageFromSub"
[discountPercentage]="discountPercentage"
[acceptingSponsorship]="acceptingSponsorship"
[additionalServiceAccount]="additionalServiceAccount"
[planIntervals]="planIntervals"
[storageGb]="storageGb"
[totalOpened]="totalOpened"
[isSecretsManagerTrial]="isSecretsManagerTrial()"
[estimatedTax]="estimatedTax"
></app-cost-summary>
</bit-hint>
<bit-hint
class="tw-w-[55%] !tw-text-main"
*ngIf="selectedInterval == planIntervals.Monthly"
>
<app-cost-summary
[organization]="organization"
[sub]="sub"
[selectedPlan]="selectedPlan"
[selectedInterval]="selectedInterval"
[discountPercentageFromSub]="discountPercentageFromSub"
[discountPercentage]="discountPercentage"
[acceptingSponsorship]="acceptingSponsorship"
[additionalServiceAccount]="additionalServiceAccount"
[planIntervals]="planIntervals"
[totalOpened]="totalOpened"
[storageGb]="storageGb"
[isSecretsManagerTrial]="isSecretsManagerTrial()"
[estimatedTax]="estimatedTax"
></app-cost-summary>
</bit-hint>
</div>
<!-- Cost Breakdown -->
<ng-container *ngIf="totalOpened">
<!-- Password Manager + Secrets Manager Cost Summary -->
<div *ngIf="!isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Annually">
<ng-container *ngTemplateOutlet="passwordManagerAnnualSummary"></ng-container>
<ng-container *ngTemplateOutlet="secretsManagerAnnualSummary"></ng-container>
</bit-hint>
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Monthly">
<ng-container *ngTemplateOutlet="passwordManagerMonthlySummary"></ng-container>
<ng-container *ngTemplateOutlet="secretsManagerMonthlySummary"></ng-container>
</bit-hint>
</div>
<!-- Secrets Manager + Free PM Cost Summary -->
<div *ngIf="isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Annually">
<ng-container *ngTemplateOutlet="secretsManagerAnnualSummary"></ng-container>
<ng-container *ngTemplateOutlet="passwordManagerAnnualSummary"></ng-container>
</bit-hint>
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Monthly">
<ng-container *ngTemplateOutlet="secretsManagerMonthlySummary"></ng-container>
<ng-container *ngTemplateOutlet="passwordManagerMonthlySummary"></ng-container>
</bit-hint>
</div>
<!-- PM Only Discount -->
<div
*ngIf="discountPercentage && !organization.useSecretsManager"
class="tw-flex tw-flex-wrap tw-gap-4"
<!-- Secrets Manager + Free PM Cost Summary -->
<div *ngIf="isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint
class="tw-w-[55%] !tw-text-main"
*ngIf="selectedInterval == planIntervals.Annually"
>
<bit-hint class="tw-w-1/2">
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="discountPercentageFromSub > 0"
>
<span class="tw-text-xs">
{{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
</span>
<span class="tw-line-through tw-text-xs">{{
calculateTotalAppliedDiscount(total) | currency: "$"
}}</span>
</p>
</bit-hint>
</div>
<!-- Tax and Final Total -->
<div class="tw-flex tw-flex-wrap tw-gap-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 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>
</ng-container>
<app-cost-summary
[organization]="organization"
[sub]="sub"
[selectedPlan]="selectedPlan"
[selectedInterval]="selectedInterval"
[discountPercentageFromSub]="discountPercentageFromSub"
[discountPercentage]="discountPercentage"
[acceptingSponsorship]="acceptingSponsorship"
[additionalServiceAccount]="additionalServiceAccount"
[planIntervals]="planIntervals"
[totalOpened]="totalOpened"
[storageGb]="storageGb"
[isSecretsManagerTrial]="isSecretsManagerTrial()"
[estimatedTax]="estimatedTax"
></app-cost-summary>
</bit-hint>
<bit-hint
class="tw-w-[55%] tw-text-main"
*ngIf="selectedInterval == planIntervals.Monthly"
>
<app-cost-summary
[organization]="organization"
[sub]="sub"
[selectedPlan]="selectedPlan"
[selectedInterval]="selectedInterval"
[discountPercentageFromSub]="discountPercentageFromSub"
[discountPercentage]="discountPercentage"
[acceptingSponsorship]="acceptingSponsorship"
[additionalServiceAccount]="additionalServiceAccount"
[planIntervals]="planIntervals"
[totalOpened]="totalOpened"
[storageGb]="storageGb"
[isSecretsManagerTrial]="isSecretsManagerTrial()"
[estimatedTax]="estimatedTax"
></app-cost-summary>
</bit-hint>
</div>
</ng-container>
</div>
<!-- Dialog Footer -->
<ng-container bitDialogFooter>
<button bitButton bitFormButton buttonType="primary" type="submit">
@@ -318,310 +306,3 @@
</ng-container>
</bit-dialog>
</form>
<!-- Templates for cost breakdown sections -->
<ng-template #passwordManagerAnnualSummary>
<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>
</ng-template>
<ng-template #passwordManagerMonthlySummary>
<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>
</ng-template>
<ng-template #secretsManagerAnnualSummary>
<!-- 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>
</ng-template>
<ng-template #secretsManagerMonthlySummary>
<!-- 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>
</ng-template>

View File

@@ -52,6 +52,7 @@ import {
import { BillingSharedModule } from "../billing-shared.module";
import { PaymentComponent } from "../payment/payment.component";
import { CostSummaryComponent } from "./cost-summary.component";
import { PlanSelectionService } from "./plan-selection.service";
import { PricingCalculationService } from "./pricing-calculation.service";
import { TrialPaymentMethodService } from "./trial-payment-method.service";
@@ -92,7 +93,7 @@ interface PlanCard {
// Component
@Component({
templateUrl: "./trial-payment-method-dialog.component.html",
imports: [BillingSharedModule],
imports: [BillingSharedModule, CostSummaryComponent],
providers: [TrialPaymentMethodService, PlanSelectionService, PricingCalculationService],
})
export class TrialPaymentMethodDialogComponent implements OnInit, OnDestroy {
@@ -165,7 +166,6 @@ export class TrialPaymentMethodDialogComponent implements OnInit, OnDestroy {
// Data properties
selfHosted = false;
singleOrgPolicyAppliesToActiveUser = false;
discount = 0;
planType: string;
selectedPlan: PlanResponse;
selectedInterval: number = 1;
@@ -285,36 +285,6 @@ export class TrialPaymentMethodDialogComponent implements OnInit, OnDestroy {
return this.sub?.maxStorageGb ? this.sub.maxStorageGb - 1 : 0;
}
get passwordManagerSubtotal(): number {
return this.pricingCalculationService.calculatePasswordManagerSubtotal(
this.selectedPlan,
this.sub,
this.discount,
);
}
secretsManagerSubtotal(): number {
return this.pricingCalculationService.calculateSecretsManagerSubtotal(
this.selectedPlan,
this.sub,
this.secretsManagerTotal,
);
}
get passwordManagerSeats(): number {
return this.pricingCalculationService.getPasswordManagerSeats(this.selectedPlan, this.sub);
}
get total(): number {
return this.pricingCalculationService.calculateTotal(
this.organization,
this.selectedPlan,
this.passwordManagerSubtotal,
this.estimatedTax,
this.sub,
);
}
get additionalServiceAccount(): number {
return this.pricingCalculationService.calculateAdditionalServiceAccount(
this.currentPlan,
@@ -330,41 +300,6 @@ export class TrialPaymentMethodDialogComponent implements OnInit, OnDestroy {
);
}
// Pricing calculation methods
passwordManagerSeatTotal(plan: PlanResponse): number {
return this.pricingCalculationService.calculatePasswordManagerSeatTotal(
plan,
this.sub,
this.isSecretsManagerTrial(),
);
}
secretsManagerSeatTotal(plan: PlanResponse, seats: number): number {
return this.pricingCalculationService.calculateSecretsManagerSeatTotal(plan, seats);
}
additionalStorageTotal(plan: PlanResponse): number {
return this.pricingCalculationService.calculateAdditionalStorageTotal(plan, this.sub);
}
additionalStoragePriceMonthly(selectedPlan: PlanResponse): number {
return selectedPlan.PasswordManager.additionalStoragePricePerGb;
}
additionalServiceAccountTotal(plan: PlanResponse): number {
return this.pricingCalculationService.calculateAdditionalServiceAccountTotal(
plan,
this.additionalServiceAccount,
);
}
calculateTotalAppliedDiscount(total: number): number {
return this.pricingCalculationService.calculateTotalAppliedDiscount(
total,
this.discountPercentageFromSub,
);
}
// Event handlers
changedProduct(): void {
const selectedPlan = this.selectablePlans[0];
@@ -390,10 +325,6 @@ export class TrialPaymentMethodDialogComponent implements OnInit, OnDestroy {
this.refreshSalesTax();
}
toggleTotalOpened(): void {
this.totalOpened = !this.totalOpened;
}
onKeydown(event: KeyboardEvent, index: number): void {
this.planSelectionService.handleKeydown(event, index, this.isCardDisabled.bind(this));
}