472 lines
19 KiB
HTML
472 lines
19 KiB
HTML
<ng-container *ngIf="loading">
|
|
<i
|
|
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
|
title="{{ 'loading' | i18n }}"
|
|
aria-hidden="true"
|
|
></i>
|
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
|
</ng-container>
|
|
<ng-container *ngIf="createOrganization && selfHosted">
|
|
<p bitTypography="body1">{{ "uploadLicenseFileOrg" | i18n }}</p>
|
|
<form [formGroup]="selfHostedForm" [bitSubmit]="submit">
|
|
<bit-form-field>
|
|
<bit-label>{{ "licenseFile" | i18n }}</bit-label>
|
|
<div>
|
|
<button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()">
|
|
{{ "chooseFile" | i18n }}
|
|
</button>
|
|
{{ selectedFile?.name ?? ("noFileChosen" | i18n) }}
|
|
</div>
|
|
<input
|
|
#fileSelector
|
|
hidden
|
|
bitInput
|
|
type="file"
|
|
formControlName="file"
|
|
(change)="setSelectedFile($event)"
|
|
accept="application/JSON"
|
|
/>
|
|
<bit-hint>{{ "licenseFileDesc" | i18n: "bitwarden_organization_license.json" }}</bit-hint>
|
|
</bit-form-field>
|
|
<button type="submit" bitButton bitFormButton buttonType="primary">
|
|
{{ "submit" | i18n }}
|
|
</button>
|
|
</form>
|
|
</ng-container>
|
|
<form
|
|
[formGroup]="formGroup"
|
|
[bitSubmit]="submit"
|
|
*ngIf="!loading && !selfHosted && this.passwordManagerPlans && this.secretsManagerPlans"
|
|
class="tw-pt-6"
|
|
>
|
|
<bit-section>
|
|
<app-org-info
|
|
(changedBusinessOwned)="changedOwnedBusiness()"
|
|
[formGroup]="formGroup"
|
|
[createOrganization]="createOrganization"
|
|
[isProvider]="!!providerId"
|
|
[acceptingSponsorship]="acceptingSponsorship"
|
|
>
|
|
</app-org-info>
|
|
</bit-section>
|
|
<bit-section>
|
|
<h2 bitTypography="h2">{{ "chooseYourPlan" | i18n }}</h2>
|
|
<div *ngFor="let selectableProduct of selectableProducts">
|
|
<bit-radio-group formControlName="product" [block]="true">
|
|
<bit-radio-button [value]="selectableProduct.product" (change)="changedProduct()">
|
|
<bit-label>{{ selectableProduct.nameLocalizationKey | i18n }}</bit-label>
|
|
<bit-hint class="tw-text-sm"
|
|
>{{ selectableProduct.descriptionLocalizationKey | i18n: "1" }}
|
|
<ng-container
|
|
*ngIf="selectableProduct.product === productTypes.Enterprise; else nonEnterprisePlans"
|
|
>
|
|
<ul class="tw-pl-0 tw-list-inside tw-mb-0">
|
|
<li>{{ "includeAllTeamsFeatures" | i18n }}</li>
|
|
<li *ngIf="selectableProduct.hasSelfHost">{{ "onPremHostingOptional" | i18n }}</li>
|
|
<li *ngIf="selectableProduct.hasSso">{{ "includeSsoAuthentication" | i18n }}</li>
|
|
<li *ngIf="selectableProduct.hasPolicies">
|
|
{{ "includeEnterprisePolicies" | i18n }}
|
|
</li>
|
|
<li *ngIf="selectableProduct.trialPeriodDays && createOrganization">
|
|
{{ "xDayFreeTrial" | i18n: selectableProduct.trialPeriodDays }}
|
|
</li>
|
|
</ul>
|
|
</ng-container>
|
|
<ng-template #nonEnterprisePlans>
|
|
<ng-container
|
|
*ngIf="selectableProduct.product === productTypes.Teams; else fullFeatureList"
|
|
>
|
|
<ul class="tw-pl-0 tw-list-inside tw-mb-0">
|
|
<li>{{ "includeAllTeamsStarterFeatures" | i18n }}</li>
|
|
<li>{{ "chooseMonthlyOrAnnualBilling" | i18n }}</li>
|
|
<li>{{ "abilityToAddMoreThanNMembers" | i18n: 10 }}</li>
|
|
<li *ngIf="selectableProduct.trialPeriodDays && createOrganization">
|
|
{{ "xDayFreeTrial" | i18n: selectableProduct.trialPeriodDays }}
|
|
</li>
|
|
</ul>
|
|
</ng-container>
|
|
<ng-template #fullFeatureList>
|
|
<ul class="tw-pl-0 tw-list-inside tw-mb-0">
|
|
<li *ngIf="selectableProduct.product == productTypes.Free">
|
|
{{ "limitedUsers" | i18n: selectableProduct.PasswordManager.maxSeats }}
|
|
</li>
|
|
<li
|
|
*ngIf="
|
|
selectableProduct.product != productTypes.Free &&
|
|
selectableProduct.product != productTypes.TeamsStarter &&
|
|
selectableProduct.PasswordManager.maxSeats
|
|
"
|
|
>
|
|
{{ "addShareLimitedUsers" | i18n: selectableProduct.PasswordManager.maxSeats }}
|
|
</li>
|
|
<li *ngIf="!selectableProduct.PasswordManager.maxSeats">
|
|
{{ "addShareUnlimitedUsers" | i18n }}
|
|
</li>
|
|
<li *ngIf="selectableProduct.PasswordManager.maxCollections">
|
|
{{
|
|
"limitedCollections" | i18n: selectableProduct.PasswordManager.maxCollections
|
|
}}
|
|
</li>
|
|
<li *ngIf="selectableProduct.PasswordManager.maxAdditionalSeats">
|
|
{{
|
|
"addShareLimitedUsers"
|
|
| i18n: selectableProduct.PasswordManager.maxAdditionalSeats
|
|
}}
|
|
</li>
|
|
<li *ngIf="!selectableProduct.PasswordManager.maxCollections">
|
|
{{ "createUnlimitedCollections" | i18n }}
|
|
</li>
|
|
<li *ngIf="selectableProduct.PasswordManager.baseStorageGb">
|
|
{{
|
|
"gbEncryptedFileStorage"
|
|
| i18n: selectableProduct.PasswordManager.baseStorageGb + "GB"
|
|
}}
|
|
</li>
|
|
<li *ngIf="selectableProduct.hasGroups">
|
|
{{ "controlAccessWithGroups" | i18n }}
|
|
</li>
|
|
<li *ngIf="selectableProduct.hasApi">{{ "trackAuditLogs" | i18n }}</li>
|
|
<li *ngIf="selectableProduct.hasDirectory">
|
|
{{ "syncUsersFromDirectory" | i18n }}
|
|
</li>
|
|
<li *ngIf="selectableProduct.hasSelfHost">
|
|
{{ "onPremHostingOptional" | i18n }}
|
|
</li>
|
|
<li *ngIf="selectableProduct.usersGetPremium">{{ "usersGetPremium" | i18n }}</li>
|
|
<li *ngIf="selectableProduct.product != productTypes.Free">
|
|
{{ "priorityCustomerSupport" | i18n }}
|
|
</li>
|
|
<li *ngIf="selectableProduct.trialPeriodDays && createOrganization">
|
|
{{ "xDayFreeTrial" | i18n: selectableProduct.trialPeriodDays }}
|
|
</li>
|
|
</ul>
|
|
</ng-template>
|
|
</ng-template>
|
|
</bit-hint>
|
|
</bit-radio-button>
|
|
<span *ngIf="selectableProduct.product != productTypes.Free">
|
|
<ng-container
|
|
*ngIf="selectableProduct.PasswordManager.basePrice && !acceptingSponsorship"
|
|
>
|
|
{{
|
|
(selectableProduct.isAnnual
|
|
? selectableProduct.PasswordManager.basePrice / 12
|
|
: selectableProduct.PasswordManager.basePrice
|
|
) | currency: "$"
|
|
}}
|
|
/{{ "month" | i18n }},
|
|
{{ "includesXUsers" | i18n: selectableProduct.PasswordManager.baseSeats }}
|
|
<ng-container *ngIf="selectableProduct.PasswordManager.hasAdditionalSeatsOption">
|
|
{{ ("additionalUsers" | i18n).toLowerCase() }}
|
|
{{
|
|
(selectableProduct.isAnnual
|
|
? selectableProduct.PasswordManager.seatPrice / 12
|
|
: selectableProduct.PasswordManager.seatPrice
|
|
) | currency: "$"
|
|
}}
|
|
/{{ "month" | i18n }}
|
|
</ng-container>
|
|
</ng-container>
|
|
</span>
|
|
<span
|
|
*ngIf="
|
|
!selectableProduct.PasswordManager.basePrice &&
|
|
selectableProduct.PasswordManager.hasAdditionalSeatsOption
|
|
"
|
|
>
|
|
{{
|
|
"costPerUser"
|
|
| i18n
|
|
: ((selectableProduct.isAnnual
|
|
? selectableProduct.PasswordManager.seatPrice / 12
|
|
: selectableProduct.PasswordManager.seatPrice
|
|
)
|
|
| currency: "$")
|
|
}}
|
|
/{{ "month" | i18n }}
|
|
</span>
|
|
<span *ngIf="selectableProduct.product == productTypes.Free">{{
|
|
"freeForever" | i18n
|
|
}}</span>
|
|
</bit-radio-group>
|
|
</div>
|
|
</bit-section>
|
|
<bit-section *ngIf="formGroup.value.product !== productTypes.Free">
|
|
<bit-section
|
|
*ngIf="
|
|
selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
|
|
!selectedPlan.PasswordManager.baseSeats
|
|
"
|
|
>
|
|
<h2 bitTypography="h2">{{ "users" | i18n }}</h2>
|
|
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
|
<bit-form-field class="tw-col-span-6">
|
|
<bit-label>{{ "userSeats" | i18n }}</bit-label>
|
|
<input
|
|
bitInput
|
|
type="number"
|
|
formControlName="additionalSeats"
|
|
placeholder="{{ 'userSeatsDesc' | i18n }}"
|
|
required
|
|
/>
|
|
<bit-hint class="tw-text-sm">{{ "userSeatsHowManyDesc" | i18n }}</bit-hint>
|
|
</bit-form-field>
|
|
</div>
|
|
</bit-section>
|
|
<bit-section>
|
|
<h2 bitTypography="h2">{{ "addons" | i18n }}</h2>
|
|
<div
|
|
class="tw-grid tw-grid-cols-12 tw-gap-4"
|
|
*ngIf="
|
|
selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
|
|
selectedPlan.PasswordManager.baseSeats
|
|
"
|
|
>
|
|
<bit-form-field class="tw-col-span-6">
|
|
<bit-label>{{ "additionalUserSeats" | i18n }}</bit-label>
|
|
<input
|
|
bitInput
|
|
type="number"
|
|
formControlName="additionalSeats"
|
|
placeholder="{{ 'userSeatsDesc' | i18n }}"
|
|
/>
|
|
<bit-hint class="tx-text-sm"
|
|
>{{
|
|
"userSeatsAdditionalDesc"
|
|
| i18n
|
|
: selectedPlan.PasswordManager.baseSeats
|
|
: (seatPriceMonthly(selectedPlan) | currency: "$")
|
|
}}
|
|
</bit-hint>
|
|
</bit-form-field>
|
|
</div>
|
|
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
|
<bit-form-field class="tw-col-span-6">
|
|
<bit-label>{{ "additionalStorageGb" | i18n }}</bit-label>
|
|
<input
|
|
bitInput
|
|
type="number"
|
|
formControlName="additionalStorage"
|
|
step="1"
|
|
placeholder="{{ 'additionalStorageGbDesc' | i18n }}"
|
|
/>
|
|
<bit-hint class="tw-text-sm">{{
|
|
"additionalStorageIntervalDesc"
|
|
| i18n
|
|
: "1 GB"
|
|
: (additionalStoragePriceMonthly(selectedPlan) | currency: "$")
|
|
: ("month" | i18n)
|
|
}}</bit-hint>
|
|
</bit-form-field>
|
|
</div>
|
|
</bit-section>
|
|
<bit-section>
|
|
<div
|
|
class="tw-grid tw-grid-cols-12 tw-gap-4"
|
|
*ngIf="selectedPlan.PasswordManager.hasPremiumAccessOption"
|
|
>
|
|
<bit-form-control class="tw-col-span-6">
|
|
<bit-label>{{ "premiumAccess" | i18n }}</bit-label>
|
|
<input type="checkbox" bitCheckbox formControlName="premiumAccessAddon" />
|
|
<bit-hint class="tw-text-sm">{{
|
|
"premiumAccessDesc" | i18n: (3.33 | currency: "$") : ("month" | i18n)
|
|
}}</bit-hint>
|
|
</bit-form-control>
|
|
</div>
|
|
</bit-section>
|
|
<bit-section *ngFor="let selectablePlan of selectablePlans">
|
|
<h2 bitTypography="h2">{{ "summary" | i18n }}</h2>
|
|
<bit-radio-group formControlName="plan">
|
|
<bit-radio-button
|
|
type="radio"
|
|
id="interval{{ selectablePlan.type }}"
|
|
[value]="selectablePlan.type"
|
|
>
|
|
<bit-label>{{ (selectablePlan.isAnnual ? "annually" : "monthly") | i18n }}</bit-label>
|
|
<bit-hint *ngIf="selectablePlan.isAnnual">
|
|
<p
|
|
class="tw-mb-0"
|
|
bitTypography="body2"
|
|
*ngIf="selectablePlan.PasswordManager.basePrice"
|
|
>
|
|
{{ "basePrice" | i18n }}:
|
|
{{
|
|
(selectablePlan.isAnnual
|
|
? selectablePlan.PasswordManager.basePrice / 12
|
|
: selectablePlan.PasswordManager.basePrice
|
|
) | currency: "$"
|
|
}}
|
|
× 12
|
|
{{ "monthAbbr" | i18n }}
|
|
=
|
|
<ng-container *ngIf="acceptingSponsorship; else notAcceptingSponsorship">
|
|
<span class="tw-line-through">{{
|
|
selectablePlan.PasswordManager.basePrice | currency: "$"
|
|
}}</span>
|
|
{{ "freeWithSponsorship" | i18n }}
|
|
</ng-container>
|
|
<ng-template #notAcceptingSponsorship>
|
|
{{ selectablePlan.PasswordManager.basePrice | currency: "$" }}
|
|
/{{ "year" | i18n }}
|
|
</ng-template>
|
|
</p>
|
|
<p
|
|
class="tw-mb-0"
|
|
bitTypography="body2"
|
|
*ngIf="selectablePlan.PasswordManager.hasAdditionalSeatsOption"
|
|
>
|
|
<span *ngIf="selectablePlan.PasswordManager.baseSeats"
|
|
>{{ "additionalUsers" | i18n }}:</span
|
|
>
|
|
<span *ngIf="!selectablePlan.PasswordManager.baseSeats">{{ "users" | i18n }}:</span>
|
|
{{ formGroup.controls["additionalSeats"].value || 0 }} ×
|
|
{{
|
|
(selectablePlan.isAnnual
|
|
? selectablePlan.PasswordManager.seatPrice / 12
|
|
: selectablePlan.PasswordManager.seatPrice
|
|
) | currency: "$"
|
|
}}
|
|
× 12 {{ "monthAbbr" | i18n }} =
|
|
{{
|
|
passwordManagerSeatTotal(selectablePlan, formGroup.value.additionalSeats)
|
|
| currency: "$"
|
|
}}
|
|
/{{ "year" | i18n }}
|
|
</p>
|
|
<p
|
|
class="tw-mb-0"
|
|
bitTypography="body2"
|
|
*ngIf="selectablePlan.PasswordManager.hasAdditionalStorageOption"
|
|
>
|
|
{{ "additionalStorageGb" | i18n }}:
|
|
{{ formGroup.controls["additionalStorage"].value || 0 }} ×
|
|
{{
|
|
(selectablePlan.isAnnual
|
|
? selectablePlan.PasswordManager.additionalStoragePricePerGb / 12
|
|
: selectablePlan.PasswordManager.additionalStoragePricePerGb
|
|
) | currency: "$"
|
|
}}
|
|
× 12 {{ "monthAbbr" | i18n }} =
|
|
{{ additionalStorageTotal(selectablePlan) | currency: "$" }} /{{ "year" | i18n }}
|
|
</p>
|
|
</bit-hint>
|
|
<bit-hint *ngIf="!selectablePlan.isAnnual">
|
|
<p
|
|
class="tw-mb-0"
|
|
bitTypography="body2"
|
|
*ngIf="selectablePlan.PasswordManager.basePrice"
|
|
>
|
|
{{ "basePrice" | i18n }}:
|
|
{{ selectablePlan.PasswordManager.basePrice | currency: "$" }}
|
|
{{ "monthAbbr" | i18n }}
|
|
=
|
|
{{ selectablePlan.PasswordManager.basePrice | currency: "$" }}
|
|
/{{ "month" | i18n }}
|
|
</p>
|
|
<p
|
|
class="tw-mb-0"
|
|
bitTypography="body2"
|
|
*ngIf="selectablePlan.PasswordManager.hasAdditionalSeatsOption"
|
|
>
|
|
<span *ngIf="selectablePlan.PasswordManager.baseSeats"
|
|
>{{ "additionalUsers" | i18n }}:</span
|
|
>
|
|
<span *ngIf="!selectablePlan.PasswordManager.baseSeats">{{ "users" | i18n }}:</span>
|
|
{{ formGroup.controls["additionalSeats"].value || 0 }} ×
|
|
{{ selectablePlan.PasswordManager.seatPrice | currency: "$" }}
|
|
{{ "monthAbbr" | i18n }} =
|
|
{{
|
|
passwordManagerSeatTotal(selectablePlan, formGroup.value.additionalSeats)
|
|
| currency: "$"
|
|
}}
|
|
/{{ "month" | i18n }}
|
|
</p>
|
|
<p
|
|
class="tw-mb-0"
|
|
bitTypography="body2"
|
|
*ngIf="selectablePlan.PasswordManager.hasAdditionalStorageOption"
|
|
>
|
|
{{ "additionalStorageGb" | i18n }}:
|
|
{{ formGroup.controls["additionalStorage"].value || 0 }} ×
|
|
{{ selectablePlan.PasswordManager.additionalStoragePricePerGb | currency: "$" }}
|
|
{{ "monthAbbr" | i18n }} =
|
|
{{ additionalStorageTotal(selectablePlan) | currency: "$" }} /{{ "month" | i18n }}
|
|
</p>
|
|
</bit-hint>
|
|
</bit-radio-button>
|
|
</bit-radio-group>
|
|
</bit-section>
|
|
</bit-section>
|
|
|
|
<!-- Secrets Manager -->
|
|
<bit-section>
|
|
<sm-subscribe
|
|
*ngIf="planOffersSecretsManager && !hasProvider"
|
|
[formGroup]="formGroup.controls.secretsManager"
|
|
[selectedPlan]="selectedSecretsManagerPlan"
|
|
[upgradeOrganization]="!createOrganization"
|
|
></sm-subscribe>
|
|
</bit-section>
|
|
|
|
<!-- Payment info -->
|
|
<bit-section *ngIf="formGroup.value.product !== productTypes.Free">
|
|
<h2 bitTypography="h2">
|
|
{{ (createOrganization ? "paymentInformation" : "billingInformation") | i18n }}
|
|
</h2>
|
|
<p class="tw-text-muted tw-italic tw-mb-3 tw-block" bitTypography="body2">
|
|
{{ paymentDesc }}
|
|
</p>
|
|
<app-payment
|
|
*ngIf="createOrganization || upgradeRequiresPaymentMethod"
|
|
[hideCredit]="true"
|
|
></app-payment>
|
|
<app-tax-info (onCountryChanged)="changedCountry()"></app-tax-info>
|
|
<div id="price" class="tw-my-4">
|
|
<div class="tw-text-muted tw-text-base">
|
|
{{ "passwordManagerPlanPrice" | i18n }}: {{ passwordManagerSubtotal | currency: "USD $" }}
|
|
<br />
|
|
<span *ngIf="planOffersSecretsManager && formGroup.value.secretsManager.enabled">
|
|
{{ "secretsManagerPlanPrice" | i18n }}: {{ secretsManagerSubtotal | currency: "USD $" }}
|
|
<br />
|
|
</span>
|
|
<ng-container>
|
|
{{ "estimatedTax" | i18n }}: {{ taxCharges | currency: "USD $" }}
|
|
</ng-container>
|
|
</div>
|
|
<hr class="tw-my-1 tw-grid tw-grid-cols-3 tw-ml-0" />
|
|
<p class="tw-text-lg">
|
|
<strong>{{ "total" | i18n }}:</strong> {{ total | currency: "USD $" }}/{{
|
|
selectedPlanInterval | i18n
|
|
}}
|
|
</p>
|
|
</div>
|
|
<ng-container *ngIf="!createOrganization">
|
|
<app-payment [showMethods]="false"></app-payment>
|
|
</ng-container>
|
|
</bit-section>
|
|
<bit-section *ngIf="singleOrgPolicyBlock">
|
|
<app-callout [type]="'error'">{{ "singleOrgBlockCreateMessage" | i18n }}</app-callout>
|
|
</bit-section>
|
|
<bit-section>
|
|
<button
|
|
type="submit"
|
|
buttonType="primary"
|
|
bitButton
|
|
bitFormButton
|
|
[disabled]="!formGroup.valid"
|
|
>
|
|
{{ "submit" | i18n }}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
buttonType="secondary"
|
|
bitButton
|
|
bitFormButton
|
|
(click)="cancel()"
|
|
*ngIf="showCancel"
|
|
>
|
|
{{ "cancel" | i18n }}
|
|
</button>
|
|
</bit-section>
|
|
</form>
|