From 18d47a29df627aba02ac598af9bb8f34f7e3e0cb Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:18:10 -0700 Subject: [PATCH] [CL-420][PM-18798] - Berry component and tab navigation (#14135) * berry component and nav slot * remove debug * don't worry about routes * add announce and tests * fix story * use existing notification color. fix border radius * fix berry component class * finalize berry component * fix tests * fix story * move logic to tabs-v2 component. * move navButtons to tabs-v2.component * fix layout * move story. * cleanup --- apps/browser/src/_locales/en/messages.json | 13 +++++ .../popup/layout/popup-layout.stories.ts | 58 +++++++++++++++++-- .../popup-tab-navigation.component.html | 6 +- .../layout/popup-tab-navigation.component.ts | 45 ++++++-------- apps/browser/src/popup/tabs-v2.component.html | 3 + apps/browser/src/popup/tabs-v2.component.ts | 54 +++++++++++++++-- libs/vault/src/index.ts | 1 + .../has-nudge.service.ts | 47 +++++++++++++++ .../services/custom-nudges-services/index.ts | 1 + 9 files changed, 189 insertions(+), 39 deletions(-) create mode 100644 apps/browser/src/popup/tabs-v2.component.html create mode 100644 libs/vault/src/services/custom-nudges-services/has-nudge.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index b0f3fb5d19..73c64ba7fd 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1059,6 +1059,19 @@ "notificationAddSave": { "message": "Save" }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", + "placeholders": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, "loginSaveSuccessDetails": { "message": "$USERNAME$ saved to Bitwarden.", "placeholders": { diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index 79412e6bce..a4c6b89415 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -340,6 +340,7 @@ export default { generator: "Generator", send: "Send", settings: "Settings", + labelWithNotification: (label: string) => `${label}: New Notification`, }); }, }, @@ -398,17 +399,64 @@ export default { type Story = StoryObj; -export const PopupTabNavigation: Story = { +type PopupTabNavigationStory = StoryObj; + +const navButtons = (showBerry = false) => [ + { + 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: showBerry, + }, +]; + +export const DefaultPopupTabNavigation: PopupTabNavigationStory = { render: (args) => ({ props: args, - template: /* HTML */ ` + template: /*html*/ ` - + - - `, + `, }), + args: { + navButtons: navButtons(), + }, +}; + +export const PopupTabNavigationWithBerry: PopupTabNavigationStory = { + render: (args) => ({ + props: args, + template: /*html*/ ` + + + + + `, + }), + args: { + navButtons: navButtons(true), + }, }; export const PopupPage: Story = { diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html index bed4eac3f9..27b546738c 100644 --- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html +++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html @@ -5,12 +5,13 @@
diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts index e01b4efd71..f4b82dc56f 100644 --- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts @@ -1,10 +1,19 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LinkModule } from "@bitwarden/components"; +export type NavButton = { + label: string; + page: string; + iconKey: string; + iconKeyActive: string; + showBerry?: boolean; +}; + @Component({ selector: "popup-tab-navigation", templateUrl: "popup-tab-navigation.component.html", @@ -15,30 +24,12 @@ import { LinkModule } from "@bitwarden/components"; }, }) export class PopupTabNavigationComponent { - navButtons = [ - { - 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", - }, - ]; + @Input() navButtons: NavButton[] = []; + + constructor(private i18nService: I18nService) {} + + buttonTitle(navButton: NavButton) { + const labelText = this.i18nService.t(navButton.label); + return navButton.showBerry ? this.i18nService.t("labelWithNotification", labelText) : labelText; + } } diff --git a/apps/browser/src/popup/tabs-v2.component.html b/apps/browser/src/popup/tabs-v2.component.html new file mode 100644 index 0000000000..bde3aaa3d3 --- /dev/null +++ b/apps/browser/src/popup/tabs-v2.component.html @@ -0,0 +1,3 @@ + + + diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 4cdb8fc029..1392dc565a 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -1,11 +1,53 @@ import { Component } from "@angular/core"; +import { combineLatest, map } from "rxjs"; + +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { HasNudgeService } from "@bitwarden/vault"; @Component({ selector: "app-tabs-v2", - template: ` - - - - `, + templateUrl: "./tabs-v2.component.html", + providers: [HasNudgeService], }) -export class TabsV2Component {} +export class TabsV2Component { + constructor( + private readonly hasNudgeService: HasNudgeService, + private readonly configService: ConfigService, + ) {} + + protected navButtons$ = combineLatest([ + this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), + this.hasNudgeService.shouldShowNudge$(), + ]).pipe( + map(([onboardingFeatureEnabled, showNudge]) => { + 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 && showNudge, + }, + ]; + }), + ); +} diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index f3658046a3..655b536de6 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -24,6 +24,7 @@ export * from "./components/carousel"; export * as VaultIcons from "./icons"; export * from "./services/vault-nudges.service"; +export * from "./services/custom-nudges-services"; export { DefaultSshImportPromptService } from "./services/default-ssh-import-prompt.service"; export { SshImportPromptService } from "./services/ssh-import-prompt.service"; 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 new file mode 100644 index 0000000000..b1f319451e --- /dev/null +++ b/libs/vault/src/services/custom-nudges-services/has-nudge.service.ts @@ -0,0 +1,47 @@ +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 { 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.HasVaultItems, + VaultNudgeType.IntroCarouselDismissal, + // add additional nudge types here as needed + ]; + + /** + * Returns an observable that emits true if any of the provided nudge types are present + */ + shouldShowNudge$(): Observable { + return this.accountService.activeAccount$.pipe( + switchMap((activeAccount) => { + const userId: UserId | undefined = activeAccount?.id; + if (!userId) { + return of(false); + } + + const nudgeObservables: Observable[] = this.nudgeTypes.map((nudge) => + super.shouldShowNudge$(nudge, userId), + ); + + return combineLatest(nudgeObservables).pipe( + map((nudgeStates) => nudgeStates.some((state) => state)), + distinctUntilChanged(), + ); + }), + ); + } +} diff --git a/libs/vault/src/services/custom-nudges-services/index.ts b/libs/vault/src/services/custom-nudges-services/index.ts index 84409eb35a..dd343e47d7 100644 --- a/libs/vault/src/services/custom-nudges-services/index.ts +++ b/libs/vault/src/services/custom-nudges-services/index.ts @@ -1 +1,2 @@ export * from "./has-items-nudge.service"; +export * from "./has-nudge.service";