diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts index e3b72c3319f..d7824f3df58 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts @@ -9,7 +9,6 @@ import { BehaviorSubject, Observable, Subject, of } from "rxjs"; import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; import { NudgeType, NudgesService } from "@bitwarden/angular/vault"; -import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; import { AutoConfirmExtensionSetupDialogComponent, AutomaticUserConfirmationService, @@ -185,7 +184,7 @@ describe("VaultV2Component", () => { filterVisibilityState$: new BehaviorSubject({}), } as Partial; - const accountActive$ = new BehaviorSubject({ id: "user-1" }); + const activeAccount$ = new BehaviorSubject({ id: "user-1" }); const cipherSvc = { failedToDecryptCiphers$: jest.fn().mockReturnValue(of([])), @@ -222,12 +221,6 @@ describe("VaultV2Component", () => { hasPremiumFromAnySource$: (_: string) => hasPremiumFromAnySource$, }; - const vaultProfileSvc = { - getProfileCreationDate: jest - .fn() - .mockResolvedValue(new Date(Date.now() - 8 * 24 * 60 * 60 * 1000)), // 8 days ago - }; - const configSvc = { getFeatureFlag$: jest.fn().mockImplementation((_flag: string) => of(false)), }; @@ -250,16 +243,12 @@ describe("VaultV2Component", () => { { provide: VaultPopupScrollPositionService, useValue: scrollSvc }, { provide: AccountService, - useValue: { activeAccount$: accountActive$ }, + useValue: { activeAccount$ }, }, { provide: CipherService, useValue: cipherSvc }, { provide: DialogService, useValue: dialogSvc }, { provide: IntroCarouselService, useValue: introSvc }, { provide: NudgesService, useValue: nudgesSvc }, - { - provide: VaultProfileService, - useValue: vaultProfileSvc, - }, { provide: VaultPopupCopyButtonsService, useValue: { showQuickCopyActions$: new BehaviorSubject(false) }, @@ -473,7 +462,7 @@ describe("VaultV2Component", () => { it("dismissVaultNudgeSpotlight forwards to NudgesService with active user id", fakeAsync(() => { const spy = jest.spyOn(nudgesSvc, "dismissNudge").mockResolvedValue(undefined); - accountActive$.next({ id: "user-xyz" }); + activeAccount$.next({ id: "user-xyz" }); void component.ngOnInit(); tick(); @@ -485,6 +474,10 @@ describe("VaultV2Component", () => { })); it("accountAgeInDays$ computes integer days since creation", (done) => { + activeAccount$.next({ + id: "user-123", + creationDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago + } as any); getObs(component, "accountAgeInDays$").subscribe((days) => { if (days !== null) { expect(days).toBeGreaterThanOrEqual(7); @@ -570,10 +563,6 @@ describe("VaultV2Component", () => { itemsSvc.cipherCount$.next(10); hasPremiumFromAnySource$.next(false); - vaultProfileSvc.getProfileCreationDate = jest - .fn() - .mockResolvedValue(new Date(Date.now() - 3 * 24 * 60 * 60 * 1000)); // 3 days ago - (nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => { return of(type === NudgeType.PremiumUpgrade); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index c58b7b20d2f..51e735fb1ef 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -5,26 +5,24 @@ import { Component, DestroyRef, effect, inject, OnDestroy, OnInit } from "@angul import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router, RouterModule } from "@angular/router"; import { + BehaviorSubject, combineLatest, distinctUntilChanged, filter, firstValueFrom, - from, map, Observable, shareReplay, switchMap, take, - withLatestFrom, tap, - BehaviorSubject, + withLatestFrom, } from "rxjs"; import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; -import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; import { DeactivatedOrg, NoResults, VaultOpen } from "@bitwarden/assets/svg"; import { AutoConfirmExtensionSetupDialogComponent, @@ -162,10 +160,6 @@ export class VaultV2Component implements OnInit, OnDestroy { FeatureFlag.BrowserPremiumSpotlight, ); - private showPremiumNudgeSpotlight$ = this.activeUserId$.pipe( - switchMap((userId) => this.nudgesService.showNudgeSpotlight$(NudgeType.PremiumUpgrade, userId)), - ); - protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$; protected remainingCiphers$ = this.vaultPopupItemsService.remainingCiphers$; protected allFilters$ = this.vaultPopupListFiltersService.allFilters$; @@ -173,38 +167,39 @@ export class VaultV2Component implements OnInit, OnDestroy { protected hasPremium$ = this.activeUserId$.pipe( switchMap((userId) => this.billingAccountService.hasPremiumFromAnySource$(userId)), ); - protected accountAgeInDays$ = this.activeUserId$.pipe( - switchMap((userId) => { - const creationDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)); - return creationDate$.pipe( - map((creationDate) => { - if (!creationDate) { - return 0; - } - const ageInMilliseconds = Date.now() - creationDate.getTime(); - return Math.floor(ageInMilliseconds / (1000 * 60 * 60 * 24)); - }), - ); + protected accountAgeInDays$ = this.accountService.activeAccount$.pipe( + map((account) => { + if (!account || !account.creationDate) { + return 0; + } + const creationDate = account.creationDate; + const ageInMilliseconds = Date.now() - creationDate.getTime(); + return Math.floor(ageInMilliseconds / (1000 * 60 * 60 * 24)); }), ); protected showPremiumSpotlight$ = combineLatest([ this.premiumSpotlightFeatureFlag$, - this.showPremiumNudgeSpotlight$, + this.activeUserId$.pipe( + switchMap((userId) => + this.nudgesService.showNudgeSpotlight$(NudgeType.PremiumUpgrade, userId), + ), + ), this.showHasItemsVaultSpotlight$, this.hasPremium$, this.cipherCount$, this.accountAgeInDays$, ]).pipe( - map( - ([featureFlagEnabled, showPremiumNudge, showHasItemsNudge, hasPremium, count, age]) => + map(([featureFlagEnabled, showPremiumNudge, showHasItemsNudge, hasPremium, count, age]) => { + return ( featureFlagEnabled && showPremiumNudge && !showHasItemsNudge && !hasPremium && count >= 5 && - age >= 7, - ), + age >= 7 + ); + }), shareReplay({ bufferSize: 1, refCount: true }), ); @@ -263,7 +258,6 @@ export class VaultV2Component implements OnInit, OnDestroy { private router: Router, private autoConfirmService: AutomaticUserConfirmationService, private toastService: ToastService, - private vaultProfileService: VaultProfileService, private billingAccountService: BillingAccountProfileStateService, private liveAnnouncer: LiveAnnouncer, private i18nService: I18nService, diff --git a/libs/angular/src/vault/services/custom-nudges-services/has-items-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/has-items-nudge.service.ts index d030b37dbd1..336aead0e8c 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/has-items-nudge.service.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/has-items-nudge.service.ts @@ -1,8 +1,8 @@ import { inject, Injectable } from "@angular/core"; -import { combineLatest, from, Observable, of, switchMap } from "rxjs"; -import { catchError } from "rxjs/operators"; +import { combineLatest, Observable, of, switchMap } from "rxjs"; +import { catchError, map } from "rxjs/operators"; -import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -20,11 +20,14 @@ const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; }) export class HasItemsNudgeService extends DefaultSingleNudgeService { cipherService = inject(CipherService); - vaultProfileService = inject(VaultProfileService); + accountService = inject(AccountService); logService = inject(LogService); nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable { - const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe( + const profileDate$ = this.accountService.activeAccount$.pipe( + map((account) => { + return account?.creationDate ?? new Date(); + }), catchError(() => { this.logService.error("Error getting profile creation date"); // Default to today to ensure we show the nudge