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:
@@ -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 }} ×
|
||||
{{
|
||||
(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 }}
|
||||
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
|
||||
×
|
||||
{{ 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 }}
|
||||
×
|
||||
{{ 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 }} ×
|
||||
{{
|
||||
(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 }}
|
||||
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
|
||||
×
|
||||
{{ 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 }}
|
||||
×
|
||||
{{ 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 }}
|
||||
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
|
||||
×
|
||||
{{ 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 }}
|
||||
×
|
||||
{{ 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 }}
|
||||
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
|
||||
×
|
||||
{{ 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 }}
|
||||
×
|
||||
{{ 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 }} ×
|
||||
{{
|
||||
(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 }}
|
||||
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
|
||||
×
|
||||
{{ 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 }}
|
||||
×
|
||||
{{ 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 }} ×
|
||||
{{
|
||||
(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 }}
|
||||
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
|
||||
×
|
||||
{{ 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 }}
|
||||
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
|
||||
×
|
||||
{{ 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 }}
|
||||
×
|
||||
{{ 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 }}
|
||||
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
|
||||
×
|
||||
{{ 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>
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -60,6 +60,7 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac
|
||||
PaymentComponent,
|
||||
IndividualSelfHostingLicenseUploaderComponent,
|
||||
OrganizationSelfHostingLicenseUploaderComponent,
|
||||
PricingSummaryComponent,
|
||||
],
|
||||
})
|
||||
export class BillingSharedModule {}
|
||||
|
||||
Reference in New Issue
Block a user