@lodashventure/medusa-banner 0.0.1
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/.medusa/server/src/admin/index.js +1298 -0
- package/.medusa/server/src/admin/index.mjs +1297 -0
- package/.medusa/server/src/api/admin/banners/reorder/route.js +17 -0
- package/.medusa/server/src/api/admin/banners/route.js +57 -0
- package/.medusa/server/src/api/middlewares.js +31 -0
- package/.medusa/server/src/modules/banner/index.js +13 -0
- package/.medusa/server/src/modules/banner/migrations/Migration20250414061948.js +15 -0
- package/.medusa/server/src/modules/banner/models/banner.js +10 -0
- package/.medusa/server/src/modules/banner/service.js +13 -0
- package/.medusa/server/src/workflows/delete-banners.js +24 -0
- package/.medusa/server/src/workflows/reorder-banners.js +23 -0
- package/.medusa/server/src/workflows/upload-banners.js +54 -0
- package/README.md +35 -0
- package/package.json +84 -0
|
@@ -0,0 +1,1298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
3
|
+
const adminSdk = require("@medusajs/admin-sdk");
|
|
4
|
+
const icons = require("@medusajs/icons");
|
|
5
|
+
const core = require("@dnd-kit/core");
|
|
6
|
+
const React = require("react");
|
|
7
|
+
const ui = require("@medusajs/ui");
|
|
8
|
+
const lucideReact = require("lucide-react");
|
|
9
|
+
const reactQuery = require("@tanstack/react-query");
|
|
10
|
+
const reactDropzone = require("react-dropzone");
|
|
11
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
12
|
+
const React__default = /* @__PURE__ */ _interopDefault(React);
|
|
13
|
+
function useCombinedRefs() {
|
|
14
|
+
for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
15
|
+
refs[_key] = arguments[_key];
|
|
16
|
+
}
|
|
17
|
+
return React.useMemo(
|
|
18
|
+
() => (node) => {
|
|
19
|
+
refs.forEach((ref) => ref(node));
|
|
20
|
+
},
|
|
21
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
22
|
+
refs
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const canUseDOM = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
|
|
26
|
+
function isWindow(element) {
|
|
27
|
+
const elementString = Object.prototype.toString.call(element);
|
|
28
|
+
return elementString === "[object Window]" || // In Electron context the Window object serializes to [object global]
|
|
29
|
+
elementString === "[object global]";
|
|
30
|
+
}
|
|
31
|
+
function isNode(node) {
|
|
32
|
+
return "nodeType" in node;
|
|
33
|
+
}
|
|
34
|
+
function getWindow(target) {
|
|
35
|
+
var _target$ownerDocument, _target$ownerDocument2;
|
|
36
|
+
if (!target) {
|
|
37
|
+
return window;
|
|
38
|
+
}
|
|
39
|
+
if (isWindow(target)) {
|
|
40
|
+
return target;
|
|
41
|
+
}
|
|
42
|
+
if (!isNode(target)) {
|
|
43
|
+
return window;
|
|
44
|
+
}
|
|
45
|
+
return (_target$ownerDocument = (_target$ownerDocument2 = target.ownerDocument) == null ? void 0 : _target$ownerDocument2.defaultView) != null ? _target$ownerDocument : window;
|
|
46
|
+
}
|
|
47
|
+
const useIsomorphicLayoutEffect = canUseDOM ? React.useLayoutEffect : React.useEffect;
|
|
48
|
+
let ids = {};
|
|
49
|
+
function useUniqueId(prefix, value) {
|
|
50
|
+
return React.useMemo(() => {
|
|
51
|
+
if (value) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
const id = ids[prefix] == null ? 0 : ids[prefix] + 1;
|
|
55
|
+
ids[prefix] = id;
|
|
56
|
+
return prefix + "-" + id;
|
|
57
|
+
}, [prefix, value]);
|
|
58
|
+
}
|
|
59
|
+
function createAdjustmentFn(modifier) {
|
|
60
|
+
return function(object) {
|
|
61
|
+
for (var _len = arguments.length, adjustments = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
62
|
+
adjustments[_key - 1] = arguments[_key];
|
|
63
|
+
}
|
|
64
|
+
return adjustments.reduce((accumulator, adjustment) => {
|
|
65
|
+
const entries = Object.entries(adjustment);
|
|
66
|
+
for (const [key, valueAdjustment] of entries) {
|
|
67
|
+
const value = accumulator[key];
|
|
68
|
+
if (value != null) {
|
|
69
|
+
accumulator[key] = value + modifier * valueAdjustment;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return accumulator;
|
|
73
|
+
}, {
|
|
74
|
+
...object
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const subtract = /* @__PURE__ */ createAdjustmentFn(-1);
|
|
79
|
+
function isKeyboardEvent(event) {
|
|
80
|
+
if (!event) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const {
|
|
84
|
+
KeyboardEvent
|
|
85
|
+
} = getWindow(event.target);
|
|
86
|
+
return KeyboardEvent && event instanceof KeyboardEvent;
|
|
87
|
+
}
|
|
88
|
+
const CSS = /* @__PURE__ */ Object.freeze({
|
|
89
|
+
Translate: {
|
|
90
|
+
toString(transform) {
|
|
91
|
+
if (!transform) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const {
|
|
95
|
+
x,
|
|
96
|
+
y
|
|
97
|
+
} = transform;
|
|
98
|
+
return "translate3d(" + (x ? Math.round(x) : 0) + "px, " + (y ? Math.round(y) : 0) + "px, 0)";
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
Scale: {
|
|
102
|
+
toString(transform) {
|
|
103
|
+
if (!transform) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const {
|
|
107
|
+
scaleX,
|
|
108
|
+
scaleY
|
|
109
|
+
} = transform;
|
|
110
|
+
return "scaleX(" + scaleX + ") scaleY(" + scaleY + ")";
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
Transform: {
|
|
114
|
+
toString(transform) {
|
|
115
|
+
if (!transform) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
return [CSS.Translate.toString(transform), CSS.Scale.toString(transform)].join(" ");
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
Transition: {
|
|
122
|
+
toString(_ref) {
|
|
123
|
+
let {
|
|
124
|
+
property,
|
|
125
|
+
duration,
|
|
126
|
+
easing
|
|
127
|
+
} = _ref;
|
|
128
|
+
return property + " " + duration + "ms " + easing;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
function arrayMove(array, from, to) {
|
|
133
|
+
const newArray = array.slice();
|
|
134
|
+
newArray.splice(to < 0 ? newArray.length + to : to, 0, newArray.splice(from, 1)[0]);
|
|
135
|
+
return newArray;
|
|
136
|
+
}
|
|
137
|
+
function getSortedRects(items, rects) {
|
|
138
|
+
return items.reduce((accumulator, id, index) => {
|
|
139
|
+
const rect = rects.get(id);
|
|
140
|
+
if (rect) {
|
|
141
|
+
accumulator[index] = rect;
|
|
142
|
+
}
|
|
143
|
+
return accumulator;
|
|
144
|
+
}, Array(items.length));
|
|
145
|
+
}
|
|
146
|
+
function isValidIndex(index) {
|
|
147
|
+
return index !== null && index >= 0;
|
|
148
|
+
}
|
|
149
|
+
function itemsEqual(a, b) {
|
|
150
|
+
if (a === b) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (a.length !== b.length) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
for (let i = 0; i < a.length; i++) {
|
|
157
|
+
if (a[i] !== b[i]) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
function normalizeDisabled(disabled) {
|
|
164
|
+
if (typeof disabled === "boolean") {
|
|
165
|
+
return {
|
|
166
|
+
draggable: disabled,
|
|
167
|
+
droppable: disabled
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return disabled;
|
|
171
|
+
}
|
|
172
|
+
const rectSortingStrategy = (_ref) => {
|
|
173
|
+
let {
|
|
174
|
+
rects,
|
|
175
|
+
activeIndex,
|
|
176
|
+
overIndex,
|
|
177
|
+
index
|
|
178
|
+
} = _ref;
|
|
179
|
+
const newRects = arrayMove(rects, overIndex, activeIndex);
|
|
180
|
+
const oldRect = rects[index];
|
|
181
|
+
const newRect = newRects[index];
|
|
182
|
+
if (!newRect || !oldRect) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
x: newRect.left - oldRect.left,
|
|
187
|
+
y: newRect.top - oldRect.top,
|
|
188
|
+
scaleX: newRect.width / oldRect.width,
|
|
189
|
+
scaleY: newRect.height / oldRect.height
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
const ID_PREFIX = "Sortable";
|
|
193
|
+
const Context = /* @__PURE__ */ React__default.default.createContext({
|
|
194
|
+
activeIndex: -1,
|
|
195
|
+
containerId: ID_PREFIX,
|
|
196
|
+
disableTransforms: false,
|
|
197
|
+
items: [],
|
|
198
|
+
overIndex: -1,
|
|
199
|
+
useDragOverlay: false,
|
|
200
|
+
sortedRects: [],
|
|
201
|
+
strategy: rectSortingStrategy,
|
|
202
|
+
disabled: {
|
|
203
|
+
draggable: false,
|
|
204
|
+
droppable: false
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
function SortableContext(_ref) {
|
|
208
|
+
let {
|
|
209
|
+
children,
|
|
210
|
+
id,
|
|
211
|
+
items: userDefinedItems,
|
|
212
|
+
strategy = rectSortingStrategy,
|
|
213
|
+
disabled: disabledProp = false
|
|
214
|
+
} = _ref;
|
|
215
|
+
const {
|
|
216
|
+
active,
|
|
217
|
+
dragOverlay,
|
|
218
|
+
droppableRects,
|
|
219
|
+
over,
|
|
220
|
+
measureDroppableContainers
|
|
221
|
+
} = core.useDndContext();
|
|
222
|
+
const containerId = useUniqueId(ID_PREFIX, id);
|
|
223
|
+
const useDragOverlay = Boolean(dragOverlay.rect !== null);
|
|
224
|
+
const items = React.useMemo(() => userDefinedItems.map((item) => typeof item === "object" && "id" in item ? item.id : item), [userDefinedItems]);
|
|
225
|
+
const isDragging = active != null;
|
|
226
|
+
const activeIndex = active ? items.indexOf(active.id) : -1;
|
|
227
|
+
const overIndex = over ? items.indexOf(over.id) : -1;
|
|
228
|
+
const previousItemsRef = React.useRef(items);
|
|
229
|
+
const itemsHaveChanged = !itemsEqual(items, previousItemsRef.current);
|
|
230
|
+
const disableTransforms = overIndex !== -1 && activeIndex === -1 || itemsHaveChanged;
|
|
231
|
+
const disabled = normalizeDisabled(disabledProp);
|
|
232
|
+
useIsomorphicLayoutEffect(() => {
|
|
233
|
+
if (itemsHaveChanged && isDragging) {
|
|
234
|
+
measureDroppableContainers(items);
|
|
235
|
+
}
|
|
236
|
+
}, [itemsHaveChanged, items, isDragging, measureDroppableContainers]);
|
|
237
|
+
React.useEffect(() => {
|
|
238
|
+
previousItemsRef.current = items;
|
|
239
|
+
}, [items]);
|
|
240
|
+
const contextValue = React.useMemo(
|
|
241
|
+
() => ({
|
|
242
|
+
activeIndex,
|
|
243
|
+
containerId,
|
|
244
|
+
disabled,
|
|
245
|
+
disableTransforms,
|
|
246
|
+
items,
|
|
247
|
+
overIndex,
|
|
248
|
+
useDragOverlay,
|
|
249
|
+
sortedRects: getSortedRects(items, droppableRects),
|
|
250
|
+
strategy
|
|
251
|
+
}),
|
|
252
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
253
|
+
[activeIndex, containerId, disabled.draggable, disabled.droppable, disableTransforms, items, overIndex, droppableRects, useDragOverlay, strategy]
|
|
254
|
+
);
|
|
255
|
+
return React__default.default.createElement(Context.Provider, {
|
|
256
|
+
value: contextValue
|
|
257
|
+
}, children);
|
|
258
|
+
}
|
|
259
|
+
const defaultNewIndexGetter = (_ref) => {
|
|
260
|
+
let {
|
|
261
|
+
id,
|
|
262
|
+
items,
|
|
263
|
+
activeIndex,
|
|
264
|
+
overIndex
|
|
265
|
+
} = _ref;
|
|
266
|
+
return arrayMove(items, activeIndex, overIndex).indexOf(id);
|
|
267
|
+
};
|
|
268
|
+
const defaultAnimateLayoutChanges = (_ref2) => {
|
|
269
|
+
let {
|
|
270
|
+
containerId,
|
|
271
|
+
isSorting,
|
|
272
|
+
wasDragging,
|
|
273
|
+
index,
|
|
274
|
+
items,
|
|
275
|
+
newIndex,
|
|
276
|
+
previousItems,
|
|
277
|
+
previousContainerId,
|
|
278
|
+
transition
|
|
279
|
+
} = _ref2;
|
|
280
|
+
if (!transition || !wasDragging) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
if (previousItems !== items && index === newIndex) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
if (isSorting) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
return newIndex !== index && containerId === previousContainerId;
|
|
290
|
+
};
|
|
291
|
+
const defaultTransition = {
|
|
292
|
+
duration: 200,
|
|
293
|
+
easing: "ease"
|
|
294
|
+
};
|
|
295
|
+
const transitionProperty = "transform";
|
|
296
|
+
const disabledTransition = /* @__PURE__ */ CSS.Transition.toString({
|
|
297
|
+
property: transitionProperty,
|
|
298
|
+
duration: 0,
|
|
299
|
+
easing: "linear"
|
|
300
|
+
});
|
|
301
|
+
const defaultAttributes = {
|
|
302
|
+
roleDescription: "sortable"
|
|
303
|
+
};
|
|
304
|
+
function useDerivedTransform(_ref) {
|
|
305
|
+
let {
|
|
306
|
+
disabled,
|
|
307
|
+
index,
|
|
308
|
+
node,
|
|
309
|
+
rect
|
|
310
|
+
} = _ref;
|
|
311
|
+
const [derivedTransform, setDerivedtransform] = React.useState(null);
|
|
312
|
+
const previousIndex = React.useRef(index);
|
|
313
|
+
useIsomorphicLayoutEffect(() => {
|
|
314
|
+
if (!disabled && index !== previousIndex.current && node.current) {
|
|
315
|
+
const initial = rect.current;
|
|
316
|
+
if (initial) {
|
|
317
|
+
const current = core.getClientRect(node.current, {
|
|
318
|
+
ignoreTransform: true
|
|
319
|
+
});
|
|
320
|
+
const delta = {
|
|
321
|
+
x: initial.left - current.left,
|
|
322
|
+
y: initial.top - current.top,
|
|
323
|
+
scaleX: initial.width / current.width,
|
|
324
|
+
scaleY: initial.height / current.height
|
|
325
|
+
};
|
|
326
|
+
if (delta.x || delta.y) {
|
|
327
|
+
setDerivedtransform(delta);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (index !== previousIndex.current) {
|
|
332
|
+
previousIndex.current = index;
|
|
333
|
+
}
|
|
334
|
+
}, [disabled, index, node, rect]);
|
|
335
|
+
React.useEffect(() => {
|
|
336
|
+
if (derivedTransform) {
|
|
337
|
+
setDerivedtransform(null);
|
|
338
|
+
}
|
|
339
|
+
}, [derivedTransform]);
|
|
340
|
+
return derivedTransform;
|
|
341
|
+
}
|
|
342
|
+
function useSortable(_ref) {
|
|
343
|
+
let {
|
|
344
|
+
animateLayoutChanges = defaultAnimateLayoutChanges,
|
|
345
|
+
attributes: userDefinedAttributes,
|
|
346
|
+
disabled: localDisabled,
|
|
347
|
+
data: customData,
|
|
348
|
+
getNewIndex = defaultNewIndexGetter,
|
|
349
|
+
id,
|
|
350
|
+
strategy: localStrategy,
|
|
351
|
+
resizeObserverConfig,
|
|
352
|
+
transition = defaultTransition
|
|
353
|
+
} = _ref;
|
|
354
|
+
const {
|
|
355
|
+
items,
|
|
356
|
+
containerId,
|
|
357
|
+
activeIndex,
|
|
358
|
+
disabled: globalDisabled,
|
|
359
|
+
disableTransforms,
|
|
360
|
+
sortedRects,
|
|
361
|
+
overIndex,
|
|
362
|
+
useDragOverlay,
|
|
363
|
+
strategy: globalStrategy
|
|
364
|
+
} = React.useContext(Context);
|
|
365
|
+
const disabled = normalizeLocalDisabled(localDisabled, globalDisabled);
|
|
366
|
+
const index = items.indexOf(id);
|
|
367
|
+
const data = React.useMemo(() => ({
|
|
368
|
+
sortable: {
|
|
369
|
+
containerId,
|
|
370
|
+
index,
|
|
371
|
+
items
|
|
372
|
+
},
|
|
373
|
+
...customData
|
|
374
|
+
}), [containerId, customData, index, items]);
|
|
375
|
+
const itemsAfterCurrentSortable = React.useMemo(() => items.slice(items.indexOf(id)), [items, id]);
|
|
376
|
+
const {
|
|
377
|
+
rect,
|
|
378
|
+
node,
|
|
379
|
+
isOver,
|
|
380
|
+
setNodeRef: setDroppableNodeRef
|
|
381
|
+
} = core.useDroppable({
|
|
382
|
+
id,
|
|
383
|
+
data,
|
|
384
|
+
disabled: disabled.droppable,
|
|
385
|
+
resizeObserverConfig: {
|
|
386
|
+
updateMeasurementsFor: itemsAfterCurrentSortable,
|
|
387
|
+
...resizeObserverConfig
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
const {
|
|
391
|
+
active,
|
|
392
|
+
activatorEvent,
|
|
393
|
+
activeNodeRect,
|
|
394
|
+
attributes,
|
|
395
|
+
setNodeRef: setDraggableNodeRef,
|
|
396
|
+
listeners,
|
|
397
|
+
isDragging,
|
|
398
|
+
over,
|
|
399
|
+
setActivatorNodeRef,
|
|
400
|
+
transform
|
|
401
|
+
} = core.useDraggable({
|
|
402
|
+
id,
|
|
403
|
+
data,
|
|
404
|
+
attributes: {
|
|
405
|
+
...defaultAttributes,
|
|
406
|
+
...userDefinedAttributes
|
|
407
|
+
},
|
|
408
|
+
disabled: disabled.draggable
|
|
409
|
+
});
|
|
410
|
+
const setNodeRef = useCombinedRefs(setDroppableNodeRef, setDraggableNodeRef);
|
|
411
|
+
const isSorting = Boolean(active);
|
|
412
|
+
const displaceItem = isSorting && !disableTransforms && isValidIndex(activeIndex) && isValidIndex(overIndex);
|
|
413
|
+
const shouldDisplaceDragSource = !useDragOverlay && isDragging;
|
|
414
|
+
const dragSourceDisplacement = shouldDisplaceDragSource && displaceItem ? transform : null;
|
|
415
|
+
const strategy = localStrategy != null ? localStrategy : globalStrategy;
|
|
416
|
+
const finalTransform = displaceItem ? dragSourceDisplacement != null ? dragSourceDisplacement : strategy({
|
|
417
|
+
rects: sortedRects,
|
|
418
|
+
activeNodeRect,
|
|
419
|
+
activeIndex,
|
|
420
|
+
overIndex,
|
|
421
|
+
index
|
|
422
|
+
}) : null;
|
|
423
|
+
const newIndex = isValidIndex(activeIndex) && isValidIndex(overIndex) ? getNewIndex({
|
|
424
|
+
id,
|
|
425
|
+
items,
|
|
426
|
+
activeIndex,
|
|
427
|
+
overIndex
|
|
428
|
+
}) : index;
|
|
429
|
+
const activeId = active == null ? void 0 : active.id;
|
|
430
|
+
const previous = React.useRef({
|
|
431
|
+
activeId,
|
|
432
|
+
items,
|
|
433
|
+
newIndex,
|
|
434
|
+
containerId
|
|
435
|
+
});
|
|
436
|
+
const itemsHaveChanged = items !== previous.current.items;
|
|
437
|
+
const shouldAnimateLayoutChanges = animateLayoutChanges({
|
|
438
|
+
active,
|
|
439
|
+
containerId,
|
|
440
|
+
isDragging,
|
|
441
|
+
isSorting,
|
|
442
|
+
id,
|
|
443
|
+
index,
|
|
444
|
+
items,
|
|
445
|
+
newIndex: previous.current.newIndex,
|
|
446
|
+
previousItems: previous.current.items,
|
|
447
|
+
previousContainerId: previous.current.containerId,
|
|
448
|
+
transition,
|
|
449
|
+
wasDragging: previous.current.activeId != null
|
|
450
|
+
});
|
|
451
|
+
const derivedTransform = useDerivedTransform({
|
|
452
|
+
disabled: !shouldAnimateLayoutChanges,
|
|
453
|
+
index,
|
|
454
|
+
node,
|
|
455
|
+
rect
|
|
456
|
+
});
|
|
457
|
+
React.useEffect(() => {
|
|
458
|
+
if (isSorting && previous.current.newIndex !== newIndex) {
|
|
459
|
+
previous.current.newIndex = newIndex;
|
|
460
|
+
}
|
|
461
|
+
if (containerId !== previous.current.containerId) {
|
|
462
|
+
previous.current.containerId = containerId;
|
|
463
|
+
}
|
|
464
|
+
if (items !== previous.current.items) {
|
|
465
|
+
previous.current.items = items;
|
|
466
|
+
}
|
|
467
|
+
}, [isSorting, newIndex, containerId, items]);
|
|
468
|
+
React.useEffect(() => {
|
|
469
|
+
if (activeId === previous.current.activeId) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (activeId && !previous.current.activeId) {
|
|
473
|
+
previous.current.activeId = activeId;
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const timeoutId = setTimeout(() => {
|
|
477
|
+
previous.current.activeId = activeId;
|
|
478
|
+
}, 50);
|
|
479
|
+
return () => clearTimeout(timeoutId);
|
|
480
|
+
}, [activeId]);
|
|
481
|
+
return {
|
|
482
|
+
active,
|
|
483
|
+
activeIndex,
|
|
484
|
+
attributes,
|
|
485
|
+
data,
|
|
486
|
+
rect,
|
|
487
|
+
index,
|
|
488
|
+
newIndex,
|
|
489
|
+
items,
|
|
490
|
+
isOver,
|
|
491
|
+
isSorting,
|
|
492
|
+
isDragging,
|
|
493
|
+
listeners,
|
|
494
|
+
node,
|
|
495
|
+
overIndex,
|
|
496
|
+
over,
|
|
497
|
+
setNodeRef,
|
|
498
|
+
setActivatorNodeRef,
|
|
499
|
+
setDroppableNodeRef,
|
|
500
|
+
setDraggableNodeRef,
|
|
501
|
+
transform: derivedTransform != null ? derivedTransform : finalTransform,
|
|
502
|
+
transition: getTransition()
|
|
503
|
+
};
|
|
504
|
+
function getTransition() {
|
|
505
|
+
if (
|
|
506
|
+
// Temporarily disable transitions for a single frame to set up derived transforms
|
|
507
|
+
derivedTransform || // Or to prevent items jumping to back to their "new" position when items change
|
|
508
|
+
itemsHaveChanged && previous.current.newIndex === index
|
|
509
|
+
) {
|
|
510
|
+
return disabledTransition;
|
|
511
|
+
}
|
|
512
|
+
if (shouldDisplaceDragSource && !isKeyboardEvent(activatorEvent) || !transition) {
|
|
513
|
+
return void 0;
|
|
514
|
+
}
|
|
515
|
+
if (isSorting || shouldAnimateLayoutChanges) {
|
|
516
|
+
return CSS.Transition.toString({
|
|
517
|
+
...transition,
|
|
518
|
+
property: transitionProperty
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
return void 0;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function normalizeLocalDisabled(localDisabled, globalDisabled) {
|
|
525
|
+
var _localDisabled$dragga, _localDisabled$droppa;
|
|
526
|
+
if (typeof localDisabled === "boolean") {
|
|
527
|
+
return {
|
|
528
|
+
draggable: localDisabled,
|
|
529
|
+
// Backwards compatibility
|
|
530
|
+
droppable: false
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
draggable: (_localDisabled$dragga = localDisabled == null ? void 0 : localDisabled.draggable) != null ? _localDisabled$dragga : globalDisabled.draggable,
|
|
535
|
+
droppable: (_localDisabled$droppa = localDisabled == null ? void 0 : localDisabled.droppable) != null ? _localDisabled$droppa : globalDisabled.droppable
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function hasSortableData(entry) {
|
|
539
|
+
if (!entry) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
const data = entry.data.current;
|
|
543
|
+
if (data && "sortable" in data && typeof data.sortable === "object" && "containerId" in data.sortable && "items" in data.sortable && "index" in data.sortable) {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
const directions = [core.KeyboardCode.Down, core.KeyboardCode.Right, core.KeyboardCode.Up, core.KeyboardCode.Left];
|
|
549
|
+
const sortableKeyboardCoordinates = (event, _ref) => {
|
|
550
|
+
let {
|
|
551
|
+
context: {
|
|
552
|
+
active,
|
|
553
|
+
collisionRect,
|
|
554
|
+
droppableRects,
|
|
555
|
+
droppableContainers,
|
|
556
|
+
over,
|
|
557
|
+
scrollableAncestors
|
|
558
|
+
}
|
|
559
|
+
} = _ref;
|
|
560
|
+
if (directions.includes(event.code)) {
|
|
561
|
+
event.preventDefault();
|
|
562
|
+
if (!active || !collisionRect) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const filteredContainers = [];
|
|
566
|
+
droppableContainers.getEnabled().forEach((entry) => {
|
|
567
|
+
if (!entry || entry != null && entry.disabled) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const rect = droppableRects.get(entry.id);
|
|
571
|
+
if (!rect) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
switch (event.code) {
|
|
575
|
+
case core.KeyboardCode.Down:
|
|
576
|
+
if (collisionRect.top < rect.top) {
|
|
577
|
+
filteredContainers.push(entry);
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
case core.KeyboardCode.Up:
|
|
581
|
+
if (collisionRect.top > rect.top) {
|
|
582
|
+
filteredContainers.push(entry);
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
case core.KeyboardCode.Left:
|
|
586
|
+
if (collisionRect.left > rect.left) {
|
|
587
|
+
filteredContainers.push(entry);
|
|
588
|
+
}
|
|
589
|
+
break;
|
|
590
|
+
case core.KeyboardCode.Right:
|
|
591
|
+
if (collisionRect.left < rect.left) {
|
|
592
|
+
filteredContainers.push(entry);
|
|
593
|
+
}
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
const collisions = core.closestCorners({
|
|
598
|
+
active,
|
|
599
|
+
collisionRect,
|
|
600
|
+
droppableRects,
|
|
601
|
+
droppableContainers: filteredContainers,
|
|
602
|
+
pointerCoordinates: null
|
|
603
|
+
});
|
|
604
|
+
let closestId = core.getFirstCollision(collisions, "id");
|
|
605
|
+
if (closestId === (over == null ? void 0 : over.id) && collisions.length > 1) {
|
|
606
|
+
closestId = collisions[1].id;
|
|
607
|
+
}
|
|
608
|
+
if (closestId != null) {
|
|
609
|
+
const activeDroppable = droppableContainers.get(active.id);
|
|
610
|
+
const newDroppable = droppableContainers.get(closestId);
|
|
611
|
+
const newRect = newDroppable ? droppableRects.get(newDroppable.id) : null;
|
|
612
|
+
const newNode = newDroppable == null ? void 0 : newDroppable.node.current;
|
|
613
|
+
if (newNode && newRect && activeDroppable && newDroppable) {
|
|
614
|
+
const newScrollAncestors = core.getScrollableAncestors(newNode);
|
|
615
|
+
const hasDifferentScrollAncestors = newScrollAncestors.some((element, index) => scrollableAncestors[index] !== element);
|
|
616
|
+
const hasSameContainer = isSameContainer(activeDroppable, newDroppable);
|
|
617
|
+
const isAfterActive = isAfter(activeDroppable, newDroppable);
|
|
618
|
+
const offset = hasDifferentScrollAncestors || !hasSameContainer ? {
|
|
619
|
+
x: 0,
|
|
620
|
+
y: 0
|
|
621
|
+
} : {
|
|
622
|
+
x: isAfterActive ? collisionRect.width - newRect.width : 0,
|
|
623
|
+
y: isAfterActive ? collisionRect.height - newRect.height : 0
|
|
624
|
+
};
|
|
625
|
+
const rectCoordinates = {
|
|
626
|
+
x: newRect.left,
|
|
627
|
+
y: newRect.top
|
|
628
|
+
};
|
|
629
|
+
const newCoordinates = offset.x && offset.y ? rectCoordinates : subtract(rectCoordinates, offset);
|
|
630
|
+
return newCoordinates;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return void 0;
|
|
635
|
+
};
|
|
636
|
+
function isSameContainer(a, b) {
|
|
637
|
+
if (!hasSortableData(a) || !hasSortableData(b)) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
return a.data.current.sortable.containerId === b.data.current.sortable.containerId;
|
|
641
|
+
}
|
|
642
|
+
function isAfter(a, b) {
|
|
643
|
+
if (!hasSortableData(a) || !hasSortableData(b)) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
if (!isSameContainer(a, b)) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
return a.data.current.sortable.index < b.data.current.sortable.index;
|
|
650
|
+
}
|
|
651
|
+
const useBanners = () => {
|
|
652
|
+
const getUploadedFiles = async () => {
|
|
653
|
+
try {
|
|
654
|
+
const response = await fetch("/admin/banners", {
|
|
655
|
+
method: "GET",
|
|
656
|
+
credentials: "include"
|
|
657
|
+
});
|
|
658
|
+
if (!response.ok) {
|
|
659
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
660
|
+
}
|
|
661
|
+
const data = await response.json();
|
|
662
|
+
return data ?? {
|
|
663
|
+
banners: [],
|
|
664
|
+
count: 0
|
|
665
|
+
};
|
|
666
|
+
} catch (error) {
|
|
667
|
+
console.error("Error fetching banners:", error);
|
|
668
|
+
throw error;
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
return reactQuery.useQuery({
|
|
672
|
+
queryKey: ["banners"],
|
|
673
|
+
queryFn: getUploadedFiles,
|
|
674
|
+
// No need for the arrow function wrapper
|
|
675
|
+
retry: false
|
|
676
|
+
});
|
|
677
|
+
};
|
|
678
|
+
const DraggableItem = ({ banner }) => {
|
|
679
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg shadow-md border border-gray-200 opacity-80 scale-105", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
680
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
681
|
+
"img",
|
|
682
|
+
{
|
|
683
|
+
src: banner.url,
|
|
684
|
+
alt: `Banner ${banner.id}`,
|
|
685
|
+
className: "object-cover w-full h-48 rounded-t-lg"
|
|
686
|
+
}
|
|
687
|
+
),
|
|
688
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 text-sm truncate", children: banner.id })
|
|
689
|
+
] }) });
|
|
690
|
+
};
|
|
691
|
+
const GridItem = ({
|
|
692
|
+
banner,
|
|
693
|
+
isSelected,
|
|
694
|
+
onClick,
|
|
695
|
+
onSelect
|
|
696
|
+
}) => {
|
|
697
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
698
|
+
"div",
|
|
699
|
+
{
|
|
700
|
+
className: `
|
|
701
|
+
relative overflow-hidden rounded-lg shadow-md cursor-pointer hover:shadow-lg
|
|
702
|
+
transition-all duration-200 ease-in-out
|
|
703
|
+
${isSelected ? "ring-2 ring-offset-2 ring-blue-500" : ""}
|
|
704
|
+
`,
|
|
705
|
+
onClick,
|
|
706
|
+
children: [
|
|
707
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
708
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
709
|
+
"img",
|
|
710
|
+
{
|
|
711
|
+
src: banner.url || "/placeholder.svg?height=200&width=300",
|
|
712
|
+
alt: `Banner ${banner.id}`,
|
|
713
|
+
className: "object-cover w-full h-48"
|
|
714
|
+
}
|
|
715
|
+
),
|
|
716
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate p-2 text-sm", children: banner.id })
|
|
717
|
+
] }),
|
|
718
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
719
|
+
"div",
|
|
720
|
+
{
|
|
721
|
+
className: `
|
|
722
|
+
absolute top-2 left-2 w-6 h-6 rounded-md flex items-center justify-center
|
|
723
|
+
${isSelected ? "bg-blue-500 text-white" : "bg-white/80 border border-gray-300"}
|
|
724
|
+
${isSelected ? "visible" : "invisible group-hover:visible"}
|
|
725
|
+
transition-opacity duration-200
|
|
726
|
+
`,
|
|
727
|
+
onClick: onSelect,
|
|
728
|
+
children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" })
|
|
729
|
+
}
|
|
730
|
+
)
|
|
731
|
+
]
|
|
732
|
+
}
|
|
733
|
+
);
|
|
734
|
+
};
|
|
735
|
+
const SortableItem = ({ banner }) => {
|
|
736
|
+
const {
|
|
737
|
+
attributes,
|
|
738
|
+
listeners,
|
|
739
|
+
setNodeRef,
|
|
740
|
+
transform,
|
|
741
|
+
transition,
|
|
742
|
+
isDragging
|
|
743
|
+
} = useSortable({ id: banner.id });
|
|
744
|
+
const style = {
|
|
745
|
+
transform: CSS.Transform.toString(transform),
|
|
746
|
+
transition,
|
|
747
|
+
zIndex: isDragging ? 10 : 1,
|
|
748
|
+
opacity: isDragging ? 0.5 : 1
|
|
749
|
+
};
|
|
750
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
751
|
+
"div",
|
|
752
|
+
{
|
|
753
|
+
ref: setNodeRef,
|
|
754
|
+
style,
|
|
755
|
+
className: "rounded-lg shadow-md border border-gray-200 hover:border-gray-300 transition-all relative",
|
|
756
|
+
...attributes,
|
|
757
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ...listeners, children: [
|
|
758
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
759
|
+
"img",
|
|
760
|
+
{
|
|
761
|
+
src: banner.url,
|
|
762
|
+
alt: `Banner ${banner.id}`,
|
|
763
|
+
className: "object-cover w-full h-48 rounded-t-lg"
|
|
764
|
+
}
|
|
765
|
+
),
|
|
766
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 text-sm truncate", children: banner.id })
|
|
767
|
+
] })
|
|
768
|
+
}
|
|
769
|
+
);
|
|
770
|
+
};
|
|
771
|
+
const UploadBannerDrawerContent = ({
|
|
772
|
+
maxFileSizeMb = 10,
|
|
773
|
+
onSuccess
|
|
774
|
+
}) => {
|
|
775
|
+
const [files, setFiles] = React.useState([]);
|
|
776
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
777
|
+
const onDrop = React.useCallback((acceptedFiles) => {
|
|
778
|
+
const newFiles = acceptedFiles.map((file) => {
|
|
779
|
+
const id = crypto.randomUUID();
|
|
780
|
+
let preview;
|
|
781
|
+
if (file.type.startsWith("image/")) {
|
|
782
|
+
preview = URL.createObjectURL(file);
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
file,
|
|
786
|
+
id,
|
|
787
|
+
preview
|
|
788
|
+
};
|
|
789
|
+
});
|
|
790
|
+
setFiles((prev) => [...prev, ...newFiles]);
|
|
791
|
+
}, []);
|
|
792
|
+
const removeFile = (id) => {
|
|
793
|
+
setFiles((files2) => {
|
|
794
|
+
const fileToRemove = files2.find((f) => f.id === id);
|
|
795
|
+
if (fileToRemove == null ? void 0 : fileToRemove.preview) {
|
|
796
|
+
URL.revokeObjectURL(fileToRemove.preview);
|
|
797
|
+
}
|
|
798
|
+
return files2.filter((f) => f.id !== id);
|
|
799
|
+
});
|
|
800
|
+
};
|
|
801
|
+
const handleResetFiles = () => {
|
|
802
|
+
setFiles([]);
|
|
803
|
+
};
|
|
804
|
+
const handleUploadFiles = async () => {
|
|
805
|
+
setIsLoading(true);
|
|
806
|
+
try {
|
|
807
|
+
const formData = new FormData();
|
|
808
|
+
files.forEach((file) => {
|
|
809
|
+
if (!file.file) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
formData.append("files", file.file);
|
|
813
|
+
});
|
|
814
|
+
const { uploadedFiles } = await fetch("/admin/banners", {
|
|
815
|
+
method: "POST",
|
|
816
|
+
credentials: "include",
|
|
817
|
+
body: formData
|
|
818
|
+
}).then((res) => res.json());
|
|
819
|
+
onSuccess(uploadedFiles);
|
|
820
|
+
return {
|
|
821
|
+
files,
|
|
822
|
+
uploadedFiles
|
|
823
|
+
};
|
|
824
|
+
} catch (error) {
|
|
825
|
+
console.error(error);
|
|
826
|
+
alert("Error uploading files");
|
|
827
|
+
} finally {
|
|
828
|
+
setIsLoading(false);
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
const {
|
|
832
|
+
getRootProps,
|
|
833
|
+
getInputProps,
|
|
834
|
+
isDragActive,
|
|
835
|
+
isDragAccept,
|
|
836
|
+
isDragReject,
|
|
837
|
+
isFocused
|
|
838
|
+
} = reactDropzone.useDropzone({
|
|
839
|
+
onDrop,
|
|
840
|
+
accept: {
|
|
841
|
+
"image/*": []
|
|
842
|
+
},
|
|
843
|
+
maxSize: maxFileSizeMb * 1024 * 1024
|
|
844
|
+
// maxFileSizeMb
|
|
845
|
+
});
|
|
846
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
|
|
847
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
848
|
+
"div",
|
|
849
|
+
{
|
|
850
|
+
...getRootProps(),
|
|
851
|
+
className: ui.clx(
|
|
852
|
+
"border-2 border-dashed rounded-xl p-10 transition-all duration-150 ease-in-out cursor-pointer",
|
|
853
|
+
"flex flex-col items-center justify-center gap-4",
|
|
854
|
+
isDragActive ? "bg-primary/5 border-primary/50" : "bg-background hover:bg-muted/50",
|
|
855
|
+
isDragAccept ? "border-green-500 bg-green-50 dark:bg-green-950/20" : "",
|
|
856
|
+
isDragReject ? "border-red-500 bg-red-50 dark:bg-red-950/20" : "",
|
|
857
|
+
isFocused ? "ring-2 ring-ring ring-offset-2" : "",
|
|
858
|
+
"focus-visible:outline-none"
|
|
859
|
+
),
|
|
860
|
+
children: [
|
|
861
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { ...getInputProps(), disabled: isLoading }),
|
|
862
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-full bg-primary/10 p-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { className: "h-8 w-8 text-primary" }) }),
|
|
863
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center space-y-2", children: [
|
|
864
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-medium text-lg", children: isDragActive ? isDragAccept ? "Drop files to upload" : "This file type is not supported" : "Drag & drop images here" }),
|
|
865
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
866
|
+
"or ",
|
|
867
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary font-medium", children: "browse images" }),
|
|
868
|
+
" ",
|
|
869
|
+
"from your computer"
|
|
870
|
+
] }),
|
|
871
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
872
|
+
"(max ",
|
|
873
|
+
maxFileSizeMb,
|
|
874
|
+
"MB)"
|
|
875
|
+
] })
|
|
876
|
+
] })
|
|
877
|
+
]
|
|
878
|
+
}
|
|
879
|
+
),
|
|
880
|
+
files.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
881
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
882
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline flex items-center gap-4", children: [
|
|
883
|
+
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-medium", children: [
|
|
884
|
+
"Selected Files (",
|
|
885
|
+
files.length,
|
|
886
|
+
")"
|
|
887
|
+
] }),
|
|
888
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
889
|
+
ui.Button,
|
|
890
|
+
{
|
|
891
|
+
isLoading,
|
|
892
|
+
variant: "primary",
|
|
893
|
+
onClick: handleUploadFiles,
|
|
894
|
+
children: "Confirm Upload"
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
] }),
|
|
898
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
899
|
+
ui.Button,
|
|
900
|
+
{
|
|
901
|
+
isLoading,
|
|
902
|
+
variant: "secondary",
|
|
903
|
+
onClick: handleResetFiles,
|
|
904
|
+
children: "Clear All"
|
|
905
|
+
}
|
|
906
|
+
)
|
|
907
|
+
] }),
|
|
908
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-4 grid-cols-2 sm:grid-cols-3", children: files.map((fileItem) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
909
|
+
"div",
|
|
910
|
+
{
|
|
911
|
+
className: "group relative rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden",
|
|
912
|
+
children: [
|
|
913
|
+
fileItem.preview ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-square w-full overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
914
|
+
"img",
|
|
915
|
+
{
|
|
916
|
+
src: fileItem.preview || "/placeholder.svg",
|
|
917
|
+
alt: fileItem.file.name,
|
|
918
|
+
className: "h-full w-full object-contain transition-all hover:scale-105"
|
|
919
|
+
}
|
|
920
|
+
) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aspect-square w-full flex items-center justify-center bg-muted/50", children: [
|
|
921
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImageIcon, { className: "h-6 w-6 text-primary" }),
|
|
922
|
+
";"
|
|
923
|
+
] }),
|
|
924
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
925
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 overflow-hidden", children: [
|
|
926
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium", children: fileItem.file.name }),
|
|
927
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
928
|
+
(fileItem.file.size / 1024).toFixed(1),
|
|
929
|
+
" KB"
|
|
930
|
+
] })
|
|
931
|
+
] }),
|
|
932
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
933
|
+
ui.Button,
|
|
934
|
+
{
|
|
935
|
+
isLoading,
|
|
936
|
+
variant: "secondary",
|
|
937
|
+
className: "size-7 rounded-full p-0.5 text-red-400 shrink-0",
|
|
938
|
+
onClick: (e) => {
|
|
939
|
+
e.stopPropagation();
|
|
940
|
+
removeFile(fileItem.id);
|
|
941
|
+
},
|
|
942
|
+
children: [
|
|
943
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}),
|
|
944
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Remove file" })
|
|
945
|
+
]
|
|
946
|
+
}
|
|
947
|
+
)
|
|
948
|
+
] }) })
|
|
949
|
+
]
|
|
950
|
+
},
|
|
951
|
+
fileItem.id
|
|
952
|
+
)) })
|
|
953
|
+
] })
|
|
954
|
+
] });
|
|
955
|
+
};
|
|
956
|
+
const BannerPage = () => {
|
|
957
|
+
const [open, setOpen] = React.useState(false);
|
|
958
|
+
const [selectedImage, setSelectedImage] = React.useState(null);
|
|
959
|
+
const [selectedIds, setSelectedIds] = React.useState(/* @__PURE__ */ new Set());
|
|
960
|
+
const [reorderMode, setReorderMode] = React.useState(false);
|
|
961
|
+
const [orderedBanners, setOrderedBanners] = React.useState([]);
|
|
962
|
+
const [activeBanner, setActiveBanner] = React.useState(null);
|
|
963
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
964
|
+
const dialog = ui.usePrompt();
|
|
965
|
+
const { data: bannerData, refetch } = useBanners();
|
|
966
|
+
const { count = 0, banners = [] } = bannerData || {};
|
|
967
|
+
const sensors = core.useSensors(
|
|
968
|
+
core.useSensor(core.PointerSensor, {
|
|
969
|
+
activationConstraint: {
|
|
970
|
+
distance: 8
|
|
971
|
+
// 8px movement required before drag starts
|
|
972
|
+
}
|
|
973
|
+
}),
|
|
974
|
+
core.useSensor(core.KeyboardSensor, {
|
|
975
|
+
coordinateGetter: sortableKeyboardCoordinates
|
|
976
|
+
})
|
|
977
|
+
);
|
|
978
|
+
const toggleImageSelection = (e, id) => {
|
|
979
|
+
e.stopPropagation();
|
|
980
|
+
const newSelectedIds = new Set(selectedIds);
|
|
981
|
+
if (newSelectedIds.has(id)) {
|
|
982
|
+
newSelectedIds.delete(id);
|
|
983
|
+
} else {
|
|
984
|
+
newSelectedIds.add(id);
|
|
985
|
+
}
|
|
986
|
+
setSelectedIds(newSelectedIds);
|
|
987
|
+
};
|
|
988
|
+
const selectAll = () => {
|
|
989
|
+
const allIds = banners.map((banner) => banner.id);
|
|
990
|
+
setSelectedIds(new Set(allIds));
|
|
991
|
+
};
|
|
992
|
+
const deselectAll = () => {
|
|
993
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
994
|
+
};
|
|
995
|
+
const openLightbox = (banner) => {
|
|
996
|
+
setSelectedImage(banner);
|
|
997
|
+
};
|
|
998
|
+
const closeLightbox = () => {
|
|
999
|
+
setSelectedImage(null);
|
|
1000
|
+
};
|
|
1001
|
+
const toggleReorderMode = async () => {
|
|
1002
|
+
if (reorderMode) {
|
|
1003
|
+
const confirmed = await dialog({
|
|
1004
|
+
title: "Save Changes?",
|
|
1005
|
+
description: "Do you want to save your reordering changes?",
|
|
1006
|
+
variant: "confirmation",
|
|
1007
|
+
confirmText: "Save",
|
|
1008
|
+
cancelText: "Discard"
|
|
1009
|
+
});
|
|
1010
|
+
if (confirmed) {
|
|
1011
|
+
saveNewOrder();
|
|
1012
|
+
} else {
|
|
1013
|
+
setOrderedBanners([]);
|
|
1014
|
+
setReorderMode(false);
|
|
1015
|
+
}
|
|
1016
|
+
} else {
|
|
1017
|
+
setOrderedBanners([...banners]);
|
|
1018
|
+
setReorderMode(true);
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
const handleDragStart = (event) => {
|
|
1022
|
+
const { active } = event;
|
|
1023
|
+
const activeBanner2 = orderedBanners.find(
|
|
1024
|
+
(banner) => banner.id === active.id
|
|
1025
|
+
);
|
|
1026
|
+
if (activeBanner2) {
|
|
1027
|
+
setActiveBanner(activeBanner2);
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
const handleDragEnd = (event) => {
|
|
1031
|
+
const { active, over } = event;
|
|
1032
|
+
if (over && active.id !== over.id) {
|
|
1033
|
+
setOrderedBanners((items) => {
|
|
1034
|
+
const oldIndex = items.findIndex((item) => item.id === active.id);
|
|
1035
|
+
const newIndex = items.findIndex((item) => item.id === over.id);
|
|
1036
|
+
return arrayMove(items, oldIndex, newIndex);
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
setActiveBanner(null);
|
|
1040
|
+
};
|
|
1041
|
+
const saveNewOrder = async () => {
|
|
1042
|
+
try {
|
|
1043
|
+
await fetch("/admin/banners/reorder", {
|
|
1044
|
+
method: "POST",
|
|
1045
|
+
credentials: "include",
|
|
1046
|
+
headers: {
|
|
1047
|
+
"Content-Type": "application/json",
|
|
1048
|
+
Accept: "application/json"
|
|
1049
|
+
},
|
|
1050
|
+
body: JSON.stringify({
|
|
1051
|
+
ids: orderedBanners.map((banner) => banner.id)
|
|
1052
|
+
})
|
|
1053
|
+
});
|
|
1054
|
+
setReorderMode(false);
|
|
1055
|
+
refetch();
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
console.error("Failed to save new order:", error);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
const handleDeleteMultiple = async () => {
|
|
1061
|
+
const isConfirmed = await dialog({
|
|
1062
|
+
title: "Confirm Deletion",
|
|
1063
|
+
description: `Are you sure you want to delete ${selectedIds.size} selected images? This action cannot be undone.`,
|
|
1064
|
+
confirmText: "Delete",
|
|
1065
|
+
cancelText: "Cancel"
|
|
1066
|
+
});
|
|
1067
|
+
if (!isConfirmed) return;
|
|
1068
|
+
const ids2 = Array.from(selectedIds);
|
|
1069
|
+
setIsLoading(true);
|
|
1070
|
+
try {
|
|
1071
|
+
await fetch("/admin/banners", {
|
|
1072
|
+
method: "DELETE",
|
|
1073
|
+
credentials: "include",
|
|
1074
|
+
headers: {
|
|
1075
|
+
"Content-Type": "application/json",
|
|
1076
|
+
Accept: "application/json"
|
|
1077
|
+
},
|
|
1078
|
+
body: JSON.stringify({ ids: ids2 })
|
|
1079
|
+
});
|
|
1080
|
+
setSelectedIds(/* @__PURE__ */ new Set());
|
|
1081
|
+
refetch();
|
|
1082
|
+
} catch {
|
|
1083
|
+
console.error("Failed to delete banners");
|
|
1084
|
+
} finally {
|
|
1085
|
+
setIsLoading(false);
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "container mx-auto px-4 py-8", children: [
|
|
1089
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-6", children: [
|
|
1090
|
+
/* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "text-2xl font-bold", children: [
|
|
1091
|
+
"Banners (",
|
|
1092
|
+
count,
|
|
1093
|
+
")"
|
|
1094
|
+
] }),
|
|
1095
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1096
|
+
ui.Drawer,
|
|
1097
|
+
{
|
|
1098
|
+
open,
|
|
1099
|
+
onOpenChange: (openChanged) => setOpen(openChanged),
|
|
1100
|
+
children: [
|
|
1101
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1102
|
+
ui.Drawer.Trigger,
|
|
1103
|
+
{
|
|
1104
|
+
onClick: () => {
|
|
1105
|
+
setOpen(true);
|
|
1106
|
+
},
|
|
1107
|
+
asChild: true,
|
|
1108
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { isLoading, children: "Upload new banner" })
|
|
1109
|
+
}
|
|
1110
|
+
),
|
|
1111
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
|
|
1112
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Upload new banner" }) }),
|
|
1113
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1114
|
+
UploadBannerDrawerContent,
|
|
1115
|
+
{
|
|
1116
|
+
maxFileSizeMb: 10,
|
|
1117
|
+
onSuccess: () => {
|
|
1118
|
+
setOpen(false);
|
|
1119
|
+
refetch();
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
) })
|
|
1123
|
+
] })
|
|
1124
|
+
]
|
|
1125
|
+
}
|
|
1126
|
+
)
|
|
1127
|
+
] }),
|
|
1128
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "container mx-auto px-4 py-8", children: [
|
|
1129
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center mb-6", children: [
|
|
1130
|
+
selectedIds.size > 0 && !reorderMode && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1131
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1132
|
+
ui.Button,
|
|
1133
|
+
{
|
|
1134
|
+
isLoading,
|
|
1135
|
+
onClick: selectAll,
|
|
1136
|
+
disabled: count === 0,
|
|
1137
|
+
children: "Select All"
|
|
1138
|
+
}
|
|
1139
|
+
),
|
|
1140
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1141
|
+
ui.Button,
|
|
1142
|
+
{
|
|
1143
|
+
isLoading,
|
|
1144
|
+
onClick: deselectAll,
|
|
1145
|
+
disabled: selectedIds.size === 0,
|
|
1146
|
+
children: "Deselect All"
|
|
1147
|
+
}
|
|
1148
|
+
),
|
|
1149
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1150
|
+
ui.Button,
|
|
1151
|
+
{
|
|
1152
|
+
isLoading,
|
|
1153
|
+
variant: "danger",
|
|
1154
|
+
onClick: () => handleDeleteMultiple(),
|
|
1155
|
+
disabled: selectedIds.size === 0,
|
|
1156
|
+
children: [
|
|
1157
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" }),
|
|
1158
|
+
"Delete (",
|
|
1159
|
+
selectedIds.size,
|
|
1160
|
+
")"
|
|
1161
|
+
]
|
|
1162
|
+
}
|
|
1163
|
+
)
|
|
1164
|
+
] }) }),
|
|
1165
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto", children: [
|
|
1166
|
+
banners.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1167
|
+
ui.Button,
|
|
1168
|
+
{
|
|
1169
|
+
isLoading,
|
|
1170
|
+
onClick: toggleReorderMode,
|
|
1171
|
+
variant: reorderMode ? "secondary" : "primary",
|
|
1172
|
+
children: [
|
|
1173
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoveHorizontal, { className: "h-4 w-4 mr-2" }),
|
|
1174
|
+
reorderMode ? "Exit Reorder" : "Reorder"
|
|
1175
|
+
]
|
|
1176
|
+
}
|
|
1177
|
+
),
|
|
1178
|
+
reorderMode && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1179
|
+
ui.Button,
|
|
1180
|
+
{
|
|
1181
|
+
isLoading,
|
|
1182
|
+
onClick: saveNewOrder,
|
|
1183
|
+
variant: "primary",
|
|
1184
|
+
className: "ml-2",
|
|
1185
|
+
children: [
|
|
1186
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 mr-2" }),
|
|
1187
|
+
"Save Order"
|
|
1188
|
+
]
|
|
1189
|
+
}
|
|
1190
|
+
)
|
|
1191
|
+
] })
|
|
1192
|
+
] }),
|
|
1193
|
+
reorderMode ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1194
|
+
core.DndContext,
|
|
1195
|
+
{
|
|
1196
|
+
sensors,
|
|
1197
|
+
collisionDetection: core.closestCenter,
|
|
1198
|
+
onDragStart: handleDragStart,
|
|
1199
|
+
onDragEnd: handleDragEnd,
|
|
1200
|
+
children: [
|
|
1201
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1202
|
+
SortableContext,
|
|
1203
|
+
{
|
|
1204
|
+
items: orderedBanners.map((banner) => banner.id),
|
|
1205
|
+
strategy: rectSortingStrategy,
|
|
1206
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 isolate", children: orderedBanners.map((banner) => /* @__PURE__ */ jsxRuntime.jsx(SortableItem, { banner }, banner.id)) })
|
|
1207
|
+
}
|
|
1208
|
+
),
|
|
1209
|
+
/* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { children: activeBanner ? /* @__PURE__ */ jsxRuntime.jsx(DraggableItem, { banner: activeBanner }) : null })
|
|
1210
|
+
]
|
|
1211
|
+
}
|
|
1212
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 group", children: banners.map((banner) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1213
|
+
GridItem,
|
|
1214
|
+
{
|
|
1215
|
+
banner,
|
|
1216
|
+
isSelected: selectedIds.has(banner.id),
|
|
1217
|
+
onClick: () => openLightbox(banner),
|
|
1218
|
+
onSelect: (e) => toggleImageSelection(e, banner.id)
|
|
1219
|
+
},
|
|
1220
|
+
banner.id
|
|
1221
|
+
)) }),
|
|
1222
|
+
selectedImage && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1223
|
+
"div",
|
|
1224
|
+
{
|
|
1225
|
+
className: "fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4",
|
|
1226
|
+
onClick: closeLightbox,
|
|
1227
|
+
children: [
|
|
1228
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1229
|
+
ui.Button,
|
|
1230
|
+
{
|
|
1231
|
+
isLoading,
|
|
1232
|
+
className: "absolute top-4 right-4 text-white bg-black/50 p-2 rounded-full hover:bg-black/70",
|
|
1233
|
+
onClick: (e) => {
|
|
1234
|
+
e.stopPropagation();
|
|
1235
|
+
closeLightbox();
|
|
1236
|
+
},
|
|
1237
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-6 w-6" })
|
|
1238
|
+
}
|
|
1239
|
+
),
|
|
1240
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1241
|
+
"div",
|
|
1242
|
+
{
|
|
1243
|
+
className: "relative max-w-screen-xl max-h-screen overflow-auto",
|
|
1244
|
+
onClick: (e) => e.stopPropagation(),
|
|
1245
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1246
|
+
"img",
|
|
1247
|
+
{
|
|
1248
|
+
src: selectedImage.url || "/placeholder.svg",
|
|
1249
|
+
alt: `Banner ${selectedImage.id}`,
|
|
1250
|
+
className: "max-h-[90vh] max-w-full object-contain mx-auto"
|
|
1251
|
+
}
|
|
1252
|
+
)
|
|
1253
|
+
}
|
|
1254
|
+
)
|
|
1255
|
+
]
|
|
1256
|
+
}
|
|
1257
|
+
)
|
|
1258
|
+
] })
|
|
1259
|
+
] }) });
|
|
1260
|
+
};
|
|
1261
|
+
const BannerUpload = () => {
|
|
1262
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BannerPage, {});
|
|
1263
|
+
};
|
|
1264
|
+
const config = adminSdk.defineRouteConfig({
|
|
1265
|
+
label: "Banner",
|
|
1266
|
+
icon: icons.StackPerspective
|
|
1267
|
+
});
|
|
1268
|
+
const widgetModule = { widgets: [] };
|
|
1269
|
+
const routeModule = {
|
|
1270
|
+
routes: [
|
|
1271
|
+
{
|
|
1272
|
+
Component: BannerUpload,
|
|
1273
|
+
path: "/banners"
|
|
1274
|
+
}
|
|
1275
|
+
]
|
|
1276
|
+
};
|
|
1277
|
+
const menuItemModule = {
|
|
1278
|
+
menuItems: [
|
|
1279
|
+
{
|
|
1280
|
+
label: config.label,
|
|
1281
|
+
icon: config.icon,
|
|
1282
|
+
path: "/banners",
|
|
1283
|
+
nested: void 0
|
|
1284
|
+
}
|
|
1285
|
+
]
|
|
1286
|
+
};
|
|
1287
|
+
const formModule = { customFields: {} };
|
|
1288
|
+
const displayModule = {
|
|
1289
|
+
displays: {}
|
|
1290
|
+
};
|
|
1291
|
+
const plugin = {
|
|
1292
|
+
widgetModule,
|
|
1293
|
+
routeModule,
|
|
1294
|
+
menuItemModule,
|
|
1295
|
+
formModule,
|
|
1296
|
+
displayModule
|
|
1297
|
+
};
|
|
1298
|
+
module.exports = plugin;
|