diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 24ce9d8cb12..f5b56cf829f 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -1,53 +1,70 @@ -import { Component } from "@angular/core"; -import { combineLatest, map } from "rxjs"; +import { Component, OnInit } from "@angular/core"; +import { combineLatest, firstValueFrom, map, Observable } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { HasNudgeService } from "@bitwarden/vault"; +import { UserId } from "@bitwarden/common/types/guid"; +import { VaultNudgesService } from "@bitwarden/vault"; @Component({ selector: "app-tabs-v2", templateUrl: "./tabs-v2.component.html", - providers: [HasNudgeService], }) -export class TabsV2Component { +export class TabsV2Component implements OnInit { + private activeUserId: UserId | null = null; + protected navButtons$: Observable< + { + label: string; + page: string; + iconKey: string; + iconKeyActive: string; + showBerry?: boolean; + }[] + > = new Observable(); constructor( - private readonly hasNudgeService: HasNudgeService, + private vaultNudgesService: VaultNudgesService, + private accountService: AccountService, private readonly configService: ConfigService, ) {} - protected navButtons$ = combineLatest([ - this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), - this.hasNudgeService.nudgeStatus$(), - ]).pipe( - map(([onboardingFeatureEnabled, nudgeStatus]) => { - return [ - { - label: "vault", - page: "/tabs/vault", - iconKey: "lock", - iconKeyActive: "lock-f", - }, - { - label: "generator", - page: "/tabs/generator", - iconKey: "generate", - iconKeyActive: "generate-f", - }, - { - label: "send", - page: "/tabs/send", - iconKey: "send", - iconKeyActive: "send-f", - }, - { - label: "settings", - page: "/tabs/settings", - iconKey: "cog", - iconKeyActive: "cog-f", - showBerry: onboardingFeatureEnabled && !nudgeStatus.hasSpotlightDismissed, - }, - ]; - }), - ); + async ngOnInit() { + this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + this.navButtons$ = combineLatest([ + this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), + this.vaultNudgesService.hasActiveBadges$(this.activeUserId), + ]).pipe( + map(([onboardingFeatureEnabled, hasBadges]) => { + return [ + { + label: "vault", + page: "/tabs/vault", + iconKey: "lock", + iconKeyActive: "lock-f", + }, + { + label: "generator", + page: "/tabs/generator", + iconKey: "generate", + iconKeyActive: "generate-f", + }, + { + label: "send", + page: "/tabs/send", + iconKey: "send", + iconKeyActive: "send-f", + }, + { + label: "settings", + page: "/tabs/settings", + iconKey: "cog", + iconKeyActive: "cog-f", + showBerry: onboardingFeatureEnabled && hasBadges, + }, + ]; + }), + ); + } } diff --git a/libs/vault/src/services/custom-nudges-services/has-nudge.service.ts b/libs/vault/src/services/custom-nudges-services/has-nudge.service.ts deleted file mode 100644 index c9077a7283b..00000000000 --- a/libs/vault/src/services/custom-nudges-services/has-nudge.service.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { inject, Injectable } from "@angular/core"; -import { combineLatest, distinctUntilChanged, map, Observable, of, switchMap } from "rxjs"; - -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { UserId } from "@bitwarden/common/types/guid"; - -import { DefaultSingleNudgeService } from "../default-single-nudge.service"; -import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service"; - -/** - * Custom Nudge Service used for showing if the user has any existing nudge in the Vault. - */ -@Injectable({ - providedIn: "root", -}) -export class HasNudgeService extends DefaultSingleNudgeService { - private accountService = inject(AccountService); - - private nudgeTypes: VaultNudgeType[] = [ - VaultNudgeType.EmptyVaultNudge, - // add additional nudge types here as needed - ]; - - /** - * Returns an observable that emits true if any of the provided nudge types are present - */ - nudgeStatus$(): Observable { - return this.accountService.activeAccount$.pipe( - switchMap((activeAccount) => { - const userId: UserId | undefined = activeAccount?.id; - if (!userId) { - return of({ hasBadgeDismissed: true, hasSpotlightDismissed: true }); - } - - const nudgeObservables: Observable[] = this.nudgeTypes.map((nudge) => - super.nudgeStatus$(nudge, userId), - ); - - return combineLatest(nudgeObservables).pipe( - map((nudgeStates) => { - return { - hasBadgeDismissed: true, - hasSpotlightDismissed: nudgeStates.some((state) => state.hasSpotlightDismissed), - }; - }), - distinctUntilChanged(), - ); - }), - ); - } -} diff --git a/libs/vault/src/services/custom-nudges-services/index.ts b/libs/vault/src/services/custom-nudges-services/index.ts index d725553a62d..68427a8dc4d 100644 --- a/libs/vault/src/services/custom-nudges-services/index.ts +++ b/libs/vault/src/services/custom-nudges-services/index.ts @@ -1,5 +1,4 @@ export * from "./has-items-nudge.service"; export * from "./download-bitwarden-nudge.service"; export * from "./empty-vault-nudge.service"; -export * from "./has-nudge.service"; export * from "./new-item-nudge.service"; diff --git a/libs/vault/src/services/vault-nudges.service.spec.ts b/libs/vault/src/services/vault-nudges.service.spec.ts index c579a711f38..a7469410711 100644 --- a/libs/vault/src/services/vault-nudges.service.spec.ts +++ b/libs/vault/src/services/vault-nudges.service.spec.ts @@ -126,4 +126,31 @@ describe("Vault Nudges Service", () => { expect(result).toBe(false); }); }); + + describe("HasActiveBadges", () => { + it("should return true if a nudgeType with hasBadgeDismissed === false", async () => { + TestBed.overrideProvider(EmptyVaultNudgeService, { + useValue: { + nudgeStatus$: () => of({ hasBadgeDismissed: false, hasSpotlightDismissed: false }), + }, + }); + const service = testBed.inject(VaultNudgesService); + + const result = await firstValueFrom(service.hasActiveBadges$("user-id" as UserId)); + + expect(result).toBe(true); + }); + it("should return false if all nudgeTypes have hasBadgeDismissed === true", async () => { + TestBed.overrideProvider(EmptyVaultNudgeService, { + useValue: { + nudgeStatus$: () => of({ hasBadgeDismissed: true, hasSpotlightDismissed: true }), + }, + }); + const service = testBed.inject(VaultNudgesService); + + const result = await firstValueFrom(service.hasActiveBadges$("user-id" as UserId)); + + expect(result).toBe(false); + }); + }); }); diff --git a/libs/vault/src/services/vault-nudges.service.ts b/libs/vault/src/services/vault-nudges.service.ts index fdca28808cd..171fe85252f 100644 --- a/libs/vault/src/services/vault-nudges.service.ts +++ b/libs/vault/src/services/vault-nudges.service.ts @@ -1,5 +1,5 @@ import { inject, Injectable } from "@angular/core"; -import { of, switchMap } from "rxjs"; +import { combineLatest, map, Observable, of, shareReplay, switchMap } from "rxjs"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -104,4 +104,26 @@ export class VaultNudgesService { : { hasBadgeDismissed: true, hasSpotlightDismissed: true }; await this.getNudgeService(nudge).setNudgeStatus(nudge, dismissedStatus, userId); } + + /** + * Check if there are any active badges for the user to show Berry notification in Tabs + * @param userId + */ + hasActiveBadges$(userId: UserId): Observable { + // Add more nudge types here if they have the settings badge feature + const nudgeTypes = [VaultNudgeType.EmptyVaultNudge]; + + const nudgeTypesWithBadge$ = nudgeTypes.map((nudge) => { + return this.getNudgeService(nudge) + .nudgeStatus$(nudge, userId) + .pipe( + map((status) => !status?.hasBadgeDismissed), + shareReplay({ refCount: false, bufferSize: 1 }), + ); + }); + + return combineLatest(nudgeTypesWithBadge$).pipe( + map((results) => results.some((result) => result === true)), + ); + } }