1import { useState, useEffect } from "react";
2import {
3 X,
4 Folder,
5 Star,
6 Heart,
7 Bookmark,
8 Lightbulb,
9 Zap,
10 Coffee,
11 Music,
12 Camera,
13 Code,
14 Globe,
15 Lock,
16 Flag,
17 Tag,
18 Box,
19 Archive,
20 FileText,
21 Image,
22 Video,
23 Mail,
24 Phone,
25 MapPin,
26 Calendar,
27 Clock,
28 Search,
29 Settings,
30 User,
31 Users,
32 Home,
33 Briefcase,
34 ShoppingBag,
35 Gift,
36 Award,
37 Target,
38 TrendingUp,
39 BarChart,
40 PieChart,
41 Activity,
42 Cpu,
43 Database,
44 Cloud,
45 Sun,
46 Moon,
47 Flame,
48 Leaf,
49 Droplet,
50 Snowflake,
51} from "lucide-react";
52import { createCollection, updateCollection } from "../api/client";
53
54const EMOJI_OPTIONS = [
55 "📁",
56 "📚",
57 "💡",
58 "⭐",
59 "🔖",
60 "💻",
61 "🎨",
62 "📝",
63 "🔬",
64 "🎯",
65 "🚀",
66 "💎",
67 "🌟",
68 "📌",
69 "💼",
70 "🎮",
71 "🎵",
72 "🎬",
73 "❤️",
74 "🔥",
75 "🌈",
76 "🌸",
77 "🌿",
78 "🧠",
79 "🏆",
80 "📊",
81 "🎓",
82 "✨",
83 "🔧",
84 "⚡",
85];
86
87const ICON_OPTIONS = [
88 { icon: Folder, name: "folder" },
89 { icon: Star, name: "star" },
90 { icon: Heart, name: "heart" },
91 { icon: Bookmark, name: "bookmark" },
92 { icon: Lightbulb, name: "lightbulb" },
93 { icon: Zap, name: "zap" },
94 { icon: Coffee, name: "coffee" },
95 { icon: Music, name: "music" },
96 { icon: Camera, name: "camera" },
97 { icon: Code, name: "code" },
98 { icon: Globe, name: "globe" },
99 { icon: Flag, name: "flag" },
100 { icon: Tag, name: "tag" },
101 { icon: Box, name: "box" },
102 { icon: Archive, name: "archive" },
103 { icon: FileText, name: "file" },
104 { icon: Image, name: "image" },
105 { icon: Video, name: "video" },
106 { icon: Mail, name: "mail" },
107 { icon: MapPin, name: "pin" },
108 { icon: Calendar, name: "calendar" },
109 { icon: Clock, name: "clock" },
110 { icon: Search, name: "search" },
111 { icon: Settings, name: "settings" },
112 { icon: User, name: "user" },
113 { icon: Users, name: "users" },
114 { icon: Home, name: "home" },
115 { icon: Briefcase, name: "briefcase" },
116 { icon: Gift, name: "gift" },
117 { icon: Award, name: "award" },
118 { icon: Target, name: "target" },
119 { icon: TrendingUp, name: "trending" },
120 { icon: Activity, name: "activity" },
121 { icon: Cpu, name: "cpu" },
122 { icon: Database, name: "database" },
123 { icon: Cloud, name: "cloud" },
124 { icon: Sun, name: "sun" },
125 { icon: Moon, name: "moon" },
126 { icon: Flame, name: "flame" },
127 { icon: Leaf, name: "leaf" },
128];
129
130export default function CollectionModal({
131 isOpen,
132 onClose,
133 onSuccess,
134 collectionToEdit,
135}) {
136 const [name, setName] = useState("");
137 const [description, setDescription] = useState("");
138 const [icon, setIcon] = useState("");
139 const [customEmoji, setCustomEmoji] = useState("");
140 const [activeTab, setActiveTab] = useState("emoji");
141 const [loading, setLoading] = useState(false);
142 const [error, setError] = useState(null);
143
144 useEffect(() => {
145 if (collectionToEdit) {
146 setName(collectionToEdit.name);
147 setDescription(collectionToEdit.description || "");
148 const savedIcon = collectionToEdit.icon || "";
149 setIcon(savedIcon);
150 setCustomEmoji(savedIcon);
151
152 if (savedIcon.startsWith("icon:")) {
153 setActiveTab("icons");
154 }
155 } else {
156 setName("");
157 setDescription("");
158 setIcon("");
159 setCustomEmoji("");
160 }
161 setError(null);
162 }, [collectionToEdit, isOpen]);
163
164 if (!isOpen) return null;
165
166 const handleEmojiSelect = (emoji) => {
167 if (icon === emoji) {
168 setIcon("");
169 setCustomEmoji("");
170 } else {
171 setIcon(emoji);
172 setCustomEmoji(emoji);
173 }
174 };
175
176 const handleIconSelect = (iconName) => {
177 const value = `icon:${iconName}`;
178 if (icon === value) {
179 setIcon("");
180 setCustomEmoji("");
181 } else {
182 setIcon(value);
183 setCustomEmoji(value);
184 }
185 };
186
187 const handleCustomEmojiChange = (e) => {
188 const value = e.target.value;
189 setCustomEmoji(value);
190 const emojiMatch = value.match(
191 /(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)/gu,
192 );
193 if (emojiMatch && emojiMatch.length > 0) {
194 setIcon(emojiMatch[emojiMatch.length - 1]);
195 } else if (value === "") {
196 setIcon("");
197 }
198 };
199
200 const handleSubmit = async (e) => {
201 e.preventDefault();
202 setLoading(true);
203 setError(null);
204
205 try {
206 if (collectionToEdit) {
207 await updateCollection(collectionToEdit.uri, name, description, icon);
208 } else {
209 await createCollection(name, description, icon);
210 }
211 onSuccess();
212 onClose();
213 } catch (err) {
214 console.error(err);
215 setError(err.message || "Failed to save collection");
216 } finally {
217 setLoading(false);
218 }
219 };
220
221 return (
222 <div className="modal-overlay" onClick={onClose}>
223 <div
224 className="modal-container"
225 style={{ maxWidth: "420px" }}
226 onClick={(e) => e.stopPropagation()}
227 >
228 <div className="modal-header">
229 <h2 className="modal-title">
230 {collectionToEdit ? "Edit Collection" : "New Collection"}
231 </h2>
232 <button onClick={onClose} className="modal-close-btn">
233 <X size={20} />
234 </button>
235 </div>
236
237 <form onSubmit={handleSubmit} className="modal-form">
238 {error && (
239 <div
240 className="card text-error"
241 style={{
242 padding: "12px",
243 background: "rgba(239, 68, 68, 0.1)",
244 borderColor: "rgba(239, 68, 68, 0.2)",
245 fontSize: "0.9rem",
246 }}
247 >
248 {error}
249 </div>
250 )}
251
252 <div className="form-group">
253 <label className="form-label">Icon</label>
254 <div className="icon-picker-tabs">
255 <button
256 type="button"
257 className={`icon-picker-tab ${activeTab === "emoji" ? "active" : ""}`}
258 onClick={() => setActiveTab("emoji")}
259 >
260 Emoji
261 </button>
262 <button
263 type="button"
264 className={`icon-picker-tab ${activeTab === "icons" ? "active" : ""}`}
265 onClick={() => setActiveTab("icons")}
266 >
267 Icons
268 </button>
269 </div>
270
271 {activeTab === "emoji" && (
272 <div className="emoji-picker-wrapper">
273 <div className="emoji-custom-input">
274 <input
275 type="text"
276 value={customEmoji.startsWith("icon:") ? "" : customEmoji}
277 onChange={handleCustomEmojiChange}
278 placeholder="Type any emoji..."
279 className="form-input"
280 />
281 </div>
282 <div className="emoji-picker">
283 {EMOJI_OPTIONS.map((emoji) => (
284 <button
285 key={emoji}
286 type="button"
287 className={`emoji-option ${icon === emoji ? "selected" : ""}`}
288 onClick={() => handleEmojiSelect(emoji)}
289 >
290 {emoji}
291 </button>
292 ))}
293 </div>
294 </div>
295 )}
296
297 {activeTab === "icons" && (
298 <div className="icon-picker">
299 {ICON_OPTIONS.map(({ icon: IconComponent, name: iconName }) => (
300 <button
301 key={iconName}
302 type="button"
303 className={`icon-option ${icon === `icon:${iconName}` ? "selected" : ""}`}
304 onClick={() => handleIconSelect(iconName)}
305 >
306 <IconComponent size={20} />
307 </button>
308 ))}
309 </div>
310 )}
311 </div>
312
313 <div className="form-group">
314 <label className="form-label">Name</label>
315 <input
316 type="text"
317 value={name}
318 onChange={(e) => setName(e.target.value)}
319 required
320 className="form-input"
321 placeholder="My Favorites"
322 />
323 </div>
324
325 <div className="form-group">
326 <label className="form-label">Description</label>
327 <textarea
328 value={description}
329 onChange={(e) => setDescription(e.target.value)}
330 rows={2}
331 className="form-textarea"
332 placeholder="A collection of..."
333 />
334 </div>
335
336 <div className="modal-actions">
337 <button type="button" onClick={onClose} className="btn btn-ghost">
338 Cancel
339 </button>
340 <button
341 type="submit"
342 disabled={loading}
343 className="btn btn-primary"
344 style={loading ? { opacity: 0.7, cursor: "wait" } : {}}
345 >
346 {loading
347 ? "Saving..."
348 : collectionToEdit
349 ? "Save Changes"
350 : "Create Collection"}
351 </button>
352 </div>
353 </form>
354 </div>
355 </div>
356 );
357}