Fix spacing for provider unassigned seats hint' (#9460)
This commit is contained in:
parent
c8eac6fa12
commit
7d12d1a74f
|
@ -1,5 +1,5 @@
|
|||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog dialogSize="large">
|
||||
<bit-dialog dialogSize="large" [loading]="loading">
|
||||
<span bitDialogTitle class="tw-font-semibold">
|
||||
{{ "newClientOrganization" | i18n }}
|
||||
</span>
|
||||
|
@ -49,11 +49,21 @@
|
|||
</bit-form-field>
|
||||
</div>
|
||||
<div class="tw-grid tw-grid-flow-col tw-grid-cols-2 tw-gap-4">
|
||||
<bit-form-field>
|
||||
<bit-form-field disableMargin>
|
||||
<bit-label>
|
||||
{{ "seats" | i18n }}
|
||||
</bit-label>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,11 +2,12 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
|||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
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 { 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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service";
|
||||
|
||||
|
@ -33,6 +34,7 @@ type PlanCard = {
|
|||
name: string;
|
||||
cost: number;
|
||||
type: PlanType;
|
||||
plan: PlanResponse;
|
||||
selected: boolean;
|
||||
};
|
||||
|
||||
|
@ -41,20 +43,24 @@ type PlanCard = {
|
|||
templateUrl: "./create-client-organization.component.html",
|
||||
})
|
||||
export class CreateClientOrganizationComponent implements OnInit {
|
||||
protected ResultType = CreateClientOrganizationResultType;
|
||||
protected formGroup = this.formBuilder.group({
|
||||
clientOwnerEmail: ["", [Validators.required, Validators.email]],
|
||||
organizationName: ["", Validators.required],
|
||||
seats: [null, [Validators.required, Validators.min(1)]],
|
||||
});
|
||||
protected loading = true;
|
||||
protected planCards: PlanCard[];
|
||||
protected ResultType = CreateClientOrganizationResultType;
|
||||
|
||||
private providerPlans: ProviderPlanResponse[];
|
||||
|
||||
constructor(
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
@Inject(DIALOG_DATA) private dialogParams: CreateClientOrganizationParams,
|
||||
private dialogRef: DialogRef<CreateClientOrganizationResultType>,
|
||||
private formBuilder: FormBuilder,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private toastService: ToastService,
|
||||
private webProviderService: WebProviderService,
|
||||
) {}
|
||||
|
||||
|
@ -92,6 +98,11 @@ export class CreateClientOrganizationComponent implements OnInit {
|
|||
}
|
||||
|
||||
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 enterprisePlan = this.dialogParams.plans.find(
|
||||
(plan) => plan.type === PlanType.EnterpriseMonthly,
|
||||
|
@ -102,15 +113,19 @@ export class CreateClientOrganizationComponent implements OnInit {
|
|||
name: this.i18nService.t("planNameTeams"),
|
||||
cost: teamsPlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs,
|
||||
type: teamsPlan.type,
|
||||
plan: teamsPlan,
|
||||
selected: true,
|
||||
},
|
||||
{
|
||||
name: this.i18nService.t("planNameEnterprise"),
|
||||
cost: enterprisePlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs,
|
||||
type: enterprisePlan.type,
|
||||
plan: enterprisePlan,
|
||||
selected: false,
|
||||
},
|
||||
];
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
protected selectPlan(name: string) {
|
||||
|
@ -135,8 +150,23 @@ export class CreateClientOrganizationComponent implements OnInit {
|
|||
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);
|
||||
};
|
||||
|
||||
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>
|
||||
{{ "manageSeatsDescription" | i18n }}
|
||||
</p>
|
||||
<bit-form-field>
|
||||
<bit-form-field disableMargin>
|
||||
<bit-label>
|
||||
{{ "assignedSeats" | i18n }}
|
||||
</bit-label>
|
||||
<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>
|
||||
<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>
|
||||
<ng-container bitDialogFooter>
|
||||
<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 { 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 { 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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
@ -83,7 +83,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit {
|
|||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
getPurchasedSeatsByPlan(planName: string, plans: Plans[]): number {
|
||||
getPurchasedSeatsByPlan(planName: string, plans: ProviderPlanResponse[]): number {
|
||||
const plan = plans.find((plan) => plan.planName === planName);
|
||||
if (plan) {
|
||||
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);
|
||||
if (plan) {
|
||||
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);
|
||||
if (plan) {
|
||||
return plan.seatMinimum;
|
||||
|
|
|
@ -158,6 +158,4 @@ export class ManageClientOrganizationsComponent extends BaseClientsComponent {
|
|||
|
||||
await this.load();
|
||||
};
|
||||
protected readonly openManageClientOrganizationNameDialog =
|
||||
openManageClientOrganizationNameDialog;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Subject, concatMap, takeUntil } from "rxjs";
|
|||
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||
import {
|
||||
Plans,
|
||||
ProviderPlanResponse,
|
||||
ProviderSubscriptionResponse,
|
||||
} from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||
|
||||
|
@ -75,7 +75,7 @@ export class ProviderSubscriptionComponent {
|
|||
return totalSeats > 1 ? totalSeats.toString() : "";
|
||||
}
|
||||
|
||||
sumCost(plans: Plans[]): number {
|
||||
sumCost(plans: ProviderPlanResponse[]): number {
|
||||
return plans.reduce((acc, plan) => acc + plan.cost, 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@ export class ProviderSubscriptionResponse extends BaseResponse {
|
|||
status: string;
|
||||
currentPeriodEndDate: Date;
|
||||
discountPercentage?: number | null;
|
||||
plans: ProviderPlanResponse[] = [];
|
||||
collectionMethod: string;
|
||||
unpaidPeriodEndDate?: string;
|
||||
gracePeriod?: number | null;
|
||||
suspensionDate?: string;
|
||||
plans: Plans[] = [];
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
@ -21,12 +21,12 @@ export class ProviderSubscriptionResponse extends BaseResponse {
|
|||
this.suspensionDate = this.getResponseProperty("suspensionDate");
|
||||
const plans = this.getResponseProperty("plans");
|
||||
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;
|
||||
seatMinimum: number;
|
||||
assignedSeats: number;
|
||||
|
|
Loading…
Reference in New Issue