premium and paid org callouts
This commit is contained in:
parent
1f6dd079cd
commit
1cee1c6e8f
|
@ -101,7 +101,24 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||||
break;
|
break;
|
||||||
case 'syncCompleted':
|
case 'syncCompleted':
|
||||||
break;
|
break;
|
||||||
|
case 'upgradeOrganization':
|
||||||
|
const upgradeConfirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t('upgradeOrganizationDesc'), this.i18nService.t('upgradeOrganization'),
|
||||||
|
this.i18nService.t('upgradeOrganization'), this.i18nService.t('cancel'));
|
||||||
|
if (upgradeConfirmed) {
|
||||||
|
this.router.navigate(['organizations', message.organizationId, 'settings', 'billing']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'premiumRequired':
|
||||||
|
const premiumConfirmed = await this.platformUtilsService.showDialog(
|
||||||
|
this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'),
|
||||||
|
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
|
||||||
|
if (premiumConfirmed) {
|
||||||
|
this.router.navigate(['settings/premium']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { AuditService } from 'jslib/abstractions/audit.service';
|
||||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
import { StateService } from 'jslib/abstractions/state.service';
|
import { StateService } from 'jslib/abstractions/state.service';
|
||||||
|
@ -36,9 +37,11 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit {
|
||||||
analytics: Angulartics2, toasterService: ToasterService,
|
analytics: Angulartics2, toasterService: ToasterService,
|
||||||
auditService: AuditService, stateService: StateService,
|
auditService: AuditService, stateService: StateService,
|
||||||
tokenService: TokenService, totpService: TotpService,
|
tokenService: TokenService, totpService: TotpService,
|
||||||
passwordGenerationService: PasswordGenerationService, private apiService: ApiService) {
|
passwordGenerationService: PasswordGenerationService, private apiService: ApiService,
|
||||||
|
messagingService: MessagingService) {
|
||||||
super(cipherService, folderService, i18nService, platformUtilsService, analytics,
|
super(cipherService, folderService, i18nService, platformUtilsService, analytics,
|
||||||
toasterService, auditService, stateService, tokenService, totpService, passwordGenerationService);
|
toasterService, auditService, stateService, tokenService, totpService, passwordGenerationService,
|
||||||
|
messagingService);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ export class VaultComponent implements OnInit {
|
||||||
constructor(private route: ActivatedRoute, private userService: UserService,
|
constructor(private route: ActivatedRoute, private userService: UserService,
|
||||||
private location: Location, private router: Router,
|
private location: Location, private router: Router,
|
||||||
private syncService: SyncService, private i18nService: I18nService,
|
private syncService: SyncService, private i18nService: I18nService,
|
||||||
private componentFactoryResolver: ComponentFactoryResolver) { }
|
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.parent.params.subscribe(async (params) => {
|
this.route.parent.params.subscribe(async (params) => {
|
||||||
|
@ -139,6 +140,11 @@ export class VaultComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
editCipherAttachments(cipher: CipherView) {
|
editCipherAttachments(cipher: CipherView) {
|
||||||
|
if (this.organization.maxStorageGb == null || this.organization.maxStorageGb === 0) {
|
||||||
|
this.messagingService.send('upgradeOrganization', { organizationId: cipher.organizationId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.modal != null) {
|
if (this.modal != null) {
|
||||||
this.modal.close();
|
this.modal.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<h3 class="mb-0">
|
<h3 class="mb-0">
|
||||||
{{p.name}}
|
{{p.name}}
|
||||||
<i class="fa fa-check text-success fa-fw" *ngIf="p.enabled" title="{{'enabled' | i18n}}"></i>
|
<i class="fa fa-check text-success fa-fw" *ngIf="p.enabled" title="{{'enabled' | i18n}}"></i>
|
||||||
<a href="#" appStopClick class="badge badge-primary" *ngIf="!premium && p.premium">
|
<a href="#" appStopClick class="badge badge-primary" *ngIf="!premium && p.premium" (click)="premiumRequired()">
|
||||||
{{'premium' | i18n}}
|
{{'premium' | i18n}}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
import { TokenService } from 'jslib/abstractions/token.service';
|
import { TokenService } from 'jslib/abstractions/token.service';
|
||||||
|
|
||||||
import { TwoFactorProviders } from 'jslib/services/auth.service';
|
import { TwoFactorProviders } from 'jslib/services/auth.service';
|
||||||
|
@ -42,7 +43,7 @@ export class TwoFactorSetupComponent implements OnInit {
|
||||||
private modal: ModalComponent = null;
|
private modal: ModalComponent = null;
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private tokenService: TokenService,
|
constructor(private apiService: ApiService, private tokenService: TokenService,
|
||||||
private componentFactoryResolver: ComponentFactoryResolver) { }
|
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.premium = this.tokenService.getPremium();
|
this.premium = this.tokenService.getPremium();
|
||||||
|
@ -125,6 +126,14 @@ export class TwoFactorSetupComponent implements OnInit {
|
||||||
this.openModal(this.recoveryModalRef, TwoFactorRecoveryComponent);
|
this.openModal(this.recoveryModalRef, TwoFactorRecoveryComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async premiumRequired() {
|
||||||
|
const premium = await this.tokenService.getPremium();
|
||||||
|
if (!premium) {
|
||||||
|
this.messagingService.send('premiumRequired');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private openModal<T>(ref: ViewContainerRef, type: Type<T>): T {
|
private openModal<T>(ref: ViewContainerRef, type: Type<T>): T {
|
||||||
if (this.modal != null) {
|
if (this.modal != null) {
|
||||||
this.modal.close();
|
this.modal.close();
|
||||||
|
|
|
@ -50,8 +50,7 @@
|
||||||
<a href="#" class="d-block mr-2" appStopClick title="{{'generatePassword' | i18n}}" (click)="generatePassword()">
|
<a href="#" class="d-block mr-2" appStopClick title="{{'generatePassword' | i18n}}" (click)="generatePassword()">
|
||||||
<i class="fa fa-lg fa-fw fa-refresh"></i>
|
<i class="fa fa-lg fa-fw fa-refresh"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="d-block" #checkPasswordBtn appStopClick title="{{'checkPassword' | i18n}}" (click)="checkPassword()"
|
<a href="#" class="d-block" #checkPasswordBtn appStopClick title="{{'checkPassword' | i18n}}" (click)="checkPassword()" [appApiAction]="checkPasswordPromise">
|
||||||
[appApiAction]="checkPasswordPromise">
|
|
||||||
<i class="fa fa-lg fa-fw fa-check-circle" [hidden]="checkPasswordBtn.loading"></i>
|
<i class="fa fa-lg fa-fw fa-check-circle" [hidden]="checkPasswordBtn.loading"></i>
|
||||||
<i class="fa fa-lg fa-fw fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading" title="{{'loading' | i18n}}"></i>
|
<i class="fa fa-lg fa-fw fa-spinner fa-spin" [hidden]="!checkPasswordBtn.loading" title="{{'loading' | i18n}}"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -81,6 +80,12 @@
|
||||||
<div class="col-6 form-group totp d-flex align-items-end" [ngClass]="{'low': totpLow}">
|
<div class="col-6 form-group totp d-flex align-items-end" [ngClass]="{'low': totpLow}">
|
||||||
<div *ngIf="!cipher.login.totp || !totpCode">
|
<div *ngIf="!cipher.login.totp || !totpCode">
|
||||||
<img src="../../images/totp-countdown.png" title="{{'verificationCodeTotp' | i18n}}" class="ml-2">
|
<img src="../../images/totp-countdown.png" title="{{'verificationCodeTotp' | i18n}}" class="ml-2">
|
||||||
|
<a href="#" appStopClick class="badge badge-primary ml-3" (click)="premiumRequired()" *ngIf="!cipher.organizationId && !isPremium">
|
||||||
|
{{'premium' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a href="#" appStopClick class="badge badge-primary ml-3" (click)="upgradeOrganization()" *ngIf="cipher.organizationId && !organizationUseTotp">
|
||||||
|
{{'upgrade' | i18n}}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="cipher.login.totp && totpCode" class="d-flex align-items-center">
|
<div *ngIf="cipher.login.totp && totpCode" class="d-flex align-items-center">
|
||||||
<span class="totp-countdown mr-3 ml-2">
|
<span class="totp-countdown mr-3 ml-2">
|
||||||
|
@ -364,8 +369,8 @@
|
||||||
<button *ngIf="!organization" type="button" (click)="toggleFavorite()" class="btn btn-link" title="{{(cipher.favorite ? 'unfavorite' : 'favorite') | i18n}}">
|
<button *ngIf="!organization" type="button" (click)="toggleFavorite()" class="btn btn-link" title="{{(cipher.favorite ? 'unfavorite' : 'favorite') | i18n}}">
|
||||||
<i class="fa fa-lg" [ngClass]="{'fa-star': cipher.favorite, 'fa-star-o': !cipher.favorite}"></i>
|
<i class="fa fa-lg" [ngClass]="{'fa-star': cipher.favorite, 'fa-star-o': !cipher.favorite}"></i>
|
||||||
</button>
|
</button>
|
||||||
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger" title="{{'delete' | i18n}}"
|
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger" title="{{'delete' | i18n}}" *ngIf="editMode"
|
||||||
*ngIf="editMode" [disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
|
[disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
|
||||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" title="{{'loading' | i18n}}"></i>
|
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" title="{{'loading' | i18n}}"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { AuditService } from 'jslib/abstractions/audit.service';
|
||||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
import { StateService } from 'jslib/abstractions/state.service';
|
import { StateService } from 'jslib/abstractions/state.service';
|
||||||
|
@ -40,7 +41,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit {
|
||||||
analytics: Angulartics2, toasterService: ToasterService,
|
analytics: Angulartics2, toasterService: ToasterService,
|
||||||
auditService: AuditService, stateService: StateService,
|
auditService: AuditService, stateService: StateService,
|
||||||
protected tokenService: TokenService, protected totpService: TotpService,
|
protected tokenService: TokenService, protected totpService: TotpService,
|
||||||
protected passwordGenerationService: PasswordGenerationService) {
|
protected passwordGenerationService: PasswordGenerationService, protected messagingService: MessagingService) {
|
||||||
super(cipherService, folderService, i18nService, platformUtilsService, analytics,
|
super(cipherService, folderService, i18nService, platformUtilsService, analytics,
|
||||||
toasterService, auditService, stateService);
|
toasterService, auditService, stateService);
|
||||||
}
|
}
|
||||||
|
@ -94,6 +95,18 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit {
|
||||||
return confirmed;
|
return confirmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async premiumRequired() {
|
||||||
|
const premium = await this.tokenService.getPremium();
|
||||||
|
if (!premium) {
|
||||||
|
this.messagingService.send('premiumRequired');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async upgradeOrganization() {
|
||||||
|
this.messagingService.send('upgradeOrganization', { organizationId: this.cipher.organizationId });
|
||||||
|
}
|
||||||
|
|
||||||
protected cleanUp() {
|
protected cleanUp() {
|
||||||
if (this.totpInterval) {
|
if (this.totpInterval) {
|
||||||
window.clearInterval(this.totpInterval);
|
window.clearInterval(this.totpInterval);
|
||||||
|
|
|
@ -49,6 +49,17 @@
|
||||||
</app-vault-ciphers>
|
</app-vault-ciphers>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
|
<div class="card border-warning mb-4" *ngIf="showUpdateKey">
|
||||||
|
<div class="card-header bg-warning text-white">
|
||||||
|
<i class="fa fa-warning fa-fw"></i> {{'updateKeyTitle' | i18n}}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>{{'updateEncryptionKeyShortDesc' | i18n}}</p>
|
||||||
|
<button class="btn btn-block btn-outline-secondary" type="button" (click)="updateKey()">
|
||||||
|
{{'updateEncryptionKey' | i18n}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<app-verify-email *ngIf="showVerifyEmail" class="d-block mb-4"></app-verify-email>
|
<app-verify-email *ngIf="showVerifyEmail" class="d-block mb-4"></app-verify-email>
|
||||||
<div class="card border-warning mb-4" *ngIf="showBrowserOutdated">
|
<div class="card border-warning mb-4" *ngIf="showBrowserOutdated">
|
||||||
<div class="card-header bg-warning text-white">
|
<div class="card-header bg-warning text-white">
|
||||||
|
@ -61,17 +72,6 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card border-warning mb-4" *ngIf="showUpdateKey">
|
|
||||||
<div class="card-header bg-warning text-white">
|
|
||||||
<i class="fa fa-warning fa-fw"></i> {{'updateKeyTitle' | i18n}}
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p>{{'updateEncryptionKeyShortDesc' | i18n}}</p>
|
|
||||||
<button class="btn btn-block btn-outline-secondary" type="button" (click)="updateKey()">
|
|
||||||
{{'updateEncryptionKey' | i18n}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
{{'organizations' | i18n}}
|
{{'organizations' | i18n}}
|
||||||
|
|
|
@ -32,8 +32,10 @@ import { ShareComponent } from './share.component';
|
||||||
|
|
||||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||||
import { TokenService } from 'jslib/abstractions/token.service';
|
import { TokenService } from 'jslib/abstractions/token.service';
|
||||||
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-vault',
|
selector: 'app-vault',
|
||||||
|
@ -66,7 +68,8 @@ export class VaultComponent implements OnInit {
|
||||||
constructor(private syncService: SyncService, private route: ActivatedRoute,
|
constructor(private syncService: SyncService, private route: ActivatedRoute,
|
||||||
private router: Router, private location: Location,
|
private router: Router, private location: Location,
|
||||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
private tokenService: TokenService, private cryptoService: CryptoService) { }
|
private tokenService: TokenService, private cryptoService: CryptoService,
|
||||||
|
private messagingService: MessagingService, private userService: UserService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.showVerifyEmail = !(await this.tokenService.getEmailVerified());
|
this.showVerifyEmail = !(await this.tokenService.getEmailVerified());
|
||||||
|
@ -157,7 +160,19 @@ export class VaultComponent implements OnInit {
|
||||||
this.ciphersComponent.searchText = searchText;
|
this.ciphersComponent.searchText = searchText;
|
||||||
}
|
}
|
||||||
|
|
||||||
editCipherAttachments(cipher: CipherView) {
|
async editCipherAttachments(cipher: CipherView) {
|
||||||
|
const premium = await this.tokenService.getPremium();
|
||||||
|
if (cipher.organizationId == null && !premium) {
|
||||||
|
this.messagingService.send('premiumRequired');
|
||||||
|
return;
|
||||||
|
} else if (cipher.organizationId != null) {
|
||||||
|
const org = await this.userService.getOrganization(cipher.organizationId);
|
||||||
|
if (org != null && (org.maxStorageGb == null || org.maxStorageGb === 0)) {
|
||||||
|
this.messagingService.send('upgradeOrganization', { organizationId: cipher.organizationId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.modal != null) {
|
if (this.modal != null) {
|
||||||
this.modal.close();
|
this.modal.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1026,6 +1026,12 @@
|
||||||
"premiumMembership": {
|
"premiumMembership": {
|
||||||
"message": "Premium Membership"
|
"message": "Premium Membership"
|
||||||
},
|
},
|
||||||
|
"premiumRequired": {
|
||||||
|
"message": "Premium Required"
|
||||||
|
},
|
||||||
|
"premiumRequiredDesc": {
|
||||||
|
"message": "A premium membership is required to use this feature."
|
||||||
|
},
|
||||||
"manage": {
|
"manage": {
|
||||||
"message": "Manage"
|
"message": "Manage"
|
||||||
},
|
},
|
||||||
|
@ -2301,5 +2307,14 @@
|
||||||
},
|
},
|
||||||
"loading": {
|
"loading": {
|
||||||
"message": "Loading"
|
"message": "Loading"
|
||||||
|
},
|
||||||
|
"upgrade": {
|
||||||
|
"message": "Upgrade"
|
||||||
|
},
|
||||||
|
"upgradeOrganization": {
|
||||||
|
"message": "Upgrade Organization"
|
||||||
|
},
|
||||||
|
"upgradeOrganizationDesc": {
|
||||||
|
"message": "This feature is not available for free organizations. Switch to a paid plan to unlock more features."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue