org create
This commit is contained in:
parent
bd070ff066
commit
1f62b9fdcb
2
jslib
2
jslib
|
@ -1 +1 @@
|
|||
Subproject commit 8be95bfe574a7ae2c8173921bbdfe82451436081
|
||||
Subproject commit e22915818cb7c6a756c4ac34124b14e33621c5aa
|
|
@ -15,6 +15,7 @@ import { RegisterComponent } from './accounts/register.component';
|
|||
import { TwoFactorComponent } from './accounts/two-factor.component';
|
||||
|
||||
import { AccountComponent } from './settings/account.component';
|
||||
import { CreateOrganizationComponent } from './settings/create-organization.component';
|
||||
import { DomainRulesComponent } from './settings/domain-rules.component';
|
||||
import { OptionsComponent } from './settings/options.component';
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
|
@ -60,6 +61,11 @@ const routes: Routes = [
|
|||
{ path: 'domain-rules', component: DomainRulesComponent, canActivate: [AuthGuardService] },
|
||||
{ path: 'two-factor', component: TwoFactorSetupComponent, canActivate: [AuthGuardService] },
|
||||
{ path: 'billing', component: UserBillingComponent, canActivate: [AuthGuardService] },
|
||||
{
|
||||
path: 'create-organization',
|
||||
component: CreateOrganizationComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -37,6 +37,7 @@ import { AdjustPaymentComponent } from './settings/adjust-payment.component';
|
|||
import { AdjustStorageComponent } from './settings/adjust-storage.component';
|
||||
import { ChangeEmailComponent } from './settings/change-email.component';
|
||||
import { ChangePasswordComponent } from './settings/change-password.component';
|
||||
import { CreateOrganizationComponent } from './settings/create-organization.component';
|
||||
import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.component';
|
||||
import { DeleteAccountComponent } from './settings/delete-account.component';
|
||||
import { DomainRulesComponent } from './settings/domain-rules.component';
|
||||
|
@ -127,6 +128,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||
ChangePasswordComponent,
|
||||
CiphersComponent,
|
||||
CollectionsComponent,
|
||||
CreateOrganizationComponent,
|
||||
DeauthorizeSessionsComponent,
|
||||
DeleteAccountComponent,
|
||||
DomainRulesComponent,
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
<div class="page-header">
|
||||
<h1>{{'newOrganization' | i18n}}</h1>
|
||||
</div>
|
||||
<p>{{'newOrganizationDesc' | i18n}}</p>
|
||||
<ng-container *ngIf="selfHosted">
|
||||
<p>{{'uploadLicenseFilePremium' | i18n}}</p>
|
||||
<app-update-license [user]="true" [create]="true" (onUpdated)="finalizePremium()"></app-update-license>
|
||||
</ng-container>
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="!selfHosted">
|
||||
<h2 class="mt-5">{{'generalInformation' | i18n}}</h2>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="name">{{'organizationName' | i18n}}</label>
|
||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<label for="billingEmail">{{'billingEmail' | i18n}}</label>
|
||||
<input id="billingEmail" class="form-control" type="text" name="BillingEmail" [(ngModel)]="billingEmail" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group form-check">
|
||||
<input id="ownedBusiness" class="form-check-input" type="checkbox" name="OwnedBusiness" [(ngModel)]="ownedBusiness" (change)="changedOwnedBusiness()">
|
||||
<label for="ownedBusiness" class="form-check-label">{{'accountOwnedBusiness' | i18n}}</label>
|
||||
</div>
|
||||
<div class="row" *ngIf="ownedBusiness">
|
||||
<div class="form-group col-6">
|
||||
<label for="businessName">{{'businessName' | i18n}}</label>
|
||||
<input id="businessName" class="form-control" type="text" name="BusinessName" [(ngModel)]="businessName">
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="mt-5">{{'chooseYourPlan' | i18n}}</h2>
|
||||
<div class="form-check form-check-block" *ngIf="!ownedBusiness">
|
||||
<input class="form-check-input" type="radio" name="PlanType" id="planFree" value="free" [(ngModel)]="plan" (change)="changedPlan()">
|
||||
<label class="form-check-label" for="planFree">
|
||||
{{'planNameFree' | i18n}}
|
||||
<small class="mb-1">{{'planDescFree' | i18n : '1'}}</small>
|
||||
<small>• {{'limitedUsers' | i18n : '2'}}</small>
|
||||
<small>• {{'limitedCollections' | i18n : '2'}}</small>
|
||||
<span>{{'freeForever' | i18n}}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-block" *ngIf="!ownedBusiness">
|
||||
<input class="form-check-input" type="radio" name="PlanType" id="planFamilies" value="families" [(ngModel)]="plan" (change)="changedPlan()">
|
||||
<label class="form-check-label" for="planFamilies">
|
||||
{{'planNameFamilies' | i18n}}
|
||||
<small class="mb-1">{{'planDescFamilies' | i18n}}</small>
|
||||
<small>• {{'addShareLimitedUsers' | i18n : '5'}}</small>
|
||||
<small>• {{'createUnlimitedCollections' | i18n}}</small>
|
||||
<small>• {{'gbEncryptedFileStorage' | i18n : '1 GB'}}</small>
|
||||
<small>• {{'onPremHostingOptional' | i18n}}</small>
|
||||
<small>• {{'priorityCustomerSupport' | i18n}}</small>
|
||||
<small>• {{'xDayFreeTrial' | i18n : '7'}}</small>
|
||||
<span>{{1 | currency:'$'}} /{{'month' | i18n}}, {{'includesXUsers' | i18n : 5}}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-block">
|
||||
<input class="form-check-input" type="radio" name="PlanType" id="planTeams" value="teams" [(ngModel)]="plan" (change)="changedPlan()">
|
||||
<label class="form-check-label" for="planTeams">
|
||||
{{'planNameTeams' | i18n}}
|
||||
<small class="mb-1">{{'planDescTeams' | i18n}}</small>
|
||||
<small>• {{'addShareUnlimitedUsers' | i18n}}</small>
|
||||
<small>• {{'createUnlimitedCollections' | i18n}}</small>
|
||||
<small>• {{'gbEncryptedFileStorage' | i18n : '1 GB'}}</small>
|
||||
<small>• {{'priorityCustomerSupport' | i18n}}</small>
|
||||
<small>• {{'xDayFreeTrial' | i18n : '7'}}</small>
|
||||
<span>{{5 | currency:'$'}} /{{'month' | i18n}}, {{'includesXUsers' | i18n : 5}}, {{('additionalUsers' | i18n).toLowerCase()}}
|
||||
{{2 | currency:'$'}} /{{'month' | i18n}}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-block">
|
||||
<input class="form-check-input" type="radio" name="PlanType" id="planEnterprise" value="enterprise" [(ngModel)]="plan" (change)="changedPlan()">
|
||||
<label class="form-check-label" for="planEnterprise">
|
||||
{{'planNameEnterprise' | i18n}}
|
||||
<small class="mb-1">{{'planDescEnterprise' | i18n}}</small>
|
||||
<small>• {{'addShareUnlimitedUsers' | i18n}}</small>
|
||||
<small>• {{'createUnlimitedCollections' | i18n}}</small>
|
||||
<small>• {{'gbEncryptedFileStorage' | i18n : '1 GB'}}</small>
|
||||
<small>• {{'controlAccessWithGroups' | i18n}}</small>
|
||||
<small>• {{'trackAuditLogs' | i18n}}</small>
|
||||
<small>• {{'syncUsersFromDirectory' | i18n}}</small>
|
||||
<small>• {{'onPremHostingOptional' | i18n}}</small>
|
||||
<small>• {{'priorityCustomerSupport' | i18n}}</small>
|
||||
<small>• {{'xDayFreeTrial' | i18n : '7'}}</small>
|
||||
<span>{{'costPerUser' | i18n : (3 | currency:'$')}} /{{'month' | i18n}}</span>
|
||||
</label>
|
||||
</div>
|
||||
<ng-container *ngIf="!plans[plan].noPayment">
|
||||
<ng-container *ngIf="!plans[plan].noAdditionalSeats && !plans[plan].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" [(ngModel)]="additionalSeats" min="1"
|
||||
max="100000" 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="!plans[plan].noAdditionalSeats && plans[plan].baseSeats">
|
||||
<div class="form-group col-6">
|
||||
<label for="additionalSeats">{{'additionalUserSeats' | i18n}}</label>
|
||||
<input id="additionalSeats" class="form-control" type="number" name="AdditionalSeats" [(ngModel)]="additionalSeats" min="0"
|
||||
max="100000" placeholder="{{'userSeatsDesc' | i18n}}">
|
||||
<small class="text-muted form-text">{{'userSeatsAdditionalDesc' | i18n : plans[plan].baseSeats : (plans[plan].seatPrice | 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" [(ngModel)]="additionalStorage"
|
||||
min="0" max="99" step="1" placeholder="{{'additionalStorageGbDesc' | i18n}}">
|
||||
<small class="text-muted form-text">{{'additionalStorageDesc' | i18n : '1 GB' : (storageGb.price | currency:'$')}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="spaced-header">{{'summary' | i18n}}</h2>
|
||||
<div class="form-check form-check-block">
|
||||
<input class="form-check-input" type="radio" name="BillingInterval" id="intervalAnnually" value="year" [(ngModel)]="interval">
|
||||
<label class="form-check-label" for="intervalAnnually">
|
||||
{{'annually' | i18n}}
|
||||
<small *ngIf="plans[plan].annualBasePrice">
|
||||
{{'basePrice' | i18n}}: {{plans[plan].basePrice | currency:'$'}} ×12 {{'monthAbbr' | i18n}} = {{baseTotal(true) | currency:'$'}}
|
||||
/{{'year' | i18n}}
|
||||
</small>
|
||||
<small *ngIf="!plans[plan].noAdditionalSeats">
|
||||
<span *ngIf="plans[plan].baseSeats">{{'additionalUsers' | i18n}}:</span>
|
||||
<span *ngIf="!plans[plan].baseSeats">{{'users' | i18n}}:</span>
|
||||
{{additionalSeats || 0}} × {{plans[plan].seatPrice | currency:'$'}} ×12 {{'monthAbbr' | i18n}} = {{seatTotal(true)
|
||||
| currency:'$'}} /{{'year' | i18n}}
|
||||
</small>
|
||||
<small>
|
||||
{{'additionalStorageGb' | i18n}}: {{additionalStorage || 0}} × {{storageGb.price | currency:'$'}} ×12 {{'monthAbbr'
|
||||
| i18n}} = {{additionalStorageTotal(true) | currency:'$'}} /{{'year' | i18n}}
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-block" *ngIf="plans[plan].monthlySeatPrice">
|
||||
<input class="form-check-input" type="radio" name="BillingInterval" id="intervalMonthly" value="month" [(ngModel)]="interval">
|
||||
<label class="form-check-label" for="intervalMonthly">
|
||||
{{'monthly' | i18n}}
|
||||
<small *ngIf="plans[plan].monthlyBasePrice">
|
||||
{{'basePrice' | i18n}}: {{baseTotal(false) | currency:'$'}} /{{'month' | i18n}}
|
||||
</small>
|
||||
<small *ngIf="!plans[plan].noAdditionalSeats">
|
||||
<span *ngIf="plans[plan].baseSeats">{{'additionalUsers' | i18n}}:</span>
|
||||
<span *ngIf="!plans[plan].baseSeats">{{'users' | i18n}}:</span>
|
||||
{{additionalSeats || 0}} × {{plans[plan].monthlySeatPrice | currency:'$'}} = {{seatTotal(false) | currency:'$'}} /{{'month'
|
||||
| i18n}}
|
||||
</small>
|
||||
<small>
|
||||
{{'additionalStorageGb' | i18n}}: {{additionalStorage || 0}} × {{storageGb.monthlyPrice | currency:'$'}} = {{additionalStorageTotal(false)
|
||||
| currency:'$'}} /{{'month' | i18n}}
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
<hr class="my-2">
|
||||
<strong>{{'total' | i18n}}:</strong> {{total | currency:'USD $'}} /{{interval | i18n}}
|
||||
<br>
|
||||
<small class="text-muted">{{'paymentChargedWithTrial' | i18n : (interval | i18n) }}</small>
|
||||
<h2 class="spaced-header mb-4">{{'paymentInformation' | i18n}}</h2>
|
||||
<app-payment></app-payment>
|
||||
</ng-container>
|
||||
<div [ngClass]="{'mt-4': plans[plan].noPayment}">
|
||||
<button type="submit" class="btn btn-primary btn-submit" appBlurClick [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,194 @@
|
|||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { PaymentComponent } from './payment.component';
|
||||
|
||||
import { PlanType } from 'jslib/enums/planType';
|
||||
import { OrganizationCreateRequest } from 'jslib/models/request/organizationCreateRequest';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-organization',
|
||||
templateUrl: 'create-organization.component.html',
|
||||
})
|
||||
export class CreateOrganizationComponent {
|
||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
|
||||
selfHosted = false;
|
||||
ownedBusiness = false;
|
||||
storageGbPriceMonthly = 0.33;
|
||||
additionalStorage = 0;
|
||||
additionalSeats = 0;
|
||||
plan = 'free';
|
||||
interval = 'year';
|
||||
name: string;
|
||||
billingEmail: string;
|
||||
businessName: string;
|
||||
|
||||
storageGb: any = {
|
||||
price: 0.33,
|
||||
monthlyPrice: 0.50,
|
||||
yearlyPrice: 4,
|
||||
};
|
||||
|
||||
plans: any = {
|
||||
free: {
|
||||
basePrice: 0,
|
||||
noAdditionalSeats: true,
|
||||
noPayment: true,
|
||||
},
|
||||
families: {
|
||||
basePrice: 1,
|
||||
annualBasePrice: 12,
|
||||
baseSeats: 5,
|
||||
noAdditionalSeats: true,
|
||||
annualPlanType: PlanType.FamiliesAnnually,
|
||||
},
|
||||
teams: {
|
||||
basePrice: 5,
|
||||
annualBasePrice: 60,
|
||||
monthlyBasePrice: 8,
|
||||
baseSeats: 5,
|
||||
seatPrice: 2,
|
||||
annualSeatPrice: 24,
|
||||
monthlySeatPrice: 2.5,
|
||||
monthPlanType: PlanType.TeamsMonthly,
|
||||
annualPlanType: PlanType.TeamsAnnually,
|
||||
},
|
||||
enterprise: {
|
||||
seatPrice: 3,
|
||||
annualSeatPrice: 36,
|
||||
monthlySeatPrice: 4,
|
||||
monthPlanType: PlanType.EnterpriseMonthly,
|
||||
annualPlanType: PlanType.EnterpriseAnnually,
|
||||
},
|
||||
};
|
||||
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
platformUtilsService: PlatformUtilsService, private cryptoService: CryptoService,
|
||||
private router: Router) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
let key: string = null;
|
||||
let collectionCt: string = null;
|
||||
|
||||
try {
|
||||
this.formPromise = this.cryptoService.makeShareKey().then((shareKey) => {
|
||||
key = shareKey[0].encryptedString;
|
||||
return this.cryptoService.encrypt('Default Collection', shareKey[1]);
|
||||
}).then((collection) => {
|
||||
collectionCt = collection.encryptedString;
|
||||
if (this.plan === 'free') {
|
||||
return null;
|
||||
} else {
|
||||
return this.paymentComponent.createPaymentToken();
|
||||
}
|
||||
}).then((token: string) => {
|
||||
const request = new OrganizationCreateRequest();
|
||||
request.key = key;
|
||||
request.collectionName = collectionCt;
|
||||
request.name = this.name;
|
||||
request.billingEmail = this.billingEmail;
|
||||
|
||||
if (this.plan === 'free') {
|
||||
request.planType = PlanType.Free;
|
||||
} else {
|
||||
request.paymentToken = token;
|
||||
request.businessName = this.ownedBusiness ? this.businessName : null;
|
||||
request.additionalSeats = this.additionalSeats;
|
||||
request.additionalStorageGb = this.additionalStorage;
|
||||
request.country = this.paymentComponent.getCountry();
|
||||
if (this.interval === 'month') {
|
||||
request.planType = this.plans[this.plan].monthPlanType;
|
||||
} else {
|
||||
request.planType = this.plans[this.plan].annualPlanType;
|
||||
}
|
||||
}
|
||||
|
||||
return this.apiService.postOrganization(request);
|
||||
}).then((response) => {
|
||||
return this.finalize(response.id);
|
||||
});
|
||||
await this.formPromise;
|
||||
} catch { }
|
||||
}
|
||||
|
||||
async finalize(orgId: string) {
|
||||
this.apiService.refreshIdentityToken();
|
||||
this.analytics.eventTrack.next({ action: 'Created Organization' });
|
||||
this.toasterService.popAsync('success', this.i18nService.t('organizationCreated'),
|
||||
this.i18nService.t('organizationReadyToGo'));
|
||||
this.router.navigate(['/organizations/' + orgId]);
|
||||
}
|
||||
|
||||
changedPlan() {
|
||||
if (this.plans[this.plan].monthPlanType == null) {
|
||||
this.interval = 'year';
|
||||
}
|
||||
|
||||
if (this.plans[this.plan].noAdditionalSeats) {
|
||||
this.additionalSeats = 0;
|
||||
} else if (!this.additionalSeats && !this.plans[this.plan].baseSeats &&
|
||||
!this.plans[this.plan].noAdditionalSeats) {
|
||||
this.additionalSeats = 1;
|
||||
}
|
||||
}
|
||||
|
||||
changedOwnedBusiness() {
|
||||
if (!this.ownedBusiness || this.plan === 'teams' || this.plan === 'enterprise') {
|
||||
return;
|
||||
}
|
||||
this.plan = 'teams';
|
||||
}
|
||||
|
||||
additionalStorageTotal(annual: boolean): number {
|
||||
if (annual) {
|
||||
return (this.additionalStorage || 0) * this.storageGb.yearlyPrice;
|
||||
} else {
|
||||
return (this.additionalStorage || 0) * this.storageGb.monthlyPrice;
|
||||
}
|
||||
}
|
||||
|
||||
seatTotal(annual: boolean): number {
|
||||
if (this.plans[this.plan].noAdditionalSeats) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (annual) {
|
||||
return this.plans[this.plan].annualSeatPrice * (this.additionalSeats || 0);
|
||||
} else {
|
||||
return this.plans[this.plan].monthlySeatPrice * (this.additionalSeats || 0);
|
||||
}
|
||||
}
|
||||
|
||||
baseTotal(annual: boolean): number {
|
||||
if (annual) {
|
||||
return (this.plans[this.plan].annualBasePrice || 0);
|
||||
} else {
|
||||
return (this.plans[this.plan].monthlyBasePrice || 0);
|
||||
}
|
||||
}
|
||||
|
||||
get total(): number {
|
||||
const annual = this.interval === 'year';
|
||||
return this.baseTotal(annual) + this.seatTotal(annual) + this.additionalStorageTotal(annual);
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@
|
|||
<label for="additionalStorage">{{'additionalStorageGb' | i18n}}</label>
|
||||
<input id="additionalStorage" class="form-control" type="number" name="AdditionalStorageGb" [(ngModel)]="additionalStorage"
|
||||
min="0" max="99" step="1" placeholder="{{'additionalStorageGbDesc' | i18n}}">
|
||||
<small class="text-muted form-text">{{'additionalStorageDesc' | i18n : (storageGbPrice | currency:'$')}}</small>
|
||||
<small class="text-muted form-text">{{'additionalStorageDesc' | i18n : '1 GB' : (storageGbPrice | currency:'$')}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="spaced-header">{{'summary' | i18n}}</h2>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</ul>
|
||||
<p *ngIf="!organizations || !organizations.length">{{'noOrganizationsList' | i18n}}</p>
|
||||
</ng-container>
|
||||
<button (click)="newOrganization()" class="btn btn-block btn-outline-primary">
|
||||
<a href="#" appStopClick routerLink="/settings/create-organization" class="btn btn-block btn-outline-primary">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
{{'newOrganization' | i18n}}
|
||||
</button>
|
||||
</a>
|
||||
|
|
|
@ -570,7 +570,7 @@
|
|||
"message": "New Organization"
|
||||
},
|
||||
"noOrganizationsList": {
|
||||
"message": "You do not belong to any organizations."
|
||||
"message": "You do not belong to any organizations. Organizations allow you to securely share items with other users."
|
||||
},
|
||||
"versionNumber": {
|
||||
"message": "Version $VERSION_NUMBER$",
|
||||
|
@ -1254,10 +1254,14 @@
|
|||
"message": "# of additional GB"
|
||||
},
|
||||
"additionalStorageDesc": {
|
||||
"message": "Your plan comes with 1 GB of encrypted file storage. You can add additional storage for $PRICE$ per GB /year.",
|
||||
"message": "Your plan comes with $SIZE$ of encrypted file storage. You can add additional storage for $PRICE$ per GB /year.",
|
||||
"placeholders": {
|
||||
"price": {
|
||||
"size": {
|
||||
"content": "$1",
|
||||
"example": "1 GB"
|
||||
},
|
||||
"price": {
|
||||
"content": "$2",
|
||||
"example": "$4.00"
|
||||
}
|
||||
}
|
||||
|
@ -1274,11 +1278,21 @@
|
|||
"month": {
|
||||
"message": "month"
|
||||
},
|
||||
"monthAbbr": {
|
||||
"message": "mo.",
|
||||
"description": "Short abbreviation for 'month'"
|
||||
},
|
||||
"paymentChargedAnnually": {
|
||||
"message": "Your payment method will be charged immediately and on a recurring basis each year. You may cancel at any time."
|
||||
},
|
||||
"paymentChargedMonthly": {
|
||||
"message": "Your payment method will be charged immediately and on a recurring basis each month. You may cancel at any time."
|
||||
"paymentChargedWithTrial": {
|
||||
"message": "Your plan comes with a free 7 day trial. Your card will not be charged until the trial has ended and on a recurring basis each $INTERVAL$. You may cancel at any time.",
|
||||
"placeholders": {
|
||||
"interval": {
|
||||
"content": "$1",
|
||||
"example": "year"
|
||||
}
|
||||
}
|
||||
},
|
||||
"paymentInformation": {
|
||||
"message": "Payment Information"
|
||||
|
@ -1440,5 +1454,192 @@
|
|||
},
|
||||
"accountEmailMustBeVerified": {
|
||||
"message": "Your account's email address must be verified."
|
||||
},
|
||||
"newOrganizationDesc": {
|
||||
"message": "Organizations allow you to share parts of your vault with others as well as manage related users for a specific entity such as a family, small team, or large company."
|
||||
},
|
||||
"generalInformation": {
|
||||
"message": "General Information"
|
||||
},
|
||||
"organizationName": {
|
||||
"message": "Organization Name"
|
||||
},
|
||||
"accountOwnedBusiness": {
|
||||
"message": "This account is owned by a business."
|
||||
},
|
||||
"billingEmail": {
|
||||
"message": "Billing Email"
|
||||
},
|
||||
"businessName": {
|
||||
"message": "Business Name"
|
||||
},
|
||||
"chooseYourPlan": {
|
||||
"message": "Choose Your Plan"
|
||||
},
|
||||
"users": {
|
||||
"message": "Users"
|
||||
},
|
||||
"userSeats": {
|
||||
"message": "User Seats"
|
||||
},
|
||||
"additionalUserSeats": {
|
||||
"message": "Additional User Seats"
|
||||
},
|
||||
"userSeatsDesc": {
|
||||
"message": "# of user seats"
|
||||
},
|
||||
"userSeatsAdditionalDesc": {
|
||||
"message": "Your plan comes with $BASE_SEATS$ user seats. You can add additional users for $SEAT_PRICE$ per user /month.",
|
||||
"placeholders": {
|
||||
"base_seats": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
},
|
||||
"seat_price": {
|
||||
"content": "$2",
|
||||
"example": "$2.00"
|
||||
}
|
||||
}
|
||||
},
|
||||
"userSeatsHowManyDesc": {
|
||||
"message": "How many user seats do you need? You can also add additional seats later if needed."
|
||||
},
|
||||
"planNameFree": {
|
||||
"message": "Free"
|
||||
},
|
||||
"planDescFree": {
|
||||
"message": "For testing or personal users to share with $COUNT$ other user.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"planNameFamilies": {
|
||||
"message": "Families"
|
||||
},
|
||||
"planDescFamilies": {
|
||||
"message": "For personal use, to share with family & friends."
|
||||
},
|
||||
"planNameTeams": {
|
||||
"message": "Teams"
|
||||
},
|
||||
"planDescTeams": {
|
||||
"message": "For businesses and other team organizations."
|
||||
},
|
||||
"planNameEnterprise": {
|
||||
"message": "Enterprise"
|
||||
},
|
||||
"planDescEnterprise": {
|
||||
"message": "For businesses and other large organizations."
|
||||
},
|
||||
"freeForever": {
|
||||
"message": "Free Forever"
|
||||
},
|
||||
"includesXUsers": {
|
||||
"message": "includes $COUNT$ users",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalUsers": {
|
||||
"message": "Additional Users"
|
||||
},
|
||||
"costPerUser": {
|
||||
"message": "$COST$ per user",
|
||||
"placeholders": {
|
||||
"cost": {
|
||||
"content": "$1",
|
||||
"example": "$3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"limitedUsers": {
|
||||
"message": "Limited to $COUNT$ users (including you)",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"limitedCollections": {
|
||||
"message": "Limited to $COUNT$ collections",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addShareLimitedUsers": {
|
||||
"message": "Add and share with up to $COUNT$ users",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addShareUnlimitedUsers": {
|
||||
"message": "Add and share with unlimited users"
|
||||
},
|
||||
"createUnlimitedCollections": {
|
||||
"message": "Create unlimited collections"
|
||||
},
|
||||
"gbEncryptedFileStorage": {
|
||||
"message": "$SIZE$ encrypted file storage",
|
||||
"placeholders": {
|
||||
"size": {
|
||||
"content": "$1",
|
||||
"example": "1 GB"
|
||||
}
|
||||
}
|
||||
},
|
||||
"onPremHostingOptional": {
|
||||
"message": "On-premise hosting (optional)"
|
||||
},
|
||||
"controlAccessWithGroups": {
|
||||
"message": "Control user access with groups"
|
||||
},
|
||||
"syncUsersFromDirectory": {
|
||||
"message": "Sync your users and groups from a directory"
|
||||
},
|
||||
"trackAuditLogs": {
|
||||
"message": "Track user actions with audit logs"
|
||||
},
|
||||
"enforce2faDuo": {
|
||||
"message": "Enforce 2FA with Duo"
|
||||
},
|
||||
"priorityCustomerSupport": {
|
||||
"message": "Priority customer support"
|
||||
},
|
||||
"xDayFreeTrial": {
|
||||
"message": "$COUNT$ day free trial, cancel anytime",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "7"
|
||||
}
|
||||
}
|
||||
},
|
||||
"monthly": {
|
||||
"message": "Monthly"
|
||||
},
|
||||
"annually": {
|
||||
"message": "Annually"
|
||||
},
|
||||
"basePrice": {
|
||||
"message": "Base Price"
|
||||
},
|
||||
"organizationCreated": {
|
||||
"message": "Organization Created"
|
||||
},
|
||||
"organizationReadyToGo": {
|
||||
"message": "Your new organization is ready to go!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ $h4-font-size: 1rem;
|
|||
$h5-font-size: 1rem;
|
||||
$h6-font-size: 1rem;
|
||||
|
||||
$small-font-size: 90%;
|
||||
$font-size-lg: 1.18rem;
|
||||
$code-font-size: 100%;
|
||||
|
||||
|
@ -548,3 +549,25 @@ app-user-billing {
|
|||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-check-block {
|
||||
.form-check-label {
|
||||
font-weight: bold;
|
||||
|
||||
> small {
|
||||
display: block;
|
||||
color: $text-muted;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
@extend .mt-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-check-block + .form-check-block {
|
||||
@extend .mt-3;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue