From 2619f4cfefdf9264a7f4c3a8741493323fdde901 Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Fri, 28 Nov 2025 15:39:57 +0000
Subject: fix: separate shared lists in the sidebar (#2180)

* fix: separate shared lists in the sidebar

* fix sub

* i18n
---
 apps/web/components/dashboard/sidebar/AllLists.tsx | 137 +++++++++++++++++++--
 1 file changed, 130 insertions(+), 7 deletions(-)

(limited to 'apps/web/components/dashboard/sidebar/AllLists.tsx')

diff --git a/apps/web/components/dashboard/sidebar/AllLists.tsx b/apps/web/components/dashboard/sidebar/AllLists.tsx
index 1cc3f3a5..f8b80cba 100644
--- a/apps/web/components/dashboard/sidebar/AllLists.tsx
+++ b/apps/web/components/dashboard/sidebar/AllLists.tsx
@@ -1,16 +1,24 @@
 "use client";
 
-import { useCallback, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
 import Link from "next/link";
 import { usePathname } from "next/navigation";
 import SidebarItem from "@/components/shared/sidebar/SidebarItem";
 import { Button } from "@/components/ui/button";
-import { CollapsibleTriggerTriangle } from "@/components/ui/collapsible";
+import {
+  Collapsible,
+  CollapsibleContent,
+  CollapsibleTriggerTriangle,
+} from "@/components/ui/collapsible";
 import { useTranslation } from "@/lib/i18n/client";
 import { cn } from "@/lib/utils";
 import { MoreHorizontal, Plus } from "lucide-react";
 
 import type { ZBookmarkList } from "@karakeep/shared/types/lists";
+import {
+  augmentBookmarkListsWithInitialData,
+  useBookmarkLists,
+} from "@karakeep/shared-react/hooks/lists";
 import { ZBookmarkListTreeNode } from "@karakeep/shared/utils/listUtils";
 
 import { CollapsibleBookmarkLists } from "../lists/CollapsibleBookmarkLists";
@@ -31,6 +39,36 @@ export default function AllLists({
 
   const [selectedListId, setSelectedListId] = useState<string | null>(null);
 
+  // Fetch live lists data
+  const { data: listsData } = useBookmarkLists(undefined, {
+    initialData: { lists: initialData.lists },
+  });
+  const lists = augmentBookmarkListsWithInitialData(
+    listsData,
+    initialData.lists,
+  );
+
+  // Check if any shared list is currently being viewed
+  const isViewingSharedList = useMemo(() => {
+    return lists.data.some(
+      (list) => list.userRole !== "owner" && pathName.includes(list.id),
+    );
+  }, [lists.data, pathName]);
+
+  // Check if there are any shared lists
+  const hasSharedLists = useMemo(() => {
+    return lists.data.some((list) => list.userRole !== "owner");
+  }, [lists.data]);
+
+  const [sharedListsOpen, setSharedListsOpen] = useState(isViewingSharedList);
+
+  // Auto-open shared lists if viewing one
+  useEffect(() => {
+    if (isViewingSharedList && !sharedListsOpen) {
+      setSharedListsOpen(true);
+    }
+  }, [isViewingSharedList, sharedListsOpen]);
+
   return (
     <ul className="max-h-full gap-y-2 overflow-auto text-sm">
       <li className="flex justify-between pb-3">
@@ -60,10 +98,13 @@ export default function AllLists({
         linkClassName="py-0.5"
         className="px-0.5"
       />
+
+      {/* Owned Lists */}
       <CollapsibleBookmarkLists
-        initialData={initialData.lists}
+        listsData={lists}
+        filter={(node) => node.item.userRole === "owner"}
         isOpenFunc={isNodeOpen}
-        render={({ item: node, level, open, numBookmarks }) => (
+        render={({ node, level, open, numBookmarks }) => (
           <SidebarItem
             collapseButton={
               node.children.length > 0 && (
@@ -83,8 +124,8 @@ export default function AllLists({
             className="group px-0.5"
             right={
               <ListOptions
-                onOpenChange={(open) => {
-                  if (open) {
+                onOpenChange={(isOpen) => {
+                  if (isOpen) {
                     setSelectedListId(node.item.id);
                   } else {
                     setSelectedListId(null);
@@ -101,7 +142,6 @@ export default function AllLists({
                         : "opacity-0",
                     )}
                   />
-
                   <span
                     className={cn(
                       "px-2.5 text-xs font-light text-muted-foreground opacity-100 transition-opacity duration-100 group-hover:opacity-0",
@@ -121,6 +161,89 @@ export default function AllLists({
           />
         )}
       />
+
+      {/* Shared Lists */}
+      {hasSharedLists && (
+        <Collapsible open={sharedListsOpen} onOpenChange={setSharedListsOpen}>
+          <SidebarItem
+            collapseButton={
+              <CollapsibleTriggerTriangle
+                className="absolute left-0.5 top-1/2 size-2 -translate-y-1/2"
+                open={sharedListsOpen}
+              />
+            }
+            logo={<span className="text-lg">👥</span>}
+            name={t("lists.shared_lists")}
+            path="#"
+            linkClassName="py-0.5"
+            className="px-0.5"
+          />
+          <CollapsibleContent>
+            <CollapsibleBookmarkLists
+              listsData={lists}
+              filter={(node) => node.item.userRole !== "owner"}
+              isOpenFunc={isNodeOpen}
+              indentOffset={1}
+              render={({ node, level, open, numBookmarks }) => (
+                <SidebarItem
+                  collapseButton={
+                    node.children.length > 0 && (
+                      <CollapsibleTriggerTriangle
+                        className="absolute left-0.5 top-1/2 size-2 -translate-y-1/2"
+                        open={open}
+                      />
+                    )
+                  }
+                  logo={
+                    <span className="flex">
+                      <span className="text-lg"> {node.item.icon}</span>
+                    </span>
+                  }
+                  name={node.item.name}
+                  path={`/dashboard/lists/${node.item.id}`}
+                  className="group px-0.5"
+                  right={
+                    <ListOptions
+                      onOpenChange={(isOpen) => {
+                        if (isOpen) {
+                          setSelectedListId(node.item.id);
+                        } else {
+                          setSelectedListId(null);
+                        }
+                      }}
+                      list={node.item}
+                    >
+                      <Button size="none" variant="ghost" className="relative">
+                        <MoreHorizontal
+                          className={cn(
+                            "absolute inset-0 m-auto size-4 opacity-0 transition-opacity duration-100 group-hover:opacity-100",
+                            selectedListId == node.item.id
+                              ? "opacity-100"
+                              : "opacity-0",
+                          )}
+                        />
+                        <span
+                          className={cn(
+                            "px-2.5 text-xs font-light text-muted-foreground opacity-100 transition-opacity duration-100 group-hover:opacity-0",
+                            selectedListId == node.item.id ||
+                              numBookmarks === undefined
+                              ? "opacity-0"
+                              : "opacity-100",
+                          )}
+                        >
+                          {numBookmarks}
+                        </span>
+                      </Button>
+                    </ListOptions>
+                  }
+                  linkClassName="py-0.5"
+                  style={{ marginLeft: `${level * 1}rem` }}
+                />
+              )}
+            />
+          </CollapsibleContent>
+        </Collapsible>
+      )}
     </ul>
   );
 }
-- 
cgit v1.2.3-70-g09d2

