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:
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
export class BillingBannerServiceAbstraction {
|
||||
paymentMethodBannerStates$: Observable<{ organizationId: string; visible: boolean }[]>;
|
||||
setPaymentMethodBannerState: (organizationId: string, visible: boolean) => Promise<void>;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
44
libs/common/src/billing/services/billing-banner.service.ts
Normal file
44
libs/common/src/billing/services/billing-banner.service.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user