1
0
mirror of https://github.com/bitwarden/browser synced 2026-03-01 19:11:22 +00:00
Files
browser/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.html
2025-06-18 11:04:04 +01:00

628 lines
24 KiB
HTML

<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog dialogSize="default" [loading]="loading">
<span bitDialogTitle class="tw-font-semibold">
{{ "subscribetoEnterprise" | i18n: currentPlanName }}
</span>
<div bitDialogContent>
<p>{{ "subscribetoEnterpriseSubtitle" | i18n: currentPlanName }}</p>
<!-- Plan Features List -->
<ng-container [ngSwitch]="currentPlan?.productTier">
<ul class="bwi-ul tw-text-xs" *ngSwitchCase="productTypes.Enterprise">
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "includeEnterprisePolicies" | i18n }}
</li>
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "passwordLessSso" | i18n }}
</li>
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "accountRecovery" | i18n }}
</li>
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "customRoles" | i18n }}
</li>
<li *ngIf="hasSecretsManager()">
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "unlimitedSecretsAndProjects" | i18n }}
</li>
</ul>
<ul class="bwi-ul tw-text-xs" *ngSwitchCase="productTypes.Teams">
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "secureDataSharing" | i18n }}
</li>
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "eventLogMonitoring" | i18n }}
</li>
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "directoryIntegration" | i18n }}
</li>
<li *ngIf="hasSecretsManager()">
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "unlimitedSecretsAndProjects" | i18n }}
</li>
</ul>
<ul class="bwi-ul tw-text-xs" *ngSwitchCase="productTypes.Families">
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "premiumAccounts" | i18n }}
</li>
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "unlimitedSharing" | i18n }}
</li>
<li>
<i class="bwi bwi-check tw-text-muted bwi-li" aria-hidden="true"></i>
{{ "createUnlimitedCollections" | i18n }}
</li>
</ul>
</ng-container>
<!-- Plan Selection -->
<div *ngIf="!isFamily()">
<div class="tw-mb-3 tw-flex tw-justify-between">
<h4 class="tw-text-lg tw-text-main">{{ "selectAPlan" | i18n }}</h4>
</div>
<ng-container *ngIf="!loading && !selfHosted && passwordManagerPlans">
<div
class="tw-grid tw-grid-flow-col tw-gap-4 tw-mb-2"
[class]="'tw-grid-cols-' + selectablePlans.length"
>
@for (plan of selectablePlans; let i = $index; track plan.type) {
<bit-card
[ngClass]="getPlanCardContainerClasses(plan, i)"
(click)="selectPlan(plan)"
[attr.tabindex]="focusedIndex !== i || isCardDisabled(i) ? '-1' : '0'"
(keyup)="onKeydown($event, i)"
(focus)="onFocus(i)"
[attr.aria-disabled]="isCardDisabled(i)"
[id]="i + 'a_plan_card'"
>
<div class="tw-relative">
@if (plan.isAnnual) {
<div
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': plan === selectedPlan,
'tw-bg-secondary-100': !(plan === selectedPlan),
}"
>
{{ "recommended" | i18n }}
</div>
}
<div
class="tw-px-2 tw-pb-[4px]"
[ngClass]="{
'tw-py-1': !(plan === selectedPlan),
'tw-py-0': plan === 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">{{
plan.isAnnual ? "Annually" : "Monthly"
}}</span>
<!-- Discount Badge -->
<span
class="tw-mr-1 tw-ml-2"
*ngIf="plan.isAnnual"
bitBadge
variant="success"
>{{
"upgradeDiscount"
| i18n
: (selectedInterval === planIntervals.Annually &&
discountPercentageFromSub == 0
? this.discountPercentage
: this.discountPercentageFromSub)
}}</span
>
</h3>
<span *ngIf="selectedPlan.productTier != productTypes.Free">
<ng-container
*ngIf="selectedPlan.PasswordManager.basePrice && !acceptingSponsorship"
>
<b class="tw-text-lg tw-font-semibold">
{{
(selectedPlan.isAnnual
? selectedPlan.PasswordManager.basePrice / 12
: selectedPlan.PasswordManager.basePrice
) | currency: "$"
}}
</b>
<b class="tw-text-sm tw-font-semibold">
<ng-container
*ngIf="selectedPlan.PasswordManager.hasAdditionalSeatsOption"
>
{{ ("additionalUsers" | i18n).toLowerCase() }}
{{
(selectedPlan.isAnnual
? selectedPlan.PasswordManager.seatPrice / 12
: selectedPlan.PasswordManager.seatPrice
) | currency: "$"
}}
/{{ selectedPlanInterval | i18n }}
</ng-container>
</b>
</ng-container>
</span>
<span
*ngIf="
!selectedPlan.PasswordManager.basePrice &&
selectedPlan.PasswordManager.hasAdditionalSeatsOption
"
>
<b class="tw-text-lg tw-font-semibold"
>{{
"costPerMember"
| i18n
: (((this.sub.useSecretsManager
? selectedPlan.SecretsManager.seatPrice
: 0) +
selectedPlan.PasswordManager.seatPrice) /
(selectedPlan.isAnnual ? 12 : 1)
| currency: "$")
}}
</b>
<span class="tw-text-xs tw-px-0"> /{{ "monthPerMember" | i18n }}</span>
</span>
<span *ngIf="selectedPlan.productTier == productTypes.Free"
>{{ "freeForever" | i18n }}
</span>
</div>
</div>
</bit-card>
}
</div>
</ng-container>
</div>
<!-- Payment Information -->
<ng-container>
<h2 bitTypography="h4">{{ "paymentMethod" | i18n }}</h2>
<ng-container bitDialogContent>
<app-payment
[showAccountCredit]="false"
[showBankAccount]="!!organizationId || !!providerId"
[initialPaymentMethod]="initialPaymentMethod"
></app-payment>
<app-manage-tax-information
*ngIf="taxInformation"
[showTaxIdField]="showTaxIdField"
[startWith]="taxInformation"
(taxInformationChanged)="taxInformationChanged($event)"
/>
</ng-container>
<!-- Total Summary -->
<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>
<!-- Cost Breakdown -->
<ng-container *ngIf="totalOpened">
<!-- Password Manager + Secrets Manager Cost Summary -->
<div *ngIf="!isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Annually">
<ng-container *ngTemplateOutlet="passwordManagerAnnualSummary"></ng-container>
<ng-container *ngTemplateOutlet="secretsManagerAnnualSummary"></ng-container>
</bit-hint>
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Monthly">
<ng-container *ngTemplateOutlet="passwordManagerMonthlySummary"></ng-container>
<ng-container *ngTemplateOutlet="secretsManagerMonthlySummary"></ng-container>
</bit-hint>
</div>
<!-- Secrets Manager + Free PM Cost Summary -->
<div *ngIf="isSecretsManagerTrial()" class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Annually">
<ng-container *ngTemplateOutlet="secretsManagerAnnualSummary"></ng-container>
<ng-container *ngTemplateOutlet="passwordManagerAnnualSummary"></ng-container>
</bit-hint>
<bit-hint class="tw-w-1/2" *ngIf="selectedInterval == planIntervals.Monthly">
<ng-container *ngTemplateOutlet="secretsManagerMonthlySummary"></ng-container>
<ng-container *ngTemplateOutlet="passwordManagerMonthlySummary"></ng-container>
</bit-hint>
</div>
<!-- PM Only Discount -->
<div
*ngIf="discountPercentage && !organization.useSecretsManager"
class="tw-flex tw-flex-wrap tw-gap-4"
>
<bit-hint class="tw-w-1/2">
<p
class="tw-mb-0 tw-flex tw-justify-between"
bitTypography="body2"
*ngIf="discountPercentageFromSub > 0"
>
<span class="tw-text-xs">
{{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
</span>
<span class="tw-line-through tw-text-xs">{{
calculateTotalAppliedDiscount(total) | currency: "$"
}}</span>
</p>
</bit-hint>
</div>
<!-- Tax and Final Total -->
<div class="tw-flex tw-flex-wrap tw-gap-4">
<bit-hint class="tw-w-1/2">
<p
class="tw-flex tw-justify-between tw-border-0 tw-border-solid tw-border-t tw-border-secondary-300 tw-pt-2 tw-mb-0"
>
<span class="tw-font-semibold">
{{ "estimatedTax" | i18n }}
</span>
<span>
{{ estimatedTax | currency: "USD" : "$" }}
</span>
</p>
</bit-hint>
</div>
<div class="tw-flex tw-flex-wrap tw-gap-4 tw-mt-4">
<bit-hint class="tw-w-1/2">
<p
class="tw-flex tw-justify-between tw-border-0 tw-border-solid tw-border-t tw-border-secondary-300 tw-pt-2 tw-mb-0"
>
<span class="tw-font-semibold">
{{ "total" | i18n }}
</span>
<span>
{{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }}
<span class="tw-text-xs tw-font-semibold">
/ {{ selectedPlanInterval | i18n }}</span
>
</span>
</p>
</bit-hint>
</div>
</ng-container>
</ng-container>
</div>
<!-- Dialog Footer -->
<ng-container bitDialogFooter>
<button bitButton bitFormButton buttonType="primary" type="submit">
{{ "subscribe" | i18n }}
</button>
<button bitButton buttonType="secondary" type="button" [bitDialogClose]="ResultType.CLOSED">
{{ "later" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>
<!-- Templates for cost breakdown sections -->
<ng-template #passwordManagerAnnualSummary>
<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 }} &times;
{{
(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 }}&nbsp;
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ 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 }}
&times;
{{ 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>
</ng-template>
<ng-template #passwordManagerMonthlySummary>
<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 }}&nbsp;
<span *ngIf="!selectedPlan.PasswordManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ 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 }}
&times;
{{ 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>
</ng-template>
<ng-template #secretsManagerAnnualSummary>
<!-- 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 }} &times;
{{
(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 }}&nbsp;
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ 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 }}
&times;
{{ 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>
</ng-template>
<ng-template #secretsManagerMonthlySummary>
<!-- 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 }}&nbsp;
<span *ngIf="!selectedPlan.SecretsManager.baseSeats">{{ "members" | i18n }}</span>
&times;
{{ 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 }}
&times;
{{ 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>
</ng-template>