Skip to content

Commit

Permalink
Resolve the pr comments from billing
Browse files Browse the repository at this point in the history
  • Loading branch information
cyprain-okeke committed Oct 1, 2024
1 parent a57c0fd commit c5601d9
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NgModule } from "@angular/core";

import { BannerModule } from "../../../../../../libs/components/src/banner/banner.module";
import { UserVerificationModule } from "../../auth/shared/components/user-verification";
import { LooseComponentsModule } from "../../shared";
import { BillingSharedModule } from "../shared";
Expand Down Expand Up @@ -28,6 +29,7 @@ import { SubscriptionStatusComponent } from "./subscription-status.component";
BillingSharedModule,
OrganizationPlansComponent,
LooseComponentsModule,
BannerModule,
],
declarations: [
AdjustSubscription,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
<bit-banner
id="free-trial-banner"
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
bannerType="premium"
icon="bwi-billing"
[showClose]="false"
*ngIf="freeTrialData.shownBanner"
>
{{ freeTrialData.message }}
<a
bitLink
linkType="contrast"
(click)="changePayment()"
class="tw-cursor-pointer"
rel="noreferrer noopener"
>
{{ "routeToPaymentPageTrigger" | i18n }}
</a>
</bit-banner>
<app-header></app-header>
<bit-container>
<ng-container *ngIf="loading">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { Component, ViewChild } from "@angular/core";
import { Location } from "@angular/common";
import { Component, OnDestroy, ViewChild } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, Router } from "@angular/router";
import { from, lastValueFrom, switchMap } from "rxjs";

import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response";
import { FreeTrial } from "@bitwarden/common/billing/types/free-trial";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService, ToastService } from "@bitwarden/components";

import { TrialFlowService } from "../../../core/trial-flow.service";
import { TaxInfoComponent } from "../../shared";
import {
AddCreditDialogResult,
Expand All @@ -25,26 +32,35 @@ import {
@Component({
templateUrl: "./organization-payment-method.component.html",
})
export class OrganizationPaymentMethodComponent {
export class OrganizationPaymentMethodComponent implements OnDestroy {
@ViewChild(TaxInfoComponent) taxInfoComponent: TaxInfoComponent;

organizationId: string;
isUnpaid = false;
accountCredit: number;
paymentSource?: PaymentSourceResponse;
subscriptionStatus?: string;
protected freeTrialData: FreeTrial;
organization: Organization;
organizationSubscriptionResponse: OrganizationSubscriptionResponse;

loading = true;

protected readonly Math = Math;
launchPaymentModalAutomatically = false;

constructor(
private activatedRoute: ActivatedRoute,
private billingApiService: BillingApiServiceAbstraction,
protected organizationApiService: OrganizationApiServiceAbstraction,
private dialogService: DialogService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private router: Router,
private toastService: ToastService,
private location: Location,
private trialFlowService: TrialFlowService,
private organizationService: OrganizationService,
) {
this.activatedRoute.params
.pipe(
Expand All @@ -59,6 +75,23 @@ export class OrganizationPaymentMethodComponent {
}),
)
.subscribe();

const state = this.router.getCurrentNavigation()?.extras?.state;
// incase the above state is undefined or null we use redundantState
const redundantState: any = location.getState();
if (state && Object.prototype.hasOwnProperty.call(state, "launchPaymentModalAutomatically")) {
this.launchPaymentModalAutomatically = state.launchPaymentModalAutomatically;
} else if (
redundantState &&
Object.prototype.hasOwnProperty.call(redundantState, "launchPaymentModalAutomatically")
) {
this.launchPaymentModalAutomatically = redundantState.launchPaymentModalAutomatically;
} else {
this.launchPaymentModalAutomatically = false;
}
}
ngOnDestroy(): void {
this.launchPaymentModalAutomatically = false;
}

protected addAccountCredit = async (): Promise<void> => {
Expand All @@ -83,6 +116,35 @@ export class OrganizationPaymentMethodComponent {
this.paymentSource = paymentSource;
this.subscriptionStatus = subscriptionStatus;
this.loading = false;

if (this.organizationId) {
const organizationSubscriptionPromise = this.organizationApiService.getSubscription(
this.organizationId,
);
const organizationPromise = this.organizationService.get(this.organizationId);

[this.organizationSubscriptionResponse, this.organization] = await Promise.all([
organizationSubscriptionPromise,
organizationPromise,
]);
this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
this.organization,
this.organizationSubscriptionResponse,
paymentSource,
);
}
this.isUnpaid = this.subscriptionStatus === "unpaid" ?? false;
this.loading = false;
// If the flag `launchPaymentModalAutomatically` is set to true,
// we schedule a timeout (delay of 800ms) to automatically launch the payment modal.
// This delay ensures that any prior UI/rendering operations complete before triggering the modal.
if (this.launchPaymentModalAutomatically) {
window.setTimeout(async () => {
await this.changePayment();
this.launchPaymentModalAutomatically = false;
this.location.replaceState(this.location.path(), "", {});
}, 800);
}
};

protected updatePaymentMethod = async (): Promise<void> => {
Expand All @@ -100,6 +162,20 @@ export class OrganizationPaymentMethodComponent {
}
};

changePayment = async () => {
const dialogRef = AdjustPaymentDialogV2Component.open(this.dialogService, {
data: {
initialPaymentMethod: this.paymentSource?.type,
organizationId: this.organizationId,
},
});
const result = await lastValueFrom(dialogRef.closed);
if (result === AdjustPaymentDialogV2ResultType.Submitted) {
this.location.replaceState(this.location.path(), "", {});
await this.load();
}
};

protected updateTaxInformation = async (): Promise<void> => {
this.taxInfoComponent.taxFormGroup.updateValueAndValidity();
this.taxInfoComponent.taxFormGroup.markAllAsTouched();
Expand Down
17 changes: 5 additions & 12 deletions apps/web/src/app/billing/shared/payment-method.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export class PaymentMethodComponent implements OnInit, OnDestroy {
const organizationSubscriptionPromise = this.organizationApiService.getSubscription(
this.organizationId,
);
const organizationPromise = await this.organizationService.get(this.organizationId);
const organizationPromise = this.organizationService.get(this.organizationId);

[this.billing, this.org, this.organization] = await Promise.all([
billingPromise,
Expand All @@ -131,10 +131,12 @@ export class PaymentMethodComponent implements OnInit, OnDestroy {
const subPromise = this.apiService.getUserSubscription();

[this.billing, this.sub] = await Promise.all([billingPromise, subPromise]);
this.determineOrgsWithUpcomingPaymentIssues();
}
this.isUnpaid = this.subscription?.status === "unpaid" ?? false;
this.loading = false;
// If the flag `launchPaymentModalAutomatically` is set to true,
// we schedule a timeout (delay of 800ms) to automatically launch the payment modal.
// This delay ensures that any prior UI/rendering operations complete before triggering the modal.
if (this.launchPaymentModalAutomatically) {
window.setTimeout(async () => {
await this.changePayment();
Expand Down Expand Up @@ -200,16 +202,7 @@ export class PaymentMethodComponent implements OnInit, OnDestroy {
this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
this.organization,
this.org,
this.billing,
);
}

async navigateToPaymentMethod() {
await this.router.navigate(
["organizations", `${this.organizationId}`, "billing", "payment-method"],
{
state: { launchPaymentModalAutomatically: true },
},
this.billing?.paymentSource,
);
}

Expand Down
7 changes: 4 additions & 3 deletions apps/web/src/app/core/trial-flow.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Injectable } from "@angular/core";

import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response";
import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response";
import { FreeTrial } from "@bitwarden/common/billing/types/free-trial";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";

Expand All @@ -12,11 +13,11 @@ export class TrialFlowService {
checkForOrgsWithUpcomingPaymentIssues(
organization: Organization,
organizationSubscription: OrganizationSubscriptionResponse,
billing: BillingResponse,
paymentSource: BillingSourceResponse | PaymentSourceResponse,
): FreeTrial {
const trialEndDate = organizationSubscription?.subscription?.trialEndDate;
const displayBanner =
!billing?.paymentSource &&
!paymentSource &&
organization?.isOwner &&
organizationSubscription?.subscription?.status === "trialing";
const trialRemainingDays = trialEndDate ? this.calculateTrialRemainingDays(trialEndDate) : 0;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/vault/individual-vault/vault.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
org,
subscription,
billing,
billing?.paymentSource,
);
}),
),
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/vault/org-vault/vault.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
org,
sub,
billing,
billing?.paymentSource,
);
return this.freeTrialData;
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export class OverviewComponent implements OnInit, OnDestroy {
this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
org,
sub,
billing,
billing?.paymentSource,
);
return this.freeTrialData;
}),
Expand Down

0 comments on commit c5601d9

Please sign in to comment.