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": {
|
||||
"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>
|
||||
|
||||
<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 { 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({
|
||||
selector: "app-provider-subscription",
|
||||
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