@thetechfossil/upfiles 1.0.11 → 1.0.13

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.js CHANGED
@@ -1,85 +1,111 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
1
+ 'use strict';
2
+
3
+ var React4 = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var Dialog2 = require('@radix-ui/react-dialog');
6
+
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
17
20
  }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+ n.default = e;
22
+ return Object.freeze(n);
23
+ }
29
24
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- ConnectProjectDialog: () => ConnectProjectDialog,
34
- ImageManager: () => ImageManager,
35
- ProjectFilesWidget: () => ProjectFilesWidget,
36
- UpfilesClient: () => UpfilesClient,
37
- Uploader: () => Uploader,
38
- addApiKeyManually: () => addApiKeyManually,
39
- connectProject: () => connectProject,
40
- connectUsingApiKey: () => connectUsingApiKey,
41
- createClientWithKey: () => createClientWithKey,
42
- createProject: () => createProject,
43
- fetchFileThumbnails: () => fetchFileThumbnails,
44
- fetchProjectFiles: () => fetchProjectFiles,
45
- fetchProjectImagesWithThumbs: () => fetchProjectImagesWithThumbs,
46
- listProjects: () => listProjects
47
- });
48
- module.exports = __toCommonJS(index_exports);
25
+ var React4__namespace = /*#__PURE__*/_interopNamespace(React4);
26
+ var Dialog2__namespace = /*#__PURE__*/_interopNamespace(Dialog2);
49
27
 
50
28
  // src/client.ts
29
+ var StorageQuotaError = class extends Error {
30
+ constructor(message, details) {
31
+ super(message);
32
+ this.name = "StorageQuotaError";
33
+ this.details = details;
34
+ }
35
+ };
51
36
  var UpfilesClient = class {
52
37
  constructor(opts) {
53
38
  this.baseUrl = opts.baseUrl?.replace(/\/$/, "");
54
- this.presignPath = opts.presignPath ?? "/api/plugin/upload/presigned-url";
39
+ this.presignPath = opts.presignPath ?? "/api/plugin/upload";
55
40
  this.presignUrl = opts.presignUrl;
56
41
  this.headers = opts.headers;
57
42
  this.withCredentials = opts.withCredentials;
58
43
  this.apiKey = opts.apiKey;
59
44
  this.apiKeyHeader = opts.apiKeyHeader ?? "authorization";
60
45
  this.thumbnailsPath = opts.thumbnailsPath ?? "/api/plugin/thumbnails";
61
- this.completionPath = opts.completionPath ?? "/api/plugin/upload/complete";
46
+ this.completionPath = opts.completionPath ?? "/api/plugin/files";
62
47
  this.foldersPath = opts.foldersPath ?? "/api/plugin/folders";
63
48
  this.callCompletionEndpoint = opts.callCompletionEndpoint ?? false;
49
+ this.getToken = opts.getToken;
50
+ this.projectId = opts.projectId;
51
+ this.projectName = opts.projectName;
64
52
  }
65
- async getPresignedUrl(params) {
66
- if (!params.fileName) throw new Error("fileName is required");
67
- if (!params.fileType) throw new Error("fileType is required");
68
- if (!params.fileSize || params.fileSize <= 0) throw new Error("fileSize must be > 0");
69
- const target = this.presignUrl ? this.presignUrl : this.baseUrl ? `${this.baseUrl}${this.presignPath}` : this.presignPath;
70
- const headers = {
71
- "content-type": "application/json",
72
- ...this.headers || {}
73
- };
53
+ /** Build auth headers for API requests. Priority: getToken (JWT) > apiKey > static headers */
54
+ async buildAuthHeaders() {
55
+ const headers = { ...this.headers || {} };
56
+ if (this.getToken) {
57
+ const token = await this.getToken();
58
+ if (token) {
59
+ headers["authorization"] = `Bearer ${token}`;
60
+ }
61
+ if (this.projectName) {
62
+ headers["x-project-name"] = this.projectName;
63
+ } else if (this.projectId) {
64
+ headers["x-project-id"] = this.projectId;
65
+ }
66
+ return headers;
67
+ }
74
68
  if (this.apiKey) {
75
69
  if (this.apiKeyHeader === "authorization") {
76
- headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
70
+ headers["authorization"] = this.apiKey.startsWith("upf_") ? `Bearer ${this.apiKey}` : this.apiKey;
77
71
  } else if (this.apiKeyHeader === "x-api-key") {
78
72
  headers["x-api-key"] = this.apiKey;
79
73
  } else {
80
74
  headers["x-up-api-key"] = this.apiKey;
81
75
  }
82
76
  }
77
+ return headers;
78
+ }
79
+ /** Get storage quota info for the project */
80
+ async getQuota() {
81
+ const basePath = this.presignPath.replace(/\/upload$/, "");
82
+ const target = this.baseUrl ? `${this.baseUrl}${basePath}/quota` : `${basePath}/quota`;
83
+ const headers = await this.buildAuthHeaders();
84
+ const res = await fetch(target, {
85
+ method: "GET",
86
+ headers,
87
+ credentials: this.withCredentials ? "include" : "same-origin"
88
+ });
89
+ if (!res.ok) {
90
+ let msg = "Failed to get storage quota";
91
+ try {
92
+ const data = await res.json();
93
+ msg = data.message || data.error || msg;
94
+ } catch {
95
+ }
96
+ throw new Error(msg);
97
+ }
98
+ return res.json();
99
+ }
100
+ async getPresignedUrl(params) {
101
+ if (!params.fileName) throw new Error("fileName is required");
102
+ if (!params.fileType) throw new Error("fileType is required");
103
+ if (!params.fileSize || params.fileSize <= 0) throw new Error("fileSize must be > 0");
104
+ const target = this.presignUrl ? this.presignUrl : this.baseUrl ? `${this.baseUrl}${this.presignPath}` : this.presignPath;
105
+ const headers = {
106
+ "content-type": "application/json",
107
+ ...await this.buildAuthHeaders()
108
+ };
83
109
  const res = await fetch(target, {
84
110
  method: "POST",
85
111
  headers,
@@ -90,8 +116,12 @@ var UpfilesClient = class {
90
116
  let msg = "Failed to get upload URL";
91
117
  try {
92
118
  const data = await res.json();
93
- msg = data.error || msg;
94
- } catch {
119
+ if (res.status === 413 && data.error === "STORAGE_QUOTA_EXCEEDED") {
120
+ throw new StorageQuotaError(data.message || "Storage quota exceeded", data.details);
121
+ }
122
+ msg = data.message || data.error || msg;
123
+ } catch (e) {
124
+ if (e instanceof StorageQuotaError) throw e;
95
125
  }
96
126
  throw new Error(msg);
97
127
  }
@@ -127,7 +157,11 @@ var UpfilesClient = class {
127
157
  originalName: file.name,
128
158
  size: file.size,
129
159
  contentType: file.type || "application/octet-stream",
130
- projectId: extras?.projectId
160
+ projectId: extras?.projectId,
161
+ url: presign.publicUrl,
162
+ userId: extras?.userId
163
+ // userId might be passed in extras but not typed yet in this method signature, easy fix is to cast or update signature. Better to update signature but for now access via cast to keep change minimal if extras type isn't fully updated here locally. Wait, the plan said "Update upload method to pass...". I should probably check if I can update extras type locally or just cast. The `upload` method signature has `extras`. I should probably update `extras` type in the signature if I can.
164
+ // Actually, looking at the code, `extras` is defined inline: `extras?: { projectId?: string; folderPath?: string; fetchThumbnails?: boolean; callCompletionEndpoint?: boolean }`. I should update this type definition too.
131
165
  });
132
166
  } catch (error) {
133
167
  throw new Error(`Upload verification failed: ${error.message}`);
@@ -156,22 +190,16 @@ var UpfilesClient = class {
156
190
  const target = this.baseUrl ? `${this.baseUrl}${this.completionPath}` : this.completionPath;
157
191
  const headers = {
158
192
  "content-type": "application/json",
159
- ...this.headers || {}
193
+ ...await this.buildAuthHeaders()
160
194
  };
161
- if (this.apiKey) {
162
- if (this.apiKeyHeader === "authorization") {
163
- headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
164
- } else if (this.apiKeyHeader === "x-api-key") {
165
- headers["x-api-key"] = this.apiKey;
166
- } else {
167
- headers["x-up-api-key"] = this.apiKey;
168
- }
169
- }
170
195
  const res = await fetch(target, {
171
196
  method: "POST",
172
197
  headers,
173
198
  credentials: this.withCredentials ? "include" : "same-origin",
174
- body: JSON.stringify(params)
199
+ body: JSON.stringify({
200
+ ...params,
201
+ key: params.fileKey
202
+ })
175
203
  });
176
204
  if (!res.ok) {
177
205
  let msg = "Failed to complete upload";
@@ -187,16 +215,7 @@ var UpfilesClient = class {
187
215
  async getThumbnails(fileKey) {
188
216
  const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
189
217
  const url = `${target}?fileKey=${encodeURIComponent(fileKey)}`;
190
- const headers = { ...this.headers || {} };
191
- if (this.apiKey) {
192
- if (this.apiKeyHeader === "authorization") {
193
- headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
194
- } else if (this.apiKeyHeader === "x-api-key") {
195
- headers["x-api-key"] = this.apiKey;
196
- } else {
197
- headers["x-up-api-key"] = this.apiKey;
198
- }
199
- }
218
+ const headers = await this.buildAuthHeaders();
200
219
  const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
201
220
  if (!res.ok) {
202
221
  let msg = "Failed to fetch thumbnails";
@@ -210,26 +229,19 @@ var UpfilesClient = class {
210
229
  const data = await res.json();
211
230
  return data?.thumbnails ?? [];
212
231
  }
213
- async generateThumbnails(fileKey) {
232
+ async generateThumbnails(fileKey, sizes) {
214
233
  const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
215
234
  const headers = {
216
235
  "content-type": "application/json",
217
- ...this.headers || {}
236
+ ...await this.buildAuthHeaders()
218
237
  };
219
- if (this.apiKey) {
220
- if (this.apiKeyHeader === "authorization") {
221
- headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
222
- } else if (this.apiKeyHeader === "x-api-key") {
223
- headers["x-api-key"] = this.apiKey;
224
- } else {
225
- headers["x-up-api-key"] = this.apiKey;
226
- }
227
- }
238
+ const body = { fileKey };
239
+ if (sizes && sizes.length > 0) body.sizes = sizes;
228
240
  const res = await fetch(target, {
229
241
  method: "POST",
230
242
  headers,
231
243
  credentials: this.withCredentials ? "include" : "same-origin",
232
- body: JSON.stringify({ fileKey })
244
+ body: JSON.stringify(body)
233
245
  });
234
246
  if (!res.ok) {
235
247
  let msg = "Failed to generate thumbnails";
@@ -243,19 +255,53 @@ var UpfilesClient = class {
243
255
  const data = await res.json();
244
256
  return { masterWebp: data.masterWebp, thumbnails: data.thumbnails ?? [] };
245
257
  }
258
+ /** Get thumbnail settings for the project (requires API key) */
259
+ async getThumbnailSettings() {
260
+ const basePath = this.presignPath.replace(/\/upload$/, "");
261
+ const target = this.baseUrl ? `${this.baseUrl}${basePath}/settings/thumbnails` : `${basePath}/settings/thumbnails`;
262
+ const headers = await this.buildAuthHeaders();
263
+ const res = await fetch(target, {
264
+ method: "GET",
265
+ headers,
266
+ credentials: this.withCredentials ? "include" : "same-origin"
267
+ });
268
+ if (!res.ok) {
269
+ let msg = "Failed to get thumbnail settings";
270
+ try {
271
+ const d = await res.json();
272
+ msg = d.message || msg;
273
+ } catch {
274
+ }
275
+ throw new Error(msg);
276
+ }
277
+ return res.json();
278
+ }
279
+ /** Update thumbnail settings for the project (requires API key with write scope) */
280
+ async updateThumbnailSettings(settings) {
281
+ const basePath = this.presignPath.replace(/\/upload$/, "");
282
+ const target = this.baseUrl ? `${this.baseUrl}${basePath}/settings/thumbnails` : `${basePath}/settings/thumbnails`;
283
+ const headers = { "content-type": "application/json", ...await this.buildAuthHeaders() };
284
+ const res = await fetch(target, {
285
+ method: "PATCH",
286
+ headers,
287
+ credentials: this.withCredentials ? "include" : "same-origin",
288
+ body: JSON.stringify(settings)
289
+ });
290
+ if (!res.ok) {
291
+ let msg = "Failed to update thumbnail settings";
292
+ try {
293
+ const d = await res.json();
294
+ msg = d.message || msg;
295
+ } catch {
296
+ }
297
+ throw new Error(msg);
298
+ }
299
+ return res.json();
300
+ }
246
301
  async getFolders(parentId) {
247
302
  const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
248
303
  const url = parentId ? `${target}?parentId=${encodeURIComponent(parentId)}` : target;
249
- const headers = { ...this.headers || {} };
250
- if (this.apiKey) {
251
- if (this.apiKeyHeader === "authorization") {
252
- headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
253
- } else if (this.apiKeyHeader === "x-api-key") {
254
- headers["x-api-key"] = this.apiKey;
255
- } else {
256
- headers["x-up-api-key"] = this.apiKey;
257
- }
258
- }
304
+ const headers = await this.buildAuthHeaders();
259
305
  const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
260
306
  if (!res.ok) {
261
307
  let msg = "Failed to fetch folders";
@@ -273,17 +319,8 @@ var UpfilesClient = class {
273
319
  const target = this.baseUrl ? `${this.baseUrl}${this.foldersPath}` : this.foldersPath;
274
320
  const headers = {
275
321
  "content-type": "application/json",
276
- ...this.headers || {}
322
+ ...await this.buildAuthHeaders()
277
323
  };
278
- if (this.apiKey) {
279
- if (this.apiKeyHeader === "authorization") {
280
- headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
281
- } else if (this.apiKeyHeader === "x-api-key") {
282
- headers["x-api-key"] = this.apiKey;
283
- } else {
284
- headers["x-up-api-key"] = this.apiKey;
285
- }
286
- }
287
324
  const res = await fetch(target, {
288
325
  method: "POST",
289
326
  headers,
@@ -307,16 +344,7 @@ var UpfilesClient = class {
307
344
  const filesPath = `${basePath}/files`;
308
345
  const target = this.baseUrl ? `${this.baseUrl}${filesPath}` : filesPath;
309
346
  const url = params?.folderPath ? `${target}?folderPath=${encodeURIComponent(params.folderPath)}` : target;
310
- const headers = { ...this.headers || {} };
311
- if (this.apiKey) {
312
- if (this.apiKeyHeader === "authorization") {
313
- headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
314
- } else if (this.apiKeyHeader === "x-api-key") {
315
- headers["x-api-key"] = this.apiKey;
316
- } else {
317
- headers["x-up-api-key"] = this.apiKey;
318
- }
319
- }
347
+ const headers = await this.buildAuthHeaders();
320
348
  const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
321
349
  if (!res.ok) {
322
350
  let msg = "Failed to fetch files";
@@ -334,29 +362,21 @@ var UpfilesClient = class {
334
362
  };
335
363
  }
336
364
  getEventsUrl(projectId) {
337
- const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
338
- const eventsPath = `${basePath}/events`;
339
365
  const target = this.baseUrl ? `${this.baseUrl}/api/events` : "/api/events";
340
366
  const url = new URL(target, window.location.href);
341
- if (this.apiKey) {
342
- url.searchParams.set("apiKey", this.apiKey);
343
- }
344
- if (projectId) {
345
- url.searchParams.set("projectId", projectId);
367
+ if (!this.getToken && !this.apiKey && (projectId || this.projectId)) {
368
+ url.searchParams.set("projectId", projectId || this.projectId);
346
369
  }
347
370
  return url.toString();
348
371
  }
349
372
  };
350
-
351
- // src/Uploader.tsx
352
- var import_react = __toESM(require("react"));
353
- var import_jsx_runtime = require("react/jsx-runtime");
373
+ var THUMBNAIL_SIZE_OPTIONS = [64, 128, 256, 512];
354
374
  var Uploader = ({
355
375
  clientOptions,
356
376
  multiple = true,
357
377
  accept = ["image/*", "video/*", "audio/*", "application/pdf", "text/plain", "application/json"],
358
- maxFileSize = 100 * 1024 * 1024,
359
- maxFiles = 10,
378
+ maxFileSize = 0,
379
+ maxFiles = 0,
360
380
  onComplete,
361
381
  onError,
362
382
  onUploadBegin,
@@ -367,48 +387,136 @@ var Uploader = ({
367
387
  dropzoneClassName,
368
388
  children,
369
389
  projectId,
390
+ userId,
370
391
  folderPath,
371
392
  fetchThumbnails,
372
393
  autoRecordToDb = true,
373
394
  recordUrl = "/api/files",
374
- autoUpload = false
395
+ autoUpload = false,
396
+ thumbnailEnabled,
397
+ thumbnailSizes
375
398
  }) => {
376
- const inputRef = (0, import_react.useRef)(null);
377
- const client = (0, import_react.useMemo)(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
378
- const [files, setFiles] = (0, import_react.useState)([]);
379
- const [isUploading, setIsUploading] = (0, import_react.useState)(false);
380
- const [isDragOver, setIsDragOver] = (0, import_react.useState)(false);
381
- const addFiles = (0, import_react.useCallback)((incoming) => {
382
- const filtered = incoming.filter((f2) => {
383
- if (f2.size > maxFileSize) {
384
- return false;
385
- }
386
- return true;
399
+ const inputRef = React4.useRef(null);
400
+ const client = React4.useMemo(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
401
+ const [files, setFiles] = React4.useState([]);
402
+ const [isUploading, setIsUploading] = React4.useState(false);
403
+ const [isDragOver, setIsDragOver] = React4.useState(false);
404
+ const [quotaError, setQuotaError] = React4.useState(null);
405
+ const showThumbnailUI = thumbnailEnabled !== void 0;
406
+ const [thumbEnabled, setThumbEnabled] = React4.useState(thumbnailEnabled ?? true);
407
+ const [thumbSizes, setThumbSizes] = React4.useState(thumbnailSizes ?? [256, 512]);
408
+ const [thumbFileIds, setThumbFileIds] = React4.useState(/* @__PURE__ */ new Set());
409
+ const thumbEnabledRef = React4.useRef(thumbnailEnabled ?? true);
410
+ const thumbSizesRef = React4.useRef(thumbnailSizes ?? [256, 512]);
411
+ const thumbFileIdsRef = React4.useRef(/* @__PURE__ */ new Set());
412
+ React4.useEffect(() => {
413
+ thumbEnabledRef.current = thumbEnabled;
414
+ }, [thumbEnabled]);
415
+ React4.useEffect(() => {
416
+ thumbSizesRef.current = thumbSizes;
417
+ }, [thumbSizes]);
418
+ React4.useEffect(() => {
419
+ thumbFileIdsRef.current = thumbFileIds;
420
+ }, [thumbFileIds]);
421
+ const pendingImages = React4.useMemo(
422
+ () => files.filter((f2) => f2.type.startsWith("image/") && f2.status === "pending"),
423
+ [files]
424
+ );
425
+ const thumbSelectedCount = React4.useMemo(
426
+ () => pendingImages.filter((f2) => thumbFileIds.has(f2.id)).length,
427
+ [pendingImages, thumbFileIds]
428
+ );
429
+ const handleThumbEnabledToggle = React4.useCallback(() => {
430
+ const newVal = !thumbEnabledRef.current;
431
+ thumbEnabledRef.current = newVal;
432
+ setThumbEnabled(newVal);
433
+ if (newVal) {
434
+ setFiles((prev) => {
435
+ const ids = prev.filter((f2) => f2.type.startsWith("image/") && f2.status === "pending").map((f2) => f2.id);
436
+ if (ids.length > 0) {
437
+ const next = new Set(thumbFileIdsRef.current);
438
+ ids.forEach((id) => next.add(id));
439
+ thumbFileIdsRef.current = next;
440
+ setThumbFileIds(next);
441
+ }
442
+ return prev;
443
+ });
444
+ }
445
+ }, []);
446
+ const toggleThumbSize = React4.useCallback((size) => {
447
+ setThumbSizes((prev) => {
448
+ const next = prev.includes(size) ? prev.filter((s) => s !== size) : [...prev, size].sort((a, b) => a - b);
449
+ thumbSizesRef.current = next;
450
+ return next;
451
+ });
452
+ }, []);
453
+ const toggleFileThumb = React4.useCallback((fileId) => {
454
+ setThumbFileIds((prev) => {
455
+ const next = new Set(prev);
456
+ if (next.has(fileId)) next.delete(fileId);
457
+ else next.add(fileId);
458
+ thumbFileIdsRef.current = next;
459
+ return next;
387
460
  });
388
- const limited = filtered.slice(0, Math.max(0, maxFiles - files.length));
461
+ }, []);
462
+ const selectAllThumbs = React4.useCallback(() => {
463
+ const ids = pendingImages.map((f2) => f2.id);
464
+ const next = new Set(thumbFileIdsRef.current);
465
+ ids.forEach((id) => next.add(id));
466
+ thumbFileIdsRef.current = next;
467
+ setThumbFileIds(next);
468
+ }, [pendingImages]);
469
+ const deselectAllThumbs = React4.useCallback(() => {
470
+ const pendingSet = new Set(pendingImages.map((f2) => f2.id));
471
+ const next = new Set([...thumbFileIdsRef.current].filter((id) => !pendingSet.has(id)));
472
+ thumbFileIdsRef.current = next;
473
+ setThumbFileIds(next);
474
+ }, [pendingImages]);
475
+ const addFiles = React4.useCallback((incoming) => {
476
+ setQuotaError(null);
477
+ const filtered = maxFileSize > 0 ? incoming.filter((f2) => f2.size <= maxFileSize) : incoming;
478
+ const limited = maxFiles > 0 ? filtered.slice(0, Math.max(0, maxFiles - files.length)) : filtered;
389
479
  const mapped = limited.map((f2) => ({
390
480
  id: Math.random().toString(36).slice(2),
391
481
  file: f2,
392
482
  name: f2.name,
393
483
  size: f2.size,
394
- type: f2.type,
484
+ type: f2.type || "application/octet-stream",
395
485
  progress: 0,
396
486
  status: "pending"
397
487
  }));
398
488
  setFiles((prev) => [...prev, ...mapped]);
399
- }, [files.length, maxFileSize, maxFiles]);
400
- const onInputChange = (0, import_react.useCallback)((e) => {
489
+ if (showThumbnailUI && thumbEnabledRef.current) {
490
+ const imageIds = mapped.filter((f2) => f2.type.startsWith("image/")).map((f2) => f2.id);
491
+ if (imageIds.length > 0) {
492
+ const next = new Set(thumbFileIdsRef.current);
493
+ imageIds.forEach((id) => next.add(id));
494
+ thumbFileIdsRef.current = next;
495
+ setThumbFileIds(next);
496
+ }
497
+ }
498
+ }, [files.length, maxFileSize, maxFiles, showThumbnailUI]);
499
+ const removeFile = React4.useCallback((fileId) => {
500
+ setFiles((prev) => prev.filter((f2) => f2.id !== fileId));
501
+ if (showThumbnailUI) {
502
+ const next = new Set(thumbFileIdsRef.current);
503
+ next.delete(fileId);
504
+ thumbFileIdsRef.current = next;
505
+ setThumbFileIds(next);
506
+ }
507
+ }, [showThumbnailUI]);
508
+ const onInputChange = React4.useCallback((e) => {
401
509
  const fs = Array.from(e.target.files || []);
402
510
  addFiles(fs);
403
511
  if (inputRef.current) inputRef.current.value = "";
404
512
  }, [addFiles]);
405
- const handleDrop = (0, import_react.useCallback)((e) => {
513
+ const handleDrop = React4.useCallback((e) => {
406
514
  e.preventDefault();
407
515
  e.stopPropagation();
408
516
  setIsDragOver(false);
409
517
  addFiles(Array.from(e.dataTransfer.files || []));
410
518
  }, [addFiles]);
411
- const uploadOne = (0, import_react.useCallback)(async (item) => {
519
+ const uploadOne = React4.useCallback(async (item) => {
412
520
  const update = (patch) => {
413
521
  setFiles((prev) => prev.map((f2) => f2.id === item.id ? { ...f2, ...patch } : f2));
414
522
  };
@@ -417,7 +525,7 @@ var Uploader = ({
417
525
  update({ status: "uploading", progress: 1 });
418
526
  const presign = await client.getPresignedUrl({
419
527
  fileName: item.name,
420
- fileType: item.type,
528
+ fileType: item.type || "application/octet-stream",
421
529
  fileSize: item.size,
422
530
  projectId,
423
531
  folderPath
@@ -444,14 +552,17 @@ var Uploader = ({
444
552
  xhr.send(item.file);
445
553
  });
446
554
  let completionResult = null;
447
- if (autoRecordToDb && projectId) {
555
+ const canRecordToDb = autoRecordToDb && (projectId || clientOptions?.projectName || clientOptions?.projectId || clientOptions?.apiKey);
556
+ if (canRecordToDb) {
448
557
  try {
449
558
  completionResult = await client.completeUpload({
450
559
  fileKey: presign.fileKey,
451
560
  originalName: item.name,
452
561
  size: item.size,
453
- contentType: item.type,
454
- projectId
562
+ contentType: item.type || "application/octet-stream",
563
+ projectId,
564
+ url: presign.publicUrl,
565
+ userId
455
566
  });
456
567
  } catch (e) {
457
568
  console.error("Failed to complete upload:", e);
@@ -475,39 +586,86 @@ var Uploader = ({
475
586
  }
476
587
  const finalFileKey = completionResult?.file?.key || presign.fileKey;
477
588
  const finalPublicUrl = completionResult?.file?.url || presign.publicUrl;
589
+ const fileThumbEnabled = showThumbnailUI && thumbEnabledRef.current && thumbFileIdsRef.current.has(item.id);
478
590
  const result = {
479
591
  ...item,
480
592
  status: "success",
481
593
  progress: 100,
482
- fileKey: presign.fileKey,
483
- publicUrl: presign.publicUrl,
594
+ fileKey: finalFileKey,
595
+ publicUrl: finalPublicUrl,
484
596
  projectId: presign.projectId,
485
597
  apiKeyId: presign.apiKeyId,
598
+ thumbnailEnabled: showThumbnailUI ? fileThumbEnabled : void 0,
599
+ thumbnailSizesSelected: showThumbnailUI ? fileThumbEnabled ? [...thumbSizesRef.current] : [] : void 0,
486
600
  // @ts-ignore - allow downstream to access thumbs if present
487
601
  thumbnails: thumbs,
488
602
  // @ts-ignore - allow downstream to access masterWebp if present
489
603
  masterWebp
490
604
  };
491
- update({ status: "success", progress: 100, fileKey: presign.fileKey, publicUrl: presign.publicUrl });
605
+ update({ status: "success", progress: 100, fileKey: finalFileKey, publicUrl: finalPublicUrl });
492
606
  onClientUploadComplete?.(result);
493
607
  return result;
494
608
  } catch (err) {
495
- const message = err instanceof Error ? err.message : "Upload failed";
609
+ const message = err instanceof StorageQuotaError ? err.message : err instanceof Error ? err.message : "Upload failed";
496
610
  update({ status: "error", error: message });
497
611
  throw err;
498
612
  }
499
- }, [client, projectId, folderPath, fetchThumbnails, autoRecordToDb, recordUrl, clientOptions, onUploadBegin, onUploadProgress, onClientUploadComplete]);
500
- const uploadAll = (0, import_react.useCallback)(async () => {
613
+ }, [client, projectId, userId, folderPath, fetchThumbnails, autoRecordToDb, recordUrl, clientOptions, onUploadBegin, onUploadProgress, onClientUploadComplete, showThumbnailUI]);
614
+ const uploadAll = React4.useCallback(async () => {
501
615
  const targets = files.filter((f2) => f2.status === "pending");
502
616
  if (!targets.length) return;
617
+ setQuotaError(null);
503
618
  setIsUploading(true);
504
619
  try {
620
+ try {
621
+ const quota = await client.getQuota();
622
+ if (quota.limitBytes > 0) {
623
+ const totalPendingSize = targets.reduce((sum, f2) => sum + f2.size, 0);
624
+ const remaining = quota.limitBytes - quota.usedBytes;
625
+ if (totalPendingSize > remaining) {
626
+ const fmt = (n) => {
627
+ if (n >= 1024 * 1024 * 1024) return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;
628
+ if (n >= 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(2)} MB`;
629
+ if (n >= 1024) return `${(n / 1024).toFixed(2)} KB`;
630
+ return `${n} B`;
631
+ };
632
+ const errMsg = `Storage quota exceeded. Remaining: ${fmt(remaining)}, Upload size: ${fmt(totalPendingSize)}`;
633
+ setQuotaError(errMsg);
634
+ targets.forEach((t) => {
635
+ setFiles((prev) => prev.map((f2) => f2.id === t.id ? { ...f2, status: "error", error: errMsg } : f2));
636
+ });
637
+ onError?.(new StorageQuotaError(errMsg, {
638
+ limit: quota.limitBytes,
639
+ used: quota.usedBytes,
640
+ remaining,
641
+ fileSize: totalPendingSize
642
+ }));
643
+ return;
644
+ }
645
+ }
646
+ } catch (e) {
647
+ if (e instanceof StorageQuotaError) {
648
+ setQuotaError(e.message);
649
+ targets.forEach((t) => {
650
+ setFiles((prev) => prev.map((f2) => f2.id === t.id ? { ...f2, status: "error", error: e.message } : f2));
651
+ });
652
+ onError?.(e);
653
+ return;
654
+ }
655
+ }
505
656
  const results = [];
506
657
  for (const f2 of targets) {
507
658
  try {
508
659
  const done = await uploadOne(f2);
509
660
  results.push(done);
510
661
  } catch (e) {
662
+ if (e instanceof StorageQuotaError) {
663
+ setQuotaError(e.message);
664
+ setFiles((prev) => prev.map(
665
+ (file) => file.status === "pending" ? { ...file, status: "error", error: e.message } : file
666
+ ));
667
+ break;
668
+ }
511
669
  }
512
670
  }
513
671
  onComplete?.(results.filter((f2) => f2.status === "success"));
@@ -516,8 +674,8 @@ var Uploader = ({
516
674
  } finally {
517
675
  setIsUploading(false);
518
676
  }
519
- }, [files, onComplete, onError, uploadOne]);
520
- import_react.default.useEffect(() => {
677
+ }, [files, client, onComplete, onError, uploadOne]);
678
+ React4__namespace.default.useEffect(() => {
521
679
  if (autoUpload && files.some((f2) => f2.status === "pending") && !isUploading) {
522
680
  const timer = setTimeout(() => {
523
681
  uploadAll();
@@ -525,8 +683,8 @@ var Uploader = ({
525
683
  return () => clearTimeout(timer);
526
684
  }
527
685
  }, [autoUpload, files, isUploading, uploadAll]);
528
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, children: [
529
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
686
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
687
+ /* @__PURE__ */ jsxRuntime.jsx(
530
688
  "input",
531
689
  {
532
690
  ref: inputRef,
@@ -537,7 +695,7 @@ var Uploader = ({
537
695
  onChange: onInputChange
538
696
  }
539
697
  ),
540
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
698
+ /* @__PURE__ */ jsxRuntime.jsxs(
541
699
  "div",
542
700
  {
543
701
  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"}`,
@@ -556,82 +714,175 @@ var Uploader = ({
556
714
  role: "button",
557
715
  "aria-label": "Upload files",
558
716
  children: [
559
- /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.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: [
560
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
561
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "17 8 12 3 7 8" }),
562
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
717
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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: [
718
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
719
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "17 8 12 3 7 8" }),
720
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
563
721
  ] }) }),
564
- children ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-center", children: [
565
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Drop files here or click to browse" }),
566
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: [
567
- multiple ? `Up to ${maxFiles} files` : "Single file",
568
- " \u2022 Max ",
569
- (maxFileSize / (1024 * 1024)).toFixed(0),
570
- "MB"
722
+ children ?? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
723
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Drop files here or click to browse" }),
724
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-gray-500 dark:text-gray-400 mt-1", children: [
725
+ !multiple ? "Single file" : maxFiles > 0 ? `Up to ${maxFiles} files` : "Multiple files",
726
+ maxFileSize > 0 ? ` \u2022 Max ${(maxFileSize / (1024 * 1024)).toFixed(0)}MB per file` : ""
571
727
  ] })
572
728
  ] })
573
729
  ]
574
730
  }
575
731
  ),
576
- files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-8 space-y-4", children: [
577
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-4", children: [
578
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { className: "font-semibold text-gray-900 dark:text-gray-100", children: "Selected Files" }),
579
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-3", children: [
580
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
732
+ quotaError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
733
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 w-8 h-8 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.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", className: "text-red-600 dark:text-red-400", children: [
734
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
735
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
736
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
737
+ ] }) }),
738
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-red-800 dark:text-red-300", children: quotaError }) }),
739
+ /* @__PURE__ */ jsxRuntime.jsx(
740
+ "button",
741
+ {
742
+ onClick: () => setQuotaError(null),
743
+ className: "text-red-400 hover:text-red-600 dark:hover:text-red-300 transition-colors",
744
+ children: /* @__PURE__ */ jsxRuntime.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: [
745
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
746
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
747
+ ] })
748
+ }
749
+ )
750
+ ] }) }),
751
+ files.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 space-y-4", children: [
752
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 dark:border-gray-800 pb-4", children: [
753
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold text-gray-900 dark:text-gray-100", children: "Selected Files" }),
754
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
755
+ /* @__PURE__ */ jsxRuntime.jsx(
581
756
  "button",
582
757
  {
583
758
  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",
584
759
  disabled: isUploading || files.every((f2) => f2.status !== "pending"),
585
760
  onClick: uploadAll,
586
- children: isUploading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-2", children: [
587
- /* @__PURE__ */ (0, import_jsx_runtime.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: [
588
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
589
- /* @__PURE__ */ (0, import_jsx_runtime.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" })
761
+ children: isUploading ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
762
+ /* @__PURE__ */ jsxRuntime.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: [
763
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
764
+ /* @__PURE__ */ jsxRuntime.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" })
590
765
  ] }),
591
766
  "Uploading..."
592
767
  ] }) : "Upload All"
593
768
  }
594
769
  ),
595
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
770
+ /* @__PURE__ */ jsxRuntime.jsx(
596
771
  "button",
597
772
  {
598
773
  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",
599
774
  disabled: isUploading,
600
- onClick: () => setFiles([]),
775
+ onClick: () => {
776
+ setFiles([]);
777
+ if (showThumbnailUI) {
778
+ const empty = /* @__PURE__ */ new Set();
779
+ thumbFileIdsRef.current = empty;
780
+ setThumbFileIds(empty);
781
+ }
782
+ },
601
783
  children: "Clear"
602
784
  }
603
785
  )
604
786
  ] })
605
787
  ] }),
606
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid gap-3", children: files.map((f2) => /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-4", children: [
607
- /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.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: [
608
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
609
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
610
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "21 15 16 10 5 21" })
611
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.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: [
612
- /* @__PURE__ */ (0, import_jsx_runtime.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" }),
613
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "14 2 14 8 20 8" })
788
+ showThumbnailUI && pendingImages.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-purple-50 dark:bg-purple-900/10 border border-purple-200 dark:border-purple-800/50 rounded-xl p-4", children: [
789
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
790
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
791
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
792
+ /* @__PURE__ */ jsxRuntime.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", className: "text-purple-500", children: [
793
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
794
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "2 17 12 22 22 17" }),
795
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "2 12 12 17 22 12" })
796
+ ] }),
797
+ "Generate Thumbnails"
798
+ ] }),
799
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: thumbEnabled ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
800
+ thumbSelectedCount,
801
+ " of ",
802
+ pendingImages.length,
803
+ " image",
804
+ pendingImages.length !== 1 ? "s" : "",
805
+ " selected",
806
+ " \xB7 ",
807
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: selectAllThumbs, className: "text-purple-600 hover:text-purple-700 dark:text-purple-400 dark:hover:text-purple-300", children: "Select all" }),
808
+ " \xB7 ",
809
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: deselectAllThumbs, className: "text-gray-500 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300", children: "Deselect all" })
810
+ ] }) : "Disabled for all images" })
811
+ ] }),
812
+ /* @__PURE__ */ jsxRuntime.jsx(
813
+ "button",
814
+ {
815
+ type: "button",
816
+ onClick: handleThumbEnabledToggle,
817
+ className: `relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none ${thumbEnabled ? "bg-blue-600" : "bg-gray-200 dark:bg-gray-700"}`,
818
+ role: "switch",
819
+ "aria-checked": thumbEnabled,
820
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition ${thumbEnabled ? "translate-x-5" : "translate-x-0"}` })
821
+ }
822
+ )
823
+ ] }),
824
+ thumbEnabled && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
825
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-gray-600 dark:text-gray-400 mb-2", children: "Sizes:" }),
826
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2 flex-wrap", children: THUMBNAIL_SIZE_OPTIONS.map((size) => {
827
+ const active = thumbSizes.includes(size);
828
+ return /* @__PURE__ */ jsxRuntime.jsxs(
829
+ "button",
830
+ {
831
+ type: "button",
832
+ onClick: () => toggleThumbSize(size),
833
+ className: `px-3 py-1 rounded-full text-xs font-medium border transition-colors ${active ? "bg-purple-600 border-purple-600 text-white" : "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:border-purple-400 hover:text-purple-600"}`,
834
+ children: [
835
+ size,
836
+ "px"
837
+ ]
838
+ },
839
+ size
840
+ );
841
+ }) }),
842
+ thumbSizes.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-amber-600 dark:text-amber-400 mt-2", children: "Select at least one size." })
843
+ ] })
844
+ ] }),
845
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3", children: files.map((f2) => /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
846
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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: [
847
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
848
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
849
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "21 15 16 10 5 21" })
850
+ ] }) : /* @__PURE__ */ jsxRuntime.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: [
851
+ /* @__PURE__ */ jsxRuntime.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" }),
852
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "14 2 14 8 20 8" })
614
853
  ] }) }),
615
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-1 min-w-0", children: [
616
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [
617
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100 truncate pr-4", children: f2.name }),
618
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-xs text-gray-500 font-medium whitespace-nowrap", children: [
854
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
855
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [
856
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-gray-100 truncate pr-4", children: f2.name }),
857
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 font-medium whitespace-nowrap", children: [
619
858
  (f2.size / (1024 * 1024)).toFixed(2),
620
859
  " MB"
621
860
  ] })
622
861
  ] }),
623
- f2.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-xs text-red-500 font-medium mt-1", children: f2.error }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-1.5 w-full bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
862
+ f2.status === "error" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-red-500 font-medium mt-1", children: f2.error }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1.5 w-full bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
624
863
  "div",
625
864
  {
626
865
  className: `h-full transition-all duration-300 ${f2.status === "success" ? "bg-green-500" : "bg-blue-500"}`,
627
866
  style: { width: `${f2.progress}%` }
628
867
  }
629
- ) })
868
+ ) }),
869
+ showThumbnailUI && thumbEnabled && f2.type.startsWith("image/") && f2.status === "pending" && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mt-1.5 inline-flex items-center gap-1.5 cursor-pointer select-none", children: [
870
+ /* @__PURE__ */ jsxRuntime.jsx(
871
+ "input",
872
+ {
873
+ type: "checkbox",
874
+ checked: thumbFileIds.has(f2.id),
875
+ onChange: () => toggleFileThumb(f2.id),
876
+ className: "w-3.5 h-3.5 rounded accent-purple-600 cursor-pointer"
877
+ }
878
+ ),
879
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Generate thumbnail" })
880
+ ] })
630
881
  ] }),
631
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex-shrink-0 ml-4", children: [
632
- f2.status === "success" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
633
- /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "20 6 9 17 4 12" }) }) }),
634
- f2.publicUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
882
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 ml-4", children: [
883
+ f2.status === "success" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
884
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }) }),
885
+ f2.publicUrl && /* @__PURE__ */ jsxRuntime.jsx(
635
886
  "a",
636
887
  {
637
888
  href: f2.publicUrl,
@@ -639,34 +890,34 @@ var Uploader = ({
639
890
  rel: "noreferrer",
640
891
  className: "p-1.5 text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-colors",
641
892
  title: "View online",
642
- children: /* @__PURE__ */ (0, import_jsx_runtime.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: [
643
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
644
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "15 3 21 3 21 9" }),
645
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
893
+ children: /* @__PURE__ */ jsxRuntime.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: [
894
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
895
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "15 3 21 3 21 9" }),
896
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
646
897
  ] })
647
898
  }
648
899
  )
649
900
  ] }),
650
- f2.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.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: [
651
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
652
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
901
+ f2.status === "error" && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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: [
902
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
903
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
653
904
  ] }) }),
654
- f2.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-xs font-semibold text-blue-600 dark:text-blue-400", children: [
905
+ f2.status === "uploading" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-semibold text-blue-600 dark:text-blue-400", children: [
655
906
  f2.progress,
656
907
  "%"
657
908
  ] }),
658
- f2.status === "pending" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
909
+ f2.status === "pending" && /* @__PURE__ */ jsxRuntime.jsx(
659
910
  "button",
660
911
  {
661
912
  onClick: (e) => {
662
913
  e.stopPropagation();
663
- setFiles((prev) => prev.filter((file) => file.id !== f2.id));
914
+ removeFile(f2.id);
664
915
  },
665
916
  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",
666
917
  title: "Remove",
667
- children: /* @__PURE__ */ (0, import_jsx_runtime.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: [
668
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
669
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
918
+ children: /* @__PURE__ */ jsxRuntime.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: [
919
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
920
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
670
921
  ] })
671
922
  }
672
923
  )
@@ -675,10 +926,6 @@ var Uploader = ({
675
926
  ] })
676
927
  ] });
677
928
  };
678
-
679
- // src/ProjectFilesWidget.tsx
680
- var import_react2 = __toESM(require("react"));
681
- var import_jsx_runtime2 = require("react/jsx-runtime");
682
929
  var ProjectFilesWidget = ({
683
930
  clientOptions,
684
931
  folderPath,
@@ -694,16 +941,16 @@ var ProjectFilesWidget = ({
694
941
  showDelete = false,
695
942
  refreshInterval = 5e3
696
943
  }) => {
697
- const client = (0, import_react2.useMemo)(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
698
- const [files, setFiles] = (0, import_react2.useState)([]);
699
- const [loading, setLoading] = (0, import_react2.useState)(false);
700
- const [error, setError] = (0, import_react2.useState)(null);
701
- const [query, setQuery] = (0, import_react2.useState)("");
702
- const [savingKey, setSavingKey] = (0, import_react2.useState)(null);
703
- const [deletingKey, setDeletingKey] = (0, import_react2.useState)(null);
704
- const lastUpdatedRef = import_react2.default.useRef(null);
705
- const isFetchingRef = import_react2.default.useRef(false);
706
- const fetchFiles = import_react2.default.useCallback(async (isPolling = false) => {
944
+ const client = React4.useMemo(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
945
+ const [files, setFiles] = React4.useState([]);
946
+ const [loading, setLoading] = React4.useState(false);
947
+ const [error, setError] = React4.useState(null);
948
+ const [query, setQuery] = React4.useState("");
949
+ const [savingKey, setSavingKey] = React4.useState(null);
950
+ const [deletingKey, setDeletingKey] = React4.useState(null);
951
+ const lastUpdatedRef = React4__namespace.default.useRef(null);
952
+ const isFetchingRef = React4__namespace.default.useRef(false);
953
+ const fetchFiles = React4__namespace.default.useCallback(async (isPolling = false) => {
707
954
  if (isFetchingRef.current) return;
708
955
  isFetchingRef.current = true;
709
956
  if (!isPolling) setLoading(true);
@@ -722,10 +969,10 @@ var ProjectFilesWidget = ({
722
969
  isFetchingRef.current = false;
723
970
  }
724
971
  }, [client, folderPath]);
725
- (0, import_react2.useEffect)(() => {
972
+ React4.useEffect(() => {
726
973
  fetchFiles();
727
974
  }, [fetchFiles]);
728
- (0, import_react2.useEffect)(() => {
975
+ React4.useEffect(() => {
729
976
  if (!refreshInterval || refreshInterval <= 0) return;
730
977
  const timer = setInterval(() => {
731
978
  fetchFiles(true);
@@ -748,10 +995,18 @@ var ProjectFilesWidget = ({
748
995
  "content-type": "application/json",
749
996
  ...clientOptions?.headers || {}
750
997
  };
751
- if (clientOptions?.apiKey) {
998
+ const token = clientOptions?.getToken?.();
999
+ if (token) {
1000
+ headers["authorization"] = `Bearer ${token}`;
1001
+ if (clientOptions.projectName) {
1002
+ headers["x-project-name"] = clientOptions.projectName;
1003
+ } else if (clientOptions.projectId) {
1004
+ headers["x-project-id"] = clientOptions.projectId;
1005
+ }
1006
+ } else if (clientOptions?.apiKey) {
752
1007
  const keyHeader = clientOptions.apiKeyHeader || "authorization";
753
1008
  if (keyHeader === "authorization") {
754
- headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
1009
+ headers["authorization"] = clientOptions.apiKey.startsWith("upf_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
755
1010
  } else {
756
1011
  headers[keyHeader] = clientOptions.apiKey;
757
1012
  }
@@ -771,9 +1026,9 @@ var ProjectFilesWidget = ({
771
1026
  setDeletingKey(null);
772
1027
  }
773
1028
  };
774
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, children: [
775
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
776
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1029
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, children: [
1030
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
1031
+ /* @__PURE__ */ jsxRuntime.jsx(
777
1032
  "input",
778
1033
  {
779
1034
  type: "text",
@@ -783,7 +1038,7 @@ var ProjectFilesWidget = ({
783
1038
  style: { flex: 1, padding: 8, border: "1px solid #e5e7eb", borderRadius: 6 }
784
1039
  }
785
1040
  ),
786
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1041
+ /* @__PURE__ */ jsxRuntime.jsx(
787
1042
  "button",
788
1043
  {
789
1044
  onClick: () => {
@@ -795,26 +1050,26 @@ var ProjectFilesWidget = ({
795
1050
  }
796
1051
  )
797
1052
  ] }),
798
- loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
799
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
800
- !loading && !error && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("ul", { className: listClassName, style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }, children: [
801
- visible.map((f2) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1053
+ loading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
1054
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
1055
+ !loading && !error && /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: listClassName, style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }, children: [
1056
+ visible.map((f2) => /* @__PURE__ */ jsxRuntime.jsxs(
802
1057
  "li",
803
1058
  {
804
1059
  className: itemClassName,
805
1060
  style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 10, display: "flex", justifyContent: "space-between", alignItems: "center" },
806
1061
  children: [
807
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { minWidth: 0 }, children: [
808
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.originalName }),
809
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: 12, color: "#666" }, children: [
1062
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0 }, children: [
1063
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f2.originalName }),
1064
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 12, color: "#666" }, children: [
810
1065
  (f2.size / (1024 * 1024)).toFixed(2),
811
1066
  " MB \u2022 ",
812
1067
  f2.contentType
813
1068
  ] })
814
1069
  ] }),
815
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
816
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: f2.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
817
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1070
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
1071
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: f2.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
1072
+ /* @__PURE__ */ jsxRuntime.jsx(
818
1073
  "button",
819
1074
  {
820
1075
  onClick: async () => {
@@ -851,7 +1106,7 @@ var ProjectFilesWidget = ({
851
1106
  children: savingKey === f2.key ? "Saving\u2026" : "Use"
852
1107
  }
853
1108
  ),
854
- showDelete && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1109
+ showDelete && /* @__PURE__ */ jsxRuntime.jsx(
855
1110
  "button",
856
1111
  {
857
1112
  onClick: () => handleDelete(f2.key),
@@ -865,17 +1120,26 @@ var ProjectFilesWidget = ({
865
1120
  },
866
1121
  f2.key
867
1122
  )),
868
- visible.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
1123
+ visible.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
869
1124
  ] })
870
1125
  ] });
871
1126
  };
872
1127
 
873
- // src/ConnectProjectDialog.tsx
874
- var React3 = __toESM(require("react"));
875
- var Dialog = __toESM(require("@radix-ui/react-dialog"));
876
-
877
1128
  // src/projectConnect.ts
878
1129
  var f = (globalThis.fetch || fetch).bind(globalThis);
1130
+ function buildInit(opts, extra) {
1131
+ const headers = {
1132
+ ...extra?.headers || {}
1133
+ };
1134
+ if (opts.token) {
1135
+ headers["authorization"] = `Bearer ${opts.token}`;
1136
+ }
1137
+ return {
1138
+ ...extra,
1139
+ headers,
1140
+ credentials: opts.token ? "same-origin" : "include"
1141
+ };
1142
+ }
879
1143
  async function httpJson(input, init, useFetch) {
880
1144
  const doFetch = useFetch ?? f;
881
1145
  const res = await doFetch(input, init);
@@ -883,7 +1147,7 @@ async function httpJson(input, init, useFetch) {
883
1147
  let msg = `Request failed (${res.status})`;
884
1148
  try {
885
1149
  const j = await res.json();
886
- msg = j?.error || msg;
1150
+ msg = j?.error || j?.message || msg;
887
1151
  } catch {
888
1152
  }
889
1153
  throw new Error(msg);
@@ -894,7 +1158,7 @@ async function listProjects(opts) {
894
1158
  const url = `${opts.baseUrl.replace(/\/$/, "")}/api/projects`;
895
1159
  const data = await httpJson(
896
1160
  url,
897
- { method: "GET", credentials: "include" },
1161
+ buildInit(opts, { method: "GET" }),
898
1162
  opts.fetch
899
1163
  );
900
1164
  return data?.projects ?? [];
@@ -903,53 +1167,74 @@ async function createProject(opts) {
903
1167
  const base = opts.baseUrl.replace(/\/$/, "");
904
1168
  const created = await httpJson(
905
1169
  `${base}/api/projects`,
906
- {
1170
+ buildInit(opts, {
907
1171
  method: "POST",
908
- credentials: "include",
909
1172
  headers: { "content-type": "application/json" },
910
- body: JSON.stringify({ name: opts.name })
911
- },
1173
+ body: JSON.stringify({
1174
+ name: opts.name,
1175
+ isInHouse: opts.isInHouse,
1176
+ freeStorageLimitBytes: opts.freeStorageLimitBytes
1177
+ })
1178
+ }),
912
1179
  opts.fetch
913
1180
  );
914
1181
  const projectId = created?.project?.id;
915
1182
  if (!projectId) throw new Error("Failed to create project");
1183
+ return { projectId, project: created?.project };
1184
+ }
1185
+ async function createProjectWithApiKey(opts) {
1186
+ const base = opts.baseUrl.replace(/\/$/, "");
1187
+ const { projectId } = await createProject(opts);
916
1188
  const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
917
1189
  const key = await httpJson(
918
1190
  `${base}/api/projects/${projectId}/keys`,
919
- {
1191
+ buildInit(opts, {
920
1192
  method: "POST",
921
- credentials: "include",
922
1193
  headers: { "content-type": "application/json" },
923
1194
  body: JSON.stringify({ name: keyLabel })
924
- },
1195
+ }),
925
1196
  opts.fetch
926
1197
  );
927
- const plaintext = key?.key?.plaintext;
1198
+ const plaintext = key?.key?.secret ?? key?.key?.plaintext;
928
1199
  if (!plaintext) throw new Error("Failed to create API key");
929
1200
  return { apiKey: plaintext, projectId };
930
1201
  }
1202
+ async function updateProject(opts) {
1203
+ const base = opts.baseUrl.replace(/\/$/, "");
1204
+ const body = {};
1205
+ if (opts.isInHouse !== void 0) body.isInHouse = opts.isInHouse;
1206
+ if (opts.freeStorageLimitBytes !== void 0) body.freeStorageLimitBytes = opts.freeStorageLimitBytes;
1207
+ await httpJson(
1208
+ `${base}/api/projects/${opts.projectId}`,
1209
+ buildInit(opts, {
1210
+ method: "PUT",
1211
+ headers: { "content-type": "application/json" },
1212
+ body: JSON.stringify(body)
1213
+ }),
1214
+ opts.fetch
1215
+ );
1216
+ }
931
1217
  async function connectProject(opts) {
932
1218
  const base = opts.baseUrl.replace(/\/$/, "");
933
1219
  const keyLabel = opts.keyName || `Plugin key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
934
1220
  const key = await httpJson(
935
1221
  `${base}/api/projects/${opts.projectId}/keys`,
936
- {
1222
+ buildInit(opts, {
937
1223
  method: "POST",
938
- credentials: "include",
939
1224
  headers: { "content-type": "application/json" },
940
1225
  body: JSON.stringify({ name: keyLabel })
941
- },
1226
+ }),
942
1227
  opts.fetch
943
1228
  );
944
- const plaintext = key?.key?.plaintext;
1229
+ const plaintext = key?.key?.secret ?? key?.key?.plaintext;
945
1230
  if (!plaintext) throw new Error("Failed to create API key");
946
1231
  return { apiKey: plaintext };
947
1232
  }
948
1233
  function addApiKeyManually(apiKey) {
949
1234
  const k = (apiKey || "").trim();
950
1235
  if (!k) throw new Error("API key is required");
951
- if (!/^upk_[A-Za-z0-9]+/.test(k)) {
952
- console.warn("Provided API key does not match expected format upk_*");
1236
+ if (!/^upf_[A-Za-z0-9_-]+/.test(k)) {
1237
+ console.warn("Provided API key does not match expected format upf_*");
953
1238
  }
954
1239
  return { apiKey: k };
955
1240
  }
@@ -963,19 +1248,16 @@ function connectUsingApiKey(opts) {
963
1248
  const client = new UpfilesClient({ baseUrl, apiKey, apiKeyHeader: opts.apiKeyHeader ?? "authorization" });
964
1249
  return { apiKey, client };
965
1250
  }
966
-
967
- // src/ConnectProjectDialog.tsx
968
- var import_jsx_runtime3 = require("react/jsx-runtime");
969
1251
  function ConnectProjectDialog(props) {
970
1252
  const { baseUrl, open, onOpenChange, onConnected, fetchImpl, title, description, className } = props;
971
- const [mode, setMode] = React3.useState("existing");
972
- const [loading, setLoading] = React3.useState(false);
973
- const [error, setError] = React3.useState(null);
974
- const [projects, setProjects] = React3.useState([]);
975
- const [selectedProjectId, setSelectedProjectId] = React3.useState("");
976
- const [manualKey, setManualKey] = React3.useState("");
977
- const [newProjectName, setNewProjectName] = React3.useState("");
978
- const resetState = React3.useCallback(() => {
1253
+ const [mode, setMode] = React4__namespace.useState("existing");
1254
+ const [loading, setLoading] = React4__namespace.useState(false);
1255
+ const [error, setError] = React4__namespace.useState(null);
1256
+ const [projects, setProjects] = React4__namespace.useState([]);
1257
+ const [selectedProjectId, setSelectedProjectId] = React4__namespace.useState("");
1258
+ const [manualKey, setManualKey] = React4__namespace.useState("");
1259
+ const [newProjectName, setNewProjectName] = React4__namespace.useState("");
1260
+ const resetState = React4__namespace.useCallback(() => {
979
1261
  setMode("existing");
980
1262
  setLoading(false);
981
1263
  setError(null);
@@ -984,7 +1266,7 @@ function ConnectProjectDialog(props) {
984
1266
  setManualKey("");
985
1267
  setNewProjectName("");
986
1268
  }, []);
987
- React3.useEffect(() => {
1269
+ React4__namespace.useEffect(() => {
988
1270
  const run = async () => {
989
1271
  if (!open || mode !== "existing") return;
990
1272
  try {
@@ -1036,7 +1318,7 @@ function ConnectProjectDialog(props) {
1036
1318
  setLoading(true);
1037
1319
  setError(null);
1038
1320
  if (!newProjectName.trim()) throw new Error("Project name is required");
1039
- const { apiKey, projectId } = await createProject({ baseUrl, name: newProjectName.trim(), fetch: fetchImpl });
1321
+ const { apiKey, projectId } = await createProjectWithApiKey({ baseUrl, name: newProjectName.trim(), fetch: fetchImpl });
1040
1322
  onConnected(apiKey, { projectId, source: "new" });
1041
1323
  onOpenChange(false);
1042
1324
  resetState();
@@ -1046,8 +1328,8 @@ function ConnectProjectDialog(props) {
1046
1328
  setLoading(false);
1047
1329
  }
1048
1330
  };
1049
- const ModeSwitcher = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [
1050
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1331
+ const ModeSwitcher = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
1332
+ /* @__PURE__ */ jsxRuntime.jsx(
1051
1333
  "button",
1052
1334
  {
1053
1335
  type: "button",
@@ -1056,7 +1338,7 @@ function ConnectProjectDialog(props) {
1056
1338
  children: "Connect existing"
1057
1339
  }
1058
1340
  ),
1059
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1341
+ /* @__PURE__ */ jsxRuntime.jsx(
1060
1342
  "button",
1061
1343
  {
1062
1344
  type: "button",
@@ -1065,7 +1347,7 @@ function ConnectProjectDialog(props) {
1065
1347
  children: "Add API key"
1066
1348
  }
1067
1349
  ),
1068
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1350
+ /* @__PURE__ */ jsxRuntime.jsx(
1069
1351
  "button",
1070
1352
  {
1071
1353
  type: "button",
@@ -1075,19 +1357,19 @@ function ConnectProjectDialog(props) {
1075
1357
  }
1076
1358
  )
1077
1359
  ] });
1078
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Root, { open, onOpenChange: (o) => {
1360
+ return /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Root, { open, onOpenChange: (o) => {
1079
1361
  if (!o) resetState();
1080
1362
  onOpenChange(o);
1081
- }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Dialog.Portal, { children: [
1082
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Overlay, { className: "fixed inset-0 bg-black/40" }),
1083
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Content, { className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-md rounded-lg border bg-white p-4 shadow-lg ${className ?? ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-3", children: [
1084
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Title, { className: "text-lg font-semibold", children: title ?? "Connect to Upfiles" }),
1085
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Description, { className: "text-sm text-muted-foreground", children: description ?? "Choose how to connect your project to retrieve an API key." }),
1363
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
1364
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/40" }),
1365
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Content, { className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] max-w-md rounded-lg border bg-white p-4 shadow-lg ${className ?? ""}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
1366
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { className: "text-lg font-semibold", children: title ?? "Connect to Upfiles" }),
1367
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Description, { className: "text-sm text-muted-foreground", children: description ?? "Choose how to connect your project to retrieve an API key." }),
1086
1368
  ModeSwitcher,
1087
- error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
1088
- mode === "existing" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
1089
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "Project" }),
1090
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1369
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
1370
+ mode === "existing" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1371
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: "Project" }),
1372
+ /* @__PURE__ */ jsxRuntime.jsxs(
1091
1373
  "select",
1092
1374
  {
1093
1375
  className: "w-full rounded border px-3 py-2 text-sm",
@@ -1095,14 +1377,14 @@ function ConnectProjectDialog(props) {
1095
1377
  onChange: (e) => setSelectedProjectId(e.target.value),
1096
1378
  disabled: loading,
1097
1379
  children: [
1098
- projects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: loading ? "Loading..." : "No projects found" }),
1099
- projects.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: p.id, children: p.name }, p.id))
1380
+ projects.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: loading ? "Loading..." : "No projects found" }),
1381
+ projects.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.id, children: p.name }, p.id))
1100
1382
  ]
1101
1383
  }
1102
1384
  ),
1103
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2 pt-2", children: [
1104
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
1105
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1385
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
1386
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
1387
+ /* @__PURE__ */ jsxRuntime.jsx(
1106
1388
  "button",
1107
1389
  {
1108
1390
  type: "button",
@@ -1114,9 +1396,9 @@ function ConnectProjectDialog(props) {
1114
1396
  )
1115
1397
  ] })
1116
1398
  ] }),
1117
- mode === "manual" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
1118
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "API key" }),
1119
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1399
+ mode === "manual" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1400
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: "API key" }),
1401
+ /* @__PURE__ */ jsxRuntime.jsx(
1120
1402
  "input",
1121
1403
  {
1122
1404
  className: "w-full rounded border px-3 py-2 text-sm",
@@ -1126,9 +1408,9 @@ function ConnectProjectDialog(props) {
1126
1408
  disabled: loading
1127
1409
  }
1128
1410
  ),
1129
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2 pt-2", children: [
1130
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
1131
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1411
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
1412
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
1413
+ /* @__PURE__ */ jsxRuntime.jsx(
1132
1414
  "button",
1133
1415
  {
1134
1416
  type: "button",
@@ -1140,9 +1422,9 @@ function ConnectProjectDialog(props) {
1140
1422
  )
1141
1423
  ] })
1142
1424
  ] }),
1143
- mode === "new" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-2", children: [
1144
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "text-sm font-medium", children: "Project name" }),
1145
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1425
+ mode === "new" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1426
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: "Project name" }),
1427
+ /* @__PURE__ */ jsxRuntime.jsx(
1146
1428
  "input",
1147
1429
  {
1148
1430
  className: "w-full rounded border px-3 py-2 text-sm",
@@ -1152,10 +1434,10 @@ function ConnectProjectDialog(props) {
1152
1434
  disabled: loading
1153
1435
  }
1154
1436
  ),
1155
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-muted-foreground", children: "A project will be created in your Upfiles account and an API key generated." }),
1156
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2 pt-2", children: [
1157
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Dialog.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
1158
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1437
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "A project will be created in your Upfiles account and an API key generated." }),
1438
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
1439
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-3 py-2 text-sm rounded border", children: "Cancel" }) }),
1440
+ /* @__PURE__ */ jsxRuntime.jsx(
1159
1441
  "button",
1160
1442
  {
1161
1443
  type: "button",
@@ -1205,60 +1487,77 @@ async function fetchProjectImagesWithThumbs(clientOptions, opts) {
1205
1487
  results.sort((a, b) => (keyToIndex.get(a.key) ?? 0) - (keyToIndex.get(b.key) ?? 0));
1206
1488
  return { items: results, updatedAt };
1207
1489
  }
1208
-
1209
- // src/ImageManager.tsx
1210
- var React4 = __toESM(require("react"));
1211
- var Dialog2 = __toESM(require("@radix-ui/react-dialog"));
1212
- var import_jsx_runtime4 = require("react/jsx-runtime");
1213
- var IconGrid = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1214
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
1215
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
1216
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
1217
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
1490
+ var IconGrid = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1491
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
1492
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
1493
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
1494
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
1495
+ ] });
1496
+ var IconUploadCloud = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1497
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }),
1498
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 12v9" }),
1499
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m16 16-4-4-4 4" })
1218
1500
  ] });
1219
- var IconUploadCloud = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1220
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }),
1221
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 12v9" }),
1222
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m16 16-4-4-4 4" })
1501
+ var IconFolder = ({ className }) => /* @__PURE__ */ jsxRuntime.jsx("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__ */ jsxRuntime.jsx("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" }) });
1502
+ var IconFolderPlus = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1503
+ /* @__PURE__ */ jsxRuntime.jsx("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" }),
1504
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", x2: "12", y1: "10", y2: "16" }),
1505
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", x2: "15", y1: "13", y2: "13" })
1223
1506
  ] });
1224
- var IconFolder = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)("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" }) });
1225
- var IconFolderPlus = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1226
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" }),
1227
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "12", x2: "12", y1: "10", y2: "16" }),
1228
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "9", x2: "15", y1: "13", y2: "13" })
1507
+ var IconSearch = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1508
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
1509
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21 21-4.3-4.3" })
1229
1510
  ] });
1230
- var IconSearch = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1231
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
1232
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m21 21-4.3-4.3" })
1511
+ var IconRefresh = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1512
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
1513
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 3v5h-5" }),
1514
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
1515
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 16H3v5" })
1233
1516
  ] });
1234
- var IconRefresh = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1235
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
1236
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M21 3v5h-5" }),
1237
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
1238
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M8 16H3v5" })
1517
+ var IconChevronRight = ({ className }) => /* @__PURE__ */ jsxRuntime.jsx("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__ */ jsxRuntime.jsx("path", { d: "m9 18 6-6-6-6" }) });
1518
+ var IconTrash = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1519
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 6h18" }),
1520
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
1521
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
1522
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
1523
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
1239
1524
  ] });
1240
- var IconChevronRight = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m9 18 6-6-6-6" }) });
1241
- var IconTrash = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1242
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M3 6h18" }),
1243
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
1244
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
1245
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
1246
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
1525
+ var IconX = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1526
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18" }),
1527
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 6 12 12" })
1247
1528
  ] });
1248
- var IconX = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1249
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M18 6 6 18" }),
1250
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m6 6 12 12" })
1529
+ var IconHome = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1530
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
1531
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 22 9 12 15 12 15 22" })
1251
1532
  ] });
1252
- var IconHome = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1253
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
1254
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("polyline", { points: "9 22 9 12 15 12 15 22" })
1533
+ var IconImage = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1534
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
1535
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "9", r: "2" }),
1536
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
1255
1537
  ] });
1256
- var IconImage = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1257
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
1258
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: "9", cy: "9", r: "2" }),
1259
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
1538
+ var IconLoader = ({ className }) => /* @__PURE__ */ jsxRuntime.jsx("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__ */ jsxRuntime.jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
1539
+ var IconLayers = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("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: [
1540
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
1541
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "2 17 12 22 22 17" }),
1542
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "2 12 12 17 22 12" })
1260
1543
  ] });
1261
- var IconLoader = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
1544
+ var VisuallyHidden = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(
1545
+ "span",
1546
+ {
1547
+ style: {
1548
+ position: "absolute",
1549
+ width: "1px",
1550
+ height: "1px",
1551
+ padding: "0",
1552
+ margin: "-1px",
1553
+ overflow: "hidden",
1554
+ clip: "rect(0, 0, 0, 0)",
1555
+ whiteSpace: "nowrap",
1556
+ border: "0"
1557
+ },
1558
+ children
1559
+ }
1560
+ );
1262
1561
  function ImageManager(props) {
1263
1562
  const {
1264
1563
  open,
@@ -1266,6 +1565,7 @@ function ImageManager(props) {
1266
1565
  clientOptions,
1267
1566
  projectId,
1268
1567
  folderPath,
1568
+ userId,
1269
1569
  title,
1270
1570
  description,
1271
1571
  className,
@@ -1279,25 +1579,40 @@ function ImageManager(props) {
1279
1579
  maxFiles = 10,
1280
1580
  mode = "full",
1281
1581
  showDelete = true,
1282
- refreshInterval = 5e3
1582
+ refreshInterval = 5e3,
1583
+ stayOnUploadAfterComplete = false,
1584
+ thumbnailConfig
1283
1585
  } = props;
1284
- const [activeView, setActiveView] = React4.useState(mode === "upload" ? "upload" : "browse");
1285
- const [loading, setLoading] = React4.useState(false);
1286
- const [error, setError] = React4.useState(null);
1287
- const [currentFolderId, setCurrentFolderId] = React4.useState(void 0);
1288
- const [breadcrumbs, setBreadcrumbs] = React4.useState([{ id: void 0, name: "Home" }]);
1289
- const [files, setFiles] = React4.useState([]);
1290
- const [folders, setFolders] = React4.useState([]);
1291
- const [query, setQuery] = React4.useState("");
1292
- const [deleting, setDeleting] = React4.useState(null);
1293
- const [showCreateFolder, setShowCreateFolder] = React4.useState(false);
1294
- const [newFolderName, setNewFolderName] = React4.useState("");
1295
- const [creatingFolder, setCreatingFolder] = React4.useState(false);
1296
- const [fileToDelete, setFileToDelete] = React4.useState(null);
1297
- const [thumbnailSelectorFile, setThumbnailSelectorFile] = React4.useState(null);
1298
- const lastUpdatedRef = React4.useRef(null);
1299
- const isFetchingRef = React4.useRef(false);
1300
- const load = React4.useCallback(async (isPolling = false) => {
1586
+ const [activeView, setActiveView] = React4__namespace.useState(mode === "upload" ? "upload" : "browse");
1587
+ const [loading, setLoading] = React4__namespace.useState(false);
1588
+ const [error, setError] = React4__namespace.useState(null);
1589
+ const [currentFolderId, setCurrentFolderId] = React4__namespace.useState(void 0);
1590
+ const [breadcrumbs, setBreadcrumbs] = React4__namespace.useState([{ id: void 0, name: "Home" }]);
1591
+ const [files, setFiles] = React4__namespace.useState([]);
1592
+ const [folders, setFolders] = React4__namespace.useState([]);
1593
+ const [query, setQuery] = React4__namespace.useState("");
1594
+ const [deleting, setDeleting] = React4__namespace.useState(null);
1595
+ const [showCreateFolder, setShowCreateFolder] = React4__namespace.useState(false);
1596
+ const [newFolderName, setNewFolderName] = React4__namespace.useState("");
1597
+ const [creatingFolder, setCreatingFolder] = React4__namespace.useState(false);
1598
+ const [fileToDelete, setFileToDelete] = React4__namespace.useState(null);
1599
+ const [thumbnailSelectorFile, setThumbnailSelectorFile] = React4__namespace.useState(null);
1600
+ const [selectedThumbnailForFile, setSelectedThumbnailForFile] = React4__namespace.useState(null);
1601
+ const [previewFile, setPreviewFile] = React4__namespace.useState(null);
1602
+ const [generatingThumbnails, setGeneratingThumbnails] = React4__namespace.useState(false);
1603
+ const lastUpdatedRef = React4__namespace.useRef(null);
1604
+ const isFetchingRef = React4__namespace.useRef(false);
1605
+ const deduplicateFiles = React4__namespace.useCallback((files2) => {
1606
+ const seen = /* @__PURE__ */ new Set();
1607
+ return files2.filter((file) => {
1608
+ if (seen.has(file.key)) {
1609
+ return false;
1610
+ }
1611
+ seen.add(file.key);
1612
+ return true;
1613
+ });
1614
+ }, []);
1615
+ const load = React4__namespace.useCallback(async (isPolling = false) => {
1301
1616
  if (isFetchingRef.current) return;
1302
1617
  isFetchingRef.current = true;
1303
1618
  try {
@@ -1307,13 +1622,14 @@ function ImageManager(props) {
1307
1622
  const currentPathPrefix = breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
1308
1623
  const effectiveFolderPath = currentPathPrefix || folderPath;
1309
1624
  const [filesData, foldersData] = await Promise.all([
1310
- fetchProjectImagesWithThumbs(clientOptions, { folderPath: effectiveFolderPath, limitConcurrent: 6 }),
1625
+ fetchProjectFiles(clientOptions, effectiveFolderPath),
1311
1626
  client.getFolders(currentFolderId)
1312
1627
  ]);
1313
- const { items: list, updatedAt } = filesData;
1628
+ const { files: rawFiles, updatedAt } = filesData;
1629
+ const list = rawFiles;
1314
1630
  if (isPolling && updatedAt && updatedAt === lastUpdatedRef.current) {
1315
1631
  }
1316
- setFiles(list);
1632
+ setFiles(deduplicateFiles(list));
1317
1633
  setFolders(foldersData);
1318
1634
  lastUpdatedRef.current = updatedAt;
1319
1635
  } catch (e) {
@@ -1323,7 +1639,7 @@ function ImageManager(props) {
1323
1639
  isFetchingRef.current = false;
1324
1640
  }
1325
1641
  }, [clientOptions, folderPath, currentFolderId, breadcrumbs]);
1326
- React4.useEffect(() => {
1642
+ React4__namespace.useEffect(() => {
1327
1643
  if (!open) {
1328
1644
  setFiles([]);
1329
1645
  setFolders([]);
@@ -1336,35 +1652,11 @@ function ImageManager(props) {
1336
1652
  load();
1337
1653
  }
1338
1654
  }, [open, activeView, load, mode]);
1339
- React4.useEffect(() => {
1340
- if (!open) return;
1341
- const client = new UpfilesClient(clientOptions);
1342
- const url = client.getEventsUrl(projectId);
1343
- const eventSource = new EventSource(url);
1344
- eventSource.onmessage = (event) => {
1345
- try {
1346
- const payload = JSON.parse(event.data);
1347
- if (payload.type === "file:uploaded") {
1348
- const newFile = payload.data.file;
1349
- setFiles((prev) => {
1350
- if (prev.find((f2) => f2.key === newFile.key)) return prev;
1351
- return [newFile, ...prev];
1352
- });
1353
- } else if (payload.type === "file:deleted") {
1354
- const deletedKey = payload.data.fileKey || payload.data.file?.key;
1355
- setFiles((prev) => prev.filter((f2) => f2.key !== deletedKey));
1356
- }
1357
- } catch (e) {
1358
- console.error("SSE Error parsing message", e);
1359
- }
1360
- };
1361
- return () => eventSource.close();
1362
- }, [open, clientOptions, projectId]);
1363
- const visibleFiles = React4.useMemo(() => {
1655
+ const visibleFiles = React4__namespace.useMemo(() => {
1364
1656
  const q = query.trim().toLowerCase();
1365
1657
  return !q ? files : files.filter((i) => (i.originalName || "").toLowerCase().includes(q));
1366
1658
  }, [files, query]);
1367
- const visibleFolders = React4.useMemo(() => {
1659
+ const visibleFolders = React4__namespace.useMemo(() => {
1368
1660
  const q = query.trim().toLowerCase();
1369
1661
  return !q ? folders : folders.filter((f2) => (f2.name || "").toLowerCase().includes(q));
1370
1662
  }, [folders, query]);
@@ -1383,10 +1675,18 @@ function ImageManager(props) {
1383
1675
  "content-type": "application/json",
1384
1676
  ...clientOptions?.headers || {}
1385
1677
  };
1386
- if (clientOptions?.apiKey) {
1678
+ const token = clientOptions?.getToken?.();
1679
+ if (token) {
1680
+ headers["authorization"] = `Bearer ${token}`;
1681
+ if (clientOptions.projectName) {
1682
+ headers["x-project-name"] = clientOptions.projectName;
1683
+ } else if (clientOptions.projectId) {
1684
+ headers["x-project-id"] = clientOptions.projectId;
1685
+ }
1686
+ } else if (clientOptions?.apiKey) {
1387
1687
  const keyHeader = clientOptions.apiKeyHeader || "authorization";
1388
1688
  if (keyHeader === "authorization") {
1389
- headers["authorization"] = clientOptions.apiKey.startsWith("upk_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
1689
+ headers["authorization"] = clientOptions.apiKey.startsWith("upf_") ? `Bearer ${clientOptions.apiKey}` : clientOptions.apiKey;
1390
1690
  } else {
1391
1691
  headers[keyHeader] = clientOptions.apiKey;
1392
1692
  }
@@ -1433,7 +1733,7 @@ function ImageManager(props) {
1433
1733
  setCurrentFolderId(target.id);
1434
1734
  setQuery("");
1435
1735
  };
1436
- const uploadFoldersList = React4.useMemo(() => {
1736
+ const uploadFoldersList = React4__namespace.useMemo(() => {
1437
1737
  const options = [];
1438
1738
  options.push({ id: "root", name: "Root ( / )", path: void 0 });
1439
1739
  if (currentFolderId) {
@@ -1446,65 +1746,87 @@ function ImageManager(props) {
1446
1746
  });
1447
1747
  return options;
1448
1748
  }, [breadcrumbs, currentFolderId, folders]);
1449
- const [selectedUploadFolder, setSelectedUploadFolder] = React4.useState("current");
1450
- const activeBreadcrumbPath = React4.useMemo(() => {
1749
+ const handleThumbnailClick = React4__namespace.useCallback(async (f2) => {
1750
+ if (f2.thumbnails && f2.thumbnails.length > 0) {
1751
+ setThumbnailSelectorFile(f2);
1752
+ setSelectedThumbnailForFile(null);
1753
+ return;
1754
+ }
1755
+ setGeneratingThumbnails(true);
1756
+ setThumbnailSelectorFile(f2);
1757
+ setSelectedThumbnailForFile(null);
1758
+ try {
1759
+ const client = new UpfilesClient(clientOptions);
1760
+ const result = await client.generateThumbnails(f2.key);
1761
+ const updatedFile = { ...f2, thumbnails: result.thumbnails };
1762
+ setThumbnailSelectorFile(updatedFile);
1763
+ setFiles((prev) => prev.map((file) => file.key === f2.key ? updatedFile : file));
1764
+ } catch (e) {
1765
+ console.error("Failed to generate thumbnails:", e);
1766
+ } finally {
1767
+ setGeneratingThumbnails(false);
1768
+ }
1769
+ }, [clientOptions]);
1770
+ const [selectedUploadFolder, setSelectedUploadFolder] = React4__namespace.useState("current");
1771
+ const activeBreadcrumbPath = React4__namespace.useMemo(() => {
1451
1772
  return breadcrumbs.length > 1 ? breadcrumbs.slice(1).map((b) => b.name).join("/") + "/" : void 0;
1452
1773
  }, [breadcrumbs]);
1453
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Root, { open, onOpenChange, children: [
1454
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Portal, { children: [
1455
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Overlay, { className: "fixed inset-0 bg-gray-900/50 backdrop-blur-sm z-50 transition-all duration-300" }),
1456
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1457
- Dialog2.Content,
1774
+ return /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Root, { open, onOpenChange, children: [
1775
+ /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
1776
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/60 z-[9999]" }),
1777
+ /* @__PURE__ */ jsxRuntime.jsxs(
1778
+ Dialog2__namespace.Content,
1458
1779
  {
1459
- 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 ?? ""}`,
1780
+ className: `fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[95vw] h-[90vh] max-w-7xl min-w-[800px] min-h-[600px] rounded-2xl bg-white dark:bg-gray-950 shadow-2xl z-[10000] overflow-hidden flex flex-row ${className ?? ""}`,
1460
1781
  children: [
1461
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
1462
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "p-6", children: [
1463
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h2", { className: "text-lg font-bold text-gray-900 dark:text-white flex items-center gap-2", children: [
1464
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconImage, { className: "text-blue-600" }),
1782
+ /* @__PURE__ */ jsxRuntime.jsx(VisuallyHidden, { children: /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { children: title ?? "Media Library" }) }),
1783
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-64 min-w-[256px] bg-gray-50 dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col flex-shrink-0", children: [
1784
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6", children: [
1785
+ /* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "text-lg font-bold text-gray-900 dark:text-white flex items-center gap-2", children: [
1786
+ /* @__PURE__ */ jsxRuntime.jsx(IconImage, { className: "text-blue-600" }),
1465
1787
  title ?? "Media Library"
1466
1788
  ] }),
1467
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-xs text-gray-500 mt-1", children: description ?? "Manage your assets" })
1789
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-1", children: description ?? "Manage your assets" })
1468
1790
  ] }),
1469
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("nav", { className: "flex-1 px-3 space-y-1", children: [
1470
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1791
+ /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "flex-1 px-3 space-y-1", children: [
1792
+ /* @__PURE__ */ jsxRuntime.jsxs(
1471
1793
  "button",
1472
1794
  {
1473
1795
  onClick: () => setActiveView("browse"),
1474
1796
  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"}`,
1475
1797
  children: [
1476
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconGrid, { className: "w-4 h-4" }),
1798
+ /* @__PURE__ */ jsxRuntime.jsx(IconGrid, { className: "w-4 h-4" }),
1477
1799
  "All Files"
1478
1800
  ]
1479
1801
  }
1480
1802
  ),
1481
- mode === "full" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1803
+ mode === "full" && /* @__PURE__ */ jsxRuntime.jsxs(
1482
1804
  "button",
1483
1805
  {
1484
1806
  onClick: () => setActiveView("upload"),
1485
1807
  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"}`,
1486
1808
  children: [
1487
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconUploadCloud, { className: "w-4 h-4" }),
1809
+ /* @__PURE__ */ jsxRuntime.jsx(IconUploadCloud, { className: "w-4 h-4" }),
1488
1810
  "Upload New"
1489
1811
  ]
1490
1812
  }
1491
1813
  )
1492
1814
  ] }),
1493
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "p-4 border-t border-gray-200 dark:border-gray-800", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "text-xs text-center text-gray-400", children: [
1815
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 border-t border-gray-200 dark:border-gray-800", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-center text-gray-400", children: [
1494
1816
  files.length,
1495
1817
  " files \u2022 ",
1496
1818
  folders.length,
1497
1819
  " folders"
1498
1820
  ] }) })
1499
1821
  ] }),
1500
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-1 flex flex-col bg-white dark:bg-gray-950 relative", children: [
1501
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "h-16 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between px-6 gap-4", children: [
1502
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex items-center gap-4 flex-1", children: activeView === "browse" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1503
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center text-sm text-gray-500 overflow-hidden whitespace-nowrap", children: [
1504
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => handleBreadcrumbClick(0), className: "hover:text-blue-600 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconHome, { className: "w-4 h-4" }) }),
1505
- breadcrumbs.slice(1).map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(React4.Fragment, { children: [
1506
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconChevronRight, { className: "w-4 h-4 text-gray-300 mx-1" }),
1507
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1822
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col bg-white dark:bg-gray-950 relative", children: [
1823
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-16 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between px-6 gap-4", children: [
1824
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4 flex-1", children: activeView === "browse" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1825
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center text-sm text-gray-500 overflow-hidden whitespace-nowrap", children: [
1826
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => handleBreadcrumbClick(0), className: "hover:text-blue-600 transition-colors", children: /* @__PURE__ */ jsxRuntime.jsx(IconHome, { className: "w-4 h-4" }) }),
1827
+ breadcrumbs.slice(1).map((b, i) => /* @__PURE__ */ jsxRuntime.jsxs(React4__namespace.Fragment, { children: [
1828
+ /* @__PURE__ */ jsxRuntime.jsx(IconChevronRight, { className: "w-4 h-4 text-gray-300 mx-1" }),
1829
+ /* @__PURE__ */ jsxRuntime.jsx(
1508
1830
  "button",
1509
1831
  {
1510
1832
  onClick: () => handleBreadcrumbClick(i + 1),
@@ -1512,23 +1834,23 @@ function ImageManager(props) {
1512
1834
  children: b.name
1513
1835
  }
1514
1836
  )
1515
- ] }, b.id || i))
1837
+ ] }, `${b.id || "breadcrumb"}-${i}`))
1516
1838
  ] }),
1517
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
1518
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1839
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
1840
+ /* @__PURE__ */ jsxRuntime.jsx(
1519
1841
  "button",
1520
1842
  {
1521
1843
  onClick: () => load(),
1522
1844
  disabled: loading,
1523
1845
  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",
1524
1846
  title: "Refresh",
1525
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconRefresh, { className: `w-4 h-4 ${loading ? "animate-spin" : ""}` })
1847
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconRefresh, { className: `w-4 h-4 ${loading ? "animate-spin" : ""}` })
1526
1848
  }
1527
1849
  ),
1528
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
1529
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "relative flex-1 max-w-sm group", children: [
1530
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(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" }),
1531
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1850
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-px bg-gray-200 dark:bg-gray-700 mx-2" }),
1851
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1 max-w-sm group", children: [
1852
+ /* @__PURE__ */ jsxRuntime.jsx(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" }),
1853
+ /* @__PURE__ */ jsxRuntime.jsx(
1532
1854
  "input",
1533
1855
  {
1534
1856
  value: query,
@@ -1539,45 +1861,58 @@ function ImageManager(props) {
1539
1861
  )
1540
1862
  ] })
1541
1863
  ] }) }),
1542
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
1543
- activeView === "browse" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1544
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)(IconFolderPlus, { className: "w-5 h-5" }) }),
1545
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)(IconLoader, { className: "w-5 h-5" }) })
1864
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1865
+ activeView === "browse" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1866
+ /* @__PURE__ */ jsxRuntime.jsx("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__ */ jsxRuntime.jsx(IconFolderPlus, { className: "w-5 h-5" }) }),
1867
+ /* @__PURE__ */ jsxRuntime.jsx("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__ */ jsxRuntime.jsx(IconLoader, { className: "w-5 h-5" }) })
1546
1868
  ] }),
1547
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)(IconX, { className: "w-5 h-5" }) }) })
1869
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("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__ */ jsxRuntime.jsx(IconX, { className: "w-5 h-5" }) }) })
1548
1870
  ] })
1549
1871
  ] }),
1550
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex-1 overflow-y-auto p-6 bg-gray-50/30 dark:bg-gray-900/30", children: [
1551
- activeView === "browse" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1552
- error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)("span", { children: error }) }),
1553
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: gridClassName ?? "grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6", children: [
1554
- visibleFolders.map((folder) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1872
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto p-6 bg-gray-50/30 dark:bg-gray-900/30", children: [
1873
+ activeView === "browse" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1874
+ error && /* @__PURE__ */ jsxRuntime.jsx("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__ */ jsxRuntime.jsx("span", { children: error }) }),
1875
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: gridClassName ?? "grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6", children: [
1876
+ visibleFolders.map((folder) => /* @__PURE__ */ jsxRuntime.jsxs(
1555
1877
  "div",
1556
1878
  {
1557
1879
  onClick: () => handleNavigate(folder),
1558
1880
  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",
1559
1881
  children: [
1560
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)(IconFolder, { className: "w-8 h-8 drop-shadow-sm" }) }),
1561
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-sm font-medium text-gray-700 truncate w-full text-center", children: folder.name })
1882
+ /* @__PURE__ */ jsxRuntime.jsx("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__ */ jsxRuntime.jsx(IconFolder, { className: "w-8 h-8 drop-shadow-sm" }) }),
1883
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700 truncate w-full text-center", children: folder.name })
1562
1884
  ]
1563
1885
  },
1564
1886
  folder.id
1565
1887
  )),
1566
1888
  visibleFiles.map((f2) => {
1567
- const fWithThumbs = f2;
1568
- const bestThumb = fWithThumbs.thumbnails?.sort((a, b) => a.size - b.size).find((t) => t.size >= 300) || fWithThumbs.thumbnails?.[fWithThumbs.thumbnails?.length - 1];
1569
- const thumbUrl = bestThumb?.url || f2.url;
1570
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1889
+ const displayUrl = f2.url;
1890
+ const hasThumbnails = f2.thumbnails && f2.thumbnails.length > 0;
1891
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1571
1892
  "div",
1572
1893
  {
1573
- 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",
1894
+ onClick: () => setPreviewFile(f2),
1895
+ 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 cursor-pointer",
1574
1896
  children: [
1575
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: thumbUrl, alt: "", className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" }),
1576
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-2 justify-center transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300", children: [
1577
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1897
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: displayUrl, alt: f2.originalName, className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105" }),
1898
+ f2.contentType?.startsWith("image/") && /* @__PURE__ */ jsxRuntime.jsx(
1899
+ "button",
1900
+ {
1901
+ onClick: (e) => {
1902
+ e.stopPropagation();
1903
+ handleThumbnailClick(f2);
1904
+ },
1905
+ className: `absolute top-2 left-2 p-1.5 text-white rounded-md opacity-0 group-hover:opacity-100 transition-opacity shadow-lg ${hasThumbnails ? "bg-purple-600/90 hover:bg-purple-500" : "bg-gray-600/90 hover:bg-purple-500"}`,
1906
+ title: hasThumbnails ? "View Thumbnails" : "Generate Thumbnails",
1907
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconLayers, { className: "w-4 h-4" })
1908
+ }
1909
+ ),
1910
+ /* @__PURE__ */ jsxRuntime.jsx("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__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 justify-center transform translate-y-2 group-hover:translate-y-0 transition-transform duration-300", children: [
1911
+ /* @__PURE__ */ jsxRuntime.jsx(
1578
1912
  "button",
1579
1913
  {
1580
- onClick: () => {
1914
+ onClick: (e) => {
1915
+ e.stopPropagation();
1581
1916
  onSelect({
1582
1917
  url: f2.url,
1583
1918
  key: f2.key,
@@ -1592,7 +1927,7 @@ function ImageManager(props) {
1592
1927
  children: "Select"
1593
1928
  }
1594
1929
  ),
1595
- showDelete && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1930
+ showDelete && /* @__PURE__ */ jsxRuntime.jsx(
1596
1931
  "button",
1597
1932
  {
1598
1933
  onClick: (e) => {
@@ -1600,57 +1935,70 @@ function ImageManager(props) {
1600
1935
  setFileToDelete(f2);
1601
1936
  },
1602
1937
  className: "p-1.5 bg-white/20 text-white text-xs font-semibold rounded-md hover:bg-red-500 backdrop-blur-md transition-colors",
1603
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconTrash, { className: "w-4 h-4" })
1938
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconTrash, { className: "w-4 h-4" })
1604
1939
  }
1605
1940
  )
1606
1941
  ] }) }),
1607
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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() })
1942
+ /* @__PURE__ */ jsxRuntime.jsx("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() })
1608
1943
  ]
1609
1944
  },
1610
1945
  f2.key
1611
1946
  );
1612
1947
  })
1613
1948
  ] }),
1614
- !loading && visibleFolders.length === 0 && visibleFiles.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "h-full flex flex-col items-center justify-center text-gray-400", children: [
1615
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconSearch, { className: "w-8 h-8 opacity-40" }) }),
1616
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "text-gray-900 font-medium mb-1", children: "No Assets Found" }),
1617
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm", children: "Try uploading a file or creating a new folder." })
1949
+ !loading && visibleFolders.length === 0 && visibleFiles.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-full flex flex-col items-center justify-center text-gray-400", children: [
1950
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(IconSearch, { className: "w-8 h-8 opacity-40" }) }),
1951
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-gray-900 font-medium mb-1", children: "No Assets Found" }),
1952
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "Try uploading a file or creating a new folder." })
1618
1953
  ] })
1619
1954
  ] }),
1620
- activeView === "upload" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
1621
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h3", { className: "text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2", children: [
1622
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconUploadCloud, { className: "text-blue-500" }),
1955
+ activeView === "upload" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full flex flex-col items-center justify-center max-w-2xl mx-auto gap-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full bg-white rounded-2xl border border-gray-200 p-8 shadow-sm", children: [
1956
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2", children: [
1957
+ /* @__PURE__ */ jsxRuntime.jsx(IconUploadCloud, { className: "text-blue-500" }),
1623
1958
  "Upload Files"
1624
1959
  ] }),
1625
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mb-6", children: [
1626
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Target Folder" }),
1627
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex gap-2", children: [
1628
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1960
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
1961
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Target Folder" }),
1962
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1963
+ /* @__PURE__ */ jsxRuntime.jsx(
1629
1964
  "select",
1630
1965
  {
1631
1966
  value: selectedUploadFolder,
1632
1967
  onChange: (e) => setSelectedUploadFolder(e.target.value),
1633
1968
  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",
1634
- children: uploadFoldersList.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: opt.id, children: opt.name }, opt.id))
1969
+ children: uploadFoldersList.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.id, children: opt.name }, opt.id))
1635
1970
  }
1636
1971
  ),
1637
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => setShowCreateFolder(true), className: "px-3 bg-gray-100 hover:bg-gray-200 rounded-lg text-gray-600 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconFolderPlus, { className: "w-5 h-5" }) })
1972
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setShowCreateFolder(true), className: "px-3 bg-gray-100 hover:bg-gray-200 rounded-lg text-gray-600 transition-colors", children: /* @__PURE__ */ jsxRuntime.jsx(IconFolderPlus, { className: "w-5 h-5" }) })
1638
1973
  ] })
1639
1974
  ] }),
1640
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1975
+ /* @__PURE__ */ jsxRuntime.jsx(
1641
1976
  Uploader,
1642
1977
  {
1643
1978
  clientOptions,
1644
1979
  projectId,
1980
+ userId,
1645
1981
  folderPath: uploadFoldersList.find((o) => o.id === selectedUploadFolder)?.path || activeBreadcrumbPath || folderPath,
1646
1982
  accept: ["image/*"],
1647
1983
  maxFileSize,
1648
1984
  maxFiles,
1649
1985
  autoRecordToDb,
1650
- fetchThumbnails,
1651
- autoUpload: true,
1652
- onClientUploadComplete: (file) => {
1986
+ fetchThumbnails: false,
1987
+ autoUpload: false,
1988
+ thumbnailEnabled: thumbnailConfig?.enabled ?? true,
1989
+ thumbnailSizes: thumbnailConfig?.sizes,
1990
+ onClientUploadComplete: async (file) => {
1653
1991
  if (file.status === "success" && file.publicUrl && file.fileKey) {
1992
+ let thumbnails = file.thumbnails;
1993
+ if (file.thumbnailEnabled && file.thumbnailSizesSelected && file.thumbnailSizesSelected.length > 0 && file.type?.startsWith("image/")) {
1994
+ try {
1995
+ const client = new UpfilesClient(clientOptions);
1996
+ const result = await client.generateThumbnails(file.fileKey, file.thumbnailSizesSelected);
1997
+ thumbnails = result.thumbnails;
1998
+ } catch (e) {
1999
+ console.warn("Failed to generate thumbnails:", e);
2000
+ }
2001
+ }
1654
2002
  const newItem = {
1655
2003
  key: file.fileKey,
1656
2004
  originalName: file.name,
@@ -1658,26 +2006,31 @@ function ImageManager(props) {
1658
2006
  contentType: file.type,
1659
2007
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
1660
2008
  url: file.publicUrl,
1661
- thumbnails: file.thumbnails
2009
+ thumbnails
1662
2010
  };
1663
- setFiles((prev) => [newItem, ...prev]);
2011
+ setFiles((prev) => {
2012
+ if (prev.find((f2) => f2.key === newItem.key)) return prev;
2013
+ return deduplicateFiles([newItem, ...prev]);
2014
+ });
1664
2015
  }
1665
2016
  },
1666
2017
  onComplete: () => {
1667
- if (mode === "full") {
2018
+ if (mode === "full" && !stayOnUploadAfterComplete) {
1668
2019
  setTimeout(() => {
1669
2020
  setActiveView("browse");
1670
2021
  load();
1671
2022
  }, 1e3);
2023
+ } else {
2024
+ load();
1672
2025
  }
1673
2026
  },
1674
2027
  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",
1675
2028
  buttonClassName: "hidden",
1676
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
1677
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IconUploadCloud, { className: "w-6 h-6" }) }),
1678
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
1679
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-gray-900 font-medium", children: "Click to upload or drag and drop" }),
1680
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "text-xs text-gray-500 mt-1", children: [
2029
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-4", children: [
2030
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(IconUploadCloud, { className: "w-6 h-6" }) }),
2031
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2032
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-900 font-medium", children: "Click to upload or drag and drop" }),
2033
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 mt-1", children: [
1681
2034
  "Supports PNG, JPG, GIF up to ",
1682
2035
  Math.round(maxFileSize / 1024 / 1024),
1683
2036
  "MB"
@@ -1693,11 +2046,12 @@ function ImageManager(props) {
1693
2046
  }
1694
2047
  )
1695
2048
  ] }),
1696
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Root, { open: showCreateFolder, onOpenChange: setShowCreateFolder, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Portal, { children: [
1697
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[60]" }),
1698
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(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: [
1699
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Title, { className: "text-lg font-semibold text-gray-900 mb-4", children: "New Folder" }),
1700
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2049
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Root, { open: showCreateFolder, onOpenChange: setShowCreateFolder, children: /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
2050
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/40 z-[10010]" }),
2051
+ /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.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-[10011] outline-none", "aria-describedby": "new-folder-description", children: [
2052
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { className: "text-lg font-semibold text-gray-900 mb-4", children: "New Folder" }),
2053
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Description, { id: "new-folder-description", className: "sr-only", children: "Create a new folder to organize your files" }),
2054
+ /* @__PURE__ */ jsxRuntime.jsx(
1701
2055
  "input",
1702
2056
  {
1703
2057
  autoFocus: true,
@@ -1708,9 +2062,9 @@ function ImageManager(props) {
1708
2062
  onKeyDown: (e) => e.key === "Enter" && handleCreateFolder()
1709
2063
  }
1710
2064
  ),
1711
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex justify-end gap-3", children: [
1712
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => setShowCreateFolder(false), className: "px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100 rounded-lg", children: "Cancel" }),
1713
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2065
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-3", children: [
2066
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setShowCreateFolder(false), className: "px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-100 rounded-lg", children: "Cancel" }),
2067
+ /* @__PURE__ */ jsxRuntime.jsx(
1714
2068
  "button",
1715
2069
  {
1716
2070
  onClick: handleCreateFolder,
@@ -1722,21 +2076,21 @@ function ImageManager(props) {
1722
2076
  ] })
1723
2077
  ] })
1724
2078
  ] }) }),
1725
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Root, { open: !!fileToDelete, onOpenChange: (open2) => !open2 && setFileToDelete(null), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Portal, { children: [
1726
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Overlay, { className: "fixed inset-0 bg-black/40 z-[80]" }),
1727
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(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: [
1728
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dialog2.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Delete File" }),
1729
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Dialog2.Description, { className: "text-gray-600 mb-6", children: [
2079
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Root, { open: !!fileToDelete, onOpenChange: (open2) => !open2 && setFileToDelete(null), children: /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
2080
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/40 z-[10020]" }),
2081
+ /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.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-[10021]", "aria-describedby": "delete-file-description", children: [
2082
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Delete File" }),
2083
+ /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Description, { id: "delete-file-description", className: "text-gray-600 mb-6", children: [
1730
2084
  "Are you sure you want to delete ",
1731
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "font-semibold text-gray-900", children: [
2085
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-gray-900", children: [
1732
2086
  '"',
1733
2087
  fileToDelete?.originalName,
1734
2088
  '"'
1735
2089
  ] }),
1736
2090
  "?"
1737
2091
  ] }),
1738
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex justify-end gap-3", children: [
1739
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2092
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-3", children: [
2093
+ /* @__PURE__ */ jsxRuntime.jsx(
1740
2094
  "button",
1741
2095
  {
1742
2096
  onClick: () => setFileToDelete(null),
@@ -1744,7 +2098,7 @@ function ImageManager(props) {
1744
2098
  children: "Cancel"
1745
2099
  }
1746
2100
  ),
1747
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2101
+ /* @__PURE__ */ jsxRuntime.jsx(
1748
2102
  "button",
1749
2103
  {
1750
2104
  onClick: handleDelete,
@@ -1755,23 +2109,363 @@ function ImageManager(props) {
1755
2109
  )
1756
2110
  ] })
1757
2111
  ] })
2112
+ ] }) }),
2113
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Root, { open: !!thumbnailSelectorFile, onOpenChange: (open2) => !open2 && setThumbnailSelectorFile(null), children: /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
2114
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/50 z-[10030]" }),
2115
+ /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-4xl max-h-[80vh] rounded-xl bg-white shadow-2xl z-[10031] overflow-hidden", "aria-describedby": "thumbnail-selector-description", children: [
2116
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 border-b border-gray-200", children: [
2117
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { className: "text-xl font-semibold text-gray-900 mb-2", children: "Select Thumbnail" }),
2118
+ /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Description, { id: "thumbnail-selector-description", className: "text-gray-600", children: [
2119
+ "Choose a thumbnail size for ",
2120
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-gray-900", children: [
2121
+ '"',
2122
+ thumbnailSelectorFile?.originalName,
2123
+ '"'
2124
+ ] })
2125
+ ] })
2126
+ ] }),
2127
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 overflow-y-auto max-h-[60vh]", children: [
2128
+ generatingThumbnails && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-3 py-8 text-gray-500", children: [
2129
+ /* @__PURE__ */ jsxRuntime.jsx(IconLoader, { className: "w-5 h-5 animate-spin" }),
2130
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Generating thumbnails..." })
2131
+ ] }),
2132
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 ${generatingThumbnails ? "opacity-50 pointer-events-none" : ""}`, children: [
2133
+ /* @__PURE__ */ jsxRuntime.jsxs(
2134
+ "div",
2135
+ {
2136
+ onClick: () => setSelectedThumbnailForFile(null),
2137
+ className: `group cursor-pointer rounded-lg border-2 overflow-hidden transition-all ${selectedThumbnailForFile === null ? "border-blue-500 ring-2 ring-blue-200" : "border-gray-200 hover:border-blue-300"}`,
2138
+ children: [
2139
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
2140
+ /* @__PURE__ */ jsxRuntime.jsx(
2141
+ "img",
2142
+ {
2143
+ src: thumbnailSelectorFile?.url,
2144
+ alt: "Original",
2145
+ className: "w-full h-full object-cover"
2146
+ }
2147
+ ),
2148
+ selectedThumbnailForFile === null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) }) })
2149
+ ] }),
2150
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 bg-white", children: [
2151
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-sm text-gray-900", children: "Original" }),
2152
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: "Full size" })
2153
+ ] })
2154
+ ]
2155
+ }
2156
+ ),
2157
+ thumbnailSelectorFile?.thumbnails?.map((thumbnail) => /* @__PURE__ */ jsxRuntime.jsxs(
2158
+ "div",
2159
+ {
2160
+ onClick: () => setSelectedThumbnailForFile(thumbnail),
2161
+ className: `group cursor-pointer rounded-lg border-2 overflow-hidden transition-all ${selectedThumbnailForFile?.id === thumbnail.id ? "border-blue-500 ring-2 ring-blue-200" : "border-gray-200 hover:border-blue-300"}`,
2162
+ children: [
2163
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aspect-square bg-gray-100 flex items-center justify-center relative", children: [
2164
+ /* @__PURE__ */ jsxRuntime.jsx(
2165
+ "img",
2166
+ {
2167
+ src: thumbnail.url,
2168
+ alt: `${thumbnail.size}px thumbnail`,
2169
+ className: "w-full h-full object-cover"
2170
+ }
2171
+ ),
2172
+ selectedThumbnailForFile?.id === thumbnail.id && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-blue-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) }) })
2173
+ ] }),
2174
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 bg-white", children: [
2175
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "font-medium text-sm text-gray-900", children: [
2176
+ thumbnail.size,
2177
+ "px"
2178
+ ] }),
2179
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: thumbnail.sizeType || "Thumbnail" })
2180
+ ] })
2181
+ ]
2182
+ },
2183
+ thumbnail.id
2184
+ ))
2185
+ ] })
2186
+ ] }),
2187
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 border-t border-gray-200 flex justify-end gap-3", children: [
2188
+ /* @__PURE__ */ jsxRuntime.jsx(
2189
+ "button",
2190
+ {
2191
+ onClick: () => setThumbnailSelectorFile(null),
2192
+ className: "px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg",
2193
+ children: "Cancel"
2194
+ }
2195
+ ),
2196
+ /* @__PURE__ */ jsxRuntime.jsxs(
2197
+ "button",
2198
+ {
2199
+ onClick: () => {
2200
+ if (thumbnailSelectorFile) {
2201
+ onSelect({
2202
+ url: selectedThumbnailForFile?.url || thumbnailSelectorFile.url,
2203
+ key: thumbnailSelectorFile.key,
2204
+ originalName: thumbnailSelectorFile.originalName,
2205
+ size: thumbnailSelectorFile.size,
2206
+ contentType: thumbnailSelectorFile.contentType,
2207
+ thumbnails: thumbnailSelectorFile.thumbnails,
2208
+ selectedThumbnail: selectedThumbnailForFile || void 0
2209
+ });
2210
+ onOpenChange(false);
2211
+ setThumbnailSelectorFile(null);
2212
+ }
2213
+ },
2214
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700",
2215
+ children: [
2216
+ "Select ",
2217
+ selectedThumbnailForFile ? `${selectedThumbnailForFile.size}px Thumbnail` : "Original"
2218
+ ]
2219
+ }
2220
+ )
2221
+ ] })
2222
+ ] })
2223
+ ] }) }),
2224
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Root, { open: !!previewFile, onOpenChange: (open2) => !open2 && setPreviewFile(null), children: /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Portal, { children: [
2225
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Overlay, { className: "fixed inset-0 bg-black/90 z-[10040]", onClick: () => setPreviewFile(null) }),
2226
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Content, { className: "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] h-[90vh] z-[10041] outline-none", "aria-describedby": "image-preview-description", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full flex flex-col", children: [
2227
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 left-0 right-0 z-10 bg-gradient-to-b from-black/70 to-transparent p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between", children: [
2228
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-white", children: [
2229
+ /* @__PURE__ */ jsxRuntime.jsx(Dialog2__namespace.Title, { className: "text-xl font-semibold mb-1", children: previewFile?.originalName }),
2230
+ /* @__PURE__ */ jsxRuntime.jsxs(Dialog2__namespace.Description, { id: "image-preview-description", className: "text-sm text-gray-300", children: [
2231
+ previewFile?.size ? `${(previewFile.size / 1024).toFixed(1)} KB` : "",
2232
+ " \u2022 ",
2233
+ previewFile?.contentType
2234
+ ] })
2235
+ ] }),
2236
+ /* @__PURE__ */ jsxRuntime.jsx(
2237
+ "button",
2238
+ {
2239
+ onClick: () => setPreviewFile(null),
2240
+ className: "p-2 bg-white/10 hover:bg-white/20 rounded-lg backdrop-blur-md transition-colors",
2241
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconX, { className: "w-6 h-6 text-white" })
2242
+ }
2243
+ )
2244
+ ] }) }),
2245
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center p-6", children: /* @__PURE__ */ jsxRuntime.jsx(
2246
+ "img",
2247
+ {
2248
+ src: previewFile?.url,
2249
+ alt: previewFile?.originalName,
2250
+ className: "max-w-full max-h-full object-contain",
2251
+ onError: (e) => {
2252
+ const target = e.target;
2253
+ target.style.display = "none";
2254
+ const errorDiv = document.createElement("div");
2255
+ errorDiv.className = "text-white text-center";
2256
+ errorDiv.innerHTML = `
2257
+ <div class="text-red-400 mb-2">Failed to load image</div>
2258
+ <div class="text-sm text-gray-400">URL: ${previewFile?.url}</div>
2259
+ `;
2260
+ target.parentElement?.appendChild(errorDiv);
2261
+ }
2262
+ }
2263
+ ) }),
2264
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 z-10 bg-gradient-to-t from-black/70 to-transparent p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 justify-center", children: [
2265
+ /* @__PURE__ */ jsxRuntime.jsx(
2266
+ "button",
2267
+ {
2268
+ onClick: (e) => {
2269
+ e.stopPropagation();
2270
+ if (previewFile) {
2271
+ onSelect({
2272
+ url: previewFile.url,
2273
+ key: previewFile.key,
2274
+ originalName: previewFile.originalName,
2275
+ size: previewFile.size,
2276
+ contentType: previewFile.contentType,
2277
+ thumbnails: previewFile.thumbnails
2278
+ });
2279
+ onOpenChange(false);
2280
+ setPreviewFile(null);
2281
+ }
2282
+ },
2283
+ className: "px-6 py-2.5 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-500 shadow-lg",
2284
+ children: "Select Image"
2285
+ }
2286
+ ),
2287
+ /* @__PURE__ */ jsxRuntime.jsx(
2288
+ "a",
2289
+ {
2290
+ href: previewFile?.url,
2291
+ target: "_blank",
2292
+ rel: "noopener noreferrer",
2293
+ onClick: (e) => e.stopPropagation(),
2294
+ className: "px-6 py-2.5 bg-white/10 text-white font-semibold rounded-lg hover:bg-white/20 backdrop-blur-md transition-colors",
2295
+ children: "Open in New Tab"
2296
+ }
2297
+ ),
2298
+ previewFile?.contentType?.startsWith("image/") && /* @__PURE__ */ jsxRuntime.jsxs(
2299
+ "button",
2300
+ {
2301
+ onClick: (e) => {
2302
+ e.stopPropagation();
2303
+ if (previewFile) {
2304
+ handleThumbnailClick(previewFile);
2305
+ setPreviewFile(null);
2306
+ }
2307
+ },
2308
+ className: "px-6 py-2.5 bg-purple-600 text-white font-semibold rounded-lg hover:bg-purple-500 shadow-lg flex items-center gap-2",
2309
+ children: [
2310
+ /* @__PURE__ */ jsxRuntime.jsx(IconLayers, { className: "w-5 h-5" }),
2311
+ "Select Thumbnail"
2312
+ ]
2313
+ }
2314
+ )
2315
+ ] }) })
2316
+ ] }) })
1758
2317
  ] }) })
1759
2318
  ] });
1760
2319
  }
1761
- // Annotate the CommonJS export names for ESM import in node:
1762
- 0 && (module.exports = {
1763
- ConnectProjectDialog,
1764
- ImageManager,
1765
- ProjectFilesWidget,
1766
- UpfilesClient,
1767
- Uploader,
1768
- addApiKeyManually,
1769
- connectProject,
1770
- connectUsingApiKey,
1771
- createClientWithKey,
1772
- createProject,
1773
- fetchFileThumbnails,
1774
- fetchProjectFiles,
1775
- fetchProjectImagesWithThumbs,
1776
- listProjects
1777
- });
2320
+ function useUpfilesInhouse(config) {
2321
+ const {
2322
+ baseUrl,
2323
+ getToken,
2324
+ userId,
2325
+ projectName = "default",
2326
+ enabled = true
2327
+ } = config;
2328
+ const [state, setState] = React4.useState({ clientOptions: null, projectId: null, loading: false, error: null });
2329
+ const [retryCount, setRetryCount] = React4.useState(0);
2330
+ const [token, setToken] = React4.useState(
2331
+ typeof getToken === "string" ? getToken : null
2332
+ );
2333
+ const getTokenFn = React4.useCallback(() => {
2334
+ return typeof getToken === "function" ? getToken() : getToken;
2335
+ }, [getToken]);
2336
+ React4.useEffect(() => {
2337
+ let cancelled = false;
2338
+ async function resolveToken() {
2339
+ const next = await getTokenFn();
2340
+ if (!cancelled) {
2341
+ setToken(next ?? null);
2342
+ }
2343
+ }
2344
+ resolveToken();
2345
+ window.addEventListener("focus", resolveToken);
2346
+ window.addEventListener("storage", resolveToken);
2347
+ return () => {
2348
+ cancelled = true;
2349
+ window.removeEventListener("focus", resolveToken);
2350
+ window.removeEventListener("storage", resolveToken);
2351
+ };
2352
+ }, [getTokenFn]);
2353
+ const mountedRef = React4.useRef(true);
2354
+ React4.useEffect(() => {
2355
+ mountedRef.current = true;
2356
+ return () => {
2357
+ mountedRef.current = false;
2358
+ };
2359
+ }, []);
2360
+ const buildClientOptions = React4.useCallback(
2361
+ () => ({
2362
+ baseUrl,
2363
+ getToken: getTokenFn,
2364
+ projectName
2365
+ }),
2366
+ [baseUrl, getTokenFn, projectName]
2367
+ );
2368
+ React4.useEffect(() => {
2369
+ if (!enabled || !token || !userId) {
2370
+ setState({ clientOptions: null, projectId: null, loading: false, error: null });
2371
+ return;
2372
+ }
2373
+ let cancelled = false;
2374
+ async function provision() {
2375
+ if (!mountedRef.current) return;
2376
+ setState((prev) => ({ ...prev, loading: true, error: null }));
2377
+ try {
2378
+ const connectOpts = { baseUrl, token };
2379
+ const projects = await listProjects(connectOpts);
2380
+ if (cancelled || !mountedRef.current) return;
2381
+ const existing = projects.find((p) => p.name === projectName);
2382
+ if (existing) {
2383
+ await updateProject({
2384
+ ...connectOpts,
2385
+ projectId: existing.id,
2386
+ isInHouse: true,
2387
+ freeStorageLimitBytes: 2147483648
2388
+ // 2GB
2389
+ });
2390
+ } else {
2391
+ try {
2392
+ await createProject({
2393
+ ...connectOpts,
2394
+ name: projectName,
2395
+ isInHouse: true,
2396
+ freeStorageLimitBytes: 2147483648
2397
+ // 2GB in bytes
2398
+ });
2399
+ } catch (err) {
2400
+ if (err?.message?.includes("409") || err?.message?.includes("duplicate") || err?.message?.includes("already exists")) {
2401
+ const retryProjects = await listProjects(connectOpts);
2402
+ const found = retryProjects.find((p) => p.name === projectName);
2403
+ if (!found) throw err;
2404
+ await updateProject({
2405
+ ...connectOpts,
2406
+ projectId: found.id,
2407
+ isInHouse: true,
2408
+ freeStorageLimitBytes: 2147483648
2409
+ // 2GB
2410
+ });
2411
+ } else {
2412
+ throw err;
2413
+ }
2414
+ }
2415
+ }
2416
+ if (cancelled || !mountedRef.current) return;
2417
+ setState({
2418
+ clientOptions: buildClientOptions(),
2419
+ projectId: null,
2420
+ loading: false,
2421
+ error: null
2422
+ });
2423
+ } catch (err) {
2424
+ if (cancelled || !mountedRef.current) return;
2425
+ setState({
2426
+ clientOptions: null,
2427
+ projectId: null,
2428
+ loading: false,
2429
+ error: err?.message || "Failed to auto-connect upfiles"
2430
+ });
2431
+ }
2432
+ }
2433
+ provision();
2434
+ return () => {
2435
+ cancelled = true;
2436
+ };
2437
+ }, [enabled, token, userId, baseUrl, projectName, retryCount, buildClientOptions]);
2438
+ const retry = React4.useCallback(() => {
2439
+ setRetryCount((c) => c + 1);
2440
+ }, []);
2441
+ const clearCache = React4.useCallback(() => {
2442
+ setState({ clientOptions: null, projectId: null, loading: false, error: null });
2443
+ }, []);
2444
+ return {
2445
+ ...state,
2446
+ isReady: !!state.clientOptions,
2447
+ retry,
2448
+ clearCache
2449
+ };
2450
+ }
2451
+
2452
+ exports.ConnectProjectDialog = ConnectProjectDialog;
2453
+ exports.ImageManager = ImageManager;
2454
+ exports.ProjectFilesWidget = ProjectFilesWidget;
2455
+ exports.StorageQuotaError = StorageQuotaError;
2456
+ exports.UpfilesClient = UpfilesClient;
2457
+ exports.Uploader = Uploader;
2458
+ exports.addApiKeyManually = addApiKeyManually;
2459
+ exports.connectProject = connectProject;
2460
+ exports.connectUsingApiKey = connectUsingApiKey;
2461
+ exports.createClientWithKey = createClientWithKey;
2462
+ exports.createProject = createProject;
2463
+ exports.createProjectWithApiKey = createProjectWithApiKey;
2464
+ exports.fetchFileThumbnails = fetchFileThumbnails;
2465
+ exports.fetchProjectFiles = fetchProjectFiles;
2466
+ exports.fetchProjectImagesWithThumbs = fetchProjectImagesWithThumbs;
2467
+ exports.listProjects = listProjects;
2468
+ exports.updateProject = updateProject;
2469
+ exports.useUpfilesInhouse = useUpfilesInhouse;
2470
+ //# sourceMappingURL=index.js.map
2471
+ //# sourceMappingURL=index.js.map