mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01:00
[AC-1934] Clients: Create component to display provider subscription details (#9129)
* initial commit * Make changes for provider billing details * replace the hardcoded values with real data * Apply discount on the displayed amount * Fix the design issues base on the new design changes * Fix the design space issue * Remove unnecessary If statements * Revert the change * Remove unnecessary If statements * Refactoring the discount calculation for easy understanding
This commit is contained in:
parent
26c08123bb
commit
79a0b0d46d
@ -8192,5 +8192,20 @@
|
|||||||
},
|
},
|
||||||
"updatedOrganizationName": {
|
"updatedOrganizationName": {
|
||||||
"message": "Updated organization name"
|
"message": "Updated organization name"
|
||||||
|
},
|
||||||
|
"providerPlan": {
|
||||||
|
"message": "Managed Service Provider"
|
||||||
|
},
|
||||||
|
"orgSeats": {
|
||||||
|
"message": "Organization Seats"
|
||||||
|
},
|
||||||
|
"providerDiscount": {
|
||||||
|
"message": "$AMOUNT$% Discount",
|
||||||
|
"placeholders": {
|
||||||
|
"amount": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,83 @@
|
|||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
|
|
||||||
|
<bit-container>
|
||||||
|
<ng-container *ngIf="!firstLoaded && loading">
|
||||||
|
<i class="bwi bwi-spinner bwi-spin text-muted" title="{{ 'loading' | i18n }}"></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<div class="tw-flex-col">
|
||||||
|
<strong class="tw-block tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 pb-2"
|
||||||
|
>{{ "details" | i18n }}  <span
|
||||||
|
bitBadge
|
||||||
|
variant="success"
|
||||||
|
*ngIf="subscription.discountPercentage"
|
||||||
|
>{{ "providerDiscount" | i18n: subscription.discountPercentage }}</span
|
||||||
|
>
|
||||||
|
</strong>
|
||||||
|
<bit-table>
|
||||||
|
<ng-template body>
|
||||||
|
<ng-container *ngIf="subscription">
|
||||||
|
<tr bitRow *ngFor="let i of subscription.plans">
|
||||||
|
<td bitCell class="tw-pl-0 tw-py-3">
|
||||||
|
{{ getFormattedPlanName(i.planName) }} {{ "orgSeats" | i18n }} ({{
|
||||||
|
i.cadence.toLowerCase()
|
||||||
|
}}) {{ "×" }}{{ getFormattedSeatCount(i.seatMinimum, i.purchasedSeats) }}
|
||||||
|
@
|
||||||
|
{{
|
||||||
|
getFormattedCost(
|
||||||
|
i.cost,
|
||||||
|
i.seatMinimum,
|
||||||
|
i.purchasedSeats,
|
||||||
|
subscription.discountPercentage
|
||||||
|
) | currency: "$"
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<td bitCell class="tw-text-right tw-py-3">
|
||||||
|
{{ ((100 - subscription.discountPercentage) / 100) * i.cost | currency: "$" }} /{{
|
||||||
|
"month" | i18n
|
||||||
|
}}
|
||||||
|
<div>
|
||||||
|
<bit-hint class="tw-text-sm tw-line-through">
|
||||||
|
{{ i.cost | currency: "$" }} /{{ "month" | i18n }}
|
||||||
|
</bit-hint>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr bitRow>
|
||||||
|
<td bitCell class="tw-pl-0 tw-py-3"></td>
|
||||||
|
<td bitCell class="tw-text-right">
|
||||||
|
<span class="tw-font-bold">Total:</span> {{ totalCost | currency: "$" }} /{{
|
||||||
|
"month" | i18n
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
</bit-table>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</bit-container>
|
||||||
|
@ -1,7 +1,86 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { Subject, concatMap, takeUntil } from "rxjs";
|
||||||
|
|
||||||
|
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||||
|
import {
|
||||||
|
Plans,
|
||||||
|
ProviderSubscriptionResponse,
|
||||||
|
} from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-provider-subscription",
|
selector: "app-provider-subscription",
|
||||||
templateUrl: "./provider-subscription.component.html",
|
templateUrl: "./provider-subscription.component.html",
|
||||||
})
|
})
|
||||||
export class ProviderSubscriptionComponent {}
|
export class ProviderSubscriptionComponent {
|
||||||
|
subscription: ProviderSubscriptionResponse;
|
||||||
|
providerId: string;
|
||||||
|
firstLoaded = false;
|
||||||
|
loading: boolean;
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
totalCost: number;
|
||||||
|
currentDate = new Date();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private billingApiService: BillingApiServiceAbstraction,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.route.params
|
||||||
|
.pipe(
|
||||||
|
concatMap(async (params) => {
|
||||||
|
this.providerId = params.providerId;
|
||||||
|
await this.load();
|
||||||
|
this.firstLoaded = true;
|
||||||
|
}),
|
||||||
|
takeUntil(this.destroy$),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
get isExpired() {
|
||||||
|
return this.subscription.status !== "active";
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
if (this.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
this.subscription = await this.billingApiService.getProviderSubscription(this.providerId);
|
||||||
|
this.totalCost =
|
||||||
|
((100 - this.subscription.discountPercentage) / 100) * this.sumCost(this.subscription.plans);
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedCost(
|
||||||
|
cost: number,
|
||||||
|
seatMinimum: number,
|
||||||
|
purchasedSeats: number,
|
||||||
|
discountPercentage: number,
|
||||||
|
): number {
|
||||||
|
const costPerSeat = cost / (seatMinimum + purchasedSeats);
|
||||||
|
const discountedCost = costPerSeat - (costPerSeat * discountPercentage) / 100;
|
||||||
|
return discountedCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedPlanName(planName: string): string {
|
||||||
|
const spaceIndex = planName.indexOf(" ");
|
||||||
|
return planName.substring(0, spaceIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedSeatCount(seatMinimum: number, purchasedSeats: number): string {
|
||||||
|
const totalSeats = seatMinimum + purchasedSeats;
|
||||||
|
return totalSeats > 1 ? totalSeats.toString() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
sumCost(plans: Plans[]): number {
|
||||||
|
return plans.reduce((acc, plan) => acc + plan.cost, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user