mirror of
https://github.com/bitwarden/browser.git
synced 2024-10-02 04:48:57 +02:00
Merge branch 'main' into autofill/pm-6426-create-alarms-manager-and-update-usage-of-long-lived-timeouts-rework
This commit is contained in:
commit
332336ae2d
@ -1,6 +1,6 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, takeUntil, map, lastValueFrom } from "rxjs";
|
||||
import { concatMap, takeUntil, map } from "rxjs";
|
||||
import { tap } from "rxjs/operators";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
@ -16,7 +16,6 @@ import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { TwoFactorDuoComponent } from "../../../auth/settings/two-factor-duo.component";
|
||||
import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from "../../../auth/settings/two-factor-setup.component";
|
||||
import { TwoFactorVerifyComponent } from "../../../auth/settings/two-factor-verify.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-two-factor-setup",
|
||||
@ -66,17 +65,17 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
|
||||
async manage(type: TwoFactorProviderType) {
|
||||
switch (type) {
|
||||
case TwoFactorProviderType.OrganizationDuo: {
|
||||
const twoFactorVerifyDialogRef = TwoFactorVerifyComponent.open(this.dialogService, {
|
||||
data: { type: type, organizationId: this.organizationId },
|
||||
});
|
||||
const result: AuthResponse<TwoFactorDuoResponse> = await lastValueFrom(
|
||||
twoFactorVerifyDialogRef.closed,
|
||||
const result: AuthResponse<TwoFactorDuoResponse> = await this.callTwoFactorVerifyDialog(
|
||||
TwoFactorProviderType.OrganizationDuo,
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const duoComp = await this.openModal(this.duoModalRef, TwoFactorDuoComponent);
|
||||
duoComp.type = TwoFactorProviderType.OrganizationDuo;
|
||||
duoComp.organizationId = this.organizationId;
|
||||
duoComp.auth(result);
|
||||
duoComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
|
||||
this.updateStatus(enabled, TwoFactorProviderType.OrganizationDuo);
|
||||
|
@ -1,9 +1,5 @@
|
||||
<div *ngIf="selfHosted" class="page-header">
|
||||
<h1>{{ "subscription" | i18n }}</h1>
|
||||
</div>
|
||||
<div *ngIf="!selfHosted" class="tabbed-header">
|
||||
<h1>{{ "goPremium" | i18n }}</h1>
|
||||
</div>
|
||||
<bit-section>
|
||||
<h2 *ngIf="!selfHosted" bitTypography="h2">{{ "goPremium" | i18n }}</h2>
|
||||
<bit-callout
|
||||
type="info"
|
||||
*ngIf="canAccessPremium$ | async"
|
||||
@ -16,41 +12,45 @@
|
||||
<p>{{ "premiumUpgradeUnlockFeatures" | i18n }}</p>
|
||||
<ul class="bwi-ul">
|
||||
<li>
|
||||
<i class="bwi bwi-check text-success bwi-li" aria-hidden="true"></i>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpStorage" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check text-success bwi-li" aria-hidden="true"></i>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpTwoStepOptions" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check text-success bwi-li" aria-hidden="true"></i>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpEmergency" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check text-success bwi-li" aria-hidden="true"></i>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpReports" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check text-success bwi-li" aria-hidden="true"></i>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpTotp" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check text-success bwi-li" aria-hidden="true"></i>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpSupport" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
<i class="bwi bwi-check text-success bwi-li" aria-hidden="true"></i>
|
||||
<i class="bwi bwi-check tw-text-success bwi-li" aria-hidden="true"></i>
|
||||
{{ "premiumSignUpFuture" | i18n }}
|
||||
</li>
|
||||
</ul>
|
||||
<p class="text-lg" [ngClass]="{ 'mb-0': !selfHosted }">
|
||||
<p bitTypography="body1" [ngClass]="{ 'tw-mb-0': !selfHosted }">
|
||||
{{
|
||||
"premiumPriceWithFamilyPlan" | i18n: (premiumPrice | currency: "$") : familyPlanMaxUserCount
|
||||
}}
|
||||
<a routerLink="/create-organization" [queryParams]="{ plan: 'families' }">{{
|
||||
"bitwardenFamiliesPlan" | i18n
|
||||
}}</a>
|
||||
<a
|
||||
bitLink
|
||||
linkType="primary"
|
||||
routerLink="/create-organization"
|
||||
[queryParams]="{ plan: 'families' }"
|
||||
>{{ "bitwardenFamiliesPlan" | i18n }}</a
|
||||
>
|
||||
</p>
|
||||
<a
|
||||
bitButton
|
||||
@ -63,67 +63,81 @@
|
||||
{{ "purchasePremium" | i18n }}
|
||||
</a>
|
||||
</bit-callout>
|
||||
<ng-container *ngIf="selfHosted">
|
||||
<p>{{ "uploadLicenseFilePremium" | i18n }}</p>
|
||||
<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: "bitwarden_premium_license.json"
|
||||
}}</small>
|
||||
</bit-section>
|
||||
<bit-section *ngIf="selfHosted">
|
||||
<p bitTypography="body1">{{ "uploadLicenseFilePremium" | i18n }}</p>
|
||||
<form [formGroup]="licenseForm" [bitSubmit]="submit">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "licenseFile" | i18n }}</bit-label>
|
||||
<div>
|
||||
<button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()">
|
||||
{{ "chooseFile" | i18n }}
|
||||
</button>
|
||||
{{ this.licenseFile ? this.licenseFile.name : ("noFileChosen" | i18n) }}
|
||||
</div>
|
||||
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
||||
<input
|
||||
bitInput
|
||||
#fileSelector
|
||||
type="file"
|
||||
formControlName="file"
|
||||
(change)="setSelectedFile($event)"
|
||||
hidden
|
||||
/>
|
||||
<bit-hint>{{ "licenseFileDesc" | i18n: "bitwarden_premium_license.json" }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<button type="submit" buttonType="primary" bitButton bitFormButton>
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
</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">
|
||||
<label for="additionalStorage">{{ "additionalStorageGb" | i18n }}</label>
|
||||
</bit-section>
|
||||
<form [formGroup]="addonForm" [bitSubmit]="submit" *ngIf="!selfHosted">
|
||||
<bit-section>
|
||||
<h2 bitTypography="h2">{{ "addons" | i18n }}</h2>
|
||||
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
||||
<bit-form-field class="tw-col-span-6">
|
||||
<bit-label>{{ "additionalStorageGb" | i18n }}</bit-label>
|
||||
<input
|
||||
id="additionalStorage"
|
||||
class="form-control"
|
||||
bitInput
|
||||
formControlName="additionalStorage"
|
||||
type="number"
|
||||
name="AdditionalStorageGb"
|
||||
[(ngModel)]="additionalStorage"
|
||||
min="0"
|
||||
max="99"
|
||||
step="1"
|
||||
placeholder="{{ 'additionalStorageGbDesc' | i18n }}"
|
||||
/>
|
||||
<small class="text-muted form-text">{{
|
||||
<bit-hint>{{
|
||||
"additionalStorageIntervalDesc"
|
||||
| i18n: "1 GB" : (storageGbPrice | currency: "$") : ("year" | i18n)
|
||||
}}</small>
|
||||
}}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="spaced-header">{{ "summary" | i18n }}</h2>
|
||||
</bit-section>
|
||||
<bit-section>
|
||||
<h2 bitTypography="h2">{{ "summary" | i18n }}</h2>
|
||||
{{ "premiumMembership" | i18n }}: {{ premiumPrice | currency: "$" }} <br />
|
||||
{{ "additionalStorageGb" | i18n }}: {{ additionalStorage || 0 }} GB ×
|
||||
{{ storageGbPrice | currency: "$" }} =
|
||||
{{ additionalStorageTotal | currency: "$" }}
|
||||
<hr class="my-3" />
|
||||
<h2 class="spaced-header mb-4">{{ "paymentInformation" | i18n }}</h2>
|
||||
<hr class="tw-my-3" />
|
||||
</bit-section>
|
||||
<bit-section>
|
||||
<h3 bitTypography="h2">{{ "paymentInformation" | i18n }}</h3>
|
||||
<app-payment [hideBank]="true"></app-payment>
|
||||
<app-tax-info></app-tax-info>
|
||||
<div id="price" class="my-4">
|
||||
<div class="text-muted text-sm">
|
||||
<div id="price" class="tw-my-4">
|
||||
<div class="tw-text-muted tw-text-sm">
|
||||
{{ "planPrice" | i18n }}: {{ subtotal | currency: "USD $" }}
|
||||
<br />
|
||||
<ng-container>
|
||||
{{ "estimatedTax" | i18n }}: {{ taxCharges | currency: "USD $" }}
|
||||
</ng-container>
|
||||
</div>
|
||||
<hr class="my-1 col-3 ml-0" />
|
||||
<p class="text-lg">
|
||||
<hr class="tw-my-1 tw-w-1/4 tw-ml-0" />
|
||||
<p bitTypography="body1">
|
||||
<strong>{{ "total" | i18n }}:</strong> {{ total | currency: "USD $" }}/{{ "year" | i18n }}
|
||||
</p>
|
||||
</div>
|
||||
<small class="text-muted font-italic">{{ "paymentChargedAnnually" | i18n }}</small>
|
||||
<button type="submit" bitButton [loading]="form.loading">
|
||||
<p bitTypography="body2">{{ "paymentChargedAnnually" | i18n }}</p>
|
||||
<button type="submit" bitButton bitFormButton>
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
</bit-section>
|
||||
</form>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Component, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom, Observable } from "rxjs";
|
||||
|
||||
@ -7,7 +8,6 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
@ -26,11 +26,16 @@ export class PremiumComponent implements OnInit {
|
||||
premiumPrice = 10;
|
||||
familyPlanMaxUserCount = 6;
|
||||
storageGbPrice = 4;
|
||||
additionalStorage = 0;
|
||||
cloudWebVaultUrl: string;
|
||||
licenseFile: File = null;
|
||||
|
||||
formPromise: Promise<any>;
|
||||
|
||||
protected licenseForm = new FormGroup({
|
||||
file: new FormControl(null, [Validators.required]),
|
||||
});
|
||||
protected addonForm = new FormGroup({
|
||||
additionalStorage: new FormControl(0, [Validators.max(99), Validators.min(0)]),
|
||||
});
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
@ -39,14 +44,17 @@ export class PremiumComponent implements OnInit {
|
||||
private router: Router,
|
||||
private messagingService: MessagingService,
|
||||
private syncService: SyncService,
|
||||
private logService: LogService,
|
||||
private environmentService: EnvironmentService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
|
||||
}
|
||||
|
||||
protected setSelectedFile(event: Event) {
|
||||
const fileInputEl = <HTMLInputElement>event.target;
|
||||
const file: File = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null;
|
||||
this.licenseFile = file;
|
||||
}
|
||||
async ngOnInit() {
|
||||
this.cloudWebVaultUrl = await firstValueFrom(this.environmentService.cloudWebVaultUrl$);
|
||||
if (await firstValueFrom(this.billingAccountProfileStateService.hasPremiumPersonally$)) {
|
||||
@ -56,13 +64,11 @@ export class PremiumComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
let files: FileList = null;
|
||||
submit = async () => {
|
||||
this.licenseForm.markAllAsTouched();
|
||||
this.addonForm.markAllAsTouched();
|
||||
if (this.selfHosted) {
|
||||
const fileEl = document.getElementById("file") as HTMLInputElement;
|
||||
files = fileEl.files;
|
||||
if (files == null || files.length === 0) {
|
||||
if (this.licenseFile == null) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
@ -72,7 +78,6 @@ export class PremiumComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.selfHosted) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
if (!this.tokenService.getEmailVerified()) {
|
||||
@ -85,12 +90,12 @@ export class PremiumComponent implements OnInit {
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append("license", files[0]);
|
||||
this.formPromise = this.apiService.postAccountLicense(fd).then(() => {
|
||||
fd.append("license", this.licenseFile);
|
||||
await this.apiService.postAccountLicense(fd).then(() => {
|
||||
return this.finalizePremium();
|
||||
});
|
||||
} else {
|
||||
this.formPromise = this.paymentComponent
|
||||
await this.paymentComponent
|
||||
.createPaymentToken()
|
||||
.then((result) => {
|
||||
const fd = new FormData();
|
||||
@ -114,11 +119,7 @@ export class PremiumComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
}
|
||||
await this.formPromise;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async finalizePremium() {
|
||||
await this.apiService.refreshIdentityToken();
|
||||
@ -127,6 +128,9 @@ export class PremiumComponent implements OnInit {
|
||||
await this.router.navigate(["/settings/subscription/user-subscription"]);
|
||||
}
|
||||
|
||||
get additionalStorage(): number {
|
||||
return this.addonForm.get("additionalStorage").value;
|
||||
}
|
||||
get additionalStorageTotal(): number {
|
||||
return this.storageGbPrice * Math.abs(this.additionalStorage || 0);
|
||||
}
|
||||
|
@ -1,65 +1,57 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="form-group col-8">
|
||||
<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">
|
||||
<form [formGroup]="adjustSubscriptionForm" [bitSubmit]="submit">
|
||||
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
||||
<div class="tw-col-span-8">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "subscriptionSeats" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="newSeatCount" type="number" min="0" step="1" />
|
||||
<bit-hint>
|
||||
<strong>{{ "total" | i18n }}:</strong> {{ additionalSeatCount || 0 }} ×
|
||||
{{ seatPrice | currency: "$" }} = {{ adjustedSeatTotal | currency: "$" }} /
|
||||
{{ interval | i18n }}
|
||||
</small>
|
||||
{{ interval | i18n }}</bit-hint
|
||||
>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="form-group col-sm">
|
||||
<div class="form-check">
|
||||
<div>
|
||||
<bit-form-control>
|
||||
<input
|
||||
id="limitSubscription"
|
||||
class="form-check-input"
|
||||
bitCheckbox
|
||||
formControlName="limitSubscription"
|
||||
type="checkbox"
|
||||
name="LimitSubscription"
|
||||
[(ngModel)]="limitSubscription"
|
||||
(change)="limitSubscriptionChanged()"
|
||||
/>
|
||||
<label for="limitSubscription">{{ "limitSubscription" | i18n }}</label>
|
||||
<bit-label>{{ "limitSubscription" | i18n }}</bit-label>
|
||||
<bit-hint> {{ "limitSubscriptionDesc" | i18n }}</bit-hint>
|
||||
</bit-form-control>
|
||||
</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>
|
||||
<div
|
||||
class="tw-grid tw-grid-cols-12 tw-gap-4 tw-mb-4"
|
||||
[hidden]="!adjustSubscriptionForm.value.limitSubscription"
|
||||
>
|
||||
<div class="tw-col-span-8">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "maxSeatLimit" | i18n }}</bit-label>
|
||||
<input
|
||||
id="maxAutoscaleSeats"
|
||||
class="form-control col-8"
|
||||
bitInput
|
||||
formControlName="newMaxSeats"
|
||||
type="number"
|
||||
name="MaxAutoscaleSeats"
|
||||
[(ngModel)]="newMaxSeats"
|
||||
[min]="newSeatCount == null ? 1 : newSeatCount"
|
||||
[min]="
|
||||
adjustSubscriptionForm.value.newSeatCount == null
|
||||
? 1
|
||||
: adjustSubscriptionForm.value.newSeatCount
|
||||
"
|
||||
step="1"
|
||||
[required]="limitSubscription"
|
||||
/>
|
||||
<small class="d-block text-muted">
|
||||
<bit-hint>
|
||||
<strong>{{ "maxSeatCost" | i18n }}:</strong> {{ additionalMaxSeatCount || 0 }} ×
|
||||
{{ seatPrice | currency: "$" }} = {{ maxSeatTotal | currency: "$" }} /
|
||||
{{ interval | i18n }}
|
||||
</small>
|
||||
{{ interval | i18n }}</bit-hint
|
||||
>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "save" | i18n }}</span>
|
||||
<button bitButton buttonType="primary" bitFormButton type="submit">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<app-payment [showMethods]="false"></app-payment>
|
||||
|
@ -1,77 +1,102 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-subscription-update.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-adjust-subscription",
|
||||
templateUrl: "adjust-subscription.component.html",
|
||||
})
|
||||
export class AdjustSubscription {
|
||||
export class AdjustSubscription implements OnInit, OnDestroy {
|
||||
@Input() organizationId: string;
|
||||
@Input() maxAutoscaleSeats: number;
|
||||
@Input() currentSeatCount: number;
|
||||
@Input() seatPrice = 0;
|
||||
@Input() interval = "year";
|
||||
@Output() onAdjusted = new EventEmitter();
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
formPromise: Promise<void>;
|
||||
limitSubscription: boolean;
|
||||
newSeatCount: number;
|
||||
newMaxSeats: number;
|
||||
|
||||
adjustSubscriptionForm = this.formBuilder.group({
|
||||
newSeatCount: [0, [Validators.min(0)]],
|
||||
limitSubscription: [false],
|
||||
newMaxSeats: [0, [Validators.min(0)]],
|
||||
});
|
||||
get limitSubscription(): boolean {
|
||||
return this.adjustSubscriptionForm.value.limitSubscription;
|
||||
}
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private formBuilder: FormBuilder,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.limitSubscription = this.maxAutoscaleSeats != null;
|
||||
this.newSeatCount = this.currentSeatCount;
|
||||
this.newMaxSeats = this.maxAutoscaleSeats;
|
||||
this.adjustSubscriptionForm.patchValue({
|
||||
newSeatCount: this.currentSeatCount,
|
||||
limitSubscription: this.maxAutoscaleSeats != null,
|
||||
newMaxSeats: this.maxAutoscaleSeats,
|
||||
});
|
||||
this.adjustSubscriptionForm
|
||||
.get("limitSubscription")
|
||||
.valueChanges.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((value: boolean) => {
|
||||
if (value) {
|
||||
this.adjustSubscriptionForm
|
||||
.get("newMaxSeats")
|
||||
.addValidators([
|
||||
Validators.min(
|
||||
this.adjustSubscriptionForm.value.newSeatCount == null
|
||||
? 1
|
||||
: this.adjustSubscriptionForm.value.newSeatCount,
|
||||
),
|
||||
Validators.required,
|
||||
]);
|
||||
}
|
||||
this.adjustSubscriptionForm.get("newMaxSeats").updateValueAndValidity();
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
submit = async () => {
|
||||
this.adjustSubscriptionForm.markAllAsTouched();
|
||||
if (this.adjustSubscriptionForm.invalid) {
|
||||
return;
|
||||
}
|
||||
const request = new OrganizationSubscriptionUpdateRequest(
|
||||
this.additionalSeatCount,
|
||||
this.newMaxSeats,
|
||||
);
|
||||
this.formPromise = this.organizationApiService.updatePasswordManagerSeats(
|
||||
this.organizationId,
|
||||
request,
|
||||
this.adjustSubscriptionForm.value.newMaxSeats,
|
||||
);
|
||||
await this.organizationApiService.updatePasswordManagerSeats(this.organizationId, request);
|
||||
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated"));
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("subscriptionUpdated"),
|
||||
);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
this.onAdjusted.emit();
|
||||
}
|
||||
};
|
||||
|
||||
limitSubscriptionChanged() {
|
||||
if (!this.limitSubscription) {
|
||||
this.newMaxSeats = null;
|
||||
if (!this.adjustSubscriptionForm.value.limitSubscription) {
|
||||
this.adjustSubscriptionForm.value.newMaxSeats = null;
|
||||
}
|
||||
}
|
||||
|
||||
get additionalSeatCount(): number {
|
||||
return this.newSeatCount ? this.newSeatCount - this.currentSeatCount : 0;
|
||||
return this.adjustSubscriptionForm.value.newSeatCount
|
||||
? this.adjustSubscriptionForm.value.newSeatCount - this.currentSeatCount
|
||||
: 0;
|
||||
}
|
||||
|
||||
get additionalMaxSeatCount(): number {
|
||||
return this.newMaxSeats ? this.newMaxSeats - this.currentSeatCount : 0;
|
||||
return this.adjustSubscriptionForm.value.newMaxSeats
|
||||
? this.adjustSubscriptionForm.value.newMaxSeats - this.currentSeatCount
|
||||
: 0;
|
||||
}
|
||||
|
||||
get adjustedSeatTotal(): number {
|
||||
|
Loading…
Reference in New Issue
Block a user