Organization autoscaling (#1193)
* Add seat autoscale component * Move small description under title * tweak autoscale terminology * Linter fixes * Use single component for org subscription updates * Delete unused localization string * Clarify max bill copy * Remove cancel from org subscription adjustment * Update jslib * PR review * update jslib * Simplify success toast
This commit is contained in:
parent
1df2225a52
commit
c98a189430
2
jslib
2
jslib
|
@ -1 +1 @@
|
|||
Subproject commit 2c892eb3a2a9aff1e238146b037e6f3eb5dacf9a
|
||||
Subproject commit cb00604617a3d38fb450d900dbdf63b636ae01f6
|
|
@ -1,29 +0,0 @@
|
|||
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h3 class="card-body-header">{{(add ? 'addSeats' : 'removeSeats') | i18n}}</h3>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="seatAdjustment">{{(add ? 'seatsToAdd' : 'seatsToRemove') | i18n}}</label>
|
||||
<input id="seatAdjustment" class="form-control" type="number" name="SeatAdjustment"
|
||||
[(ngModel)]="seatAdjustment" min="0" step="1" required>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="add" class="mb-3">
|
||||
<strong>{{'total' | i18n}}:</strong> {{seatAdjustment || 0}} × {{seatPrice | currency:'$'}} = {{adjustedSeatTotal
|
||||
| currency:'$'}} /{{interval | i18n}}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
|
||||
{{'cancel' | i18n}}
|
||||
</button>
|
||||
<small class="d-block text-muted mt-3">
|
||||
{{(add ? 'seatsAddNote' : 'seatsRemoveNote') | i18n}}
|
||||
</small>
|
||||
</div>
|
||||
</form>
|
||||
<app-payment [showMethods]="false"></app-payment>
|
|
@ -1,87 +0,0 @@
|
|||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
|
||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
|
||||
import { SeatRequest } from 'jslib-common/models/request/seatRequest';
|
||||
|
||||
import { PaymentComponent } from '../../settings/payment.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-adjust-seats',
|
||||
templateUrl: 'adjust-seats.component.html',
|
||||
})
|
||||
export class AdjustSeatsComponent {
|
||||
@Input() seatPrice = 0;
|
||||
@Input() add = true;
|
||||
@Input() organizationId: string;
|
||||
@Input() interval = 'year';
|
||||
@Output() onAdjusted = new EventEmitter<number>();
|
||||
@Output() onCanceled = new EventEmitter();
|
||||
|
||||
@ViewChild(PaymentComponent, { static: true }) paymentComponent: PaymentComponent;
|
||||
|
||||
seatAdjustment = 0;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private router: Router,
|
||||
private activatedRoute: ActivatedRoute) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
const request = new SeatRequest();
|
||||
request.seatAdjustment = this.seatAdjustment;
|
||||
if (!this.add) {
|
||||
request.seatAdjustment *= -1;
|
||||
}
|
||||
|
||||
let paymentFailed = false;
|
||||
const action = async () => {
|
||||
const result = await this.apiService.postOrganizationSeat(this.organizationId, request);
|
||||
if (result != null && result.paymentIntentClientSecret != null) {
|
||||
try {
|
||||
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
||||
} catch {
|
||||
paymentFailed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
this.formPromise = action();
|
||||
await this.formPromise;
|
||||
this.onAdjusted.emit(this.seatAdjustment);
|
||||
if (paymentFailed) {
|
||||
this.toasterService.popAsync({
|
||||
body: this.i18nService.t('couldNotChargeCardPayInvoice'),
|
||||
type: 'warning',
|
||||
timeout: 10000,
|
||||
});
|
||||
this.router.navigate(['../billing'], { relativeTo: this.activatedRoute });
|
||||
} else {
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedSeats', request.seatAdjustment.toString()));
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.onCanceled.emit();
|
||||
}
|
||||
|
||||
get adjustedSeatTotal(): number {
|
||||
return this.seatAdjustment * this.seatPrice;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<label for="newSeatCount">{{'subscriptionSeats' | i18n}}</label>
|
||||
<input id="newSeatCount" class="form-control" type="number" name="NewSeatCount"
|
||||
[(ngModel)]="newSeatCount" min="0" step="1" required>
|
||||
<small class="d-block text-muted mb-4">
|
||||
<strong>{{'total' | i18n}}:</strong> {{newSeatCount || 0}} × {{seatPrice | currency:'$'}} =
|
||||
{{adjustedSeatTotal | currency:'$'}} / {{interval | i18n}}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="form-group col-sm">
|
||||
<div class="form-check">
|
||||
<input id="limitSubscription" class="form-check-input" type="checkbox" name="LimitSubscription"
|
||||
[(ngModel)]="limitSubscription" (change)="limitSubscriptionChanged()">
|
||||
<label for="limitSubscription">{{'limitSubscription' | i18n}}</label>
|
||||
</div>
|
||||
<small class="d-block text-muted">{{'limitSubscriptionDesc' | i18n}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4" [hidden]="!limitSubscription">
|
||||
<div class="form-group col-sm">
|
||||
<label for="maxAutoscaleSeats">{{'maxSeatLimit' | i18n}}</label>
|
||||
<input id="maxAutoscaleSeats" class="form-control col-6" type="number" name="MaxAutoscaleSeats"
|
||||
[(ngModel)]="newMaxSeats" [min]="newSeatCount == null ? 1 : newSeatCount" step="1"
|
||||
[required]="limitSubscription">
|
||||
<small class="d-block text-muted">
|
||||
<strong>{{'maxSeatCost' | i18n}}:</strong> {{newMaxSeats || 0}} ×
|
||||
{{seatPrice | currency:'$'}} = {{maxSeatTotal | currency:'$'}} / {{interval | i18n}}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<app-payment [showMethods]="false"></app-payment>
|
|
@ -0,0 +1,68 @@
|
|||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
|
||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { OrganizationSubscriptionUpdateRequest } from 'jslib-common/models/request/organizationSubscriptionUpdateRequest';
|
||||
|
||||
@Component({
|
||||
selector: 'app-adjust-subscription',
|
||||
templateUrl: 'adjust-subscription.component.html',
|
||||
})
|
||||
export class AdjustSubscription {
|
||||
@Input() organizationId: string;
|
||||
@Input() maxAutoscaleSeats: number;
|
||||
@Input() currentSeatCount: number;
|
||||
@Input() seatPrice = 0;
|
||||
@Input() interval = 'year';
|
||||
@Output() onAdjusted = new EventEmitter();
|
||||
|
||||
formPromise: Promise<any>;
|
||||
limitSubscription: boolean;
|
||||
newSeatCount: number;
|
||||
newMaxSeats: number;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private toasterService: ToasterService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.limitSubscription = this.maxAutoscaleSeats != null;
|
||||
this.newSeatCount = this.currentSeatCount;
|
||||
this.newMaxSeats = this.maxAutoscaleSeats;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
const seatAdjustment = this.newSeatCount - this.currentSeatCount;
|
||||
const request = new OrganizationSubscriptionUpdateRequest(seatAdjustment, this.newMaxSeats);
|
||||
this.formPromise = this.apiService.postOrganizationUpdateSubscription(this.organizationId, request);
|
||||
|
||||
await this.formPromise;
|
||||
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('subscriptionUpdated'));
|
||||
} catch { }
|
||||
this.onAdjusted.emit();
|
||||
}
|
||||
|
||||
limitSubscriptionChanged() {
|
||||
if (!this.limitSubscription) {
|
||||
this.newMaxSeats = null;
|
||||
}
|
||||
}
|
||||
|
||||
get adjustedSeatTotal(): number {
|
||||
return this.newSeatCount * this.seatPrice;
|
||||
}
|
||||
|
||||
get maxSeatTotal(): number {
|
||||
return this.newMaxSeats * this.seatPrice;
|
||||
}
|
||||
}
|
|
@ -23,110 +23,60 @@
|
|||
<span>{{'reinstateSubscription' | i18n}}</span>
|
||||
</button>
|
||||
</app-callout>
|
||||
<dl *ngIf="selfHosted">
|
||||
<dt>{{'billingPlan' | i18n}}</dt>
|
||||
<dd>{{sub.plan.name}}</dd>
|
||||
<dt>{{'expiration' | i18n}}</dt>
|
||||
<dd *ngIf="sub.expiration">
|
||||
{{sub.expiration | date:'mediumDate'}}
|
||||
<span *ngIf="isExpired" class="text-danger ml-2">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{'licenseIsExpired' | i18n}}
|
||||
</span>
|
||||
</dd>
|
||||
<dd *ngIf="!sub.expiration">{{'neverExpires' | i18n}}</dd>
|
||||
</dl>
|
||||
<div class="row" *ngIf="!selfHosted">
|
||||
<div class="col-4">
|
||||
<dl>
|
||||
<dt>{{'billingPlan' | i18n}}</dt>
|
||||
<dd>{{sub.plan.name}}</dd>
|
||||
<ng-container *ngIf="subscription">
|
||||
<dt>{{'status' | i18n}}</dt>
|
||||
<dd>
|
||||
<span class="text-capitalize">{{subscription.status || '-'}}</span>
|
||||
<span class="badge badge-warning"
|
||||
*ngIf="subscriptionMarkedForCancel">{{'pendingCancellation' | i18n}}</span>
|
||||
</dd>
|
||||
<dt>{{'nextCharge' | i18n}}</dt>
|
||||
<dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount | currency:'$'))
|
||||
: '-'}}
|
||||
</dd>
|
||||
</ng-container>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-8" *ngIf="subscription">
|
||||
<strong class="d-block mb-1">{{'details' | i18n}}</strong>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr *ngFor="let i of subscription.items">
|
||||
<td>
|
||||
{{i.name}} {{i.quantity > 1 ? '×' + i.quantity : ''}} @ {{i.amount | currency:'$'}}
|
||||
</td>
|
||||
<td>
|
||||
{{(i.quantity * i.amount) | currency:'$'}} /{{i.interval | i18n}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="selfHosted">
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
|
||||
{{'updateLicense' | i18n}}
|
||||
</button>
|
||||
<a href="https://vault.bitwarden.com" target="_blank" rel="noopener" class="btn btn-outline-secondary">
|
||||
{{'manageSubscription' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card mt-3" *ngIf="showUpdateLicense">
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}"
|
||||
(click)="closeUpdateLicense(false)"><span aria-hidden="true">×</span></button>
|
||||
<h3 class="card-body-header">{{'updateLicense' | i18n}}</h3>
|
||||
<app-update-license [organizationId]="organizationId" (onUpdated)="closeUpdateLicense(true)"
|
||||
(onCanceled)="closeUpdateLicense(false)"></app-update-license>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!selfHosted">
|
||||
<div class="d-flex">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="changePlan()" *ngIf="!showChangePlan">
|
||||
{{'changeBillingPlan' | i18n}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary ml-1" (click)="downloadLicense()"
|
||||
*ngIf="canDownloadLicense" [disabled]="showDownloadLicense">
|
||||
{{'downloadLicense' | i18n}}
|
||||
</button>
|
||||
<button #cancelBtn type="button" class="btn btn-outline-danger btn-submit ml-auto" (click)="cancel()"
|
||||
[appApiAction]="cancelPromise" [disabled]="cancelBtn.loading"
|
||||
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'cancelSubscription' | i18n}}</span>
|
||||
</button>
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<dl>
|
||||
<dt>{{'billingPlan' | i18n}}</dt>
|
||||
<dd>{{sub.plan.name}}</dd>
|
||||
<ng-container *ngIf="subscription">
|
||||
<dt>{{'status' | i18n}}</dt>
|
||||
<dd>
|
||||
<span class="text-capitalize">{{subscription.status || '-'}}</span>
|
||||
<span class="badge badge-warning"
|
||||
*ngIf="subscriptionMarkedForCancel">{{'pendingCancellation' |
|
||||
i18n}}</span>
|
||||
</dd>
|
||||
<dt>{{'nextCharge' | i18n}}</dt>
|
||||
<dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount |
|
||||
currency:'$'))
|
||||
: '-'}}
|
||||
</dd>
|
||||
</ng-container>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<strong class="d-block mb-1">{{'details' | i18n}}</strong>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr *ngFor="let i of subscription.items">
|
||||
<td>
|
||||
{{i.name}} {{i.quantity > 1 ? '×' + i.quantity : ''}} @ {{i.amount |
|
||||
currency:'$'}}
|
||||
</td>
|
||||
<td>
|
||||
{{(i.quantity * i.amount) | currency:'$'}} /{{i.interval | i18n}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ng-container *ngIf="userOrg?.providerId != null">
|
||||
<div class="col-sm">
|
||||
<dl>
|
||||
<dt>{{'provider' | i18n}}</dt>
|
||||
<dd>{{'yourProviderIs' | i18n : userOrg.providerName}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<app-change-plan [organizationId]="organizationId" (onChanged)="closeChangePlan(true)"
|
||||
(onCanceled)="closeChangePlan(false)" *ngIf="showChangePlan"></app-change-plan>
|
||||
<div class="mt-3" *ngIf="showDownloadLicense">
|
||||
<app-download-license [organizationId]="organizationId" (onDownloaded)="closeDownloadLicense()"
|
||||
(onCanceled)="closeDownloadLicense()"></app-download-license>
|
||||
</div>
|
||||
<h2 class="spaced-header">{{'userSeats' | i18n}}</h2>
|
||||
<p>{{'subscriptionUserSeats' | i18n : sub.seats}}</p>
|
||||
<h2 class="spaced-header">{{'manageSubscription' | i18n}}</h2>
|
||||
<p class="mb-4">{{subscriptionDesc}}</p>
|
||||
<ng-container *ngIf="subscription && canAdjustSeats && !subscription.cancelled && !subscriptionMarkedForCancel">
|
||||
<div class="mt-3">
|
||||
<div class="d-flex" *ngIf="!showAdjustSeats">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="adjustSeats(true)">
|
||||
{{'addSeats' | i18n}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary ml-1" (click)="adjustSeats(false)">
|
||||
{{'removeSeats' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
<app-adjust-seats [seatPrice]="seatPrice" [add]="adjustSeatsAdd" [organizationId]="organizationId"
|
||||
[interval]="billingInterval" (onAdjusted)="closeSeats(true)" (onCanceled)="closeSeats(false)"
|
||||
*ngIf="showAdjustSeats"></app-adjust-seats>
|
||||
<app-adjust-subscription [seatPrice]="seatPrice" [organizationId]="organizationId" [interval]="billingInterval"
|
||||
[currentSeatCount]="seats" [maxAutoscaleSeats]="maxAutoscaleSeats" (onAdjusted)="subscriptionAdjusted()">
|
||||
</app-adjust-subscription>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h2 class="spaced-header">{{'storage' | i18n}}</h2>
|
||||
|
@ -152,11 +102,59 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="userOrg?.providerId != null">
|
||||
<div class="secondary-header border-0 mb-0">
|
||||
<h1>{{'provider' | i18n}}</h1>
|
||||
<h2 class="spaced-header">{{'additionalOptions' | i18n}}</h2>
|
||||
<p class="mb-4">
|
||||
{{'additionalOptionsDesc' | i18n }}
|
||||
</p>
|
||||
<div class="d-flex">
|
||||
<button type="button" class="btn btn-outline-secondary ml-1" (click)="downloadLicense()" *ngIf="canDownloadLicense"
|
||||
[disabled]="showDownloadLicense">
|
||||
{{'downloadLicense' | i18n}}
|
||||
</button>
|
||||
<button #cancelBtn type="button" class="btn btn-outline-danger btn-submit ml-1" (click)="cancel()"
|
||||
[appApiAction]="cancelPromise" [disabled]="cancelBtn.loading"
|
||||
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'cancelSubscription' | i18n}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<app-change-plan [organizationId]="organizationId" (onChanged)="closeChangePlan(true)"
|
||||
(onCanceled)="closeChangePlan(false)" *ngIf="showChangePlan"></app-change-plan>
|
||||
<div class="mt-3" *ngIf="showDownloadLicense">
|
||||
<app-download-license [organizationId]="organizationId" (onDownloaded)="closeDownloadLicense()"
|
||||
(onCanceled)="closeDownloadLicense()"></app-download-license>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selfHosted">
|
||||
<dl>
|
||||
<dt>{{'billingPlan' | i18n}}</dt>
|
||||
<dd>{{sub.plan.name}}</dd>
|
||||
<dt>{{'expiration' | i18n}}</dt>
|
||||
<dd *ngIf="sub.expiration">
|
||||
{{sub.expiration | date:'mediumDate'}}
|
||||
<span *ngIf="isExpired" class="text-danger ml-2">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{'licenseIsExpired' | i18n}}
|
||||
</span>
|
||||
</dd>
|
||||
<dd *ngIf="!sub.expiration">{{'neverExpires' | i18n}}</dd>
|
||||
</dl>
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
|
||||
{{'updateLicense' | i18n}}
|
||||
</button>
|
||||
<a href="https://vault.bitwarden.com" target="_blank" rel="noopener" class="btn btn-outline-secondary">
|
||||
{{'manageSubscription' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card mt-3" *ngIf="showUpdateLicense">
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}"
|
||||
(click)="closeUpdateLicense(false)"><span aria-hidden="true">×</span></button>
|
||||
<h3 class="card-body-header">{{'updateLicense' | i18n}}</h3>
|
||||
<app-update-license [organizationId]="organizationId" (onUpdated)="closeUpdateLicense(true)"
|
||||
(onCanceled)="closeUpdateLicense(false)"></app-update-license>
|
||||
</div>
|
||||
{{'yourProviderIs' | i18n : userOrg.providerName}}
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
|
|
@ -26,6 +26,7 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
|||
organizationId: string;
|
||||
adjustSeatsAdd = true;
|
||||
showAdjustSeats = false;
|
||||
showAdjustSeatAutoscale = false;
|
||||
adjustStorageAdd = true;
|
||||
showAdjustStorage = false;
|
||||
showUpdateLicense = false;
|
||||
|
@ -142,16 +143,8 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
adjustSeats(add: boolean) {
|
||||
this.adjustSeatsAdd = add;
|
||||
this.showAdjustSeats = true;
|
||||
}
|
||||
|
||||
closeSeats(load: boolean) {
|
||||
this.showAdjustSeats = false;
|
||||
if (load) {
|
||||
this.load();
|
||||
}
|
||||
subscriptionAdjusted() {
|
||||
this.load();
|
||||
}
|
||||
|
||||
adjustStorage(add: boolean) {
|
||||
|
@ -205,6 +198,14 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
|||
return this.sub.plan.seatPrice;
|
||||
}
|
||||
|
||||
get seats() {
|
||||
return this.sub.seats;
|
||||
}
|
||||
|
||||
get maxAutoscaleSeats() {
|
||||
return this.sub.maxAutoscaleSeats;
|
||||
}
|
||||
|
||||
get canAdjustSeats() {
|
||||
return this.sub.plan.hasAdditionalSeatsOption;
|
||||
}
|
||||
|
@ -213,4 +214,14 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
|||
return (this.sub.planType !== PlanType.Free && this.subscription == null) ||
|
||||
(this.subscription != null && !this.subscription.cancelled);
|
||||
}
|
||||
|
||||
get subscriptionDesc() {
|
||||
if (this.sub.maxAutoscaleSeats == this.sub.seats && this.sub.seats != null) {
|
||||
return this.i18nService.t('subscriptionMaxReached', this.sub.seats.toString());
|
||||
} else if (this.sub.maxAutoscaleSeats == null) {
|
||||
return this.i18nService.t('subscriptionUserSeatsUnlimitedAutoscale');
|
||||
} else {
|
||||
return this.i18nService.t('subscriptionUserSeatsLimitedAutoscale', this.sub.maxAutoscaleSeats.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ import { UserConfirmComponent as OrgUserConfirmComponent } from './organizations
|
|||
import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component';
|
||||
|
||||
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
||||
import { AdjustSeatsComponent } from './organizations/settings/adjust-seats.component';
|
||||
import { AdjustSubscription } from './organizations/settings/adjust-subscription.component';
|
||||
import { ChangePlanComponent } from './organizations/settings/change-plan.component';
|
||||
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
|
||||
import { DownloadLicenseComponent } from './organizations/settings/download-license.component';
|
||||
|
@ -303,7 +303,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
|||
AddCreditComponent,
|
||||
AddEditComponent,
|
||||
AdjustPaymentComponent,
|
||||
AdjustSeatsComponent,
|
||||
AdjustSubscription,
|
||||
AdjustStorageComponent,
|
||||
ApiActionDirective,
|
||||
ApiKeyComponent,
|
||||
|
|
|
@ -2878,6 +2878,16 @@
|
|||
"enterInstallationId": {
|
||||
"message": "Enter your installation id"
|
||||
},
|
||||
"limitSubscriptionDesc": {
|
||||
"message": "Set a seat limit for your subscription. Once this limit is reached, you will not be able to invite new users."
|
||||
},
|
||||
"maxSeatLimit": {
|
||||
"message": "Maximum Seat Limit (optional)",
|
||||
"description": "Upper limit of seats to allow through autoscaling"
|
||||
},
|
||||
"maxSeatCost": {
|
||||
"message": "Max potential seat cost"
|
||||
},
|
||||
"addSeats": {
|
||||
"message": "Add Seats",
|
||||
"description": "Seat = User Seat"
|
||||
|
@ -2886,6 +2896,9 @@
|
|||
"message": "Remove Seats",
|
||||
"description": "Seat = User Seat"
|
||||
},
|
||||
"subscriptionDesc": {
|
||||
"message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited users exceed your subscription seats, you will immediately receive a prorated charge for the additional users."
|
||||
},
|
||||
"subscriptionUserSeats": {
|
||||
"message": "Your subscription allows for a total of $COUNT$ users.",
|
||||
"placeholders": {
|
||||
|
@ -2895,6 +2908,42 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"limitSubscription": {
|
||||
"message": "Limit Subscription (Optional)"
|
||||
},
|
||||
"subscriptionSeats": {
|
||||
"message": "Subscription Seats"
|
||||
},
|
||||
"subscriptionUpdated": {
|
||||
"message": "Subscription updated"
|
||||
},
|
||||
"additionalOptions": {
|
||||
"message": "Additional Options"
|
||||
},
|
||||
"additionalOptionsDesc": {
|
||||
"message": "For additional help in managing your subscription, please contact Customer Support."
|
||||
},
|
||||
"subscriptionUserSeatsUnlimitedAutoscale": {
|
||||
"message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited users exceed your subscription seats, you will immediately receive a prorated charge for the additional users."
|
||||
},
|
||||
"subscriptionUserSeatsLimitedAutoscale": {
|
||||
"message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited users exceed your subscription seats, you will immediately receive a prorated charge for the additional users until your $MAX$ seat limit is reached.",
|
||||
"placeholders": {
|
||||
"max": {
|
||||
"content": "$1",
|
||||
"example": "50"
|
||||
}
|
||||
}
|
||||
},
|
||||
"subscriptionMaxReached": {
|
||||
"message": "Adjustments to your subscription will result in prorated changes to your billing totals. You cannot invite more than $COUNT$ users without increasing your subscription seats.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "50"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seatsToAdd": {
|
||||
"message": "Seats To Add"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue