mirror of
https://github.com/bitwarden/browser
synced 2025-12-30 15:13:32 +00:00
995 lines
40 KiB
HTML
995 lines
40 KiB
HTML
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
|
<bit-dialog dialogSize="large" [loading]="loading">
|
|
<span bitDialogTitle class="tw-font-semibold">
|
|
{{ dialogHeaderName }}
|
|
</span>
|
|
<div bitDialogContent>
|
|
<p>{{ "upgradePlans" | i18n }}</p>
|
|
<div class="tw-mb-3 tw-flex tw-justify-between">
|
|
<span [hidden]="isSubscriptionCanceled" class="tw-text-lg tw-pr-1 tw-font-bold">{{
|
|
"selectAPlan" | i18n
|
|
}}</span>
|
|
<!-- Discount Badge -->
|
|
<div class="tw-flex tw-items-center tw-gap-2">
|
|
<span
|
|
class="tw-mr-1"
|
|
[hidden]="isSubscriptionCanceled"
|
|
*ngIf="
|
|
this.discountPercentageFromSub > 0
|
|
? discountPercentageFromSub
|
|
: this.discountPercentage && selectedInterval === planIntervals.Annually
|
|
"
|
|
bitBadge
|
|
variant="success"
|
|
>{{
|
|
"upgradeDiscount"
|
|
| i18n
|
|
: (selectedInterval === planIntervals.Annually && discountPercentageFromSub == 0
|
|
? this.discountPercentage
|
|
: this.discountPercentageFromSub)
|
|
}}</span
|
|
>
|
|
<!-- Plan Interval Toggle -->
|
|
<div class="tw-inline-block" *ngIf="!isSubscriptionCanceled">
|
|
<bit-toggle-group
|
|
[selected]="selectedInterval"
|
|
(selectedChange)="updateInterval($event)"
|
|
>
|
|
<bit-toggle
|
|
*ngFor="
|
|
let planInterval of getPlanIntervals();
|
|
trackBy: optimizedNgForRender;
|
|
let i = index
|
|
"
|
|
[value]="planInterval.value"
|
|
>
|
|
{{ planInterval.name }}
|
|
</bit-toggle>
|
|
</bit-toggle-group>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Plan Selection Cards -->
|
|
<ng-container *ngIf="!loading && !selfHosted && this.passwordManagerPlans">
|
|
<div
|
|
class="tw-grid tw-grid-flow-col tw-gap-4 tw-mb-4"
|
|
[class]="'tw-grid-cols-' + selectableProducts.length"
|
|
>
|
|
<div
|
|
*ngFor="
|
|
let selectableProduct of selectableProducts;
|
|
trackBy: manageSelectableProduct;
|
|
let i = index
|
|
"
|
|
[ngClass]="getPlanCardContainerClasses(selectableProduct, i)"
|
|
(click)="selectPlan(selectableProduct)"
|
|
[attr.tabindex]="focusedIndex !== i || isCardDisabled(i) ? '-1' : '0'"
|
|
class="product-card"
|
|
(keyup)="onKeydown($event, i)"
|
|
(focus)="onFocus(i)"
|
|
[attr.aria-disabled]="isCardDisabled(i)"
|
|
[id]="i + 'a_plan_card'"
|
|
>
|
|
<div class="tw-relative">
|
|
<div
|
|
*ngIf="
|
|
selectableProduct.productTier === productTypes.Enterprise &&
|
|
!isSubscriptionCanceled
|
|
"
|
|
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 }}
|
|
</div>
|
|
<div
|
|
class="tw-px-2 tw-pb-[4px]"
|
|
[ngClass]="{
|
|
'tw-py-1': !(selectableProduct === selectedPlan),
|
|
'tw-py-0': selectableProduct === selectedPlan,
|
|
}"
|
|
>
|
|
<h3
|
|
class="tw-text-[1.25rem] tw-mt-[6px] tw-font-bold tw-mb-0 tw-leading-[2rem] tw-flex tw-items-center"
|
|
>
|
|
<span class="tw-capitalize tw-whitespace-nowrap">{{
|
|
selectableProduct.nameLocalizationKey | i18n
|
|
}}</span>
|
|
<span
|
|
bitBadge
|
|
variant="secondary"
|
|
*ngIf="selectableProduct === currentPlan"
|
|
class="tw-ml-2 tw-align-middle"
|
|
>
|
|
{{ "current" | i18n }}</span
|
|
>
|
|
</h3>
|
|
<span *ngIf="selectableProduct.productTier != productTypes.Free">
|
|
<ng-container
|
|
*ngIf="selectableProduct.PasswordManager.basePrice && !acceptingSponsorship"
|
|
>
|
|
<b class="tw-text-lg tw-font-semibold">
|
|
{{
|
|
(selectableProduct.isAnnual
|
|
? selectableProduct.PasswordManager.basePrice / 12
|
|
: selectableProduct.PasswordManager.basePrice
|
|
) | currency: "$"
|
|
}}
|
|
</b>
|
|
<span class="tw-text-xs tw-px-0">
|
|
/{{
|
|
selectableProduct.productTier === productTypes.Families
|
|
? "month"
|
|
: ("monthPerMember" | i18n)
|
|
}}</span
|
|
>
|
|
<b class="tw-text-sm tw-font-semibold">
|
|
<ng-container
|
|
*ngIf="selectableProduct.PasswordManager.hasAdditionalSeatsOption"
|
|
>
|
|
{{ ("additionalUsers" | i18n).toLowerCase() }}
|
|
{{
|
|
(selectableProduct.isAnnual
|
|
? selectableProduct.PasswordManager.seatPrice / 12
|
|
: selectableProduct.PasswordManager.seatPrice
|
|
) | currency: "$"
|
|
}}
|
|
/{{ selectedPlanInterval | i18n }}
|
|
</ng-container>
|
|
</b>
|
|
</ng-container>
|
|
</span>
|
|
<span
|
|
*ngIf="
|
|
!selectableProduct.PasswordManager.basePrice &&
|
|
selectableProduct.PasswordManager.hasAdditionalSeatsOption
|
|
"
|
|
>
|
|
<b class="tw-text-lg tw-font-semibold"
|
|
>{{
|
|
"costPerMember"
|
|
| i18n
|
|
: ((selectableProduct.isAnnual
|
|
? selectableProduct.PasswordManager.seatPrice / 12
|
|
: selectableProduct.PasswordManager.seatPrice
|
|
)
|
|
| currency: "$")
|
|
}}
|
|
</b>
|
|
<span class="tw-text-xs tw-px-0"> /{{ "monthPerMember" | i18n }}</span>
|
|
</span>
|
|
<span *ngIf="selectableProduct.productTier == productTypes.Free"
|
|
>{{ "freeForever" | i18n }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<ng-container
|
|
*ngIf="
|
|
selectableProduct.productTier === productTypes.Enterprise;
|
|
else nonEnterprisePlans
|
|
"
|
|
>
|
|
<p
|
|
class="tw-text-xs tw-px-2 tw-font-semibold tw-mb-1"
|
|
*ngIf="organization.useSecretsManager"
|
|
>
|
|
{{ "bitwardenPasswordManager" | i18n }}
|
|
</p>
|
|
<p class="tw-text-xs tw-px-2 tw-mb-1">{{ "enterprisePlanUpgradeMessage" | i18n }}</p>
|
|
|
|
<ul class="bwi-ul tw-text-xs">
|
|
<li>
|
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
|
{{ "includeEnterprisePolicies" | i18n }}
|
|
</li>
|
|
<li>
|
|
<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 tw-mb-1"
|
|
*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>
|
|
</ul>
|
|
</ng-container>
|
|
<ng-template #nonEnterprisePlans>
|
|
<ng-container
|
|
*ngIf="
|
|
selectableProduct.productTier === productTypes.Teams &&
|
|
teamsStarterPlanIsAvailable;
|
|
else fullFeatureList
|
|
"
|
|
>
|
|
<ul class="tw-px-2 tw-pb-2 tw-list-inside tw-mb-0 tw-text-xs">
|
|
<li>{{ "includeAllTeamsStarterFeatures" | i18n }}</li>
|
|
<li>{{ "chooseMonthlyOrAnnualBilling" | i18n }}</li>
|
|
<li>{{ "abilityToAddMoreThanNMembers" | i18n: 10 }}</li>
|
|
</ul>
|
|
</ng-container>
|
|
<ng-template #fullFeatureList>
|
|
<p
|
|
class="tw-text-xs tw-px-2 tw-font-semibold tw-mb-1"
|
|
*ngIf="organization.useSecretsManager"
|
|
>
|
|
{{ "bitwardenPasswordManager" | i18n }}
|
|
</p>
|
|
<p
|
|
*ngIf="selectableProduct.productTier === productTypes.Teams"
|
|
class="tw-text-xs tw-px-2 tw-mb-1"
|
|
>
|
|
{{ "teamsPlanUpgradeMessage" | i18n }}
|
|
</p>
|
|
<p
|
|
*ngIf="selectableProduct.productTier === productTypes.Families"
|
|
class="tw-text-xs tw-px-2 tw-mb-1"
|
|
>
|
|
{{ "familyPlanUpgradeMessage" | i18n }}
|
|
</p>
|
|
<ul
|
|
class="bwi-ul tw-text-xs tw-mb-1"
|
|
*ngIf="selectableProduct.productTier == productTypes.Families"
|
|
>
|
|
<li>
|
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
|
{{ "premiumAccounts" | i18n }}
|
|
</li>
|
|
<li>
|
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
|
{{ "unlimitedSharing" | i18n }}
|
|
</li>
|
|
<li>
|
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
|
{{ "unlimitedCollections" | i18n }}
|
|
</li>
|
|
</ul>
|
|
<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>
|
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
|
{{ "eventLogMonitoring" | i18n }}
|
|
</li>
|
|
<li>
|
|
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
|
{{ "directoryIntegration" | i18n }}
|
|
</li>
|
|
</ul>
|
|
<p
|
|
class="tw-text-xs tw-px-2 tw-font-semibold tw-mb-1"
|
|
*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>
|
|
<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>
|
|
{{ "UpTo20MachineAccounts" | i18n }}
|
|
</li>
|
|
</ul>
|
|
</ng-template>
|
|
</ng-template>
|
|
</div>
|
|
</div>
|
|
<br />
|
|
<bit-callout
|
|
*ngIf="organization.useSecretsManager && !isSecretsManagerTrial()"
|
|
type="info"
|
|
title="SECRETS MANAGER SUBSCRIPTION"
|
|
>
|
|
{{ "secretsManagerSubscriptionInfo" | i18n }}
|
|
</bit-callout>
|
|
<bit-callout
|
|
*ngIf="organization.useSecretsManager && isSecretsManagerTrial()"
|
|
type="info"
|
|
title="PASSWORD MANAGER SUBSCRIPTION"
|
|
>
|
|
{{ "secretsManagerComplimentaryPasswordManager" | i18n }}
|
|
</bit-callout>
|
|
<br />
|
|
</ng-container>
|
|
<!-- Payment info -->
|
|
<ng-container
|
|
*ngIf="formGroup.value.productTier !== productTypes.Free || isSubscriptionCanceled"
|
|
>
|
|
<h2 bitTypography="h4">{{ "paymentMethod" | i18n }}</h2>
|
|
<p
|
|
*ngIf="
|
|
!showPayment && (paymentSource || billing?.paymentSource) && !isSubscriptionCanceled
|
|
"
|
|
>
|
|
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
|
|
{{ paymentSource?.description }}
|
|
<span class="ml-2 tw-text-primary-600 tw-cursor-pointer" (click)="toggleShowPayment()">
|
|
{{ "changePaymentMethod" | i18n }}
|
|
</span>
|
|
<a></a>
|
|
</p>
|
|
<ng-container *ngIf="canUpdatePaymentInformation()">
|
|
<app-payment [showAccountCredit]="false" />
|
|
<app-manage-tax-information
|
|
[startWith]="taxInformation"
|
|
(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="row">
|
|
<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: "$"
|
|
}}
|
|
/{{ 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="col-6" *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="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: "$"
|
|
}}
|
|
/{{ 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="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
|
|
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="row"
|
|
>
|
|
<bit-hint class="col-6">
|
|
<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="row tw-mt-4">
|
|
<bit-hint class="col-6">
|
|
<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="row tw-mt-4">
|
|
<bit-hint class="col-6">
|
|
<p
|
|
class="tw-flex tw-justify-between tw-border-0 tw-border-solid tw-border-t tw-border-secondary-300 tw-pt-2 tw-mb-0"
|
|
>
|
|
<span class="tw-font-semibold">
|
|
{{ "total" | i18n }}
|
|
</span>
|
|
<span>
|
|
{{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }}
|
|
<span class="tw-text-xs tw-font-semibold">
|
|
/ {{ selectedPlanInterval | i18n }}</span
|
|
>
|
|
</span>
|
|
</p>
|
|
</bit-hint>
|
|
</div>
|
|
</ng-container>
|
|
</div>
|
|
<ng-container bitDialogFooter>
|
|
<button bitButton bitFormButton buttonType="primary" type="submit">
|
|
{{ "upgrade" | i18n }}
|
|
</button>
|
|
<button bitButton buttonType="secondary" type="button" [bitDialogClose]="ResultType.Closed">
|
|
{{ "cancel" | i18n }}
|
|
</button>
|
|
</ng-container>
|
|
</bit-dialog>
|
|
</form>
|