1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 08:43:33 +00:00

[PM-8161] Payment optional trial MVP (#10872)

* Initial comment

* Add changes for the create org with payment method

* Add the secrets manager trail flow

* Add the banners

* Add changes for the Disabled Org

* Add banner to payment method page

* Refactoring changes

* Resolve the bug on tha payment method

* Resolve lint error

* Resolve Pr comments

* resolve the lint issue

* Resolve the lint wrong file issue

* Rename object properly

* Resolve pr comments from sm team

* Resolve the pr comments from sm team

* Fix the failing test

* Resolve some issue with vault

* Resolve the comments from sm team

* Resolve some pr comments from vault team

* Resolve pr comments from auth team

* Exported ValidOrgParams enum

* Removed unnecessary interpolation

* Corrected bit-banner id for trial

* Resolve pr comments from auth team

* Resolve pr comments from auth team

* Removed unnecessary method

* Made OrganizationCreateRequest a subtype of OrganizationNoPaymentMethodCreateRequest

* Resolve review changes from sm

* Resolve review changes from dm

* Resolve the pr comments from billing

* move the free-trial to core

* Move free-trial change to right file

* Revert changes on the free trial  page

* Resolve the comment on protected trial page

* Resolve the comment on protected trial page

* Revert the next async change

* resolve pr comment fro vault team

* resolve the default message comments

* remove unused method

* resolve email sending issue

* Fix the pop issue on payment method

* Fix some console errors

* Fix the pop refresh page

* move the trial services to billing folder

* resolve pr comments

* Resolve the import issues

* Move the observable up

* Resolve blank payment method for trialing org

* Changes to  disable icon is removed onsubmit

* Remove unused references

* add a missing a period at the end of it

* resolve the reload issue

* Resolve the disable icon issue

* Fix the admin access bug

* Resolve the lint issue

* Fix the message incorrect format

* Formatting fixed

* Resolve the access issue of other users role
This commit is contained in:
cyprain-okeke
2024-11-11 17:05:37 +01:00
committed by GitHub
parent 888b9e346c
commit f593269133
39 changed files with 971 additions and 81 deletions

View File

@@ -1,10 +1,13 @@
import { Component, OnInit, ViewChild } from "@angular/core";
import { Location } from "@angular/common";
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, FormControl, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { lastValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
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 { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { BillingPaymentResponse } from "@bitwarden/common/billing/models/response/billing-payment.response";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
@@ -13,8 +16,12 @@ import { VerifyBankRequest } from "@bitwarden/common/models/request/verify-bank.
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SyncService } from "@bitwarden/common/platform/sync";
import { DialogService, ToastService } from "@bitwarden/components";
import { FreeTrial } from "../../core/types/free-trial";
import { TrialFlowService } from "../services/trial-flow.service";
import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component";
import {
AdjustPaymentDialogResult,
@@ -26,7 +33,7 @@ import { TaxInfoComponent } from "./tax-info.component";
templateUrl: "payment-method.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class PaymentMethodComponent implements OnInit {
export class PaymentMethodComponent implements OnInit, OnDestroy {
@ViewChild(TaxInfoComponent) taxInfo: TaxInfoComponent;
loading = false;
@@ -37,6 +44,7 @@ export class PaymentMethodComponent implements OnInit {
paymentMethodType = PaymentMethodType;
organizationId: string;
isUnpaid = false;
organization: Organization;
verifyBankForm = this.formBuilder.group({
amount1: new FormControl<number>(null, [
@@ -52,6 +60,8 @@ export class PaymentMethodComponent implements OnInit {
});
taxForm = this.formBuilder.group({});
launchPaymentModalAutomatically = false;
protected freeTrialData: FreeTrial;
constructor(
protected apiService: ApiService,
@@ -59,12 +69,30 @@ export class PaymentMethodComponent implements OnInit {
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
private router: Router,
private location: Location,
private logService: LogService,
private route: ActivatedRoute,
private formBuilder: FormBuilder,
private dialogService: DialogService,
private toastService: ToastService,
) {}
private trialFlowService: TrialFlowService,
private organizationService: OrganizationService,
protected syncService: SyncService,
) {
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;
}
}
async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
@@ -88,27 +116,37 @@ export class PaymentMethodComponent implements OnInit {
return;
}
this.loading = true;
if (this.forOrganization) {
const billingPromise = this.organizationApiService.getBilling(this.organizationId);
const organizationSubscriptionPromise = this.organizationApiService.getSubscription(
this.organizationId,
);
const organizationPromise = this.organizationService.get(this.organizationId);
[this.billing, this.org] = await Promise.all([
[this.billing, this.org, this.organization] = await Promise.all([
billingPromise,
organizationSubscriptionPromise,
organizationPromise,
]);
this.determineOrgsWithUpcomingPaymentIssues();
} else {
const billingPromise = this.apiService.getUserBillingPayment();
const subPromise = this.apiService.getUserSubscription();
[this.billing, this.sub] = await Promise.all([billingPromise, subPromise]);
}
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();
this.launchPaymentModalAutomatically = false;
this.location.replaceState(this.location.path(), "", {});
}, 800);
}
};
addCredit = async () => {
@@ -132,6 +170,11 @@ export class PaymentMethodComponent implements OnInit {
});
const result = await lastValueFrom(dialogRef.closed);
if (result === AdjustPaymentDialogResult.Adjusted) {
this.location.replaceState(this.location.path(), "", {});
if (this.launchPaymentModalAutomatically && !this.organization.enabled) {
await this.syncService.fullSync(true);
}
this.launchPaymentModalAutomatically = false;
await this.load();
}
};
@@ -162,6 +205,14 @@ export class PaymentMethodComponent implements OnInit {
});
};
determineOrgsWithUpcomingPaymentIssues() {
this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
this.organization,
this.org,
this.billing?.paymentSource,
);
}
get isCreditBalance() {
return this.billing == null || this.billing.balance <= 0;
}
@@ -203,4 +254,8 @@ export class PaymentMethodComponent implements OnInit {
get subscription() {
return this.sub?.subscription ?? this.org?.subscription ?? null;
}
ngOnDestroy(): void {
this.launchPaymentModalAutomatically = false;
}
}