From 5b196df1e0a8f3557ce66d6936be15beae663708 Mon Sep 17 00:00:00 2001 From: Cy Okeke Date: Tue, 24 Jun 2025 14:32:50 +0100 Subject: [PATCH] Refactor total summary to another component --- .../change-plan-dialog.component.html | 685 ++---------------- .../change-plan-dialog.component.ts | 121 +--- .../cost-summary.component.html | 603 +++++++++++++++ .../cost-summary.component.ts | 109 +++ ...trial-payment-method-dialog.component.html | 489 +++---------- .../trial-payment-method-dialog.component.ts | 73 +- 6 files changed, 879 insertions(+), 1201 deletions(-) create mode 100644 apps/web/src/app/billing/shared/trial-subscription-dialog/cost-summary.component.html create mode 100644 apps/web/src/app/billing/shared/trial-subscription-dialog/cost-summary.component.ts diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index 91e2e83a92c..603e5aed6d4 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -365,619 +365,90 @@ (taxInformationChanged)="taxInformationChanged($event)" > -
-

- {{ "total" | i18n }}: - {{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }} USD - / {{ selectedPlanInterval | i18n }} - -

-
-
- -

- {{ "passwordManager" | i18n }} -

-

- - {{ passwordManagerSeats }} - {{ "members" | i18n }} × - {{ - (selectedPlan.isAnnual - ? selectedPlan.PasswordManager.basePrice / 12 - : selectedPlan.PasswordManager.basePrice - ) | currency: "$" - }} - /{{ selectedPlanInterval | i18n }} - - - - {{ - selectedPlan.PasswordManager.basePrice | currency: "$" - }} - {{ "freeWithSponsorship" | i18n }} - - - {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} - - -

-

- - {{ "additionalUsers" | i18n }}: - {{ passwordManagerSeats || 0 }}  - {{ "members" | i18n }} - × - {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - - - {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} - -

-

- - {{ storageGb }} - {{ "additionalStorageGbMessage" | i18n }} - × - {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - {{ additionalStorageTotal(selectedPlan) | currency: "$" }} -

- -

- - - {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} - - {{ - calculateTotalAppliedDiscount( - passwordManagerSeatTotal(selectedPlan) + additionalStorageTotal(selectedPlan) - ) | currency: "$" - }} - -

- -

- {{ "secretsManager" | i18n }} -

-

- - {{ sub?.smSeats }} - {{ "members" | i18n }} × - {{ - (selectedPlan.isAnnual - ? selectedPlan.SecretsManager.basePrice / 12 - : selectedPlan.SecretsManager.basePrice - ) | currency: "$" - }} - /{{ selectedPlanInterval | i18n }} - -

-

- - {{ "additionalUsers" | i18n }}: - {{ sub?.smSeats || 0 }}  - {{ "members" | i18n }} - × - {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - - - {{ secretsManagerSeatTotal(selectedPlan, sub.smSeats) | currency: "$" }} - -

-

- - {{ additionalServiceAccount }} - {{ "serviceAccounts" | i18n | lowercase }} - × - {{ selectedPlan?.SecretsManager?.additionalPricePerServiceAccount | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }} -

- -

- - - {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} - - {{ - calculateTotalAppliedDiscount( - additionalServiceAccountTotal(selectedPlan) + - secretsManagerSeatTotal(selectedPlan, sub.smSeats) - ) | currency: "$" - }} - -

+
+ + - -

- {{ "passwordManager" | i18n }} -

-

- - {{ "basePrice" | i18n }}: - {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} - {{ "monthAbbr" | i18n }} - - - {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} - -

-

- - {{ "additionalUsers" | i18n }}: - {{ passwordManagerSeats }}  - {{ "members" | i18n }} - × - {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - - {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} - -

-

- - {{ storageGb }} - {{ "additionalStorageGbMessage" | i18n }} - × - {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - {{ - storageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$" - }} -

- -

- - - {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} - - {{ calculateTotalAppliedDiscount(total) | currency: "$" }} - -

- -

- {{ "secretsManager" | i18n }} -

-

- - {{ "basePrice" | i18n }}: - {{ selectedPlan.SecretsManager.basePrice | currency: "$" }} - {{ "monthAbbr" | i18n }} - - - {{ selectedPlan.SecretsManager.basePrice | currency: "$" }} - -

-

- - {{ "additionalUsers" | i18n }}: - {{ sub?.smSeats }}  - {{ "members" | i18n }} - × - {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - - {{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }} - -

-

- - {{ additionalServiceAccount }} - {{ "serviceAccounts" | i18n | lowercase }} - × - {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }} -

- -

- - - {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} - - {{ - additionalServiceAccountTotal(selectedPlan) + - secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" - }} - -

+ +
-
- - -

- {{ "secretsManager" | i18n }} -

-

- - {{ sub?.smSeats }} - {{ "members" | i18n }} × - {{ - (selectedPlan.isAnnual - ? selectedPlan.SecretsManager.basePrice / 12 - : selectedPlan.SecretsManager.basePrice - ) | currency: "$" - }} - /{{ selectedPlanInterval | i18n }} - -

-

- - {{ "additionalUsers" | i18n }}: - {{ sub?.smSeats || 0 }}  - {{ "members" | i18n }} - × - {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - - - {{ secretsManagerSeatTotal(selectedPlan, sub.smSeats) | currency: "$" }} - -

-

- - {{ additionalServiceAccount }} - {{ "serviceAccounts" | i18n }} - × - {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }} -

- -

- {{ "passwordManager" | i18n }} -

-

- - {{ sub?.seats }} - {{ "members" | i18n }} × - {{ - (selectedPlan.isAnnual - ? selectedPlan.PasswordManager.basePrice / 12 - : selectedPlan.PasswordManager.basePrice - ) | currency: "$" - }} - /{{ selectedPlanInterval | i18n }} - - - - {{ - selectedPlan.PasswordManager.basePrice | currency: "$" - }} - {{ "freeWithSponsorship" | i18n }} - - - {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} - - -

-

- - {{ "additionalUsers" | i18n }}: - {{ sub?.seats || 0 }}  - {{ "members" | i18n }} - × - {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - - - {{ "freeForOneYear" | i18n }} - - - - {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} - -

+
+ + - - -

- {{ "secretsManager" | i18n }} -

-

- - {{ "basePrice" | i18n }}: - {{ selectedPlan.SecretsManager.basePrice | currency: "$" }} - {{ "monthAbbr" | i18n }} - - - {{ selectedPlan.SecretsManager.basePrice | currency: "$" }} - -

-

- - {{ "additionalUsers" | i18n }}: - {{ sub?.smSeats }}  - {{ "members" | i18n }} - × - {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - - {{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }} - -

-

- - {{ additionalServiceAccount }} - {{ "serviceAccounts" | i18n }} - × - {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }} -

- -

- {{ "passwordManager" | i18n }} -

-

- - {{ "basePrice" | i18n }}: - {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} - {{ "monthAbbr" | i18n }} - - - {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} - -

-

- - {{ "additionalUsers" | i18n }}: - {{ sub?.seats }}  - {{ "members" | i18n }} - × - {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} - /{{ selectedPlanInterval | i18n }} - - - {{ "freeForOneYear" | i18n }} - - - - {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} - -

-
-
- -
- -

- - - {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} - - {{ - calculateTotalAppliedDiscount(total) | currency: "$" - }} - -

-
-
-
- -

- - {{ "estimatedTax" | i18n }} - - - {{ estimatedTax | currency: "USD" : "$" }} - -

-
-
-
- -

- - {{ "total" | i18n }} - - - {{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }} - - / {{ selectedPlanInterval | i18n }} - -

+ +
diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index dc8474d24d6..6f6cd3eac82 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -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; isSubscriptionCanceled: boolean = false; - secretsManagerTotal: number; private destroy$ = new Subject(); @@ -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 []; diff --git a/apps/web/src/app/billing/shared/trial-subscription-dialog/cost-summary.component.html b/apps/web/src/app/billing/shared/trial-subscription-dialog/cost-summary.component.html new file mode 100644 index 00000000000..5d189536e25 --- /dev/null +++ b/apps/web/src/app/billing/shared/trial-subscription-dialog/cost-summary.component.html @@ -0,0 +1,603 @@ + +
+

+ {{ "total" | i18n }}: + {{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }} USD + / {{ selectedPlanInterval | i18n }} + +

+
+ +
+ +

+ {{ "passwordManager" | i18n }} +

+

+ + {{ passwordManagerSeats }} + {{ "members" | i18n }} × + {{ + (selectedPlan.isAnnual + ? selectedPlan.PasswordManager.basePrice / 12 + : selectedPlan.PasswordManager.basePrice + ) | currency: "$" + }} + /{{ selectedPlanInterval | i18n }} + + + + {{ + selectedPlan.PasswordManager.basePrice | currency: "$" + }} + {{ "freeWithSponsorship" | i18n }} + + + {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} + + +

+

+ + {{ "additionalUsers" | i18n }}: + {{ passwordManagerSeats || 0 }}  + {{ "members" | i18n }} + × + {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + + + {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} + +

+

+ + {{ storageGb }} + {{ "additionalStorageGbMessage" | i18n }} + × + {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + {{ additionalStorageTotal(selectedPlan) | currency: "$" }} +

+ +

+ + + {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} + + {{ + calculateTotalAppliedDiscount( + passwordManagerSeatTotal(selectedPlan) + additionalStorageTotal(selectedPlan) + ) | currency: "$" + }} + +

+ +

+ {{ "secretsManager" | i18n }} +

+

+ + {{ sub?.smSeats }} + {{ "members" | i18n }} × + {{ + (selectedPlan.isAnnual + ? selectedPlan.SecretsManager.basePrice / 12 + : selectedPlan.SecretsManager.basePrice + ) | currency: "$" + }} + /{{ selectedPlanInterval | i18n }} + +

+

+ + {{ "additionalUsers" | i18n }}: + {{ sub?.smSeats || 0 }}  + {{ "members" | i18n }} + × + {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + + + {{ secretsManagerSeatTotal(selectedPlan, sub.smSeats) | currency: "$" }} + +

+

+ + {{ additionalServiceAccount }} + {{ "serviceAccounts" | i18n | lowercase }} + × + {{ selectedPlan?.SecretsManager?.additionalPricePerServiceAccount | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }} +

+ +

+ + + {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} + + {{ + calculateTotalAppliedDiscount( + additionalServiceAccountTotal(selectedPlan) + + secretsManagerSeatTotal(selectedPlan, sub.smSeats) + ) | currency: "$" + }} + +

+
+ +

+ {{ "passwordManager" | i18n }} +

+

+ + {{ "basePrice" | i18n }}: + {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} + {{ "monthAbbr" | i18n }} + + + {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} + +

+

+ + {{ "additionalUsers" | i18n }}: + {{ passwordManagerSeats }}  + {{ "members" | i18n }} + × + {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + + {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} + +

+

+ + {{ storageGb }} + {{ "additionalStorageGbMessage" | i18n }} + × + {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + {{ + storageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$" + }} +

+ +

+ + + {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} + + {{ calculateTotalAppliedDiscount(total) | currency: "$" }} + +

+ +

+ {{ "secretsManager" | i18n }} +

+

+ + {{ "basePrice" | i18n }}: + {{ selectedPlan.SecretsManager.basePrice | currency: "$" }} + {{ "monthAbbr" | i18n }} + + + {{ selectedPlan.SecretsManager.basePrice | currency: "$" }} + +

+

+ + {{ "additionalUsers" | i18n }}: + {{ sub?.smSeats }}  + {{ "members" | i18n }} + × + {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + + {{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }} + +

+

+ + {{ additionalServiceAccount }} + {{ "serviceAccounts" | i18n | lowercase }} + × + {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }} +

+ +

+ + + {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} + + {{ + additionalServiceAccountTotal(selectedPlan) + + secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" + }} + +

+
+
+ +
+ + +

+ {{ "secretsManager" | i18n }} +

+

+ + {{ sub?.smSeats }} + {{ "members" | i18n }} × + {{ + (selectedPlan.isAnnual + ? selectedPlan.SecretsManager.basePrice / 12 + : selectedPlan.SecretsManager.basePrice + ) | currency: "$" + }} + /{{ selectedPlanInterval | i18n }} + +

+

+ + {{ "additionalUsers" | i18n }}: + {{ sub?.smSeats || 0 }}  + {{ "members" | i18n }} + × + {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + + + {{ secretsManagerSeatTotal(selectedPlan, sub.smSeats) | currency: "$" }} + +

+

+ + {{ additionalServiceAccount }} + {{ "serviceAccounts" | i18n }} + × + {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }} +

+ +

+ {{ "passwordManager" | i18n }} +

+

+ + {{ sub?.seats }} + {{ "members" | i18n }} × + {{ + (selectedPlan.isAnnual + ? selectedPlan.PasswordManager.basePrice / 12 + : selectedPlan.PasswordManager.basePrice + ) | currency: "$" + }} + /{{ selectedPlanInterval | i18n }} + + + + {{ + selectedPlan.PasswordManager.basePrice | currency: "$" + }} + {{ "freeWithSponsorship" | i18n }} + + + {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} + + +

+

+ + {{ "additionalUsers" | i18n }}: + {{ sub?.seats || 0 }}  + {{ "members" | i18n }} + × + {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + + + {{ "freeForOneYear" | i18n }} + + + + {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} + +

+
+ + +

+ {{ "secretsManager" | i18n }} +

+

+ + {{ "basePrice" | i18n }}: + {{ selectedPlan.SecretsManager.basePrice | currency: "$" }} + {{ "monthAbbr" | i18n }} + + + {{ selectedPlan.SecretsManager.basePrice | currency: "$" }} + +

+

+ + {{ "additionalUsers" | i18n }}: + {{ sub?.smSeats }}  + {{ "members" | i18n }} + × + {{ selectedPlan.SecretsManager.seatPrice | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + + {{ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$" }} + +

+

+ + {{ additionalServiceAccount }} + {{ "serviceAccounts" | i18n }} + × + {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }} +

+ +

+ {{ "passwordManager" | i18n }} +

+

+ + {{ "basePrice" | i18n }}: + {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} + {{ "monthAbbr" | i18n }} + + + {{ selectedPlan.PasswordManager.basePrice | currency: "$" }} + +

+

+ + {{ "additionalUsers" | i18n }}: + {{ sub?.seats }}  + {{ "members" | i18n }} + × + {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} + /{{ selectedPlanInterval | i18n }} + + + {{ "freeForOneYear" | i18n }} + + + + {{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }} + +

+
+
+ +
+ +

+ + + {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} + + {{ + calculateTotalAppliedDiscount(total) | currency: "$" + }} + +

+
+
+
+ +

+ + {{ "estimatedTax" | i18n }} + + + {{ estimatedTax | currency: "USD" : "$" }} + +

+
+
+
+ +

+ + {{ "total" | i18n }} + + + {{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }} + / {{ selectedPlanInterval | i18n }} + +

+
+
+
diff --git a/apps/web/src/app/billing/shared/trial-subscription-dialog/cost-summary.component.ts b/apps/web/src/app/billing/shared/trial-subscription-dialog/cost-summary.component.ts new file mode 100644 index 00000000000..90602840be3 --- /dev/null +++ b/apps/web/src/app/billing/shared/trial-subscription-dialog/cost-summary.component.ts @@ -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, + ); + } +} diff --git a/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.html b/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.html index 0bdef48b91c..157d8f8c051 100644 --- a/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.html +++ b/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.html @@ -205,108 +205,96 @@ /> - -
-

- {{ "total" | i18n }}: - {{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }} USD - / {{ selectedPlanInterval | i18n }} - -

+ + +
+ + + + + +
- - - -
- - - - - - - - -
- - -
- - - - - - - - -
- - -
+
+ - -

- - {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }} - - {{ - calculateTotalAppliedDiscount(total) | currency: "$" - }} -

-
-
- - -
- -

- - {{ "estimatedTax" | i18n }} - - - {{ estimatedTax | currency: "USD" : "$" }} - -

-
-
- -
- -

- - {{ "total" | i18n }} - - - {{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }} - - / {{ selectedPlanInterval | i18n }} - -

-
-
- + + + + + +
-