org billing seat adjustments

This commit is contained in:
Kyle Spearrin 2018-07-17 12:07:52 -04:00
parent e4f12ed47f
commit e4a684ff10
8 changed files with 176 additions and 5 deletions

2
jslib

@ -1 +1 @@
Subproject commit 4228277d23503d563560b44a652293d23233aa1b
Subproject commit 1cb3447bdd3531d08eb77a8b7a0ad65124428a09

View File

@ -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,

View 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}} &times; {{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>

View 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;
}
}

View File

@ -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">

View File

@ -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() {
return 4;
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;
}
}

View File

@ -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">

View File

@ -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"
}
}
}
}