From a62d269a8910be6f8f34930610389b6ccdec0daa Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Thu, 1 May 2025 12:43:55 -0400 Subject: [PATCH] [PM-18803] nudges new items (#14523) * Added new-items-nudge service and component to show spotlight for new item nudges --- apps/browser/src/_locales/en/messages.json | 32 +++++- apps/desktop/src/locales/en/messages.json | 30 ++++++ apps/web/src/locales/en/messages.json | 30 ++++++ .../src/platform/state/state-definitions.ts | 2 +- .../src/cipher-form/cipher-form.stories.ts | 20 +++- .../components/cipher-form.component.html | 1 + .../components/cipher-form.component.ts | 2 + .../new-item-nudge.component.html | 8 ++ .../new-item-nudge.component.spec.ts | 101 ++++++++++++++++++ .../new-item-nudge.component.ts | 90 ++++++++++++++++ .../has-nudge.service.ts | 2 - .../services/custom-nudges-services/index.ts | 1 + .../new-item-nudge.service.ts | 65 +++++++++++ .../src/services/vault-nudges.service.spec.ts | 2 + .../src/services/vault-nudges.service.ts | 19 +++- 15 files changed, 398 insertions(+), 7 deletions(-) create mode 100644 libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.html create mode 100644 libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts create mode 100644 libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts create mode 100644 libs/vault/src/services/custom-nudges-services/new-item-nudge.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 5c9e829e82f..6e1e2ef57ac 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5248,5 +5248,35 @@ }, "hasItemsVaultNudgeBody": { "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } -} \ No newline at end of file +} diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 81e3a94ff4d..2350e0df4c7 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -3712,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index ce45b538bbe..59ba7961d82 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -10629,6 +10629,36 @@ "newBusinessUnit": { "message": "New business unit" }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." + }, "restart": { "message": "Restart" }, diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 70e0c3998dd..587212299df 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -206,7 +206,7 @@ export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk"); export const SECURITY_TASKS_DISK = new StateDefinition("securityTasks", "disk"); export const AT_RISK_PASSWORDS_PAGE_DISK = new StateDefinition("atRiskPasswordsPage", "disk"); export const NOTIFICATION_DISK = new StateDefinition("notifications", "disk"); -export const VAULT_NUDGES_DISK = new StateDefinition("vaultNudges", "disk"); +export const VAULT_NUDGES_DISK = new StateDefinition("vaultNudges", "disk", { web: "disk-local" }); export const VAULT_BROWSER_INTRO_CAROUSEL = new StateDefinition( "vaultBrowserIntroCarousel", "disk", diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 50577472120..9943f07292d 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -34,7 +34,9 @@ import { AsyncActionsModule, ButtonModule, ItemModule, ToastService } from "@bit import { CipherFormConfig, CipherFormGenerationService, + NudgeStatus, PasswordRepromptService, + VaultNudgesService, } from "@bitwarden/vault"; // FIXME: remove `/apps` import from `/libs` // FIXME: remove `src` and fix import @@ -47,6 +49,7 @@ import { CipherFormService } from "./abstractions/cipher-form.service"; import { TotpCaptureService } from "./abstractions/totp-capture.service"; import { CipherFormModule } from "./cipher-form.module"; import { CipherFormComponent } from "./components/cipher-form.component"; +import { NewItemNudgeComponent } from "./components/new-item-nudge/new-item-nudge.component"; import { CipherFormCacheService } from "./services/default-cipher-form-cache.service"; const defaultConfig: CipherFormConfig = { @@ -132,8 +135,23 @@ export default { component: CipherFormComponent, decorators: [ moduleMetadata({ - imports: [CipherFormModule, AsyncActionsModule, ButtonModule, ItemModule], + imports: [ + CipherFormModule, + AsyncActionsModule, + ButtonModule, + ItemModule, + NewItemNudgeComponent, + ], providers: [ + { + provide: VaultNudgesService, + useValue: { + showNudge$: new BehaviorSubject({ + hasBadgeDismissed: true, + hasSpotlightDismissed: true, + } as NudgeStatus), + }, + }, { provide: CipherFormService, useClass: TestAddEditFormService, diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.html b/libs/vault/src/cipher-form/components/cipher-form.component.html index 6b327486c47..614c7f3dc7a 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.html +++ b/libs/vault/src/cipher-form/components/cipher-form.component.html @@ -1,3 +1,4 @@ +
diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.ts b/libs/vault/src/cipher-form/components/cipher-form.component.ts index 080af489253..96e1328338b 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.ts @@ -45,6 +45,7 @@ import { CardDetailsSectionComponent } from "./card-details-section/card-details import { IdentitySectionComponent } from "./identity/identity.component"; import { ItemDetailsSectionComponent } from "./item-details/item-details-section.component"; import { LoginDetailsSectionComponent } from "./login-details-section/login-details-section.component"; +import { NewItemNudgeComponent } from "./new-item-nudge/new-item-nudge.component"; import { SshKeySectionComponent } from "./sshkey-section/sshkey-section.component"; @Component({ @@ -76,6 +77,7 @@ import { SshKeySectionComponent } from "./sshkey-section/sshkey-section.componen NgIf, AdditionalOptionsSectionComponent, LoginDetailsSectionComponent, + NewItemNudgeComponent, ], }) export class CipherFormComponent implements AfterViewInit, OnInit, OnChanges, CipherFormContainer { diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.html b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.html new file mode 100644 index 00000000000..5cd1246fd36 --- /dev/null +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.html @@ -0,0 +1,8 @@ + + + + diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts new file mode 100644 index 00000000000..073c588690d --- /dev/null +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts @@ -0,0 +1,101 @@ +import { CommonModule } from "@angular/common"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherType } from "@bitwarden/sdk-internal"; + +import { VaultNudgesService, VaultNudgeType } from "../../../services/vault-nudges.service"; + +import { NewItemNudgeComponent } from "./new-item-nudge.component"; + +describe("NewItemNudgeComponent", () => { + let component: NewItemNudgeComponent; + let fixture: ComponentFixture; + + let i18nService: MockProxy; + let accountService: MockProxy; + let vaultNudgesService: MockProxy; + + beforeEach(async () => { + i18nService = mock({ t: (key: string) => key }); + accountService = mock(); + vaultNudgesService = mock(); + + await TestBed.configureTestingModule({ + imports: [NewItemNudgeComponent, CommonModule], + providers: [ + { provide: I18nService, useValue: i18nService }, + { provide: AccountService, useValue: accountService }, + { provide: VaultNudgesService, useValue: vaultNudgesService }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NewItemNudgeComponent); + component = fixture.componentInstance; + component.configType = null; // Set to null for initial state + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should set nudge title and body for CipherType.Login type", async () => { + component.configType = CipherType.Login; + accountService.activeAccount$ = of({ id: "test-user-id" as UserId } as Account); + jest.spyOn(component, "checkHasSpotlightDismissed").mockResolvedValue(true); + + await component.ngOnInit(); + + expect(component.showNewItemSpotlight).toBe(true); + expect(component.nudgeTitle).toBe("newLoginNudgeTitle"); + expect(component.nudgeBody).toBe("newLoginNudgeBody"); + expect(component.dismissalNudgeType).toBe(VaultNudgeType.newLoginItemStatus); + }); + + it("should set nudge title and body for CipherType.Card type", async () => { + component.configType = CipherType.Card; + accountService.activeAccount$ = of({ id: "test-user-id" as UserId } as Account); + jest.spyOn(component, "checkHasSpotlightDismissed").mockResolvedValue(true); + + await component.ngOnInit(); + + expect(component.showNewItemSpotlight).toBe(true); + expect(component.nudgeTitle).toBe("newCardNudgeTitle"); + expect(component.nudgeBody).toBe("newCardNudgeBody"); + expect(component.dismissalNudgeType).toBe(VaultNudgeType.newCardItemStatus); + }); + + it("should not show anything if spotlight has been dismissed", async () => { + component.configType = CipherType.Identity; + accountService.activeAccount$ = of({ id: "test-user-id" as UserId } as Account); + jest.spyOn(component, "checkHasSpotlightDismissed").mockResolvedValue(false); + + await component.ngOnInit(); + + expect(component.showNewItemSpotlight).toBe(false); + expect(component.dismissalNudgeType).toBe(VaultNudgeType.newIdentityItemStatus); + }); + + it("should set showNewItemSpotlight to false when user dismisses spotlight", async () => { + component.showNewItemSpotlight = true; + component.dismissalNudgeType = VaultNudgeType.newLoginItemStatus; + component.activeUserId = "test-user-id" as UserId; + + const dismissSpy = jest.spyOn(vaultNudgesService, "dismissNudge").mockResolvedValue(); + + await component.dismissNewItemSpotlight(); + + expect(component.showNewItemSpotlight).toBe(false); + expect(dismissSpy).toHaveBeenCalledWith( + VaultNudgeType.newLoginItemStatus, + component.activeUserId, + ); + }); +}); diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts new file mode 100644 index 00000000000..b497585d4fb --- /dev/null +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts @@ -0,0 +1,90 @@ +import { NgIf } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherType } from "@bitwarden/sdk-internal"; + +import { SpotlightComponent } from "../../../components/spotlight/spotlight.component"; +import { VaultNudgesService, VaultNudgeType } from "../../../services/vault-nudges.service"; + +@Component({ + selector: "vault-new-item-nudge", + templateUrl: "./new-item-nudge.component.html", + standalone: true, + imports: [NgIf, SpotlightComponent], +}) +export class NewItemNudgeComponent implements OnInit { + @Input({ required: true }) configType: CipherType | null = null; + activeUserId: UserId | null = null; + showNewItemSpotlight: boolean = false; + nudgeTitle: string = ""; + nudgeBody: string = ""; + dismissalNudgeType: VaultNudgeType | null = null; + + constructor( + private i18nService: I18nService, + private accountService: AccountService, + private vaultNudgesService: VaultNudgesService, + ) {} + + async ngOnInit() { + this.activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + switch (this.configType) { + case CipherType.Login: + this.dismissalNudgeType = VaultNudgeType.newLoginItemStatus; + this.nudgeTitle = this.i18nService.t("newLoginNudgeTitle"); + this.nudgeBody = this.i18nService.t("newLoginNudgeBody"); + break; + + case CipherType.Card: + this.dismissalNudgeType = VaultNudgeType.newCardItemStatus; + this.nudgeTitle = this.i18nService.t("newCardNudgeTitle"); + this.nudgeBody = this.i18nService.t("newCardNudgeBody"); + break; + + case CipherType.Identity: + this.dismissalNudgeType = VaultNudgeType.newIdentityItemStatus; + this.nudgeTitle = this.i18nService.t("newIdentityNudgeTitle"); + this.nudgeBody = this.i18nService.t("newIdentityNudgeBody"); + break; + + case CipherType.SecureNote: + this.dismissalNudgeType = VaultNudgeType.newNoteItemStatus; + this.nudgeTitle = this.i18nService.t("newNoteNudgeTitle"); + this.nudgeBody = this.i18nService.t("newNoteNudgeBody"); + break; + + case CipherType.SshKey: + this.dismissalNudgeType = VaultNudgeType.newSshItemStatus; + this.nudgeTitle = this.i18nService.t("newSshNudgeTitle"); + this.nudgeBody = this.i18nService.t("newSshNudgeBody"); + break; + default: + throw new Error("Unsupported cipher type"); + } + this.showNewItemSpotlight = await this.checkHasSpotlightDismissed( + this.dismissalNudgeType as VaultNudgeType, + this.activeUserId, + ); + } + + async dismissNewItemSpotlight() { + if (this.dismissalNudgeType && this.activeUserId) { + await this.vaultNudgesService.dismissNudge( + this.dismissalNudgeType, + this.activeUserId as UserId, + ); + this.showNewItemSpotlight = false; + } + } + + async checkHasSpotlightDismissed(nudgeType: VaultNudgeType, userId: UserId): Promise { + return !(await firstValueFrom(this.vaultNudgesService.showNudge$(nudgeType, userId))) + .hasSpotlightDismissed; + } +} 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 index 0c14cff002f..c9077a7283b 100644 --- a/libs/vault/src/services/custom-nudges-services/has-nudge.service.ts +++ b/libs/vault/src/services/custom-nudges-services/has-nudge.service.ts @@ -18,8 +18,6 @@ export class HasNudgeService extends DefaultSingleNudgeService { private nudgeTypes: VaultNudgeType[] = [ VaultNudgeType.EmptyVaultNudge, - VaultNudgeType.HasVaultItems, - VaultNudgeType.IntroCarouselDismissal, // add additional nudge types here as needed ]; diff --git a/libs/vault/src/services/custom-nudges-services/index.ts b/libs/vault/src/services/custom-nudges-services/index.ts index 9a1f0acd420..131db023175 100644 --- a/libs/vault/src/services/custom-nudges-services/index.ts +++ b/libs/vault/src/services/custom-nudges-services/index.ts @@ -1,3 +1,4 @@ export * from "./has-items-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/custom-nudges-services/new-item-nudge.service.ts b/libs/vault/src/services/custom-nudges-services/new-item-nudge.service.ts new file mode 100644 index 00000000000..93ef5d81dc4 --- /dev/null +++ b/libs/vault/src/services/custom-nudges-services/new-item-nudge.service.ts @@ -0,0 +1,65 @@ +import { inject, Injectable } from "@angular/core"; +import { combineLatest, Observable, switchMap } from "rxjs"; + +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; + +import { DefaultSingleNudgeService } from "../default-single-nudge.service"; +import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service"; + +/** + * Custom Nudge Service Checking Nudge Status For Vault New Item Types + */ +@Injectable({ + providedIn: "root", +}) +export class NewItemNudgeService extends DefaultSingleNudgeService { + cipherService = inject(CipherService); + + nudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable { + return combineLatest([ + this.getNudgeStatus$(nudgeType, userId), + this.cipherService.cipherViews$(userId), + ]).pipe( + switchMap(async ([nudgeStatus, ciphers]) => { + if (nudgeStatus.hasSpotlightDismissed) { + return nudgeStatus; + } + + let currentType: CipherType; + + switch (nudgeType) { + case VaultNudgeType.newLoginItemStatus: + currentType = CipherType.Login; + break; + case VaultNudgeType.newCardItemStatus: + currentType = CipherType.Card; + break; + case VaultNudgeType.newIdentityItemStatus: + currentType = CipherType.Identity; + break; + case VaultNudgeType.newNoteItemStatus: + currentType = CipherType.SecureNote; + break; + case VaultNudgeType.newSshItemStatus: + currentType = CipherType.SshKey; + break; + } + + const ciphersBoolean = ciphers.some((cipher) => cipher.type === currentType); + + if (ciphersBoolean) { + const dismissedStatus = { + hasSpotlightDismissed: true, + hasBadgeDismissed: true, + }; + await this.setNudgeStatus(nudgeType, dismissedStatus, userId); + return dismissedStatus; + } + + return nudgeStatus; + }), + ); + } +} diff --git a/libs/vault/src/services/vault-nudges.service.spec.ts b/libs/vault/src/services/vault-nudges.service.spec.ts index a01cac94fb1..69ddf1cdaa0 100644 --- a/libs/vault/src/services/vault-nudges.service.spec.ts +++ b/libs/vault/src/services/vault-nudges.service.spec.ts @@ -5,6 +5,7 @@ import { firstValueFrom, of } from "rxjs"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FakeStateProvider, mockAccountServiceWith } from "../../../common/spec"; @@ -46,6 +47,7 @@ describe("Vault Nudges Service", () => { provide: EmptyVaultNudgeService, useValue: mock(), }, + { provide: CipherService, useValue: mock() }, ], }); }); diff --git a/libs/vault/src/services/vault-nudges.service.ts b/libs/vault/src/services/vault-nudges.service.ts index 28198d17068..98f28af9954 100644 --- a/libs/vault/src/services/vault-nudges.service.ts +++ b/libs/vault/src/services/vault-nudges.service.ts @@ -6,7 +6,11 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { UserKeyDefinition, VAULT_NUDGES_DISK } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { HasItemsNudgeService, EmptyVaultNudgeService } from "./custom-nudges-services"; +import { + HasItemsNudgeService, + EmptyVaultNudgeService, + NewItemNudgeService, +} from "./custom-nudges-services"; import { DefaultSingleNudgeService, SingleNudgeService } from "./default-single-nudge.service"; export type NudgeStatus = { @@ -23,7 +27,11 @@ export enum VaultNudgeType { */ EmptyVaultNudge = "empty-vault-nudge", HasVaultItems = "has-vault-items", - IntroCarouselDismissal = "intro-carousel-dismissal", + newLoginItemStatus = "new-login-item-status", + newCardItemStatus = "new-card-item-status", + newIdentityItemStatus = "new-identity-item-status", + newNoteItemStatus = "new-note-item-status", + newSshItemStatus = "new-ssh-item-status", } export const VAULT_NUDGE_DISMISSED_DISK_KEY = new UserKeyDefinition< @@ -37,6 +45,8 @@ export const VAULT_NUDGE_DISMISSED_DISK_KEY = new UserKeyDefinition< providedIn: "root", }) export class VaultNudgesService { + private newItemNudgeService = inject(NewItemNudgeService); + /** * Custom nudge services to use for specific nudge types * Each nudge type can have its own service to determine when to show the nudge @@ -45,6 +55,11 @@ export class VaultNudgesService { private customNudgeServices: any = { [VaultNudgeType.HasVaultItems]: inject(HasItemsNudgeService), [VaultNudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService), + [VaultNudgeType.newLoginItemStatus]: this.newItemNudgeService, + [VaultNudgeType.newCardItemStatus]: this.newItemNudgeService, + [VaultNudgeType.newIdentityItemStatus]: this.newItemNudgeService, + [VaultNudgeType.newNoteItemStatus]: this.newItemNudgeService, + [VaultNudgeType.newSshItemStatus]: this.newItemNudgeService, }; /**