diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts
index 46ebc331f41..7fb3729e789 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts
@@ -18,6 +18,7 @@ import {
import { I18nPipe } from "@bitwarden/ui-common";
import { OrganizationUserView } from "../../../core";
+import { AvatarIdPipe } from "../../pipes/avatar-id.pipe";
export type BulkEnableSecretsManagerDialogData = {
orgId: string;
@@ -31,6 +32,7 @@ export type BulkEnableSecretsManagerDialogData = {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncActionsModule,
+ AvatarIdPipe,
AvatarModule,
ButtonModule,
DialogModule,
diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-reinvite-failure-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-reinvite-failure-dialog.component.html
index 477bb95617e..b0bb3d0eb19 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-reinvite-failure-dialog.component.html
+++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-reinvite-failure-dialog.component.html
@@ -32,7 +32,7 @@
diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.html
index c08293889fc..65dde643d5a 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.html
+++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.html
@@ -37,7 +37,7 @@
|
@@ -81,7 +81,7 @@
|
diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts
index b7d2c58d147..fd3b801de99 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts
@@ -20,6 +20,8 @@ import {
} from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
+import { AvatarIdPipe } from "../../pipes/avatar-id.pipe";
+
import { BaseBulkRemoveComponent } from "./base-bulk-remove.component";
import { BulkUserDetails } from "./bulk-status.component";
@@ -35,6 +37,7 @@ type BulkRemoveDialogParams = {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncActionsModule,
+ AvatarIdPipe,
AvatarModule,
ButtonModule,
CalloutModule,
diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html
index 4a7d8556548..cc27e760695 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html
+++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html
@@ -44,7 +44,7 @@
@@ -97,7 +97,7 @@
diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts
index 68a0c0b657f..f180ab71626 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts
@@ -26,6 +26,8 @@ import {
} from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
+import { AvatarIdPipe } from "../../pipes/avatar-id.pipe";
+
import { BulkUserDetails } from "./bulk-status.component";
type BulkRestoreDialogParams = {
@@ -41,6 +43,7 @@ type BulkRestoreDialogParams = {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncActionsModule,
+ AvatarIdPipe,
AvatarModule,
ButtonModule,
CalloutModule,
diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.html b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.html
index 145f829ba7a..5efbd35e9a5 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.html
+++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.html
@@ -20,7 +20,7 @@
|
diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts
index 67a3250e3ab..f31f9201df9 100644
--- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts
@@ -23,6 +23,7 @@ import {
import { I18nPipe } from "@bitwarden/ui-common";
import { OrganizationUserView } from "../../../core/views/organization-user.view";
+import { AvatarIdPipe } from "../../pipes/avatar-id.pipe";
export interface BulkUserDetails {
id: string;
@@ -59,6 +60,7 @@ type BulkStatusDialogData = {
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
+ AvatarIdPipe,
AvatarModule,
ButtonModule,
CalloutModule,
diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.html b/apps/web/src/app/admin-console/organizations/members/members.component.html
index f96943e9abb..87e705e4018 100644
--- a/apps/web/src/app/admin-console/organizations/members/members.component.html
+++ b/apps/web/src/app/admin-console/organizations/members/members.component.html
@@ -222,7 +222,7 @@
@@ -283,7 +283,7 @@
diff --git a/apps/web/src/app/admin-console/organizations/members/members.module.ts b/apps/web/src/app/admin-console/organizations/members/members.module.ts
index 371c7fc716e..e0502aeeb2d 100644
--- a/apps/web/src/app/admin-console/organizations/members/members.module.ts
+++ b/apps/web/src/app/admin-console/organizations/members/members.module.ts
@@ -27,7 +27,7 @@ import { InviteMembersDialogComponent } from "./components/invite-members-dialog
import { UserDialogModule } from "./components/member-dialog";
import { MembersRoutingModule } from "./members-routing.module";
import { MembersComponent } from "./members.component";
-import { UserStatusPipe } from "./pipes";
+import { AvatarIdPipe, UserStatusPipe } from "./pipes";
import {
OrganizationMembersService,
MemberActionsService,
@@ -50,6 +50,7 @@ import {
IconModule,
BerryComponent,
TooltipDirective,
+ AvatarIdPipe,
BulkConfirmDialogComponent,
BulkDeleteDialogComponent,
BulkEnableSecretsManagerDialogComponent,
diff --git a/apps/web/src/app/admin-console/organizations/members/pipes/avatar-id.pipe.spec.ts b/apps/web/src/app/admin-console/organizations/members/pipes/avatar-id.pipe.spec.ts
new file mode 100644
index 00000000000..a278c993382
--- /dev/null
+++ b/apps/web/src/app/admin-console/organizations/members/pipes/avatar-id.pipe.spec.ts
@@ -0,0 +1,17 @@
+import { AvatarIdPipe } from "./avatar-id.pipe";
+
+describe("AvatarIdPipe", () => {
+ let pipe: AvatarIdPipe;
+
+ beforeEach(() => {
+ pipe = new AvatarIdPipe();
+ });
+
+ it("prefers the account id (userId) when present", () => {
+ expect(pipe.transform({ id: "org-user-id", userId: "account-id" })).toBe("account-id");
+ });
+
+ it("falls back to the org/provider user id when userId is not provided", () => {
+ expect(pipe.transform({ id: "org-user-id" })).toBe("org-user-id");
+ });
+});
diff --git a/apps/web/src/app/admin-console/organizations/members/pipes/avatar-id.pipe.ts b/apps/web/src/app/admin-console/organizations/members/pipes/avatar-id.pipe.ts
new file mode 100644
index 00000000000..1009b0ea47d
--- /dev/null
+++ b/apps/web/src/app/admin-console/organizations/members/pipes/avatar-id.pipe.ts
@@ -0,0 +1,13 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+import { AvatarIdentifiable, resolveAvatarId } from "../utils/resolve-avatar-id";
+
+/** Resolves a member's `bit-avatar` id. See {@link resolveAvatarId}. */
+@Pipe({
+ name: "avatarId",
+})
+export class AvatarIdPipe implements PipeTransform {
+ transform(user: AvatarIdentifiable): string {
+ return resolveAvatarId(user);
+ }
+}
diff --git a/apps/web/src/app/admin-console/organizations/members/pipes/index.ts b/apps/web/src/app/admin-console/organizations/members/pipes/index.ts
index 67c485ed361..98909f7ee5a 100644
--- a/apps/web/src/app/admin-console/organizations/members/pipes/index.ts
+++ b/apps/web/src/app/admin-console/organizations/members/pipes/index.ts
@@ -1 +1,2 @@
+export * from "./avatar-id.pipe";
export * from "./user-status.pipe";
diff --git a/apps/web/src/app/admin-console/organizations/members/utils/resolve-avatar-id.spec.ts b/apps/web/src/app/admin-console/organizations/members/utils/resolve-avatar-id.spec.ts
new file mode 100644
index 00000000000..daa769afb76
--- /dev/null
+++ b/apps/web/src/app/admin-console/organizations/members/utils/resolve-avatar-id.spec.ts
@@ -0,0 +1,33 @@
+import { resolveAvatarId } from "./resolve-avatar-id";
+
+describe("resolveAvatarId", () => {
+ it("prefers the account id (userId) when present", () => {
+ expect(resolveAvatarId({ id: "org-user-id", userId: "account-id" })).toBe("account-id");
+ });
+
+ it("falls back to the org/provider user id when userId is undefined", () => {
+ expect(resolveAvatarId({ id: "org-user-id", userId: undefined })).toBe("org-user-id");
+ });
+
+ it("falls back to the org/provider user id when userId is not provided", () => {
+ expect(resolveAvatarId({ id: "org-user-id" })).toBe("org-user-id");
+ });
+
+ it("falls back to the org/provider user id when userId is null", () => {
+ const user: { id: string; userId?: string } = { id: "org-user-id", userId: null as any };
+ expect(resolveAvatarId(user)).toBe("org-user-id");
+ });
+
+ it("returns the same id for the same member regardless of which caller resolves it", () => {
+ // Regression test: the members list and every bulk action dialog must resolve the same
+ // avatar identifier for the same member, otherwise the same person shows a different
+ // avatar color in different parts of the UI.
+ const invitedMember: { id: string; userId?: string } = { id: "org-user-id", userId: undefined };
+
+ const idAsRenderedByMembersList = resolveAvatarId(invitedMember);
+ const idAsRenderedByBulkDialog = resolveAvatarId(invitedMember);
+
+ expect(idAsRenderedByMembersList).toBe(idAsRenderedByBulkDialog);
+ expect(idAsRenderedByMembersList).toBe("org-user-id");
+ });
+});
diff --git a/apps/web/src/app/admin-console/organizations/members/utils/resolve-avatar-id.ts b/apps/web/src/app/admin-console/organizations/members/utils/resolve-avatar-id.ts
new file mode 100644
index 00000000000..54d90f74035
--- /dev/null
+++ b/apps/web/src/app/admin-console/organizations/members/utils/resolve-avatar-id.ts
@@ -0,0 +1,12 @@
+export interface AvatarIdentifiable {
+ id: string;
+ userId?: string;
+}
+
+/**
+ * Resolves a consistent avatar id for a member, preferring their account id (`userId`) and
+ * falling back to the org/provider user id (e.g. for invited members with no linked account).
+ */
+export function resolveAvatarId(user: AvatarIdentifiable): string {
+ return user.userId ?? user.id;
+}
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts
index 181089fd487..52f5d4882c0 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts
@@ -33,6 +33,7 @@ import {
import { I18nPipe } from "@bitwarden/ui-common";
import { BaseBulkConfirmComponent } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component";
import { BulkUserDetails } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/bulk-status.component";
+import { AvatarIdPipe } from "@bitwarden/web-vault/app/admin-console/organizations/members/pipes/avatar-id.pipe";
type BulkConfirmDialogParams = {
providerId: string;
@@ -47,6 +48,7 @@ type BulkConfirmDialogParams = {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncActionsModule,
+ AvatarIdPipe,
AvatarModule,
ButtonModule,
CalloutModule,
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts
index 9e21f14579d..9cb0be4442e 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts
@@ -19,6 +19,7 @@ import {
import { I18nPipe } from "@bitwarden/ui-common";
import { BaseBulkRemoveComponent } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/base-bulk-remove.component";
import { BulkUserDetails } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/bulk-status.component";
+import { AvatarIdPipe } from "@bitwarden/web-vault/app/admin-console/organizations/members/pipes/avatar-id.pipe";
type BulkRemoveDialogParams = {
providerId: string;
@@ -33,6 +34,7 @@ type BulkRemoveDialogParams = {
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncActionsModule,
+ AvatarIdPipe,
AvatarModule,
ButtonModule,
CalloutModule,