From 73f31fd828b43f0b3a2e9ad2ca419ed49c10c4d5 Mon Sep 17 00:00:00 2001 From: William Martin Date: Tue, 25 Nov 2025 12:42:12 -0500 Subject: [PATCH] refactor: update web header to use bit-header component - Refactor web-header to use new bit-header component from libs/components - Extract account menu to separate component - Update header module to import HeaderComponent --- .../header/account-menu.component.html | 62 +++++++++ .../layouts/header/account-menu.component.ts | 50 +++++++ .../src/app/layouts/header/header.module.ts | 9 +- .../layouts/header/web-header.component.html | 124 +++--------------- .../layouts/header/web-header.component.ts | 83 ++++-------- 5 files changed, 158 insertions(+), 170 deletions(-) create mode 100644 apps/web/src/app/layouts/header/account-menu.component.html create mode 100644 apps/web/src/app/layouts/header/account-menu.component.ts diff --git a/apps/web/src/app/layouts/header/account-menu.component.html b/apps/web/src/app/layouts/header/account-menu.component.html new file mode 100644 index 00000000000..a1c5571ee16 --- /dev/null +++ b/apps/web/src/app/layouts/header/account-menu.component.html @@ -0,0 +1,62 @@ +@if (account()) { + + + +
+
+ +
+ {{ "loggedInAs" | i18n }} + + {{ account() | userName }} + +
+
+ + @if (selfHosted) { + + + {{ hostname }} + + } + + + + + + {{ "accountSettings" | i18n }} + + + + {{ "getHelp" | i18n }} + + + + {{ "getApps" | i18n }} + + + + + @if (canLock$ | async) { + + } + + +
+
+} diff --git a/apps/web/src/app/layouts/header/account-menu.component.ts b/apps/web/src/app/layouts/header/account-menu.component.ts new file mode 100644 index 00000000000..efaf078616b --- /dev/null +++ b/apps/web/src/app/layouts/header/account-menu.component.ts @@ -0,0 +1,50 @@ +import { ChangeDetectionStrategy, Component, inject } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { map, Observable } from "rxjs"; + +import { LockService, LogoutService } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { + VaultTimeoutAction, + VaultTimeoutSettingsService, +} from "@bitwarden/common/key-management/vault-timeout"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component"; +import { SharedModule } from "../../shared"; + +@Component({ + selector: "app-account-menu", + templateUrl: "./account-menu.component.html", + imports: [SharedModule, DynamicAvatarComponent], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AccountMenuComponent { + private readonly platformUtilsService = inject(PlatformUtilsService); + private readonly vaultTimeoutSettingsService = inject(VaultTimeoutSettingsService); + private readonly accountService = inject(AccountService); + private readonly logoutService = inject(LogoutService); + private readonly lockService = inject(LockService); + + protected readonly account = toSignal(this.accountService.activeAccount$); + + protected readonly canLock$: Observable = this.vaultTimeoutSettingsService + .availableVaultTimeoutActions$() + .pipe(map((actions) => actions.includes(VaultTimeoutAction.Lock))); + protected readonly selfHosted = this.platformUtilsService.isSelfHost(); + protected readonly hostname = globalThis.location.hostname; + + protected async lock() { + const userId = this.account()?.id; + if (userId) { + await this.lockService.lock(userId); + } + } + + protected async logout() { + const userId = this.account()?.id; + if (userId) { + await this.logoutService.logout(userId); + } + } +} diff --git a/apps/web/src/app/layouts/header/header.module.ts b/apps/web/src/app/layouts/header/header.module.ts index 7518c83a885..cd21c65f6ef 100644 --- a/apps/web/src/app/layouts/header/header.module.ts +++ b/apps/web/src/app/layouts/header/header.module.ts @@ -1,16 +1,9 @@ import { NgModule } from "@angular/core"; -import { BannerModule } from "@bitwarden/components"; - -import { DynamicAvatarComponent } from "../../components/dynamic-avatar.component"; -import { SharedModule } from "../../shared"; -import { ProductSwitcherModule } from "../product-switcher/product-switcher.module"; - import { WebHeaderComponent } from "./web-header.component"; @NgModule({ - imports: [SharedModule, DynamicAvatarComponent, ProductSwitcherModule, BannerModule], - declarations: [WebHeaderComponent], + imports: [WebHeaderComponent], exports: [WebHeaderComponent], }) export class HeaderModule {} diff --git a/apps/web/src/app/layouts/header/web-header.component.html b/apps/web/src/app/layouts/header/web-header.component.html index 4b833e771dd..97e2a1e3706 100644 --- a/apps/web/src/app/layouts/header/web-header.component.html +++ b/apps/web/src/app/layouts/header/web-header.component.html @@ -1,110 +1,24 @@ -
-
-
+@let routeData = routeData$ | async; +@if (routeData) { + + -

-
- - {{ title || (routeData.titleId | i18n) }} -
-
-

-
-
-
- - - - + - -
-
- -
- {{ "loggedInAs" | i18n }} - - {{ account | userName }} - -
-
+ + + - - - - {{ hostname }} - - + + + - + + + - - - {{ "accountSettings" | i18n }} - - - - {{ "getHelp" | i18n }} - - - - {{ "getApps" | i18n }} - - - - - - -
-
- -
-
- -
-
-
-
- -
-
+ + + + +} diff --git a/apps/web/src/app/layouts/header/web-header.component.ts b/apps/web/src/app/layouts/header/web-header.component.ts index 694ee5c4ae9..5f06743c549 100644 --- a/apps/web/src/app/layouts/header/web-header.component.ts +++ b/apps/web/src/app/layouts/header/web-header.component.ts @@ -1,75 +1,44 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Input } from "@angular/core"; +import { ChangeDetectionStrategy, Component, inject, input } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { map, Observable } from "rxjs"; -import { User } from "@bitwarden/angular/pipes/user-name.pipe"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { - VaultTimeoutAction, - VaultTimeoutSettingsService, -} from "@bitwarden/common/key-management/vault-timeout"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { UserId } from "@bitwarden/common/types/guid"; +import { BannerModule, HeaderComponent } from "@bitwarden/components"; + +import { SharedModule } from "../../shared"; +import { ProductSwitcherModule } from "../product-switcher/product-switcher.module"; + +import { AccountMenuComponent } from "./account-menu.component"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-header", templateUrl: "./web-header.component.html", - standalone: false, + imports: [ + SharedModule, + ProductSwitcherModule, + BannerModule, + HeaderComponent, + AccountMenuComponent, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class WebHeaderComponent { + private route = inject(ActivatedRoute); + /** * Custom title that overrides the route data `titleId` */ - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() title: string; + readonly title = input(); /** * Icon to show before the title */ - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() icon: string; + readonly icon = input(); - protected routeData$: Observable<{ titleId: string }>; - protected account$: Observable; - protected canLock$: Observable; - protected selfHosted: boolean; - protected hostname = location.hostname; - - constructor( - private route: ActivatedRoute, - private platformUtilsService: PlatformUtilsService, - private vaultTimeoutSettingsService: VaultTimeoutSettingsService, - private messagingService: MessagingService, - private accountService: AccountService, - ) { - this.routeData$ = this.route.data.pipe( - map((params) => { - return { - titleId: params.titleId, - }; - }), - ); - - this.selfHosted = this.platformUtilsService.isSelfHost(); - - this.account$ = this.accountService.activeAccount$; - this.canLock$ = this.vaultTimeoutSettingsService - .availableVaultTimeoutActions$() - .pipe(map((actions) => actions.includes(VaultTimeoutAction.Lock))); - } - - protected lock() { - this.messagingService.send("lockVault"); - } - - protected logout() { - this.messagingService.send("logout"); - } + protected routeData$: Observable<{ titleId: string }> = this.route.data.pipe( + map((params) => { + return { + titleId: params.titleId, + }; + }), + ); }