From ef27670f5c027be87d279b9b32553e980e55d888 Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Wed, 24 Dec 2025 15:15:46 +0200
Subject: feat: show bookmark owner icon in shared lists (#2277)

* feat: Add owner icon to bookmarks in shared lists

Display a small icon showing the bookmark owner's name and email on hover when viewing bookmarks from other users in shared lists. The icon appears in the top-right corner of bookmark cards across all layout types (grid, list, compact).

Changes:
- Add user field to ZBookmark type to include owner name and email
- Update bookmark queries to fetch user information via join
- Create BookmarkOwnerIcon component with tooltip showing owner details
- Integrate owner indicator into BookmarkLayoutAdaptingCard for all layouts
- Only show icon for bookmarks not owned by current user

* use icons in more places

* remove tooltip providers

* fix non list context

---------

Co-authored-by: Claude <noreply@anthropic.com>
---
 apps/web/components/dashboard/lists/ListHeader.tsx | 64 ++++++++++++++++------
 1 file changed, 48 insertions(+), 16 deletions(-)

(limited to 'apps/web/components/dashboard/lists/ListHeader.tsx')

diff --git a/apps/web/components/dashboard/lists/ListHeader.tsx b/apps/web/components/dashboard/lists/ListHeader.tsx
index 8e014e2a..ecbb6431 100644
--- a/apps/web/components/dashboard/lists/ListHeader.tsx
+++ b/apps/web/components/dashboard/lists/ListHeader.tsx
@@ -6,11 +6,11 @@ import { Button } from "@/components/ui/button";
 import {
   Tooltip,
   TooltipContent,
-  TooltipProvider,
   TooltipTrigger,
 } from "@/components/ui/tooltip";
+import { UserAvatar } from "@/components/ui/user-avatar";
 import { useTranslation } from "@/lib/i18n/client";
-import { MoreHorizontal, SearchIcon, Users } from "lucide-react";
+import { MoreHorizontal, SearchIcon } from "lucide-react";
 
 import { api } from "@karakeep/shared-react/trpc";
 import { parseSearchQuery } from "@karakeep/shared/searchQueryParser";
@@ -35,6 +35,16 @@ export default function ListHeader({
     },
   );
 
+  const { data: collaboratorsData } = api.lists.getCollaborators.useQuery(
+    {
+      listId: initialData.id,
+    },
+    {
+      refetchOnWindowFocus: false,
+      enabled: list.hasCollaborators,
+    },
+  );
+
   const parsedQuery = useMemo(() => {
     if (!list.query) {
       return null;
@@ -55,22 +65,44 @@ export default function ListHeader({
         <span className="text-2xl">
           {list.icon} {list.name}
         </span>
-        {list.hasCollaborators && (
-          <TooltipProvider>
-            <Tooltip>
-              <TooltipTrigger asChild>
-                <Users className="size-5 text-primary" />
-              </TooltipTrigger>
-              <TooltipContent>
-                <p>{t("lists.shared")}</p>
-              </TooltipContent>
-            </Tooltip>
-          </TooltipProvider>
+        {list.hasCollaborators && collaboratorsData && (
+          <div className="group flex">
+            {collaboratorsData.owner && (
+              <Tooltip>
+                <TooltipTrigger>
+                  <div className="-mr-2 transition-all duration-300 ease-out group-hover:mr-1">
+                    <UserAvatar
+                      name={collaboratorsData.owner.name}
+                      image={collaboratorsData.owner.image}
+                      className="size-5 shrink-0 rounded-full ring-2 ring-background"
+                    />
+                  </div>
+                </TooltipTrigger>
+                <TooltipContent>
+                  <p>{collaboratorsData.owner.name}</p>
+                </TooltipContent>
+              </Tooltip>
+            )}
+            {collaboratorsData.collaborators.map((collab) => (
+              <Tooltip key={collab.userId}>
+                <TooltipTrigger>
+                  <div className="-mr-2 transition-all duration-300 ease-out group-hover:mr-1">
+                    <UserAvatar
+                      name={collab.user.name}
+                      image={collab.user.image}
+                      className="size-5 shrink-0 rounded-full ring-2 ring-background"
+                    />
+                  </div>
+                </TooltipTrigger>
+                <TooltipContent>
+                  <p>{collab.user.name}</p>
+                </TooltipContent>
+              </Tooltip>
+            ))}
+          </div>
         )}
         {list.description && (
-          <span className="text-lg text-gray-400">
-            {`(${list.description})`}
-          </span>
+          <span className="text-lg text-gray-400">{`(${list.description})`}</span>
         )}
       </div>
       <div className="flex items-center">
-- 
cgit v1.2.3-70-g09d2

