"use client" import { useEffect, useMemo, useState } from "react" import { Building2, Copy, Eye, Globe, Link2, Loader2, Mail, Pencil, RefreshCw, Shield, SlidersHorizontal, Trash2, UserRound, Users, } from "lucide-react" import { toast } from "sonner" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" import { useSearchContacts } from "@/lib/api/hooks/use-contact-queries" import { useDriveShares, useDriveMutations } from "@/lib/api/hooks/use-drive-queries" import type { DriveShare } from "@/lib/api/types" import { displayFileName } from "@/lib/drive/display-file-name" import { FOLDER_SHARE_PERMISSION_OPTIONS, folderPermissionsFromRole, folderPermissionsToBitmask, type FolderSharePermissionId, type FolderSharePermissions, } from "@/lib/drive/drive-share-permissions" import { NC_SHARE_TYPE, SHARE_SECTION_LABELS, formatShareDate, groupSharesBySection, shareAccessLabel, shareLinkForCopy, shareOwnerLabel, sharePermissionsLabel, shareRecipientLabel, type ShareListSection, } from "@/lib/drive/drive-share-types" import { useDriveUIStore } from "@/lib/stores/drive-ui-store" import { DRIVE_BTN_GHOST, DRIVE_BTN_PRIMARY, DRIVE_DIALOG_CONTENT, DRIVE_DIALOG_DIVIDER, DRIVE_DIALOG_FOOTER, DRIVE_DIALOG_HEADER, DRIVE_DIALOG_OVERLAY, DRIVE_FIELD_CLASS, DRIVE_PANEL_MUTED, DRIVE_TEXT_PRIMARY, DRIVE_TEXT_SECONDARY, DRIVE_TEXT_TITLE, DRIVE_TEXTAREA_CLASS, } from "@/lib/drive/drive-dialog-styles" import { cn } from "@/lib/utils" function shareItemLabel(path: string) { const trimmed = path.replace(/\/+$/, "") const base = trimmed.slice(trimmed.lastIndexOf("/") + 1) return displayFileName(base || path) } type SharePermissionMode = "viewer" | "editor" | "advanced" type LinkAccessMode = "public" | "internal" const PERMISSION_OPTIONS: { id: SharePermissionMode label: string icon: typeof Eye folderOnly?: boolean }[] = [ { id: "viewer", label: "Lecteur", icon: Eye }, { id: "editor", label: "Éditeur", icon: Pencil }, { id: "advanced", label: "Avancé", icon: SlidersHorizontal, folderOnly: true }, ] const LINK_ACCESS_OPTIONS: { id: LinkAccessMode label: string description: string icon: typeof Globe }[] = [ { id: "public", label: "Lien public", description: "Toute personne disposant du lien peut consulter l'élément.", icon: Globe, }, { id: "internal", label: "Lien interne", description: "Réservé aux utilisateurs inscrits et connectés.", icon: Users, }, ] function shareSectionIcon(section: ShareListSection) { if (section === "people") return UserRound if (section === "groups") return Building2 return Link2 } function RoleChip({ permissions }: { permissions: number }) { return ( {sharePermissionsLabel(permissions)} ) } function shareTooltipLines(share: DriveShare): string[] { const lines: string[] = [] const owner = shareOwnerLabel(share) if (owner) lines.push(`Propriétaire · ${owner}`) const created = formatShareDate(share.created_at) if (created) lines.push(`Créé le ${created}`) const expires = formatShareDate(share.expires_at) if (expires) lines.push(`Expire le ${expires}`) if (share.has_password) lines.push("Protégé par mot de passe") const url = shareLinkForCopy(share) if (url) lines.push(url) if (share.note?.trim()) lines.push(`« ${share.note.trim()} »`) return lines } function ShareEntryRow({ share, onDelete, deleting, }: { share: DriveShare onDelete: () => void deleting: boolean }) { const url = shareLinkForCopy(share) const recipient = shareRecipientLabel(share) const accessLabel = shareAccessLabel(share) const isLink = share.share_type === NC_SHARE_TYPE.LINK const tooltipLines = shareTooltipLines(share) const copy = async () => { if (!url) return try { await navigator.clipboard.writeText(url) toast.success("Lien copié") } catch { toast.error("Impossible de copier le lien") } } const primaryLabel = isLink ? accessLabel : (recipient ?? accessLabel) return (
0 && "cursor-default" )} >
{isLink ? ( ) : share.share_type === NC_SHARE_TYPE.GROUP ? ( ) : ( )}

{primaryLabel}

{share.has_password ? ( ) : null}
{url ? ( ) : null}
{tooltipLines.length > 0 ? ( {tooltipLines.map((line) => (

{line}

))}
) : null}
) } function ActiveSharesPanel({ shares, loading, error, onRetry, deletingShareId, onDeleteShare, }: { shares: DriveShare[] loading: boolean error: boolean onRetry: () => void deletingShareId: string | null onDeleteShare: (shareId: string) => void }) { const grouped = useMemo(() => groupSharesBySection(shares), [shares]) const sectionOrder: ShareListSection[] = ["people", "groups", "links"] const hasShares = shares.length > 0 if (loading) { return (
Chargement…
) } if (error) { return (

Impossible de charger les partages.

) } if (!hasShares) return null return (

Utilisateurs avec accès

{sectionOrder.map((section) => { const items = grouped[section] if (items.length === 0) return null const SectionIcon = shareSectionIcon(section) return (

{SHARE_SECTION_LABELS[section]}

{items.map((share) => ( onDeleteShare(share.id)} /> ))}
) })}
) } function AdvancedPermissionsPanel({ folderPermissions, onFolderPermissionChange, }: { folderPermissions: FolderSharePermissions onFolderPermissionChange: (id: FolderSharePermissionId, checked: boolean) => void }) { const advancedPermissionBits = folderPermissionsToBitmask(folderPermissions) return (
{FOLDER_SHARE_PERMISSION_OPTIONS.map((option) => { const checkboxId = `drive-share-perm-${option.id}` return (
onFolderPermissionChange(option.id, value === true)} />
) })} {!folderPermissions.viewContent && folderPermissions.addFiles ? (

Dépôt uniquement : les visiteurs pourront ajouter des fichiers sans voir le contenu existant.

) : null} {advancedPermissionBits === 0 ? (

Sélectionnez au moins une autorisation.

) : null}
) } function PermissionSelect({ value, isFolder, onChange, className, }: { value: SharePermissionMode isFolder: boolean onChange: (mode: SharePermissionMode) => void className?: string }) { const options = PERMISSION_OPTIONS.filter((o) => isFolder || !o.folderOnly) const selected = options.find((o) => o.id === value) ?? options[0] const SelectedIcon = selected.icon return ( ) } export function ShareDialog() { const path = useDriveUIStore((s) => s.sharePath) const shareItemType = useDriveUIStore((s) => s.shareItemType) const setSharePath = useDriveUIStore((s) => s.setSharePath) const [linkAccessMode, setLinkAccessMode] = useState("public") const [permissionMode, setPermissionMode] = useState("viewer") const [folderPermissions, setFolderPermissions] = useState( () => folderPermissionsFromRole("viewer") ) const [contactEmail, setContactEmail] = useState("") const [contactQuery, setContactQuery] = useState("") const [contactNote, setContactNote] = useState("") const [recipientRegistered, setRecipientRegistered] = useState(null) const [deletingShareId, setDeletingShareId] = useState(null) const { data, isLoading: sharesLoading, isError: sharesError, refetch: refetchShares } = useDriveShares( path ?? "", Boolean(path) ) const { createShare, deleteShare, lookupShareRecipient } = useDriveMutations() const lookupRecipientEmail = lookupShareRecipient.mutateAsync const { data: contactResults = [] } = useSearchContacts(contactQuery) const itemLabel = useMemo(() => (path ? shareItemLabel(path) : ""), [path]) const isFolder = shareItemType === "directory" const selectedLinkAccess = LINK_ACCESS_OPTIONS.find((o) => o.id === linkAccessMode) ?? LINK_ACCESS_OPTIONS[0] const LinkAccessIcon = selectedLinkAccess.icon const showContactExtras = contactEmail.trim().length > 0 const hasValidContactEmail = contactEmail.trim().includes("@") useEffect(() => { if (path) { setLinkAccessMode("public") setPermissionMode("viewer") setFolderPermissions(folderPermissionsFromRole("viewer")) setContactEmail("") setContactQuery("") setContactNote("") setRecipientRegistered(null) } }, [path]) useEffect(() => { const email = contactEmail.trim().toLowerCase() if (!email.includes("@")) { setRecipientRegistered(null) return } const timer = window.setTimeout(() => { lookupRecipientEmail(email) .then((res) => setRecipientRegistered(res.registered)) .catch(() => setRecipientRegistered(null)) }, 350) return () => window.clearTimeout(timer) }, [contactEmail, lookupRecipientEmail]) const advancedPermissionBits = folderPermissionsToBitmask(folderPermissions) const canCreateLink = !isFolder || permissionMode !== "advanced" || advancedPermissionBits > 0 const canShareWithContact = hasValidContactEmail && canCreateLink const setFolderPermission = (id: FolderSharePermissionId, checked: boolean) => { setFolderPermissions((prev) => ({ ...prev, [id]: checked })) } const onPermissionModeChange = (nextMode: SharePermissionMode) => { setPermissionMode(nextMode) if (nextMode === "advanced") { setFolderPermissions(folderPermissionsFromRole(permissionMode === "editor" ? "editor" : "viewer")) } } const close = () => setSharePath(null, null) const sharePayload = () => { const base = isFolder && permissionMode === "advanced" ? { path: path!, permissions: advancedPermissionBits } : { path: path!, role: permissionMode === "editor" ? "editor" : "viewer" } return base } const onCreateLink = async () => { if (!path || !canCreateLink) return try { const share = await createShare.mutateAsync({ ...sharePayload(), mode: linkAccessMode, }) const link = shareLinkForCopy(share) if (link) { await navigator.clipboard.writeText(link) toast.success( linkAccessMode === "internal" ? "Lien interne copié" : "Lien public copié" ) } else { toast.success("Partage créé") } void refetchShares() } catch { toast.error("Partage impossible") } } const onShareWithContact = async () => { if (!path || !canShareWithContact) return try { const share = await createShare.mutateAsync({ ...sharePayload(), mode: "contact", share_with: contactEmail.trim().toLowerCase(), note: contactNote.trim() || undefined, send_mail: true, }) if (share.access_mode === "user" || share.share_type === NC_SHARE_TYPE.USER) { toast.success("Partagé — visible dans « Partagés avec moi »") } else { toast.success("Invitation envoyée par e-mail") } setContactEmail("") setContactNote("") setContactQuery("") void refetchShares() } catch { toast.error("Partage impossible") } } const onDeleteShare = async (shareId: string) => { setDeletingShareId(shareId) try { await deleteShare.mutateAsync(shareId) toast.success("Partage supprimé") void refetchShares() } catch { toast.error("Suppression impossible") } finally { setDeletingShareId(null) } } const existingShares = data?.shares ?? [] return ( !open && close()}> Partager « {itemLabel} » {isFolder ? "Partager le dossier" : "Partager le fichier"} {itemLabel}
{/* Ajouter des personnes */}
{ setContactEmail(e.target.value) setContactQuery(e.target.value) }} placeholder="Ajouter des personnes par e-mail" autoComplete="off" className={cn(DRIVE_FIELD_CLASS, "h-10 pr-3")} /> {contactQuery.length >= 2 && contactResults.length > 0 ? (
{contactResults.slice(0, 6).map((c) => c.email ? ( ) : null )}
) : null}
{showContactExtras ? ( ) : null}
{showContactExtras ? (