@jytextiles/medusa-plugin-faire-store-sync 0.2.8 → 0.2.10
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 +281 -268
- package/.medusa/server/src/admin/index.mjs +283 -270
- package/.medusa/server/src/api/admin/faire/taxonomy/route.js +27 -4
- package/.medusa/server/src/jobs/refresh-faire-token.js +6 -1
- package/.medusa/server/src/lib/faire-client.js +8 -3
- package/.medusa/server/src/modules/faire-sync/service.js +15 -3
- package/.medusa/server/src/workflows/sync-product-to-faire.js +15 -1
- package/package.json +1 -1
- package/src/admin/lib/api.ts +11 -4
- package/src/admin/routes/settings/faire/page.tsx +20 -14
- package/src/admin/widgets/faire-product-widget.tsx +73 -39
- package/src/api/admin/faire/taxonomy/route.ts +31 -3
- package/src/jobs/refresh-faire-token.ts +5 -0
- package/src/lib/faire-client.ts +7 -2
- package/src/modules/faire-sync/service.ts +14 -2
- package/src/workflows/sync-product-to-faire.ts +16 -0
|
@@ -63,9 +63,14 @@ const faireApi = {
|
|
|
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
65
|
brand: () => sdk.client.fetch("/admin/faire/brand"),
|
|
66
|
-
taxonomy: () =>
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
taxonomy: (opts = {}) => {
|
|
67
|
+
var _a;
|
|
68
|
+
const query = {};
|
|
69
|
+
if (opts.q) query.q = opts.q;
|
|
70
|
+
if (opts.limit !== void 0) query.limit = String(opts.limit);
|
|
71
|
+
if ((_a = opts.ids) == null ? void 0 : _a.length) query.ids = opts.ids.join(",");
|
|
72
|
+
return sdk.client.fetch("/admin/faire/taxonomy", { query });
|
|
73
|
+
},
|
|
69
74
|
products: (opts = {}) => {
|
|
70
75
|
const query = {};
|
|
71
76
|
if (opts.limit !== void 0) query.limit = String(opts.limit);
|
|
@@ -74,7 +79,7 @@ const faireApi = {
|
|
|
74
79
|
return sdk.client.fetch("/admin/faire/products", { query });
|
|
75
80
|
}
|
|
76
81
|
};
|
|
77
|
-
const
|
|
82
|
+
const PICKER_LIMIT = 50;
|
|
78
83
|
const badgeColor = (status) => {
|
|
79
84
|
switch (status) {
|
|
80
85
|
case "synced":
|
|
@@ -90,7 +95,7 @@ const badgeColor = (status) => {
|
|
|
90
95
|
}
|
|
91
96
|
};
|
|
92
97
|
const FaireProductWidget = ({ data }) => {
|
|
93
|
-
var _a, _b, _c, _d;
|
|
98
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
94
99
|
const queryClient = reactQuery.useQueryClient();
|
|
95
100
|
const productKey = ["faire", "product-status", data.id];
|
|
96
101
|
const statusQuery = reactQuery.useQuery({
|
|
@@ -137,38 +142,46 @@ const FaireProductWidget = ({ data }) => {
|
|
|
137
142
|
const productType = (_d = data.type) == null ? void 0 : _d.value;
|
|
138
143
|
const [pickerOpen, setPickerOpen] = react.useState(false);
|
|
139
144
|
const [pickerSearch, setPickerSearch] = react.useState("");
|
|
140
|
-
const [
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
const [debouncedSearch, setDebouncedSearch] = react.useState("");
|
|
146
|
+
react.useEffect(() => {
|
|
147
|
+
const t = setTimeout(() => setDebouncedSearch(pickerSearch.trim()), 250);
|
|
148
|
+
return () => clearTimeout(t);
|
|
149
|
+
}, [pickerSearch]);
|
|
150
|
+
const [pinnedOverride, setPinnedOverride] = react.useState(void 0);
|
|
143
151
|
const taxonomyQuery = reactQuery.useQuery({
|
|
144
|
-
queryKey: ["faire", "taxonomy"],
|
|
152
|
+
queryKey: ["faire", "taxonomy", "search", debouncedSearch],
|
|
145
153
|
enabled: pickerOpen,
|
|
146
|
-
|
|
154
|
+
placeholderData: reactQuery.keepPreviousData,
|
|
155
|
+
queryFn: async () => faireApi.taxonomy({
|
|
156
|
+
q: debouncedSearch || void 0,
|
|
157
|
+
limit: PICKER_LIMIT
|
|
158
|
+
})
|
|
147
159
|
});
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
const results = ((_e = taxonomyQuery.data) == null ? void 0 : _e.taxonomy) ?? [];
|
|
161
|
+
const matchCount = ((_f = taxonomyQuery.data) == null ? void 0 : _f.count) ?? 0;
|
|
162
|
+
const pinnedId = pinnedOverride !== void 0 ? pinnedOverride.id ?? void 0 : metadata.faire_taxonomy_type_id;
|
|
163
|
+
const pinnedNameFromMeta = pinnedOverride !== void 0 ? pinnedOverride.name ?? void 0 : metadata.faire_taxonomy_type_name;
|
|
164
|
+
const pinnedResolveQuery = reactQuery.useQuery({
|
|
165
|
+
queryKey: ["faire", "taxonomy", "resolve", pinnedId],
|
|
166
|
+
enabled: !!pinnedId && !pinnedNameFromMeta && /^tt_/.test(String(pinnedId)),
|
|
167
|
+
queryFn: async () => (await faireApi.taxonomy({ ids: [pinnedId] })).taxonomy
|
|
168
|
+
});
|
|
169
|
+
const pinnedName = pinnedNameFromMeta ?? (pinnedId && /^tt_/.test(String(pinnedId)) ? (_h = (_g = pinnedResolveQuery.data) == null ? void 0 : _g[0]) == null ? void 0 : _h.name : pinnedId ? String(pinnedId) : void 0);
|
|
156
170
|
const resolvedSource = pinnedId ? { label: pinnedName || String(pinnedId), from: "pinned" } : metadata.faire_category ? { label: String(metadata.faire_category), from: "metadata" } : productType ? { label: productType, from: "type" } : { label: "Account fallback", from: "fallback" };
|
|
157
|
-
const filteredTaxonomy = react.useMemo(() => {
|
|
158
|
-
const q = pickerSearch.trim().toLowerCase();
|
|
159
|
-
const list = q ? taxonomy.filter((t) => t.name.toLowerCase().includes(q)) : taxonomy;
|
|
160
|
-
return list.slice(0, MAX_RENDERED);
|
|
161
|
-
}, [taxonomy, pickerSearch]);
|
|
162
171
|
const saveCategory = reactQuery.useMutation({
|
|
163
|
-
mutationFn: (
|
|
164
|
-
metadata: {
|
|
172
|
+
mutationFn: (choice) => sdk.admin.product.update(data.id, {
|
|
173
|
+
metadata: {
|
|
174
|
+
...metadata,
|
|
175
|
+
faire_taxonomy_type_id: (choice == null ? void 0 : choice.id) ?? null,
|
|
176
|
+
faire_taxonomy_type_name: (choice == null ? void 0 : choice.name) ?? null
|
|
177
|
+
}
|
|
165
178
|
}),
|
|
166
|
-
onSuccess: (_res,
|
|
167
|
-
setPinnedOverride(
|
|
179
|
+
onSuccess: (_res, choice) => {
|
|
180
|
+
setPinnedOverride({ id: (choice == null ? void 0 : choice.id) ?? null, name: (choice == null ? void 0 : choice.name) ?? null });
|
|
168
181
|
queryClient.invalidateQueries({ queryKey: ["product", data.id] });
|
|
169
182
|
setPickerOpen(false);
|
|
170
183
|
setPickerSearch("");
|
|
171
|
-
ui.toast.success(
|
|
184
|
+
ui.toast.success(choice ? "Faire category set" : "Faire category cleared");
|
|
172
185
|
},
|
|
173
186
|
onError: (err) => ui.toast.error("Failed to save category", { description: err == null ? void 0 : err.message })
|
|
174
187
|
});
|
|
@@ -251,15 +264,11 @@ const FaireProductWidget = ({ data }) => {
|
|
|
251
264
|
}
|
|
252
265
|
),
|
|
253
266
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 overflow-y-auto", children: [
|
|
254
|
-
taxonomyQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle px-1 py-2", children: "Loading categories…" }) :
|
|
255
|
-
"No categories match “",
|
|
256
|
-
pickerSearch,
|
|
257
|
-
"”."
|
|
258
|
-
] }) : filteredTaxonomy.map((t) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
267
|
+
taxonomyQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle px-1 py-2", children: "Loading categories…" }) : results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle px-1 py-2", children: debouncedSearch ? `No categories match “${debouncedSearch}”.` : "No categories available." }) : results.map((t) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
259
268
|
"button",
|
|
260
269
|
{
|
|
261
270
|
type: "button",
|
|
262
|
-
onClick: () => saveCategory.mutate(t
|
|
271
|
+
onClick: () => saveCategory.mutate(t),
|
|
263
272
|
disabled: saveCategory.isPending,
|
|
264
273
|
className: ui.clx(
|
|
265
274
|
"flex items-center justify-between rounded-md px-3 py-2 text-left text-sm",
|
|
@@ -273,12 +282,12 @@ const FaireProductWidget = ({ data }) => {
|
|
|
273
282
|
},
|
|
274
283
|
t.id
|
|
275
284
|
)),
|
|
276
|
-
!taxonomyQuery.isLoading &&
|
|
285
|
+
!taxonomyQuery.isLoading && matchCount > results.length && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", className: "text-ui-fg-muted px-1 py-1", children: [
|
|
277
286
|
"Showing ",
|
|
278
|
-
|
|
287
|
+
results.length,
|
|
279
288
|
" of ",
|
|
280
|
-
|
|
281
|
-
" — refine your search to narrow down."
|
|
289
|
+
matchCount,
|
|
290
|
+
" matches — refine your search to narrow down."
|
|
282
291
|
] })
|
|
283
292
|
] })
|
|
284
293
|
] }),
|
|
@@ -476,24 +485,28 @@ const FaireSettingsPage = () => {
|
|
|
476
485
|
] }),
|
|
477
486
|
connected ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
478
487
|
/* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: "green", children: "Connected" }),
|
|
479
|
-
/* @__PURE__ */ jsxRuntime.
|
|
480
|
-
ui.Button,
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
488
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
|
|
489
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "small", variant: "secondary", children: [
|
|
490
|
+
"Sync",
|
|
491
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ChevronDownMini, {})
|
|
492
|
+
] }) }),
|
|
493
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { children: [
|
|
494
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
495
|
+
ui.DropdownMenu.Item,
|
|
496
|
+
{
|
|
497
|
+
onClick: () => navigate("/settings/faire/bulk"),
|
|
498
|
+
children: "Bulk sync"
|
|
499
|
+
}
|
|
500
|
+
),
|
|
501
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
502
|
+
ui.DropdownMenu.Item,
|
|
503
|
+
{
|
|
504
|
+
onClick: () => navigate("/settings/faire/settings"),
|
|
505
|
+
children: "Sync settings"
|
|
506
|
+
}
|
|
507
|
+
)
|
|
508
|
+
] })
|
|
509
|
+
] }),
|
|
497
510
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
498
511
|
ui.Button,
|
|
499
512
|
{
|
|
@@ -606,6 +619,213 @@ const config = adminSdk.defineRouteConfig({
|
|
|
606
619
|
const handle$3 = {
|
|
607
620
|
breadcrumb: () => "Faire Sync"
|
|
608
621
|
};
|
|
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
|
+
};
|
|
609
829
|
const Root = ({ prev = "..", children }) => {
|
|
610
830
|
const navigate = reactRouterDom.useNavigate();
|
|
611
831
|
const [open, setOpen] = react.useState(false);
|
|
@@ -915,215 +1135,8 @@ const FaireBulkPage = () => {
|
|
|
915
1135
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
|
|
916
1136
|
] });
|
|
917
1137
|
};
|
|
918
|
-
const handle$2 = {
|
|
919
|
-
breadcrumb: () => "Bulk sync"
|
|
920
|
-
};
|
|
921
|
-
const STATUS_KEY = ["faire", "status"];
|
|
922
|
-
const TAXONOMY_KEY = ["faire", "taxonomy"];
|
|
923
|
-
const PARENT$1 = "/settings/faire";
|
|
924
|
-
const NONE = "__none__";
|
|
925
|
-
const cleanSettings = (form) => {
|
|
926
|
-
const out = { ...form };
|
|
927
|
-
for (const key of [
|
|
928
|
-
"default_brand_id",
|
|
929
|
-
"default_category"
|
|
930
|
-
]) {
|
|
931
|
-
if (!out[key] || out[key] === NONE) out[key] = null;
|
|
932
|
-
}
|
|
933
|
-
if (out.default_wholesale_markup_percent === "" || out.default_wholesale_markup_percent === NONE) {
|
|
934
|
-
out.default_wholesale_markup_percent = null;
|
|
935
|
-
}
|
|
936
|
-
if (out.default_lead_time_days === "" || out.default_lead_time_days === NONE) {
|
|
937
|
-
out.default_lead_time_days = null;
|
|
938
|
-
}
|
|
939
|
-
return out;
|
|
940
|
-
};
|
|
941
|
-
const FaireSyncSettingsDrawer = () => {
|
|
942
|
-
const navigate = reactRouterDom.useNavigate();
|
|
943
|
-
const queryClient = reactQuery.useQueryClient();
|
|
944
|
-
const [open, setOpen] = react.useState(true);
|
|
945
|
-
const [form, setForm] = react.useState({});
|
|
946
|
-
const close = () => {
|
|
947
|
-
setOpen(false);
|
|
948
|
-
navigate(PARENT$1);
|
|
949
|
-
};
|
|
950
|
-
const statusQuery = reactQuery.useQuery({
|
|
951
|
-
queryKey: STATUS_KEY,
|
|
952
|
-
queryFn: () => faireApi.status()
|
|
953
|
-
});
|
|
954
|
-
const status = statusQuery.data;
|
|
955
|
-
const connected = !!(status == null ? void 0 : status.connected);
|
|
956
|
-
const taxonomyQuery = reactQuery.useQuery({
|
|
957
|
-
queryKey: TAXONOMY_KEY,
|
|
958
|
-
queryFn: async () => (await faireApi.taxonomy().catch(() => ({ taxonomy: [] }))).taxonomy || []
|
|
959
|
-
});
|
|
960
|
-
const taxonomy = taxonomyQuery.data ?? [];
|
|
961
|
-
react.useEffect(() => {
|
|
962
|
-
if (status == null ? void 0 : status.settings) setForm(status.settings);
|
|
963
|
-
}, [status == null ? void 0 : status.settings]);
|
|
964
|
-
const saveMutation = reactQuery.useMutation({
|
|
965
|
-
mutationFn: (payload) => faireApi.saveSettings(payload),
|
|
966
|
-
onSuccess: () => {
|
|
967
|
-
queryClient.invalidateQueries({ queryKey: STATUS_KEY });
|
|
968
|
-
ui.toast.success("Sync settings saved");
|
|
969
|
-
close();
|
|
970
|
-
},
|
|
971
|
-
onError: (err) => ui.toast.error("Failed to save settings", { description: err.message })
|
|
972
|
-
});
|
|
973
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer, { open, onOpenChange: (o) => o ? setOpen(true) : close(), children: [
|
|
974
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
|
|
975
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Header, { children: [
|
|
976
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Sync settings" }),
|
|
977
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Description, { children: "Publish readiness and the defaults applied to every product pushed to Faire." })
|
|
978
|
-
] }),
|
|
979
|
-
/* @__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: [
|
|
980
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
981
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
982
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Publish readiness" }),
|
|
983
|
-
/* @__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." })
|
|
984
|
-
] }),
|
|
985
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
986
|
-
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.connected, label: "Faire connected" }),
|
|
987
|
-
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.brand, label: "Brand configured" }),
|
|
988
|
-
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.wholesale_pricing, label: "Wholesale pricing" }),
|
|
989
|
-
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.taxonomy, label: "Default category" })
|
|
990
|
-
] })
|
|
991
|
-
] }),
|
|
992
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
993
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
994
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Sync defaults" }),
|
|
995
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Applied to every product unless overridden in product metadata." })
|
|
996
|
-
] }),
|
|
997
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4", children: [
|
|
998
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Brand ID", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
999
|
-
ui.Input,
|
|
1000
|
-
{
|
|
1001
|
-
value: String(form.default_brand_id ?? ""),
|
|
1002
|
-
onChange: (e) => setForm({ ...form, default_brand_id: e.target.value || null }),
|
|
1003
|
-
disabled: !connected,
|
|
1004
|
-
placeholder: "b_..."
|
|
1005
|
-
}
|
|
1006
|
-
) }),
|
|
1007
|
-
/* @__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(
|
|
1008
|
-
ui.Input,
|
|
1009
|
-
{
|
|
1010
|
-
type: "number",
|
|
1011
|
-
value: String(form.default_wholesale_markup_percent ?? ""),
|
|
1012
|
-
onChange: (e) => setForm({
|
|
1013
|
-
...form,
|
|
1014
|
-
default_wholesale_markup_percent: e.target.value ? Number(e.target.value) : null
|
|
1015
|
-
}),
|
|
1016
|
-
disabled: !connected,
|
|
1017
|
-
placeholder: "e.g. 50"
|
|
1018
|
-
}
|
|
1019
|
-
) }) }),
|
|
1020
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default min order quantity", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1021
|
-
ui.Input,
|
|
1022
|
-
{
|
|
1023
|
-
type: "number",
|
|
1024
|
-
value: String(form.default_min_order_quantity ?? 1),
|
|
1025
|
-
onChange: (e) => setForm({
|
|
1026
|
-
...form,
|
|
1027
|
-
default_min_order_quantity: e.target.value ? Number(e.target.value) : 1
|
|
1028
|
-
}),
|
|
1029
|
-
disabled: !connected
|
|
1030
|
-
}
|
|
1031
|
-
) }),
|
|
1032
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default lead time (days)", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1033
|
-
ui.Input,
|
|
1034
|
-
{
|
|
1035
|
-
type: "number",
|
|
1036
|
-
value: String(form.default_lead_time_days ?? ""),
|
|
1037
|
-
onChange: (e) => setForm({
|
|
1038
|
-
...form,
|
|
1039
|
-
default_lead_time_days: e.target.value ? Number(e.target.value) : null
|
|
1040
|
-
}),
|
|
1041
|
-
disabled: !connected,
|
|
1042
|
-
placeholder: "e.g. 14"
|
|
1043
|
-
}
|
|
1044
|
-
) }),
|
|
1045
|
-
/* @__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(
|
|
1046
|
-
SelectField,
|
|
1047
|
-
{
|
|
1048
|
-
value: String(form.default_category ?? ""),
|
|
1049
|
-
onValueChange: (v) => setForm({ ...form, default_category: v || null }),
|
|
1050
|
-
disabled: !connected,
|
|
1051
|
-
options: taxonomy.map((t) => ({ value: t.id, label: t.name }))
|
|
1052
|
-
}
|
|
1053
|
-
) }) }),
|
|
1054
|
-
/* @__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(
|
|
1055
|
-
ui.Switch,
|
|
1056
|
-
{
|
|
1057
|
-
checked: form.follow_product_status !== false,
|
|
1058
|
-
onCheckedChange: (v) => setForm({ ...form, follow_product_status: v })
|
|
1059
|
-
}
|
|
1060
|
-
) }) }),
|
|
1061
|
-
/* @__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(
|
|
1062
|
-
ui.Switch,
|
|
1063
|
-
{
|
|
1064
|
-
checked: !!form.auto_publish,
|
|
1065
|
-
onCheckedChange: (v) => setForm({ ...form, auto_publish: v })
|
|
1066
|
-
}
|
|
1067
|
-
) }) })
|
|
1068
|
-
] })
|
|
1069
|
-
] })
|
|
1070
|
-
] }) }),
|
|
1071
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
|
|
1072
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: close, children: "Cancel" }),
|
|
1073
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1074
|
-
ui.Button,
|
|
1075
|
-
{
|
|
1076
|
-
onClick: () => saveMutation.mutate(cleanSettings(form)),
|
|
1077
|
-
isLoading: saveMutation.isPending,
|
|
1078
|
-
disabled: !connected,
|
|
1079
|
-
children: "Save settings"
|
|
1080
|
-
}
|
|
1081
|
-
)
|
|
1082
|
-
] })
|
|
1083
|
-
] }),
|
|
1084
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
|
|
1085
|
-
] });
|
|
1086
|
-
};
|
|
1087
|
-
const Field = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
1088
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
|
|
1089
|
-
children
|
|
1090
|
-
] });
|
|
1091
|
-
const SelectField = ({
|
|
1092
|
-
value,
|
|
1093
|
-
onValueChange,
|
|
1094
|
-
options,
|
|
1095
|
-
disabled
|
|
1096
|
-
}) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1097
|
-
ui.Select,
|
|
1098
|
-
{
|
|
1099
|
-
value: value ? value : NONE,
|
|
1100
|
-
onValueChange: (v) => onValueChange(v === NONE ? "" : v),
|
|
1101
|
-
disabled,
|
|
1102
|
-
size: "small",
|
|
1103
|
-
children: [
|
|
1104
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Not set" }) }),
|
|
1105
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
1106
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: NONE, children: "Not set" }),
|
|
1107
|
-
options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: o.value, children: o.label }, o.value))
|
|
1108
|
-
] })
|
|
1109
|
-
]
|
|
1110
|
-
}
|
|
1111
|
-
);
|
|
1112
|
-
const ChecklistItem = ({ ok, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1113
|
-
"div",
|
|
1114
|
-
{
|
|
1115
|
-
className: ui.clx(
|
|
1116
|
-
"flex items-center gap-2 rounded-lg border px-3 py-2",
|
|
1117
|
-
ok ? "border-ui-tag-green-border bg-ui-tag-green-bg" : "border-ui-tag-red-border bg-ui-tag-red-bg"
|
|
1118
|
-
),
|
|
1119
|
-
children: [
|
|
1120
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: ok ? "green" : "red", children: ok ? "Ready" : "Missing" }),
|
|
1121
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: label })
|
|
1122
|
-
]
|
|
1123
|
-
}
|
|
1124
|
-
);
|
|
1125
1138
|
const handle$1 = {
|
|
1126
|
-
breadcrumb: () => "
|
|
1139
|
+
breadcrumb: () => "Bulk sync"
|
|
1127
1140
|
};
|
|
1128
1141
|
const PARENT = "/settings/faire";
|
|
1129
1142
|
const STATUS_COLORS = {
|
|
@@ -1319,13 +1332,13 @@ const routeModule = {
|
|
|
1319
1332
|
handle: handle$3,
|
|
1320
1333
|
children: [
|
|
1321
1334
|
{
|
|
1322
|
-
Component:
|
|
1323
|
-
path: "/settings/faire/
|
|
1335
|
+
Component: FaireSyncSettingsDrawer,
|
|
1336
|
+
path: "/settings/faire/settings",
|
|
1324
1337
|
handle: handle$2
|
|
1325
1338
|
},
|
|
1326
1339
|
{
|
|
1327
|
-
Component:
|
|
1328
|
-
path: "/settings/faire/
|
|
1340
|
+
Component: FaireBulkPage,
|
|
1341
|
+
path: "/settings/faire/bulk",
|
|
1329
1342
|
handle: handle$1
|
|
1330
1343
|
}
|
|
1331
1344
|
]
|