@jytextiles/medusa-plugin-faire-store-sync 0.2.12 → 0.2.14
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 +251 -213
- package/.medusa/server/src/admin/index.mjs +253 -215
- package/.medusa/server/src/api/admin/faire/syncs/[id]/route.js +19 -2
- package/.medusa/server/src/workflows/remove-product-from-faire.js +92 -0
- package/.medusa/server/src/workflows/sync-product-to-faire.js +51 -15
- package/package.json +1 -1
- package/src/admin/lib/api.ts +2 -0
- package/src/admin/routes/settings/faire/[id]/page.tsx +38 -2
- package/src/api/admin/faire/syncs/[id]/route.ts +19 -0
- package/src/workflows/remove-product-from-faire.ts +121 -0
- package/src/workflows/sync-product-to-faire.ts +57 -16
|
@@ -62,6 +62,7 @@ const faireApi = {
|
|
|
62
62
|
},
|
|
63
63
|
getSync: (id) => sdk.client.fetch(`/admin/faire/syncs/${id}`),
|
|
64
64
|
retrySync: (id) => sdk.client.fetch(`/admin/faire/syncs/${id}`, { method: "POST" }),
|
|
65
|
+
deleteSync: (id) => sdk.client.fetch(`/admin/faire/syncs/${id}`, { method: "DELETE" }),
|
|
65
66
|
brand: () => sdk.client.fetch("/admin/faire/brand"),
|
|
66
67
|
taxonomy: (opts = {}) => {
|
|
67
68
|
var _a;
|
|
@@ -619,213 +620,6 @@ const config = adminSdk.defineRouteConfig({
|
|
|
619
620
|
const handle$3 = {
|
|
620
621
|
breadcrumb: () => "Faire Sync"
|
|
621
622
|
};
|
|
622
|
-
const STATUS_KEY = ["faire", "status"];
|
|
623
|
-
const TAXONOMY_KEY = ["faire", "taxonomy"];
|
|
624
|
-
const PARENT$1 = "/settings/faire";
|
|
625
|
-
const NONE = "__none__";
|
|
626
|
-
const cleanSettings = (form) => {
|
|
627
|
-
const out = { ...form };
|
|
628
|
-
for (const key of [
|
|
629
|
-
"default_brand_id",
|
|
630
|
-
"default_category"
|
|
631
|
-
]) {
|
|
632
|
-
if (!out[key] || out[key] === NONE) out[key] = null;
|
|
633
|
-
}
|
|
634
|
-
if (out.default_wholesale_markup_percent === "" || out.default_wholesale_markup_percent === NONE) {
|
|
635
|
-
out.default_wholesale_markup_percent = null;
|
|
636
|
-
}
|
|
637
|
-
if (out.default_lead_time_days === "" || out.default_lead_time_days === NONE) {
|
|
638
|
-
out.default_lead_time_days = null;
|
|
639
|
-
}
|
|
640
|
-
return out;
|
|
641
|
-
};
|
|
642
|
-
const FaireSyncSettingsDrawer = () => {
|
|
643
|
-
const navigate = reactRouterDom.useNavigate();
|
|
644
|
-
const queryClient = reactQuery.useQueryClient();
|
|
645
|
-
const [open, setOpen] = react.useState(true);
|
|
646
|
-
const [form, setForm] = react.useState({});
|
|
647
|
-
const close = () => {
|
|
648
|
-
setOpen(false);
|
|
649
|
-
navigate(PARENT$1);
|
|
650
|
-
};
|
|
651
|
-
const statusQuery = reactQuery.useQuery({
|
|
652
|
-
queryKey: STATUS_KEY,
|
|
653
|
-
queryFn: () => faireApi.status()
|
|
654
|
-
});
|
|
655
|
-
const status = statusQuery.data;
|
|
656
|
-
const connected = !!(status == null ? void 0 : status.connected);
|
|
657
|
-
const taxonomyQuery = reactQuery.useQuery({
|
|
658
|
-
queryKey: TAXONOMY_KEY,
|
|
659
|
-
queryFn: async () => (await faireApi.taxonomy().catch(() => ({ taxonomy: [] }))).taxonomy || []
|
|
660
|
-
});
|
|
661
|
-
const taxonomy = taxonomyQuery.data ?? [];
|
|
662
|
-
react.useEffect(() => {
|
|
663
|
-
if (status == null ? void 0 : status.settings) setForm(status.settings);
|
|
664
|
-
}, [status == null ? void 0 : status.settings]);
|
|
665
|
-
const saveMutation = reactQuery.useMutation({
|
|
666
|
-
mutationFn: (payload) => faireApi.saveSettings(payload),
|
|
667
|
-
onSuccess: () => {
|
|
668
|
-
queryClient.invalidateQueries({ queryKey: STATUS_KEY });
|
|
669
|
-
ui.toast.success("Sync settings saved");
|
|
670
|
-
close();
|
|
671
|
-
},
|
|
672
|
-
onError: (err) => ui.toast.error("Failed to save settings", { description: err.message })
|
|
673
|
-
});
|
|
674
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer, { open, onOpenChange: (o) => o ? setOpen(true) : close(), children: [
|
|
675
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
|
|
676
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Header, { children: [
|
|
677
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Sync settings" }),
|
|
678
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Description, { children: "Publish readiness and the defaults applied to every product pushed to Faire." })
|
|
679
|
-
] }),
|
|
680
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-col gap-6 overflow-y-auto", children: statusQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-10 w-full" }, i)) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
681
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
682
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
683
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Publish readiness" }),
|
|
684
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Faire needs a brand and a wholesale-pricing strategy to publish products. Missing fields sync products as drafts." })
|
|
685
|
-
] }),
|
|
686
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
687
|
-
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.connected, label: "Faire connected" }),
|
|
688
|
-
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.brand, label: "Brand configured" }),
|
|
689
|
-
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.wholesale_pricing, label: "Wholesale pricing" }),
|
|
690
|
-
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.taxonomy, label: "Default category" })
|
|
691
|
-
] })
|
|
692
|
-
] }),
|
|
693
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
694
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
695
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Sync defaults" }),
|
|
696
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Applied to every product unless overridden in product metadata." })
|
|
697
|
-
] }),
|
|
698
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4", children: [
|
|
699
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Brand ID", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
700
|
-
ui.Input,
|
|
701
|
-
{
|
|
702
|
-
value: String(form.default_brand_id ?? ""),
|
|
703
|
-
onChange: (e) => setForm({ ...form, default_brand_id: e.target.value || null }),
|
|
704
|
-
disabled: !connected,
|
|
705
|
-
placeholder: "b_..."
|
|
706
|
-
}
|
|
707
|
-
) }),
|
|
708
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Wholesale markup % (off retail)", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Wholesale price = retail × (100 − markup%) / 100. E.g. 50 → half of retail.", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
709
|
-
ui.Input,
|
|
710
|
-
{
|
|
711
|
-
type: "number",
|
|
712
|
-
value: String(form.default_wholesale_markup_percent ?? ""),
|
|
713
|
-
onChange: (e) => setForm({
|
|
714
|
-
...form,
|
|
715
|
-
default_wholesale_markup_percent: e.target.value ? Number(e.target.value) : null
|
|
716
|
-
}),
|
|
717
|
-
disabled: !connected,
|
|
718
|
-
placeholder: "e.g. 50"
|
|
719
|
-
}
|
|
720
|
-
) }) }),
|
|
721
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default min order quantity", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
722
|
-
ui.Input,
|
|
723
|
-
{
|
|
724
|
-
type: "number",
|
|
725
|
-
value: String(form.default_min_order_quantity ?? 1),
|
|
726
|
-
onChange: (e) => setForm({
|
|
727
|
-
...form,
|
|
728
|
-
default_min_order_quantity: e.target.value ? Number(e.target.value) : 1
|
|
729
|
-
}),
|
|
730
|
-
disabled: !connected
|
|
731
|
-
}
|
|
732
|
-
) }),
|
|
733
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default lead time (days)", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
734
|
-
ui.Input,
|
|
735
|
-
{
|
|
736
|
-
type: "number",
|
|
737
|
-
value: String(form.default_lead_time_days ?? ""),
|
|
738
|
-
onChange: (e) => setForm({
|
|
739
|
-
...form,
|
|
740
|
-
default_lead_time_days: e.target.value ? Number(e.target.value) : null
|
|
741
|
-
}),
|
|
742
|
-
disabled: !connected,
|
|
743
|
-
placeholder: "e.g. 14"
|
|
744
|
-
}
|
|
745
|
-
) }),
|
|
746
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Fallback category (taxonomy)", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Faire requires a category per product. Resolution order: product metadata → the product's Type (matched by name) → this fallback. Set the product Type or pick a category on the product page for accuracy.", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
747
|
-
SelectField,
|
|
748
|
-
{
|
|
749
|
-
value: String(form.default_category ?? ""),
|
|
750
|
-
onValueChange: (v) => setForm({ ...form, default_category: v || null }),
|
|
751
|
-
disabled: !connected,
|
|
752
|
-
options: taxonomy.map((t) => ({ value: t.id, label: t.name }))
|
|
753
|
-
}
|
|
754
|
-
) }) }),
|
|
755
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Follow product status", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "If on, published Medusa products are published on Faire; draft products sync as drafts.", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
756
|
-
ui.Switch,
|
|
757
|
-
{
|
|
758
|
-
checked: form.follow_product_status !== false,
|
|
759
|
-
onCheckedChange: (v) => setForm({ ...form, follow_product_status: v })
|
|
760
|
-
}
|
|
761
|
-
) }) }),
|
|
762
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Auto publish", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Publish products to Faire automatically on sync (otherwise create as draft).", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
763
|
-
ui.Switch,
|
|
764
|
-
{
|
|
765
|
-
checked: !!form.auto_publish,
|
|
766
|
-
onCheckedChange: (v) => setForm({ ...form, auto_publish: v })
|
|
767
|
-
}
|
|
768
|
-
) }) })
|
|
769
|
-
] })
|
|
770
|
-
] })
|
|
771
|
-
] }) }),
|
|
772
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
|
|
773
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: close, children: "Cancel" }),
|
|
774
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
775
|
-
ui.Button,
|
|
776
|
-
{
|
|
777
|
-
onClick: () => saveMutation.mutate(cleanSettings(form)),
|
|
778
|
-
isLoading: saveMutation.isPending,
|
|
779
|
-
disabled: !connected,
|
|
780
|
-
children: "Save settings"
|
|
781
|
-
}
|
|
782
|
-
)
|
|
783
|
-
] })
|
|
784
|
-
] }),
|
|
785
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
|
|
786
|
-
] });
|
|
787
|
-
};
|
|
788
|
-
const Field = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
789
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
|
|
790
|
-
children
|
|
791
|
-
] });
|
|
792
|
-
const SelectField = ({
|
|
793
|
-
value,
|
|
794
|
-
onValueChange,
|
|
795
|
-
options,
|
|
796
|
-
disabled
|
|
797
|
-
}) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
798
|
-
ui.Select,
|
|
799
|
-
{
|
|
800
|
-
value: value ? value : NONE,
|
|
801
|
-
onValueChange: (v) => onValueChange(v === NONE ? "" : v),
|
|
802
|
-
disabled,
|
|
803
|
-
size: "small",
|
|
804
|
-
children: [
|
|
805
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Not set" }) }),
|
|
806
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
807
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: NONE, children: "Not set" }),
|
|
808
|
-
options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: o.value, children: o.label }, o.value))
|
|
809
|
-
] })
|
|
810
|
-
]
|
|
811
|
-
}
|
|
812
|
-
);
|
|
813
|
-
const ChecklistItem = ({ ok, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
814
|
-
"div",
|
|
815
|
-
{
|
|
816
|
-
className: ui.clx(
|
|
817
|
-
"flex items-center gap-2 rounded-lg border px-3 py-2",
|
|
818
|
-
ok ? "border-ui-tag-green-border bg-ui-tag-green-bg" : "border-ui-tag-red-border bg-ui-tag-red-bg"
|
|
819
|
-
),
|
|
820
|
-
children: [
|
|
821
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: ok ? "green" : "red", children: ok ? "Ready" : "Missing" }),
|
|
822
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: label })
|
|
823
|
-
]
|
|
824
|
-
}
|
|
825
|
-
);
|
|
826
|
-
const handle$2 = {
|
|
827
|
-
breadcrumb: () => "Sync settings"
|
|
828
|
-
};
|
|
829
623
|
const Root = ({ prev = "..", children }) => {
|
|
830
624
|
const navigate = reactRouterDom.useNavigate();
|
|
831
625
|
const [open, setOpen] = react.useState(false);
|
|
@@ -1135,9 +929,216 @@ const FaireBulkPage = () => {
|
|
|
1135
929
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
|
|
1136
930
|
] });
|
|
1137
931
|
};
|
|
1138
|
-
const handle$
|
|
932
|
+
const handle$2 = {
|
|
1139
933
|
breadcrumb: () => "Bulk sync"
|
|
1140
934
|
};
|
|
935
|
+
const STATUS_KEY = ["faire", "status"];
|
|
936
|
+
const TAXONOMY_KEY = ["faire", "taxonomy"];
|
|
937
|
+
const PARENT$1 = "/settings/faire";
|
|
938
|
+
const NONE = "__none__";
|
|
939
|
+
const cleanSettings = (form) => {
|
|
940
|
+
const out = { ...form };
|
|
941
|
+
for (const key of [
|
|
942
|
+
"default_brand_id",
|
|
943
|
+
"default_category"
|
|
944
|
+
]) {
|
|
945
|
+
if (!out[key] || out[key] === NONE) out[key] = null;
|
|
946
|
+
}
|
|
947
|
+
if (out.default_wholesale_markup_percent === "" || out.default_wholesale_markup_percent === NONE) {
|
|
948
|
+
out.default_wholesale_markup_percent = null;
|
|
949
|
+
}
|
|
950
|
+
if (out.default_lead_time_days === "" || out.default_lead_time_days === NONE) {
|
|
951
|
+
out.default_lead_time_days = null;
|
|
952
|
+
}
|
|
953
|
+
return out;
|
|
954
|
+
};
|
|
955
|
+
const FaireSyncSettingsDrawer = () => {
|
|
956
|
+
const navigate = reactRouterDom.useNavigate();
|
|
957
|
+
const queryClient = reactQuery.useQueryClient();
|
|
958
|
+
const [open, setOpen] = react.useState(true);
|
|
959
|
+
const [form, setForm] = react.useState({});
|
|
960
|
+
const close = () => {
|
|
961
|
+
setOpen(false);
|
|
962
|
+
navigate(PARENT$1);
|
|
963
|
+
};
|
|
964
|
+
const statusQuery = reactQuery.useQuery({
|
|
965
|
+
queryKey: STATUS_KEY,
|
|
966
|
+
queryFn: () => faireApi.status()
|
|
967
|
+
});
|
|
968
|
+
const status = statusQuery.data;
|
|
969
|
+
const connected = !!(status == null ? void 0 : status.connected);
|
|
970
|
+
const taxonomyQuery = reactQuery.useQuery({
|
|
971
|
+
queryKey: TAXONOMY_KEY,
|
|
972
|
+
queryFn: async () => (await faireApi.taxonomy().catch(() => ({ taxonomy: [] }))).taxonomy || []
|
|
973
|
+
});
|
|
974
|
+
const taxonomy = taxonomyQuery.data ?? [];
|
|
975
|
+
react.useEffect(() => {
|
|
976
|
+
if (status == null ? void 0 : status.settings) setForm(status.settings);
|
|
977
|
+
}, [status == null ? void 0 : status.settings]);
|
|
978
|
+
const saveMutation = reactQuery.useMutation({
|
|
979
|
+
mutationFn: (payload) => faireApi.saveSettings(payload),
|
|
980
|
+
onSuccess: () => {
|
|
981
|
+
queryClient.invalidateQueries({ queryKey: STATUS_KEY });
|
|
982
|
+
ui.toast.success("Sync settings saved");
|
|
983
|
+
close();
|
|
984
|
+
},
|
|
985
|
+
onError: (err) => ui.toast.error("Failed to save settings", { description: err.message })
|
|
986
|
+
});
|
|
987
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer, { open, onOpenChange: (o) => o ? setOpen(true) : close(), children: [
|
|
988
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
|
|
989
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Header, { children: [
|
|
990
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Sync settings" }),
|
|
991
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Description, { children: "Publish readiness and the defaults applied to every product pushed to Faire." })
|
|
992
|
+
] }),
|
|
993
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-col gap-6 overflow-y-auto", children: statusQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-10 w-full" }, i)) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
994
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
995
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
996
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Publish readiness" }),
|
|
997
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Faire needs a brand and a wholesale-pricing strategy to publish products. Missing fields sync products as drafts." })
|
|
998
|
+
] }),
|
|
999
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
1000
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.connected, label: "Faire connected" }),
|
|
1001
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.brand, label: "Brand configured" }),
|
|
1002
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.wholesale_pricing, label: "Wholesale pricing" }),
|
|
1003
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.taxonomy, label: "Default category" })
|
|
1004
|
+
] })
|
|
1005
|
+
] }),
|
|
1006
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
1007
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
1008
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Sync defaults" }),
|
|
1009
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Applied to every product unless overridden in product metadata." })
|
|
1010
|
+
] }),
|
|
1011
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4", children: [
|
|
1012
|
+
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Brand ID", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1013
|
+
ui.Input,
|
|
1014
|
+
{
|
|
1015
|
+
value: String(form.default_brand_id ?? ""),
|
|
1016
|
+
onChange: (e) => setForm({ ...form, default_brand_id: e.target.value || null }),
|
|
1017
|
+
disabled: !connected,
|
|
1018
|
+
placeholder: "b_..."
|
|
1019
|
+
}
|
|
1020
|
+
) }),
|
|
1021
|
+
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Wholesale markup % (off retail)", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Wholesale price = retail × (100 − markup%) / 100. E.g. 50 → half of retail.", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1022
|
+
ui.Input,
|
|
1023
|
+
{
|
|
1024
|
+
type: "number",
|
|
1025
|
+
value: String(form.default_wholesale_markup_percent ?? ""),
|
|
1026
|
+
onChange: (e) => setForm({
|
|
1027
|
+
...form,
|
|
1028
|
+
default_wholesale_markup_percent: e.target.value ? Number(e.target.value) : null
|
|
1029
|
+
}),
|
|
1030
|
+
disabled: !connected,
|
|
1031
|
+
placeholder: "e.g. 50"
|
|
1032
|
+
}
|
|
1033
|
+
) }) }),
|
|
1034
|
+
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default min order quantity", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1035
|
+
ui.Input,
|
|
1036
|
+
{
|
|
1037
|
+
type: "number",
|
|
1038
|
+
value: String(form.default_min_order_quantity ?? 1),
|
|
1039
|
+
onChange: (e) => setForm({
|
|
1040
|
+
...form,
|
|
1041
|
+
default_min_order_quantity: e.target.value ? Number(e.target.value) : 1
|
|
1042
|
+
}),
|
|
1043
|
+
disabled: !connected
|
|
1044
|
+
}
|
|
1045
|
+
) }),
|
|
1046
|
+
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default lead time (days)", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1047
|
+
ui.Input,
|
|
1048
|
+
{
|
|
1049
|
+
type: "number",
|
|
1050
|
+
value: String(form.default_lead_time_days ?? ""),
|
|
1051
|
+
onChange: (e) => setForm({
|
|
1052
|
+
...form,
|
|
1053
|
+
default_lead_time_days: e.target.value ? Number(e.target.value) : null
|
|
1054
|
+
}),
|
|
1055
|
+
disabled: !connected,
|
|
1056
|
+
placeholder: "e.g. 14"
|
|
1057
|
+
}
|
|
1058
|
+
) }),
|
|
1059
|
+
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Fallback category (taxonomy)", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Faire requires a category per product. Resolution order: product metadata → the product's Type (matched by name) → this fallback. Set the product Type or pick a category on the product page for accuracy.", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1060
|
+
SelectField,
|
|
1061
|
+
{
|
|
1062
|
+
value: String(form.default_category ?? ""),
|
|
1063
|
+
onValueChange: (v) => setForm({ ...form, default_category: v || null }),
|
|
1064
|
+
disabled: !connected,
|
|
1065
|
+
options: taxonomy.map((t) => ({ value: t.id, label: t.name }))
|
|
1066
|
+
}
|
|
1067
|
+
) }) }),
|
|
1068
|
+
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Follow product status", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "If on, published Medusa products are published on Faire; draft products sync as drafts.", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1069
|
+
ui.Switch,
|
|
1070
|
+
{
|
|
1071
|
+
checked: form.follow_product_status !== false,
|
|
1072
|
+
onCheckedChange: (v) => setForm({ ...form, follow_product_status: v })
|
|
1073
|
+
}
|
|
1074
|
+
) }) }),
|
|
1075
|
+
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Auto publish", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Publish products to Faire automatically on sync (otherwise create as draft).", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1076
|
+
ui.Switch,
|
|
1077
|
+
{
|
|
1078
|
+
checked: !!form.auto_publish,
|
|
1079
|
+
onCheckedChange: (v) => setForm({ ...form, auto_publish: v })
|
|
1080
|
+
}
|
|
1081
|
+
) }) })
|
|
1082
|
+
] })
|
|
1083
|
+
] })
|
|
1084
|
+
] }) }),
|
|
1085
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
|
|
1086
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: close, children: "Cancel" }),
|
|
1087
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1088
|
+
ui.Button,
|
|
1089
|
+
{
|
|
1090
|
+
onClick: () => saveMutation.mutate(cleanSettings(form)),
|
|
1091
|
+
isLoading: saveMutation.isPending,
|
|
1092
|
+
disabled: !connected,
|
|
1093
|
+
children: "Save settings"
|
|
1094
|
+
}
|
|
1095
|
+
)
|
|
1096
|
+
] })
|
|
1097
|
+
] }),
|
|
1098
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
|
|
1099
|
+
] });
|
|
1100
|
+
};
|
|
1101
|
+
const Field = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
1102
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
|
|
1103
|
+
children
|
|
1104
|
+
] });
|
|
1105
|
+
const SelectField = ({
|
|
1106
|
+
value,
|
|
1107
|
+
onValueChange,
|
|
1108
|
+
options,
|
|
1109
|
+
disabled
|
|
1110
|
+
}) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1111
|
+
ui.Select,
|
|
1112
|
+
{
|
|
1113
|
+
value: value ? value : NONE,
|
|
1114
|
+
onValueChange: (v) => onValueChange(v === NONE ? "" : v),
|
|
1115
|
+
disabled,
|
|
1116
|
+
size: "small",
|
|
1117
|
+
children: [
|
|
1118
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Not set" }) }),
|
|
1119
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
1120
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: NONE, children: "Not set" }),
|
|
1121
|
+
options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: o.value, children: o.label }, o.value))
|
|
1122
|
+
] })
|
|
1123
|
+
]
|
|
1124
|
+
}
|
|
1125
|
+
);
|
|
1126
|
+
const ChecklistItem = ({ ok, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1127
|
+
"div",
|
|
1128
|
+
{
|
|
1129
|
+
className: ui.clx(
|
|
1130
|
+
"flex items-center gap-2 rounded-lg border px-3 py-2",
|
|
1131
|
+
ok ? "border-ui-tag-green-border bg-ui-tag-green-bg" : "border-ui-tag-red-border bg-ui-tag-red-bg"
|
|
1132
|
+
),
|
|
1133
|
+
children: [
|
|
1134
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: ok ? "green" : "red", children: ok ? "Ready" : "Missing" }),
|
|
1135
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: label })
|
|
1136
|
+
]
|
|
1137
|
+
}
|
|
1138
|
+
);
|
|
1139
|
+
const handle$1 = {
|
|
1140
|
+
breadcrumb: () => "Sync settings"
|
|
1141
|
+
};
|
|
1141
1142
|
const PARENT = "/settings/faire";
|
|
1142
1143
|
const STATUS_COLORS = {
|
|
1143
1144
|
success: "green",
|
|
@@ -1149,9 +1150,11 @@ const STATUS_COLORS = {
|
|
|
1149
1150
|
const FaireSyncDetailPage = () => {
|
|
1150
1151
|
const { id } = reactRouterDom.useParams();
|
|
1151
1152
|
const navigate = reactRouterDom.useNavigate();
|
|
1153
|
+
const prompt = ui.usePrompt();
|
|
1152
1154
|
const [record, setRecord] = react.useState(null);
|
|
1153
1155
|
const [loading, setLoading] = react.useState(true);
|
|
1154
1156
|
const [retrying, setRetrying] = react.useState(false);
|
|
1157
|
+
const [deleting, setDeleting] = react.useState(false);
|
|
1155
1158
|
const [error, setError] = react.useState(null);
|
|
1156
1159
|
react.useEffect(() => {
|
|
1157
1160
|
if (!id) return;
|
|
@@ -1174,6 +1177,28 @@ const FaireSyncDetailPage = () => {
|
|
|
1174
1177
|
setRetrying(false);
|
|
1175
1178
|
}
|
|
1176
1179
|
};
|
|
1180
|
+
const handleDelete = async () => {
|
|
1181
|
+
if (!record) return;
|
|
1182
|
+
const confirmed = await prompt({
|
|
1183
|
+
title: "Remove from Faire?",
|
|
1184
|
+
description: "Faire has no hard-delete API, so this unpublishes the product (sets it back to draft, hidden from buyers) and unlinks it from the product. Re-sync to publish it again, or delete it permanently from the Faire brand portal.",
|
|
1185
|
+
confirmText: "Remove",
|
|
1186
|
+
cancelText: "Cancel"
|
|
1187
|
+
});
|
|
1188
|
+
if (!confirmed) return;
|
|
1189
|
+
setDeleting(true);
|
|
1190
|
+
setError(null);
|
|
1191
|
+
try {
|
|
1192
|
+
await faireApi.deleteSync(record.id);
|
|
1193
|
+
const updated = await faireApi.getSync(record.id).catch(() => null);
|
|
1194
|
+
if (updated) setRecord(updated.sync || updated);
|
|
1195
|
+
navigate(PARENT);
|
|
1196
|
+
} catch (err) {
|
|
1197
|
+
setError(err.message || "Remove failed");
|
|
1198
|
+
} finally {
|
|
1199
|
+
setDeleting(false);
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1177
1202
|
const warnings = (record == null ? void 0 : record.error_message) ? record.error_message.split(" | ") : [];
|
|
1178
1203
|
return /* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal, { prev: PARENT, children: [
|
|
1179
1204
|
/* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal.Header, { children: [
|
|
@@ -1182,7 +1207,7 @@ const FaireSyncDetailPage = () => {
|
|
|
1182
1207
|
record && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: STATUS_COLORS[record.status] || "grey", children: record.status })
|
|
1183
1208
|
] }),
|
|
1184
1209
|
record && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
|
|
1185
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", disabled: retrying, children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
|
|
1210
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", disabled: retrying || deleting, children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
|
|
1186
1211
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { align: "end", children: [
|
|
1187
1212
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1188
1213
|
ui.DropdownMenu.Item,
|
|
@@ -1206,6 +1231,19 @@ const FaireSyncDetailPage = () => {
|
|
|
1206
1231
|
"Open on Faire"
|
|
1207
1232
|
]
|
|
1208
1233
|
}
|
|
1234
|
+
),
|
|
1235
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
|
|
1236
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1237
|
+
ui.DropdownMenu.Item,
|
|
1238
|
+
{
|
|
1239
|
+
className: "gap-x-2 text-ui-fg-error",
|
|
1240
|
+
onClick: handleDelete,
|
|
1241
|
+
disabled: deleting,
|
|
1242
|
+
children: [
|
|
1243
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-error" }),
|
|
1244
|
+
deleting ? "Removing…" : "Remove from Faire"
|
|
1245
|
+
]
|
|
1246
|
+
}
|
|
1209
1247
|
)
|
|
1210
1248
|
] })
|
|
1211
1249
|
] }) })
|
|
@@ -1332,13 +1370,13 @@ const routeModule = {
|
|
|
1332
1370
|
handle: handle$3,
|
|
1333
1371
|
children: [
|
|
1334
1372
|
{
|
|
1335
|
-
Component:
|
|
1336
|
-
path: "/settings/faire/
|
|
1373
|
+
Component: FaireBulkPage,
|
|
1374
|
+
path: "/settings/faire/bulk",
|
|
1337
1375
|
handle: handle$2
|
|
1338
1376
|
},
|
|
1339
1377
|
{
|
|
1340
|
-
Component:
|
|
1341
|
-
path: "/settings/faire/
|
|
1378
|
+
Component: FaireSyncSettingsDrawer,
|
|
1379
|
+
path: "/settings/faire/settings",
|
|
1342
1380
|
handle: handle$1
|
|
1343
1381
|
}
|
|
1344
1382
|
]
|