@thetechfossil/upfiles 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -10,6 +10,7 @@ var UpfilesClient = class {
10
10
  this.apiKeyHeader = opts.apiKeyHeader ?? "authorization";
11
11
  this.thumbnailsPath = opts.thumbnailsPath ?? "/api/plugin/thumbnails";
12
12
  this.completionPath = opts.completionPath ?? "/api/plugin/upload/complete";
13
+ this.foldersPath = opts.foldersPath ?? "/api/plugin/folders";
13
14
  this.callCompletionEndpoint = opts.callCompletionEndpoint ?? false;
14
15
  }
15
16
  async getPresignedUrl(params) {
@@ -132,6 +133,7 @@ var UpfilesClient = class {
132
133
  }
133
134
  throw new Error(msg);
134
135
  }
136
+ return res.json();
135
137
  }
136
138
  async getThumbnails(fileKey) {
137
139
  const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
@@ -192,6 +194,65 @@ var UpfilesClient = class {
192
194
  const data = await res.json();
193
195
  return { masterWebp: data.masterWebp, thumbnails: data.thumbnails ?? [] };
194
196
  }
197
+ async getFolders(parentId) {
198
+ const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
199
+ const url = parentId ? `${target}?parentId=${encodeURIComponent(parentId)}` : target;
200
+ const headers = { ...this.headers || {} };
201
+ if (this.apiKey) {
202
+ if (this.apiKeyHeader === "authorization") {
203
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
204
+ } else if (this.apiKeyHeader === "x-api-key") {
205
+ headers["x-api-key"] = this.apiKey;
206
+ } else {
207
+ headers["x-up-api-key"] = this.apiKey;
208
+ }
209
+ }
210
+ const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
211
+ if (!res.ok) {
212
+ let msg = "Failed to fetch folders";
213
+ try {
214
+ const data2 = await res.json();
215
+ msg = data2.error || msg;
216
+ } catch {
217
+ }
218
+ throw new Error(msg);
219
+ }
220
+ const data = await res.json();
221
+ return data?.folders ?? [];
222
+ }
223
+ async createFolder(name, parentId) {
224
+ const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
225
+ const headers = {
226
+ "content-type": "application/json",
227
+ ...this.headers || {}
228
+ };
229
+ if (this.apiKey) {
230
+ if (this.apiKeyHeader === "authorization") {
231
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
232
+ } else if (this.apiKeyHeader === "x-api-key") {
233
+ headers["x-api-key"] = this.apiKey;
234
+ } else {
235
+ headers["x-up-api-key"] = this.apiKey;
236
+ }
237
+ }
238
+ const res = await fetch(target, {
239
+ method: "POST",
240
+ headers,
241
+ credentials: this.withCredentials ? "include" : "same-origin",
242
+ body: JSON.stringify({ name, parentId })
243
+ });
244
+ if (!res.ok) {
245
+ let msg = "Failed to create folder";
246
+ try {
247
+ const data2 = await res.json();
248
+ msg = data2.error || msg;
249
+ } catch {
250
+ }
251
+ throw new Error(msg);
252
+ }
253
+ const data = await res.json();
254
+ return data.folder;
255
+ }
195
256
  async getProjectFiles(params) {
196
257
  const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
197
258
  const filesPath = `${basePath}/files`;
@@ -218,7 +279,23 @@ var UpfilesClient = class {
218
279
  throw new Error(msg);
219
280
  }
220
281
  const data = await res.json();
221
- return data?.files ?? [];
282
+ return {
283
+ files: data?.files ?? [],
284
+ updatedAt: res.headers.get("X-Files-Updated-At")
285
+ };
286
+ }
287
+ getEventsUrl(projectId) {
288
+ const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
289
+ const eventsPath = `${basePath}/events`;
290
+ const target = this.baseUrl ? `${this.baseUrl}/api/events` : "/api/events";
291
+ const url = new URL(target, window.location.href);
292
+ if (this.apiKey) {
293
+ url.searchParams.set("apiKey", this.apiKey);
294
+ }
295
+ if (projectId) {
296
+ url.searchParams.set("projectId", projectId);
297
+ }
298
+ return url.toString();
222
299
  }
223
300
  };
224
301
 
@@ -243,7 +320,7 @@ var Uploader = ({
243
320
  projectId,
244
321
  folderPath,
245
322
  fetchThumbnails,
246
- autoRecordToDb = false,
323
+ autoRecordToDb = true,
247
324
  recordUrl = "/api/files",
248
325
  autoUpload = false
249
326
  }) => {
@@ -317,9 +394,10 @@ var Uploader = ({
317
394
  xhr.setRequestHeader("Content-Type", item.type || "application/octet-stream");
318
395
  xhr.send(item.file);
319
396
  });
397
+ let completionResult = null;
320
398
  if (autoRecordToDb && projectId) {
321
399
  try {
322
- await client.completeUpload({
400
+ completionResult = await client.completeUpload({
323
401
  fileKey: presign.fileKey,
324
402
  originalName: item.name,
325
403
  size: item.size,
@@ -333,15 +411,21 @@ var Uploader = ({
333
411
  }
334
412
  let thumbs;
335
413
  let masterWebp;
336
- try {
337
- if (fetchThumbnails) {
338
- const result2 = await client.generateThumbnails(presign.fileKey);
414
+ if (completionResult?.thumbnails?.length > 0) {
415
+ thumbs = completionResult.thumbnails;
416
+ masterWebp = completionResult.masterWebp;
417
+ } else if (fetchThumbnails) {
418
+ const fileKeyToUse = completionResult?.file?.key || presign.fileKey;
419
+ try {
420
+ const result2 = await client.generateThumbnails(fileKeyToUse);
339
421
  thumbs = result2.thumbnails;
340
422
  masterWebp = result2.masterWebp;
423
+ } catch (e) {
424
+ console.warn("Failed to generate thumbnails:", e);
341
425
  }
342
- } catch (e) {
343
- console.warn("Failed to generate thumbnails:", e);
344
426
  }
427
+ const finalFileKey = completionResult?.file?.key || presign.fileKey;
428
+ const finalPublicUrl = completionResult?.file?.url || presign.publicUrl;
345
429
  const result = {
346
430
  ...item,
347
431
  status: "success",
@@ -404,10 +488,10 @@ var Uploader = ({
404
488
  onChange: onInputChange
405
489
  }
406
490
  ),
407
- /* @__PURE__ */ jsx(
491
+ /* @__PURE__ */ jsxs(
408
492
  "div",
409
493
  {
410
- className: dropzoneClassName,
494
+ className: `${dropzoneClassName} border-2 border-dashed rounded-xl transition-all duration-200 cursor-pointer p-10 flex flex-col items-center justify-center gap-4 ${isDragOver ? "border-blue-500 bg-blue-50 dark:bg-blue-900/20" : "border-gray-200 dark:border-gray-800 hover:border-gray-300 dark:hover:border-gray-700 bg-gray-50/50 dark:bg-gray-900/50"}`,
411
495
  onDragOver: (e) => {
412
496
  e.preventDefault();
413
497
  e.stopPropagation();
@@ -422,51 +506,121 @@ var Uploader = ({
422
506
  onClick: () => inputRef.current?.click(),
423
507
  role: "button",
424
508
  "aria-label": "Upload files",
425
- children: children ?? /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: 16 }, children: [
426
- /* @__PURE__ */ jsx("div", { style: { fontWeight: 600 }, children: "Drop files here or click to browse" }),
427
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#666" }, children: [
428
- multiple ? `Up to ${maxFiles} files` : "Single file",
429
- " \u2022 Max ",
430
- (maxFileSize / (1024 * 1024)).toFixed(0),
431
- "MB"
509
+ children: [
510
+ /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center text-blue-600 dark:text-blue-400", children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
511
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
512
+ /* @__PURE__ */ jsx("polyline", { points: "17 8 12 3 7 8" }),
513
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
514
+ ] }) }),
515
+ children ?? /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
516
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Drop files here or click to browse" }),
517
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: [
518
+ multiple ? `Up to ${maxFiles} files` : "Single file",
519
+ " \u2022 Max ",
520
+ (maxFileSize / (1024 * 1024)).toFixed(0),
521
+ "MB"
522
+ ] })
432
523
  ] })
433
- ] })
524
+ ]
434
525
  }
435
526
  ),
436
- files.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
437
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginBottom: 8 }, children: [
438
- /* @__PURE__ */ jsx(
439
- "button",
440
- {
441
- className: buttonClassName,
442
- disabled: isUploading || files.every((f2) => f2.status !== "pending"),
443
- onClick: uploadAll,
444
- children: isUploading ? "Uploading..." : "Upload"
445
- }
446
- ),
447
- /* @__PURE__ */ jsx(
448
- "button",
449
- {
450
- className: buttonClassName,
451
- disabled: isUploading,
452
- onClick: () => setFiles([]),
453
- children: "Clear"
454
- }
455
- )
527
+ files.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-4", children: [
528
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-4", children: [
529
+ /* @__PURE__ */ jsx("h4", { className: "font-semibold text-gray-900 dark:text-gray-100", children: "Selected Files" }),
530
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
531
+ /* @__PURE__ */ jsx(
532
+ "button",
533
+ {
534
+ className: "px-4 py-2 bg-blue-600 text-white text-sm font-semibold rounded-lg hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-colors shadow-sm",
535
+ disabled: isUploading || files.every((f2) => f2.status !== "pending"),
536
+ onClick: uploadAll,
537
+ children: isUploading ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
538
+ /* @__PURE__ */ jsxs("svg", { className: "animate-spin h-4 w-4 text-white", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
539
+ /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
540
+ /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })
541
+ ] }),
542
+ "Uploading..."
543
+ ] }) : "Upload All"
544
+ }
545
+ ),
546
+ /* @__PURE__ */ jsx(
547
+ "button",
548
+ {
549
+ className: "px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-sm font-semibold rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-50 transition-colors",
550
+ disabled: isUploading,
551
+ onClick: () => setFiles([]),
552
+ children: "Clear"
553
+ }
554
+ )
555
+ ] })
456
556
  ] }),
457
- /* @__PURE__ */ jsx("ul", { style: { display: "grid", gap: 8, listStyle: "none", padding: 0 }, children: files.map((f2) => /* @__PURE__ */ jsx("li", { style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 8 }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
458
- /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
459
- /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.name }),
460
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#666" }, children: [
461
- (f2.size / (1024 * 1024)).toFixed(2),
462
- " MB"
557
+ /* @__PURE__ */ jsx("div", { className: "grid gap-3", children: files.map((f2) => /* @__PURE__ */ jsx("div", { className: "group relative bg-white dark:bg-gray-900 border border-gray-100 dark:border-gray-800 rounded-xl p-4 transition-all hover:shadow-md", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
558
+ /* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded-lg bg-gray-50 dark:bg-gray-800 flex items-center justify-center flex-shrink-0", children: f2.type.startsWith("image/") ? /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-indigo-500", children: [
559
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
560
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
561
+ /* @__PURE__ */ jsx("polyline", { points: "21 15 16 10 5 21" })
562
+ ] }) : /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-blue-500", children: [
563
+ /* @__PURE__ */ jsx("path", { d: "M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" }),
564
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" })
565
+ ] }) }),
566
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
567
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
568
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100 truncate pr-4", children: f2.name }),
569
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-500 font-medium whitespace-nowrap", children: [
570
+ (f2.size / (1024 * 1024)).toFixed(2),
571
+ " MB"
572
+ ] })
463
573
  ] }),
464
- f2.error && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#b91c1c" }, children: f2.error }),
465
- f2.publicUrl && f2.status === "success" && /* @__PURE__ */ jsx("a", { href: f2.publicUrl, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" })
574
+ f2.status === "error" ? /* @__PURE__ */ jsx("div", { className: "text-xs text-red-500 font-medium mt-1", children: f2.error }) : /* @__PURE__ */ jsx("div", { className: "h-1.5 w-full bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden mt-2", children: /* @__PURE__ */ jsx(
575
+ "div",
576
+ {
577
+ className: `h-full transition-all duration-300 ${f2.status === "success" ? "bg-green-500" : "bg-blue-500"}`,
578
+ style: { width: `${f2.progress}%` }
579
+ }
580
+ ) })
466
581
  ] }),
467
- /* @__PURE__ */ jsxs("div", { style: { minWidth: 120, textAlign: "right" }, children: [
468
- /* @__PURE__ */ jsx("div", { style: { height: 6, background: "#e5e7eb", borderRadius: 999, overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { width: `${f2.progress}%`, height: "100%", background: f2.status === "error" ? "#ef4444" : "#2563eb" } }) }),
469
- /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#666", marginTop: 4 }, children: f2.status })
582
+ /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 ml-4", children: [
583
+ f2.status === "success" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
584
+ /* @__PURE__ */ jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-green-100 dark:bg-green-900/30 text-green-600 dark:text-green-400", children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) }) }),
585
+ f2.publicUrl && /* @__PURE__ */ jsx(
586
+ "a",
587
+ {
588
+ href: f2.publicUrl,
589
+ target: "_blank",
590
+ rel: "noreferrer",
591
+ className: "p-1.5 text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-colors",
592
+ title: "View online",
593
+ children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
594
+ /* @__PURE__ */ jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
595
+ /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
596
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
597
+ ] })
598
+ }
599
+ )
600
+ ] }),
601
+ f2.status === "error" && /* @__PURE__ */ jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400", children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: [
602
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
603
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
604
+ ] }) }),
605
+ f2.status === "uploading" && /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-blue-600 dark:text-blue-400", children: [
606
+ f2.progress,
607
+ "%"
608
+ ] }),
609
+ f2.status === "pending" && /* @__PURE__ */ jsx(
610
+ "button",
611
+ {
612
+ onClick: (e) => {
613
+ e.stopPropagation();
614
+ setFiles((prev) => prev.filter((file) => file.id !== f2.id));
615
+ },
616
+ className: "p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-colors",
617
+ title: "Remove",
618
+ children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
619
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
620
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
621
+ ] })
622
+ }
623
+ )
470
624
  ] })
471
625
  ] }) }, f2.id)) })
472
626
  ] })
@@ -474,7 +628,7 @@ var Uploader = ({
474
628
  };
475
629
 
476
630
  // src/ProjectFilesWidget.tsx
477
- import { useEffect, useMemo as useMemo2, useState as useState2 } from "react";
631
+ import React2, { useEffect, useMemo as useMemo2, useState as useState2 } from "react";
478
632
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
479
633
  var ProjectFilesWidget = ({
480
634
  clientOptions,
@@ -488,7 +642,8 @@ var ProjectFilesWidget = ({
488
642
  onSaved,
489
643
  onDelete,
490
644
  deleteUrl = "/api/files",
491
- showDelete = false
645
+ showDelete = false,
646
+ refreshInterval = 5e3
492
647
  }) => {
493
648
  const client = useMemo2(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
494
649
  const [files, setFiles] = useState2([]);
@@ -497,25 +652,37 @@ var ProjectFilesWidget = ({
497
652
  const [query, setQuery] = useState2("");
498
653
  const [savingKey, setSavingKey] = useState2(null);
499
654
  const [deletingKey, setDeletingKey] = useState2(null);
500
- useEffect(() => {
501
- let cancelled = false;
502
- const run = async () => {
503
- setLoading(true);
504
- setError(null);
505
- try {
506
- const data = await client.getProjectFiles({ folderPath });
507
- if (!cancelled) setFiles(data);
508
- } catch (e) {
509
- if (!cancelled) setError(e?.message || "Failed to load files");
510
- } finally {
511
- if (!cancelled) setLoading(false);
655
+ const lastUpdatedRef = React2.useRef(null);
656
+ const isFetchingRef = React2.useRef(false);
657
+ const fetchFiles = React2.useCallback(async (isPolling = false) => {
658
+ if (isFetchingRef.current) return;
659
+ isFetchingRef.current = true;
660
+ if (!isPolling) setLoading(true);
661
+ setError(null);
662
+ try {
663
+ const { files: newFiles, updatedAt } = await client.getProjectFiles({ folderPath });
664
+ if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
665
+ return;
512
666
  }
513
- };
514
- run();
515
- return () => {
516
- cancelled = true;
517
- };
667
+ setFiles(newFiles);
668
+ lastUpdatedRef.current = updatedAt;
669
+ } catch (e) {
670
+ if (!isPolling) setError(e?.message || "Failed to load files");
671
+ } finally {
672
+ if (!isPolling) setLoading(false);
673
+ isFetchingRef.current = false;
674
+ }
518
675
  }, [client, folderPath]);
676
+ useEffect(() => {
677
+ fetchFiles();
678
+ }, [fetchFiles]);
679
+ useEffect(() => {
680
+ if (!refreshInterval || refreshInterval <= 0) return;
681
+ const timer = setInterval(() => {
682
+ fetchFiles(true);
683
+ }, refreshInterval);
684
+ return () => clearInterval(timer);
685
+ }, [fetchFiles, refreshInterval]);
519
686
  const visible = files.filter(
520
687
  (f2) => !query ? true : (f2.originalName || "").toLowerCase().includes(query.toLowerCase())
521
688
  );
@@ -572,18 +739,7 @@ var ProjectFilesWidget = ({
572
739
  {
573
740
  onClick: () => {
574
741
  setQuery("");
575
- (async () => {
576
- setLoading(true);
577
- setError(null);
578
- try {
579
- const data = await client.getProjectFiles({ folderPath });
580
- setFiles(data);
581
- } catch (e) {
582
- setError(e?.message || "Failed to load files");
583
- } finally {
584
- setLoading(false);
585
- }
586
- })();
742
+ fetchFiles();
587
743
  },
588
744
  style: { padding: "8px 12px", border: "1px solid #e5e7eb", borderRadius: 6, background: "#fff" },
589
745
  children: "Refresh"
@@ -976,7 +1132,7 @@ async function fetchFileThumbnails(clientOptions, fileKey) {
976
1132
  return client.getThumbnails(fileKey);
977
1133
  }
978
1134
  async function fetchProjectImagesWithThumbs(clientOptions, opts) {
979
- const files = await fetchProjectFiles(clientOptions, opts?.folderPath);
1135
+ const { files, updatedAt } = await fetchProjectFiles(clientOptions, opts?.folderPath);
980
1136
  const limit = Math.max(1, Math.min(opts?.limitConcurrent ?? 6, 12));
981
1137
  const queue = [...files];
982
1138
  const results = [];
@@ -998,13 +1154,62 @@ async function fetchProjectImagesWithThumbs(clientOptions, opts) {
998
1154
  await Promise.all(Array.from({ length: workers }, () => worker()));
999
1155
  const keyToIndex = new Map(files.map((f2, i) => [f2.key, i]));
1000
1156
  results.sort((a, b) => (keyToIndex.get(a.key) ?? 0) - (keyToIndex.get(b.key) ?? 0));
1001
- return results;
1157
+ return { items: results, updatedAt };
1002
1158
  }
1003
1159
 
1004
1160
  // src/ImageManager.tsx
1005
1161
  import * as React4 from "react";
1006
1162
  import * as Dialog2 from "@radix-ui/react-dialog";
1007
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1163
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1164
+ var IconGrid = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1165
+ /* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
1166
+ /* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
1167
+ /* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
1168
+ /* @__PURE__ */ jsx4("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
1169
+ ] });
1170
+ var IconUploadCloud = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1171
+ /* @__PURE__ */ jsx4("path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }),
1172
+ /* @__PURE__ */ jsx4("path", { d: "M12 12v9" }),
1173
+ /* @__PURE__ */ jsx4("path", { d: "m16 16-4-4-4 4" })
1174
+ ] });
1175
+ var IconFolder = ({ className }) => /* @__PURE__ */ jsx4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", stroke: "currentColor", strokeWidth: "0", className, children: /* @__PURE__ */ jsx4("path", { d: "M19.5 21a3 3 0 0 0 3-3v-4.5a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3V18a3 3 0 0 0 3 3h15ZM1.5 10.146V6a3 3 0 0 1 3-3h5.379a2.25 2.25 0 0 1 1.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 0 1 3 3v1.146A4.483 4.483 0 0 0 19.5 9h-15a4.483 4.483 0 0 0-3 1.146Z" }) });
1176
+ var IconFolderPlus = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1177
+ /* @__PURE__ */ jsx4("path", { d: "M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 2H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2Z" }),
1178
+ /* @__PURE__ */ jsx4("line", { x1: "12", x2: "12", y1: "10", y2: "16" }),
1179
+ /* @__PURE__ */ jsx4("line", { x1: "9", x2: "15", y1: "13", y2: "13" })
1180
+ ] });
1181
+ var IconSearch = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1182
+ /* @__PURE__ */ jsx4("circle", { cx: "11", cy: "11", r: "8" }),
1183
+ /* @__PURE__ */ jsx4("path", { d: "m21 21-4.3-4.3" })
1184
+ ] });
1185
+ var IconRefresh = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1186
+ /* @__PURE__ */ jsx4("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
1187
+ /* @__PURE__ */ jsx4("path", { d: "M21 3v5h-5" }),
1188
+ /* @__PURE__ */ jsx4("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
1189
+ /* @__PURE__ */ jsx4("path", { d: "M8 16H3v5" })
1190
+ ] });
1191
+ var IconChevronRight = ({ className }) => /* @__PURE__ */ jsx4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ jsx4("path", { d: "m9 18 6-6-6-6" }) });
1192
+ var IconTrash = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1193
+ /* @__PURE__ */ jsx4("path", { d: "M3 6h18" }),
1194
+ /* @__PURE__ */ jsx4("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
1195
+ /* @__PURE__ */ jsx4("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
1196
+ /* @__PURE__ */ jsx4("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
1197
+ /* @__PURE__ */ jsx4("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
1198
+ ] });
1199
+ var IconX = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1200
+ /* @__PURE__ */ jsx4("path", { d: "M18 6 6 18" }),
1201
+ /* @__PURE__ */ jsx4("path", { d: "m6 6 12 12" })
1202
+ ] });
1203
+ var IconHome = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1204
+ /* @__PURE__ */ jsx4("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
1205
+ /* @__PURE__ */ jsx4("polyline", { points: "9 22 9 12 15 12 15 22" })
1206
+ ] });
1207
+ var IconImage = ({ className }) => /* @__PURE__ */ jsxs4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1208
+ /* @__PURE__ */ jsx4("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
1209
+ /* @__PURE__ */ jsx4("circle", { cx: "9", cy: "9", r: "2" }),
1210
+ /* @__PURE__ */ jsx4("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
1211
+ ] });
1212
+ var IconLoader = ({ className }) => /* @__PURE__ */ jsx4("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ jsx4("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
1008
1213
  function ImageManager(props) {
1009
1214
  const {
1010
1215
  open,
@@ -1024,209 +1229,485 @@ function ImageManager(props) {
1024
1229
  maxFileSize = 100 * 1024 * 1024,
1025
1230
  maxFiles = 10,
1026
1231
  mode = "full",
1027
- showDelete = true
1232
+ showDelete = true,
1233
+ refreshInterval = 5e3
1028
1234
  } = props;
1029
- const [tab, setTab] = React4.useState(mode === "upload" ? "upload" : "browse");
1235
+ const [activeView, setActiveView] = React4.useState(mode === "upload" ? "upload" : "browse");
1030
1236
  const [loading, setLoading] = React4.useState(false);
1031
1237
  const [error, setError] = React4.useState(null);
1032
- const [items, setItems] = React4.useState([]);
1238
+ const [currentFolderId, setCurrentFolderId] = React4.useState(void 0);
1239
+ const [breadcrumbs, setBreadcrumbs] = React4.useState([{ id: void 0, name: "Home" }]);
1240
+ const [files, setFiles] = React4.useState([]);
1241
+ const [folders, setFolders] = React4.useState([]);
1033
1242
  const [query, setQuery] = React4.useState("");
1034
1243
  const [deleting, setDeleting] = React4.useState(null);
1035
- const load = React4.useCallback(async () => {
1244
+ const [showCreateFolder, setShowCreateFolder] = React4.useState(false);
1245
+ const [newFolderName, setNewFolderName] = React4.useState("");
1246
+ const [creatingFolder, setCreatingFolder] = React4.useState(false);
1247
+ const [fileToDelete, setFileToDelete] = React4.useState(null);
1248
+ const [thumbnailSelectorFile, setThumbnailSelectorFile] = React4.useState(null);
1249
+ const lastUpdatedRef = React4.useRef(null);
1250
+ const isFetchingRef = React4.useRef(false);
1251
+ const load = React4.useCallback(async (isPolling = false) => {
1252
+ if (isFetchingRef.current) return;
1253
+ isFetchingRef.current = true;
1036
1254
  try {
1037
- setLoading(true);
1255
+ if (!isPolling) setLoading(true);
1038
1256
  setError(null);
1039
- const list = await fetchProjectImagesWithThumbs(clientOptions, { folderPath, limitConcurrent: 6 });
1040
- setItems(list);
1257
+ const client = new UpfilesClient(clientOptions);
1258
+ const currentPathPrefix = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
1259
+ const effectiveFolderPath = currentPathPrefix || folderPath;
1260
+ const [filesData, foldersData] = await Promise.all([
1261
+ fetchProjectImagesWithThumbs(clientOptions, { folderPath: effectiveFolderPath, limitConcurrent: 6 }),
1262
+ client.getFolders(currentFolderId)
1263
+ ]);
1264
+ const { items: list, updatedAt } = filesData;
1265
+ if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
1266
+ }
1267
+ setFiles(list);
1268
+ setFolders(foldersData);
1269
+ lastUpdatedRef.current = updatedAt;
1041
1270
  } catch (e) {
1042
- setError(e?.message || "Failed to load images");
1271
+ if (!isPolling) setError(e?.message || "Failed to load images");
1043
1272
  } finally {
1044
- setLoading(false);
1273
+ if (!isPolling) setLoading(false);
1274
+ isFetchingRef.current = false;
1045
1275
  }
1046
- }, [clientOptions, folderPath]);
1276
+ }, [clientOptions, folderPath, currentFolderId, breadcrumbs]);
1047
1277
  React4.useEffect(() => {
1048
1278
  if (!open) {
1049
- setItems([]);
1279
+ setFiles([]);
1280
+ setFolders([]);
1050
1281
  setQuery("");
1051
1282
  setError(null);
1283
+ lastUpdatedRef.current = null;
1052
1284
  return;
1053
1285
  }
1054
- if (mode !== "upload" && tab === "browse") {
1286
+ if (mode !== "upload" && activeView === "browse") {
1055
1287
  load();
1056
1288
  }
1057
- }, [open, tab, load, mode]);
1058
- const visible = React4.useMemo(() => {
1059
- const q = query.trim().toLowerCase();
1060
- return !q ? items : items.filter((i) => (i.originalName || "").toLowerCase().includes(q));
1061
- }, [items, query]);
1062
- const handleDelete = React4.useCallback(
1063
- async (key) => {
1064
- if (!confirm("Delete this file?")) return;
1289
+ }, [open, activeView, load, mode]);
1290
+ React4.useEffect(() => {
1291
+ if (!open) return;
1292
+ const client = new UpfilesClient(clientOptions);
1293
+ const url = client.getEventsUrl(projectId);
1294
+ const eventSource = new EventSource(url);
1295
+ eventSource.onmessage = (event) => {
1065
1296
  try {
1066
- setDeleting(key);
1067
- if (onDelete) {
1068
- await onDelete(key);
1069
- } else {
1070
- const baseUrl = clientOptions?.baseUrl || "";
1071
- const url = baseUrl ? `${baseUrl}${deleteUrl}` : deleteUrl;
1072
- const headers = {
1073
- "content-type": "application/json",
1074
- ...clientOptions?.headers || {}
1075
- };
1076
- if (clientOptions?.apiKey) {
1077
- const keyHeader = clientOptions.apiKeyHeader || "authorization";
1078
- if (keyHeader === "authorization") {
1079
- headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
1080
- } else {
1081
- headers[keyHeader] = clientOptions.apiKey;
1082
- }
1083
- }
1084
- const res = await fetch(url, {
1085
- method: "DELETE",
1086
- headers,
1087
- credentials: clientOptions?.withCredentials ? "include" : "same-origin",
1088
- body: JSON.stringify({ fileKey: key })
1297
+ const payload = JSON.parse(event.data);
1298
+ if (payload.type === "file:uploaded") {
1299
+ const newFile = payload.data.file;
1300
+ setFiles((prev) => {
1301
+ if (prev.find((f2) => f2.key === newFile.key)) return prev;
1302
+ return [newFile, ...prev];
1089
1303
  });
1090
- if (!res.ok) throw new Error("Delete failed");
1304
+ } else if (payload.type === "file:deleted") {
1305
+ const deletedKey = payload.data.fileKey || payload.data.file?.key;
1306
+ setFiles((prev) => prev.filter((f2) => f2.key !== deletedKey));
1091
1307
  }
1092
- setItems((prev) => prev.filter((f2) => f2.key !== key));
1093
1308
  } catch (e) {
1094
- alert(e?.message || "Failed to delete");
1095
- } finally {
1096
- setDeleting(null);
1309
+ console.error("SSE Error parsing message", e);
1097
1310
  }
1098
- },
1099
- [clientOptions, deleteUrl, onDelete]
1100
- );
1101
- return /* @__PURE__ */ jsx4(Dialog2.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs4(Dialog2.Portal, { children: [
1102
- /* @__PURE__ */ jsx4(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 dark:bg-black/60" }),
1103
- /* @__PURE__ */ jsx4(
1104
- Dialog2.Content,
1105
- {
1106
- className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-4xl rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4 shadow-lg max-h-[90vh] overflow-auto ${className ?? ""}`,
1107
- children: /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
1108
- /* @__PURE__ */ jsx4(Dialog2.Title, { className: "text-lg font-semibold text-gray-900 dark:text-white", children: title ?? "Image Manager" }),
1109
- /* @__PURE__ */ jsx4(Dialog2.Description, { className: "text-sm text-gray-600 dark:text-gray-400", children: description ?? "Upload new images or select from existing ones." }),
1110
- mode === "full" && /* @__PURE__ */ jsxs4("div", { className: "flex gap-2 border-b border-gray-200 dark:border-gray-700", children: [
1111
- /* @__PURE__ */ jsx4(
1112
- "button",
1113
- {
1114
- type: "button",
1115
- onClick: () => setTab("browse"),
1116
- className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === "browse" ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`,
1117
- children: "Browse"
1118
- }
1119
- ),
1120
- /* @__PURE__ */ jsx4(
1121
- "button",
1122
- {
1123
- type: "button",
1124
- onClick: () => setTab("upload"),
1125
- className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === "upload" ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`,
1126
- children: "Upload"
1127
- }
1128
- )
1129
- ] }),
1130
- (mode === "browse" || mode === "full" && tab === "browse") && /* @__PURE__ */ jsxs4(Fragment, { children: [
1131
- /* @__PURE__ */ jsxs4("div", { className: "flex gap-2 items-center", children: [
1132
- /* @__PURE__ */ jsx4(
1133
- "input",
1134
- {
1135
- className: "w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white px-3 py-2 text-sm placeholder:text-gray-500 dark:placeholder:text-gray-400",
1136
- placeholder: "Search by filename...",
1137
- value: query,
1138
- onChange: (e) => setQuery(e.target.value)
1139
- }
1140
- ),
1141
- /* @__PURE__ */ jsx4("button", { type: "button", className: "px-3 py-2 text-sm rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50", onClick: load, disabled: loading, children: loading ? "Loading\u2026" : "Refresh" })
1311
+ };
1312
+ return () => eventSource.close();
1313
+ }, [open, clientOptions, projectId]);
1314
+ const visibleFiles = React4.useMemo(() => {
1315
+ const q = query.trim().toLowerCase();
1316
+ return !q ? files : files.filter((i) => (i.originalName || "").toLowerCase().includes(q));
1317
+ }, [files, query]);
1318
+ const visibleFolders = React4.useMemo(() => {
1319
+ const q = query.trim().toLowerCase();
1320
+ return !q ? folders : folders.filter((f2) => (f2.name || "").toLowerCase().includes(q));
1321
+ }, [folders, query]);
1322
+ const handleDelete = async () => {
1323
+ if (!fileToDelete) return;
1324
+ const key = fileToDelete.key;
1325
+ try {
1326
+ setDeleting(key);
1327
+ if (onDelete) {
1328
+ await onDelete(key);
1329
+ } else {
1330
+ const client = new UpfilesClient(clientOptions);
1331
+ const baseUrl = clientOptions?.baseUrl || "";
1332
+ const url = baseUrl ? `${baseUrl}${deleteUrl}` : deleteUrl;
1333
+ const headers = {
1334
+ "content-type": "application/json",
1335
+ ...clientOptions?.headers || {}
1336
+ };
1337
+ if (clientOptions?.apiKey) {
1338
+ const keyHeader = clientOptions.apiKeyHeader || "authorization";
1339
+ if (keyHeader === "authorization") {
1340
+ headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
1341
+ } else {
1342
+ headers[keyHeader] = clientOptions.apiKey;
1343
+ }
1344
+ }
1345
+ const res = await fetch(url, {
1346
+ method: "DELETE",
1347
+ headers,
1348
+ credentials: clientOptions?.withCredentials ? "include" : "same-origin",
1349
+ body: JSON.stringify({ fileKey: key })
1350
+ });
1351
+ if (!res.ok) throw new Error("Delete failed");
1352
+ }
1353
+ setFiles((prev) => prev.filter((f2) => f2.key !== key));
1354
+ setFileToDelete(null);
1355
+ } catch (e) {
1356
+ alert(e?.message || "Failed to delete");
1357
+ } finally {
1358
+ setDeleting(null);
1359
+ }
1360
+ };
1361
+ const handleCreateFolder = async () => {
1362
+ if (!newFolderName.trim()) return;
1363
+ setCreatingFolder(true);
1364
+ try {
1365
+ const client = new UpfilesClient(clientOptions);
1366
+ await client.createFolder(newFolderName, currentFolderId);
1367
+ setNewFolderName("");
1368
+ setShowCreateFolder(false);
1369
+ load();
1370
+ } catch (e) {
1371
+ alert(e.message || "Failed to create folder");
1372
+ } finally {
1373
+ setCreatingFolder(false);
1374
+ }
1375
+ };
1376
+ const handleNavigate = (folder) => {
1377
+ setCurrentFolderId(folder.id);
1378
+ setBreadcrumbs((prev) => [...prev, { id: folder.id, name: folder.name }]);
1379
+ setQuery("");
1380
+ };
1381
+ const handleBreadcrumbClick = (index) => {
1382
+ const target = breadcrumbs[index];
1383
+ setBreadcrumbs(breadcrumbs.slice(0, index + 1));
1384
+ setCurrentFolderId(target.id);
1385
+ setQuery("");
1386
+ };
1387
+ const uploadFoldersList = React4.useMemo(() => {
1388
+ const options = [];
1389
+ options.push({ id: "root", name: "Root ( / )", path: void 0 });
1390
+ if (currentFolderId) {
1391
+ const currentPath = breadcrumbs.slice(1).map((b) => b.name).join("/") + "/";
1392
+ options.push({ id: "current", name: `Current ( ${breadcrumbs[breadcrumbs.length - 1].name} )`, path: currentPath });
1393
+ }
1394
+ folders.forEach((f2) => {
1395
+ const parentPath = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : "";
1396
+ options.push({ id: f2.id, name: f2.name, path: parentPath + f2.name + "/" });
1397
+ });
1398
+ return options;
1399
+ }, [breadcrumbs, currentFolderId, folders]);
1400
+ const [selectedUploadFolder, setSelectedUploadFolder] = React4.useState("current");
1401
+ const activeBreadcrumbPath = React4.useMemo(() => {
1402
+ return breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
1403
+ }, [breadcrumbs]);
1404
+ return /* @__PURE__ */ jsxs4(Dialog2.Root, { open, onOpenChange, children: [
1405
+ /* @__PURE__ */ jsxs4(Dialog2.Portal, { children: [
1406
+ /* @__PURE__ */ jsx4(Dialog2.Overlay, { className: "fixed inset-0 bg-gray-900/50 backdrop-blur-sm z-50 transition-all duration-300" }),
1407
+ /* @__PURE__ */ jsxs4(
1408
+ Dialog2.Content,
1409
+ {
1410
+ className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] h-[85vh] max-w-6xl rounded-2xl bg-white dark:bg-gray-950 shadow-2xl z-50 overflow-hidden flex flex-row ${className ?? ""}`,
1411
+ children: [
1412
+ /* @__PURE__ */ jsxs4("div", { className: "w-64 bg-gray-50 dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col flex-shrink-0", children: [
1413
+ /* @__PURE__ */ jsxs4("div", { className: "p-6", children: [
1414
+ /* @__PURE__ */ jsxs4("h2", { className: "text-lg font-bold text-gray-900 dark:text-white flex items-center gap-2", children: [
1415
+ /* @__PURE__ */ jsx4(IconImage, { className: "text-blue-600" }),
1416
+ title ?? "Media Library"
1417
+ ] }),
1418
+ /* @__PURE__ */ jsx4("p", { className: "text-xs text-gray-500 mt-1", children: description ?? "Manage your assets" })
1419
+ ] }),
1420
+ /* @__PURE__ */ jsxs4("nav", { className: "flex-1 px-3 space-y-1", children: [
1421
+ /* @__PURE__ */ jsxs4(
1422
+ "button",
1423
+ {
1424
+ onClick: () => setActiveView("browse"),
1425
+ className: `w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-colors ${activeView === "browse" ? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"}`,
1426
+ children: [
1427
+ /* @__PURE__ */ jsx4(IconGrid, { className: "w-4 h-4" }),
1428
+ "All Files"
1429
+ ]
1430
+ }
1431
+ ),
1432
+ mode === "full" && /* @__PURE__ */ jsxs4(
1433
+ "button",
1434
+ {
1435
+ onClick: () => setActiveView("upload"),
1436
+ className: `w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-colors ${activeView === "upload" ? "bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"}`,
1437
+ children: [
1438
+ /* @__PURE__ */ jsx4(IconUploadCloud, { className: "w-4 h-4" }),
1439
+ "Upload New"
1440
+ ]
1441
+ }
1442
+ )
1443
+ ] }),
1444
+ /* @__PURE__ */ jsx4("div", { className: "p-4 border-t border-gray-200 dark:border-gray-800", children: /* @__PURE__ */ jsxs4("div", { className: "text-xs text-center text-gray-400", children: [
1445
+ files.length,
1446
+ " files \u2022 ",
1447
+ folders.length,
1448
+ " folders"
1449
+ ] }) })
1142
1450
  ] }),
1143
- error && /* @__PURE__ */ jsx4("div", { className: "rounded border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 px-3 py-2 text-sm text-red-700 dark:text-red-400", children: error }),
1144
- /* @__PURE__ */ jsxs4("div", { className: gridClassName ?? "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 max-h-[50vh] overflow-auto", children: [
1145
- !loading && visible.map((f2) => {
1146
- const thumbUrl = f2.thumbnails?.[0]?.url || f2.url;
1147
- return /* @__PURE__ */ jsxs4("div", { className: "group relative aspect-square overflow-hidden rounded border border-gray-200 dark:border-gray-700", children: [
1148
- /* @__PURE__ */ jsx4("img", { src: thumbUrl, alt: f2.originalName, className: "h-full w-full object-cover" }),
1149
- /* @__PURE__ */ jsxs4("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-center justify-center gap-2", children: [
1451
+ /* @__PURE__ */ jsxs4("div", { className: "flex-1 flex flex-col bg-white dark:bg-gray-950 relative", children: [
1452
+ /* @__PURE__ */ jsxs4("div", { className: "h-16 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between px-6 gap-4", children: [
1453
+ /* @__PURE__ */ jsx4("div", { className: "flex items-center gap-4 flex-1", children: activeView === "browse" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1454
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center text-sm text-gray-500 overflow-hidden whitespace-nowrap", children: [
1455
+ /* @__PURE__ */ jsx4("button", { onClick: () => handleBreadcrumbClick(0), className: "hover:text-blue-600 transition-colors", children: /* @__PURE__ */ jsx4(IconHome, { className: "w-4 h-4" }) }),
1456
+ breadcrumbs.slice(1).map((b, i) => /* @__PURE__ */ jsxs4(React4.Fragment, { children: [
1457
+ /* @__PURE__ */ jsx4(IconChevronRight, { className: "w-4 h-4 text-gray-300 mx-1" }),
1458
+ /* @__PURE__ */ jsx4(
1459
+ "button",
1460
+ {
1461
+ onClick: () => handleBreadcrumbClick(i + 1),
1462
+ className: `hover:text-blue-600 transition-colors truncate max-w-[100px] ${i === breadcrumbs.length - 2 ? "font-semibold text-gray-900 dark:text-white" : ""}`,
1463
+ children: b.name
1464
+ }
1465
+ )
1466
+ ] }, b.id || i))
1467
+ ] }),
1468
+ /* @__PURE__ */ jsx4("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
1469
+ /* @__PURE__ */ jsx4(
1470
+ "button",
1471
+ {
1472
+ onClick: () => load(),
1473
+ disabled: loading,
1474
+ className: "p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-all disabled:opacity-50",
1475
+ title: "Refresh",
1476
+ children: /* @__PURE__ */ jsx4(IconRefresh, { className: `w-4 h-4 ${loading ? "animate-spin" : ""}` })
1477
+ }
1478
+ ),
1479
+ /* @__PURE__ */ jsx4("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
1480
+ /* @__PURE__ */ jsxs4("div", { className: "relative flex-1 max-w-sm group", children: [
1481
+ /* @__PURE__ */ jsx4(IconSearch, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 group-focus-within:text-blue-500 transition-colors" }),
1150
1482
  /* @__PURE__ */ jsx4(
1151
- "button",
1152
- {
1153
- type: "button",
1154
- onClick: () => {
1155
- onSelect({
1156
- url: f2.url,
1157
- key: f2.key,
1158
- originalName: f2.originalName,
1159
- size: f2.size,
1160
- contentType: f2.contentType,
1161
- thumbnails: f2.thumbnails
1162
- });
1163
- onOpenChange(false);
1164
- },
1165
- className: "opacity-0 group-hover:opacity-100 px-3 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded",
1166
- children: "Select"
1167
- }
1168
- ),
1169
- showDelete && /* @__PURE__ */ jsx4(
1170
- "button",
1483
+ "input",
1171
1484
  {
1172
- type: "button",
1173
- onClick: () => handleDelete(f2.key),
1174
- disabled: deleting === f2.key,
1175
- className: "opacity-0 group-hover:opacity-100 px-3 py-1 text-xs bg-red-600 hover:bg-red-700 text-white rounded disabled:opacity-50",
1176
- children: deleting === f2.key ? "..." : "Delete"
1485
+ value: query,
1486
+ onChange: (e) => setQuery(e.target.value),
1487
+ placeholder: "Search files...",
1488
+ className: "w-full pl-9 pr-4 py-1.5 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
1177
1489
  }
1178
1490
  )
1491
+ ] })
1492
+ ] }) }),
1493
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
1494
+ activeView === "browse" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1495
+ /* @__PURE__ */ jsx4("button", { onClick: () => setShowCreateFolder(true), className: "p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 rounded-lg transition-colors", title: "New Folder", children: /* @__PURE__ */ jsx4(IconFolderPlus, { className: "w-5 h-5" }) }),
1496
+ /* @__PURE__ */ jsx4("button", { onClick: () => load(), className: `p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/10 rounded-lg transition-colors ${loading ? "animate-spin text-blue-600" : ""}`, title: "Refresh", children: /* @__PURE__ */ jsx4(IconLoader, { className: "w-5 h-5" }) })
1497
+ ] }),
1498
+ /* @__PURE__ */ jsx4(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx4("button", { className: "ml-2 p-2 text-gray-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/10 rounded-lg transition-colors", children: /* @__PURE__ */ jsx4(IconX, { className: "w-5 h-5" }) }) })
1499
+ ] })
1500
+ ] }),
1501
+ /* @__PURE__ */ jsxs4("div", { className: "flex-1 overflow-y-auto p-6 bg-gray-50/30 dark:bg-gray-900/30", children: [
1502
+ activeView === "browse" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1503
+ error && /* @__PURE__ */ jsx4("div", { className: "mb-6 bg-red-50 text-red-600 p-4 rounded-lg text-sm border border-red-100 flex items-center gap-2", children: /* @__PURE__ */ jsx4("span", { children: error }) }),
1504
+ /* @__PURE__ */ jsxs4("div", { className: gridClassName ?? "grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6", children: [
1505
+ visibleFolders.map((folder) => /* @__PURE__ */ jsxs4(
1506
+ "div",
1507
+ {
1508
+ onClick: () => handleNavigate(folder),
1509
+ className: "group flex flex-col items-center gap-3 p-4 rounded-xl border border-gray-200/60 bg-white hover:border-blue-200 hover:shadow-lg hover:shadow-blue-500/5 hover:-translate-y-1 transition-all cursor-pointer",
1510
+ children: [
1511
+ /* @__PURE__ */ jsx4("div", { className: "w-16 h-12 bg-blue-50 rounded-lg flex items-center justify-center text-blue-500 group-hover:scale-110 transition-transform", children: /* @__PURE__ */ jsx4(IconFolder, { className: "w-8 h-8 drop-shadow-sm" }) }),
1512
+ /* @__PURE__ */ jsx4("span", { className: "text-sm font-medium text-gray-700 truncate w-full text-center", children: folder.name })
1513
+ ]
1514
+ },
1515
+ folder.id
1516
+ )),
1517
+ visibleFiles.map((f2) => {
1518
+ const fWithThumbs = f2;
1519
+ const bestThumb = fWithThumbs.thumbnails?.sort((a, b) => a.size - b.size).find((t) => t.size >= 300) || fWithThumbs.thumbnails?.[fWithThumbs.thumbnails?.length - 1];
1520
+ const thumbUrl = bestThumb?.url || f2.url;
1521
+ return /* @__PURE__ */ jsxs4(
1522
+ "div",
1523
+ {
1524
+ className: "group relative aspect-[4/3] bg-white rounded-xl border border-gray-200 overflow-hidden shadow-sm hover:shadow-xl hover:shadow-gray-200/50 hover:border-blue-300 transition-all duration-300",
1525
+ children: [
1526
+ /* @__PURE__ */ jsx4("img", { src: thumbUrl, alt: "", className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" }),
1527
+ /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex flex-col justify-end p-3", children: /* @__PURE__ */ jsxs4("div", { className: "flex gap-2 justify-center transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300", children: [
1528
+ /* @__PURE__ */ jsx4(
1529
+ "button",
1530
+ {
1531
+ onClick: () => {
1532
+ onSelect({
1533
+ url: f2.url,
1534
+ key: f2.key,
1535
+ originalName: f2.originalName,
1536
+ size: f2.size,
1537
+ contentType: f2.contentType,
1538
+ thumbnails: f2.thumbnails
1539
+ });
1540
+ onOpenChange(false);
1541
+ },
1542
+ className: "px-3 py-1.5 bg-blue-600 text-white text-xs font-semibold rounded-md hover:bg-blue-500 shadow-lg",
1543
+ children: "Select"
1544
+ }
1545
+ ),
1546
+ showDelete && /* @__PURE__ */ jsx4(
1547
+ "button",
1548
+ {
1549
+ onClick: (e) => {
1550
+ e.stopPropagation();
1551
+ setFileToDelete(f2);
1552
+ },
1553
+ className: "p-1.5 bg-white/20 text-white text-xs font-semibold rounded-md hover:bg-red-500 backdrop-blur-md transition-colors",
1554
+ children: /* @__PURE__ */ jsx4(IconTrash, { className: "w-4 h-4" })
1555
+ }
1556
+ )
1557
+ ] }) }),
1558
+ /* @__PURE__ */ jsx4("div", { className: "absolute top-2 right-2 bg-black/50 backdrop-blur-md text-white text-[10px] px-1.5 py-0.5 rounded opacity-0 group-hover:opacity-100 transition-opacity", children: f2.originalName.split(".").pop()?.toUpperCase() })
1559
+ ]
1560
+ },
1561
+ f2.key
1562
+ );
1563
+ })
1564
+ ] }),
1565
+ !loading && visibleFolders.length === 0 && visibleFiles.length === 0 && /* @__PURE__ */ jsxs4("div", { className: "h-full flex flex-col items-center justify-center text-gray-400", children: [
1566
+ /* @__PURE__ */ jsx4("div", { className: "w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mb-4", children: /* @__PURE__ */ jsx4(IconSearch, { className: "w-8 h-8 opacity-40" }) }),
1567
+ /* @__PURE__ */ jsx4("h3", { className: "text-gray-900 font-medium mb-1", children: "No Assets Found" }),
1568
+ /* @__PURE__ */ jsx4("p", { className: "text-sm", children: "Try uploading a file or creating a new folder." })
1569
+ ] })
1570
+ ] }),
1571
+ activeView === "upload" && /* @__PURE__ */ jsx4("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto", children: /* @__PURE__ */ jsxs4("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
1572
+ /* @__PURE__ */ jsxs4("h3", { className: "text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2", children: [
1573
+ /* @__PURE__ */ jsx4(IconUploadCloud, { className: "text-blue-500" }),
1574
+ "Upload Files"
1575
+ ] }),
1576
+ /* @__PURE__ */ jsxs4("div", { className: "mb-6", children: [
1577
+ /* @__PURE__ */ jsx4("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Target Folder" }),
1578
+ /* @__PURE__ */ jsxs4("div", { className: "flex gap-2", children: [
1579
+ /* @__PURE__ */ jsx4(
1580
+ "select",
1581
+ {
1582
+ value: selectedUploadFolder,
1583
+ onChange: (e) => setSelectedUploadFolder(e.target.value),
1584
+ className: "block w-full rounded-lg border-gray-200 bg-gray-50 text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-500 p-2.5",
1585
+ children: uploadFoldersList.map((opt) => /* @__PURE__ */ jsx4("option", { value: opt.id, children: opt.name }, opt.id))
1586
+ }
1587
+ ),
1588
+ /* @__PURE__ */ jsx4("button", { onClick: () => setShowCreateFolder(true), className: "px-3 bg-gray-100 hover:bg-gray-200 rounded-lg text-gray-600 transition-colors", children: /* @__PURE__ */ jsx4(IconFolderPlus, { className: "w-5 h-5" }) })
1589
+ ] })
1179
1590
  ] }),
1180
- /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 bg-black/50 p-1 text-[10px] text-white truncate", children: f2.originalName })
1181
- ] }, f2.key);
1182
- }),
1183
- loading && /* @__PURE__ */ jsx4("div", { className: "col-span-full text-sm text-gray-600 dark:text-gray-400", children: "Loading\u2026" }),
1184
- !loading && visible.length === 0 && /* @__PURE__ */ jsx4("div", { className: "col-span-full text-sm text-gray-600 dark:text-gray-400", children: "No images found" })
1591
+ /* @__PURE__ */ jsx4(
1592
+ Uploader,
1593
+ {
1594
+ clientOptions,
1595
+ projectId,
1596
+ folderPath: uploadFoldersList.find((o) => o.id === selectedUploadFolder)?.path || activeBreadcrumbPath || folderPath,
1597
+ accept: ["image/*"],
1598
+ maxFileSize,
1599
+ maxFiles,
1600
+ autoRecordToDb,
1601
+ fetchThumbnails,
1602
+ autoUpload: true,
1603
+ onClientUploadComplete: (file) => {
1604
+ if (file.status === "success" && file.publicUrl && file.fileKey) {
1605
+ const newItem = {
1606
+ key: file.fileKey,
1607
+ originalName: file.name,
1608
+ size: file.size,
1609
+ contentType: file.type,
1610
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
1611
+ url: file.publicUrl,
1612
+ thumbnails: file.thumbnails
1613
+ };
1614
+ setFiles((prev) => [newItem, ...prev]);
1615
+ }
1616
+ },
1617
+ onComplete: () => {
1618
+ if (mode === "full") {
1619
+ setTimeout(() => {
1620
+ setActiveView("browse");
1621
+ load();
1622
+ }, 1e3);
1623
+ }
1624
+ },
1625
+ dropzoneClassName: "border-2 border-dashed border-gray-300 bg-gray-50 rounded-xl p-12 text-center cursor-pointer hover:border-blue-500 hover:bg-blue-50/50 transition-all",
1626
+ buttonClassName: "hidden",
1627
+ children: /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-center gap-4", children: [
1628
+ /* @__PURE__ */ jsx4("div", { className: "w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx4(IconUploadCloud, { className: "w-6 h-6" }) }),
1629
+ /* @__PURE__ */ jsxs4("div", { children: [
1630
+ /* @__PURE__ */ jsx4("p", { className: "text-gray-900 font-medium", children: "Click to upload or drag and drop" }),
1631
+ /* @__PURE__ */ jsxs4("p", { className: "text-xs text-gray-500 mt-1", children: [
1632
+ "Supports PNG, JPG, GIF up to ",
1633
+ Math.round(maxFileSize / 1024 / 1024),
1634
+ "MB"
1635
+ ] })
1636
+ ] })
1637
+ ] })
1638
+ }
1639
+ )
1640
+ ] }) })
1641
+ ] })
1185
1642
  ] })
1643
+ ]
1644
+ }
1645
+ )
1646
+ ] }),
1647
+ /* @__PURE__ */ jsx4(Dialog2.Root, { open: showCreateFolder, onOpenChange: setShowCreateFolder, children: /* @__PURE__ */ jsxs4(Dialog2.Portal, { children: [
1648
+ /* @__PURE__ */ jsx4(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[60]" }),
1649
+ /* @__PURE__ */ jsxs4(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-sm rounded-xl bg-white p-6 shadow-2xl z-[61] outline-none", children: [
1650
+ /* @__PURE__ */ jsx4(Dialog2.Title, { className: "text-lg font-semibold text-gray-900 mb-4", children: "New Folder" }),
1651
+ /* @__PURE__ */ jsx4(
1652
+ "input",
1653
+ {
1654
+ autoFocus: true,
1655
+ className: "w-full rounded-lg border border-gray-200 bg-gray-50 px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none mb-6",
1656
+ placeholder: "Folder Name",
1657
+ value: newFolderName,
1658
+ onChange: (e) => setNewFolderName(e.target.value),
1659
+ onKeyDown: (e) => e.key === "Enter" && handleCreateFolder()
1660
+ }
1661
+ ),
1662
+ /* @__PURE__ */ jsxs4("div", { className: "flex justify-end gap-3", children: [
1663
+ /* @__PURE__ */ jsx4("button", { onClick: () => setShowCreateFolder(false), className: "px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100 rounded-lg", children: "Cancel" }),
1664
+ /* @__PURE__ */ jsx4(
1665
+ "button",
1666
+ {
1667
+ onClick: handleCreateFolder,
1668
+ disabled: creatingFolder || !newFolderName.trim(),
1669
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50",
1670
+ children: "Create"
1671
+ }
1672
+ )
1673
+ ] })
1674
+ ] })
1675
+ ] }) }),
1676
+ /* @__PURE__ */ jsx4(Dialog2.Root, { open: !!fileToDelete, onOpenChange: (open2) => !open2 && setFileToDelete(null), children: /* @__PURE__ */ jsxs4(Dialog2.Portal, { children: [
1677
+ /* @__PURE__ */ jsx4(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[80]" }),
1678
+ /* @__PURE__ */ jsxs4(Dialog2.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md rounded-xl bg-white p-6 shadow-2xl z-[81]", children: [
1679
+ /* @__PURE__ */ jsx4(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Delete File" }),
1680
+ /* @__PURE__ */ jsxs4(Dialog2.Description, { className: "text-gray-600 mb-6", children: [
1681
+ "Are you sure you want to delete ",
1682
+ /* @__PURE__ */ jsxs4("span", { className: "font-semibold text-gray-900", children: [
1683
+ '"',
1684
+ fileToDelete?.originalName,
1685
+ '"'
1186
1686
  ] }),
1187
- (mode === "upload" || mode === "full" && tab === "upload") && /* @__PURE__ */ jsx4(
1188
- Uploader,
1687
+ "?"
1688
+ ] }),
1689
+ /* @__PURE__ */ jsxs4("div", { className: "flex justify-end gap-3", children: [
1690
+ /* @__PURE__ */ jsx4(
1691
+ "button",
1189
1692
  {
1190
- clientOptions,
1191
- projectId,
1192
- folderPath,
1193
- accept: ["image/*"],
1194
- maxFileSize,
1195
- maxFiles,
1196
- autoRecordToDb,
1197
- fetchThumbnails,
1198
- autoUpload: true,
1199
- onClientUploadComplete: (file) => {
1200
- if (file.status === "success" && file.publicUrl && file.fileKey) {
1201
- const newItem = {
1202
- key: file.fileKey,
1203
- originalName: file.name,
1204
- size: file.size,
1205
- contentType: file.type,
1206
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
1207
- url: file.publicUrl,
1208
- thumbnails: file.thumbnails
1209
- };
1210
- setItems((prev) => [newItem, ...prev]);
1211
- }
1212
- },
1213
- onComplete: (files) => {
1214
- if (mode === "full") {
1215
- setTimeout(() => {
1216
- load();
1217
- setTab("browse");
1218
- }, 500);
1219
- }
1220
- },
1221
- dropzoneClassName: "border-2 border-dashed border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-700/50 rounded-lg p-8 text-center cursor-pointer hover:border-blue-500 dark:hover:border-blue-400 transition-colors text-gray-700 dark:text-gray-300",
1222
- buttonClassName: "px-4 py-2 text-sm rounded border border-gray-300 dark:border-gray-600 bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50"
1693
+ onClick: () => setFileToDelete(null),
1694
+ className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg",
1695
+ children: "Cancel"
1223
1696
  }
1224
1697
  ),
1225
- /* @__PURE__ */ jsx4("div", { className: "flex justify-end gap-2 pt-2 border-t border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsx4(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ jsx4("button", { type: "button", className: "px-4 py-2 text-sm rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600", children: "Close" }) }) })
1698
+ /* @__PURE__ */ jsx4(
1699
+ "button",
1700
+ {
1701
+ onClick: handleDelete,
1702
+ disabled: !!deleting,
1703
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-red-600 text-white hover:bg-red-700 disabled:opacity-70",
1704
+ children: deleting ? "Deleting..." : "Delete"
1705
+ }
1706
+ )
1226
1707
  ] })
1227
- }
1228
- )
1229
- ] }) });
1708
+ ] })
1709
+ ] }) })
1710
+ ] });
1230
1711
  }
1231
1712
  export {
1232
1713
  ConnectProjectDialog,