mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-02 23:11:40 +01:00
org billing seat adjustments
This commit is contained in:
parent
e4f12ed47f
commit
e4a684ff10
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit 4228277d23503d563560b44a652293d23233aa1b
|
||||
Subproject commit 1cb3447bdd3531d08eb77a8b7a0ad65124428a09
|
@ -52,6 +52,7 @@ import { UserAddEditComponent as OrgUserAddEditComponent } 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 { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
|
||||
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component';
|
||||
import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component';
|
||||
@ -149,6 +150,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
|
||||
AccountComponent,
|
||||
AddEditComponent,
|
||||
AdjustPaymentComponent,
|
||||
AdjustSeatsComponent,
|
||||
AdjustStorageComponent,
|
||||
ApiActionDirective,
|
||||
AppComponent,
|
||||
|
26
src/app/organizations/settings/adjust-seats.component.html
Normal file
26
src/app/organizations/settings/adjust-seats.component.html
Normal file
@ -0,0 +1,26 @@
|
||||
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="card-body">
|
||||
<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"></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>
|
58
src/app/organizations/settings/adjust-seats.component.ts
Normal file
58
src/app/organizations/settings/adjust-seats.component.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
import { SeatRequest } from 'jslib/models/request/seatRequest';
|
||||
|
||||
@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();
|
||||
|
||||
seatAdjustment = 0;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
const request = new SeatRequest();
|
||||
request.seatAdjustment = this.seatAdjustment;
|
||||
if (!this.add) {
|
||||
request.seatAdjustment *= -1;
|
||||
}
|
||||
|
||||
this.formPromise = this.apiService.postOrganizationSeat(this.organizationId, request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.add ? 'Added Seats' : 'Removed Seats' });
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedSeats', request.seatAdjustment.toString()));
|
||||
this.onAdjusted.emit(this.seatAdjustment);
|
||||
} catch { }
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.onCanceled.emit();
|
||||
}
|
||||
|
||||
get adjustedSeatTotal(): number {
|
||||
return this.seatAdjustment * this.seatAdjustment;
|
||||
}
|
||||
}
|
@ -90,6 +90,22 @@
|
||||
<span>{{'cancelSubscription' | i18n}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h2 class="spaced-header">{{'userSeats' | i18n}}</h2>
|
||||
<p>{{'subscriptionUserSeats' | i18n : billing.seats}}</p>
|
||||
<ng-container *ngIf="subscription && canAdjustSeats">
|
||||
<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>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h2 class="spaced-header">{{'storage' | i18n}}</h2>
|
||||
<p>{{'subscriptionStorage' | i18n : billing.maxStorageGb || 0 : billing.storageName || '0 MB'}}</p>
|
||||
<div class="progress">
|
||||
|
@ -30,6 +30,8 @@ export class OrganizationBillingComponent implements OnInit {
|
||||
loading = false;
|
||||
firstLoaded = false;
|
||||
organizationId: string;
|
||||
adjustSeatsAdd = true;
|
||||
showAdjustSeats = false;
|
||||
adjustStorageAdd = true;
|
||||
showAdjustStorage = false;
|
||||
showAdjustPayment = false;
|
||||
@ -146,6 +148,18 @@ export class OrganizationBillingComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
adjustSeats(add: boolean) {
|
||||
this.adjustSeatsAdd = add;
|
||||
this.showAdjustSeats = true;
|
||||
}
|
||||
|
||||
closeSeats(load: boolean) {
|
||||
this.showAdjustSeats = false;
|
||||
if (load) {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
|
||||
adjustStorage(add: boolean) {
|
||||
this.adjustStorageAdd = add;
|
||||
this.showAdjustStorage = true;
|
||||
@ -216,6 +230,23 @@ export class OrganizationBillingComponent implements OnInit {
|
||||
}
|
||||
|
||||
get seatPrice() {
|
||||
switch (this.billing.planType) {
|
||||
case PlanType.EnterpriseMonthly:
|
||||
return 4;
|
||||
case PlanType.EnterpriseAnnually:
|
||||
return 3;
|
||||
case PlanType.TeamsMonthly:
|
||||
return 2.5;
|
||||
case PlanType.TeamsAnnually:
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
get canAdjustSeats() {
|
||||
return this.billing.planType === PlanType.EnterpriseMonthly ||
|
||||
this.billing.planType === PlanType.EnterpriseAnnually ||
|
||||
this.billing.planType === PlanType.TeamsMonthly || this.billing.planType === PlanType.TeamsAnnually;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div class="form-group col-6">
|
||||
<label for="storageAdjustment">{{(add ? 'gbStorageAdd' : 'gbStorageRemove') | i18n}}</label>
|
||||
<input id="storageAdjustment" class="form-control" type="number" name="StroageGbAdjustment" [(ngModel)]="storageAdjustment"
|
||||
min="0" max="99" step="1">
|
||||
min="0" max="99" step="1" required>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="add" class="mb-3">
|
||||
|
@ -1445,10 +1445,10 @@
|
||||
"message": "GB of Storage To Remove"
|
||||
},
|
||||
"storageAddNote": {
|
||||
"message": "Adding storage to your plan will result in adjustments to your billing totals and immediately charge your payment method on file. The first charge will be prorated for the remainder of the current billing cycle."
|
||||
"message": "Adding storage will result in adjustments to your billing totals and immediately charge your payment method on file. The first charge will be prorated for the remainder of the current billing cycle."
|
||||
},
|
||||
"storageRemoveNote": {
|
||||
"message": "Removing storage will result in adjustments to your billing totals that will be prorated as credits to your next billing charge."
|
||||
"message": "Removing storage will result in adjustments to your billing totals that will be prorated as credits toward your next billing charge."
|
||||
},
|
||||
"adjustedStorage": {
|
||||
"message": "Adjusted $AMOUNT$ GB of storage.",
|
||||
@ -2222,5 +2222,43 @@
|
||||
},
|
||||
"enterInstallationId": {
|
||||
"message": "Enter your installation id"
|
||||
},
|
||||
"addSeats": {
|
||||
"message": "Add Seats",
|
||||
"description": "Seat = User Seat"
|
||||
},
|
||||
"removeSeats": {
|
||||
"message": "Remove Seats",
|
||||
"description": "Seat = User Seat"
|
||||
},
|
||||
"subscriptionUserSeats": {
|
||||
"message": "Your subscription allows for a total of $COUNT$ users.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "50"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seatsToAdd": {
|
||||
"message": "Seats To Add"
|
||||
},
|
||||
"seatsToRemove": {
|
||||
"message": "Seats To Remove"
|
||||
},
|
||||
"seatsAddNote": {
|
||||
"message": "Adding user seats will result in adjustments to your billing totals and immediately charge your payment method on file. The first charge will be prorated for the remainder of the current billing cycle."
|
||||
},
|
||||
"seatsRemoveNote": {
|
||||
"message": "Removing user seats will result in adjustments to your billing totals that will be prorated as credits toward your next billing charge."
|
||||
},
|
||||
"adjustedSeats": {
|
||||
"message": "Adjusted $AMOUNT$ user seats.",
|
||||
"placeholders": {
|
||||
"amount": {
|
||||
"content": "$1",
|
||||
"example": "15"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user