mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-25 12:15:18 +01:00
[AC-2721][Defect] Apply Subscription Status Updates in Provider Subscription details (#9484)
* Add status banner to the provider subscription page * Add the isexpired method * Add the unpaid status banner
This commit is contained in:
parent
a7b45a44e3
commit
0f9f7f4df6
@ -18,6 +18,7 @@ import {
|
||||
ProviderSelectPaymentMethodDialogComponent,
|
||||
ProviderSubscriptionComponent,
|
||||
} from "../../billing/providers";
|
||||
import { SubscriptionStatusComponent } from "../../billing/providers/subscription/subscription-status.component";
|
||||
|
||||
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
||||
import { ClientsComponent } from "./clients/clients.component";
|
||||
@ -70,6 +71,7 @@ import { SetupComponent } from "./setup/setup.component";
|
||||
ProviderSubscriptionComponent,
|
||||
ProviderSelectPaymentMethodDialogComponent,
|
||||
ProviderPaymentMethodComponent,
|
||||
SubscriptionStatusComponent,
|
||||
],
|
||||
providers: [WebProviderService, ProviderPermissionsGuard],
|
||||
})
|
||||
|
@ -1,32 +1,10 @@
|
||||
<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>
|
||||
|
||||
<app-subscription-status [providerSubscriptionResponse]="subscription"> </app-subscription-status>
|
||||
<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"
|
||||
|
@ -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,6 +4,10 @@ export class ProviderSubscriptionResponse extends BaseResponse {
|
||||
status: string;
|
||||
currentPeriodEndDate: Date;
|
||||
discountPercentage?: number | null;
|
||||
collectionMethod: string;
|
||||
unpaidPeriodEndDate?: string;
|
||||
gracePeriod?: number | null;
|
||||
suspensionDate?: string;
|
||||
plans: Plans[] = [];
|
||||
|
||||
constructor(response: any) {
|
||||
@ -11,6 +15,10 @@ export class ProviderSubscriptionResponse extends BaseResponse {
|
||||
this.status = this.getResponseProperty("status");
|
||||
this.currentPeriodEndDate = new Date(this.getResponseProperty("currentPeriodEndDate"));
|
||||
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");
|
||||
if (plans != null) {
|
||||
this.plans = plans.map((i: any) => new Plans(i));
|
||||
|
Loading…
Reference in New Issue
Block a user