mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +00:00
[AC-2708][AC-2712][AC-2713] upgrading from a Organizations new design changes (#10662)
* Changes base on the new design * changes base on the new design * Fix the family plan summary issue
This commit is contained in:
@@ -5,40 +5,40 @@
|
|||||||
</span>
|
</span>
|
||||||
<div bitDialogContent>
|
<div bitDialogContent>
|
||||||
<p>{{ "upgradePlan" | i18n }}</p>
|
<p>{{ "upgradePlan" | i18n }}</p>
|
||||||
<div class="tw-mb-3">
|
<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>
|
||||||
<bit-radio-group
|
<div class="tw-items-center tw-gap-2">
|
||||||
formControlName="planInterval"
|
<span
|
||||||
class="tw-flex tw-items-start tw-gap-5"
|
*ngIf="
|
||||||
(change)="planTypeChanged()"
|
this.discountPercentageFromSub > 0
|
||||||
>
|
? discountPercentageFromSub
|
||||||
<bit-radio-button
|
: this.discountPercentage && selectedInterval === 1
|
||||||
class="tw-inline-block"
|
"
|
||||||
*ngFor="let planInterval of getPlanIntervals()"
|
bitBadge
|
||||||
id="plan-annually"
|
variant="success"
|
||||||
[value]="planInterval.value"
|
>{{
|
||||||
|
"upgradeDiscount"
|
||||||
|
| i18n
|
||||||
|
: (this.discountPercentageFromSub > 0
|
||||||
|
? discountPercentageFromSub
|
||||||
|
: this.discountPercentage)
|
||||||
|
}}</span
|
||||||
>
|
>
|
||||||
<bit-label>
|
|
||||||
<i class="bwi" aria-hidden="true"></i>
|
<div class="tw-inline-block">
|
||||||
{{ planInterval.name }} <span
|
<bit-toggle-group
|
||||||
*ngIf="
|
[selected]="selectedInterval"
|
||||||
this.discountPercentageFromSub > 0
|
(selectedChange)="updateInterval($event)"
|
||||||
? discountPercentageFromSub
|
|
||||||
: this.discountPercentage && planInterval.value === 1
|
|
||||||
"
|
|
||||||
bitBadge
|
|
||||||
variant="success"
|
|
||||||
>{{
|
|
||||||
"upgradeDiscount"
|
|
||||||
| i18n
|
|
||||||
: (this.discountPercentageFromSub > 0
|
|
||||||
? discountPercentageFromSub
|
|
||||||
: this.discountPercentage)
|
|
||||||
}}</span
|
|
||||||
></bit-label
|
|
||||||
>
|
>
|
||||||
</bit-radio-button>
|
<bit-toggle
|
||||||
</bit-radio-group>
|
*ngFor="let planInterval of getPlanIntervals()"
|
||||||
|
[value]="planInterval.value"
|
||||||
|
>
|
||||||
|
{{ planInterval.name }}
|
||||||
|
</bit-toggle>
|
||||||
|
</bit-toggle-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="!loading && !selfHosted && this.passwordManagerPlans">
|
<ng-container *ngIf="!loading && !selfHosted && this.passwordManagerPlans">
|
||||||
<div
|
<div
|
||||||
@@ -52,18 +52,17 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div class="tw-relative">
|
<div class="tw-relative">
|
||||||
<div
|
|
||||||
*ngIf="selectableProduct == selectedPlan"
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{{ "selected" | i18n }}
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="tw-px-2 tw-py-4"
|
class="tw-px-2 tw-py-4"
|
||||||
[ngClass]="{ 'tw-pt-10': !(selectableProduct == selectedPlan) }"
|
[ngClass]="{ 'tw-pt-10': !(selectableProduct == selectedPlan) }"
|
||||||
>
|
>
|
||||||
<h3 class="tw-text-lg tw-font-bold tw-uppercase">
|
<h3 class="tw-text-lg tw-font-bold">
|
||||||
{{ selectableProduct.nameLocalizationKey | i18n }}
|
<span class="tw-uppercase">{{
|
||||||
|
selectableProduct.nameLocalizationKey | i18n
|
||||||
|
}}</span>
|
||||||
|
<span bitBadge variant="secondary" *ngIf="selectableProduct === currentPlan">
|
||||||
|
{{ "current" | i18n }}</span
|
||||||
|
>
|
||||||
</h3>
|
</h3>
|
||||||
<span *ngIf="selectableProduct.productTier != productTypes.Free">
|
<span *ngIf="selectableProduct.productTier != productTypes.Free">
|
||||||
<ng-container
|
<ng-container
|
||||||
@@ -126,16 +125,50 @@
|
|||||||
else nonEnterprisePlans
|
else nonEnterprisePlans
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
<p class="tw-text-xs tw-px-2 tw-font-semibold" *ngIf="organization.useSecretsManager">
|
||||||
|
{{ "bitwardenPasswordManager" | i18n }}
|
||||||
|
</p>
|
||||||
<p class="tw-text-xs tw-px-2">{{ "upgradeEnterpriseMessage" | i18n }}</p>
|
<p class="tw-text-xs tw-px-2">{{ "upgradeEnterpriseMessage" | i18n }}</p>
|
||||||
<p class="tw-text-xs tw-px-2 tw-mb-0">{{ "includeAllTeamsFeatures" | i18n }}</p>
|
|
||||||
<ul class="tw-px-3 tw-pb-2 tw-list-inside tw-mb-0 tw-text-xs">
|
<ul class="bwi-ul tw-text-xs">
|
||||||
<li *ngIf="selectableProduct.hasPolicies">
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
{{ "includeEnterprisePolicies" | i18n }}
|
{{ "includeEnterprisePolicies" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="selectableProduct.hasSso">
|
<li>
|
||||||
{{ "includeSsoAuthenticationMessage" | i18n }}
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "passwordLessSso" | i18n }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "accountRecovery" | i18n }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "customRoles" | i18n }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p class="tw-text-xs tw-px-2 tw-font-semibold" *ngIf="organization.useSecretsManager">
|
||||||
|
{{ "bitwardenSecretsManager" | i18n }}
|
||||||
|
</p>
|
||||||
|
<ul class="bwi-ul tw-text-xs" *ngIf="organization.useSecretsManager">
|
||||||
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "unlimitedSecretsStorage" | i18n }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "unlimitedUsers" | i18n }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "unlimitedProjects" | i18n }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "UpTo50MachineAccounts" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="selectableProduct.hasSelfHost">{{ "optionalOnPremHosting" | i18n }}</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #nonEnterprisePlans>
|
<ng-template #nonEnterprisePlans>
|
||||||
@@ -153,6 +186,12 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #fullFeatureList>
|
<ng-template #fullFeatureList>
|
||||||
|
<p
|
||||||
|
class="tw-text-xs tw-px-2 tw-font-semibold"
|
||||||
|
*ngIf="organization.useSecretsManager"
|
||||||
|
>
|
||||||
|
{{ "bitwardenPasswordManager" | i18n }}
|
||||||
|
</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"
|
||||||
@@ -165,44 +204,90 @@
|
|||||||
>
|
>
|
||||||
{{ "upgradeFamilyMessage" | i18n }}
|
{{ "upgradeFamilyMessage" | i18n }}
|
||||||
</p>
|
</p>
|
||||||
<ul class="tw-px-2 tw-pb-2 tw-list-inside tw-mb-0 tw-text-xs">
|
<ul
|
||||||
<li *ngIf="selectableProduct.productTier == productTypes.Free">
|
class="bwi-ul tw-text-xs"
|
||||||
{{ "limitedUsers" | i18n: selectableProduct.PasswordManager.maxSeats }}
|
*ngIf="selectableProduct.productTier == productTypes.Families"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "premiumAccounts" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="!selectableProduct.PasswordManager.maxSeats">
|
<li>
|
||||||
{{ "teamsInviteMessage" | i18n }}
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "unlimitedSharing" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="selectableProduct.PasswordManager.maxCollections">
|
<li>
|
||||||
{{
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
"chooseMonthlyOrAnnualBilling"
|
{{ "unlimitedCollections" | i18n }}
|
||||||
| i18n: selectableProduct.PasswordManager.maxCollections
|
|
||||||
}}
|
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="!selectableProduct.PasswordManager.maxCollections">
|
</ul>
|
||||||
{{ "createUnlimitedCollections" | i18n }}
|
<ul
|
||||||
|
class="bwi-ul tw-text-xs"
|
||||||
|
*ngIf="selectableProduct.productTier == productTypes.Teams"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "secureDataSharing" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="selectableProduct.hasGroups">
|
<li>
|
||||||
{{ "accessToCreateGroups" | i18n }}
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "eventLogMonitoring" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="selectableProduct.hasDirectory">
|
<li>
|
||||||
{{ "syncGroupsAndUsersFromDirectory" | i18n }}
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "directoryIntegration" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="selectableProduct.productTier == productTypes.Families">
|
</ul>
|
||||||
{{ "accessToPremiumFeatures" | i18n }}
|
<p
|
||||||
|
class="tw-text-xs tw-px-2 tw-font-semibold"
|
||||||
|
*ngIf="
|
||||||
|
organization.useSecretsManager &&
|
||||||
|
selectableProduct.productTier !== productTypes.Families
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ "bitwardenSecretsManager" | i18n }}
|
||||||
|
</p>
|
||||||
|
<ul
|
||||||
|
class="bwi-ul tw-text-xs"
|
||||||
|
*ngIf="
|
||||||
|
organization.useSecretsManager &&
|
||||||
|
selectableProduct.productTier == productTypes.Teams
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "unlimitedSecretsStorage" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="selectableProduct.productTier == productTypes.Families">
|
<li>
|
||||||
{{ "priorityCustomerSupport" | i18n }}
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "unlimitedProjects" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
<li *ngIf="selectableProduct.hasSelfHost">
|
<li>
|
||||||
{{ "optionalOnPremHosting" | i18n }}
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||||
|
{{ "UpTo20MachineAccounts" | i18n }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
|
<bit-callout
|
||||||
|
*ngIf="organization.useSecretsManager && !isSecretsManagerTrial()"
|
||||||
|
type="info"
|
||||||
|
title="INFO"
|
||||||
|
>
|
||||||
|
{{ "secretsManagerSubInfo" | i18n }}
|
||||||
|
</bit-callout>
|
||||||
|
<bit-callout
|
||||||
|
*ngIf="organization.useSecretsManager && isSecretsManagerTrial()"
|
||||||
|
type="info"
|
||||||
|
title="INFO"
|
||||||
|
>
|
||||||
|
{{ "secretsManagerWithFreePasswordManagerInfo" | i18n }}
|
||||||
|
</bit-callout>
|
||||||
|
<br />
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Payment info -->
|
<!-- Payment info -->
|
||||||
<ng-container *ngIf="formGroup.value.productTier !== productTypes.Free">
|
<ng-container *ngIf="formGroup.value.productTier !== productTypes.Free">
|
||||||
<h2 bitTypography="h4">{{ "paymentMethod" | i18n }}</h2>
|
<h2 bitTypography="h4">{{ "paymentMethod" | i18n }}</h2>
|
||||||
@@ -237,16 +322,327 @@
|
|||||||
></button>
|
></button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- SM + PM and PM only cost summary -->
|
||||||
<div *ngIf="totalOpened" class="row">
|
<div *ngIf="totalOpened && !isSecretsManagerTrial()" class="row">
|
||||||
<bit-hint class="col-6" *ngIf="selectedInterval == planIntervals.Annually">
|
<bit-hint class="col-6" *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: "$"
|
||||||
|
}}
|
||||||
|
/{{ "year" | 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: "$" }}
|
||||||
|
/{{ "year" | i18n }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
class="tw-mb-0 tw-flex tw-justify-between"
|
||||||
|
bitTypography="body2"
|
||||||
|
*ngIf="
|
||||||
|
selectedPlan.PasswordManager.hasAdditionalStorageOption &&
|
||||||
|
!organization.useSecretsManager
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ 0 }}
|
||||||
|
{{ "additionalStorageGbMessage" | i18n }}
|
||||||
|
×
|
||||||
|
{{ selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$" }}
|
||||||
|
/{{ "year" | i18n }}
|
||||||
|
</span>
|
||||||
|
<span>{{ 0 | currency: "$" }}</span>
|
||||||
|
</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: "$"
|
||||||
|
}}
|
||||||
|
/{{ "year" | 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: "$" }}
|
||||||
|
/{{ "year" | 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
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ additionalServiceAccount }}
|
||||||
|
{{ "additionalStorageGbMessage" | i18n }}
|
||||||
|
×
|
||||||
|
{{ selectedPlan?.SecretsManager?.additionalPricePerServiceAccount | currency: "$" }}
|
||||||
|
/{{ "month" | i18n }}
|
||||||
|
</span>
|
||||||
|
<span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span>
|
||||||
|
</p>
|
||||||
|
</bit-hint>
|
||||||
|
<bit-hint class="col-6" *ngIf="selectedInterval == planIntervals.Monthly">
|
||||||
|
<p class="tw-font-semibold tw-mb-1" *ngIf="organization.useSecretsManager">
|
||||||
|
{{ "passwordManager" | i18n }}
|
||||||
|
</p>
|
||||||
<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="selectedPlan.PasswordManager.basePrice"
|
*ngIf="selectedPlan.PasswordManager.basePrice"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{{ selectedPlan.PasswordManager.baseSeats }}
|
{{ "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: "$" }}
|
||||||
|
/{{ "month" | i18n }}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
class="tw-mb-0 tw-flex tw-justify-between"
|
||||||
|
bitTypography="body2"
|
||||||
|
*ngIf="
|
||||||
|
selectedPlan.PasswordManager.hasAdditionalStorageOption &&
|
||||||
|
!organization.useSecretsManager
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ 0 }}
|
||||||
|
{{ "additionalStorageGbMessage" | i18n }}
|
||||||
|
×
|
||||||
|
{{ selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$" }}
|
||||||
|
/{{ "month" | i18n }}
|
||||||
|
</span>
|
||||||
|
<span>{{ 0 | currency: "$" }}</span>
|
||||||
|
</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: "$" }}
|
||||||
|
/{{ "month" | 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
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ additionalServiceAccount }}
|
||||||
|
{{ "additionalStorageGbMessage" | i18n }}
|
||||||
|
×
|
||||||
|
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
|
||||||
|
/{{ "month" | i18n }}
|
||||||
|
</span>
|
||||||
|
<span>{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}</span>
|
||||||
|
</p>
|
||||||
|
</bit-hint>
|
||||||
|
</div>
|
||||||
|
<!-- SM + Free PM cost summary -->
|
||||||
|
<div *ngIf="totalOpened && isSecretsManagerTrial()" class="row">
|
||||||
|
<bit-hint class="col-6" *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: "$"
|
||||||
|
}}
|
||||||
|
/{{ "year" | 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: "$" }}
|
||||||
|
/{{ "year" | 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
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ additionalServiceAccount }}
|
||||||
|
{{ "additionalStorageGbMessage" | i18n }}
|
||||||
|
×
|
||||||
|
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
|
||||||
|
/{{ "month" | 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>
|
||||||
|
{{ organization.seats }}
|
||||||
{{ "members" | i18n }} ×
|
{{ "members" | i18n }} ×
|
||||||
{{
|
{{
|
||||||
(selectedPlan.isAnnual
|
(selectedPlan.isAnnual
|
||||||
@@ -284,26 +680,77 @@
|
|||||||
/{{ "year" | i18n }}
|
/{{ "year" | i18n }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span *ngIf="isSecretsManagerTrial()">
|
||||||
|
{{ "freeForOneYear" | i18n }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span *ngIf="!isSecretsManagerTrial()">
|
||||||
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
|
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
</bit-hint>
|
||||||
|
<bit-hint class="col-6" *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
|
<p
|
||||||
class="tw-mb-0 tw-flex tw-justify-between"
|
class="tw-mb-0 tw-flex tw-justify-between"
|
||||||
bitTypography="body2"
|
bitTypography="body2"
|
||||||
*ngIf="selectedPlan.PasswordManager.hasAdditionalStorageOption"
|
*ngIf="
|
||||||
|
selectedPlan.SecretsManager.hasAdditionalSeatsOption &&
|
||||||
|
organization.useSecretsManager
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{{ 0 }}
|
<span *ngIf="selectedPlan.SecretsManager.baseSeats"
|
||||||
|
>{{ "additionalUsers" | i18n }}:</span
|
||||||
|
>
|
||||||
|
{{ sub?.smSeats }}
|
||||||
|
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
|
||||||
|
×
|
||||||
|
{{ selectedPlan.SecretsManager.seatPrice | currency: "$" }}
|
||||||
|
/{{ "month" | 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
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ additionalServiceAccount }}
|
||||||
{{ "additionalStorageGbMessage" | i18n }}
|
{{ "additionalStorageGbMessage" | i18n }}
|
||||||
×
|
×
|
||||||
{{ selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$" }}
|
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
|
||||||
/{{ "year" | i18n }}
|
/{{ "month" | i18n }}
|
||||||
</span>
|
</span>
|
||||||
<span>{{ 0 | currency: "$" }}</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>
|
||||||
</bit-hint>
|
|
||||||
<bit-hint class="col-6" *ngIf="selectedInterval == planIntervals.Monthly">
|
|
||||||
<p
|
<p
|
||||||
class="tw-mb-0 tw-flex tw-justify-between"
|
class="tw-mb-0 tw-flex tw-justify-between"
|
||||||
bitTypography="body2"
|
bitTypography="body2"
|
||||||
@@ -327,33 +774,22 @@
|
|||||||
<span *ngIf="selectedPlan.PasswordManager.baseSeats"
|
<span *ngIf="selectedPlan.PasswordManager.baseSeats"
|
||||||
>{{ "additionalUsers" | i18n }}:</span
|
>{{ "additionalUsers" | i18n }}:</span
|
||||||
>
|
>
|
||||||
{{ formGroup.controls["additionalSeats"].value || 0 }}
|
{{ organization.seats }}
|
||||||
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
|
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
|
||||||
×
|
×
|
||||||
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
|
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
|
||||||
/{{ "month" | i18n }}
|
/{{ "month" | i18n }}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span *ngIf="isSecretsManagerTrial()">
|
||||||
|
{{ "freeForOneYear" | i18n }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span *ngIf="!isSecretsManagerTrial()">
|
||||||
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
|
{{ passwordManagerSeatTotal(selectedPlan) | currency: "$" }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p
|
|
||||||
class="tw-mb-0 tw-flex tw-justify-between"
|
|
||||||
bitTypography="body2"
|
|
||||||
*ngIf="selectedPlan.PasswordManager.hasAdditionalStorageOption"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{{ 0 }}
|
|
||||||
{{ "additionalStorageGbMessage" | i18n }}
|
|
||||||
×
|
|
||||||
{{ selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$" }}
|
|
||||||
/{{ "month" | i18n }}
|
|
||||||
</span>
|
|
||||||
<span>{{ 0 | currency: "$" }}</span>
|
|
||||||
</p>
|
|
||||||
</bit-hint>
|
</bit-hint>
|
||||||
</div>
|
</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
|
||||||
@@ -373,7 +809,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ng-container bitDialogFooter>
|
<ng-container bitDialogFooter>
|
||||||
<button bitButton bitFormButton buttonType="primary" type="submit">
|
<button bitButton bitFormButton buttonType="primary" type="submit">
|
||||||
{{ "save" | i18n }}
|
{{ "upgrade" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button bitButton buttonType="secondary" type="button" [bitDialogClose]="ResultType.Closed">
|
<button bitButton buttonType="secondary" type="button" [bitDialogClose]="ResultType.Closed">
|
||||||
{{ "cancel" | i18n }}
|
{{ "cancel" | i18n }}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
clientOwnerEmail: ["", [Validators.email]],
|
clientOwnerEmail: ["", [Validators.email]],
|
||||||
plan: [this.plan],
|
plan: [this.plan],
|
||||||
productTier: [this.productTier],
|
productTier: [this.productTier],
|
||||||
planInterval: [1],
|
// planInterval: [1],
|
||||||
});
|
});
|
||||||
|
|
||||||
planType: string;
|
planType: string;
|
||||||
@@ -146,6 +146,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
selectedInterval: number = 1;
|
selectedInterval: number = 1;
|
||||||
planIntervals = PlanInterval;
|
planIntervals = PlanInterval;
|
||||||
passwordManagerPlans: PlanResponse[];
|
passwordManagerPlans: PlanResponse[];
|
||||||
|
secretsManagerPlans: PlanResponse[];
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
sub: OrganizationSubscriptionResponse;
|
sub: OrganizationSubscriptionResponse;
|
||||||
billing: BillingResponse;
|
billing: BillingResponse;
|
||||||
@@ -188,6 +189,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
if (!this.selfHosted) {
|
if (!this.selfHosted) {
|
||||||
const plans = await this.apiService.getPlans();
|
const plans = await this.apiService.getPlans();
|
||||||
this.passwordManagerPlans = plans.data.filter((plan) => !!plan.PasswordManager);
|
this.passwordManagerPlans = plans.data.filter((plan) => !!plan.PasswordManager);
|
||||||
|
this.secretsManagerPlans = plans.data.filter((plan) => !!plan.SecretsManager);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.productTier === ProductTierType.Enterprise ||
|
this.productTier === ProductTierType.Enterprise ||
|
||||||
@@ -230,12 +232,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
selected: false,
|
selected: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
this.formGroup
|
|
||||||
.get("planInterval")
|
|
||||||
.valueChanges.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe((value: number) => (this.selectedInterval = value));
|
|
||||||
|
|
||||||
this.discountPercentageFromSub = this.sub?.customerDiscount?.percentOff;
|
this.discountPercentageFromSub = this.sub?.customerDiscount?.percentOff;
|
||||||
|
|
||||||
this.setInitialPlanSelection();
|
this.setInitialPlanSelection();
|
||||||
@@ -243,15 +239,42 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setInitialPlanSelection() {
|
setInitialPlanSelection() {
|
||||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
if (
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSecretsManagerTrial(): boolean {
|
||||||
|
return (
|
||||||
|
this.sub?.subscription?.items?.some((item) =>
|
||||||
|
this.sub?.customerDiscount?.appliesTo?.includes(item.productId),
|
||||||
|
) ?? false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
planTypeChanged() {
|
planTypeChanged() {
|
||||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
if (
|
||||||
|
this.organization.useSecretsManager &&
|
||||||
|
this.currentPlan.productTier == ProductTierType.Free
|
||||||
|
) {
|
||||||
|
this.selectPlan(this.getPlanByType(ProductTierType.Teams));
|
||||||
|
} else {
|
||||||
|
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInterval(event: number) {
|
||||||
|
this.selectedInterval = event;
|
||||||
|
this.planTypeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getPlanIntervals() {
|
protected getPlanIntervals() {
|
||||||
@@ -270,7 +293,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
protected getPlanCardContainerClasses(plan: PlanResponse, index: number) {
|
protected getPlanCardContainerClasses(plan: PlanResponse, index: number) {
|
||||||
let cardState: PlanCardState;
|
let cardState: PlanCardState;
|
||||||
|
|
||||||
if (plan == this.selectedPlan) {
|
if (plan == this.currentPlan) {
|
||||||
|
cardState = PlanCardState.Disabled;
|
||||||
|
} else if (plan == this.selectedPlan) {
|
||||||
cardState = PlanCardState.Selected;
|
cardState = PlanCardState.Selected;
|
||||||
} else if (
|
} else if (
|
||||||
this.selectedInterval === PlanInterval.Monthly &&
|
this.selectedInterval === PlanInterval.Monthly &&
|
||||||
@@ -283,36 +308,19 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
switch (cardState) {
|
switch (cardState) {
|
||||||
case PlanCardState.Selected: {
|
case PlanCardState.Selected: {
|
||||||
if (this.currentPlan.productTier === ProductTierType.Teams) {
|
return [
|
||||||
return [
|
"tw-group",
|
||||||
"tw-group",
|
"tw-cursor-pointer",
|
||||||
"tw-cursor-pointer",
|
"tw-block",
|
||||||
"tw-block",
|
"tw-rounded",
|
||||||
"tw-rounded",
|
"tw-border",
|
||||||
"tw-w-1/2",
|
"tw-border-solid",
|
||||||
"tw-border",
|
"tw-border-primary-600",
|
||||||
"tw-border-solid",
|
"hover:tw-border-primary-700",
|
||||||
"tw-border-primary-600",
|
"focus:tw-border-2",
|
||||||
"hover:tw-border-primary-700",
|
"focus:tw-border-primary-700",
|
||||||
"focus:tw-border-2",
|
"focus:tw-rounded-lg",
|
||||||
"focus:tw-border-primary-700",
|
];
|
||||||
"focus:tw-rounded-lg",
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
"tw-group",
|
|
||||||
"tw-cursor-pointer",
|
|
||||||
"tw-block",
|
|
||||||
"tw-rounded",
|
|
||||||
"tw-border",
|
|
||||||
"tw-border-solid",
|
|
||||||
"tw-border-primary-600",
|
|
||||||
"hover:tw-border-primary-700",
|
|
||||||
"focus:tw-border-2",
|
|
||||||
"focus:tw-border-primary-700",
|
|
||||||
"focus:tw-rounded-lg",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case PlanCardState.NotSelected: {
|
case PlanCardState.NotSelected: {
|
||||||
return [
|
return [
|
||||||
@@ -336,10 +344,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
"tw-text-muted",
|
"tw-text-muted",
|
||||||
"tw-block",
|
"tw-block",
|
||||||
"tw-rounded",
|
"tw-rounded",
|
||||||
"tw-border",
|
|
||||||
"tw-border-solid",
|
|
||||||
"tw-border-1",
|
|
||||||
"tw-border-secondary-300",
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,6 +373,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get selectedSecretsManagerPlan() {
|
||||||
|
return this.secretsManagerPlans.find((plan) => plan.type === this.selectedPlan.type);
|
||||||
|
}
|
||||||
|
|
||||||
get selectedPlanInterval() {
|
get selectedPlanInterval() {
|
||||||
return this.selectedPlan.isAnnual ? "year" : "month";
|
return this.selectedPlan.isAnnual ? "year" : "month";
|
||||||
}
|
}
|
||||||
@@ -397,9 +405,37 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.planIsEnabled(plan),
|
this.planIsEnabled(plan),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.currentPlan.productTier === ProductTierType.Free &&
|
||||||
|
this.selectedInterval === PlanInterval.Monthly &&
|
||||||
|
!this.organization.useSecretsManager
|
||||||
|
) {
|
||||||
|
const familyPlan = this.passwordManagerPlans.find(
|
||||||
|
(plan) => plan.productTier == ProductTierType.Families,
|
||||||
|
);
|
||||||
|
result.push(familyPlan);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.organization.useSecretsManager &&
|
||||||
|
this.currentPlan.productTier === ProductTierType.Free
|
||||||
|
) {
|
||||||
|
const familyPlanIndex = result.findIndex(
|
||||||
|
(plan) => plan.productTier === ProductTierType.Families,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (familyPlanIndex !== -1) {
|
||||||
|
result.splice(familyPlanIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentPlan.productTier !== ProductTierType.Free) {
|
||||||
|
result.push(this.currentPlan);
|
||||||
|
}
|
||||||
|
|
||||||
result.sort((planA, planB) => planA.displaySortOrder - planB.displaySortOrder);
|
result.sort((planA, planB) => planA.displaySortOrder - planB.displaySortOrder);
|
||||||
|
|
||||||
return result.reverse();
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectablePlans() {
|
get selectablePlans() {
|
||||||
@@ -414,7 +450,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
passwordManagerSeatTotal(plan: PlanResponse): number {
|
passwordManagerSeatTotal(plan: PlanResponse): number {
|
||||||
if (!plan.PasswordManager.hasAdditionalSeatsOption) {
|
if (!plan.PasswordManager.hasAdditionalSeatsOption || this.isSecretsManagerTrial()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,6 +458,33 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
return result;
|
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 *
|
||||||
|
Math.abs(this.organization.maxStorageGb || 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalServiceAccountTotal(plan: PlanResponse): number {
|
||||||
|
if (!plan.SecretsManager.hasAdditionalServiceAccountOption || this.additionalServiceAccount) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan.SecretsManager.additionalPricePerServiceAccount * this.additionalServiceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
get passwordManagerSubtotal() {
|
get passwordManagerSubtotal() {
|
||||||
let subTotal = this.selectedPlan.PasswordManager.basePrice;
|
let subTotal = this.selectedPlan.PasswordManager.basePrice;
|
||||||
if (this.selectedPlan.PasswordManager.hasAdditionalSeatsOption) {
|
if (this.selectedPlan.PasswordManager.hasAdditionalSeatsOption) {
|
||||||
@@ -433,13 +496,37 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
return subTotal - this.discount;
|
return subTotal - this.discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get secretsManagerSubtotal() {
|
||||||
|
const plan = this.selectedSecretsManagerPlan;
|
||||||
|
|
||||||
|
if (!this.organization.useSecretsManager) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
plan.SecretsManager.basePrice +
|
||||||
|
this.secretsManagerSeatTotal(plan, this.sub?.smSeats) +
|
||||||
|
this.additionalServiceAccountTotal(plan)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get taxCharges() {
|
get taxCharges() {
|
||||||
return this.taxComponent != null && this.taxComponent.taxRate != null
|
return this.taxComponent != null && this.taxComponent.taxRate != null
|
||||||
? (this.taxComponent.taxRate / 100) * this.passwordManagerSubtotal
|
? (this.taxComponent.taxRate / 100) * this.passwordManagerSubtotal
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get passwordManagerSeats() {
|
||||||
|
if (this.selectedPlan.productTier === ProductTierType.Families) {
|
||||||
|
return this.selectedPlan.PasswordManager.baseSeats;
|
||||||
|
}
|
||||||
|
return this.organization.seats;
|
||||||
|
}
|
||||||
|
|
||||||
get total() {
|
get total() {
|
||||||
|
if (this.organization.useSecretsManager) {
|
||||||
|
return this.passwordManagerSubtotal + this.secretsManagerSubtotal + this.taxCharges || 0;
|
||||||
|
}
|
||||||
return this.passwordManagerSubtotal + this.taxCharges || 0;
|
return this.passwordManagerSubtotal + this.taxCharges || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,6 +534,15 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
return this.selectablePlans.some((plan) => plan.type === PlanType.TeamsStarter);
|
return this.selectablePlans.some((plan) => plan.type === PlanType.TeamsStarter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get additionalServiceAccount() {
|
||||||
|
const baseServiceAccount = this.selectedPlan.SecretsManager?.baseServiceAccount || 0;
|
||||||
|
const usedServiceAccounts = this.sub?.smServiceAccounts || 0;
|
||||||
|
|
||||||
|
const additionalServiceAccounts = baseServiceAccount - usedServiceAccounts;
|
||||||
|
|
||||||
|
return additionalServiceAccounts <= 0 ? Math.abs(additionalServiceAccounts) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
changedProduct() {
|
changedProduct() {
|
||||||
const selectedPlan = this.selectablePlans[0];
|
const selectedPlan = this.selectablePlans[0];
|
||||||
|
|
||||||
@@ -552,6 +648,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
request.billingAddressPostalCode = this.taxComponent.taxFormGroup?.value.postalCode;
|
request.billingAddressPostalCode = this.taxComponent.taxFormGroup?.value.postalCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Secrets Manager
|
||||||
|
this.buildSecretsManagerRequest(request);
|
||||||
|
|
||||||
if (this.upgradeRequiresPaymentMethod || this.showPayment) {
|
if (this.upgradeRequiresPaymentMethod || this.showPayment) {
|
||||||
const tokenResult = await this.paymentComponent.createPaymentToken();
|
const tokenResult = await this.paymentComponent.createPaymentToken();
|
||||||
const paymentRequest = new PaymentRequest();
|
const paymentRequest = new PaymentRequest();
|
||||||
@@ -593,6 +692,22 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildSecretsManagerRequest(request: OrganizationUpgradeRequest): void {
|
||||||
|
request.useSecretsManager = this.organization.useSecretsManager;
|
||||||
|
if (!this.organization.useSecretsManager) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.selectedPlan.SecretsManager.hasAdditionalSeatsOption &&
|
||||||
|
this.currentPlan.productTier === ProductTierType.Free
|
||||||
|
) {
|
||||||
|
request.additionalSmSeats = this.organization.seats;
|
||||||
|
} else {
|
||||||
|
request.additionalSmSeats = this.sub?.smSeats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private upgradeFlowPrefillForm() {
|
private upgradeFlowPrefillForm() {
|
||||||
if (this.acceptingSponsorship) {
|
if (this.acceptingSponsorship) {
|
||||||
this.formGroup.controls.productTier.setValue(ProductTierType.Families);
|
this.formGroup.controls.productTier.setValue(ProductTierType.Families);
|
||||||
|
|||||||
@@ -8954,5 +8954,56 @@
|
|||||||
},
|
},
|
||||||
"additionalStorageGbMessage": {
|
"additionalStorageGbMessage": {
|
||||||
"message": "GB additional storage"
|
"message": "GB additional storage"
|
||||||
|
},
|
||||||
|
"premiumAccounts": {
|
||||||
|
"message": "6 premium accounts"
|
||||||
|
},
|
||||||
|
"unlimitedSharing": {
|
||||||
|
"message": "Unlimited sharing"
|
||||||
|
},
|
||||||
|
"unlimitedCollections": {
|
||||||
|
"message": "Unlimited collections"
|
||||||
|
},
|
||||||
|
"secureDataSharing": {
|
||||||
|
"message": "Secure data sharing"
|
||||||
|
},
|
||||||
|
"eventLogMonitoring": {
|
||||||
|
"message": "Event log monitoring"
|
||||||
|
},
|
||||||
|
"directoryIntegration": {
|
||||||
|
"message": "Directory integration"
|
||||||
|
},
|
||||||
|
"passwordLessSso": {
|
||||||
|
"message": "PasswordLess SSO"
|
||||||
|
},
|
||||||
|
"accountRecovery": {
|
||||||
|
"message": "Account recovery"
|
||||||
|
},
|
||||||
|
"customRoles": {
|
||||||
|
"message": "Custom roles"
|
||||||
|
},
|
||||||
|
"unlimitedSecretsStorage": {
|
||||||
|
"message": "Unlimited secrets storage"
|
||||||
|
},
|
||||||
|
"unlimitedUsers": {
|
||||||
|
"message": "Unlimited users"
|
||||||
|
},
|
||||||
|
"UpTo50MachineAccounts": {
|
||||||
|
"message": "Up to 50 machine accounts"
|
||||||
|
},
|
||||||
|
"UpTo20MachineAccounts": {
|
||||||
|
"message": "Up to 20 machine accounts"
|
||||||
|
},
|
||||||
|
"current": {
|
||||||
|
"message": "Current"
|
||||||
|
},
|
||||||
|
"secretsManagerSubInfo": {
|
||||||
|
"message": "Your Secrets Manager subscription will upgrade base on the plan selected"
|
||||||
|
},
|
||||||
|
"bitwardenPasswordManager": {
|
||||||
|
"message": "Bitwarden Password Manager"
|
||||||
|
},
|
||||||
|
"secretsManagerWithFreePasswordManagerInfo": {
|
||||||
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user