@withwiz/gallery 0.1.0

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.
Files changed (77) hide show
  1. package/README.ko.md +484 -0
  2. package/README.md +484 -0
  3. package/dist/chunk-B3MQL6ZR.mjs +79 -0
  4. package/dist/chunk-B3MQL6ZR.mjs.map +1 -0
  5. package/dist/chunk-BC3TPJQY.mjs +122 -0
  6. package/dist/chunk-BC3TPJQY.mjs.map +1 -0
  7. package/dist/chunk-BTLELFKC.mjs +11 -0
  8. package/dist/chunk-BTLELFKC.mjs.map +1 -0
  9. package/dist/chunk-GDUGKVDQ.mjs +50 -0
  10. package/dist/chunk-GDUGKVDQ.mjs.map +1 -0
  11. package/dist/chunk-GFOWZGJX.js +50 -0
  12. package/dist/chunk-GFOWZGJX.js.map +1 -0
  13. package/dist/chunk-ILSZPLME.js +47 -0
  14. package/dist/chunk-ILSZPLME.js.map +1 -0
  15. package/dist/chunk-M7CCTDUG.js +122 -0
  16. package/dist/chunk-M7CCTDUG.js.map +1 -0
  17. package/dist/chunk-WVRTM6E7.js +19 -0
  18. package/dist/chunk-WVRTM6E7.js.map +1 -0
  19. package/dist/chunk-XLPMNJJV.js +11 -0
  20. package/dist/chunk-XLPMNJJV.js.map +1 -0
  21. package/dist/chunk-XSPWM4DD.js +79 -0
  22. package/dist/chunk-XSPWM4DD.js.map +1 -0
  23. package/dist/chunk-YUMB3GBN.mjs +47 -0
  24. package/dist/chunk-YUMB3GBN.mjs.map +1 -0
  25. package/dist/chunk-ZEZCUJJD.mjs +19 -0
  26. package/dist/chunk-ZEZCUJJD.mjs.map +1 -0
  27. package/dist/components/gallery.css +1074 -0
  28. package/dist/components/index.d.mts +156 -0
  29. package/dist/components/index.d.ts +156 -0
  30. package/dist/components/index.js +1215 -0
  31. package/dist/components/index.js.map +1 -0
  32. package/dist/components/index.mjs +1215 -0
  33. package/dist/components/index.mjs.map +1 -0
  34. package/dist/errors-B9gzUsX-.d.ts +39 -0
  35. package/dist/errors-DYrlDCzP.d.mts +39 -0
  36. package/dist/gallery.css +1074 -0
  37. package/dist/hooks/index.d.mts +65 -0
  38. package/dist/hooks/index.d.ts +65 -0
  39. package/dist/hooks/index.js +14 -0
  40. package/dist/hooks/index.js.map +1 -0
  41. package/dist/hooks/index.mjs +14 -0
  42. package/dist/hooks/index.mjs.map +1 -0
  43. package/dist/index.d.mts +17 -0
  44. package/dist/index.d.ts +17 -0
  45. package/dist/index.js +37 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/index.mjs +37 -0
  48. package/dist/index.mjs.map +1 -0
  49. package/dist/presets/ballet.d.mts +64 -0
  50. package/dist/presets/ballet.d.ts +64 -0
  51. package/dist/presets/ballet.js +162 -0
  52. package/dist/presets/ballet.js.map +1 -0
  53. package/dist/presets/ballet.mjs +162 -0
  54. package/dist/presets/ballet.mjs.map +1 -0
  55. package/dist/server/index.d.mts +157 -0
  56. package/dist/server/index.d.ts +157 -0
  57. package/dist/server/index.js +619 -0
  58. package/dist/server/index.js.map +1 -0
  59. package/dist/server/index.mjs +619 -0
  60. package/dist/server/index.mjs.map +1 -0
  61. package/dist/types/index.d.mts +137 -0
  62. package/dist/types/index.d.ts +137 -0
  63. package/dist/types/index.js +1 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/dist/types/index.mjs +1 -0
  66. package/dist/types/index.mjs.map +1 -0
  67. package/dist/useGalleryLightbox-BYzPyBYB.d.mts +22 -0
  68. package/dist/useGalleryLightbox-BYzPyBYB.d.ts +22 -0
  69. package/dist/validators/index.d.mts +68 -0
  70. package/dist/validators/index.d.ts +68 -0
  71. package/dist/validators/index.js +7 -0
  72. package/dist/validators/index.js.map +1 -0
  73. package/dist/validators/index.mjs +7 -0
  74. package/dist/validators/index.mjs.map +1 -0
  75. package/package.json +85 -0
  76. package/prisma/gallery.schema.prisma +62 -0
  77. package/prisma/migrations/2026-05-24-enum-to-table.sql +65 -0
@@ -0,0 +1,1215 @@
1
+ "use client";
2
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
3
+
4
+ var _chunkWVRTM6E7js = require('../chunk-WVRTM6E7.js');
5
+
6
+
7
+ var _chunkM7CCTDUGjs = require('../chunk-M7CCTDUG.js');
8
+
9
+
10
+ var _chunkXLPMNJJVjs = require('../chunk-XLPMNJJV.js');
11
+
12
+ // src/components/ToggleSwitch.tsx
13
+ var _jsxruntime = require('react/jsx-runtime');
14
+ function ToggleSwitch(props) {
15
+ const { checked, onChange, disabled = false, label, size = "md", className } = props;
16
+ const handleToggle = () => {
17
+ if (disabled) return;
18
+ onChange(!checked);
19
+ };
20
+ const handleKeyDown = (e) => {
21
+ if (disabled) return;
22
+ if (e.key === " " || e.key === "Enter") {
23
+ e.preventDefault();
24
+ onChange(!checked);
25
+ }
26
+ };
27
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-toggle", `gallery-toggle--${size}`, className), children: [
28
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
29
+ "button",
30
+ {
31
+ type: "button",
32
+ role: "switch",
33
+ "aria-checked": checked,
34
+ "aria-disabled": disabled || void 0,
35
+ "aria-label": label,
36
+ disabled,
37
+ onClick: handleToggle,
38
+ onKeyDown: handleKeyDown,
39
+ className: _chunkXLPMNJJVjs.cn.call(void 0,
40
+ "gallery-toggle__track",
41
+ checked && "gallery-toggle--checked",
42
+ disabled && "gallery-toggle--disabled"
43
+ ),
44
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "gallery-toggle__thumb", "aria-hidden": "true" })
45
+ }
46
+ ),
47
+ label ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "gallery-toggle__label", children: label }) : null
48
+ ] });
49
+ }
50
+
51
+ // src/components/ImageDropZone.tsx
52
+ var _react = require('react');
53
+
54
+ var DEFAULT_TEXT = {
55
+ dropPrompt: "Drop image here or click to browse",
56
+ replacePrompt: "Drop a new image to replace",
57
+ rejected: "Some files were rejected"
58
+ };
59
+ function ImageDropZone(props) {
60
+ const {
61
+ onFiles,
62
+ accept,
63
+ maxSize,
64
+ multiple,
65
+ disabled = false,
66
+ previewUrl,
67
+ i18n,
68
+ className
69
+ } = props;
70
+ const inputRef = _react.useRef.call(void 0, null);
71
+ const { isDragging, dropProps, inputProps, rejectedReasons } = _chunkM7CCTDUGjs.useImageDropZone.call(void 0, {
72
+ onFiles,
73
+ accept,
74
+ maxSize,
75
+ multiple,
76
+ disabled
77
+ });
78
+ const text = {
79
+ dropPrompt: _nullishCoalesce(_optionalChain([i18n, 'optionalAccess', _2 => _2.dropPrompt]), () => ( DEFAULT_TEXT.dropPrompt)),
80
+ replacePrompt: _nullishCoalesce(_optionalChain([i18n, 'optionalAccess', _3 => _3.replacePrompt]), () => ( DEFAULT_TEXT.replacePrompt)),
81
+ rejected: _nullishCoalesce(_optionalChain([i18n, 'optionalAccess', _4 => _4.rejected]), () => ( DEFAULT_TEXT.rejected))
82
+ };
83
+ const openPicker = () => {
84
+ if (disabled) return;
85
+ _optionalChain([inputRef, 'access', _5 => _5.current, 'optionalAccess', _6 => _6.click, 'call', _7 => _7()]);
86
+ };
87
+ const containerClass = _chunkXLPMNJJVjs.cn.call(void 0,
88
+ "gallery-dropzone",
89
+ isDragging && "gallery-dropzone--dragging",
90
+ disabled && "gallery-dropzone--disabled",
91
+ previewUrl && "gallery-dropzone--has-preview",
92
+ className
93
+ );
94
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-dropzone-wrap", children: [
95
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
96
+ "input",
97
+ {
98
+ ref: inputRef,
99
+ ...inputProps,
100
+ disabled,
101
+ className: "gallery-dropzone__input",
102
+ style: { position: "absolute", width: 1, height: 1, opacity: 0, pointerEvents: "none" },
103
+ "aria-hidden": "true",
104
+ tabIndex: -1
105
+ }
106
+ ),
107
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
108
+ "div",
109
+ {
110
+ className: containerClass,
111
+ onClick: openPicker,
112
+ role: "button",
113
+ tabIndex: disabled ? -1 : 0,
114
+ "aria-disabled": disabled || void 0,
115
+ onKeyDown: (e) => {
116
+ if (disabled) return;
117
+ if (e.key === "Enter" || e.key === " ") {
118
+ e.preventDefault();
119
+ openPicker();
120
+ }
121
+ },
122
+ ...dropProps,
123
+ children: previewUrl ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
124
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
125
+ "img",
126
+ {
127
+ src: previewUrl,
128
+ alt: "",
129
+ className: "gallery-dropzone__preview"
130
+ }
131
+ ),
132
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-dropzone__prompt gallery-dropzone__prompt--overlay", children: text.replacePrompt })
133
+ ] }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-dropzone__prompt", children: text.dropPrompt })
134
+ }
135
+ ),
136
+ rejectedReasons.length > 0 ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "ul", { className: "gallery-dropzone__rejected", role: "alert", children: [
137
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "li", { className: "gallery-dropzone__rejected-title", children: text.rejected }),
138
+ rejectedReasons.map((reason, i) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "li", { className: "gallery-dropzone__rejected-item", children: reason }, i))
139
+ ] }) : null
140
+ ] });
141
+ }
142
+
143
+ // src/components/GalleryManagerLayout.tsx
144
+
145
+ var DEFAULT_TEXT2 = {
146
+ "admin.emptyState": "Select an item to edit, or create a new one."
147
+ };
148
+ function t(i18n, key) {
149
+ return _nullishCoalesce(_nullishCoalesce(_optionalChain([i18n, 'optionalAccess', _8 => _8[key]]), () => ( DEFAULT_TEXT2[key])), () => ( key));
150
+ }
151
+ function GalleryManagerLayout(props) {
152
+ const {
153
+ listRoot,
154
+ filterControls,
155
+ toolbar,
156
+ editForm,
157
+ emptyState,
158
+ homePreview,
159
+ className,
160
+ i18n,
161
+ ui
162
+ } = props;
163
+ const slots = _nullishCoalesce(_optionalChain([ui, 'optionalAccess', _9 => _9.slots]), () => ( {}));
164
+ const classes = _nullishCoalesce(_optionalChain([ui, 'optionalAccess', _10 => _10.classNames]), () => ( {}));
165
+ const toolbarContent = _nullishCoalesce(slots.managerToolbar, () => ( toolbar));
166
+ const listContent = _nullishCoalesce(slots.listRoot, () => ( listRoot));
167
+ const editContent = _nullishCoalesce(slots.editFormRoot, () => ( editForm));
168
+ const previewContent = _nullishCoalesce(slots.homePreviewRoot, () => ( homePreview));
169
+ const emptyContent = _nullishCoalesce(_nullishCoalesce(slots.emptyState, () => ( emptyState)), () => ( /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: t(i18n, "admin.emptyState") })));
170
+ const hasPreview = previewContent !== void 0 && previewContent !== null;
171
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
172
+ "div",
173
+ {
174
+ className: _chunkXLPMNJJVjs.cn.call(void 0,
175
+ "gallery-manager",
176
+ hasPreview ? "gallery-manager--with-preview" : "gallery-manager--no-preview",
177
+ classes.managerRoot,
178
+ className
179
+ ),
180
+ children: [
181
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "aside", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-manager__list", classes.listRoot), children: [
182
+ toolbarContent ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-manager__toolbar", classes.managerToolbar), children: toolbarContent }) : null,
183
+ filterControls ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-manager__filter", children: filterControls }) : null,
184
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-manager__list-body", children: listContent })
185
+ ] }),
186
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "section", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-manager__form", classes.editFormRoot), children: editContent !== null && editContent !== void 0 ? editContent : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-manager__empty", classes.emptyState), children: emptyContent }) }),
187
+ hasPreview ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "aside", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-manager__preview", classes.homePreviewRoot), children: previewContent }) : null
188
+ ]
189
+ }
190
+ );
191
+ }
192
+
193
+ // src/components/GalleryEditForm.tsx
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+ var DEFAULT_TEXT3 = {
203
+ "form.caption": "Caption",
204
+ "form.captionPlaceholder": "Image description (optional)",
205
+ "form.category": "Category",
206
+ "form.sortOrder": "Sort order",
207
+ "form.published": "Published",
208
+ "form.featured": "Featured",
209
+ "form.save": "Save",
210
+ "form.cancel": "Cancel",
211
+ "form.imageDropPrompt": "Drop image here or click to browse",
212
+ "form.imageReplacePrompt": "Drop a new image to replace",
213
+ "admin.featuredOverLimit": "Featured cap reached \u2014 uncheck others first"
214
+ };
215
+ function t2(i18n, key) {
216
+ return _nullishCoalesce(_nullishCoalesce(_optionalChain([i18n, 'optionalAccess', _11 => _11[key]]), () => ( DEFAULT_TEXT3[key])), () => ( key));
217
+ }
218
+ function buildInitialForm(value, categories) {
219
+ if (value) {
220
+ return {
221
+ imageUrl: value.imageUrl,
222
+ imageKey: _nullishCoalesce(value.imageKey, () => ( "")),
223
+ caption: _nullishCoalesce(value.caption, () => ( "")),
224
+ categoryId: value.categoryId,
225
+ sortOrder: value.sortOrder,
226
+ featured: value.featured,
227
+ published: value.published
228
+ };
229
+ }
230
+ return {
231
+ imageUrl: "",
232
+ imageKey: "",
233
+ caption: "",
234
+ categoryId: _nullishCoalesce(_optionalChain([categories, 'access', _12 => _12[0], 'optionalAccess', _13 => _13.id]), () => ( "")),
235
+ sortOrder: 0,
236
+ featured: false,
237
+ published: false
238
+ };
239
+ }
240
+ function GalleryEditForm(props) {
241
+ const {
242
+ value,
243
+ categories,
244
+ onSubmit,
245
+ onCancel,
246
+ onDelete,
247
+ multipleMode = false,
248
+ onSubmitMany,
249
+ canToggleFeatured = true,
250
+ saving = false,
251
+ onImageSelect,
252
+ i18n,
253
+ className
254
+ } = props;
255
+ const isNew = value === null;
256
+ const [form, setForm] = _react.useState.call(void 0, () => buildInitialForm(value, categories));
257
+ const [multiImages, setMultiImages] = _react.useState.call(void 0, []);
258
+ const [imageError, setImageError] = _react.useState.call(void 0, null);
259
+ const valueIdRef = _react.useRef.call(void 0, _nullishCoalesce(_optionalChain([value, 'optionalAccess', _14 => _14.id]), () => ( null)));
260
+ const dirtyRef = _react.useRef.call(void 0, false);
261
+ _react.useEffect.call(void 0, () => {
262
+ const newId = _nullishCoalesce(_optionalChain([value, 'optionalAccess', _15 => _15.id]), () => ( null));
263
+ if (newId !== valueIdRef.current) {
264
+ valueIdRef.current = newId;
265
+ if (!dirtyRef.current) {
266
+ setForm(buildInitialForm(value, categories));
267
+ }
268
+ } else if (value && !dirtyRef.current) {
269
+ setForm(buildInitialForm(value, categories));
270
+ }
271
+ }, [value, categories]);
272
+ _react.useEffect.call(void 0, () => {
273
+ if (!form.categoryId && categories.length > 0) {
274
+ setForm((f) => ({ ...f, categoryId: categories[0].id }));
275
+ }
276
+ }, [categories, form.categoryId]);
277
+ const isMultiActive = isNew && multipleMode && multiImages.length > 1;
278
+ const featuredDisabled = !canToggleFeatured && !form.featured;
279
+ const updateField = _react.useCallback.call(void 0, (key, val) => {
280
+ dirtyRef.current = true;
281
+ setForm((prev) => ({ ...prev, [key]: val }));
282
+ }, []);
283
+ const handleFiles = _react.useCallback.call(void 0,
284
+ async (files) => {
285
+ if (files.length === 0) return;
286
+ setImageError(null);
287
+ if (!onImageSelect) {
288
+ setImageError("onImageSelect prop missing \u2014 host must provide upload handler");
289
+ return;
290
+ }
291
+ try {
292
+ if (isNew && multipleMode) {
293
+ const uploaded = await Promise.all(files.map((f) => onImageSelect(f)));
294
+ setMultiImages((prev) => {
295
+ const next = [...prev, ...uploaded];
296
+ setForm((f) => {
297
+ if (!f.imageUrl && next.length > 0) {
298
+ return { ...f, imageUrl: next[0].url, imageKey: _nullishCoalesce(next[0].key, () => ( "")) };
299
+ }
300
+ return f;
301
+ });
302
+ return next;
303
+ });
304
+ } else {
305
+ const r = await onImageSelect(files[0]);
306
+ setForm((f) => ({ ...f, imageUrl: r.url, imageKey: _nullishCoalesce(r.key, () => ( "")) }));
307
+ }
308
+ } catch (err) {
309
+ setImageError(err instanceof Error ? err.message : "Image upload failed");
310
+ }
311
+ },
312
+ [isNew, multipleMode, onImageSelect]
313
+ );
314
+ const handleSubmit = _react.useCallback.call(void 0, async () => {
315
+ if (isMultiActive && onSubmitMany) {
316
+ const items = multiImages.map((img, i) => ({
317
+ imageUrl: img.url,
318
+ imageKey: img.key || void 0,
319
+ caption: form.caption ? `${form.caption}_${i + 1}` : void 0,
320
+ categoryId: form.categoryId,
321
+ sortOrder: form.sortOrder + i,
322
+ featured: form.featured,
323
+ published: form.published
324
+ }));
325
+ await onSubmitMany(items);
326
+ return;
327
+ }
328
+ if (!form.imageUrl) {
329
+ setImageError("Image is required");
330
+ return;
331
+ }
332
+ const payload = {
333
+ imageUrl: form.imageUrl,
334
+ imageKey: form.imageKey || void 0,
335
+ caption: form.caption || void 0,
336
+ categoryId: form.categoryId,
337
+ sortOrder: form.sortOrder,
338
+ featured: form.featured,
339
+ published: form.published
340
+ };
341
+ await onSubmit(payload);
342
+ }, [form, isMultiActive, multiImages, onSubmit, onSubmitMany]);
343
+ const captionPreview = _react.useMemo.call(void 0, () => {
344
+ if (!isMultiActive || !form.caption) return null;
345
+ return multiImages.slice(0, 3).map((_, i) => `${form.caption}_${i + 1}`).join(", ") + (multiImages.length > 3 ? ", ..." : "");
346
+ }, [isMultiActive, multiImages, form.caption]);
347
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-edit-form", className), children: [
348
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "header", { className: "gallery-edit-form__header", children: [
349
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "gallery-edit-form__title", children: isNew ? isMultiActive ? `Batch (${multiImages.length})` : "New image" : "Edit image" }),
350
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-edit-form__actions", "data-toggle-group": true, children: [
351
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { "data-toggle": "featured", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
352
+ ToggleSwitch,
353
+ {
354
+ checked: form.featured,
355
+ onChange: (v) => updateField("featured", v),
356
+ label: t2(i18n, "form.featured"),
357
+ disabled: featuredDisabled,
358
+ size: "sm"
359
+ }
360
+ ) }),
361
+ featuredDisabled ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "gallery-edit-form__note", children: t2(i18n, "admin.featuredOverLimit") }) : null,
362
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { "data-toggle": "published", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
363
+ ToggleSwitch,
364
+ {
365
+ checked: form.published,
366
+ onChange: (v) => updateField("published", v),
367
+ label: t2(i18n, "form.published"),
368
+ size: "sm"
369
+ }
370
+ ) }),
371
+ !isNew && onDelete ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
372
+ "button",
373
+ {
374
+ type: "button",
375
+ className: "gallery-edit-form__btn gallery-edit-form__btn--danger",
376
+ onClick: () => void onDelete(),
377
+ children: "Delete"
378
+ }
379
+ ) : null,
380
+ onCancel ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
381
+ "button",
382
+ {
383
+ type: "button",
384
+ className: "gallery-edit-form__btn",
385
+ onClick: onCancel,
386
+ children: t2(i18n, "form.cancel")
387
+ }
388
+ ) : null,
389
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
390
+ "button",
391
+ {
392
+ type: "button",
393
+ className: "gallery-edit-form__btn gallery-edit-form__btn--primary",
394
+ onClick: () => void handleSubmit(),
395
+ disabled: saving,
396
+ children: saving ? "Saving..." : isMultiActive ? `Save ${multiImages.length}` : t2(i18n, "form.save")
397
+ }
398
+ )
399
+ ] })
400
+ ] }),
401
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-edit-form__section", children: [
402
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-edit-form__section-title", children: "Image" }),
403
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
404
+ ImageDropZone,
405
+ {
406
+ onFiles: handleFiles,
407
+ previewUrl: form.imageUrl || null,
408
+ multiple: isNew && multipleMode,
409
+ i18n: {
410
+ dropPrompt: t2(i18n, "form.imageDropPrompt"),
411
+ replacePrompt: t2(i18n, "form.imageReplacePrompt")
412
+ }
413
+ }
414
+ ),
415
+ imageError ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-edit-form__error", role: "alert", children: imageError }) : null,
416
+ isMultiActive ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-edit-form__multi-grid", children: multiImages.map((img, i) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-edit-form__multi-tile", children: [
417
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { src: img.url, alt: "", className: "gallery-edit-form__multi-img" }),
418
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
419
+ "button",
420
+ {
421
+ type: "button",
422
+ className: "gallery-edit-form__multi-remove",
423
+ onClick: () => {
424
+ setMultiImages((prev) => {
425
+ const next = prev.filter((_, idx) => idx !== i);
426
+ setForm((f) => {
427
+ if (next.length > 0) {
428
+ return { ...f, imageUrl: next[0].url, imageKey: _nullishCoalesce(next[0].key, () => ( "")) };
429
+ }
430
+ return { ...f, imageUrl: "", imageKey: "" };
431
+ });
432
+ return next;
433
+ });
434
+ },
435
+ "aria-label": "remove",
436
+ children: "\xD7"
437
+ }
438
+ )
439
+ ] }, _nullishCoalesce(img.key, () => ( `mi-${i}`)))) }) : null
440
+ ] }),
441
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-edit-form__section", children: [
442
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-edit-form__section-title", children: t2(i18n, "form.caption") }),
443
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
444
+ "input",
445
+ {
446
+ type: "text",
447
+ name: "caption",
448
+ className: "gallery-edit-form__input",
449
+ value: form.caption,
450
+ maxLength: 200,
451
+ placeholder: t2(i18n, "form.captionPlaceholder"),
452
+ onChange: (e) => updateField("caption", e.target.value)
453
+ }
454
+ ),
455
+ captionPreview ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-edit-form__hint", children: [
456
+ "Preview: ",
457
+ captionPreview
458
+ ] }) : null
459
+ ] }),
460
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-edit-form__row", children: [
461
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-edit-form__section", children: [
462
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-edit-form__section-title", children: t2(i18n, "form.category") }),
463
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
464
+ "select",
465
+ {
466
+ name: "categoryId",
467
+ className: "gallery-edit-form__input",
468
+ value: form.categoryId,
469
+ onChange: (e) => updateField("categoryId", e.target.value),
470
+ children: categories.map((c) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "option", { value: c.id, children: c.labelKo }, c.id))
471
+ }
472
+ )
473
+ ] }),
474
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-edit-form__section", children: [
475
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-edit-form__section-title", children: t2(i18n, "form.sortOrder") }),
476
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
477
+ "input",
478
+ {
479
+ type: "number",
480
+ name: "sortOrder",
481
+ className: "gallery-edit-form__input",
482
+ value: form.sortOrder,
483
+ min: 0,
484
+ onChange: (e) => {
485
+ const n = parseInt(e.target.value, 10);
486
+ updateField("sortOrder", Number.isNaN(n) ? 0 : n);
487
+ }
488
+ }
489
+ ),
490
+ isMultiActive ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-edit-form__hint", children: [
491
+ form.sortOrder,
492
+ " ~ ",
493
+ form.sortOrder + multiImages.length - 1
494
+ ] }) : null
495
+ ] })
496
+ ] })
497
+ ] });
498
+ }
499
+
500
+ // src/components/GalleryHomePreview.tsx
501
+
502
+
503
+ var DEFAULT_TEXT4 = {
504
+ "preview.title": "Home preview",
505
+ "preview.dragHint": "Drag tiles to reorder",
506
+ "preview.emptyState": "Select images to feature on the home page"
507
+ };
508
+ function t3(i18n, key) {
509
+ return _nullishCoalesce(_nullishCoalesce(_optionalChain([i18n, 'optionalAccess', _16 => _16[key]]), () => ( DEFAULT_TEXT4[key])), () => ( key));
510
+ }
511
+ function GalleryHomePreview(props) {
512
+ const { items, maxCount, onReorder, onToggleFeatured, i18n, className } = props;
513
+ const [dragIndex, setDragIndex] = _react.useState.call(void 0, null);
514
+ const [dragOverIndex, setDragOverIndex] = _react.useState.call(void 0, null);
515
+ const reorderedRef = _react.useRef.call(void 0, null);
516
+ const handleDragStart = _react.useCallback.call(void 0,
517
+ (e, index) => {
518
+ setDragIndex(index);
519
+ try {
520
+ e.dataTransfer.effectAllowed = "move";
521
+ } catch (e2) {
522
+ }
523
+ },
524
+ []
525
+ );
526
+ const handleDragOver = _react.useCallback.call(void 0,
527
+ (e, index) => {
528
+ e.preventDefault();
529
+ try {
530
+ e.dataTransfer.dropEffect = "move";
531
+ } catch (e3) {
532
+ }
533
+ setDragOverIndex(index);
534
+ },
535
+ []
536
+ );
537
+ const handleDrop = _react.useCallback.call(void 0,
538
+ (e, dropIndex) => {
539
+ e.preventDefault();
540
+ if (dragIndex === null || dragIndex === dropIndex) {
541
+ reorderedRef.current = null;
542
+ return;
543
+ }
544
+ const reordered = [...items];
545
+ const tmp = reordered[dragIndex];
546
+ reordered[dragIndex] = reordered[dropIndex];
547
+ reordered[dropIndex] = tmp;
548
+ reorderedRef.current = reordered.map((it) => it.id);
549
+ },
550
+ [dragIndex, items]
551
+ );
552
+ const handleDragEnd = _react.useCallback.call(void 0, () => {
553
+ if (reorderedRef.current) {
554
+ void onReorder(reorderedRef.current);
555
+ }
556
+ reorderedRef.current = null;
557
+ setDragIndex(null);
558
+ setDragOverIndex(null);
559
+ }, [onReorder]);
560
+ const handleStarClick = _react.useCallback.call(void 0,
561
+ (item) => {
562
+ void onToggleFeatured(item.id, !item.featured);
563
+ },
564
+ [onToggleFeatured]
565
+ );
566
+ if (items.length === 0) {
567
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-home-preview", "gallery-home-preview--empty", className), children: [
568
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-home-preview__header", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "gallery-home-preview__title", children: t3(i18n, "preview.title") }) }),
569
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-home-preview__empty", children: t3(i18n, "preview.emptyState") })
570
+ ] });
571
+ }
572
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-home-preview", className), children: [
573
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-home-preview__header", children: [
574
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "gallery-home-preview__title", children: t3(i18n, "preview.title") }),
575
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "gallery-home-preview__count", children: [
576
+ items.length,
577
+ "/",
578
+ maxCount
579
+ ] })
580
+ ] }),
581
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-home-preview__hint", children: t3(i18n, "preview.dragHint") }),
582
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
583
+ "div",
584
+ {
585
+ className: "gallery-home-preview__mosaic",
586
+ "data-count": maxCount,
587
+ children: Array.from({ length: maxCount }).map((_, slotIndex) => {
588
+ const item = items[slotIndex];
589
+ if (!item) {
590
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
591
+ "div",
592
+ {
593
+ className: "gallery-home-preview__tile gallery-home-preview__tile--empty",
594
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "gallery-home-preview__order", children: slotIndex + 1 })
595
+ },
596
+ `empty-${slotIndex}`
597
+ );
598
+ }
599
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
600
+ "div",
601
+ {
602
+ className: _chunkXLPMNJJVjs.cn.call(void 0,
603
+ "gallery-home-preview__tile",
604
+ dragOverIndex === slotIndex && "gallery-home-preview__tile--drag-over",
605
+ dragIndex === slotIndex && "gallery-home-preview__tile--dragging"
606
+ ),
607
+ draggable: true,
608
+ onDragStart: (e) => handleDragStart(e, slotIndex),
609
+ onDragOver: (e) => handleDragOver(e, slotIndex),
610
+ onDrop: (e) => handleDrop(e, slotIndex),
611
+ onDragEnd: handleDragEnd,
612
+ children: [
613
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
614
+ "img",
615
+ {
616
+ src: item.imageUrl,
617
+ alt: _nullishCoalesce(item.caption, () => ( "")),
618
+ className: "gallery-home-preview__img"
619
+ }
620
+ ),
621
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
622
+ "button",
623
+ {
624
+ type: "button",
625
+ className: _chunkXLPMNJJVjs.cn.call(void 0,
626
+ "gallery-home-preview__star",
627
+ item.featured && "gallery-home-preview__star--active"
628
+ ),
629
+ onClick: () => handleStarClick(item),
630
+ "aria-label": item.featured ? "unfeature" : "feature",
631
+ "aria-pressed": item.featured,
632
+ children: item.featured ? "\u2605" : "\u2606"
633
+ }
634
+ ),
635
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "gallery-home-preview__order", children: slotIndex + 1 }),
636
+ item.caption ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-home-preview__caption", children: item.caption }) : null
637
+ ]
638
+ },
639
+ item.id
640
+ );
641
+ })
642
+ }
643
+ )
644
+ ] });
645
+ }
646
+
647
+ // src/components/GalleryAdminManager.tsx
648
+
649
+
650
+
651
+
652
+
653
+
654
+
655
+
656
+ async function clientFetch(url, init) {
657
+ return fetch(url, {
658
+ credentials: "include",
659
+ headers: _optionalChain([init, 'optionalAccess', _17 => _17.body]) ? { "Content-Type": "application/json", ..._optionalChain([init, 'optionalAccess', _18 => _18.headers]) } : _optionalChain([init, 'optionalAccess', _19 => _19.headers]),
660
+ ...init
661
+ });
662
+ }
663
+ async function jsonOrNull(res) {
664
+ try {
665
+ return await res.json();
666
+ } catch (e4) {
667
+ return null;
668
+ }
669
+ }
670
+ var DEFAULT_TEXT5 = {
671
+ "admin.title": "Gallery",
672
+ "admin.newButton": "+ New",
673
+ "admin.searchPlaceholder": "Search...",
674
+ "admin.emptyState": "Select an item to edit"
675
+ };
676
+ function t4(i18n, key) {
677
+ return _nullishCoalesce(_nullishCoalesce(_optionalChain([i18n, 'optionalAccess', _20 => _20[key]]), () => ( DEFAULT_TEXT5[key])), () => ( key));
678
+ }
679
+ function GalleryAdminManager(props) {
680
+ const { initialMode, initialSelectedId, className } = props;
681
+ const config = _chunkWVRTM6E7js.getGalleryConfig.call(void 0, );
682
+ const i18n = _nullishCoalesce(config.i18n, () => ( {}));
683
+ const ui = config.ui;
684
+ const maxFeatured = config.limits.maxFeatured;
685
+ const mosaicCount = config.limits.mosaicCount;
686
+ const [items, setItems] = _react.useState.call(void 0, []);
687
+ const [categories, setCategories] = _react.useState.call(void 0, []);
688
+ const [selectedId, setSelectedId] = _react.useState.call(void 0, _nullishCoalesce(initialSelectedId, () => ( null)));
689
+ const [mode, setMode] = _react.useState.call(void 0,
690
+ _nullishCoalesce(initialMode, () => ( (initialSelectedId ? "edit" : "list")))
691
+ );
692
+ const [saving, setSaving] = _react.useState.call(void 0, false);
693
+ const [search, setSearch] = _react.useState.call(void 0, "");
694
+ const [refreshKey, setRefreshKey] = _react.useState.call(void 0, 0);
695
+ const mountedRef = _react.useRef.call(void 0, true);
696
+ _react.useEffect.call(void 0, () => {
697
+ mountedRef.current = true;
698
+ return () => {
699
+ mountedRef.current = false;
700
+ };
701
+ }, []);
702
+ _react.useEffect.call(void 0, () => {
703
+ let cancelled = false;
704
+ (async () => {
705
+ try {
706
+ const res = await clientFetch("/api/admin/galleries");
707
+ const json = await jsonOrNull(res);
708
+ if (!cancelled && mountedRef.current) {
709
+ const data = _nullishCoalesce(_nullishCoalesce(_optionalChain([json, 'optionalAccess', _21 => _21.data]), () => ( json)), () => ( []));
710
+ setItems(Array.isArray(data) ? data : []);
711
+ }
712
+ } catch (e5) {
713
+ }
714
+ })();
715
+ return () => {
716
+ cancelled = true;
717
+ };
718
+ }, [refreshKey]);
719
+ _react.useEffect.call(void 0, () => {
720
+ let cancelled = false;
721
+ (async () => {
722
+ try {
723
+ const res = await clientFetch("/api/admin/gallery-categories");
724
+ const json = await jsonOrNull(res);
725
+ if (!cancelled && mountedRef.current) {
726
+ const data = _nullishCoalesce(_nullishCoalesce(_optionalChain([json, 'optionalAccess', _22 => _22.data]), () => ( json)), () => ( []));
727
+ setCategories(Array.isArray(data) ? data : []);
728
+ }
729
+ } catch (e6) {
730
+ }
731
+ })();
732
+ return () => {
733
+ cancelled = true;
734
+ };
735
+ }, []);
736
+ const selectedItem = _react.useMemo.call(void 0, () => {
737
+ if (!selectedId) return null;
738
+ return _nullishCoalesce(items.find((i) => i.id === selectedId), () => ( null));
739
+ }, [selectedId, items]);
740
+ const filteredItems = _react.useMemo.call(void 0, () => {
741
+ if (!search.trim()) return items;
742
+ const q = search.trim().toLowerCase();
743
+ return items.filter((g) => (_nullishCoalesce(g.caption, () => ( ""))).toLowerCase().includes(q));
744
+ }, [items, search]);
745
+ const featuredCount = _react.useMemo.call(void 0,
746
+ () => items.filter((i) => i.featured && i.published).length,
747
+ [items]
748
+ );
749
+ const previewItems = _react.useMemo.call(void 0,
750
+ () => items.filter((i) => i.featured && i.published).sort((a, b) => a.sortOrder - b.sortOrder),
751
+ [items]
752
+ );
753
+ const refresh = _react.useCallback.call(void 0, () => setRefreshKey((k) => k + 1), []);
754
+ const handleSelect = _react.useCallback.call(void 0, (id) => {
755
+ setSelectedId(id);
756
+ setMode("edit");
757
+ }, []);
758
+ const handleNewClick = _react.useCallback.call(void 0, () => {
759
+ setSelectedId(null);
760
+ setMode("new");
761
+ }, []);
762
+ const handleCancel = _react.useCallback.call(void 0, () => {
763
+ setSelectedId(null);
764
+ setMode("list");
765
+ }, []);
766
+ const handleSubmit = _react.useCallback.call(void 0,
767
+ async (data) => {
768
+ setSaving(true);
769
+ try {
770
+ if (mode === "new") {
771
+ await clientFetch("/api/admin/galleries", {
772
+ method: "POST",
773
+ body: JSON.stringify(data)
774
+ });
775
+ } else if (selectedId) {
776
+ await clientFetch(`/api/admin/galleries/${selectedId}`, {
777
+ method: "PUT",
778
+ body: JSON.stringify(data)
779
+ });
780
+ }
781
+ if (mountedRef.current) {
782
+ refresh();
783
+ setMode("list");
784
+ setSelectedId(null);
785
+ }
786
+ } finally {
787
+ if (mountedRef.current) setSaving(false);
788
+ }
789
+ },
790
+ [mode, selectedId, refresh]
791
+ );
792
+ const handleSubmitMany = _react.useCallback.call(void 0,
793
+ async (batch) => {
794
+ setSaving(true);
795
+ try {
796
+ await clientFetch("/api/admin/galleries/bulk", {
797
+ method: "POST",
798
+ body: JSON.stringify({ items: batch })
799
+ });
800
+ if (mountedRef.current) {
801
+ refresh();
802
+ setMode("list");
803
+ }
804
+ } finally {
805
+ if (mountedRef.current) setSaving(false);
806
+ }
807
+ },
808
+ [refresh]
809
+ );
810
+ const handleDelete = _react.useCallback.call(void 0, async () => {
811
+ if (!selectedId) return;
812
+ if (typeof window !== "undefined" && !window.confirm("Delete this item?")) return;
813
+ await clientFetch(`/api/admin/galleries/${selectedId}`, { method: "DELETE" });
814
+ if (mountedRef.current) {
815
+ setSelectedId(null);
816
+ setMode("list");
817
+ refresh();
818
+ }
819
+ }, [selectedId, refresh]);
820
+ const handleToggleFeatured = _react.useCallback.call(void 0,
821
+ async (id, next) => {
822
+ if (next && featuredCount >= maxFeatured) {
823
+ if (typeof window !== "undefined") {
824
+ window.alert(t4(i18n, "admin.featuredOverLimit"));
825
+ }
826
+ return;
827
+ }
828
+ await clientFetch(`/api/admin/galleries/${id}`, {
829
+ method: "PUT",
830
+ body: JSON.stringify({ featured: next })
831
+ });
832
+ if (mountedRef.current) refresh();
833
+ },
834
+ [featuredCount, maxFeatured, i18n, refresh]
835
+ );
836
+ const handleReorder = _react.useCallback.call(void 0,
837
+ async (orderedIds) => {
838
+ await Promise.all(
839
+ orderedIds.map(
840
+ (id, index) => clientFetch(`/api/admin/galleries/${id}`, {
841
+ method: "PUT",
842
+ body: JSON.stringify({ sortOrder: index + 1 })
843
+ })
844
+ )
845
+ );
846
+ if (mountedRef.current) refresh();
847
+ },
848
+ [refresh]
849
+ );
850
+ const isMultipleMode = mode === "new";
851
+ const editForm = mode === "list" ? null : /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
852
+ GalleryEditForm,
853
+ {
854
+ value: mode === "edit" ? selectedItem : null,
855
+ categories,
856
+ saving,
857
+ multipleMode: isMultipleMode,
858
+ canToggleFeatured: featuredCount < maxFeatured || (_nullishCoalesce(_optionalChain([selectedItem, 'optionalAccess', _23 => _23.featured]), () => ( false))),
859
+ i18n,
860
+ onSubmit: handleSubmit,
861
+ onSubmitMany: handleSubmitMany,
862
+ onCancel: handleCancel,
863
+ onDelete: mode === "edit" ? handleDelete : void 0
864
+ },
865
+ mode === "new" ? "new" : _nullishCoalesce(selectedId, () => ( "edit"))
866
+ );
867
+ const filterControls = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-manager__filter-controls", children: [
868
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
869
+ "input",
870
+ {
871
+ type: "search",
872
+ className: "gallery-manager__search",
873
+ placeholder: t4(i18n, "admin.searchPlaceholder"),
874
+ value: search,
875
+ onChange: (e) => setSearch(e.target.value)
876
+ }
877
+ ),
878
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "gallery-manager__count", children: filteredItems.length })
879
+ ] });
880
+ const toolbar = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-manager__toolbar-row", children: [
881
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h1", { className: "gallery-manager__title", children: t4(i18n, "admin.title") }),
882
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
883
+ "button",
884
+ {
885
+ type: "button",
886
+ className: "gallery-manager__new-btn",
887
+ onClick: handleNewClick,
888
+ children: t4(i18n, "admin.newButton")
889
+ }
890
+ )
891
+ ] });
892
+ const listRoot = /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "ul", { className: "gallery-manager__list-ul", children: filteredItems.map((item) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
893
+ "li",
894
+ {
895
+ className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-list-item", selectedId === item.id && "gallery-list-item--active"),
896
+ onClick: () => handleSelect(item.id),
897
+ children: [
898
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { src: item.imageUrl, alt: "", className: "gallery-list-item__thumb" }),
899
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-list-item__body", children: [
900
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-list-item__caption", children: item.caption || "(no caption)" }),
901
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-list-item__meta", children: [
902
+ item.featured ? "\u2605 " : "",
903
+ item.published ? "\uACF5\uAC1C" : "\uBE44\uACF5\uAC1C",
904
+ " \xB7 \uC21C\uC11C ",
905
+ item.sortOrder
906
+ ] })
907
+ ] })
908
+ ]
909
+ },
910
+ item.id
911
+ )) });
912
+ const homePreview = /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
913
+ GalleryHomePreview,
914
+ {
915
+ items: previewItems,
916
+ maxCount: mosaicCount,
917
+ onReorder: handleReorder,
918
+ onToggleFeatured: handleToggleFeatured,
919
+ i18n
920
+ }
921
+ );
922
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
923
+ GalleryManagerLayout,
924
+ {
925
+ className,
926
+ listRoot,
927
+ filterControls,
928
+ toolbar,
929
+ editForm,
930
+ homePreview,
931
+ i18n,
932
+ ui
933
+ }
934
+ );
935
+ }
936
+
937
+ // src/components/CategoryAdminManager.tsx
938
+
939
+
940
+
941
+
942
+
943
+
944
+
945
+ var DEFAULT_TEXT6 = {
946
+ "category.title": "Gallery categories",
947
+ "category.newButton": "+ New category",
948
+ "category.slug": "Slug (uppercase)",
949
+ "category.labelKo": "Label (Ko)",
950
+ "category.labelEn": "Label (En)",
951
+ "category.deleteConfirmInUse": "This category has galleries \u2014 remove or move them first"
952
+ };
953
+ function t5(i18n, key) {
954
+ return _nullishCoalesce(_nullishCoalesce(_optionalChain([i18n, 'optionalAccess', _24 => _24[key]]), () => ( DEFAULT_TEXT6[key])), () => ( key));
955
+ }
956
+ var EMPTY_FORM = {
957
+ id: null,
958
+ slug: "",
959
+ labelKo: "",
960
+ labelEn: "",
961
+ sortOrder: 0,
962
+ isActive: true
963
+ };
964
+ async function clientFetch2(url, init) {
965
+ return fetch(url, {
966
+ credentials: "include",
967
+ headers: _optionalChain([init, 'optionalAccess', _25 => _25.body]) ? { "Content-Type": "application/json", ..._optionalChain([init, 'optionalAccess', _26 => _26.headers]) } : _optionalChain([init, 'optionalAccess', _27 => _27.headers]),
968
+ ...init
969
+ });
970
+ }
971
+ async function jsonOrNull2(res) {
972
+ try {
973
+ return await res.json();
974
+ } catch (e7) {
975
+ return null;
976
+ }
977
+ }
978
+ function CategoryAdminManager(props) {
979
+ const { className } = props;
980
+ const config = _chunkWVRTM6E7js.getGalleryConfig.call(void 0, );
981
+ const i18n = _nullishCoalesce(config.i18n, () => ( {}));
982
+ const [items, setItems] = _react.useState.call(void 0, []);
983
+ const [form, setForm] = _react.useState.call(void 0, EMPTY_FORM);
984
+ const [error, setError] = _react.useState.call(void 0, null);
985
+ const [refreshKey, setRefreshKey] = _react.useState.call(void 0, 0);
986
+ const [saving, setSaving] = _react.useState.call(void 0, false);
987
+ const mountedRef = _react.useRef.call(void 0, true);
988
+ _react.useEffect.call(void 0, () => {
989
+ mountedRef.current = true;
990
+ return () => {
991
+ mountedRef.current = false;
992
+ };
993
+ }, []);
994
+ _react.useEffect.call(void 0, () => {
995
+ let cancelled = false;
996
+ (async () => {
997
+ try {
998
+ const res = await clientFetch2("/api/admin/gallery-categories");
999
+ const json = await jsonOrNull2(res);
1000
+ if (!cancelled && mountedRef.current) {
1001
+ const data = _nullishCoalesce(_nullishCoalesce(_optionalChain([json, 'optionalAccess', _28 => _28.data]), () => ( json)), () => ( []));
1002
+ setItems(Array.isArray(data) ? data : []);
1003
+ }
1004
+ } catch (e8) {
1005
+ }
1006
+ })();
1007
+ return () => {
1008
+ cancelled = true;
1009
+ };
1010
+ }, [refreshKey]);
1011
+ const refresh = _react.useCallback.call(void 0, () => setRefreshKey((k) => k + 1), []);
1012
+ const handleEdit = _react.useCallback.call(void 0, (cat) => {
1013
+ setError(null);
1014
+ setForm({
1015
+ id: cat.id,
1016
+ slug: cat.slug,
1017
+ labelKo: cat.labelKo,
1018
+ labelEn: _nullishCoalesce(cat.labelEn, () => ( "")),
1019
+ sortOrder: cat.sortOrder,
1020
+ isActive: cat.isActive
1021
+ });
1022
+ }, []);
1023
+ const handleNew = _react.useCallback.call(void 0, () => {
1024
+ setError(null);
1025
+ setForm(EMPTY_FORM);
1026
+ }, []);
1027
+ const handleSave = _react.useCallback.call(void 0, async () => {
1028
+ setSaving(true);
1029
+ setError(null);
1030
+ try {
1031
+ const body = JSON.stringify({
1032
+ slug: form.slug,
1033
+ labelKo: form.labelKo,
1034
+ labelEn: form.labelEn || void 0,
1035
+ sortOrder: form.sortOrder,
1036
+ isActive: form.isActive
1037
+ });
1038
+ const url = form.id ? `/api/admin/gallery-categories/${form.id}` : "/api/admin/gallery-categories";
1039
+ const method = form.id ? "PUT" : "POST";
1040
+ const res = await clientFetch2(url, { method, body });
1041
+ if (!res.ok) {
1042
+ const json = await jsonOrNull2(res);
1043
+ setError(_nullishCoalesce(_optionalChain([json, 'optionalAccess', _29 => _29.error, 'optionalAccess', _30 => _30.message]), () => ( `Save failed (${res.status})`)));
1044
+ return;
1045
+ }
1046
+ if (mountedRef.current) {
1047
+ setForm(EMPTY_FORM);
1048
+ refresh();
1049
+ }
1050
+ } finally {
1051
+ if (mountedRef.current) setSaving(false);
1052
+ }
1053
+ }, [form, refresh]);
1054
+ const handleDelete = _react.useCallback.call(void 0,
1055
+ async (cat) => {
1056
+ if (typeof window !== "undefined" && !window.confirm(`Delete "${cat.labelKo}"?`)) return;
1057
+ setError(null);
1058
+ const res = await clientFetch2(`/api/admin/gallery-categories/${cat.id}`, {
1059
+ method: "DELETE"
1060
+ });
1061
+ if (res.status === 409) {
1062
+ setError(t5(i18n, "category.deleteConfirmInUse"));
1063
+ return;
1064
+ }
1065
+ if (!res.ok) {
1066
+ const json = await jsonOrNull2(res);
1067
+ setError(_nullishCoalesce(_optionalChain([json, 'optionalAccess', _31 => _31.error, 'optionalAccess', _32 => _32.message]), () => ( `Delete failed (${res.status})`)));
1068
+ return;
1069
+ }
1070
+ if (mountedRef.current) refresh();
1071
+ },
1072
+ [i18n, refresh]
1073
+ );
1074
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkXLPMNJJVjs.cn.call(void 0, "gallery-category-admin", className), children: [
1075
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "header", { className: "gallery-category-admin__header", children: [
1076
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "gallery-category-admin__title", children: t5(i18n, "category.title") }),
1077
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1078
+ "button",
1079
+ {
1080
+ type: "button",
1081
+ className: "gallery-category-admin__new",
1082
+ onClick: handleNew,
1083
+ children: t5(i18n, "category.newButton")
1084
+ }
1085
+ )
1086
+ ] }),
1087
+ error ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-category-admin__error", role: "alert", children: error }) : null,
1088
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "ul", { className: "gallery-category-admin__list", children: items.map((c) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "li", { className: "gallery-category-admin__item", children: [
1089
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
1090
+ "button",
1091
+ {
1092
+ type: "button",
1093
+ className: "gallery-category-admin__item-row",
1094
+ onClick: () => handleEdit(c),
1095
+ children: [
1096
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "gallery-category-admin__slug", children: c.slug }),
1097
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "gallery-category-admin__label", children: c.labelKo }),
1098
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "gallery-category-admin__order", children: [
1099
+ "#",
1100
+ c.sortOrder
1101
+ ] }),
1102
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "gallery-category-admin__active", children: c.isActive ? "\u2713" : "\u2717" })
1103
+ ]
1104
+ }
1105
+ ),
1106
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1107
+ "button",
1108
+ {
1109
+ type: "button",
1110
+ className: "gallery-category-admin__delete",
1111
+ onClick: () => void handleDelete(c),
1112
+ "aria-label": "delete",
1113
+ children: "\xD7"
1114
+ }
1115
+ )
1116
+ ] }, c.id)) }),
1117
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-category-admin__form", children: [
1118
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "gallery-category-admin__form-title", children: form.id ? "Edit category" : "New category" }),
1119
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-category-admin__field", children: [
1120
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { className: "gallery-category-admin__field-label", children: t5(i18n, "category.slug") }),
1121
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1122
+ "input",
1123
+ {
1124
+ name: "slug",
1125
+ className: "gallery-category-admin__input",
1126
+ value: form.slug,
1127
+ onChange: (e) => setForm((f) => ({ ...f, slug: e.target.value.toUpperCase() })),
1128
+ placeholder: "UPPER_SNAKE"
1129
+ }
1130
+ )
1131
+ ] }),
1132
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-category-admin__field", children: [
1133
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { className: "gallery-category-admin__field-label", children: t5(i18n, "category.labelKo") }),
1134
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1135
+ "input",
1136
+ {
1137
+ name: "labelKo",
1138
+ className: "gallery-category-admin__input",
1139
+ value: form.labelKo,
1140
+ onChange: (e) => setForm((f) => ({ ...f, labelKo: e.target.value }))
1141
+ }
1142
+ )
1143
+ ] }),
1144
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-category-admin__field", children: [
1145
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { className: "gallery-category-admin__field-label", children: t5(i18n, "category.labelEn") }),
1146
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1147
+ "input",
1148
+ {
1149
+ name: "labelEn",
1150
+ className: "gallery-category-admin__input",
1151
+ value: form.labelEn,
1152
+ onChange: (e) => setForm((f) => ({ ...f, labelEn: e.target.value }))
1153
+ }
1154
+ )
1155
+ ] }),
1156
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-category-admin__field gallery-category-admin__field--row", children: [
1157
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "label", { className: "gallery-category-admin__field-label", children: "Sort order" }),
1158
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1159
+ "input",
1160
+ {
1161
+ type: "number",
1162
+ name: "sortOrder",
1163
+ className: "gallery-category-admin__input",
1164
+ value: form.sortOrder,
1165
+ min: 0,
1166
+ onChange: (e) => {
1167
+ const n = parseInt(e.target.value, 10);
1168
+ setForm((f) => ({ ...f, sortOrder: Number.isNaN(n) ? 0 : n }));
1169
+ }
1170
+ }
1171
+ ),
1172
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1173
+ ToggleSwitch,
1174
+ {
1175
+ checked: form.isActive,
1176
+ onChange: (v) => setForm((f) => ({ ...f, isActive: v })),
1177
+ label: "Active",
1178
+ size: "sm"
1179
+ }
1180
+ )
1181
+ ] }),
1182
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "gallery-category-admin__form-actions", children: [
1183
+ form.id ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1184
+ "button",
1185
+ {
1186
+ type: "button",
1187
+ className: "gallery-category-admin__cancel",
1188
+ onClick: () => setForm(EMPTY_FORM),
1189
+ children: "Cancel"
1190
+ }
1191
+ ) : null,
1192
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1193
+ "button",
1194
+ {
1195
+ type: "button",
1196
+ className: "gallery-category-admin__save",
1197
+ onClick: () => void handleSave(),
1198
+ disabled: saving || !form.slug || !form.labelKo,
1199
+ children: saving ? "Saving..." : form.id ? "Update" : "Create"
1200
+ }
1201
+ )
1202
+ ] })
1203
+ ] })
1204
+ ] });
1205
+ }
1206
+
1207
+
1208
+
1209
+
1210
+
1211
+
1212
+
1213
+
1214
+ exports.CategoryAdminManager = CategoryAdminManager; exports.GalleryAdminManager = GalleryAdminManager; exports.GalleryEditForm = GalleryEditForm; exports.GalleryHomePreview = GalleryHomePreview; exports.GalleryManagerLayout = GalleryManagerLayout; exports.ImageDropZone = ImageDropZone; exports.ToggleSwitch = ToggleSwitch;
1215
+ //# sourceMappingURL=index.js.map