mirror of
https://github.com/bitwarden/browser
synced 2026-03-01 11:01:17 +00:00
441 lines
17 KiB
HTML
441 lines
17 KiB
HTML
<ng-container *ngIf="loading">
|
|
<i
|
|
class="bwi bwi-spinner bwi-spin text-muted"
|
|
title="{{ 'loading' | i18n }}"
|
|
aria-hidden="true"
|
|
></i>
|
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
|
</ng-container>
|
|
<ng-container *ngIf="createOrganization && selfHosted">
|
|
<p>{{ "uploadLicenseFileOrg" | i18n }}</p>
|
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
|
<div class="form-group">
|
|
<label for="file">{{ "licenseFile" | i18n }}</label>
|
|
<input type="file" id="file" class="form-control-file" name="file" required />
|
|
<small class="form-text text-muted">{{
|
|
"licenseFileDesc" | i18n : "bitwarden_organization_license.json"
|
|
}}</small>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
|
<span>{{ "submit" | i18n }}</span>
|
|
</button>
|
|
</form>
|
|
</ng-container>
|
|
<form
|
|
#form
|
|
[formGroup]="formGroup"
|
|
(ngSubmit)="submit()"
|
|
[appApiAction]="formPromise"
|
|
ngNativeValidate
|
|
*ngIf="!loading && !selfHosted && this.passwordManagerPlans && this.secretsManagerPlans"
|
|
class="tw-pt-6"
|
|
>
|
|
<app-org-info
|
|
(changedBusinessOwned)="changedOwnedBusiness()"
|
|
[formGroup]="formGroup"
|
|
[createOrganization]="createOrganization"
|
|
[isProvider]="!!providerId"
|
|
[acceptingSponsorship]="acceptingSponsorship"
|
|
></app-org-info>
|
|
<h2 class="mt-5">{{ "chooseYourPlan" | i18n }}</h2>
|
|
<div *ngFor="let selectableProduct of selectableProducts" class="form-check form-check-block">
|
|
<input
|
|
class="form-check-input"
|
|
type="radio"
|
|
name="product"
|
|
id="product{{ selectableProduct.product }}"
|
|
[value]="selectableProduct.product"
|
|
formControlName="product"
|
|
(change)="changedProduct()"
|
|
/>
|
|
<label class="form-check-label" for="product{{ selectableProduct.product }}">
|
|
{{ selectableProduct.nameLocalizationKey | i18n }}
|
|
<small class="mb-1">{{ selectableProduct.descriptionLocalizationKey | i18n : "1" }}</small>
|
|
<ng-container
|
|
*ngIf="selectableProduct.product === productTypes.Enterprise; else nonEnterprisePlans"
|
|
>
|
|
<small>• {{ "includeAllTeamsFeatures" | i18n }}</small>
|
|
<small *ngIf="selectableProduct.hasSelfHost">• {{ "onPremHostingOptional" | i18n }}</small>
|
|
<small *ngIf="selectableProduct.hasSso">• {{ "includeSsoAuthentication" | i18n }}</small>
|
|
<small *ngIf="selectableProduct.hasPolicies"
|
|
>• {{ "includeEnterprisePolicies" | i18n }}</small
|
|
>
|
|
<small *ngIf="selectableProduct.trialPeriodDays && createOrganization"
|
|
>•
|
|
{{ "xDayFreeTrial" | i18n : selectableProduct.trialPeriodDays }}
|
|
</small>
|
|
</ng-container>
|
|
<ng-template #nonEnterprisePlans>
|
|
<ng-container
|
|
*ngIf="
|
|
teamsStarterPlanFeatureFlagIsEnabled &&
|
|
selectableProduct.product === productTypes.Teams;
|
|
else fullFeatureList
|
|
"
|
|
>
|
|
<small>• {{ "includeAllTeamsStarterFeatures" | i18n }}</small>
|
|
<small>• {{ "chooseMonthlyOrAnnualBilling" | i18n }}</small>
|
|
<small>• {{ "abilityToAddMoreThanNMembers" | i18n : 10 }}</small>
|
|
<small *ngIf="selectableProduct.trialPeriodDays && createOrganization">
|
|
• {{ "xDayFreeTrial" | i18n : selectableProduct.trialPeriodDays }}
|
|
</small>
|
|
</ng-container>
|
|
<ng-template #fullFeatureList>
|
|
<small *ngIf="selectableProduct.product == productTypes.Free"
|
|
>• {{ "limitedUsers" | i18n : selectableProduct.PasswordManager.maxSeats }}</small
|
|
>
|
|
<small
|
|
*ngIf="
|
|
selectableProduct.product != productTypes.Free &&
|
|
selectableProduct.product != productTypes.TeamsStarter &&
|
|
selectableProduct.PasswordManager.maxSeats
|
|
"
|
|
>•
|
|
{{ "addShareLimitedUsers" | i18n : selectableProduct.PasswordManager.maxSeats }}</small
|
|
>
|
|
<small *ngIf="!selectableProduct.PasswordManager.maxSeats"
|
|
>• {{ "addShareUnlimitedUsers" | i18n }}</small
|
|
>
|
|
<small *ngIf="selectableProduct.PasswordManager.maxCollections"
|
|
>•
|
|
{{
|
|
"limitedCollections" | i18n : selectableProduct.PasswordManager.maxCollections
|
|
}}</small
|
|
>
|
|
<small *ngIf="selectableProduct.PasswordManager.maxAdditionalSeats"
|
|
>•
|
|
{{
|
|
"addShareLimitedUsers" | i18n : selectableProduct.PasswordManager.maxAdditionalSeats
|
|
}}</small
|
|
>
|
|
<small *ngIf="!selectableProduct.PasswordManager.maxCollections"
|
|
>• {{ "createUnlimitedCollections" | i18n }}</small
|
|
>
|
|
<small *ngIf="selectableProduct.PasswordManager.baseStorageGb"
|
|
>•
|
|
{{
|
|
"gbEncryptedFileStorage"
|
|
| i18n : selectableProduct.PasswordManager.baseStorageGb + "GB"
|
|
}}</small
|
|
>
|
|
<small *ngIf="selectableProduct.hasGroups"
|
|
>• {{ "controlAccessWithGroups" | i18n }}</small
|
|
>
|
|
<small *ngIf="selectableProduct.hasApi">• {{ "trackAuditLogs" | i18n }}</small>
|
|
<small *ngIf="selectableProduct.hasDirectory"
|
|
>• {{ "syncUsersFromDirectory" | i18n }}</small
|
|
>
|
|
<small *ngIf="selectableProduct.hasSelfHost"
|
|
>• {{ "onPremHostingOptional" | i18n }}</small
|
|
>
|
|
<small *ngIf="selectableProduct.usersGetPremium">• {{ "usersGetPremium" | i18n }}</small>
|
|
<small *ngIf="selectableProduct.product != productTypes.Free"
|
|
>• {{ "priorityCustomerSupport" | i18n }}</small
|
|
>
|
|
<small *ngIf="selectableProduct.trialPeriodDays && createOrganization"
|
|
>•
|
|
{{ "xDayFreeTrial" | i18n : selectableProduct.trialPeriodDays }}
|
|
</small>
|
|
</ng-template>
|
|
</ng-template>
|
|
<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>
|
|
</label>
|
|
</div>
|
|
<div *ngIf="formGroup.value.product !== productTypes.Free">
|
|
<ng-container
|
|
*ngIf="
|
|
selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
|
|
!selectedPlan.PasswordManager.baseSeats
|
|
"
|
|
>
|
|
<h2 class="mt-5">{{ "users" | i18n }}</h2>
|
|
<div class="row">
|
|
<div class="col-6">
|
|
<label for="additionalSeats">{{ "userSeats" | i18n }}</label>
|
|
<input
|
|
id="additionalSeats"
|
|
class="form-control"
|
|
type="number"
|
|
name="additionalSeats"
|
|
formControlName="additionalSeats"
|
|
placeholder="{{ 'userSeatsDesc' | i18n }}"
|
|
required
|
|
/>
|
|
<small class="text-muted form-text">{{ "userSeatsHowManyDesc" | i18n }}</small>
|
|
</div>
|
|
</div>
|
|
</ng-container>
|
|
<h2 class="mt-5">{{ "addons" | i18n }}</h2>
|
|
<div
|
|
class="row"
|
|
*ngIf="
|
|
selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
|
|
selectedPlan.PasswordManager.baseSeats
|
|
"
|
|
>
|
|
<div class="form-group col-6">
|
|
<label for="additionalSeats">{{ "additionalUserSeats" | i18n }}</label>
|
|
<input
|
|
id="additionalSeats"
|
|
class="form-control"
|
|
type="number"
|
|
name="additionalSeats"
|
|
formControlName="additionalSeats"
|
|
placeholder="{{ 'userSeatsDesc' | i18n }}"
|
|
/>
|
|
<small class="text-muted form-text">{{
|
|
"userSeatsAdditionalDesc"
|
|
| i18n
|
|
: selectedPlan.PasswordManager.baseSeats
|
|
: (seatPriceMonthly(selectedPlan) | currency : "$")
|
|
}}</small>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="form-group col-6">
|
|
<label for="additionalStorage">{{ "additionalStorageGb" | i18n }}</label>
|
|
<input
|
|
id="additionalStorage"
|
|
class="form-control"
|
|
type="number"
|
|
name="additionalStorageGb"
|
|
formControlName="additionalStorage"
|
|
step="1"
|
|
placeholder="{{ 'additionalStorageGbDesc' | i18n }}"
|
|
/>
|
|
<small class="text-muted form-text">{{
|
|
"additionalStorageIntervalDesc"
|
|
| i18n
|
|
: "1 GB"
|
|
: (additionalStoragePriceMonthly(selectedPlan) | currency : "$")
|
|
: ("month" | i18n)
|
|
}}</small>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="form-group col-6" *ngIf="selectedPlan.PasswordManager.hasPremiumAccessOption">
|
|
<div class="form-check">
|
|
<input
|
|
id="premiumAccess"
|
|
class="form-check-input"
|
|
type="checkbox"
|
|
name="premiumAccessAddon"
|
|
formControlName="premiumAccessAddon"
|
|
/>
|
|
<label for="premiumAccess" class="form-check-label bold">{{
|
|
"premiumAccess" | i18n
|
|
}}</label>
|
|
</div>
|
|
<small class="text-muted form-text">{{
|
|
"premiumAccessDesc" | i18n : (3.33 | currency : "$") : ("month" | i18n)
|
|
}}</small>
|
|
</div>
|
|
</div>
|
|
<h2 class="spaced-header">{{ "summary" | i18n }}</h2>
|
|
<div class="form-check form-check-block" *ngFor="let selectablePlan of selectablePlans">
|
|
<input
|
|
class="form-check-input"
|
|
type="radio"
|
|
name="plan"
|
|
id="interval{{ selectablePlan.type }}"
|
|
[value]="selectablePlan.type"
|
|
formControlName="plan"
|
|
/>
|
|
<label class="form-check-label" for="interval{{ selectablePlan.type }}">
|
|
<ng-container *ngIf="selectablePlan.isAnnual">
|
|
{{ "annually" | i18n }}
|
|
<small *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 style="text-decoration: line-through">{{
|
|
selectablePlan.PasswordManager.basePrice | currency : "$"
|
|
}}</span>
|
|
{{ "freeWithSponsorship" | i18n }}
|
|
</ng-container>
|
|
<ng-template #notAcceptingSponsorship>
|
|
{{ selectablePlan.PasswordManager.basePrice | currency : "$" }}
|
|
/{{ "year" | i18n }}
|
|
</ng-template>
|
|
</small>
|
|
<small *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 }}
|
|
</small>
|
|
<small *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 }}
|
|
</small>
|
|
</ng-container>
|
|
<ng-container *ngIf="!selectablePlan.isAnnual">
|
|
{{ "monthly" | i18n }}
|
|
<small *ngIf="selectablePlan.PasswordManager.basePrice">
|
|
{{ "basePrice" | i18n }}:
|
|
{{ selectablePlan.PasswordManager.basePrice | currency : "$" }}
|
|
{{ "monthAbbr" | i18n }}
|
|
=
|
|
{{ selectablePlan.PasswordManager.basePrice | currency : "$" }}
|
|
/{{ "month" | i18n }}
|
|
</small>
|
|
<small *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 }}
|
|
</small>
|
|
<small *ngIf="selectablePlan.PasswordManager.hasAdditionalStorageOption">
|
|
{{ "additionalStorageGb" | i18n }}:
|
|
{{ formGroup.controls["additionalStorage"].value || 0 }} ×
|
|
{{ selectablePlan.PasswordManager.additionalStoragePricePerGb | currency : "$" }}
|
|
{{ "monthAbbr" | i18n }} =
|
|
{{ additionalStorageTotal(selectablePlan) | currency : "$" }} /{{ "month" | i18n }}
|
|
</small>
|
|
</ng-container>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Secrets Manager -->
|
|
<div class="tw-my-10">
|
|
<sm-subscribe
|
|
*ngIf="planOffersSecretsManager && !hasProvider"
|
|
[formGroup]="formGroup.controls.secretsManager"
|
|
[selectedPlan]="selectedSecretsManagerPlan"
|
|
[upgradeOrganization]="!createOrganization"
|
|
></sm-subscribe>
|
|
</div>
|
|
|
|
<!-- Payment info -->
|
|
<div *ngIf="formGroup.value.product !== productTypes.Free">
|
|
<h2 class="mb-4">
|
|
{{ (createOrganization ? "paymentInformation" : "billingInformation") | i18n }}
|
|
</h2>
|
|
<small class="text-muted font-italic mb-3 d-block">
|
|
{{ paymentDesc }}
|
|
</small>
|
|
<app-payment
|
|
*ngIf="createOrganization || upgradeRequiresPaymentMethod"
|
|
[hideCredit]="true"
|
|
></app-payment>
|
|
<app-tax-info (onCountryChanged)="changedCountry()"></app-tax-info>
|
|
<div id="price" class="my-4">
|
|
<div class="text-muted text-sm">
|
|
{{ "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="my-1 col-3 ml-0" />
|
|
<p class="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>
|
|
</div>
|
|
<div *ngIf="singleOrgPolicyBlock" class="mt-4">
|
|
<app-callout [type]="'error'">{{ "singleOrgBlockCreateMessage" | i18n }}</app-callout>
|
|
</div>
|
|
<div class="mt-4">
|
|
<button
|
|
type="submit"
|
|
buttonType="primary"
|
|
bitButton
|
|
[loading]="form.loading"
|
|
[disabled]="!formGroup.valid"
|
|
>
|
|
{{ "submit" | i18n }}
|
|
</button>
|
|
<button type="button" buttonType="secondary" bitButton (click)="cancel()" *ngIf="showCancel">
|
|
{{ "cancel" | i18n }}
|
|
</button>
|
|
</div>
|
|
</form>
|