diff options
Diffstat (limited to 'apps/web/components/dashboard/lists/ListOptions.tsx')
| -rw-r--r-- | apps/web/components/dashboard/lists/ListOptions.tsx | 161 |
1 files changed, 118 insertions, 43 deletions
diff --git a/apps/web/components/dashboard/lists/ListOptions.tsx b/apps/web/components/dashboard/lists/ListOptions.tsx index 7e020374..b80ac680 100644 --- a/apps/web/components/dashboard/lists/ListOptions.tsx +++ b/apps/web/components/dashboard/lists/ListOptions.tsx @@ -8,6 +8,7 @@ import { import { useShowArchived } from "@/components/utils/useShowArchived"; import { useTranslation } from "@/lib/i18n/client"; import { + DoorOpen, FolderInput, Pencil, Plus, @@ -15,12 +16,15 @@ import { Square, SquareCheck, Trash2, + Users, } from "lucide-react"; import { ZBookmarkList } from "@karakeep/shared/types/lists"; import { EditListModal } from "../lists/EditListModal"; import DeleteListConfirmationDialog from "./DeleteListConfirmationDialog"; +import LeaveListConfirmationDialog from "./LeaveListConfirmationDialog"; +import { ManageCollaboratorsModal } from "./ManageCollaboratorsModal"; import { MergeListModal } from "./MergeListModal"; import { ShareListModal } from "./ShareListModal"; @@ -39,10 +43,102 @@ export function ListOptions({ const { showArchived, onClickShowArchived } = useShowArchived(); const [deleteListDialogOpen, setDeleteListDialogOpen] = useState(false); + const [leaveListDialogOpen, setLeaveListDialogOpen] = useState(false); const [newNestedListModalOpen, setNewNestedListModalOpen] = useState(false); const [mergeListModalOpen, setMergeListModalOpen] = useState(false); const [editModalOpen, setEditModalOpen] = useState(false); const [shareModalOpen, setShareModalOpen] = useState(false); + const [collaboratorsModalOpen, setCollaboratorsModalOpen] = useState(false); + + // Only owners can manage the list (edit, delete, manage collaborators, etc.) + const isOwner = list.userRole === "owner"; + // Collaborators (non-owners) can leave the list + const isCollaborator = + list.userRole === "editor" || list.userRole === "viewer"; + + // Define action items array + const actionItems = [ + { + id: "edit", + title: t("actions.edit"), + icon: <Pencil className="size-4" />, + visible: isOwner, + disabled: false, + onClick: () => setEditModalOpen(true), + }, + { + id: "share", + title: t("lists.share_list"), + icon: <Share className="size-4" />, + visible: isOwner, + disabled: false, + onClick: () => setShareModalOpen(true), + }, + { + id: "manage-collaborators", + title: isOwner + ? t("lists.collaborators.manage") + : t("lists.collaborators.view"), + icon: <Users className="size-4" />, + visible: true, // Always visible for all roles + disabled: false, + onClick: () => setCollaboratorsModalOpen(true), + }, + { + id: "new-nested-list", + title: t("lists.new_nested_list"), + icon: <Plus className="size-4" />, + visible: isOwner, + disabled: false, + onClick: () => setNewNestedListModalOpen(true), + }, + { + id: "merge-list", + title: t("lists.merge_list"), + icon: <FolderInput className="size-4" />, + visible: isOwner, + disabled: false, + onClick: () => setMergeListModalOpen(true), + }, + { + id: "toggle-archived", + title: t("actions.toggle_show_archived"), + icon: showArchived ? ( + <SquareCheck className="size-4" /> + ) : ( + <Square className="size-4" /> + ), + visible: true, + disabled: false, + onClick: onClickShowArchived, + }, + { + id: "leave-list", + title: t("lists.leave_list.action"), + icon: <DoorOpen className="size-4" />, + visible: isCollaborator, + disabled: false, + className: "flex gap-2 text-destructive", + onClick: () => setLeaveListDialogOpen(true), + }, + { + id: "delete", + title: t("actions.delete"), + icon: <Trash2 className="size-4" />, + visible: isOwner, + disabled: false, + className: "flex gap-2 text-destructive", + onClick: () => setDeleteListDialogOpen(true), + }, + ]; + + // Filter visible items + const visibleItems = actionItems.filter((item) => item.visible); + + // If no items are visible, don't render the dropdown + if (visibleItems.length === 0) { + return null; + } return ( <DropdownMenu open={isOpen} onOpenChange={onOpenChange}> @@ -51,6 +147,12 @@ export function ListOptions({ setOpen={setShareModalOpen} list={list} /> + <ManageCollaboratorsModal + open={collaboratorsModalOpen} + setOpen={setCollaboratorsModalOpen} + list={list} + readOnly={!isOwner} + /> <EditListModal open={newNestedListModalOpen} setOpen={setNewNestedListModalOpen} @@ -73,51 +175,24 @@ export function ListOptions({ open={deleteListDialogOpen} setOpen={setDeleteListDialogOpen} /> + <LeaveListConfirmationDialog + list={list} + open={leaveListDialogOpen} + setOpen={setLeaveListDialogOpen} + /> <DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger> <DropdownMenuContent> - <DropdownMenuItem - className="flex gap-2" - onClick={() => setEditModalOpen(true)} - > - <Pencil className="size-4" /> - <span>{t("actions.edit")}</span> - </DropdownMenuItem> - <DropdownMenuItem - className="flex gap-2" - onClick={() => setShareModalOpen(true)} - > - <Share className="size-4" /> - <span>{t("lists.share_list")}</span> - </DropdownMenuItem> - <DropdownMenuItem - className="flex gap-2" - onClick={() => setNewNestedListModalOpen(true)} - > - <Plus className="size-4" /> - <span>{t("lists.new_nested_list")}</span> - </DropdownMenuItem> - <DropdownMenuItem - className="flex gap-2" - onClick={() => setMergeListModalOpen(true)} - > - <FolderInput className="size-4" /> - <span>{t("lists.merge_list")}</span> - </DropdownMenuItem> - <DropdownMenuItem className="flex gap-2" onClick={onClickShowArchived}> - {showArchived ? ( - <SquareCheck className="size-4" /> - ) : ( - <Square className="size-4" /> - )} - <span>{t("actions.toggle_show_archived")}</span> - </DropdownMenuItem> - <DropdownMenuItem - className="flex gap-2" - onClick={() => setDeleteListDialogOpen(true)} - > - <Trash2 className="size-4" /> - <span>{t("actions.delete")}</span> - </DropdownMenuItem> + {visibleItems.map((item) => ( + <DropdownMenuItem + key={item.id} + className={item.className ?? "flex gap-2"} + disabled={item.disabled} + onClick={item.onClick} + > + {item.icon} + <span>{item.title}</span> + </DropdownMenuItem> + ))} </DropdownMenuContent> </DropdownMenu> ); |
