create and update premium license for self host
This commit is contained in:
parent
463c1f8b77
commit
3bb667f524
2
jslib
2
jslib
|
@ -1 +1 @@
|
|||
Subproject commit 20622db73c5c2a56777944bb06f32a21bf2e763f
|
||||
Subproject commit 8be95bfe574a7ae2c8173921bbdfe82451436081
|
|
@ -54,6 +54,7 @@ import { TwoFactorSetupComponent } from './settings/two-factor-setup.component';
|
|||
import { TwoFactorU2fComponent } from './settings/two-factor-u2f.component';
|
||||
import { TwoFactorVerifyComponent } from './settings/two-factor-verify.component';
|
||||
import { TwoFactorYubiKeyComponent } from './settings/two-factor-yubikey.component';
|
||||
import { UpdateLicenseComponent } from './settings/update-license.component';
|
||||
import { UserBillingComponent } from './settings/user-billing.component';
|
||||
|
||||
import { BreachReportComponent } from './tools/breach-report.component';
|
||||
|
@ -171,6 +172,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
|||
TwoFactorU2fComponent,
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorYubiKeyComponent,
|
||||
UpdateLicenseComponent,
|
||||
UserBillingComponent,
|
||||
UserLayoutComponent,
|
||||
VaultComponent,
|
||||
|
|
|
@ -114,8 +114,8 @@ export function initFactory(): Function {
|
|||
const isDev = platformUtilsService.isDev();
|
||||
await apiService.setUrls({
|
||||
base: isDev ? null : window.location.origin,
|
||||
api: isDev ? 'https://api.bitwarden.com' : null,
|
||||
identity: isDev ? 'https://identity.bitwarden.com' : null,
|
||||
api: isDev ? 'http://localhost:4000' : null,
|
||||
identity: isDev ? 'http://localhost:33656' : null,
|
||||
});
|
||||
|
||||
lockService.init(true);
|
||||
|
|
|
@ -22,9 +22,17 @@
|
|||
{{'premiumSignUpFuture' | i18n}}
|
||||
</li>
|
||||
</ul>
|
||||
<p class="text-lg mb-0">{{'premiumPrice' | i18n : (premiumPrice | currency:'$')}}</p>
|
||||
<p class="text-lg" [ngClass]="{'mb-0':!selfHosted}">{{'premiumPrice' | i18n : (premiumPrice | currency:'$')}}</p>
|
||||
<a href="https://vault.bitwarden.com/#/settings/billing" target="_blank" rel="noopener" class="btn btn-outline-secondary"
|
||||
*ngIf="selfHosted">
|
||||
{{'purchasePremium' | i18n}}
|
||||
</a>
|
||||
</app-callout>
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<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">{{'addons' | i18n}}</h2>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Angulartics2 } from 'angulartics2';
|
|||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { PaymentComponent } from './payment.component';
|
||||
|
||||
|
@ -21,6 +22,7 @@ export class PremiumComponent {
|
|||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
@Output() onPremiumPurchased = new EventEmitter();
|
||||
|
||||
selfHosted = false;
|
||||
premiumPrice = 10;
|
||||
storageGbPrice = 4;
|
||||
additionalStorage = 0;
|
||||
|
@ -28,7 +30,10 @@ export class PremiumComponent {
|
|||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
platformUtilsService: PlatformUtilsService) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
|
@ -44,6 +49,13 @@ export class PremiumComponent {
|
|||
} catch { }
|
||||
}
|
||||
|
||||
async finalizePremium() {
|
||||
await this.apiService.refreshIdentityToken();
|
||||
this.analytics.eventTrack.next({ action: 'Signed Up Premium' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('premiumUpdated'));
|
||||
this.onPremiumPurchased.emit();
|
||||
}
|
||||
|
||||
get additionalStorageTotal(): number {
|
||||
return this.storageGbPrice * this.additionalStorage;
|
||||
}
|
||||
|
@ -51,11 +63,4 @@ export class PremiumComponent {
|
|||
get total(): number {
|
||||
return this.additionalStorageTotal + this.premiumPrice;
|
||||
}
|
||||
|
||||
private async finalizePremium() {
|
||||
await this.apiService.refreshIdentityToken();
|
||||
this.analytics.eventTrack.next({ action: 'Signed Up Premium' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('premiumUpdated'));
|
||||
this.onPremiumPurchased.emit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<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 : (user ? 'bitwarden_premium_license.json' : 'bitwarden_organization_license.json')}}</small>
|
||||
</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()" *ngIf="!create">
|
||||
{{'cancel' | i18n}}
|
||||
</button>
|
||||
</form>
|
|
@ -0,0 +1,68 @@
|
|||
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 { TokenService } from 'jslib/abstractions/token.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-update-license',
|
||||
templateUrl: 'update-license.component.html',
|
||||
})
|
||||
export class UpdateLicenseComponent {
|
||||
@Input() create = true;
|
||||
@Input() user = true;
|
||||
@Output() onUpdated = new EventEmitter();
|
||||
@Output() onCanceled = new EventEmitter();
|
||||
|
||||
storageAdjustment = 0;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private tokenService: TokenService) { }
|
||||
|
||||
async submit() {
|
||||
const fileEl = document.getElementById('file') as HTMLInputElement;
|
||||
const files = fileEl.files;
|
||||
if (files == null || files.length === 0) {
|
||||
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('selectFile'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.user) {
|
||||
const fd = new FormData();
|
||||
fd.append('license', files[0]);
|
||||
if (this.create) {
|
||||
if (!this.tokenService.getEmailVerified()) {
|
||||
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('accountEmailMustBeVerified'));
|
||||
return;
|
||||
}
|
||||
this.formPromise = this.apiService.postPremium(fd);
|
||||
} else {
|
||||
this.formPromise = this.apiService.postAccountLicense(fd);
|
||||
}
|
||||
}
|
||||
await this.formPromise;
|
||||
if (!this.create) {
|
||||
this.analytics.eventTrack.next({ action: 'Updated License' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('updatedLicense'));
|
||||
}
|
||||
this.onUpdated.emit();
|
||||
} catch { }
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.onCanceled.emit();
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
<app-premium *ngIf="!premium" (onPremiumPurchased)="load()"></app-premium>
|
||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="premium && !firstLoaded && loading"></i>
|
||||
<ng-container *ngIf="premium && billing">
|
||||
<app-callout type="warning" title="{{'canceled' | i18n}}" *ngIf="subscription.cancelled">{{'subscriptionCanceled' | i18n}}</app-callout>
|
||||
<app-callout type="warning" title="{{'canceled' | i18n}}" *ngIf="subscription && subscription.cancelled">{{'subscriptionCanceled' | i18n}}</app-callout>
|
||||
<app-callout type="warning" title="{{'pendingCancellation' | i18n}}" *ngIf="subscriptionMarkedForCancel">
|
||||
<p>{{'subscriptionPendingCanceled' | i18n}}</p>
|
||||
<button #reinstateBtn type="button" class="btn btn-outline-secondary btn-submit" (click)="reinstate()" [appApiAction]="reinstatePromise"
|
||||
|
@ -33,7 +33,8 @@
|
|||
</dd>
|
||||
<dt>{{'nextCharge' | i18n}}</dt>
|
||||
<dd>{{nextInvoice ? ((nextInvoice.date | date: 'mediumDate') + ', ' + (nextInvoice.amount | currency:'$')) :
|
||||
'-'}}</dd>
|
||||
'-'}}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
|
@ -53,12 +54,20 @@
|
|||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="selfHosted">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
|
||||
{{'updateLicense' | i18n}}
|
||||
</button>
|
||||
<a href="https://vault.bitwarden.com/#/settings/billing" target="_blank" rel="noopener" class="btn btn-outline-secondary">
|
||||
{{'manageSubscription' | i18n}}
|
||||
</a>
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="updateLicense()">
|
||||
{{'updateLicense' | i18n}}
|
||||
</button>
|
||||
<a href="https://vault.bitwarden.com/#/settings/billing" target="_blank" rel="noopener" class="btn btn-outline-secondary">
|
||||
{{'manageSubscription' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card mt-3" *ngIf="showUpdateLicense">
|
||||
<div class="card-body">
|
||||
<h3 class="card-body-header">{{'updateLicense' | i18n}}</h3>
|
||||
<app-update-license [create]="false" [user]="true" (onUpdated)="closeUpdateLicense(true)" (onCanceled)="closeUpdateLicense(false)"></app-update-license>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!selfHosted">
|
||||
<ng-container *ngIf="!subscription.cancelled || subscriptionMarkedForCancel">
|
||||
|
|
|
@ -26,6 +26,7 @@ export class UserBillingComponent implements OnInit {
|
|||
adjustStorageAdd = true;
|
||||
showAdjustStorage = false;
|
||||
showAdjustPayment = false;
|
||||
showUpdateLicense = false;
|
||||
billing: BillingResponse;
|
||||
paymentMethodType = PaymentMethodType;
|
||||
selfHosted = false;
|
||||
|
@ -110,6 +111,14 @@ export class UserBillingComponent implements OnInit {
|
|||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.showUpdateLicense = true;
|
||||
}
|
||||
|
||||
closeUpdateLicense(load: boolean) {
|
||||
this.showUpdateLicense = false;
|
||||
if (load) {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
|
||||
adjustStorage(add: boolean) {
|
||||
|
|
|
@ -1331,6 +1331,9 @@
|
|||
"updateLicense": {
|
||||
"message": "Update License"
|
||||
},
|
||||
"updatedLicense": {
|
||||
"message": "Updated license"
|
||||
},
|
||||
"manageSubscription": {
|
||||
"message": "Manage Subscription"
|
||||
},
|
||||
|
@ -1413,5 +1416,26 @@
|
|||
},
|
||||
"updatedPaymentMethod": {
|
||||
"message": "Updated payment method."
|
||||
},
|
||||
"purchasePremium": {
|
||||
"message": "Purchase Premium"
|
||||
},
|
||||
"licenseFile": {
|
||||
"message": "License File"
|
||||
},
|
||||
"licenseFileDesc": {
|
||||
"message": "Your license file will be named something like $FILE_NAME$",
|
||||
"placeholders": {
|
||||
"file_name": {
|
||||
"content": "$1",
|
||||
"example": "bitwarden_premium_license.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"uploadLicenseFilePremium": {
|
||||
"message": "To upgrade your account to a premium membership you need to upload a valid license file."
|
||||
},
|
||||
"accountEmailMustBeVerified": {
|
||||
"message": "Your account's email address must be verified."
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue