feat: folder permissions (ACLs) - share folders with users/groups, inherit to subfolders, permission dialog UI

This commit is contained in:
Agent Zero
2026-07-28 23:58:19 +02:00
parent 784a771039
commit cc021cda99
12 changed files with 1185 additions and 4 deletions
@@ -10,7 +10,8 @@ import {
useMoveContactToFolder,
} from '@/api/hooks';
import { buildFolderTree, type ContactFolderTreeNode, type ContactFolder } from '@/api/contactFolders';
import { ChevronRight, Folder, MoreVertical, Palette, Pencil, Pin, Plus, Tag, Trash2, Users } from 'lucide-react';
import { ChevronRight, Folder, MoreVertical, Palette, Pencil, Pin, Plus, Shield, Tag, Trash2, Users } from 'lucide-react';
import { FolderPermissionDialog } from './FolderPermissionDialog';
export type ContactFilter = 'all' | 'company' | 'person' | `tag:${string}` | `folder:${string}`;
@@ -74,6 +75,7 @@ function FolderDropdown({
onDelete,
onColor,
onPin,
onPermissions,
}: {
state: DropdownState;
onClose: () => void;
@@ -81,6 +83,7 @@ function FolderDropdown({
onDelete: (id: string) => void;
onColor: (id: string) => void;
onPin: (id: string) => void;
onPermissions: (id: string) => void;
}) {
const ref = useRef<HTMLDivElement>(null);
@@ -96,6 +99,7 @@ function FolderDropdown({
{ label: 'Umbenennen', icon: Pencil, action: () => { onRename(state.folderId); onClose(); } },
{ label: 'Farbe', icon: Palette, action: () => { onColor(state.folderId); onClose(); } },
{ label: 'Anpinnen', icon: Pin, action: () => { onPin(state.folderId); onClose(); } },
{ label: 'Rechte', icon: Shield, action: () => { onPermissions(state.folderId); onClose(); } },
{ label: 'L\u00f6schen', icon: Trash2, action: () => { onDelete(state.folderId); onClose(); }, danger: true },
];
@@ -268,6 +272,7 @@ export function ContactFolderTree({
const [colorPicker, setColorPicker] = useState<{ folderId: string; color: string } | null>(null);
const [dragOverFolderId, setDragOverFolderId] = useState<string | null>(null);
const [multiSelectMode, setMultiSelectMode] = useState(false);
const [permDialog, setPermDialog] = useState<{ folderId: string; folderName: string } | null>(null);
const { data: folders, isLoading: foldersLoading } = useContactFolders();
const createFolderMut = useCreateContactFolder();
@@ -329,6 +334,11 @@ export function ContactFolderTree({
updateFolderMut.mutate({ id, data: { pinned: !pinned } as any });
};
const handlePermissions = (id: string) => {
const folder = folderList.find((f) => f.id === id);
setPermDialog({ folderId: id, folderName: folder?.name || 'Ordner' });
};
// Drag and Drop handlers
const handleDragOver = (e: React.DragEvent, folderId: string) => {
e.preventDefault();
@@ -530,6 +540,15 @@ export function ContactFolderTree({
onDelete={handleDelete}
onColor={handleColor}
onPin={handlePin}
onPermissions={handlePermissions}
/>
)}
{permDialog && (
<FolderPermissionDialog
folderId={permDialog.folderId}
folderName={permDialog.folderName}
onClose={() => setPermDialog(null)}
/>
)}