@emreunes/medusa-category-images 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 +545 -0
- package/.medusa/server/src/admin/index.mjs +544 -0
- package/.medusa/server/src/api/admin/categories/[category_id]/images/batch/route.js +34 -0
- package/.medusa/server/src/api/admin/categories/[category_id]/images/route.js +42 -0
- package/.medusa/server/src/api/middlewares.js +31 -0
- package/.medusa/server/src/links/product-category-image.js +19 -0
- package/.medusa/server/src/modules/product-media/index.js +13 -0
- package/.medusa/server/src/modules/product-media/migrations/Migration20251013152717.js +16 -0
- package/.medusa/server/src/modules/product-media/models/product-category-image.js +20 -0
- package/.medusa/server/src/modules/product-media/service.js +13 -0
- package/.medusa/server/src/scripts/seed.js +814 -0
- package/.medusa/server/src/workflows/create-category-images.js +24 -0
- package/.medusa/server/src/workflows/delete-category-image.js +31 -0
- package/.medusa/server/src/workflows/steps/convert-category-thumbnails.js +35 -0
- package/.medusa/server/src/workflows/steps/create-category-images.js +35 -0
- package/.medusa/server/src/workflows/steps/delete-category-image.js +29 -0
- package/.medusa/server/src/workflows/steps/update-category-images.js +26 -0
- package/.medusa/server/src/workflows/update-category-images.js +36 -0
- package/README.md +56 -0
- package/package.json +71 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
3
|
+
const adminSdk = require("@medusajs/admin-sdk");
|
|
4
|
+
const ui = require("@medusajs/ui");
|
|
5
|
+
const reactQuery = require("@tanstack/react-query");
|
|
6
|
+
const Medusa = require("@medusajs/js-sdk");
|
|
7
|
+
const react = require("react");
|
|
8
|
+
const icons = require("@medusajs/icons");
|
|
9
|
+
require("@medusajs/admin-shared");
|
|
10
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
11
|
+
const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
|
|
12
|
+
const sdk = new Medusa__default.default({
|
|
13
|
+
baseUrl: typeof window !== "undefined" ? window.location.origin : "http://localhost:9000",
|
|
14
|
+
auth: {
|
|
15
|
+
type: "session"
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const CategoryImageItem = ({
|
|
19
|
+
id,
|
|
20
|
+
url,
|
|
21
|
+
alt,
|
|
22
|
+
isThumbnail,
|
|
23
|
+
isSelected,
|
|
24
|
+
onToggleSelect
|
|
25
|
+
}) => {
|
|
26
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
27
|
+
"div",
|
|
28
|
+
{
|
|
29
|
+
className: "shadow-elevation-card-rest hover:shadow-elevation-card-hover focus-visible:shadow-borders-focus bg-ui-bg-subtle-hover group relative aspect-square h-auto max-w-full overflow-hidden rounded-lg outline-none",
|
|
30
|
+
children: [
|
|
31
|
+
isThumbnail && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-2 top-2", children: /* @__PURE__ */ jsxRuntime.jsx(icons.ThumbnailBadge, {}) }),
|
|
32
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.clx(
|
|
33
|
+
"transition-fg absolute right-2 top-2 opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 group-focus:opacity-100",
|
|
34
|
+
isSelected && "opacity-100"
|
|
35
|
+
), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
36
|
+
ui.Checkbox,
|
|
37
|
+
{
|
|
38
|
+
checked: isSelected,
|
|
39
|
+
onCheckedChange: onToggleSelect
|
|
40
|
+
}
|
|
41
|
+
) }),
|
|
42
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
43
|
+
"img",
|
|
44
|
+
{
|
|
45
|
+
src: url,
|
|
46
|
+
alt,
|
|
47
|
+
className: "size-full object-cover object-center"
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
id
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
const CategoryImageGallery = ({
|
|
56
|
+
existingImages,
|
|
57
|
+
uploadedFiles,
|
|
58
|
+
imagesToDelete,
|
|
59
|
+
currentThumbnailId,
|
|
60
|
+
selectedImageIds,
|
|
61
|
+
onToggleSelect
|
|
62
|
+
}) => {
|
|
63
|
+
const visibleExistingImages = existingImages.filter(
|
|
64
|
+
(image) => image.id && !imagesToDelete.has(image.id)
|
|
65
|
+
);
|
|
66
|
+
const hasNoImages = visibleExistingImages.length === 0 && uploadedFiles.length === 0;
|
|
67
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-ui-bg-subtle size-full overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid h-fit auto-rows-auto grid-cols-4 gap-6 p-6", children: [
|
|
68
|
+
visibleExistingImages.map((image) => {
|
|
69
|
+
if (!image.id) return null;
|
|
70
|
+
const imageId = image.id;
|
|
71
|
+
const isThumbnail = currentThumbnailId === imageId;
|
|
72
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
73
|
+
CategoryImageItem,
|
|
74
|
+
{
|
|
75
|
+
id: imageId,
|
|
76
|
+
url: image.url,
|
|
77
|
+
alt: `Category ${image.type}`,
|
|
78
|
+
isThumbnail,
|
|
79
|
+
isSelected: selectedImageIds.has(imageId),
|
|
80
|
+
onToggleSelect: () => onToggleSelect(imageId)
|
|
81
|
+
},
|
|
82
|
+
imageId
|
|
83
|
+
);
|
|
84
|
+
}),
|
|
85
|
+
uploadedFiles.map((file) => {
|
|
86
|
+
const uploadedId = `uploaded:${file.id}`;
|
|
87
|
+
const isThumbnail = currentThumbnailId === uploadedId;
|
|
88
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
89
|
+
CategoryImageItem,
|
|
90
|
+
{
|
|
91
|
+
id: file.id,
|
|
92
|
+
url: file.url,
|
|
93
|
+
alt: "Uploaded",
|
|
94
|
+
isThumbnail,
|
|
95
|
+
isSelected: selectedImageIds.has(uploadedId),
|
|
96
|
+
onToggleSelect: () => onToggleSelect(file.id, true)
|
|
97
|
+
},
|
|
98
|
+
file.id
|
|
99
|
+
);
|
|
100
|
+
}),
|
|
101
|
+
hasNoImages && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-4 flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-center", children: "No images yet. Upload images to get started." }) })
|
|
102
|
+
] }) });
|
|
103
|
+
};
|
|
104
|
+
const CategoryImageUpload = ({
|
|
105
|
+
fileInputRef,
|
|
106
|
+
isUploading,
|
|
107
|
+
onFileSelect
|
|
108
|
+
}) => {
|
|
109
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-ui-bg-base overflow-auto border-b px-6 py-4 lg:border-b-0 lg:border-l", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col space-y-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
110
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
111
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-1", children: [
|
|
112
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "font-sans txt-compact-small font-medium", children: "Media" }),
|
|
113
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-normal font-sans txt-compact-small text-ui-fg-muted", children: "(Optional)" })
|
|
114
|
+
] }),
|
|
115
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "txt-small text-ui-fg-subtle", children: "Add media to the product to showcase it in your storefront." })
|
|
116
|
+
] }),
|
|
117
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
118
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
119
|
+
"input",
|
|
120
|
+
{
|
|
121
|
+
ref: fileInputRef,
|
|
122
|
+
type: "file",
|
|
123
|
+
multiple: true,
|
|
124
|
+
accept: "image/jpeg,image/png,image/gif,image/webp,image/heic,image/svg+xml",
|
|
125
|
+
onChange: (e) => onFileSelect(e.target.files),
|
|
126
|
+
hidden: true
|
|
127
|
+
}
|
|
128
|
+
),
|
|
129
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
130
|
+
"button",
|
|
131
|
+
{
|
|
132
|
+
type: "button",
|
|
133
|
+
onClick: () => {
|
|
134
|
+
var _a;
|
|
135
|
+
return (_a = fileInputRef.current) == null ? void 0 : _a.click();
|
|
136
|
+
},
|
|
137
|
+
disabled: isUploading,
|
|
138
|
+
className: "bg-ui-bg-component border-ui-border-strong transition-fg group flex w-full flex-col items-center gap-y-2 rounded-lg border border-dashed p-8 hover:border-ui-border-interactive focus:border-ui-border-interactive focus:shadow-borders-focus outline-none focus:border-solid disabled:opacity-50 disabled:cursor-not-allowed",
|
|
139
|
+
onDragOver: (e) => {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
},
|
|
143
|
+
onDrop: (e) => {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
e.stopPropagation();
|
|
146
|
+
if (!isUploading) {
|
|
147
|
+
onFileSelect(e.dataTransfer.files);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
children: [
|
|
151
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-ui-fg-subtle group-disabled:text-ui-fg-disabled flex items-center gap-x-2", children: [
|
|
152
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ArrowDownTray, {}),
|
|
153
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-normal font-sans txt-medium", children: isUploading ? "Uploading..." : "Upload images" })
|
|
154
|
+
] }),
|
|
155
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-normal font-sans txt-compact-small text-ui-fg-muted group-disabled:text-ui-fg-disabled", children: "Drag and drop images here or click to upload." })
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
] })
|
|
160
|
+
] }) }) });
|
|
161
|
+
};
|
|
162
|
+
const useCategoryImageMutations = ({
|
|
163
|
+
categoryId,
|
|
164
|
+
onCreateSuccess,
|
|
165
|
+
onUpdateSuccess,
|
|
166
|
+
onDeleteSuccess
|
|
167
|
+
}) => {
|
|
168
|
+
const queryClient = reactQuery.useQueryClient();
|
|
169
|
+
const uploadFilesMutation = reactQuery.useMutation({
|
|
170
|
+
mutationFn: async (files) => {
|
|
171
|
+
const response = await sdk.admin.upload.create({ files });
|
|
172
|
+
return response;
|
|
173
|
+
},
|
|
174
|
+
onError: (error) => {
|
|
175
|
+
console.error("Failed to upload files:", error);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
const createImagesMutation = reactQuery.useMutation({
|
|
179
|
+
mutationFn: async (images) => {
|
|
180
|
+
const response = await sdk.client.fetch(
|
|
181
|
+
`/admin/categories/${categoryId}/images`,
|
|
182
|
+
{
|
|
183
|
+
method: "POST",
|
|
184
|
+
headers: {
|
|
185
|
+
"Content-Type": "application/json"
|
|
186
|
+
},
|
|
187
|
+
body: {
|
|
188
|
+
images
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
return response;
|
|
193
|
+
},
|
|
194
|
+
onSuccess: () => {
|
|
195
|
+
queryClient.invalidateQueries({ queryKey: ["category-images", categoryId] });
|
|
196
|
+
onCreateSuccess == null ? void 0 : onCreateSuccess();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
const updateImagesMutation = reactQuery.useMutation({
|
|
200
|
+
mutationFn: async (updates) => {
|
|
201
|
+
const response = await sdk.client.fetch(
|
|
202
|
+
`/admin/categories/${categoryId}/images/batch`,
|
|
203
|
+
{
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: {
|
|
206
|
+
"Content-Type": "application/json"
|
|
207
|
+
},
|
|
208
|
+
body: {
|
|
209
|
+
updates
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
return response;
|
|
214
|
+
},
|
|
215
|
+
onSuccess: () => {
|
|
216
|
+
queryClient.invalidateQueries({ queryKey: ["category-images", categoryId] });
|
|
217
|
+
onUpdateSuccess == null ? void 0 : onUpdateSuccess();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
const deleteImagesMutation = reactQuery.useMutation({
|
|
221
|
+
mutationFn: async (ids) => {
|
|
222
|
+
const response = await sdk.client.fetch(
|
|
223
|
+
`/admin/categories/${categoryId}/images/batch`,
|
|
224
|
+
{
|
|
225
|
+
method: "DELETE",
|
|
226
|
+
headers: {
|
|
227
|
+
"Content-Type": "application/json"
|
|
228
|
+
},
|
|
229
|
+
body: {
|
|
230
|
+
ids
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
return response;
|
|
235
|
+
},
|
|
236
|
+
onSuccess: (_data, deletedIds) => {
|
|
237
|
+
queryClient.invalidateQueries({ queryKey: ["category-images", categoryId] });
|
|
238
|
+
onDeleteSuccess == null ? void 0 : onDeleteSuccess(deletedIds);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
uploadFilesMutation,
|
|
243
|
+
createImagesMutation,
|
|
244
|
+
updateImagesMutation,
|
|
245
|
+
deleteImagesMutation
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
const CategoryMediaModal = ({
|
|
249
|
+
categoryId,
|
|
250
|
+
existingImages
|
|
251
|
+
}) => {
|
|
252
|
+
const [open, setOpen] = react.useState(false);
|
|
253
|
+
const [uploadedFiles, setUploadedFiles] = react.useState([]);
|
|
254
|
+
const [selectedImageIds, setSelectedImageIds] = react.useState(/* @__PURE__ */ new Set());
|
|
255
|
+
const [currentThumbnailId, setCurrentThumbnailId] = react.useState(null);
|
|
256
|
+
const [imagesToDelete, setImagesToDelete] = react.useState(/* @__PURE__ */ new Set());
|
|
257
|
+
const fileInputRef = react.useRef(null);
|
|
258
|
+
const queryClient = reactQuery.useQueryClient();
|
|
259
|
+
const {
|
|
260
|
+
uploadFilesMutation,
|
|
261
|
+
createImagesMutation,
|
|
262
|
+
updateImagesMutation,
|
|
263
|
+
deleteImagesMutation
|
|
264
|
+
} = useCategoryImageMutations({
|
|
265
|
+
categoryId,
|
|
266
|
+
onCreateSuccess: () => {
|
|
267
|
+
setOpen(false);
|
|
268
|
+
resetModalState();
|
|
269
|
+
},
|
|
270
|
+
onUpdateSuccess: () => {
|
|
271
|
+
setSelectedImageIds(/* @__PURE__ */ new Set());
|
|
272
|
+
},
|
|
273
|
+
onDeleteSuccess: (deletedIds) => {
|
|
274
|
+
setSelectedImageIds(/* @__PURE__ */ new Set());
|
|
275
|
+
if (currentThumbnailId && deletedIds.includes(currentThumbnailId)) {
|
|
276
|
+
setCurrentThumbnailId(null);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
const isSaving = createImagesMutation.isPending || updateImagesMutation.isPending || deleteImagesMutation.isPending;
|
|
281
|
+
const resetModalState = () => {
|
|
282
|
+
setUploadedFiles([]);
|
|
283
|
+
setSelectedImageIds(/* @__PURE__ */ new Set());
|
|
284
|
+
setCurrentThumbnailId(null);
|
|
285
|
+
setImagesToDelete(/* @__PURE__ */ new Set());
|
|
286
|
+
};
|
|
287
|
+
const initializeThumbnail = () => {
|
|
288
|
+
const thumbnailImage = existingImages.find((img) => img.type === "thumbnail");
|
|
289
|
+
if (thumbnailImage == null ? void 0 : thumbnailImage.id) {
|
|
290
|
+
setCurrentThumbnailId(thumbnailImage.id);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const handleOpenChange = (isOpen) => {
|
|
294
|
+
setOpen(isOpen);
|
|
295
|
+
if (isOpen) {
|
|
296
|
+
initializeThumbnail();
|
|
297
|
+
} else {
|
|
298
|
+
resetModalState();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
const handleUploadFile = (files) => {
|
|
302
|
+
if (!files || files.length === 0) return;
|
|
303
|
+
const filesArray = Array.from(files);
|
|
304
|
+
uploadFilesMutation.mutate(filesArray, {
|
|
305
|
+
onSuccess: (data) => {
|
|
306
|
+
setUploadedFiles((prev) => [...prev, ...data.files]);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
if (fileInputRef.current) {
|
|
310
|
+
fileInputRef.current.value = "";
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
const handleImageSelection = (id, isUploaded = false) => {
|
|
314
|
+
const itemId = isUploaded ? `uploaded:${id}` : id;
|
|
315
|
+
const newSelected = new Set(selectedImageIds);
|
|
316
|
+
if (newSelected.has(itemId)) {
|
|
317
|
+
newSelected.delete(itemId);
|
|
318
|
+
} else {
|
|
319
|
+
newSelected.add(itemId);
|
|
320
|
+
}
|
|
321
|
+
setSelectedImageIds(newSelected);
|
|
322
|
+
};
|
|
323
|
+
const handleSetAsThumbnail = () => {
|
|
324
|
+
if (selectedImageIds.size !== 1) return;
|
|
325
|
+
const selectedId = Array.from(selectedImageIds)[0];
|
|
326
|
+
setCurrentThumbnailId(selectedId);
|
|
327
|
+
if (selectedId.startsWith("uploaded:")) {
|
|
328
|
+
const uploadedFileId = selectedId.replace("uploaded:", "");
|
|
329
|
+
setUploadedFiles(
|
|
330
|
+
(prev) => prev.map((file) => file.id === uploadedFileId ? { ...file, type: "thumbnail" } : file)
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
setSelectedImageIds(/* @__PURE__ */ new Set());
|
|
334
|
+
};
|
|
335
|
+
const handleDelete = () => {
|
|
336
|
+
if (selectedImageIds.size === 0) return;
|
|
337
|
+
const uploadedFileIds = [];
|
|
338
|
+
const savedImageIds = [];
|
|
339
|
+
selectedImageIds.forEach((id) => {
|
|
340
|
+
if (id.startsWith("uploaded:")) {
|
|
341
|
+
uploadedFileIds.push(id.replace("uploaded:", ""));
|
|
342
|
+
} else {
|
|
343
|
+
savedImageIds.push(id);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
if (uploadedFileIds.length > 0) {
|
|
347
|
+
setUploadedFiles(
|
|
348
|
+
(prev) => prev.filter((file) => !uploadedFileIds.includes(file.id))
|
|
349
|
+
);
|
|
350
|
+
if (currentThumbnailId == null ? void 0 : currentThumbnailId.startsWith("uploaded:")) {
|
|
351
|
+
const thumbnailFileId = currentThumbnailId.replace("uploaded:", "");
|
|
352
|
+
if (uploadedFileIds.includes(thumbnailFileId)) {
|
|
353
|
+
setCurrentThumbnailId(null);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (savedImageIds.length > 0) {
|
|
358
|
+
setImagesToDelete((prev) => {
|
|
359
|
+
const newSet = new Set(prev);
|
|
360
|
+
savedImageIds.forEach((id) => newSet.add(id));
|
|
361
|
+
return newSet;
|
|
362
|
+
});
|
|
363
|
+
if (currentThumbnailId && savedImageIds.includes(currentThumbnailId)) {
|
|
364
|
+
setCurrentThumbnailId(null);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
setSelectedImageIds(/* @__PURE__ */ new Set());
|
|
368
|
+
};
|
|
369
|
+
const handleSave = async () => {
|
|
370
|
+
const hasNewImages = uploadedFiles.length > 0;
|
|
371
|
+
const hasImagesToDelete = imagesToDelete.size > 0;
|
|
372
|
+
const initialThumbnail = existingImages.find((img) => img.type === "thumbnail");
|
|
373
|
+
const thumbnailChanged = currentThumbnailId && !currentThumbnailId.startsWith("uploaded:") && currentThumbnailId !== (initialThumbnail == null ? void 0 : initialThumbnail.id);
|
|
374
|
+
if (!hasNewImages && !hasImagesToDelete && !thumbnailChanged) {
|
|
375
|
+
setOpen(false);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const operations = [];
|
|
380
|
+
if (hasNewImages) {
|
|
381
|
+
const imagesToCreate = uploadedFiles.map((file) => ({
|
|
382
|
+
url: file.url,
|
|
383
|
+
file_id: file.id,
|
|
384
|
+
type: file.type || (currentThumbnailId === `uploaded:${file.id}` ? "thumbnail" : "image")
|
|
385
|
+
}));
|
|
386
|
+
operations.push(createImagesMutation.mutateAsync(imagesToCreate));
|
|
387
|
+
}
|
|
388
|
+
if (thumbnailChanged) {
|
|
389
|
+
const updates = [
|
|
390
|
+
{
|
|
391
|
+
id: currentThumbnailId,
|
|
392
|
+
type: "thumbnail"
|
|
393
|
+
}
|
|
394
|
+
];
|
|
395
|
+
operations.push(updateImagesMutation.mutateAsync(updates));
|
|
396
|
+
}
|
|
397
|
+
if (hasImagesToDelete) {
|
|
398
|
+
const idsToDelete = Array.from(imagesToDelete);
|
|
399
|
+
operations.push(deleteImagesMutation.mutateAsync(idsToDelete));
|
|
400
|
+
}
|
|
401
|
+
await Promise.all(operations);
|
|
402
|
+
queryClient.invalidateQueries({ queryKey: ["category-images", categoryId] });
|
|
403
|
+
setOpen(false);
|
|
404
|
+
resetModalState();
|
|
405
|
+
ui.toast.success("Category media saved successfully");
|
|
406
|
+
} catch (error) {
|
|
407
|
+
ui.toast.error("Failed to save changes");
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal, { open, onOpenChange: handleOpenChange, children: [
|
|
411
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", children: "Edit" }) }),
|
|
412
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
|
|
413
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: "Edit Media" }) }),
|
|
414
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Body, { className: "flex h-full overflow-hidden", children: [
|
|
415
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full h-full flex-col-reverse lg:grid lg:grid-cols-[1fr_560px]", children: [
|
|
416
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
417
|
+
CategoryImageGallery,
|
|
418
|
+
{
|
|
419
|
+
existingImages,
|
|
420
|
+
uploadedFiles,
|
|
421
|
+
imagesToDelete,
|
|
422
|
+
currentThumbnailId,
|
|
423
|
+
selectedImageIds,
|
|
424
|
+
onToggleSelect: handleImageSelection
|
|
425
|
+
}
|
|
426
|
+
),
|
|
427
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
428
|
+
CategoryImageUpload,
|
|
429
|
+
{
|
|
430
|
+
fileInputRef,
|
|
431
|
+
isUploading: uploadFilesMutation.isPending,
|
|
432
|
+
onFileSelect: handleUploadFile
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
] }),
|
|
436
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.CommandBar, { open: selectedImageIds.size > 0, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.CommandBar.Bar, { children: [
|
|
437
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.CommandBar.Value, { children: [
|
|
438
|
+
selectedImageIds.size,
|
|
439
|
+
" selected"
|
|
440
|
+
] }),
|
|
441
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.CommandBar.Seperator, {}),
|
|
442
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
443
|
+
ui.CommandBar.Command,
|
|
444
|
+
{
|
|
445
|
+
action: handleSetAsThumbnail,
|
|
446
|
+
label: "Set as thumbnail",
|
|
447
|
+
shortcut: "t",
|
|
448
|
+
disabled: selectedImageIds.size !== 1
|
|
449
|
+
}
|
|
450
|
+
),
|
|
451
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.CommandBar.Seperator, {}),
|
|
452
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
453
|
+
ui.CommandBar.Command,
|
|
454
|
+
{
|
|
455
|
+
action: handleDelete,
|
|
456
|
+
label: "Delete",
|
|
457
|
+
shortcut: "d"
|
|
458
|
+
}
|
|
459
|
+
)
|
|
460
|
+
] }) })
|
|
461
|
+
] }),
|
|
462
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Footer, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
|
|
463
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", children: "Cancel" }) }),
|
|
464
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
465
|
+
ui.Button,
|
|
466
|
+
{
|
|
467
|
+
size: "small",
|
|
468
|
+
onClick: handleSave,
|
|
469
|
+
isLoading: isSaving,
|
|
470
|
+
children: "Save"
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
] }) })
|
|
474
|
+
] })
|
|
475
|
+
] });
|
|
476
|
+
};
|
|
477
|
+
const CategoryMediaWidget = ({ data }) => {
|
|
478
|
+
const { data: response, isLoading } = reactQuery.useQuery({
|
|
479
|
+
queryKey: ["category-images", data.id],
|
|
480
|
+
queryFn: async () => {
|
|
481
|
+
const result = await sdk.client.fetch(
|
|
482
|
+
`/admin/categories/${data.id}/images`
|
|
483
|
+
);
|
|
484
|
+
return result;
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
const images = (response == null ? void 0 : response.category_images) || [];
|
|
488
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
489
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
490
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Media" }),
|
|
491
|
+
/* @__PURE__ */ jsxRuntime.jsx(CategoryMediaModal, { categoryId: data.id, existingImages: images })
|
|
492
|
+
] }),
|
|
493
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[repeat(auto-fill,96px)] gap-4", children: [
|
|
494
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-full", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle text-sm", children: "Loading..." }) }),
|
|
495
|
+
!isLoading && images.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-full", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle text-sm", children: "No images added yet" }) }),
|
|
496
|
+
images.map((image) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
497
|
+
"div",
|
|
498
|
+
{
|
|
499
|
+
className: "relative aspect-square overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle",
|
|
500
|
+
children: [
|
|
501
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
502
|
+
"img",
|
|
503
|
+
{
|
|
504
|
+
src: image.url,
|
|
505
|
+
alt: `Category ${image.type}`,
|
|
506
|
+
className: "h-full w-full object-cover"
|
|
507
|
+
}
|
|
508
|
+
),
|
|
509
|
+
image.type === "thumbnail" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-2 left-2", children: /* @__PURE__ */ jsxRuntime.jsx(icons.ThumbnailBadge, {}) })
|
|
510
|
+
]
|
|
511
|
+
},
|
|
512
|
+
image.id
|
|
513
|
+
))
|
|
514
|
+
] }) })
|
|
515
|
+
] });
|
|
516
|
+
};
|
|
517
|
+
adminSdk.defineWidgetConfig({
|
|
518
|
+
zone: "product_category.details.after"
|
|
519
|
+
});
|
|
520
|
+
const widgetModule = { widgets: [
|
|
521
|
+
{
|
|
522
|
+
Component: CategoryMediaWidget,
|
|
523
|
+
zone: ["product_category.details.after"]
|
|
524
|
+
}
|
|
525
|
+
] };
|
|
526
|
+
const routeModule = {
|
|
527
|
+
routes: []
|
|
528
|
+
};
|
|
529
|
+
const menuItemModule = {
|
|
530
|
+
menuItems: []
|
|
531
|
+
};
|
|
532
|
+
const formModule = { customFields: {} };
|
|
533
|
+
const displayModule = {
|
|
534
|
+
displays: {}
|
|
535
|
+
};
|
|
536
|
+
const i18nModule = { resources: {} };
|
|
537
|
+
const plugin = {
|
|
538
|
+
widgetModule,
|
|
539
|
+
routeModule,
|
|
540
|
+
menuItemModule,
|
|
541
|
+
formModule,
|
|
542
|
+
displayModule,
|
|
543
|
+
i18nModule
|
|
544
|
+
};
|
|
545
|
+
module.exports = plugin;
|