1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

[AC-1758] Show banner when organization requires a payment method (#7088)

* Add billing banner states to account settings

* Add billing banner service

* Add add-payment-method-banners.component

* Use add-payment-method-banners.component in layouts

* Clear banner on payment method addition

* Ran prettier after CI update

* Finalize banners styling/translations

* Will's (non-Tailwind) feedback

* Review feedback

* Review feedback

* Review feedback

* Replace StateService with StateProvider in BillingBannerService

* Remove StateService methods
This commit is contained in:
Alex Morask
2024-01-23 12:47:52 -05:00
committed by GitHub
parent 4475f67bbc
commit 014281cb93
18 changed files with 208 additions and 8 deletions

View File

@@ -9,6 +9,7 @@ import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/reques
import { PaymentRequest } from "../../../billing/models/request/payment.request";
import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request";
import { BillingResponse } from "../../../billing/models/response/billing.response";
import { OrganizationRisksSubscriptionFailureResponse } from "../../../billing/models/response/organization-risks-subscription-failure.response";
import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response";
import { PaymentResponse } from "../../../billing/models/response/payment.response";
import { TaxInfoResponse } from "../../../billing/models/response/tax-info.response";
@@ -78,4 +79,5 @@ export class OrganizationApiServiceAbstraction {
id: string,
request: OrganizationCollectionManagementUpdateRequest,
) => Promise<OrganizationResponse>;
risksSubscriptionFailure: (id: string) => Promise<OrganizationRisksSubscriptionFailureResponse>;
}

View File

@@ -10,6 +10,7 @@ import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/reques
import { PaymentRequest } from "../../../billing/models/request/payment.request";
import { SecretsManagerSubscribeRequest } from "../../../billing/models/request/sm-subscribe.request";
import { BillingResponse } from "../../../billing/models/response/billing.response";
import { OrganizationRisksSubscriptionFailureResponse } from "../../../billing/models/response/organization-risks-subscription-failure.response";
import { OrganizationSubscriptionResponse } from "../../../billing/models/response/organization-subscription.response";
import { PaymentResponse } from "../../../billing/models/response/payment.response";
import { TaxInfoResponse } from "../../../billing/models/response/tax-info.response";
@@ -342,4 +343,18 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
await this.syncService.fullSync(true);
return data;
}
async risksSubscriptionFailure(
id: string,
): Promise<OrganizationRisksSubscriptionFailureResponse> {
const r = await this.apiService.send(
"GET",
"/organizations/" + id + "/risks-subscription-failure",
null,
true,
true,
);
return new OrganizationRisksSubscriptionFailureResponse(r);
}
}

View File

@@ -0,0 +1,6 @@
import { Observable } from "rxjs";
export class BillingBannerServiceAbstraction {
paymentMethodBannerStates$: Observable<{ organizationId: string; visible: boolean }[]>;
setPaymentMethodBannerState: (organizationId: string, visible: boolean) => Promise<void>;
}

View File

@@ -0,0 +1,13 @@
import { BaseResponse } from "../../../models/response/base.response";
export class OrganizationRisksSubscriptionFailureResponse extends BaseResponse {
organizationId: string;
risksSubscriptionFailure: boolean;
constructor(response: any) {
super(response);
this.organizationId = this.getResponseProperty("OrganizationId");
this.risksSubscriptionFailure = this.getResponseProperty("RisksSubscriptionFailure");
}
}

View File

@@ -0,0 +1,44 @@
import { map, Observable } from "rxjs";
import {
ActiveUserState,
BILLING_BANNERS_DISK,
KeyDefinition,
StateProvider,
} from "../../platform/state";
import { BillingBannerServiceAbstraction } from "../abstractions/billing-banner.service.abstraction";
const PAYMENT_METHOD_BANNERS_KEY = KeyDefinition.record<boolean>(
BILLING_BANNERS_DISK,
"paymentMethodBanners",
{
deserializer: (b) => b,
},
);
export class BillingBannerService implements BillingBannerServiceAbstraction {
private paymentMethodBannerStates: ActiveUserState<Record<string, boolean>>;
paymentMethodBannerStates$: Observable<{ organizationId: string; visible: boolean }[]>;
constructor(private stateProvider: StateProvider) {
this.paymentMethodBannerStates = this.stateProvider.getActive(PAYMENT_METHOD_BANNERS_KEY);
this.paymentMethodBannerStates$ = this.paymentMethodBannerStates.state$.pipe(
map((billingBannerStates) =>
!billingBannerStates
? []
: Object.entries(billingBannerStates).map(([organizationId, visible]) => ({
organizationId,
visible,
})),
),
);
}
async setPaymentMethodBannerState(organizationId: string, visibility: boolean): Promise<void> {
await this.paymentMethodBannerStates.update((states) => {
states ??= {};
states[organizationId] = visibility;
return states;
});
}
}

View File

@@ -20,3 +20,5 @@ import { StateDefinition } from "./state-definition";
export const ACCOUNT_MEMORY = new StateDefinition("account", "memory");
export const CRYPTO_DISK = new StateDefinition("crypto", "disk");
export const BILLING_BANNERS_DISK = new StateDefinition("billingBanners", "disk");