JavaScript 56.7%
TypeScript 42.6%
1"use client";23import { useState, useEffect } from "react";4import { Users, Check, Search } from "lucide-react";5import Modal from "../ui/Modal";6import Button from "../ui/Button";7import toast from "react-hot-toast";8import { useRouter } from "next/navigation";910interface UserOption {11 id: string;12 name: string | null;13 username: string | null;14}1516interface CreateSharedFolderModalProps {17 isOpen: boolean;18 onClose: () => void;19 onCreated: () => void;20}2122const inputClasses = "field";2324export default function CreateSharedFolderModal({25 isOpen,26 onClose,27 onCreated,28}: CreateSharedFolderModalProps) {29 const router = useRouter();30 const [name, setName] = useState("");31 const [users, setUsers] = useState<UserOption[]>([]);32 const [selectedIds, setSelectedIds] = useState<string[]>([]);33 const [userSearch, setUserSearch] = useState("");34 const [loading, setLoading] = useState(false);35 const [loadingUsers, setLoadingUsers] = useState(false);3637 useEffect(() => {38 if (!isOpen) return;39 setLoadingUsers(true);40 fetch("/api/users/public")41 .then((r) => r.ok ? r.json() : [])42 .then((data) => setUsers(Array.isArray(data) ? data : []))43 .finally(() => setLoadingUsers(false));44 }, [isOpen]);4546 const handleClose = () => {47 setName("");48 setSelectedIds([]);49 setUserSearch("");50 onClose();51 };5253 const toggleUser = (id: string) => {54 setSelectedIds((prev) =>55 prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]56 );57 };5859 const handleCreate = async () => {60 if (!name.trim()) return;61 setLoading(true);62 try {63 const res = await fetch("/api/shared-spaces", {64 method: "POST",65 headers: { "Content-Type": "application/json" },66 body: JSON.stringify({ name: name.trim(), memberIds: selectedIds }),67 });6869 if (res.ok) {70 const space = await res.json();71 toast.success(`Dossier partagé "${name}" créé`);72 handleClose();73 onCreated();74 // Navigate to the new shared space75 router.push(`/dashboard/shared-space/${space.id}`);76 } else {77 const data = await res.json();78 toast.error(data.error || "Erreur lors de la création");79 }80 } catch {81 toast.error("Erreur réseau");82 } finally {83 setLoading(false);84 }85 };8687 const filteredUsers = users.filter((u) => {88 const q = userSearch.toLowerCase();89 return (90 u.username?.toLowerCase().includes(q) ||91 u.name?.toLowerCase().includes(q)92 );93 });9495 return (96 <Modal isOpen={isOpen} onClose={handleClose} title="Nouveau dossier partagé">97 {/* Name */}98 <div className="mb-4">99 <label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1.5">100 Nom du dossier101 </label>102 <input103 type="text"104 value={name}105 onChange={(e) => setName(e.target.value)}106 onKeyDown={(e) => e.key === "Enter" && handleCreate()}107 placeholder="Ex: Projet Alpha, Famille..."108 className={inputClasses}109 autoFocus110 />111 </div>112113 {/* User picker */}114 <div className="mb-4">115 <label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1.5 flex items-center gap-1.5">116 <Users className="w-3.5 h-3.5" />117 Partager avec118 {selectedIds.length > 0 && (119 <span className="ml-auto text-blue-600 dark:text-blue-400 font-semibold">120 {selectedIds.length} sélectionné(s)121 </span>122 )}123 </label>124125 {loadingUsers ? (126 <div className="flex items-center justify-center py-6 text-sm text-gray-400">127 <div className="w-4 h-4 border-2 border-gray-300 dark:border-white/20 border-t-blue-700 rounded-full animate-spin mr-2" />128 Chargement des utilisateurs...129 </div>130 ) : users.length === 0 ? (131 <p className="text-xs text-gray-400 text-center py-4">132 Aucun autre utilisateur disponible.133 </p>134 ) : (135 <div className="border border-gray-200 dark:border-white/10 rounded-lg overflow-hidden">136 {/* Search within users */}137 {users.length > 4 && (138 <div className="p-2 border-b border-gray-100 dark:border-white/10 relative">139 <Search className="w-3.5 h-3.5 absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" />140 <input141 type="text"142 value={userSearch}143 onChange={(e) => setUserSearch(e.target.value)}144 placeholder="Filtrer les utilisateurs..."145 className="w-full pl-7 pr-3 py-1.5 text-xs bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded focus:outline-none text-gray-900 dark:text-white"146 />147 </div>148 )}149150 <div className="max-h-44 overflow-auto">151 {filteredUsers.length === 0 && (152 <p className="text-xs text-gray-400 text-center py-3">Aucun résultat</p>153 )}154 {filteredUsers.map((u) => {155 const isSelected = selectedIds.includes(u.id);156 const displayName = u.name || u.username || u.id;157 const initial = displayName[0].toUpperCase();158 return (159 <button160 key={u.id}161 onClick={() => toggleUser(u.id)}162 className={`w-full flex items-center gap-3 px-3 py-2.5 text-left transition-colors border-b border-gray-50 dark:border-white/5 last:border-0 ${163 isSelected164 ? "bg-blue-50 dark:bg-blue-500/10"165 : "hover:bg-gray-50 dark:hover:bg-white/5"166 }`}167 >168 {/* Avatar */}169 <div className={`w-7 h-7 rounded-full flex items-center justify-center flex-shrink-0 text-xs font-semibold ${170 isSelected171 ? "bg-blue-700 text-white"172 : "bg-gray-200 dark:bg-white/10 text-gray-600 dark:text-gray-300"173 }`}>174 {isSelected ? <Check className="w-3.5 h-3.5" /> : initial}175 </div>176177 <div className="flex-1 min-w-0">178 {u.name && <p className="text-sm font-medium text-gray-900 dark:text-white truncate">{u.name}</p>}179 <p className={`text-xs truncate ${u.name ? "text-gray-400" : "text-sm font-medium text-gray-900 dark:text-white"}`}>180 @{u.username}181 </p>182 </div>183184 {/* Checkbox indicator */}185 <div className={`w-4 h-4 rounded border flex-shrink-0 flex items-center justify-center transition-colors ${186 isSelected187 ? "bg-blue-700 border-blue-700"188 : "border-gray-300 dark:border-white/20"189 }`}>190 {isSelected && <Check className="w-3 h-3 text-white" />}191 </div>192 </button>193 );194 })}195 </div>196 </div>197 )}198 </div>199200 {/* Info note */}201 <p className="text-xs text-gray-400 dark:text-gray-500 mb-4 flex items-start gap-1.5">202 <span className="text-blue-400 mt-0.5">ℹ</span>203 Vous serez automatiquement membre. Le dossier apparaîtra dans la sidebar de chaque membre.204 </p>205206 <div className="flex gap-2 justify-end">207 <Button variant="ghost" onClick={handleClose}>208 Annuler209 </Button>210 <Button211 onClick={handleCreate}212 loading={loading}213 disabled={!name.trim()}214 >215 <Users className="w-4 h-4" />216 Créer le dossier partagé217 </Button>218 </div>219 </Modal>220 );221}222