mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-07 19:07:45 +01:00
Merge branch 'main' of github.com:bitwarden/clients
This commit is contained in:
commit
cf802fc32c
@ -4,25 +4,6 @@
|
|||||||
[title]="'autofillSuggestions' | i18n"
|
[title]="'autofillSuggestions' | i18n"
|
||||||
[showRefresh]="showRefresh"
|
[showRefresh]="showRefresh"
|
||||||
(onRefresh)="refreshCurrentTab()"
|
(onRefresh)="refreshCurrentTab()"
|
||||||
|
[description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : null"
|
||||||
showAutofillButton
|
showAutofillButton
|
||||||
></app-vault-list-items-container>
|
></app-vault-list-items-container>
|
||||||
<ng-container *ngIf="showEmptyAutofillTip$ | async">
|
|
||||||
<bit-section>
|
|
||||||
<bit-section-header>
|
|
||||||
<h2 bitTypography="h6">
|
|
||||||
{{ "autofillSuggestions" | i18n }}
|
|
||||||
</h2>
|
|
||||||
<button
|
|
||||||
*ngIf="showRefresh"
|
|
||||||
bitIconButton="bwi-refresh"
|
|
||||||
size="small"
|
|
||||||
type="button"
|
|
||||||
[appA11yTitle]="'refresh' | i18n"
|
|
||||||
(click)="refreshCurrentTab()"
|
|
||||||
></button>
|
|
||||||
</bit-section-header>
|
|
||||||
<span class="tw-text-muted tw-px-1" bitTypography="body2">{{
|
|
||||||
"autofillSuggestionsTip" | i18n
|
|
||||||
}}</span>
|
|
||||||
</bit-section>
|
|
||||||
</ng-container>
|
|
||||||
|
@ -3,6 +3,7 @@ import { Component } from "@angular/core";
|
|||||||
import { combineLatest, map, Observable } from "rxjs";
|
import { combineLatest, map, Observable } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import {
|
import {
|
||||||
IconButtonModule,
|
IconButtonModule,
|
||||||
SectionComponent,
|
SectionComponent,
|
||||||
@ -45,7 +46,7 @@ export class AutofillVaultListItemsComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable that determines whether the empty autofill tip should be shown.
|
* Observable that determines whether the empty autofill tip should be shown.
|
||||||
* The tip is shown when there are no ciphers to autofill, no filter is applied, and autofill is allowed in
|
* The tip is shown when there are no login ciphers to autofill, no filter is applied, and autofill is allowed in
|
||||||
* the current context (e.g. not in a popout).
|
* the current context (e.g. not in a popout).
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
@ -54,7 +55,10 @@ export class AutofillVaultListItemsComponent {
|
|||||||
this.autofillCiphers$,
|
this.autofillCiphers$,
|
||||||
this.vaultPopupItemsService.autofillAllowed$,
|
this.vaultPopupItemsService.autofillAllowed$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([hasFilter, ciphers, canAutoFill]) => !hasFilter && canAutoFill && ciphers.length === 0),
|
map(
|
||||||
|
([hasFilter, ciphers, canAutoFill]) =>
|
||||||
|
!hasFilter && canAutoFill && ciphers.filter((c) => c.type == CipherType.Login).length === 0,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
constructor(private vaultPopupItemsService: VaultPopupItemsService) {
|
constructor(private vaultPopupItemsService: VaultPopupItemsService) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<bit-section *ngIf="ciphers?.length > 0">
|
<bit-section *ngIf="ciphers?.length > 0 || description">
|
||||||
<bit-section-header>
|
<bit-section-header>
|
||||||
<h2 bitTypography="h6">
|
<h2 bitTypography="h6">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
@ -13,6 +13,9 @@
|
|||||||
></button>
|
></button>
|
||||||
<span bitTypography="body2" slot="end">{{ ciphers.length }}</span>
|
<span bitTypography="body2" slot="end">{{ ciphers.length }}</span>
|
||||||
</bit-section-header>
|
</bit-section-header>
|
||||||
|
<div *ngIf="description" class="tw-text-muted tw-px-1 tw-mb-2" bitTypography="body2">
|
||||||
|
{{ description }}
|
||||||
|
</div>
|
||||||
<bit-item-group>
|
<bit-item-group>
|
||||||
<bit-item *ngFor="let cipher of ciphers">
|
<bit-item *ngFor="let cipher of ciphers">
|
||||||
<a
|
<a
|
||||||
|
@ -50,6 +50,13 @@ export class VaultListItemsContainerComponent {
|
|||||||
@Input()
|
@Input()
|
||||||
title: string;
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional description for the vault list item section. Will be shown below the title even when
|
||||||
|
* no ciphers are available.
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
description: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Option to show a refresh button in the section header.
|
* Option to show a refresh button in the section header.
|
||||||
*/
|
*/
|
||||||
|
@ -1,106 +1,77 @@
|
|||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
|
|
||||||
<bit-container>
|
<bit-container>
|
||||||
<p>{{ "preferencesDesc" | i18n }}</p>
|
<p bitTypography="body1">{{ "preferencesDesc" | i18n }}</p>
|
||||||
<form [formGroup]="form" (ngSubmit)="submit()" ngNativeValidate>
|
<form [formGroup]="form" [bitSubmit]="submit" class="tw-w-1/2">
|
||||||
<div class="row">
|
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
|
||||||
<div class="col-6">
|
<span *ngIf="policy.timeout && policy.action">
|
||||||
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
|
{{
|
||||||
<span *ngIf="policy.timeout && policy.action">
|
"vaultTimeoutPolicyWithActionInEffect"
|
||||||
{{
|
| i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
|
||||||
"vaultTimeoutPolicyWithActionInEffect"
|
}}
|
||||||
| i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
|
</span>
|
||||||
}}
|
<span *ngIf="policy.timeout && !policy.action">
|
||||||
</span>
|
{{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }}
|
||||||
<span *ngIf="policy.timeout && !policy.action">
|
</span>
|
||||||
{{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }}
|
<span *ngIf="!policy.timeout && policy.action">
|
||||||
</span>
|
{{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
|
||||||
<span *ngIf="!policy.timeout && policy.action">
|
</span>
|
||||||
{{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
|
</app-callout>
|
||||||
</span>
|
<app-vault-timeout-input
|
||||||
</app-callout>
|
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||||
<app-vault-timeout-input
|
[formControl]="form.controls.vaultTimeout"
|
||||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
ngDefaultControl
|
||||||
[formControl]="form.controls.vaultTimeout"
|
>
|
||||||
ngDefaultControl
|
</app-vault-timeout-input>
|
||||||
>
|
|
||||||
</app-vault-timeout-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ng-container *ngIf="availableVaultTimeoutActions$ | async as availableVaultTimeoutActions">
|
<ng-container *ngIf="availableVaultTimeoutActions$ | async as availableVaultTimeoutActions">
|
||||||
<div *ngIf="availableVaultTimeoutActions.length > 1" class="form-group">
|
<bit-radio-group
|
||||||
<label>{{ "vaultTimeoutAction" | i18n }}</label>
|
formControlName="vaultTimeoutAction"
|
||||||
<div
|
*ngIf="availableVaultTimeoutActions.length > 1"
|
||||||
|
>
|
||||||
|
<bit-label>{{ "vaultTimeoutAction" | i18n }}</bit-label>
|
||||||
|
<bit-radio-button
|
||||||
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
|
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
|
||||||
class="form-check form-check-block"
|
id="vaultTimeoutActionLock"
|
||||||
|
[value]="VaultTimeoutAction.Lock"
|
||||||
>
|
>
|
||||||
<input
|
<bit-label>{{ "lock" | i18n }}</bit-label>
|
||||||
class="form-check-input"
|
<bit-hint>{{ "vaultTimeoutActionLockDesc" | i18n }}</bit-hint>
|
||||||
type="radio"
|
</bit-radio-button>
|
||||||
name="vaultTimeoutAction"
|
<bit-radio-button
|
||||||
id="vaultTimeoutActionLock"
|
|
||||||
value="{{ VaultTimeoutAction.Lock }}"
|
|
||||||
formControlName="vaultTimeoutAction"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="vaultTimeoutActionLock">
|
|
||||||
{{ "lock" | i18n }}
|
|
||||||
<small>{{ "vaultTimeoutActionLockDesc" | i18n }}</small>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.LogOut)"
|
*ngIf="availableVaultTimeoutActions.includes(VaultTimeoutAction.LogOut)"
|
||||||
class="form-check mt-2 form-check-block"
|
id="vaultTimeoutActionLogOut"
|
||||||
|
[value]="VaultTimeoutAction.LogOut"
|
||||||
>
|
>
|
||||||
<input
|
<bit-label>{{ "logOut" | i18n }}</bit-label>
|
||||||
class="form-check-input"
|
<bit-hint>{{ "vaultTimeoutActionLogOutDesc" | i18n }}</bit-hint>
|
||||||
type="radio"
|
</bit-radio-button>
|
||||||
name="vaultTimeoutAction"
|
</bit-radio-group>
|
||||||
id="vaultTimeoutActionLogOut"
|
|
||||||
value="{{ VaultTimeoutAction.LogOut }}"
|
|
||||||
formControlName="vaultTimeoutAction"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="vaultTimeoutActionLogOut">
|
|
||||||
{{ "logOut" | i18n }}
|
|
||||||
<small>{{ "vaultTimeoutActionLogOutDesc" | i18n }}</small>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="row">
|
<bit-form-field>
|
||||||
<div class="col-6">
|
<bit-label
|
||||||
<div class="form-group">
|
>{{ "language" | i18n }}
|
||||||
<div class="d-flex">
|
|
||||||
<label for="locale">{{ "language" | i18n }}</label>
|
|
||||||
<a
|
|
||||||
class="ml-auto"
|
|
||||||
href="https://bitwarden.com/help/localization/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<select id="locale" name="Locale" formControlName="locale" class="form-control">
|
|
||||||
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
|
||||||
</select>
|
|
||||||
<small class="form-text text-muted">{{ "languageDesc" | i18n }}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input
|
|
||||||
class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="enableFavicons"
|
|
||||||
name="enableFavicons"
|
|
||||||
formControlName="enableFavicons"
|
|
||||||
/>
|
|
||||||
<label class="form-check-label" for="enableFavicons">
|
|
||||||
{{ "enableFavicon" | i18n }}
|
|
||||||
</label>
|
|
||||||
<a
|
<a
|
||||||
|
bitLink
|
||||||
|
class="tw-float-right"
|
||||||
|
href="https://bitwarden.com/help/localization/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</bit-label>
|
||||||
|
<bit-select formControlName="locale" id="locale">
|
||||||
|
<bit-option *ngFor="let o of localeOptions" [value]="o.value" [label]="o.name"></bit-option>
|
||||||
|
</bit-select>
|
||||||
|
<bit-hint>{{ "languageDesc" | i18n }}</bit-hint>
|
||||||
|
</bit-form-field>
|
||||||
|
<bit-form-control>
|
||||||
|
<input type="checkbox" bitCheckbox formControlName="enableFavicons" />
|
||||||
|
<bit-label
|
||||||
|
>{{ "enableFavicon" | i18n }}
|
||||||
|
<a
|
||||||
|
bitLink
|
||||||
href="https://bitwarden.com/help/website-icons/"
|
href="https://bitwarden.com/help/website-icons/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
@ -108,22 +79,16 @@
|
|||||||
>
|
>
|
||||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</bit-label>
|
||||||
<small class="form-text text-muted">{{ "faviconDesc" | i18n }}</small>
|
<bit-hint>{{ "faviconDesc" | i18n }}</bit-hint>
|
||||||
</div>
|
</bit-form-control>
|
||||||
<div class="row">
|
<bit-form-field>
|
||||||
<div class="col-6">
|
<bit-label>{{ "theme" | i18n }}</bit-label>
|
||||||
<div class="form-group">
|
<bit-select formControlName="theme" id="theme">
|
||||||
<label for="theme">{{ "theme" | i18n }}</label>
|
<bit-option *ngFor="let o of themeOptions" [value]="o.value" [label]="o.name"></bit-option>
|
||||||
<select id="theme" name="theme" formControlName="theme" class="form-control">
|
</bit-select>
|
||||||
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
<bit-hint>{{ "themeDesc" | i18n }}</bit-hint>
|
||||||
</select>
|
</bit-form-field>
|
||||||
<small class="form-text text-muted">{{ "themeDesc" | i18n }}</small>
|
<button bitButton bitFormButton type="submit" buttonType="primary">{{ "save" | i18n }}</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">
|
|
||||||
{{ "save" | i18n }}
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
</bit-container>
|
</bit-container>
|
||||||
|
@ -158,7 +158,7 @@ export class PreferencesComponent implements OnInit {
|
|||||||
this.form.setValue(initialFormValues, { emitEvent: false });
|
this.form.setValue(initialFormValues, { emitEvent: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
submit = async () => {
|
||||||
if (!this.form.controls.vaultTimeout.valid) {
|
if (!this.form.controls.vaultTimeout.valid) {
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"error",
|
"error",
|
||||||
@ -188,7 +188,7 @@ export class PreferencesComponent implements OnInit {
|
|||||||
this.i18nService.t("preferencesUpdated"),
|
this.i18nService.t("preferencesUpdated"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.destroy$.next();
|
this.destroy$.next();
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
ProviderSelectPaymentMethodDialogComponent,
|
ProviderSelectPaymentMethodDialogComponent,
|
||||||
ProviderSubscriptionComponent,
|
ProviderSubscriptionComponent,
|
||||||
} from "../../billing/providers";
|
} from "../../billing/providers";
|
||||||
|
import { SubscriptionStatusComponent } from "../../billing/providers/subscription/subscription-status.component";
|
||||||
|
|
||||||
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
||||||
import { ClientsComponent } from "./clients/clients.component";
|
import { ClientsComponent } from "./clients/clients.component";
|
||||||
@ -70,6 +71,7 @@ import { SetupComponent } from "./setup/setup.component";
|
|||||||
ProviderSubscriptionComponent,
|
ProviderSubscriptionComponent,
|
||||||
ProviderSelectPaymentMethodDialogComponent,
|
ProviderSelectPaymentMethodDialogComponent,
|
||||||
ProviderPaymentMethodComponent,
|
ProviderPaymentMethodComponent,
|
||||||
|
SubscriptionStatusComponent,
|
||||||
],
|
],
|
||||||
providers: [WebProviderService, ProviderPermissionsGuard],
|
providers: [WebProviderService, ProviderPermissionsGuard],
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||||
<bit-dialog dialogSize="large">
|
<bit-dialog dialogSize="large" [loading]="loading">
|
||||||
<span bitDialogTitle class="tw-font-semibold">
|
<span bitDialogTitle class="tw-font-semibold">
|
||||||
{{ "newClientOrganization" | i18n }}
|
{{ "newClientOrganization" | i18n }}
|
||||||
</span>
|
</span>
|
||||||
@ -49,11 +49,21 @@
|
|||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-grid tw-grid-flow-col tw-grid-cols-2 tw-gap-4">
|
<div class="tw-grid tw-grid-flow-col tw-grid-cols-2 tw-gap-4">
|
||||||
<bit-form-field>
|
<bit-form-field disableMargin>
|
||||||
<bit-label>
|
<bit-label>
|
||||||
{{ "seats" | i18n }}
|
{{ "seats" | i18n }}
|
||||||
</bit-label>
|
</bit-label>
|
||||||
<input type="text" bitInput formControlName="seats" />
|
<input type="text" bitInput formControlName="seats" />
|
||||||
|
<bit-hint
|
||||||
|
class="tw-text-muted tw-grid tw-grid-flow-col tw-gap-1 tw-grid-cols-1 tw-grid-rows-2"
|
||||||
|
*ngIf="unassignedSeatsForSelectedPlan > 0"
|
||||||
|
>
|
||||||
|
<span class="tw-col-span-1"
|
||||||
|
>{{ unassignedSeatsForSelectedPlan }}
|
||||||
|
{{ "unassignedSeatsDescription" | i18n | lowercase }}</span
|
||||||
|
>
|
||||||
|
<span class="tw-col-span-1">0 {{ "purchaseSeatDescription" | i18n | lowercase }}</span>
|
||||||
|
</bit-hint>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,11 +2,12 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
|||||||
import { Component, Inject, OnInit } from "@angular/core";
|
import { Component, Inject, OnInit } from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
|
|
||||||
|
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||||
import { PlanType } from "@bitwarden/common/billing/enums";
|
import { PlanType } from "@bitwarden/common/billing/enums";
|
||||||
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
||||||
|
import { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
import { DialogService } from "@bitwarden/components";
|
|
||||||
|
|
||||||
import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service";
|
import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service";
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ type PlanCard = {
|
|||||||
name: string;
|
name: string;
|
||||||
cost: number;
|
cost: number;
|
||||||
type: PlanType;
|
type: PlanType;
|
||||||
|
plan: PlanResponse;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,20 +43,24 @@ type PlanCard = {
|
|||||||
templateUrl: "./create-client-organization.component.html",
|
templateUrl: "./create-client-organization.component.html",
|
||||||
})
|
})
|
||||||
export class CreateClientOrganizationComponent implements OnInit {
|
export class CreateClientOrganizationComponent implements OnInit {
|
||||||
protected ResultType = CreateClientOrganizationResultType;
|
|
||||||
protected formGroup = this.formBuilder.group({
|
protected formGroup = this.formBuilder.group({
|
||||||
clientOwnerEmail: ["", [Validators.required, Validators.email]],
|
clientOwnerEmail: ["", [Validators.required, Validators.email]],
|
||||||
organizationName: ["", Validators.required],
|
organizationName: ["", Validators.required],
|
||||||
seats: [null, [Validators.required, Validators.min(1)]],
|
seats: [null, [Validators.required, Validators.min(1)]],
|
||||||
});
|
});
|
||||||
|
protected loading = true;
|
||||||
protected planCards: PlanCard[];
|
protected planCards: PlanCard[];
|
||||||
|
protected ResultType = CreateClientOrganizationResultType;
|
||||||
|
|
||||||
|
private providerPlans: ProviderPlanResponse[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private billingApiService: BillingApiServiceAbstraction,
|
||||||
@Inject(DIALOG_DATA) private dialogParams: CreateClientOrganizationParams,
|
@Inject(DIALOG_DATA) private dialogParams: CreateClientOrganizationParams,
|
||||||
private dialogRef: DialogRef<CreateClientOrganizationResultType>,
|
private dialogRef: DialogRef<CreateClientOrganizationResultType>,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private toastService: ToastService,
|
||||||
private webProviderService: WebProviderService,
|
private webProviderService: WebProviderService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -92,6 +98,11 @@ export class CreateClientOrganizationComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
|
const subscription = await this.billingApiService.getProviderSubscription(
|
||||||
|
this.dialogParams.providerId,
|
||||||
|
);
|
||||||
|
this.providerPlans = subscription?.plans ?? [];
|
||||||
|
|
||||||
const teamsPlan = this.dialogParams.plans.find((plan) => plan.type === PlanType.TeamsMonthly);
|
const teamsPlan = this.dialogParams.plans.find((plan) => plan.type === PlanType.TeamsMonthly);
|
||||||
const enterprisePlan = this.dialogParams.plans.find(
|
const enterprisePlan = this.dialogParams.plans.find(
|
||||||
(plan) => plan.type === PlanType.EnterpriseMonthly,
|
(plan) => plan.type === PlanType.EnterpriseMonthly,
|
||||||
@ -102,15 +113,19 @@ export class CreateClientOrganizationComponent implements OnInit {
|
|||||||
name: this.i18nService.t("planNameTeams"),
|
name: this.i18nService.t("planNameTeams"),
|
||||||
cost: teamsPlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs,
|
cost: teamsPlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs,
|
||||||
type: teamsPlan.type,
|
type: teamsPlan.type,
|
||||||
|
plan: teamsPlan,
|
||||||
selected: true,
|
selected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: this.i18nService.t("planNameEnterprise"),
|
name: this.i18nService.t("planNameEnterprise"),
|
||||||
cost: enterprisePlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs,
|
cost: enterprisePlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs,
|
||||||
type: enterprisePlan.type,
|
type: enterprisePlan.type,
|
||||||
|
plan: enterprisePlan,
|
||||||
selected: false,
|
selected: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected selectPlan(name: string) {
|
protected selectPlan(name: string) {
|
||||||
@ -135,8 +150,23 @@ export class CreateClientOrganizationComponent implements OnInit {
|
|||||||
this.formGroup.value.seats,
|
this.formGroup.value.seats,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("createdNewClient"));
|
this.toastService.showToast({
|
||||||
|
variant: "success",
|
||||||
|
title: null,
|
||||||
|
message: this.i18nService.t("createdNewClient"),
|
||||||
|
});
|
||||||
|
|
||||||
this.dialogRef.close(this.ResultType.Submitted);
|
this.dialogRef.close(this.ResultType.Submitted);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected get unassignedSeatsForSelectedPlan(): number {
|
||||||
|
if (this.loading || !this.planCards) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const selectedPlan = this.planCards.find((planCard) => planCard.selected).plan;
|
||||||
|
const selectedProviderPlan = this.providerPlans.find(
|
||||||
|
(providerPlan) => providerPlan.planName === selectedPlan.name,
|
||||||
|
);
|
||||||
|
return selectedProviderPlan.seatMinimum - selectedProviderPlan.assignedSeats;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,22 +7,20 @@
|
|||||||
<p>
|
<p>
|
||||||
{{ "manageSeatsDescription" | i18n }}
|
{{ "manageSeatsDescription" | i18n }}
|
||||||
</p>
|
</p>
|
||||||
<bit-form-field>
|
<bit-form-field disableMargin>
|
||||||
<bit-label>
|
<bit-label>
|
||||||
{{ "assignedSeats" | i18n }}
|
{{ "assignedSeats" | i18n }}
|
||||||
</bit-label>
|
</bit-label>
|
||||||
<input id="assignedSeats" type="number" bitInput required [(ngModel)]="assignedSeats" />
|
<input id="assignedSeats" type="number" bitInput required [(ngModel)]="assignedSeats" />
|
||||||
|
<bit-hint class="tw-text-muted" *ngIf="remainingOpenSeats > 0">
|
||||||
|
<div class="tw-grid tw-grid-flow-col tw-gap-1 tw-grid-cols-1 tw-grid-rows-2">
|
||||||
|
<span class="tw-col-span-1"
|
||||||
|
>{{ unassignedSeats }} {{ "unassignedSeatsDescription" | i18n | lowercase }}</span
|
||||||
|
>
|
||||||
|
<span class="tw-col-span-1">0 {{ "purchaseSeatDescription" | i18n | lowercase }}</span>
|
||||||
|
</div>
|
||||||
|
</bit-hint>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
<ng-container *ngIf="remainingOpenSeats > 0">
|
|
||||||
<p>
|
|
||||||
<small class="tw-text-muted">{{ unassignedSeats }}</small>
|
|
||||||
<small class="tw-text-muted">{{ "unassignedSeatsDescription" | i18n }}</small>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<small class="tw-text-muted">{{ AdditionalSeatPurchased }}</small>
|
|
||||||
<small class="tw-text-muted">{{ "purchaseSeatDescription" | i18n }}</small>
|
|
||||||
</p>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
<ng-container bitDialogFooter>
|
<ng-container bitDialogFooter>
|
||||||
<button
|
<button
|
||||||
|
@ -4,7 +4,7 @@ import { Component, Inject, OnInit } from "@angular/core";
|
|||||||
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
||||||
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||||
import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request";
|
import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request";
|
||||||
import { Plans } from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
import { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
@ -83,7 +83,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit {
|
|||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
getPurchasedSeatsByPlan(planName: string, plans: Plans[]): number {
|
getPurchasedSeatsByPlan(planName: string, plans: ProviderPlanResponse[]): number {
|
||||||
const plan = plans.find((plan) => plan.planName === planName);
|
const plan = plans.find((plan) => plan.planName === planName);
|
||||||
if (plan) {
|
if (plan) {
|
||||||
return plan.purchasedSeats;
|
return plan.purchasedSeats;
|
||||||
@ -92,7 +92,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAssignedByPlan(planName: string, plans: Plans[]): number {
|
getAssignedByPlan(planName: string, plans: ProviderPlanResponse[]): number {
|
||||||
const plan = plans.find((plan) => plan.planName === planName);
|
const plan = plans.find((plan) => plan.planName === planName);
|
||||||
if (plan) {
|
if (plan) {
|
||||||
return plan.assignedSeats;
|
return plan.assignedSeats;
|
||||||
@ -101,7 +101,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviderSeatMinimumByPlan(planName: string, plans: Plans[]) {
|
getProviderSeatMinimumByPlan(planName: string, plans: ProviderPlanResponse[]) {
|
||||||
const plan = plans.find((plan) => plan.planName === planName);
|
const plan = plans.find((plan) => plan.planName === planName);
|
||||||
if (plan) {
|
if (plan) {
|
||||||
return plan.seatMinimum;
|
return plan.seatMinimum;
|
||||||
|
@ -158,6 +158,4 @@ export class ManageClientOrganizationsComponent extends BaseClientsComponent {
|
|||||||
|
|
||||||
await this.load();
|
await this.load();
|
||||||
};
|
};
|
||||||
protected readonly openManageClientOrganizationNameDialog =
|
|
||||||
openManageClientOrganizationNameDialog;
|
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,10 @@
|
|||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
|
|
||||||
<bit-container>
|
<bit-container>
|
||||||
<ng-container *ngIf="!firstLoaded && loading">
|
<ng-container *ngIf="!firstLoaded && loading">
|
||||||
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<app-subscription-status [providerSubscriptionResponse]="subscription"> </app-subscription-status>
|
||||||
<ng-container *ngIf="subscription && firstLoaded">
|
|
||||||
<bit-callout type="warning" title="{{ 'canceled' | i18n }}" *ngIf="false">
|
|
||||||
{{ "subscriptionCanceled" | i18n }}</bit-callout
|
|
||||||
>
|
|
||||||
|
|
||||||
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
|
|
||||||
<dt>{{ "billingPlan" | i18n }}</dt>
|
|
||||||
<dd>{{ "providerPlan" | i18n }}</dd>
|
|
||||||
<ng-container *ngIf="subscription">
|
|
||||||
<dt>{{ "status" | i18n }}</dt>
|
|
||||||
<dd>
|
|
||||||
<span class="tw-capitalize">{{ subscription.status }}</span>
|
|
||||||
</dd>
|
|
||||||
<dt [ngClass]="{ 'tw-text-danger': isExpired }">{{ "nextCharge" | i18n }}</dt>
|
|
||||||
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
|
|
||||||
{{ subscription.currentPeriodEndDate | date: "mediumDate" }}
|
|
||||||
</dd>
|
|
||||||
</ng-container>
|
|
||||||
</dl>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<div class="tw-flex-col">
|
<div class="tw-flex-col">
|
||||||
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 pb-2"
|
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 pb-2"
|
||||||
|
@ -4,7 +4,7 @@ import { Subject, concatMap, takeUntil } from "rxjs";
|
|||||||
|
|
||||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||||
import {
|
import {
|
||||||
Plans,
|
ProviderPlanResponse,
|
||||||
ProviderSubscriptionResponse,
|
ProviderSubscriptionResponse,
|
||||||
} from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
} from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ export class ProviderSubscriptionComponent {
|
|||||||
return totalSeats > 1 ? totalSeats.toString() : "";
|
return totalSeats > 1 ? totalSeats.toString() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
sumCost(plans: Plans[]): number {
|
sumCost(plans: ProviderPlanResponse[]): number {
|
||||||
return plans.reduce((acc, plan) => acc + plan.cost, 0);
|
return plans.reduce((acc, plan) => acc + plan.cost, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
<ng-container>
|
||||||
|
<bit-callout *ngIf="data.callout" [type]="data.callout.severity" [title]="data.callout.header">
|
||||||
|
<p>{{ data.callout.body }}</p>
|
||||||
|
<button
|
||||||
|
*ngIf="data.callout.showReinstatementButton"
|
||||||
|
bitButton
|
||||||
|
buttonType="secondary"
|
||||||
|
[bitAction]="requestReinstatement"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{{ "reinstateSubscription" | i18n }}
|
||||||
|
</button>
|
||||||
|
</bit-callout>
|
||||||
|
<dl class="tw-grid tw-grid-flow-col tw-grid-rows-2">
|
||||||
|
<dt>{{ "billingPlan" | i18n }}</dt>
|
||||||
|
<dd>{{ "providerPlan" | i18n }}</dd>
|
||||||
|
<ng-container *ngIf="data.status && data.date">
|
||||||
|
<dt>{{ data.status.label }}</dt>
|
||||||
|
<dd>
|
||||||
|
<span class="tw-capitalize">
|
||||||
|
{{ displayedStatus }}
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
|
<dt [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||||
|
{{ data.date.label | titlecase }}
|
||||||
|
</dt>
|
||||||
|
<dd [ngClass]="{ 'tw-text-danger': isExpired }">
|
||||||
|
{{ data.date.value | date: "mediumDate" }}
|
||||||
|
</dd>
|
||||||
|
</ng-container>
|
||||||
|
</dl>
|
||||||
|
</ng-container>
|
@ -0,0 +1,188 @@
|
|||||||
|
import { DatePipe } from "@angular/common";
|
||||||
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
|
|
||||||
|
import { ProviderSubscriptionResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
|
type ComponentData = {
|
||||||
|
status?: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
date?: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
callout?: {
|
||||||
|
severity: "danger" | "warning";
|
||||||
|
header: string;
|
||||||
|
body: string;
|
||||||
|
showReinstatementButton: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-subscription-status",
|
||||||
|
templateUrl: "subscription-status.component.html",
|
||||||
|
})
|
||||||
|
export class SubscriptionStatusComponent {
|
||||||
|
@Input({ required: true }) providerSubscriptionResponse: ProviderSubscriptionResponse;
|
||||||
|
@Output() reinstatementRequested = new EventEmitter<void>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private datePipe: DatePipe,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get displayedStatus(): string {
|
||||||
|
return this.data.status.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get planName() {
|
||||||
|
return this.providerSubscriptionResponse.plans[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
get status(): string {
|
||||||
|
return this.subscription.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isExpired() {
|
||||||
|
return this.subscription.status !== "active";
|
||||||
|
}
|
||||||
|
|
||||||
|
get subscription() {
|
||||||
|
return this.providerSubscriptionResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
get data(): ComponentData {
|
||||||
|
const defaultStatusLabel = this.i18nService.t("status");
|
||||||
|
|
||||||
|
const nextChargeDateLabel = this.i18nService.t("nextCharge");
|
||||||
|
const subscriptionExpiredDateLabel = this.i18nService.t("subscriptionExpired");
|
||||||
|
const cancellationDateLabel = this.i18nService.t("cancellationDate");
|
||||||
|
|
||||||
|
switch (this.status) {
|
||||||
|
case "free": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case "trialing": {
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: this.i18nService.t("trial"),
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: nextChargeDateLabel,
|
||||||
|
value: this.subscription.currentPeriodEndDate.toDateString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "active": {
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: this.i18nService.t("active"),
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: nextChargeDateLabel,
|
||||||
|
value: this.subscription.currentPeriodEndDate.toDateString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "past_due": {
|
||||||
|
const pastDueText = this.i18nService.t("pastDue");
|
||||||
|
const suspensionDate = this.datePipe.transform(
|
||||||
|
this.subscription.suspensionDate,
|
||||||
|
"mediumDate",
|
||||||
|
);
|
||||||
|
const calloutBody =
|
||||||
|
this.subscription.collectionMethod === "charge_automatically"
|
||||||
|
? this.i18nService.t(
|
||||||
|
"pastDueWarningForChargeAutomatically",
|
||||||
|
this.subscription.gracePeriod,
|
||||||
|
suspensionDate,
|
||||||
|
)
|
||||||
|
: this.i18nService.t(
|
||||||
|
"pastDueWarningForSendInvoice",
|
||||||
|
this.subscription.gracePeriod,
|
||||||
|
suspensionDate,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: pastDueText,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: subscriptionExpiredDateLabel,
|
||||||
|
value: this.subscription.unpaidPeriodEndDate,
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
severity: "warning",
|
||||||
|
header: pastDueText,
|
||||||
|
body: calloutBody,
|
||||||
|
showReinstatementButton: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "unpaid": {
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: this.i18nService.t("unpaid"),
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: subscriptionExpiredDateLabel,
|
||||||
|
value: this.subscription.currentPeriodEndDate.toDateString(),
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
severity: "danger",
|
||||||
|
header: this.i18nService.t("unpaidInvoice"),
|
||||||
|
body: this.i18nService.t("toReactivateYourSubscription"),
|
||||||
|
showReinstatementButton: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "pending_cancellation": {
|
||||||
|
const pendingCancellationText = this.i18nService.t("pendingCancellation");
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: pendingCancellationText,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: cancellationDateLabel,
|
||||||
|
value: this.subscription.currentPeriodEndDate.toDateString(),
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
severity: "warning",
|
||||||
|
header: pendingCancellationText,
|
||||||
|
body: this.i18nService.t("subscriptionPendingCanceled"),
|
||||||
|
showReinstatementButton: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "incomplete_expired":
|
||||||
|
case "canceled": {
|
||||||
|
const canceledText = this.i18nService.t("canceled");
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
label: defaultStatusLabel,
|
||||||
|
value: canceledText,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
label: cancellationDateLabel,
|
||||||
|
value: this.subscription.currentPeriodEndDate.toDateString(),
|
||||||
|
},
|
||||||
|
callout: {
|
||||||
|
severity: "danger",
|
||||||
|
header: canceledText,
|
||||||
|
body: this.i18nService.t("subscriptionCanceled"),
|
||||||
|
showReinstatementButton: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestReinstatement = () => this.reinstatementRequested.emit();
|
||||||
|
}
|
@ -4,21 +4,29 @@ export class ProviderSubscriptionResponse extends BaseResponse {
|
|||||||
status: string;
|
status: string;
|
||||||
currentPeriodEndDate: Date;
|
currentPeriodEndDate: Date;
|
||||||
discountPercentage?: number | null;
|
discountPercentage?: number | null;
|
||||||
plans: Plans[] = [];
|
plans: ProviderPlanResponse[] = [];
|
||||||
|
collectionMethod: string;
|
||||||
|
unpaidPeriodEndDate?: string;
|
||||||
|
gracePeriod?: number | null;
|
||||||
|
suspensionDate?: string;
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.status = this.getResponseProperty("status");
|
this.status = this.getResponseProperty("status");
|
||||||
this.currentPeriodEndDate = new Date(this.getResponseProperty("currentPeriodEndDate"));
|
this.currentPeriodEndDate = new Date(this.getResponseProperty("currentPeriodEndDate"));
|
||||||
this.discountPercentage = this.getResponseProperty("discountPercentage");
|
this.discountPercentage = this.getResponseProperty("discountPercentage");
|
||||||
|
this.collectionMethod = this.getResponseProperty("collectionMethod");
|
||||||
|
this.unpaidPeriodEndDate = this.getResponseProperty("unpaidPeriodEndDate");
|
||||||
|
this.gracePeriod = this.getResponseProperty("gracePeriod");
|
||||||
|
this.suspensionDate = this.getResponseProperty("suspensionDate");
|
||||||
const plans = this.getResponseProperty("plans");
|
const plans = this.getResponseProperty("plans");
|
||||||
if (plans != null) {
|
if (plans != null) {
|
||||||
this.plans = plans.map((i: any) => new Plans(i));
|
this.plans = plans.map((i: any) => new ProviderPlanResponse(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Plans extends BaseResponse {
|
export class ProviderPlanResponse extends BaseResponse {
|
||||||
planName: string;
|
planName: string;
|
||||||
seatMinimum: number;
|
seatMinimum: number;
|
||||||
assignedSeats: number;
|
assignedSeats: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user