1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 10:13:31 +00:00

[AC-3022]Selecting a plan makes the plan cards content to auto-adjusts (#10992)

* Resolve the recommended issue

* Resolve the discount display issues

* remove unused tw property

* Resolve all the outstanding bugs

* Fix the A11y bug

* Resolve the base storage issue

* Rename service account in the summary

* changes for the A11y bug

* Fix the improper keyboard navigation in modal

* Add some additional ui changes
This commit is contained in:
cyprain-okeke
2024-09-12 19:12:38 +01:00
committed by GitHub
parent f70b3df2d2
commit fc2c83f0d3
5 changed files with 303 additions and 86 deletions

View File

@@ -7,31 +7,37 @@
<p>{{ "upgradePlans" | i18n }}</p> <p>{{ "upgradePlans" | i18n }}</p>
<div class="tw-mb-3 tw-flex tw-justify-between"> <div class="tw-mb-3 tw-flex tw-justify-between">
<span class="tw-text-lg tw-pr-1 tw-font-bold">{{ "selectAPlan" | i18n }}</span> <span class="tw-text-lg tw-pr-1 tw-font-bold">{{ "selectAPlan" | i18n }}</span>
<!-- Discount Badge -->
<div class="tw-items-center tw-gap-2"> <div class="tw-items-center tw-gap-2">
<span <span
class="tw-mr-1"
*ngIf=" *ngIf="
this.discountPercentageFromSub > 0 this.discountPercentageFromSub > 0
? discountPercentageFromSub ? discountPercentageFromSub
: this.discountPercentage && selectedInterval === 1 : this.discountPercentage && selectedInterval === planIntervals.Annually
" "
bitBadge bitBadge
variant="success" variant="success"
>{{ >{{
"upgradeDiscount" "upgradeDiscount"
| i18n | i18n
: (this.discountPercentageFromSub > 0 : (selectedInterval === planIntervals.Annually
? discountPercentageFromSub ? discountPercentageFromSub + this.discountPercentage
: this.discountPercentage) : this.discountPercentageFromSub)
}}</span }}</span
> >
<!-- Plan Interval Toggle -->
<div class="tw-inline-block"> <div class="tw-inline-block">
<bit-toggle-group <bit-toggle-group
[selected]="selectedInterval" [selected]="selectedInterval"
(selectedChange)="updateInterval($event)" (selectedChange)="updateInterval($event)"
> >
<bit-toggle <bit-toggle
*ngFor="let planInterval of getPlanIntervals()" *ngFor="
let planInterval of getPlanIntervals();
trackBy: optimizedNgForRender;
let i = index
"
[value]="planInterval.value" [value]="planInterval.value"
> >
{{ planInterval.name }} {{ planInterval.name }}
@@ -40,6 +46,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Plan Selection Cards -->
<ng-container *ngIf="!loading && !selfHosted && this.passwordManagerPlans"> <ng-container *ngIf="!loading && !selfHosted && this.passwordManagerPlans">
<div <div
class="tw-grid tw-grid-flow-col tw-gap-4 tw-mb-4" class="tw-grid tw-grid-flow-col tw-gap-4 tw-mb-4"
@@ -53,23 +60,34 @@
> >
<div class="tw-relative"> <div class="tw-relative">
<div <div
*ngIf="selectableProduct === selectedPlan" *ngIf="selectableProduct.productTier === productTypes.Enterprise"
class="tw-bg-primary-600 tw-text-center !tw-text-contrast tw-text-sm tw-font-bold tw-py-1 group-hover:tw-bg-primary-700" class="tw-bg-secondary-100 tw-text-center !tw-border-0 tw-text-sm tw-font-bold tw-py-1"
[ngClass]="{
'tw-bg-primary-700 !tw-text-contrast': selectableProduct === selectedPlan,
'tw-bg-secondary-100': !(selectableProduct === selectedPlan),
}"
> >
{{ "recommended" | i18n }} {{ "recommended" | i18n }}
</div> </div>
<div <div
class="tw-px-2 tw-py-4" class="tw-px-2 tw-pb-[4px]"
[ngClass]="{ [ngClass]="{
'tw-py-1': !(selectableProduct === selectedPlan), 'tw-py-1': !(selectableProduct === selectedPlan),
'tw-py-0': selectableProduct === selectedPlan, 'tw-py-0': selectableProduct === selectedPlan,
}" }"
> >
<h3 class="tw-text-lg tw-font-bold"> <h3
class="tw-text-[1.5rem] tw-mt-[6px] tw-font-bold tw-mb-0 tw-leading-[2rem] tw-flex tw-items-center"
>
<span class="tw-capitalize">{{ <span class="tw-capitalize">{{
selectableProduct.nameLocalizationKey | i18n selectableProduct.nameLocalizationKey | i18n
}}</span> }}</span>
<span bitBadge variant="secondary" *ngIf="selectableProduct === currentPlan"> <span
bitBadge
variant="secondary"
*ngIf="selectableProduct === currentPlan"
class="tw-ml-2 tw-align-middle"
>
{{ "current" | i18n }}</span {{ "current" | i18n }}</span
> >
</h3> </h3>
@@ -133,10 +151,13 @@
else nonEnterprisePlans else nonEnterprisePlans
" "
> >
<p class="tw-text-xs tw-px-2 tw-font-semibold" *ngIf="organization.useSecretsManager"> <p
class="tw-text-xs tw-px-2 tw-font-semibold tw-mb-1"
*ngIf="organization.useSecretsManager"
>
{{ "bitwardenPasswordManager" | i18n }} {{ "bitwardenPasswordManager" | i18n }}
</p> </p>
<p class="tw-text-xs tw-px-2">{{ "enterprisePlanUpgradeMessage" | i18n }}</p> <p class="tw-text-xs tw-px-2 tw-mb-1">{{ "enterprisePlanUpgradeMessage" | i18n }}</p>
<ul class="bwi-ul tw-text-xs"> <ul class="bwi-ul tw-text-xs">
<li> <li>
@@ -157,7 +178,10 @@
</li> </li>
</ul> </ul>
<p class="tw-text-xs tw-px-2 tw-font-semibold" *ngIf="organization.useSecretsManager"> <p
class="tw-text-xs tw-px-2 tw-font-semibold tw-mb-1"
*ngIf="organization.useSecretsManager"
>
{{ "bitwardenSecretsManager" | i18n }} {{ "bitwardenSecretsManager" | i18n }}
</p> </p>
<ul class="bwi-ul tw-text-xs" *ngIf="organization.useSecretsManager"> <ul class="bwi-ul tw-text-xs" *ngIf="organization.useSecretsManager">
@@ -195,25 +219,25 @@
</ng-container> </ng-container>
<ng-template #fullFeatureList> <ng-template #fullFeatureList>
<p <p
class="tw-text-xs tw-px-2 tw-font-semibold" class="tw-text-xs tw-px-2 tw-font-semibold tw-mb-1"
*ngIf="organization.useSecretsManager" *ngIf="organization.useSecretsManager"
> >
{{ "bitwardenPasswordManager" | i18n }} {{ "bitwardenPasswordManager" | i18n }}
</p> </p>
<p <p
*ngIf="selectableProduct.productTier === productTypes.Teams" *ngIf="selectableProduct.productTier === productTypes.Teams"
class="tw-text-xs tw-px-2" class="tw-text-xs tw-px-2 tw-mb-1"
> >
{{ "teamsPlanUpgradeMessage" | i18n }} {{ "teamsPlanUpgradeMessage" | i18n }}
</p> </p>
<p <p
*ngIf="selectableProduct.productTier === productTypes.Families" *ngIf="selectableProduct.productTier === productTypes.Families"
class="tw-text-xs tw-px-2" class="tw-text-xs tw-px-2 tw-mb-1"
> >
{{ "familyPlanUpgradeMessage" | i18n }} {{ "familyPlanUpgradeMessage" | i18n }}
</p> </p>
<ul <ul
class="bwi-ul tw-text-xs" class="bwi-ul tw-text-xs tw-mb-1"
*ngIf="selectableProduct.productTier == productTypes.Families" *ngIf="selectableProduct.productTier == productTypes.Families"
> >
<li> <li>
@@ -247,7 +271,7 @@
</li> </li>
</ul> </ul>
<p <p
class="tw-text-xs tw-px-2 tw-font-semibold" class="tw-text-xs tw-px-2 tw-font-semibold tw-mb-1"
*ngIf=" *ngIf="
organization.useSecretsManager && organization.useSecretsManager &&
selectableProduct.productTier !== productTypes.Families selectableProduct.productTier !== productTypes.Families
@@ -283,16 +307,16 @@
<bit-callout <bit-callout
*ngIf="organization.useSecretsManager && !isSecretsManagerTrial()" *ngIf="organization.useSecretsManager && !isSecretsManagerTrial()"
type="info" type="info"
title="INFO" title="SECRETS MANAGER SUBSCRIPTION"
> >
{{ "secretsManagerSubInfo" | i18n }} {{ "secretsManagerSubInfo" | i18n }}
</bit-callout> </bit-callout>
<bit-callout <bit-callout
*ngIf="organization.useSecretsManager && isSecretsManagerTrial()" *ngIf="organization.useSecretsManager && isSecretsManagerTrial()"
type="info" type="info"
title="INFO" title="PASSWORD MANAGER SUBSCRIPTION"
> >
{{ "secretsManagerWithFreePasswordManagerInfo" | i18n }} {{ "secretsManagerComplimentaryPasswordManager" | i18n }}
</bit-callout> </bit-callout>
<br /> <br />
</ng-container> </ng-container>
@@ -392,23 +416,37 @@
<p <p
class="tw-mb-0 tw-flex tw-justify-between" class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2" bitTypography="body2"
*ngIf=" *ngIf="selectedPlan.PasswordManager.hasAdditionalStorageOption && storageGb > 0"
selectedPlan.PasswordManager.hasAdditionalStorageOption &&
!organization.useSecretsManager &&
organization.maxStorageGb > 0
"
> >
<span> <span>
{{ organization.maxStorageGb }} {{ storageGb }}
{{ "additionalStorageGbMessage" | i18n }} {{ "additionalStorageGbMessage" | i18n }}
&times; &times;
{{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }}
/{{ "year" | i18n }} /{{ "year" | i18n }}
</span> </span>
<span>{{ <span>{{ additionalStorageTotal(selectedPlan) | currency: "$" }}</span>
organization.maxStorageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb </p>
| currency: "$" <!--Discount PM Annual-->
}}</span> <p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="organization.useSecretsManager && !isSecretsManagerTrial()"
>
<ng-container *ngIf="selectedInterval == planIntervals.Annually">
<span class="tw-text-xs">
{{
"providerDiscount"
| i18n: this.discountPercentageFromSub + this.discountPercentage
| lowercase
}}
</span>
<span class="tw-line-through tw-text-xs">{{
calculateTotalAppliedDiscount(
passwordManagerSeatTotal(selectedPlan) + additionalStorageTotal(selectedPlan)
) | currency: "$"
}}</span>
</ng-container>
</p> </p>
<!-- secrets manager summary for annual --> <!-- secrets manager summary for annual -->
<p class="tw-font-semibold tw-mt-3 tw-mb-1" *ngIf="organization.useSecretsManager"> <p class="tw-font-semibold tw-mt-3 tw-mb-1" *ngIf="organization.useSecretsManager">
@@ -459,18 +497,40 @@
bitTypography="body2" bitTypography="body2"
*ngIf=" *ngIf="
selectedPlan?.SecretsManager?.hasAdditionalServiceAccountOption && selectedPlan?.SecretsManager?.hasAdditionalServiceAccountOption &&
additionalServiceAccount additionalServiceAccount > 0
" "
> >
<span> <span>
{{ additionalServiceAccount }} {{ additionalServiceAccount }}
{{ "additionalStorageGbMessage" | i18n }} {{ "serviceAccounts" | i18n | lowercase }}
&times; &times;
{{ selectedPlan?.SecretsManager?.additionalPricePerServiceAccount | currency: "$" }} {{ selectedPlan?.SecretsManager?.additionalPricePerServiceAccount | currency: "$" }}
/{{ "month" | i18n }} /{{ "month" | i18n }}
</span> </span>
<span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span> <span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span>
</p> </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">
<span class="tw-text-xs">
{{
"providerDiscount"
| i18n: this.discountPercentageFromSub + this.discountPercentage
| 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>
<bit-hint class="col-6" *ngIf="selectedInterval == planIntervals.Monthly"> <bit-hint class="col-6" *ngIf="selectedInterval == planIntervals.Monthly">
<p class="tw-font-semibold tw-mb-1" *ngIf="organization.useSecretsManager"> <p class="tw-font-semibold tw-mb-1" *ngIf="organization.useSecretsManager">
@@ -512,24 +572,39 @@
<p <p
class="tw-mb-0 tw-flex tw-justify-between" class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2" bitTypography="body2"
*ngIf=" *ngIf="selectedPlan.PasswordManager.hasAdditionalStorageOption && storageGb > 0"
selectedPlan.PasswordManager.hasAdditionalStorageOption &&
!organization.useSecretsManager &&
organization.maxStorageGb > 0
"
> >
<span> <span>
{{ organization.maxStorageGb }} {{ storageGb }}
{{ "additionalStorageGbMessage" | i18n }} {{ "additionalStorageGbMessage" | i18n }}
&times; &times;
{{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }} {{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }}
/{{ "month" | i18n }} /{{ "month" | i18n }}
</span> </span>
<span>{{ <span>{{
organization.maxStorageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb storageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$"
| currency: "$"
}}</span> }}</span>
</p> </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 --> <!-- secrets manager summary for monthly -->
<p class="tw-font-semibold tw-mt-3 tw-mb-1" *ngIf="organization.useSecretsManager"> <p class="tw-font-semibold tw-mt-3 tw-mb-1" *ngIf="organization.useSecretsManager">
{{ "secretsManager" | i18n }} {{ "secretsManager" | i18n }}
@@ -575,18 +650,41 @@
bitTypography="body2" bitTypography="body2"
*ngIf=" *ngIf="
selectedPlan.SecretsManager.hasAdditionalServiceAccountOption && selectedPlan.SecretsManager.hasAdditionalServiceAccountOption &&
additionalServiceAccount additionalServiceAccount > 0
" "
> >
<span> <span>
{{ additionalServiceAccount }} {{ additionalServiceAccount }}
{{ "additionalStorageGbMessage" | i18n }} {{ "serviceAccounts" | i18n | lowercase }}
&times; &times;
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
/{{ "month" | i18n }} /{{ "month" | i18n }}
</span> </span>
<span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span> <span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span>
</p> </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> </bit-hint>
</div> </div>
<!-- SM + Free PM cost summary --> <!-- SM + Free PM cost summary -->
@@ -641,18 +739,40 @@
bitTypography="body2" bitTypography="body2"
*ngIf=" *ngIf="
selectedPlan.SecretsManager.hasAdditionalServiceAccountOption && selectedPlan.SecretsManager.hasAdditionalServiceAccountOption &&
additionalServiceAccount additionalServiceAccount > 0
" "
> >
<span> <span>
{{ additionalServiceAccount }} {{ additionalServiceAccount }}
{{ "additionalStorageGbMessage" | i18n }} {{ "serviceAccounts" | i18n }}
&times; &times;
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
/{{ "month" | i18n }} /{{ "month" | i18n }}
</span> </span>
<span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span> <span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span>
</p> </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">
<span class="tw-text-xs">
{{
"providerDiscount"
| i18n: this.discountPercentageFromSub + this.discountPercentage
| lowercase
}}
</span>
<span class="tw-line-through tw-text-xs">{{
calculateTotalAppliedDiscount(
additionalServiceAccountTotal(selectedPlan) +
secretsManagerSeatTotal(selectedPlan, sub.smSeats)
) | currency: "$"
}}</span>
</ng-container>
</p>
<!-- password manager summary for annual --> <!-- password manager summary for annual -->
<p class="tw-font-semibold tw-mt-3 tw-mb-0" *ngIf="organization.useSecretsManager"> <p class="tw-font-semibold tw-mt-3 tw-mb-0" *ngIf="organization.useSecretsManager">
{{ "passwordManager" | i18n }} {{ "passwordManager" | i18n }}
@@ -663,7 +783,7 @@
*ngIf="selectedPlan.PasswordManager.basePrice" *ngIf="selectedPlan.PasswordManager.basePrice"
> >
<span> <span>
{{ organization.seats }} {{ sub?.seats }}
{{ "members" | i18n }} &times; {{ "members" | i18n }} &times;
{{ {{
(selectedPlan.isAnnual (selectedPlan.isAnnual
@@ -694,7 +814,7 @@
<span *ngIf="selectedPlan.PasswordManager.baseSeats" <span *ngIf="selectedPlan.PasswordManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span >{{ "additionalUsers" | i18n }}:</span
> >
{{ organization.seats || 0 }}&nbsp; {{ sub?.seats || 0 }}&nbsp;
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span> <span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
&times; &times;
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
@@ -756,12 +876,12 @@
bitTypography="body2" bitTypography="body2"
*ngIf=" *ngIf="
selectedPlan.SecretsManager.hasAdditionalServiceAccountOption && selectedPlan.SecretsManager.hasAdditionalServiceAccountOption &&
additionalServiceAccount additionalServiceAccount > 0
" "
> >
<span> <span>
{{ additionalServiceAccount }} {{ additionalServiceAccount }}
{{ "additionalStorageGbMessage" | i18n }} {{ "serviceAccounts" | i18n }}
&times; &times;
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }} {{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
/{{ "month" | i18n }} /{{ "month" | i18n }}
@@ -795,7 +915,7 @@
<span *ngIf="selectedPlan.PasswordManager.baseSeats" <span *ngIf="selectedPlan.PasswordManager.baseSeats"
>{{ "additionalUsers" | i18n }}:</span >{{ "additionalUsers" | i18n }}:</span
> >
{{ organization.seats }}&nbsp; {{ sub?.seats }}&nbsp;
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span> <span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
&times; &times;
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }} {{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
@@ -811,6 +931,46 @@
</p> </p>
</bit-hint> </bit-hint>
</div> </div>
<!-- discountPercentage to PM Only -->
<div
*ngIf="totalOpened && discountPercentage && !organization.useSecretsManager"
class="row"
>
<bit-hint class="col-6">
<p class="tw-mb-0 tw-flex tw-justify-between" bitTypography="body2">
<ng-container
*ngIf="
selectedInterval == planIntervals.Annually;
else MonthlyOrAnnuallyWithDiscount
"
>
<span class="tw-text-xs">
{{
"providerDiscount"
| i18n: this.discountPercentageFromSub + this.discountPercentage
| lowercase
}}
</span>
<span class="tw-line-through tw-text-xs">{{
calculateTotalAppliedDiscount(total) | currency: "$"
}}</span>
</ng-container>
<ng-template #MonthlyOrAnnuallyWithDiscount>
<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-template>
</p>
</bit-hint>
</div>
<div *ngIf="totalOpened" id="price" class="row tw-mt-4"> <div *ngIf="totalOpened" id="price" class="row tw-mt-4">
<bit-hint class="col-6"> <bit-hint class="col-6">
<p <p
@@ -821,7 +981,9 @@
</span> </span>
<span> <span>
{{ total | currency: "USD" : "$" }} {{ total | currency: "USD" : "$" }}
<span class="tw-text-xs tw-font-light"> / {{ selectedPlanInterval | i18n }}</span> <span class="tw-text-xs tw-font-semibold">
/ {{ selectedPlanInterval | i18n }}</span
>
</span> </span>
</p> </p>
</bit-hint> </bit-hint>

View File

@@ -246,27 +246,28 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
selected: false, selected: false,
}, },
]; ];
this.discountPercentageFromSub = this.sub?.customerDiscount?.percentOff; this.discountPercentageFromSub = this.isSecretsManagerTrial()
? 0
: (this.sub?.customerDiscount?.percentOff ?? 0);
this.setInitialPlanSelection(); this.setInitialPlanSelection();
this.loading = false; this.loading = false;
} }
setInitialPlanSelection() { setInitialPlanSelection() {
if ( this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
this.organization.useSecretsManager &&
this.currentPlan.productTier == ProductTierType.Free
) {
this.selectPlan(this.getPlanByType(ProductTierType.Teams));
} else {
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
}
} }
getPlanByType(productTier: ProductTierType) { getPlanByType(productTier: ProductTierType) {
return this.selectableProducts.find((product) => product.productTier === productTier); return this.selectableProducts.find((product) => product.productTier === productTier);
} }
secretsManagerTrialDiscount() {
return this.sub?.customerDiscount?.appliesTo?.includes("sm-standalone")
? this.discountPercentage
: this.discountPercentageFromSub + this.discountPercentage;
}
isSecretsManagerTrial(): boolean { isSecretsManagerTrial(): boolean {
return ( return (
this.sub?.subscription?.items?.some((item) => this.sub?.subscription?.items?.some((item) =>
@@ -276,14 +277,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
} }
planTypeChanged() { planTypeChanged() {
if ( this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
this.organization.useSecretsManager &&
this.currentPlan.productTier == ProductTierType.Free
) {
this.selectPlan(this.getPlanByType(ProductTierType.Teams));
} else {
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
}
} }
updateInterval(event: number) { updateInterval(event: number) {
@@ -304,6 +298,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
]; ];
} }
optimizedNgForRender(index: number) {
return index;
}
protected getPlanCardContainerClasses(plan: PlanResponse, index: number) { protected getPlanCardContainerClasses(plan: PlanResponse, index: number) {
let cardState: PlanCardState; let cardState: PlanCardState;
@@ -370,6 +368,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
) { ) {
return; return;
} }
if (plan === this.currentPlan) {
return;
}
this.selectedPlan = plan; this.selectedPlan = plan;
this.formGroup.patchValue({ productTier: plan.productTier }); this.formGroup.patchValue({ productTier: plan.productTier });
} }
@@ -463,6 +465,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
return result; return result;
} }
get storageGb() {
return this.sub?.maxStorageGb - 1;
}
passwordManagerSeatTotal(plan: PlanResponse): number { passwordManagerSeatTotal(plan: PlanResponse): number {
if (!plan.PasswordManager.hasAdditionalSeatsOption || this.isSecretsManagerTrial()) { if (!plan.PasswordManager.hasAdditionalSeatsOption || this.isSecretsManagerTrial()) {
return 0; return 0;
@@ -486,8 +492,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
} }
return ( return (
plan.PasswordManager.additionalStoragePricePerGb * plan.PasswordManager.additionalStoragePricePerGb * Math.abs(this.sub?.maxStorageGb - 1 || 0)
Math.abs(this.organization.maxStorageGb || 0)
); );
} }
@@ -499,7 +504,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
} }
additionalServiceAccountTotal(plan: PlanResponse): number { additionalServiceAccountTotal(plan: PlanResponse): number {
if (!plan.SecretsManager.hasAdditionalServiceAccountOption || this.additionalServiceAccount) { if (
!plan.SecretsManager.hasAdditionalServiceAccountOption ||
this.additionalServiceAccount == 0
) {
return 0; return 0;
} }
@@ -541,7 +549,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
if (this.selectedPlan.productTier === ProductTierType.Families) { if (this.selectedPlan.productTier === ProductTierType.Families) {
return this.selectedPlan.PasswordManager.baseSeats; return this.selectedPlan.PasswordManager.baseSeats;
} }
return this.organization.seats; return this.sub?.seats;
} }
get total() { get total() {
@@ -565,7 +573,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
} }
get additionalServiceAccount() { get additionalServiceAccount() {
const baseServiceAccount = this.selectedPlan.SecretsManager?.baseServiceAccount || 0; const baseServiceAccount = this.currentPlan.SecretsManager?.baseServiceAccount || 0;
const usedServiceAccounts = this.sub?.smServiceAccounts || 0; const usedServiceAccounts = this.sub?.smServiceAccounts || 0;
const additionalServiceAccounts = baseServiceAccount - usedServiceAccounts; const additionalServiceAccounts = baseServiceAccount - usedServiceAccounts;
@@ -652,7 +660,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
if (!this.acceptingSponsorship && !this.isInTrialFlow) { if (!this.acceptingSponsorship && !this.isInTrialFlow) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/organizations/" + orgId]); this.router.navigate(["/organizations/" + orgId + "/members"]);
} }
if (this.isInTrialFlow) { if (this.isInTrialFlow) {
@@ -676,11 +684,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
private async updateOrganization() { private async updateOrganization() {
const request = new OrganizationUpgradeRequest(); const request = new OrganizationUpgradeRequest();
if (this.selectedPlan.productTier !== ProductTierType.Families) { if (this.selectedPlan.productTier !== ProductTierType.Families) {
request.additionalSeats = this.organization.seats; request.additionalSeats = this.sub?.seats;
} }
if (this.organization.maxStorageGb > this.selectedPlan.PasswordManager.baseStorageGb) { if (this.sub?.maxStorageGb > this.selectedPlan.PasswordManager.baseStorageGb) {
request.additionalStorageGb = request.additionalStorageGb =
this.organization.maxStorageGb - this.selectedPlan.PasswordManager.baseStorageGb; this.sub?.maxStorageGb - this.selectedPlan.PasswordManager.baseStorageGb;
} }
request.premiumAccessAddon = request.premiumAccessAddon =
this.selectedPlan.PasswordManager.hasPremiumAccessOption && this.selectedPlan.PasswordManager.hasPremiumAccessOption &&
@@ -768,6 +776,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
request.additionalSmSeats = this.organization.seats; request.additionalSmSeats = this.organization.seats;
} else { } else {
request.additionalSmSeats = this.sub?.smSeats; request.additionalSmSeats = this.sub?.smSeats;
request.additionalServiceAccounts = this.additionalServiceAccount;
} }
} }
@@ -812,6 +821,16 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
this.totalOpened = !this.totalOpened; this.totalOpened = !this.totalOpened;
} }
calculateTotalAppliedDiscount(total: number) {
const discountPercent =
this.selectedInterval == PlanInterval.Annually
? this.discountPercentage + this.discountPercentageFromSub
: this.discountPercentageFromSub;
const discountedTotal = total / (1 - discountPercent / 100);
return discountedTotal;
}
get paymentSourceClasses() { get paymentSourceClasses() {
if (this.billing.paymentSource == null) { if (this.billing.paymentSource == null) {
return []; return [];

View File

@@ -69,14 +69,25 @@
></app-subscription-status> ></app-subscription-status>
<ng-container *ngIf="userOrg.canEditSubscription"> <ng-container *ngIf="userOrg.canEditSubscription">
<div class="tw-flex-col"> <div class="tw-flex-col">
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300">{{ <strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300"
"details" | i18n >{{ "details" | i18n
}}</strong> }}<span
class="tw-ml-3"
*ngIf="customerDiscount?.percentOff > 0 && !isSecretsManagerTrial()"
bitBadge
variant="success"
>{{ "providerDiscount" | i18n: customerDiscount?.percentOff }}</span
></strong
>
<bit-table> <bit-table>
<ng-template body> <ng-template body>
<ng-container *ngIf="subscription"> <ng-container *ngIf="subscription">
<tr bitRow *ngFor="let i of subscriptionLineItems"> <tr bitRow *ngFor="let i of subscriptionLineItems">
<td bitCell [ngClass]="{ 'tw-pl-20': i.addonSubscriptionItem }"> <td
bitCell
[ngClass]="{ 'tw-pl-20': i.addonSubscriptionItem }"
class="tw-align-middle"
>
<span *ngIf="!i.addonSubscriptionItem">{{ i.productName | i18n }} -</span> <span *ngIf="!i.addonSubscriptionItem">{{ i.productName | i18n }} -</span>
{{ i.name }} {{ i.quantity > 1 ? "&times;" + i.quantity : "" }} &#64; {{ i.name }} {{ i.quantity > 1 ? "&times;" + i.quantity : "" }} &#64;
{{ i.amount | currency: "$" }} {{ i.amount | currency: "$" }}
@@ -91,7 +102,19 @@
{{ "freeForOneYear" | i18n }} {{ "freeForOneYear" | i18n }}
</ng-container> </ng-container>
<ng-template #calculateElse> <ng-template #calculateElse>
{{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }} <div class="tw-flex tw-flex-col">
<span>
{{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}
</span>
<span
*ngIf="customerDiscount?.percentOff && !isSecretsManagerTrial()"
class="tw-line-through !tw-text-muted"
>{{
calculateTotalAppliedDiscount(i.quantity * i.amount) | currency: "$"
}}
/ {{ "year" | i18n }}</span
>
</div>
</ng-template> </ng-template>
</td> </td>
</tr> </tr>
@@ -112,7 +135,7 @@
</ng-container> </ng-container>
<ng-container *ngIf="userOrg.canEditSubscription"> <ng-container *ngIf="userOrg.canEditSubscription">
<div class="tw-mt-7"> <div class="tw-mt-5">
<button <button
bitButton bitButton
buttonType="secondary" buttonType="secondary"

View File

@@ -430,6 +430,14 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
} }
} }
isSecretsManagerTrial(): boolean {
return (
this.sub?.subscription?.items?.some((item) =>
this.sub?.customerDiscount?.appliesTo?.includes(item.productId),
) ?? false
);
}
closeChangePlan() { closeChangePlan() {
this.showChangePlan = false; this.showChangePlan = false;
} }
@@ -464,6 +472,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.load(); this.load();
} }
calculateTotalAppliedDiscount(total: number) {
const discountedTotal = total / (1 - this.customerDiscount?.percentOff / 100);
return discountedTotal;
}
adjustStorage = (add: boolean) => { adjustStorage = (add: boolean) => {
return async () => { return async () => {
const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$); const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$);

View File

@@ -9076,8 +9076,8 @@
"bitwardenPasswordManager": { "bitwardenPasswordManager": {
"message": "Bitwarden Password Manager" "message": "Bitwarden Password Manager"
}, },
"secretsManagerWithFreePasswordManagerInfo": { "secretsManagerComplimentaryPasswordManager": {
"message": "Your complementary one year Password Manager subscription will upgrade to the selected plan. You will not be charged until the complimentary period is over." "message": "Your complimentary one year Password Manager subscription will upgrade to the selected plan. You will not be charged until the complimentary period is over."
}, },
"fileSavedToDevice": { "fileSavedToDevice": {
"message": "File saved to device. Manage from your device downloads." "message": "File saved to device. Manage from your device downloads."