medusa-product-helper 0.0.26 → 0.0.28
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.
|
@@ -149,7 +149,8 @@ const useMetadataConfig = (entity) => {
|
|
|
149
149
|
const useProductMetadataConfig = () => useMetadataConfig("product");
|
|
150
150
|
const useCategoryMetadataConfig = () => useMetadataConfig("category");
|
|
151
151
|
const useOrderMetadataConfig = () => useMetadataConfig("order");
|
|
152
|
-
const
|
|
152
|
+
const useCollectionMetadataConfig = () => useMetadataConfig("collection");
|
|
153
|
+
const CONFIG_DOCS_URL$3 = "https://docs.medusajs.com/admin/extension-points/widgets#product-category-details";
|
|
153
154
|
const CategoryMetadataTableWidget = ({ data }) => {
|
|
154
155
|
const { data: descriptors = [], isPending, isError } = useCategoryMetadataConfig();
|
|
155
156
|
const categoryId = (data == null ? void 0 : data.id) ?? void 0;
|
|
@@ -287,6 +288,390 @@ const CategoryMetadataTableWidget = ({ data }) => {
|
|
|
287
288
|
setIsSaving(false);
|
|
288
289
|
}
|
|
289
290
|
};
|
|
291
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col gap-y-4", children: [
|
|
292
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-col gap-y-1", children: [
|
|
293
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
|
|
294
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Metadata" }),
|
|
295
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", rounded: "full", children: descriptors.length })
|
|
296
|
+
] }),
|
|
297
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Structured metadata mapped to the keys you configured in the plugin options." })
|
|
298
|
+
] }),
|
|
299
|
+
isPending || !isInitializedRef.current || Object.keys(values).length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-[160px] w-full" }) : isError ? /* @__PURE__ */ jsxRuntime.jsxs(ui.InlineTip, { variant: "error", label: "Configuration unavailable", children: [
|
|
300
|
+
"Unable to load metadata configuration for this plugin. Confirm that the plugin is registered with options in ",
|
|
301
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "medusa-config.ts" }),
|
|
302
|
+
"."
|
|
303
|
+
] }) : !descriptors.length ? /* @__PURE__ */ jsxRuntime.jsxs(ui.InlineTip, { variant: "info", label: "No configured metadata keys", children: [
|
|
304
|
+
"Provide a ",
|
|
305
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "metadataDescriptors" }),
|
|
306
|
+
" array in the plugin options to control which keys show up here.",
|
|
307
|
+
" ",
|
|
308
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
309
|
+
"a",
|
|
310
|
+
{
|
|
311
|
+
className: "text-ui-fg-interactive underline",
|
|
312
|
+
href: CONFIG_DOCS_URL$3,
|
|
313
|
+
target: "_blank",
|
|
314
|
+
rel: "noreferrer",
|
|
315
|
+
children: "Learn how to configure it."
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
319
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-lg border border-ui-border-base", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "min-w-full divide-y divide-ui-border-base", children: [
|
|
320
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
321
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
322
|
+
"th",
|
|
323
|
+
{
|
|
324
|
+
scope: "col",
|
|
325
|
+
className: "txt-compact-xsmall-plus text-left uppercase tracking-wide text-ui-fg-muted px-4 py-3",
|
|
326
|
+
children: "Label"
|
|
327
|
+
}
|
|
328
|
+
),
|
|
329
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
330
|
+
"th",
|
|
331
|
+
{
|
|
332
|
+
scope: "col",
|
|
333
|
+
className: "txt-compact-xsmall-plus text-left uppercase tracking-wide text-ui-fg-muted px-4 py-3",
|
|
334
|
+
children: "Value"
|
|
335
|
+
}
|
|
336
|
+
)
|
|
337
|
+
] }) }),
|
|
338
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-ui-border-subtle bg-ui-bg-base", children: descriptors.map((descriptor) => {
|
|
339
|
+
const value = values[descriptor.key];
|
|
340
|
+
const error = errors[descriptor.key];
|
|
341
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
342
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
343
|
+
"th",
|
|
344
|
+
{
|
|
345
|
+
scope: "row",
|
|
346
|
+
className: "txt-compact-medium text-ui-fg-base align-top px-4 py-4",
|
|
347
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
348
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: descriptor.label ?? descriptor.key }),
|
|
349
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "txt-compact-xsmall-plus text-ui-fg-muted uppercase tracking-wide", children: descriptor.type })
|
|
350
|
+
] })
|
|
351
|
+
}
|
|
352
|
+
),
|
|
353
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "align-top px-4 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
354
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
355
|
+
ValueField$3,
|
|
356
|
+
{
|
|
357
|
+
descriptor,
|
|
358
|
+
value,
|
|
359
|
+
onStringChange: handleStringChange,
|
|
360
|
+
onBooleanChange: handleBooleanChange
|
|
361
|
+
}
|
|
362
|
+
),
|
|
363
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "txt-compact-small text-ui-fg-error", children: error })
|
|
364
|
+
] }) })
|
|
365
|
+
] }, descriptor.key);
|
|
366
|
+
}) })
|
|
367
|
+
] }) }),
|
|
368
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 border-t border-ui-border-subtle pt-3 md:flex-row md:items-center md:justify-between", children: [
|
|
369
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Changes are stored on the category metadata object. Clearing a field removes the corresponding key on save." }),
|
|
370
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
371
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
372
|
+
ui.Button,
|
|
373
|
+
{
|
|
374
|
+
variant: "secondary",
|
|
375
|
+
size: "small",
|
|
376
|
+
disabled: !isDirty || isSaving,
|
|
377
|
+
onClick: handleReset,
|
|
378
|
+
children: "Reset"
|
|
379
|
+
}
|
|
380
|
+
),
|
|
381
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
382
|
+
ui.Button,
|
|
383
|
+
{
|
|
384
|
+
size: "small",
|
|
385
|
+
onClick: handleSubmit,
|
|
386
|
+
disabled: !isDirty || hasErrors || isSaving,
|
|
387
|
+
isLoading: isSaving,
|
|
388
|
+
children: "Save metadata"
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
] })
|
|
392
|
+
] })
|
|
393
|
+
] })
|
|
394
|
+
] });
|
|
395
|
+
};
|
|
396
|
+
const ValueField$3 = ({
|
|
397
|
+
descriptor,
|
|
398
|
+
value,
|
|
399
|
+
onStringChange,
|
|
400
|
+
onBooleanChange
|
|
401
|
+
}) => {
|
|
402
|
+
const fileInputRef = react.useRef(null);
|
|
403
|
+
const [isUploading, setIsUploading] = react.useState(false);
|
|
404
|
+
const handleFileUpload = async (event) => {
|
|
405
|
+
var _a;
|
|
406
|
+
const file = (_a = event.target.files) == null ? void 0 : _a[0];
|
|
407
|
+
if (!file) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
setIsUploading(true);
|
|
411
|
+
try {
|
|
412
|
+
const formData = new FormData();
|
|
413
|
+
formData.append("files", file);
|
|
414
|
+
const response = await fetch("/admin/uploads", {
|
|
415
|
+
method: "POST",
|
|
416
|
+
credentials: "include",
|
|
417
|
+
body: formData
|
|
418
|
+
});
|
|
419
|
+
if (!response.ok) {
|
|
420
|
+
const payload = await response.json().catch(() => null);
|
|
421
|
+
throw new Error((payload == null ? void 0 : payload.message) ?? "File upload failed");
|
|
422
|
+
}
|
|
423
|
+
const result = await response.json();
|
|
424
|
+
if (result.files && result.files.length > 0) {
|
|
425
|
+
const uploadedFile = result.files[0];
|
|
426
|
+
const fileUrl = uploadedFile.url || uploadedFile.key;
|
|
427
|
+
if (fileUrl) {
|
|
428
|
+
onStringChange(descriptor.key, fileUrl);
|
|
429
|
+
ui.toast.success("File uploaded successfully");
|
|
430
|
+
} else {
|
|
431
|
+
throw new Error("File upload succeeded but no URL returned");
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
throw new Error("File upload failed - no files returned");
|
|
435
|
+
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
ui.toast.error(
|
|
438
|
+
error instanceof Error ? error.message : "Failed to upload file"
|
|
439
|
+
);
|
|
440
|
+
} finally {
|
|
441
|
+
setIsUploading(false);
|
|
442
|
+
if (fileInputRef.current) {
|
|
443
|
+
fileInputRef.current.value = "";
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
if (descriptor.type === "bool") {
|
|
448
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
449
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
450
|
+
ui.Switch,
|
|
451
|
+
{
|
|
452
|
+
checked: Boolean(value),
|
|
453
|
+
onCheckedChange: (checked) => onBooleanChange(descriptor.key, Boolean(checked)),
|
|
454
|
+
"aria-label": `Toggle ${descriptor.label ?? descriptor.key}`
|
|
455
|
+
}
|
|
456
|
+
),
|
|
457
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "txt-compact-small text-ui-fg-muted", children: Boolean(value) ? "True" : "False" })
|
|
458
|
+
] });
|
|
459
|
+
}
|
|
460
|
+
if (descriptor.type === "text") {
|
|
461
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
462
|
+
ui.Textarea,
|
|
463
|
+
{
|
|
464
|
+
value: value ?? "",
|
|
465
|
+
placeholder: "Enter text",
|
|
466
|
+
rows: 3,
|
|
467
|
+
onChange: (event) => onStringChange(descriptor.key, event.target.value)
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
if (descriptor.type === "number") {
|
|
472
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
473
|
+
ui.Input,
|
|
474
|
+
{
|
|
475
|
+
type: "text",
|
|
476
|
+
inputMode: "decimal",
|
|
477
|
+
placeholder: "0.00",
|
|
478
|
+
value: value ?? "",
|
|
479
|
+
onChange: (event) => onStringChange(descriptor.key, event.target.value)
|
|
480
|
+
}
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
484
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
485
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
486
|
+
ui.Input,
|
|
487
|
+
{
|
|
488
|
+
type: "url",
|
|
489
|
+
placeholder: "https://example.com/file",
|
|
490
|
+
value: value ?? "",
|
|
491
|
+
onChange: (event) => onStringChange(descriptor.key, event.target.value),
|
|
492
|
+
className: "flex-1"
|
|
493
|
+
}
|
|
494
|
+
),
|
|
495
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
496
|
+
"input",
|
|
497
|
+
{
|
|
498
|
+
ref: fileInputRef,
|
|
499
|
+
type: "file",
|
|
500
|
+
className: "hidden",
|
|
501
|
+
onChange: handleFileUpload,
|
|
502
|
+
disabled: isUploading,
|
|
503
|
+
"aria-label": `Upload file for ${descriptor.label ?? descriptor.key}`
|
|
504
|
+
}
|
|
505
|
+
),
|
|
506
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
507
|
+
ui.Button,
|
|
508
|
+
{
|
|
509
|
+
type: "button",
|
|
510
|
+
variant: "secondary",
|
|
511
|
+
size: "small",
|
|
512
|
+
onClick: () => {
|
|
513
|
+
var _a;
|
|
514
|
+
return (_a = fileInputRef.current) == null ? void 0 : _a.click();
|
|
515
|
+
},
|
|
516
|
+
disabled: isUploading,
|
|
517
|
+
isLoading: isUploading,
|
|
518
|
+
children: isUploading ? "Uploading..." : "Upload"
|
|
519
|
+
}
|
|
520
|
+
)
|
|
521
|
+
] }),
|
|
522
|
+
typeof value === "string" && value && /* @__PURE__ */ jsxRuntime.jsx(
|
|
523
|
+
"a",
|
|
524
|
+
{
|
|
525
|
+
className: "txt-compact-small-plus text-ui-fg-interactive underline",
|
|
526
|
+
href: value,
|
|
527
|
+
target: "_blank",
|
|
528
|
+
rel: "noreferrer",
|
|
529
|
+
children: "View file"
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
] });
|
|
533
|
+
};
|
|
534
|
+
adminSdk.defineWidgetConfig({
|
|
535
|
+
zone: "product_category.details.after"
|
|
536
|
+
});
|
|
537
|
+
const CONFIG_DOCS_URL$2 = "https://docs.medusajs.com/admin/extension-points/widgets#product-collection-details";
|
|
538
|
+
const CollectionMetadataTableWidget = ({ data }) => {
|
|
539
|
+
const { data: descriptors = [], isPending, isError } = useCollectionMetadataConfig();
|
|
540
|
+
const collectionId = (data == null ? void 0 : data.id) ?? void 0;
|
|
541
|
+
const [baselineMetadata, setBaselineMetadata] = react.useState(
|
|
542
|
+
(data == null ? void 0 : data.metadata) ?? {}
|
|
543
|
+
);
|
|
544
|
+
const queryClient = reactQuery.useQueryClient();
|
|
545
|
+
const previousCollectionIdRef = react.useRef(collectionId);
|
|
546
|
+
const isInitializedRef = react.useRef(false);
|
|
547
|
+
const dataRef = react.useRef(data);
|
|
548
|
+
const descriptorsRef = react.useRef(descriptors);
|
|
549
|
+
react.useEffect(() => {
|
|
550
|
+
dataRef.current = data;
|
|
551
|
+
descriptorsRef.current = descriptors;
|
|
552
|
+
}, [data, descriptors]);
|
|
553
|
+
react.useEffect(() => {
|
|
554
|
+
var _a;
|
|
555
|
+
if (previousCollectionIdRef.current === collectionId && isInitializedRef.current) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const collectionIdChanged = previousCollectionIdRef.current !== collectionId;
|
|
559
|
+
if (collectionIdChanged || !isInitializedRef.current) {
|
|
560
|
+
const currentMetadata = (data == null ? void 0 : data.metadata) ?? ((_a = dataRef.current) == null ? void 0 : _a.metadata) ?? {};
|
|
561
|
+
const currentDescriptors = descriptorsRef.current.length > 0 ? descriptorsRef.current : descriptors;
|
|
562
|
+
if (currentDescriptors.length === 0) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
previousCollectionIdRef.current = collectionId;
|
|
566
|
+
setBaselineMetadata(currentMetadata);
|
|
567
|
+
const newInitialState = buildInitialFormState(currentDescriptors, currentMetadata);
|
|
568
|
+
setValues(newInitialState);
|
|
569
|
+
isInitializedRef.current = true;
|
|
570
|
+
}
|
|
571
|
+
}, [collectionId]);
|
|
572
|
+
const metadataStringRef = react.useRef("");
|
|
573
|
+
react.useEffect(() => {
|
|
574
|
+
const hasCollectionData = !!data;
|
|
575
|
+
const descriptorsLoaded = descriptors.length > 0;
|
|
576
|
+
const sameCollection = previousCollectionIdRef.current === collectionId;
|
|
577
|
+
const notInitialized = !isInitializedRef.current;
|
|
578
|
+
const currentMetadata = (data == null ? void 0 : data.metadata) ?? {};
|
|
579
|
+
const currentMetadataString = JSON.stringify(currentMetadata);
|
|
580
|
+
const metadataChanged = currentMetadataString !== metadataStringRef.current;
|
|
581
|
+
if (hasCollectionData && descriptorsLoaded && sameCollection && (notInitialized || metadataChanged)) {
|
|
582
|
+
const newInitialState = buildInitialFormState(descriptors, currentMetadata);
|
|
583
|
+
setBaselineMetadata(currentMetadata);
|
|
584
|
+
setValues(newInitialState);
|
|
585
|
+
metadataStringRef.current = currentMetadataString;
|
|
586
|
+
isInitializedRef.current = true;
|
|
587
|
+
}
|
|
588
|
+
}, [data, descriptors.length, collectionId]);
|
|
589
|
+
const initialState = react.useMemo(
|
|
590
|
+
() => buildInitialFormState(descriptors, baselineMetadata),
|
|
591
|
+
[descriptors, baselineMetadata]
|
|
592
|
+
);
|
|
593
|
+
const [values, setValues] = react.useState({});
|
|
594
|
+
const [isSaving, setIsSaving] = react.useState(false);
|
|
595
|
+
const errors = react.useMemo(() => {
|
|
596
|
+
return descriptors.reduce((acc, descriptor) => {
|
|
597
|
+
const error = validateValueForDescriptor(descriptor, values[descriptor.key]);
|
|
598
|
+
if (error) {
|
|
599
|
+
acc[descriptor.key] = error;
|
|
600
|
+
}
|
|
601
|
+
return acc;
|
|
602
|
+
}, {});
|
|
603
|
+
}, [descriptors, values]);
|
|
604
|
+
const hasErrors = Object.keys(errors).length > 0;
|
|
605
|
+
const isDirty = react.useMemo(() => {
|
|
606
|
+
return hasMetadataChanges({
|
|
607
|
+
descriptors,
|
|
608
|
+
values,
|
|
609
|
+
originalMetadata: baselineMetadata
|
|
610
|
+
});
|
|
611
|
+
}, [descriptors, values, baselineMetadata]);
|
|
612
|
+
const handleStringChange = (key, nextValue) => {
|
|
613
|
+
setValues((prev) => ({
|
|
614
|
+
...prev,
|
|
615
|
+
[key]: nextValue
|
|
616
|
+
}));
|
|
617
|
+
};
|
|
618
|
+
const handleBooleanChange = (key, nextValue) => {
|
|
619
|
+
setValues((prev) => ({
|
|
620
|
+
...prev,
|
|
621
|
+
[key]: nextValue
|
|
622
|
+
}));
|
|
623
|
+
};
|
|
624
|
+
const handleReset = () => {
|
|
625
|
+
setValues(initialState);
|
|
626
|
+
};
|
|
627
|
+
const handleSubmit = async () => {
|
|
628
|
+
if (!(data == null ? void 0 : data.id) || !descriptors.length) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
setIsSaving(true);
|
|
632
|
+
try {
|
|
633
|
+
const metadataPayload = buildMetadataPayload({
|
|
634
|
+
descriptors,
|
|
635
|
+
values,
|
|
636
|
+
originalMetadata: baselineMetadata
|
|
637
|
+
});
|
|
638
|
+
const response = await fetch(`/admin/collections/${data.id}`, {
|
|
639
|
+
method: "POST",
|
|
640
|
+
credentials: "include",
|
|
641
|
+
headers: {
|
|
642
|
+
"Content-Type": "application/json"
|
|
643
|
+
},
|
|
644
|
+
body: JSON.stringify({
|
|
645
|
+
metadata: metadataPayload
|
|
646
|
+
})
|
|
647
|
+
});
|
|
648
|
+
if (!response.ok) {
|
|
649
|
+
const payload = await response.json().catch(() => null);
|
|
650
|
+
throw new Error((payload == null ? void 0 : payload.message) ?? "Unable to save metadata");
|
|
651
|
+
}
|
|
652
|
+
const updated = await response.json();
|
|
653
|
+
const nextMetadata = updated.product_collection.metadata;
|
|
654
|
+
setBaselineMetadata(nextMetadata);
|
|
655
|
+
setValues(buildInitialFormState(descriptors, nextMetadata));
|
|
656
|
+
ui.toast.success("Metadata saved");
|
|
657
|
+
await queryClient.invalidateQueries({
|
|
658
|
+
queryKey: ["collections"]
|
|
659
|
+
});
|
|
660
|
+
await queryClient.invalidateQueries({
|
|
661
|
+
queryKey: ["collection", data.id]
|
|
662
|
+
});
|
|
663
|
+
if (data.id) {
|
|
664
|
+
queryClient.refetchQueries({
|
|
665
|
+
queryKey: ["collection", data.id]
|
|
666
|
+
}).catch(() => {
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
} catch (error) {
|
|
670
|
+
ui.toast.error(error instanceof Error ? error.message : "Save failed");
|
|
671
|
+
} finally {
|
|
672
|
+
setIsSaving(false);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
290
675
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col gap-y-4", children: [
|
|
291
676
|
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-col gap-y-1", children: [
|
|
292
677
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
|
|
@@ -365,7 +750,7 @@ const CategoryMetadataTableWidget = ({ data }) => {
|
|
|
365
750
|
}) })
|
|
366
751
|
] }) }),
|
|
367
752
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 border-t border-ui-border-subtle pt-3 md:flex-row md:items-center md:justify-between", children: [
|
|
368
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Changes are stored on the
|
|
753
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Changes are stored on the collection metadata object. Clearing a field removes the corresponding key on save." }),
|
|
369
754
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
370
755
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
371
756
|
ui.Button,
|
|
@@ -531,7 +916,7 @@ const ValueField$2 = ({
|
|
|
531
916
|
] });
|
|
532
917
|
};
|
|
533
918
|
adminSdk.defineWidgetConfig({
|
|
534
|
-
zone: "
|
|
919
|
+
zone: "product_collection.details.after"
|
|
535
920
|
});
|
|
536
921
|
const HideCategoryDefaultMetadataWidget = () => {
|
|
537
922
|
react.useEffect(() => {
|
|
@@ -680,14 +1065,14 @@ const OrderMetadataTableWidget = ({ data }) => {
|
|
|
680
1065
|
}, [orderId]);
|
|
681
1066
|
const metadataStringRef = react.useRef("");
|
|
682
1067
|
react.useEffect(() => {
|
|
683
|
-
const
|
|
1068
|
+
const hasOrderData = !!data;
|
|
684
1069
|
const descriptorsLoaded = descriptors.length > 0;
|
|
685
1070
|
const sameOrder = previousOrderIdRef.current === orderId;
|
|
686
1071
|
const notInitialized = !isInitializedRef.current;
|
|
687
|
-
const
|
|
1072
|
+
const currentMetadata = (data == null ? void 0 : data.metadata) ?? {};
|
|
1073
|
+
const currentMetadataString = JSON.stringify(currentMetadata);
|
|
688
1074
|
const metadataChanged = currentMetadataString !== metadataStringRef.current;
|
|
689
|
-
if (
|
|
690
|
-
const currentMetadata = data.metadata ?? {};
|
|
1075
|
+
if (hasOrderData && descriptorsLoaded && sameOrder && (notInitialized || metadataChanged)) {
|
|
691
1076
|
const newInitialState = buildInitialFormState(descriptors, currentMetadata);
|
|
692
1077
|
setBaselineMetadata(currentMetadata);
|
|
693
1078
|
setValues(newInitialState);
|
|
@@ -1540,6 +1925,10 @@ const widgetModule = { widgets: [
|
|
|
1540
1925
|
Component: CategoryMetadataTableWidget,
|
|
1541
1926
|
zone: ["product_category.details.after"]
|
|
1542
1927
|
},
|
|
1928
|
+
{
|
|
1929
|
+
Component: CollectionMetadataTableWidget,
|
|
1930
|
+
zone: ["product_collection.details.after"]
|
|
1931
|
+
},
|
|
1543
1932
|
{
|
|
1544
1933
|
Component: HideCategoryDefaultMetadataWidget,
|
|
1545
1934
|
zone: ["product_category.details.side.before"]
|
|
@@ -148,7 +148,8 @@ const useMetadataConfig = (entity) => {
|
|
|
148
148
|
const useProductMetadataConfig = () => useMetadataConfig("product");
|
|
149
149
|
const useCategoryMetadataConfig = () => useMetadataConfig("category");
|
|
150
150
|
const useOrderMetadataConfig = () => useMetadataConfig("order");
|
|
151
|
-
const
|
|
151
|
+
const useCollectionMetadataConfig = () => useMetadataConfig("collection");
|
|
152
|
+
const CONFIG_DOCS_URL$3 = "https://docs.medusajs.com/admin/extension-points/widgets#product-category-details";
|
|
152
153
|
const CategoryMetadataTableWidget = ({ data }) => {
|
|
153
154
|
const { data: descriptors = [], isPending, isError } = useCategoryMetadataConfig();
|
|
154
155
|
const categoryId = (data == null ? void 0 : data.id) ?? void 0;
|
|
@@ -286,6 +287,390 @@ const CategoryMetadataTableWidget = ({ data }) => {
|
|
|
286
287
|
setIsSaving(false);
|
|
287
288
|
}
|
|
288
289
|
};
|
|
290
|
+
return /* @__PURE__ */ jsxs(Container, { className: "flex flex-col gap-y-4", children: [
|
|
291
|
+
/* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-y-1", children: [
|
|
292
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-3", children: [
|
|
293
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Metadata" }),
|
|
294
|
+
/* @__PURE__ */ jsx(Badge, { size: "2xsmall", rounded: "full", children: descriptors.length })
|
|
295
|
+
] }),
|
|
296
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Structured metadata mapped to the keys you configured in the plugin options." })
|
|
297
|
+
] }),
|
|
298
|
+
isPending || !isInitializedRef.current || Object.keys(values).length === 0 ? /* @__PURE__ */ jsx(Skeleton, { className: "h-[160px] w-full" }) : isError ? /* @__PURE__ */ jsxs(InlineTip, { variant: "error", label: "Configuration unavailable", children: [
|
|
299
|
+
"Unable to load metadata configuration for this plugin. Confirm that the plugin is registered with options in ",
|
|
300
|
+
/* @__PURE__ */ jsx("code", { children: "medusa-config.ts" }),
|
|
301
|
+
"."
|
|
302
|
+
] }) : !descriptors.length ? /* @__PURE__ */ jsxs(InlineTip, { variant: "info", label: "No configured metadata keys", children: [
|
|
303
|
+
"Provide a ",
|
|
304
|
+
/* @__PURE__ */ jsx("code", { children: "metadataDescriptors" }),
|
|
305
|
+
" array in the plugin options to control which keys show up here.",
|
|
306
|
+
" ",
|
|
307
|
+
/* @__PURE__ */ jsx(
|
|
308
|
+
"a",
|
|
309
|
+
{
|
|
310
|
+
className: "text-ui-fg-interactive underline",
|
|
311
|
+
href: CONFIG_DOCS_URL$3,
|
|
312
|
+
target: "_blank",
|
|
313
|
+
rel: "noreferrer",
|
|
314
|
+
children: "Learn how to configure it."
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
318
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-ui-border-base", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full divide-y divide-ui-border-base", children: [
|
|
319
|
+
/* @__PURE__ */ jsx("thead", { className: "bg-ui-bg-subtle", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
320
|
+
/* @__PURE__ */ jsx(
|
|
321
|
+
"th",
|
|
322
|
+
{
|
|
323
|
+
scope: "col",
|
|
324
|
+
className: "txt-compact-xsmall-plus text-left uppercase tracking-wide text-ui-fg-muted px-4 py-3",
|
|
325
|
+
children: "Label"
|
|
326
|
+
}
|
|
327
|
+
),
|
|
328
|
+
/* @__PURE__ */ jsx(
|
|
329
|
+
"th",
|
|
330
|
+
{
|
|
331
|
+
scope: "col",
|
|
332
|
+
className: "txt-compact-xsmall-plus text-left uppercase tracking-wide text-ui-fg-muted px-4 py-3",
|
|
333
|
+
children: "Value"
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
] }) }),
|
|
337
|
+
/* @__PURE__ */ jsx("tbody", { className: "divide-y divide-ui-border-subtle bg-ui-bg-base", children: descriptors.map((descriptor) => {
|
|
338
|
+
const value = values[descriptor.key];
|
|
339
|
+
const error = errors[descriptor.key];
|
|
340
|
+
return /* @__PURE__ */ jsxs("tr", { children: [
|
|
341
|
+
/* @__PURE__ */ jsx(
|
|
342
|
+
"th",
|
|
343
|
+
{
|
|
344
|
+
scope: "row",
|
|
345
|
+
className: "txt-compact-medium text-ui-fg-base align-top px-4 py-4",
|
|
346
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
347
|
+
/* @__PURE__ */ jsx("span", { children: descriptor.label ?? descriptor.key }),
|
|
348
|
+
/* @__PURE__ */ jsx("span", { className: "txt-compact-xsmall-plus text-ui-fg-muted uppercase tracking-wide", children: descriptor.type })
|
|
349
|
+
] })
|
|
350
|
+
}
|
|
351
|
+
),
|
|
352
|
+
/* @__PURE__ */ jsx("td", { className: "align-top px-4 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
353
|
+
/* @__PURE__ */ jsx(
|
|
354
|
+
ValueField$3,
|
|
355
|
+
{
|
|
356
|
+
descriptor,
|
|
357
|
+
value,
|
|
358
|
+
onStringChange: handleStringChange,
|
|
359
|
+
onBooleanChange: handleBooleanChange
|
|
360
|
+
}
|
|
361
|
+
),
|
|
362
|
+
error && /* @__PURE__ */ jsx(Text, { className: "txt-compact-small text-ui-fg-error", children: error })
|
|
363
|
+
] }) })
|
|
364
|
+
] }, descriptor.key);
|
|
365
|
+
}) })
|
|
366
|
+
] }) }),
|
|
367
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-3 border-t border-ui-border-subtle pt-3 md:flex-row md:items-center md:justify-between", children: [
|
|
368
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted", children: "Changes are stored on the category metadata object. Clearing a field removes the corresponding key on save." }),
|
|
369
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
370
|
+
/* @__PURE__ */ jsx(
|
|
371
|
+
Button,
|
|
372
|
+
{
|
|
373
|
+
variant: "secondary",
|
|
374
|
+
size: "small",
|
|
375
|
+
disabled: !isDirty || isSaving,
|
|
376
|
+
onClick: handleReset,
|
|
377
|
+
children: "Reset"
|
|
378
|
+
}
|
|
379
|
+
),
|
|
380
|
+
/* @__PURE__ */ jsx(
|
|
381
|
+
Button,
|
|
382
|
+
{
|
|
383
|
+
size: "small",
|
|
384
|
+
onClick: handleSubmit,
|
|
385
|
+
disabled: !isDirty || hasErrors || isSaving,
|
|
386
|
+
isLoading: isSaving,
|
|
387
|
+
children: "Save metadata"
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
] })
|
|
391
|
+
] })
|
|
392
|
+
] })
|
|
393
|
+
] });
|
|
394
|
+
};
|
|
395
|
+
const ValueField$3 = ({
|
|
396
|
+
descriptor,
|
|
397
|
+
value,
|
|
398
|
+
onStringChange,
|
|
399
|
+
onBooleanChange
|
|
400
|
+
}) => {
|
|
401
|
+
const fileInputRef = useRef(null);
|
|
402
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
403
|
+
const handleFileUpload = async (event) => {
|
|
404
|
+
var _a;
|
|
405
|
+
const file = (_a = event.target.files) == null ? void 0 : _a[0];
|
|
406
|
+
if (!file) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
setIsUploading(true);
|
|
410
|
+
try {
|
|
411
|
+
const formData = new FormData();
|
|
412
|
+
formData.append("files", file);
|
|
413
|
+
const response = await fetch("/admin/uploads", {
|
|
414
|
+
method: "POST",
|
|
415
|
+
credentials: "include",
|
|
416
|
+
body: formData
|
|
417
|
+
});
|
|
418
|
+
if (!response.ok) {
|
|
419
|
+
const payload = await response.json().catch(() => null);
|
|
420
|
+
throw new Error((payload == null ? void 0 : payload.message) ?? "File upload failed");
|
|
421
|
+
}
|
|
422
|
+
const result = await response.json();
|
|
423
|
+
if (result.files && result.files.length > 0) {
|
|
424
|
+
const uploadedFile = result.files[0];
|
|
425
|
+
const fileUrl = uploadedFile.url || uploadedFile.key;
|
|
426
|
+
if (fileUrl) {
|
|
427
|
+
onStringChange(descriptor.key, fileUrl);
|
|
428
|
+
toast.success("File uploaded successfully");
|
|
429
|
+
} else {
|
|
430
|
+
throw new Error("File upload succeeded but no URL returned");
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
throw new Error("File upload failed - no files returned");
|
|
434
|
+
}
|
|
435
|
+
} catch (error) {
|
|
436
|
+
toast.error(
|
|
437
|
+
error instanceof Error ? error.message : "Failed to upload file"
|
|
438
|
+
);
|
|
439
|
+
} finally {
|
|
440
|
+
setIsUploading(false);
|
|
441
|
+
if (fileInputRef.current) {
|
|
442
|
+
fileInputRef.current.value = "";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
if (descriptor.type === "bool") {
|
|
447
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
448
|
+
/* @__PURE__ */ jsx(
|
|
449
|
+
Switch,
|
|
450
|
+
{
|
|
451
|
+
checked: Boolean(value),
|
|
452
|
+
onCheckedChange: (checked) => onBooleanChange(descriptor.key, Boolean(checked)),
|
|
453
|
+
"aria-label": `Toggle ${descriptor.label ?? descriptor.key}`
|
|
454
|
+
}
|
|
455
|
+
),
|
|
456
|
+
/* @__PURE__ */ jsx(Text, { className: "txt-compact-small text-ui-fg-muted", children: Boolean(value) ? "True" : "False" })
|
|
457
|
+
] });
|
|
458
|
+
}
|
|
459
|
+
if (descriptor.type === "text") {
|
|
460
|
+
return /* @__PURE__ */ jsx(
|
|
461
|
+
Textarea,
|
|
462
|
+
{
|
|
463
|
+
value: value ?? "",
|
|
464
|
+
placeholder: "Enter text",
|
|
465
|
+
rows: 3,
|
|
466
|
+
onChange: (event) => onStringChange(descriptor.key, event.target.value)
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
if (descriptor.type === "number") {
|
|
471
|
+
return /* @__PURE__ */ jsx(
|
|
472
|
+
Input,
|
|
473
|
+
{
|
|
474
|
+
type: "text",
|
|
475
|
+
inputMode: "decimal",
|
|
476
|
+
placeholder: "0.00",
|
|
477
|
+
value: value ?? "",
|
|
478
|
+
onChange: (event) => onStringChange(descriptor.key, event.target.value)
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
483
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
484
|
+
/* @__PURE__ */ jsx(
|
|
485
|
+
Input,
|
|
486
|
+
{
|
|
487
|
+
type: "url",
|
|
488
|
+
placeholder: "https://example.com/file",
|
|
489
|
+
value: value ?? "",
|
|
490
|
+
onChange: (event) => onStringChange(descriptor.key, event.target.value),
|
|
491
|
+
className: "flex-1"
|
|
492
|
+
}
|
|
493
|
+
),
|
|
494
|
+
/* @__PURE__ */ jsx(
|
|
495
|
+
"input",
|
|
496
|
+
{
|
|
497
|
+
ref: fileInputRef,
|
|
498
|
+
type: "file",
|
|
499
|
+
className: "hidden",
|
|
500
|
+
onChange: handleFileUpload,
|
|
501
|
+
disabled: isUploading,
|
|
502
|
+
"aria-label": `Upload file for ${descriptor.label ?? descriptor.key}`
|
|
503
|
+
}
|
|
504
|
+
),
|
|
505
|
+
/* @__PURE__ */ jsx(
|
|
506
|
+
Button,
|
|
507
|
+
{
|
|
508
|
+
type: "button",
|
|
509
|
+
variant: "secondary",
|
|
510
|
+
size: "small",
|
|
511
|
+
onClick: () => {
|
|
512
|
+
var _a;
|
|
513
|
+
return (_a = fileInputRef.current) == null ? void 0 : _a.click();
|
|
514
|
+
},
|
|
515
|
+
disabled: isUploading,
|
|
516
|
+
isLoading: isUploading,
|
|
517
|
+
children: isUploading ? "Uploading..." : "Upload"
|
|
518
|
+
}
|
|
519
|
+
)
|
|
520
|
+
] }),
|
|
521
|
+
typeof value === "string" && value && /* @__PURE__ */ jsx(
|
|
522
|
+
"a",
|
|
523
|
+
{
|
|
524
|
+
className: "txt-compact-small-plus text-ui-fg-interactive underline",
|
|
525
|
+
href: value,
|
|
526
|
+
target: "_blank",
|
|
527
|
+
rel: "noreferrer",
|
|
528
|
+
children: "View file"
|
|
529
|
+
}
|
|
530
|
+
)
|
|
531
|
+
] });
|
|
532
|
+
};
|
|
533
|
+
defineWidgetConfig({
|
|
534
|
+
zone: "product_category.details.after"
|
|
535
|
+
});
|
|
536
|
+
const CONFIG_DOCS_URL$2 = "https://docs.medusajs.com/admin/extension-points/widgets#product-collection-details";
|
|
537
|
+
const CollectionMetadataTableWidget = ({ data }) => {
|
|
538
|
+
const { data: descriptors = [], isPending, isError } = useCollectionMetadataConfig();
|
|
539
|
+
const collectionId = (data == null ? void 0 : data.id) ?? void 0;
|
|
540
|
+
const [baselineMetadata, setBaselineMetadata] = useState(
|
|
541
|
+
(data == null ? void 0 : data.metadata) ?? {}
|
|
542
|
+
);
|
|
543
|
+
const queryClient = useQueryClient();
|
|
544
|
+
const previousCollectionIdRef = useRef(collectionId);
|
|
545
|
+
const isInitializedRef = useRef(false);
|
|
546
|
+
const dataRef = useRef(data);
|
|
547
|
+
const descriptorsRef = useRef(descriptors);
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
dataRef.current = data;
|
|
550
|
+
descriptorsRef.current = descriptors;
|
|
551
|
+
}, [data, descriptors]);
|
|
552
|
+
useEffect(() => {
|
|
553
|
+
var _a;
|
|
554
|
+
if (previousCollectionIdRef.current === collectionId && isInitializedRef.current) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const collectionIdChanged = previousCollectionIdRef.current !== collectionId;
|
|
558
|
+
if (collectionIdChanged || !isInitializedRef.current) {
|
|
559
|
+
const currentMetadata = (data == null ? void 0 : data.metadata) ?? ((_a = dataRef.current) == null ? void 0 : _a.metadata) ?? {};
|
|
560
|
+
const currentDescriptors = descriptorsRef.current.length > 0 ? descriptorsRef.current : descriptors;
|
|
561
|
+
if (currentDescriptors.length === 0) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
previousCollectionIdRef.current = collectionId;
|
|
565
|
+
setBaselineMetadata(currentMetadata);
|
|
566
|
+
const newInitialState = buildInitialFormState(currentDescriptors, currentMetadata);
|
|
567
|
+
setValues(newInitialState);
|
|
568
|
+
isInitializedRef.current = true;
|
|
569
|
+
}
|
|
570
|
+
}, [collectionId]);
|
|
571
|
+
const metadataStringRef = useRef("");
|
|
572
|
+
useEffect(() => {
|
|
573
|
+
const hasCollectionData = !!data;
|
|
574
|
+
const descriptorsLoaded = descriptors.length > 0;
|
|
575
|
+
const sameCollection = previousCollectionIdRef.current === collectionId;
|
|
576
|
+
const notInitialized = !isInitializedRef.current;
|
|
577
|
+
const currentMetadata = (data == null ? void 0 : data.metadata) ?? {};
|
|
578
|
+
const currentMetadataString = JSON.stringify(currentMetadata);
|
|
579
|
+
const metadataChanged = currentMetadataString !== metadataStringRef.current;
|
|
580
|
+
if (hasCollectionData && descriptorsLoaded && sameCollection && (notInitialized || metadataChanged)) {
|
|
581
|
+
const newInitialState = buildInitialFormState(descriptors, currentMetadata);
|
|
582
|
+
setBaselineMetadata(currentMetadata);
|
|
583
|
+
setValues(newInitialState);
|
|
584
|
+
metadataStringRef.current = currentMetadataString;
|
|
585
|
+
isInitializedRef.current = true;
|
|
586
|
+
}
|
|
587
|
+
}, [data, descriptors.length, collectionId]);
|
|
588
|
+
const initialState = useMemo(
|
|
589
|
+
() => buildInitialFormState(descriptors, baselineMetadata),
|
|
590
|
+
[descriptors, baselineMetadata]
|
|
591
|
+
);
|
|
592
|
+
const [values, setValues] = useState({});
|
|
593
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
594
|
+
const errors = useMemo(() => {
|
|
595
|
+
return descriptors.reduce((acc, descriptor) => {
|
|
596
|
+
const error = validateValueForDescriptor(descriptor, values[descriptor.key]);
|
|
597
|
+
if (error) {
|
|
598
|
+
acc[descriptor.key] = error;
|
|
599
|
+
}
|
|
600
|
+
return acc;
|
|
601
|
+
}, {});
|
|
602
|
+
}, [descriptors, values]);
|
|
603
|
+
const hasErrors = Object.keys(errors).length > 0;
|
|
604
|
+
const isDirty = useMemo(() => {
|
|
605
|
+
return hasMetadataChanges({
|
|
606
|
+
descriptors,
|
|
607
|
+
values,
|
|
608
|
+
originalMetadata: baselineMetadata
|
|
609
|
+
});
|
|
610
|
+
}, [descriptors, values, baselineMetadata]);
|
|
611
|
+
const handleStringChange = (key, nextValue) => {
|
|
612
|
+
setValues((prev) => ({
|
|
613
|
+
...prev,
|
|
614
|
+
[key]: nextValue
|
|
615
|
+
}));
|
|
616
|
+
};
|
|
617
|
+
const handleBooleanChange = (key, nextValue) => {
|
|
618
|
+
setValues((prev) => ({
|
|
619
|
+
...prev,
|
|
620
|
+
[key]: nextValue
|
|
621
|
+
}));
|
|
622
|
+
};
|
|
623
|
+
const handleReset = () => {
|
|
624
|
+
setValues(initialState);
|
|
625
|
+
};
|
|
626
|
+
const handleSubmit = async () => {
|
|
627
|
+
if (!(data == null ? void 0 : data.id) || !descriptors.length) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
setIsSaving(true);
|
|
631
|
+
try {
|
|
632
|
+
const metadataPayload = buildMetadataPayload({
|
|
633
|
+
descriptors,
|
|
634
|
+
values,
|
|
635
|
+
originalMetadata: baselineMetadata
|
|
636
|
+
});
|
|
637
|
+
const response = await fetch(`/admin/collections/${data.id}`, {
|
|
638
|
+
method: "POST",
|
|
639
|
+
credentials: "include",
|
|
640
|
+
headers: {
|
|
641
|
+
"Content-Type": "application/json"
|
|
642
|
+
},
|
|
643
|
+
body: JSON.stringify({
|
|
644
|
+
metadata: metadataPayload
|
|
645
|
+
})
|
|
646
|
+
});
|
|
647
|
+
if (!response.ok) {
|
|
648
|
+
const payload = await response.json().catch(() => null);
|
|
649
|
+
throw new Error((payload == null ? void 0 : payload.message) ?? "Unable to save metadata");
|
|
650
|
+
}
|
|
651
|
+
const updated = await response.json();
|
|
652
|
+
const nextMetadata = updated.product_collection.metadata;
|
|
653
|
+
setBaselineMetadata(nextMetadata);
|
|
654
|
+
setValues(buildInitialFormState(descriptors, nextMetadata));
|
|
655
|
+
toast.success("Metadata saved");
|
|
656
|
+
await queryClient.invalidateQueries({
|
|
657
|
+
queryKey: ["collections"]
|
|
658
|
+
});
|
|
659
|
+
await queryClient.invalidateQueries({
|
|
660
|
+
queryKey: ["collection", data.id]
|
|
661
|
+
});
|
|
662
|
+
if (data.id) {
|
|
663
|
+
queryClient.refetchQueries({
|
|
664
|
+
queryKey: ["collection", data.id]
|
|
665
|
+
}).catch(() => {
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
} catch (error) {
|
|
669
|
+
toast.error(error instanceof Error ? error.message : "Save failed");
|
|
670
|
+
} finally {
|
|
671
|
+
setIsSaving(false);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
289
674
|
return /* @__PURE__ */ jsxs(Container, { className: "flex flex-col gap-y-4", children: [
|
|
290
675
|
/* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-y-1", children: [
|
|
291
676
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-3", children: [
|
|
@@ -364,7 +749,7 @@ const CategoryMetadataTableWidget = ({ data }) => {
|
|
|
364
749
|
}) })
|
|
365
750
|
] }) }),
|
|
366
751
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-3 border-t border-ui-border-subtle pt-3 md:flex-row md:items-center md:justify-between", children: [
|
|
367
|
-
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted", children: "Changes are stored on the
|
|
752
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted", children: "Changes are stored on the collection metadata object. Clearing a field removes the corresponding key on save." }),
|
|
368
753
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
369
754
|
/* @__PURE__ */ jsx(
|
|
370
755
|
Button,
|
|
@@ -530,7 +915,7 @@ const ValueField$2 = ({
|
|
|
530
915
|
] });
|
|
531
916
|
};
|
|
532
917
|
defineWidgetConfig({
|
|
533
|
-
zone: "
|
|
918
|
+
zone: "product_collection.details.after"
|
|
534
919
|
});
|
|
535
920
|
const HideCategoryDefaultMetadataWidget = () => {
|
|
536
921
|
useEffect(() => {
|
|
@@ -679,14 +1064,14 @@ const OrderMetadataTableWidget = ({ data }) => {
|
|
|
679
1064
|
}, [orderId]);
|
|
680
1065
|
const metadataStringRef = useRef("");
|
|
681
1066
|
useEffect(() => {
|
|
682
|
-
const
|
|
1067
|
+
const hasOrderData = !!data;
|
|
683
1068
|
const descriptorsLoaded = descriptors.length > 0;
|
|
684
1069
|
const sameOrder = previousOrderIdRef.current === orderId;
|
|
685
1070
|
const notInitialized = !isInitializedRef.current;
|
|
686
|
-
const
|
|
1071
|
+
const currentMetadata = (data == null ? void 0 : data.metadata) ?? {};
|
|
1072
|
+
const currentMetadataString = JSON.stringify(currentMetadata);
|
|
687
1073
|
const metadataChanged = currentMetadataString !== metadataStringRef.current;
|
|
688
|
-
if (
|
|
689
|
-
const currentMetadata = data.metadata ?? {};
|
|
1074
|
+
if (hasOrderData && descriptorsLoaded && sameOrder && (notInitialized || metadataChanged)) {
|
|
690
1075
|
const newInitialState = buildInitialFormState(descriptors, currentMetadata);
|
|
691
1076
|
setBaselineMetadata(currentMetadata);
|
|
692
1077
|
setValues(newInitialState);
|
|
@@ -1539,6 +1924,10 @@ const widgetModule = { widgets: [
|
|
|
1539
1924
|
Component: CategoryMetadataTableWidget,
|
|
1540
1925
|
zone: ["product_category.details.after"]
|
|
1541
1926
|
},
|
|
1927
|
+
{
|
|
1928
|
+
Component: CollectionMetadataTableWidget,
|
|
1929
|
+
zone: ["product_collection.details.after"]
|
|
1930
|
+
},
|
|
1542
1931
|
{
|
|
1543
1932
|
Component: HideCategoryDefaultMetadataWidget,
|
|
1544
1933
|
zone: ["product_category.details.side.before"]
|
|
@@ -7,6 +7,7 @@ const ENTITY_PARAM = {
|
|
|
7
7
|
PRODUCT: "product",
|
|
8
8
|
CATEGORY: "category",
|
|
9
9
|
ORDER: "order",
|
|
10
|
+
COLLECTION: "collection",
|
|
10
11
|
};
|
|
11
12
|
async function GET(req, res) {
|
|
12
13
|
const configModule = req.scope.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE);
|
|
@@ -16,9 +17,11 @@ async function GET(req, res) {
|
|
|
16
17
|
? options.metadata.categories.descriptors
|
|
17
18
|
: entity === ENTITY_PARAM.ORDER
|
|
18
19
|
? options.metadata.orders.descriptors
|
|
19
|
-
:
|
|
20
|
+
: entity === ENTITY_PARAM.COLLECTION
|
|
21
|
+
? options.metadata.collections.descriptors
|
|
22
|
+
: options.metadata.products.descriptors;
|
|
20
23
|
res.json({
|
|
21
24
|
metadataDescriptors,
|
|
22
25
|
});
|
|
23
26
|
}
|
|
24
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
27
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3Byb2R1Y3QtbWV0YWRhdGEtY29uZmlnL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBY0Esa0JBb0JDO0FBakNELHFEQUFxRTtBQUVyRSxtRkFBb0Y7QUFFcEYsTUFBTSxZQUFZLEdBQUc7SUFDbkIsT0FBTyxFQUFFLFNBQVM7SUFDbEIsUUFBUSxFQUFFLFVBQVU7SUFDcEIsS0FBSyxFQUFFLE9BQU87SUFDZCxVQUFVLEVBQUUsWUFBWTtDQUNoQixDQUFBO0FBSUgsS0FBSyxVQUFVLEdBQUcsQ0FBQyxHQUFrQixFQUFFLEdBQW1CO0lBQy9ELE1BQU0sWUFBWSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUNwQyxpQ0FBeUIsQ0FBQyxhQUFhLENBQ3hDLENBQUE7SUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFBLG9EQUEyQixFQUFDLFlBQVksQ0FBQyxDQUFBO0lBQ3pELE1BQU0sTUFBTSxHQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsTUFBeUIsSUFBSSxZQUFZLENBQUMsT0FBTyxDQUFBO0lBRTVFLE1BQU0sbUJBQW1CLEdBQ3ZCLE1BQU0sS0FBSyxZQUFZLENBQUMsUUFBUTtRQUM5QixDQUFDLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsV0FBVztRQUN6QyxDQUFDLENBQUMsTUFBTSxLQUFLLFlBQVksQ0FBQyxLQUFLO1lBQy9CLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxXQUFXO1lBQ3JDLENBQUMsQ0FBQyxNQUFNLEtBQUssWUFBWSxDQUFDLFVBQVU7Z0JBQ3BDLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxXQUFXO2dCQUMxQyxDQUFDLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFBO0lBRTNDLEdBQUcsQ0FBQyxJQUFJLENBQUM7UUFDUCxtQkFBbUI7S0FDcEIsQ0FBQyxDQUFBO0FBQ0osQ0FBQyJ9
|
|
@@ -66,6 +66,9 @@ const MetadataSchema = zod_1.z.object({
|
|
|
66
66
|
orders: MetadataCollectionSchema.default({
|
|
67
67
|
descriptors: [],
|
|
68
68
|
}),
|
|
69
|
+
collections: MetadataCollectionSchema.default({
|
|
70
|
+
descriptors: [],
|
|
71
|
+
}),
|
|
69
72
|
});
|
|
70
73
|
const PromotionWindowSchema = zod_1.z.object({
|
|
71
74
|
start_metadata_key: zod_1.z.string().min(1).default("promotion_start"),
|
|
@@ -109,6 +112,9 @@ const ProductHelperOptionsSchema = zod_1.z.object({
|
|
|
109
112
|
orders: {
|
|
110
113
|
descriptors: [],
|
|
111
114
|
},
|
|
115
|
+
collections: {
|
|
116
|
+
descriptors: [],
|
|
117
|
+
},
|
|
112
118
|
}),
|
|
113
119
|
default_price_range: PriceRangeSchema.default({
|
|
114
120
|
label: "custom",
|
|
@@ -162,6 +168,10 @@ function normalizeProductHelperOptions(input) {
|
|
|
162
168
|
...parsed.metadata.orders,
|
|
163
169
|
descriptors: (0, utils_2.normalizeMetadataDescriptors)(parsed.metadata.orders.descriptors),
|
|
164
170
|
},
|
|
171
|
+
collections: {
|
|
172
|
+
...parsed.metadata.collections,
|
|
173
|
+
descriptors: (0, utils_2.normalizeMetadataDescriptors)(parsed.metadata.collections.descriptors),
|
|
174
|
+
},
|
|
165
175
|
},
|
|
166
176
|
filterProviders: parsed.filterProviders ?? [],
|
|
167
177
|
disableBuiltInProviders: parsed.disableBuiltInProviders ?? [],
|
|
@@ -182,4 +192,4 @@ function resolveProductHelperOptions(configModule) {
|
|
|
182
192
|
}
|
|
183
193
|
return exports.DEFAULT_PRODUCT_HELPER_OPTIONS;
|
|
184
194
|
}
|
|
185
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
195
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvZHVjdC1oZWxwZXItb3B0aW9ucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9jb25maWcvcHJvZHVjdC1oZWxwZXItb3B0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFtTkEsc0VBb0NDO0FBRUQsa0VBbUJDO0FBNVFELHFEQUFvRDtBQUNwRCw2QkFBdUI7QUFFdkIsNERBR3lDO0FBQ3pDLDREQUErRTtBQUUvRSxNQUFNLGdCQUFnQixHQUFHLE9BQUM7S0FDdkIsTUFBTSxDQUFDO0lBQ04sS0FBSyxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUM7SUFDbEQsYUFBYSxFQUFFLE9BQUM7U0FDYixNQUFNLEVBQUU7U0FDUixJQUFJLEVBQUU7U0FDTixNQUFNLENBQUMsQ0FBQyxFQUFFLG1DQUFtQyxDQUFDO1NBQzlDLFNBQVMsQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsS0FBSyxDQUFDLFdBQVcsRUFBRSxDQUFDO1NBQ3pDLFFBQVEsRUFBRTtJQUNiLEdBQUcsRUFBRSxPQUFDO1NBQ0gsTUFBTSxDQUFDO1FBQ04sTUFBTSxFQUFFLElBQUk7UUFDWixrQkFBa0IsRUFBRSw0QkFBNEI7S0FDakQsQ0FBQztTQUNELFdBQVcsRUFBRTtTQUNiLFFBQVEsRUFBRTtTQUNWLE9BQU8sQ0FBQyxJQUFJLENBQUM7SUFDaEIsR0FBRyxFQUFFLE9BQUM7U0FDSCxNQUFNLENBQUM7UUFDTixNQUFNLEVBQUUsSUFBSTtRQUNaLGtCQUFrQixFQUFFLDRCQUE0QjtLQUNqRCxDQUFDO1NBQ0QsV0FBVyxFQUFFO1NBQ2IsUUFBUSxFQUFFO1NBQ1YsT0FBTyxDQUFDLElBQUksQ0FBQztDQUNqQixDQUFDO0tBQ0QsTUFBTSxDQUNMLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FDUixLQUFL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
|