From 79a0b0d46db222f9b05d0460d82b063b99edff26 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 14 May 2024 14:50:36 +0100 Subject: [PATCH] [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 --- apps/web/src/locales/en/messages.json | 15 ++++ .../provider-subscription.component.html | 82 +++++++++++++++++++ .../provider-subscription.component.ts | 81 +++++++++++++++++- 3 files changed, 177 insertions(+), 1 deletion(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 24919bc925..cd3147532d 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -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" + } + } } } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.html b/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.html index e91fa55ecf..fdcb8a6701 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.html @@ -1 +1,83 @@ + + + + + {{ "loading" | i18n }} + + + + + {{ "subscriptionCanceled" | i18n }} + +
+
{{ "billingPlan" | i18n }}
+
{{ "providerPlan" | i18n }}
+ +
{{ "status" | i18n }}
+
+ {{ subscription.status }} +
+
{{ "nextCharge" | i18n }}
+
+ {{ subscription.currentPeriodEndDate | date: "mediumDate" }} +
+
+
+
+ + +
+ {{ "details" | i18n }}  {{ "providerDiscount" | i18n: subscription.discountPercentage }} + + + + + + + {{ getFormattedPlanName(i.planName) }} {{ "orgSeats" | i18n }} ({{ + i.cadence.toLowerCase() + }}) {{ "×" }}{{ getFormattedSeatCount(i.seatMinimum, i.purchasedSeats) }} + @ + {{ + getFormattedCost( + i.cost, + i.seatMinimum, + i.purchasedSeats, + subscription.discountPercentage + ) | currency: "$" + }} + + + {{ ((100 - subscription.discountPercentage) / 100) * i.cost | currency: "$" }} /{{ + "month" | i18n + }} +
+ + {{ i.cost | currency: "$" }} /{{ "month" | i18n }} + +
+ + + + + + + Total: {{ totalCost | currency: "$" }} /{{ + "month" | i18n + }} + + +
+
+
+
+
+
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.ts index 1498dfac4c..8ede48576f 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.ts @@ -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(); + 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(); + } +}