mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-22 16:29:09 +01:00
[PM-6975] Replace purchasedPremium broadcast message with observables (#8421)
In https://github.com/bitwarden/clients/pull/8133 the premium state changed to be derived from observables, which means we can get rid of the `purchasePremium` messages that are sent and instead rely directly on the observable to distribute the state.
This commit is contained in:
parent
daa9e742e7
commit
23c89bda74
@ -124,10 +124,7 @@ export class PremiumComponent implements OnInit {
|
||||
await this.apiService.refreshIdentityToken();
|
||||
await this.syncService.fullSync(true);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("premiumUpdated"));
|
||||
this.messagingService.send("purchasedPremium");
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/settings/subscription/user-subscription"]);
|
||||
await this.router.navigate(["/settings/subscription/user-subscription"]);
|
||||
}
|
||||
|
||||
get additionalStorageTotal(): number {
|
||||
|
@ -19,7 +19,7 @@
|
||||
<bit-nav-item
|
||||
[text]="'subscription' | i18n"
|
||||
route="settings/subscription"
|
||||
*ngIf="!hideSubscription"
|
||||
*ngIf="showSubscription$ | async"
|
||||
></bit-nav-item>
|
||||
<bit-nav-item [text]="'domainRules' | i18n" route="settings/domain-rules"></bit-nav-item>
|
||||
<bit-nav-item
|
||||
@ -29,7 +29,7 @@
|
||||
<bit-nav-item
|
||||
[text]="'sponsoredFamilies' | i18n"
|
||||
route="settings/sponsored-families"
|
||||
*ngIf="hasFamilySponsorshipAvailable"
|
||||
*ngIf="hasFamilySponsorshipAvailable$ | async"
|
||||
></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
</nav>
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { Observable, combineLatest, concatMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
@ -18,8 +17,6 @@ import { PaymentMethodWarningsModule } from "../billing/shared";
|
||||
|
||||
import { PasswordManagerLogo } from "./password-manager-logo";
|
||||
|
||||
const BroadcasterSubscriptionId = "UserLayoutComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-user-layout",
|
||||
templateUrl: "user-layout.component.html",
|
||||
@ -34,10 +31,10 @@ const BroadcasterSubscriptionId = "UserLayoutComponent";
|
||||
PaymentMethodWarningsModule,
|
||||
],
|
||||
})
|
||||
export class UserLayoutComponent implements OnInit, OnDestroy {
|
||||
export class UserLayoutComponent implements OnInit {
|
||||
protected readonly logo = PasswordManagerLogo;
|
||||
hasFamilySponsorshipAvailable: boolean;
|
||||
hideSubscription: boolean;
|
||||
protected hasFamilySponsorshipAvailable$: Observable<boolean>;
|
||||
protected showSubscription$: Observable<boolean>;
|
||||
|
||||
protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.ShowPaymentMethodWarningBanners,
|
||||
@ -45,8 +42,6 @@ export class UserLayoutComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
constructor(
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationService: OrganizationService,
|
||||
private apiService: ApiService,
|
||||
@ -58,43 +53,28 @@ export class UserLayoutComponent implements OnInit, OnDestroy {
|
||||
async ngOnInit() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "purchasedPremium":
|
||||
await this.load();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await this.syncService.fullSync(false);
|
||||
await this.load();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
this.hasFamilySponsorshipAvailable$ = this.organizationService.canManageSponsorships$;
|
||||
|
||||
async load() {
|
||||
const hasPremiumPersonally = await firstValueFrom(
|
||||
// We want to hide the subscription menu for organizations that provide premium.
|
||||
// Except if the user has premium personally or has a billing history.
|
||||
this.showSubscription$ = combineLatest([
|
||||
this.billingAccountProfileStateService.hasPremiumPersonally$,
|
||||
);
|
||||
const hasPremiumFromOrg = await firstValueFrom(
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$,
|
||||
);
|
||||
const selfHosted = this.platformUtilsService.isSelfHost();
|
||||
]).pipe(
|
||||
concatMap(async ([hasPremiumPersonally, hasPremiumFromOrg]) => {
|
||||
const isCloud = !this.platformUtilsService.isSelfHost();
|
||||
|
||||
this.hasFamilySponsorshipAvailable = await this.organizationService.canManageSponsorships();
|
||||
let billing = null;
|
||||
if (!selfHosted) {
|
||||
if (isCloud) {
|
||||
// TODO: We should remove the need to call this!
|
||||
billing = await this.apiService.getUserBillingHistory();
|
||||
}
|
||||
this.hideSubscription =
|
||||
!hasPremiumPersonally && hasPremiumFromOrg && (selfHosted || billing?.hasNoHistory);
|
||||
|
||||
const cloudAndBillingHistory = isCloud && !billing?.hasNoHistory;
|
||||
return hasPremiumPersonally || !hasPremiumFromOrg || cloudAndBillingHistory;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
const BroadcasterSubscriptionId = "SettingsComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-settings",
|
||||
templateUrl: "settings.component.html",
|
||||
})
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
premium: boolean;
|
||||
selfHosted: boolean;
|
||||
hasFamilySponsorshipAvailable: boolean;
|
||||
hideSubscription: boolean;
|
||||
|
||||
constructor(
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationService: OrganizationService,
|
||||
private apiService: ApiService,
|
||||
private billingAccountProfileStateServiceAbstraction: BillingAccountProfileStateService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "purchasedPremium":
|
||||
await this.load();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.selfHosted = await this.platformUtilsService.isSelfHost();
|
||||
await this.load();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.premium = await firstValueFrom(
|
||||
this.billingAccountProfileStateServiceAbstraction.hasPremiumPersonally$,
|
||||
);
|
||||
this.hasFamilySponsorshipAvailable = await this.organizationService.canManageSponsorships();
|
||||
const hasPremiumFromOrg = await firstValueFrom(
|
||||
this.billingAccountProfileStateServiceAbstraction.hasPremiumFromAnyOrganization$,
|
||||
);
|
||||
let billing = null;
|
||||
if (!this.selfHosted) {
|
||||
billing = await this.apiService.getUserBillingHistory();
|
||||
}
|
||||
this.hideSubscription =
|
||||
!this.premium && hasPremiumFromOrg && (this.selfHosted || billing?.hasNoHistory);
|
||||
}
|
||||
}
|
@ -116,7 +116,7 @@ export abstract class OrganizationService {
|
||||
* https://bitwarden.atlassian.net/browse/AC-2252.
|
||||
*/
|
||||
getFromState: (id: string) => Promise<Organization>;
|
||||
canManageSponsorships: () => Promise<boolean>;
|
||||
canManageSponsorships$: Observable<boolean>;
|
||||
hasOrganizations: () => Promise<boolean>;
|
||||
get$: (id: string) => Observable<Organization | undefined>;
|
||||
get: (id: string) => Promise<Organization>;
|
||||
|
@ -121,7 +121,7 @@ describe("OrganizationService", () => {
|
||||
const mockData: OrganizationData[] = buildMockOrganizations(1);
|
||||
mockData[0].familySponsorshipAvailable = true;
|
||||
fakeActiveUserState.nextState(arrayToRecord(mockData));
|
||||
const result = await organizationService.canManageSponsorships();
|
||||
const result = await firstValueFrom(organizationService.canManageSponsorships$);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
@ -129,7 +129,7 @@ describe("OrganizationService", () => {
|
||||
const mockData: OrganizationData[] = buildMockOrganizations(1);
|
||||
mockData[0].familySponsorshipFriendlyName = "Something";
|
||||
fakeActiveUserState.nextState(arrayToRecord(mockData));
|
||||
const result = await organizationService.canManageSponsorships();
|
||||
const result = await firstValueFrom(organizationService.canManageSponsorships$);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
@ -137,7 +137,7 @@ describe("OrganizationService", () => {
|
||||
const mockData: OrganizationData[] = buildMockOrganizations(1);
|
||||
mockData[0].familySponsorshipFriendlyName = null;
|
||||
fakeActiveUserState.nextState(arrayToRecord(mockData));
|
||||
const result = await organizationService.canManageSponsorships();
|
||||
const result = await firstValueFrom(organizationService.canManageSponsorships$);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
@ -77,14 +77,10 @@ export class OrganizationService implements InternalOrganizationServiceAbstracti
|
||||
return await firstValueFrom(this.getOrganizationsFromState$(userId as UserId));
|
||||
}
|
||||
|
||||
async canManageSponsorships(): Promise<boolean> {
|
||||
return await firstValueFrom(
|
||||
this.organizations$.pipe(
|
||||
canManageSponsorships$ = this.organizations$.pipe(
|
||||
mapToExcludeOrganizationsWithoutFamilySponsorshipSupport(),
|
||||
mapToBooleanHasAnyOrganizations(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async hasOrganizations(): Promise<boolean> {
|
||||
return await firstValueFrom(this.organizations$.pipe(mapToBooleanHasAnyOrganizations()));
|
||||
|
Loading…
Reference in New Issue
Block a user