@jytextiles/medusa-plugin-faire-store-sync 0.2.7 → 0.2.8
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 +274 -148
- package/.medusa/server/src/admin/index.mjs +276 -150
- package/.medusa/server/src/api/admin/faire/status/route.js +1 -3
- package/.medusa/server/src/workflows/sync-product-to-faire.js +10 -4
- package/package.json +1 -1
- package/src/admin/lib/api.ts +1 -1
- package/src/admin/routes/settings/faire/{bulk → @bulk}/page.tsx +17 -7
- package/src/admin/routes/settings/faire/{settings → @settings}/page.tsx +11 -22
- package/src/admin/routes/settings/faire/page.tsx +9 -2
- package/src/admin/widgets/faire-product-widget.tsx +182 -1
- package/src/api/admin/faire/status/route.ts +0 -2
- package/src/workflows/sync-product-to-faire.ts +9 -3
- /package/src/admin/routes/settings/faire/{bulk → @bulk}/use-bulk-product-columns.tsx +0 -0
|
@@ -3,9 +3,9 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
3
3
|
const adminSdk = require("@medusajs/admin-sdk");
|
|
4
4
|
const ui = require("@medusajs/ui");
|
|
5
5
|
const reactQuery = require("@tanstack/react-query");
|
|
6
|
+
const react = require("react");
|
|
6
7
|
const Medusa = require("@medusajs/js-sdk");
|
|
7
8
|
const icons = require("@medusajs/icons");
|
|
8
|
-
const react = require("react");
|
|
9
9
|
const reactRouterDom = require("react-router-dom");
|
|
10
10
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
11
11
|
const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
|
|
@@ -74,6 +74,7 @@ const faireApi = {
|
|
|
74
74
|
return sdk.client.fetch("/admin/faire/products", { query });
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
|
+
const MAX_RENDERED = 60;
|
|
77
78
|
const badgeColor = (status) => {
|
|
78
79
|
switch (status) {
|
|
79
80
|
case "synced":
|
|
@@ -89,7 +90,7 @@ const badgeColor = (status) => {
|
|
|
89
90
|
}
|
|
90
91
|
};
|
|
91
92
|
const FaireProductWidget = ({ data }) => {
|
|
92
|
-
var _a, _b, _c;
|
|
93
|
+
var _a, _b, _c, _d;
|
|
93
94
|
const queryClient = reactQuery.useQueryClient();
|
|
94
95
|
const productKey = ["faire", "product-status", data.id];
|
|
95
96
|
const statusQuery = reactQuery.useQuery({
|
|
@@ -132,6 +133,45 @@ const FaireProductWidget = ({ data }) => {
|
|
|
132
133
|
status: latest.status
|
|
133
134
|
} : null;
|
|
134
135
|
const notReady = connected && readiness && !readiness.ready_to_publish;
|
|
136
|
+
const metadata = data.metadata || {};
|
|
137
|
+
const productType = (_d = data.type) == null ? void 0 : _d.value;
|
|
138
|
+
const [pickerOpen, setPickerOpen] = react.useState(false);
|
|
139
|
+
const [pickerSearch, setPickerSearch] = react.useState("");
|
|
140
|
+
const [pinnedOverride, setPinnedOverride] = react.useState(
|
|
141
|
+
void 0
|
|
142
|
+
);
|
|
143
|
+
const taxonomyQuery = reactQuery.useQuery({
|
|
144
|
+
queryKey: ["faire", "taxonomy"],
|
|
145
|
+
enabled: pickerOpen,
|
|
146
|
+
queryFn: async () => (await faireApi.taxonomy()).taxonomy ?? []
|
|
147
|
+
});
|
|
148
|
+
const taxonomy = taxonomyQuery.data ?? [];
|
|
149
|
+
const pinnedId = pinnedOverride !== void 0 ? pinnedOverride ?? void 0 : metadata.faire_taxonomy_type_id;
|
|
150
|
+
const pinnedName = react.useMemo(() => {
|
|
151
|
+
var _a2;
|
|
152
|
+
if (!pinnedId) return void 0;
|
|
153
|
+
if (!/^tt_/.test(String(pinnedId))) return String(pinnedId);
|
|
154
|
+
return (_a2 = taxonomy.find((t) => t.id === pinnedId)) == null ? void 0 : _a2.name;
|
|
155
|
+
}, [pinnedId, taxonomy]);
|
|
156
|
+
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
|
+
const saveCategory = reactQuery.useMutation({
|
|
163
|
+
mutationFn: (value) => sdk.admin.product.update(data.id, {
|
|
164
|
+
metadata: { ...metadata, faire_taxonomy_type_id: value }
|
|
165
|
+
}),
|
|
166
|
+
onSuccess: (_res, value) => {
|
|
167
|
+
setPinnedOverride(value);
|
|
168
|
+
queryClient.invalidateQueries({ queryKey: ["product", data.id] });
|
|
169
|
+
setPickerOpen(false);
|
|
170
|
+
setPickerSearch("");
|
|
171
|
+
ui.toast.success(value ? "Faire category set" : "Faire category cleared");
|
|
172
|
+
},
|
|
173
|
+
onError: (err) => ui.toast.error("Failed to save category", { description: err == null ? void 0 : err.message })
|
|
174
|
+
});
|
|
135
175
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
136
176
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
137
177
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
@@ -158,6 +198,25 @@ const FaireProductWidget = ({ data }) => {
|
|
|
158
198
|
}
|
|
159
199
|
)
|
|
160
200
|
] }) }),
|
|
201
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 rounded-lg border px-3 py-2", children: [
|
|
202
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
203
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
204
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Faire category" }),
|
|
205
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: resolvedSource.from === "fallback" ? "orange" : "grey", children: resolvedSource.from === "pinned" ? "Pinned" : resolvedSource.from === "type" ? "From Type" : resolvedSource.from === "metadata" ? "Metadata" : "Fallback" })
|
|
206
|
+
] }),
|
|
207
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: resolvedSource.label })
|
|
208
|
+
] }),
|
|
209
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
210
|
+
ui.Button,
|
|
211
|
+
{
|
|
212
|
+
size: "small",
|
|
213
|
+
variant: "secondary",
|
|
214
|
+
disabled: !connected,
|
|
215
|
+
onClick: () => setPickerOpen(true),
|
|
216
|
+
children: pinnedId ? "Change" : "Set category"
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
] }),
|
|
161
220
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
162
221
|
ui.Tooltip,
|
|
163
222
|
{
|
|
@@ -174,7 +233,68 @@ const FaireProductWidget = ({ data }) => {
|
|
|
174
233
|
)
|
|
175
234
|
}
|
|
176
235
|
) })
|
|
177
|
-
] })
|
|
236
|
+
] }),
|
|
237
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open: pickerOpen, onOpenChange: setPickerOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
|
|
238
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Header, { children: [
|
|
239
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Pick a Faire category" }),
|
|
240
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Description, { children: "Search Faire's product taxonomy and pin an exact category to this product." })
|
|
241
|
+
] }),
|
|
242
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Body, { className: "flex flex-col gap-3 overflow-hidden", children: [
|
|
243
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
244
|
+
ui.Input,
|
|
245
|
+
{
|
|
246
|
+
type: "search",
|
|
247
|
+
autoFocus: true,
|
|
248
|
+
placeholder: "Search categories…",
|
|
249
|
+
value: pickerSearch,
|
|
250
|
+
onChange: (e) => setPickerSearch(e.target.value)
|
|
251
|
+
}
|
|
252
|
+
),
|
|
253
|
+
/* @__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…" }) : filteredTaxonomy.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle px-1 py-2", children: [
|
|
255
|
+
"No categories match “",
|
|
256
|
+
pickerSearch,
|
|
257
|
+
"”."
|
|
258
|
+
] }) : filteredTaxonomy.map((t) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
259
|
+
"button",
|
|
260
|
+
{
|
|
261
|
+
type: "button",
|
|
262
|
+
onClick: () => saveCategory.mutate(t.id),
|
|
263
|
+
disabled: saveCategory.isPending,
|
|
264
|
+
className: ui.clx(
|
|
265
|
+
"flex items-center justify-between rounded-md px-3 py-2 text-left text-sm",
|
|
266
|
+
"hover:bg-ui-bg-base-hover",
|
|
267
|
+
t.id === pinnedId && "bg-ui-bg-highlight"
|
|
268
|
+
),
|
|
269
|
+
children: [
|
|
270
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.name }),
|
|
271
|
+
t.id === pinnedId && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "green", children: "Current" })
|
|
272
|
+
]
|
|
273
|
+
},
|
|
274
|
+
t.id
|
|
275
|
+
)),
|
|
276
|
+
!taxonomyQuery.isLoading && taxonomy.length > filteredTaxonomy.length && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", className: "text-ui-fg-muted px-1 py-1", children: [
|
|
277
|
+
"Showing ",
|
|
278
|
+
filteredTaxonomy.length,
|
|
279
|
+
" of ",
|
|
280
|
+
taxonomy.length,
|
|
281
|
+
" — refine your search to narrow down."
|
|
282
|
+
] })
|
|
283
|
+
] })
|
|
284
|
+
] }),
|
|
285
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
|
|
286
|
+
pinnedId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
287
|
+
ui.Button,
|
|
288
|
+
{
|
|
289
|
+
variant: "secondary",
|
|
290
|
+
onClick: () => saveCategory.mutate(null),
|
|
291
|
+
isLoading: saveCategory.isPending,
|
|
292
|
+
children: "Clear"
|
|
293
|
+
}
|
|
294
|
+
),
|
|
295
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", children: "Cancel" }) })
|
|
296
|
+
] })
|
|
297
|
+
] }) })
|
|
178
298
|
] });
|
|
179
299
|
};
|
|
180
300
|
adminSdk.defineWidgetConfig({
|
|
@@ -342,7 +462,8 @@ const FaireSettingsPage = () => {
|
|
|
342
462
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-0", children: [
|
|
343
463
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-5 w-32" }) }),
|
|
344
464
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3 px-6 pb-6", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-10 w-full" }, i)) })
|
|
345
|
-
] })
|
|
465
|
+
] }),
|
|
466
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Outlet, {})
|
|
346
467
|
] });
|
|
347
468
|
}
|
|
348
469
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
@@ -470,6 +591,7 @@ const FaireSettingsPage = () => {
|
|
|
470
591
|
)
|
|
471
592
|
] })
|
|
472
593
|
] }) }),
|
|
594
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Outlet, {}),
|
|
473
595
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
|
|
474
596
|
] });
|
|
475
597
|
};
|
|
@@ -692,10 +814,7 @@ const FaireBulkPage = () => {
|
|
|
692
814
|
search: { state: search, onSearchChange: setSearch }
|
|
693
815
|
});
|
|
694
816
|
return /* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal, { prev: "/settings/faire", children: [
|
|
695
|
-
/* @__PURE__ */ jsxRuntime.jsx(RouteFocusModal.Header, { children: /* @__PURE__ */ jsxRuntime.
|
|
696
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: "Bulk sync products to Faire" }),
|
|
697
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Select products (or all matching) and push them to Faire as a background sync via the command bar." })
|
|
698
|
-
] }) }),
|
|
817
|
+
/* @__PURE__ */ jsxRuntime.jsx(RouteFocusModal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(RouteFocusModal.Title, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Bulk sync" }) }) }),
|
|
699
818
|
/* @__PURE__ */ jsxRuntime.jsx(RouteFocusModal.Body, { className: "flex flex-1 flex-col overflow-hidden p-0", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
|
|
700
819
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable.Toolbar, { className: "flex flex-col gap-y-3 border-b px-6 py-4", children: [
|
|
701
820
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2 md:flex-row md:items-center md:justify-between", children: [
|
|
@@ -735,6 +854,22 @@ const FaireBulkPage = () => {
|
|
|
735
854
|
},
|
|
736
855
|
children: selectAllMatching ? `All ${count} selected` : `Select all ${count}`
|
|
737
856
|
}
|
|
857
|
+
),
|
|
858
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
859
|
+
ui.Button,
|
|
860
|
+
{
|
|
861
|
+
size: "small",
|
|
862
|
+
onClick: () => {
|
|
863
|
+
if (!connected) {
|
|
864
|
+
ui.toast.error("Faire is not connected");
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
pushMutation.mutate();
|
|
868
|
+
},
|
|
869
|
+
disabled: !connected || targetCount === 0,
|
|
870
|
+
isLoading: pushMutation.isPending,
|
|
871
|
+
children: "Push to Faire"
|
|
872
|
+
}
|
|
738
873
|
)
|
|
739
874
|
] })
|
|
740
875
|
] }),
|
|
@@ -783,128 +918,14 @@ const FaireBulkPage = () => {
|
|
|
783
918
|
const handle$2 = {
|
|
784
919
|
breadcrumb: () => "Bulk sync"
|
|
785
920
|
};
|
|
786
|
-
const PARENT$1 = "/settings/faire";
|
|
787
|
-
const STATUS_COLORS = {
|
|
788
|
-
success: "green",
|
|
789
|
-
failed: "red",
|
|
790
|
-
draft: "orange",
|
|
791
|
-
pending: "grey",
|
|
792
|
-
syncing: "grey"
|
|
793
|
-
};
|
|
794
|
-
const FaireSyncDetailPage = () => {
|
|
795
|
-
const { id } = reactRouterDom.useParams();
|
|
796
|
-
const navigate = reactRouterDom.useNavigate();
|
|
797
|
-
const [record, setRecord] = react.useState(null);
|
|
798
|
-
const [loading, setLoading] = react.useState(true);
|
|
799
|
-
const [retrying, setRetrying] = react.useState(false);
|
|
800
|
-
const [error, setError] = react.useState(null);
|
|
801
|
-
react.useEffect(() => {
|
|
802
|
-
if (!id) return;
|
|
803
|
-
setLoading(true);
|
|
804
|
-
faireApi.getSync(id).then((res) => {
|
|
805
|
-
setRecord(res.sync || res);
|
|
806
|
-
}).catch((err) => setError(err.message || "Failed to load sync record")).finally(() => setLoading(false));
|
|
807
|
-
}, [id]);
|
|
808
|
-
const handleRetry = async () => {
|
|
809
|
-
if (!record) return;
|
|
810
|
-
setRetrying(true);
|
|
811
|
-
setError(null);
|
|
812
|
-
try {
|
|
813
|
-
await faireApi.retrySync(record.id);
|
|
814
|
-
const updated = await faireApi.getSync(record.id).catch(() => null);
|
|
815
|
-
if (updated) setRecord(updated.sync || updated);
|
|
816
|
-
} catch (err) {
|
|
817
|
-
setError(err.message || "Retry failed");
|
|
818
|
-
} finally {
|
|
819
|
-
setRetrying(false);
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
const warnings = (record == null ? void 0 : record.error_message) ? record.error_message.split(" | ") : [];
|
|
823
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal, { prev: PARENT$1, children: [
|
|
824
|
-
/* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal.Header, { children: [
|
|
825
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
826
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Sync record" }),
|
|
827
|
-
record && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: STATUS_COLORS[record.status] || "grey", children: record.status })
|
|
828
|
-
] }),
|
|
829
|
-
record && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
|
|
830
|
-
/* @__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, {}) }) }),
|
|
831
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { align: "end", children: [
|
|
832
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
833
|
-
ui.DropdownMenu.Item,
|
|
834
|
-
{
|
|
835
|
-
className: "gap-x-2",
|
|
836
|
-
onClick: handleRetry,
|
|
837
|
-
disabled: retrying,
|
|
838
|
-
children: [
|
|
839
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.ArrowPath, { className: "text-ui-fg-subtle" }),
|
|
840
|
-
retrying ? "Re-syncing…" : "Re-sync product"
|
|
841
|
-
]
|
|
842
|
-
}
|
|
843
|
-
),
|
|
844
|
-
record.product_url && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
845
|
-
ui.DropdownMenu.Item,
|
|
846
|
-
{
|
|
847
|
-
className: "gap-x-2",
|
|
848
|
-
onClick: () => window.open(record.product_url, "_blank"),
|
|
849
|
-
children: [
|
|
850
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, { className: "text-ui-fg-subtle" }),
|
|
851
|
-
"Open on Faire"
|
|
852
|
-
]
|
|
853
|
-
}
|
|
854
|
-
)
|
|
855
|
-
] })
|
|
856
|
-
] }) })
|
|
857
|
-
] }),
|
|
858
|
-
/* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal.Body, { className: "flex flex-col gap-4 overflow-y-auto p-6", children: [
|
|
859
|
-
error && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-tag-red-text", children: error }),
|
|
860
|
-
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-10 w-full" }, i)) }) : !record ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-4", children: [
|
|
861
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Sync record not found" }),
|
|
862
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => navigate(PARENT$1), children: "Back to Faire settings" })
|
|
863
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
864
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-ui-border-base grid grid-cols-2 gap-4 rounded-lg border p-4", children: [
|
|
865
|
-
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Record id", value: record.id, mono: true }),
|
|
866
|
-
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Product", value: record.product_id, mono: true }),
|
|
867
|
-
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Action", value: record.action }),
|
|
868
|
-
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Faire product token", value: record.product_token || "—", mono: true }),
|
|
869
|
-
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "State", value: record.product_state || "—" }),
|
|
870
|
-
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Published", value: record.published ? "Yes" : "No" }),
|
|
871
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
872
|
-
Detail,
|
|
873
|
-
{
|
|
874
|
-
label: "Synced at",
|
|
875
|
-
value: record.synced_at ? new Date(record.synced_at).toLocaleString() : "—"
|
|
876
|
-
}
|
|
877
|
-
)
|
|
878
|
-
] }),
|
|
879
|
-
warnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-ui-border-base flex flex-col gap-2 rounded-lg border p-4", children: [
|
|
880
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Issues" }),
|
|
881
|
-
warnings.map((w, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-tag-red-text text-sm", children: w }, i))
|
|
882
|
-
] })
|
|
883
|
-
] })
|
|
884
|
-
] }),
|
|
885
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
|
|
886
|
-
] });
|
|
887
|
-
};
|
|
888
|
-
const Detail = ({
|
|
889
|
-
label,
|
|
890
|
-
value,
|
|
891
|
-
mono
|
|
892
|
-
}) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
893
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
|
|
894
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: mono ? "font-mono text-xs" : "", children: value })
|
|
895
|
-
] });
|
|
896
|
-
const handle$1 = {
|
|
897
|
-
breadcrumb: () => "Sync record"
|
|
898
|
-
};
|
|
899
921
|
const STATUS_KEY = ["faire", "status"];
|
|
900
922
|
const TAXONOMY_KEY = ["faire", "taxonomy"];
|
|
901
|
-
const PARENT = "/settings/faire";
|
|
923
|
+
const PARENT$1 = "/settings/faire";
|
|
902
924
|
const NONE = "__none__";
|
|
903
925
|
const cleanSettings = (form) => {
|
|
904
926
|
const out = { ...form };
|
|
905
927
|
for (const key of [
|
|
906
928
|
"default_brand_id",
|
|
907
|
-
"default_shipping_policy_id",
|
|
908
929
|
"default_category"
|
|
909
930
|
]) {
|
|
910
931
|
if (!out[key] || out[key] === NONE) out[key] = null;
|
|
@@ -924,7 +945,7 @@ const FaireSyncSettingsDrawer = () => {
|
|
|
924
945
|
const [form, setForm] = react.useState({});
|
|
925
946
|
const close = () => {
|
|
926
947
|
setOpen(false);
|
|
927
|
-
navigate(PARENT);
|
|
948
|
+
navigate(PARENT$1);
|
|
928
949
|
};
|
|
929
950
|
const statusQuery = reactQuery.useQuery({
|
|
930
951
|
queryKey: STATUS_KEY,
|
|
@@ -965,7 +986,6 @@ const FaireSyncSettingsDrawer = () => {
|
|
|
965
986
|
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.connected, label: "Faire connected" }),
|
|
966
987
|
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.brand, label: "Brand configured" }),
|
|
967
988
|
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.wholesale_pricing, label: "Wholesale pricing" }),
|
|
968
|
-
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.shipping_policy, label: "Shipping policy" }),
|
|
969
989
|
/* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.taxonomy, label: "Default category" })
|
|
970
990
|
] })
|
|
971
991
|
] }),
|
|
@@ -1022,7 +1042,7 @@ const FaireSyncSettingsDrawer = () => {
|
|
|
1022
1042
|
placeholder: "e.g. 14"
|
|
1023
1043
|
}
|
|
1024
1044
|
) }),
|
|
1025
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "
|
|
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(
|
|
1026
1046
|
SelectField,
|
|
1027
1047
|
{
|
|
1028
1048
|
value: String(form.default_category ?? ""),
|
|
@@ -1030,16 +1050,7 @@ const FaireSyncSettingsDrawer = () => {
|
|
|
1030
1050
|
disabled: !connected,
|
|
1031
1051
|
options: taxonomy.map((t) => ({ value: t.id, label: t.name }))
|
|
1032
1052
|
}
|
|
1033
|
-
) }),
|
|
1034
|
-
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Shipping policy ID", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1035
|
-
ui.Input,
|
|
1036
|
-
{
|
|
1037
|
-
value: String(form.default_shipping_policy_id ?? ""),
|
|
1038
|
-
onChange: (e) => setForm({ ...form, default_shipping_policy_id: e.target.value || null }),
|
|
1039
|
-
disabled: !connected,
|
|
1040
|
-
placeholder: "sp_..."
|
|
1041
|
-
}
|
|
1042
|
-
) }),
|
|
1053
|
+
) }) }),
|
|
1043
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(
|
|
1044
1055
|
ui.Switch,
|
|
1045
1056
|
{
|
|
@@ -1111,9 +1122,122 @@ const ChecklistItem = ({ ok, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
|
1111
1122
|
]
|
|
1112
1123
|
}
|
|
1113
1124
|
);
|
|
1114
|
-
const handle = {
|
|
1125
|
+
const handle$1 = {
|
|
1115
1126
|
breadcrumb: () => "Sync settings"
|
|
1116
1127
|
};
|
|
1128
|
+
const PARENT = "/settings/faire";
|
|
1129
|
+
const STATUS_COLORS = {
|
|
1130
|
+
success: "green",
|
|
1131
|
+
failed: "red",
|
|
1132
|
+
draft: "orange",
|
|
1133
|
+
pending: "grey",
|
|
1134
|
+
syncing: "grey"
|
|
1135
|
+
};
|
|
1136
|
+
const FaireSyncDetailPage = () => {
|
|
1137
|
+
const { id } = reactRouterDom.useParams();
|
|
1138
|
+
const navigate = reactRouterDom.useNavigate();
|
|
1139
|
+
const [record, setRecord] = react.useState(null);
|
|
1140
|
+
const [loading, setLoading] = react.useState(true);
|
|
1141
|
+
const [retrying, setRetrying] = react.useState(false);
|
|
1142
|
+
const [error, setError] = react.useState(null);
|
|
1143
|
+
react.useEffect(() => {
|
|
1144
|
+
if (!id) return;
|
|
1145
|
+
setLoading(true);
|
|
1146
|
+
faireApi.getSync(id).then((res) => {
|
|
1147
|
+
setRecord(res.sync || res);
|
|
1148
|
+
}).catch((err) => setError(err.message || "Failed to load sync record")).finally(() => setLoading(false));
|
|
1149
|
+
}, [id]);
|
|
1150
|
+
const handleRetry = async () => {
|
|
1151
|
+
if (!record) return;
|
|
1152
|
+
setRetrying(true);
|
|
1153
|
+
setError(null);
|
|
1154
|
+
try {
|
|
1155
|
+
await faireApi.retrySync(record.id);
|
|
1156
|
+
const updated = await faireApi.getSync(record.id).catch(() => null);
|
|
1157
|
+
if (updated) setRecord(updated.sync || updated);
|
|
1158
|
+
} catch (err) {
|
|
1159
|
+
setError(err.message || "Retry failed");
|
|
1160
|
+
} finally {
|
|
1161
|
+
setRetrying(false);
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
const warnings = (record == null ? void 0 : record.error_message) ? record.error_message.split(" | ") : [];
|
|
1165
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal, { prev: PARENT, children: [
|
|
1166
|
+
/* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal.Header, { children: [
|
|
1167
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1168
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Sync record" }),
|
|
1169
|
+
record && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: STATUS_COLORS[record.status] || "grey", children: record.status })
|
|
1170
|
+
] }),
|
|
1171
|
+
record && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
|
|
1172
|
+
/* @__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, {}) }) }),
|
|
1173
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { align: "end", children: [
|
|
1174
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1175
|
+
ui.DropdownMenu.Item,
|
|
1176
|
+
{
|
|
1177
|
+
className: "gap-x-2",
|
|
1178
|
+
onClick: handleRetry,
|
|
1179
|
+
disabled: retrying,
|
|
1180
|
+
children: [
|
|
1181
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ArrowPath, { className: "text-ui-fg-subtle" }),
|
|
1182
|
+
retrying ? "Re-syncing…" : "Re-sync product"
|
|
1183
|
+
]
|
|
1184
|
+
}
|
|
1185
|
+
),
|
|
1186
|
+
record.product_url && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1187
|
+
ui.DropdownMenu.Item,
|
|
1188
|
+
{
|
|
1189
|
+
className: "gap-x-2",
|
|
1190
|
+
onClick: () => window.open(record.product_url, "_blank"),
|
|
1191
|
+
children: [
|
|
1192
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, { className: "text-ui-fg-subtle" }),
|
|
1193
|
+
"Open on Faire"
|
|
1194
|
+
]
|
|
1195
|
+
}
|
|
1196
|
+
)
|
|
1197
|
+
] })
|
|
1198
|
+
] }) })
|
|
1199
|
+
] }),
|
|
1200
|
+
/* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal.Body, { className: "flex flex-col gap-4 overflow-y-auto p-6", children: [
|
|
1201
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-tag-red-text", children: error }),
|
|
1202
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-10 w-full" }, i)) }) : !record ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-4", children: [
|
|
1203
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Sync record not found" }),
|
|
1204
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => navigate(PARENT), children: "Back to Faire settings" })
|
|
1205
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1206
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-ui-border-base grid grid-cols-2 gap-4 rounded-lg border p-4", children: [
|
|
1207
|
+
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Record id", value: record.id, mono: true }),
|
|
1208
|
+
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Product", value: record.product_id, mono: true }),
|
|
1209
|
+
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Action", value: record.action }),
|
|
1210
|
+
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Faire product token", value: record.product_token || "—", mono: true }),
|
|
1211
|
+
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "State", value: record.product_state || "—" }),
|
|
1212
|
+
/* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Published", value: record.published ? "Yes" : "No" }),
|
|
1213
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1214
|
+
Detail,
|
|
1215
|
+
{
|
|
1216
|
+
label: "Synced at",
|
|
1217
|
+
value: record.synced_at ? new Date(record.synced_at).toLocaleString() : "—"
|
|
1218
|
+
}
|
|
1219
|
+
)
|
|
1220
|
+
] }),
|
|
1221
|
+
warnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-ui-border-base flex flex-col gap-2 rounded-lg border p-4", children: [
|
|
1222
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Issues" }),
|
|
1223
|
+
warnings.map((w, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-tag-red-text text-sm", children: w }, i))
|
|
1224
|
+
] })
|
|
1225
|
+
] })
|
|
1226
|
+
] }),
|
|
1227
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
|
|
1228
|
+
] });
|
|
1229
|
+
};
|
|
1230
|
+
const Detail = ({
|
|
1231
|
+
label,
|
|
1232
|
+
value,
|
|
1233
|
+
mono
|
|
1234
|
+
}) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
1235
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
|
|
1236
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: mono ? "font-mono text-xs" : "", children: value })
|
|
1237
|
+
] });
|
|
1238
|
+
const handle = {
|
|
1239
|
+
breadcrumb: () => "Sync record"
|
|
1240
|
+
};
|
|
1117
1241
|
const EXCHANGED_PREFIX = "faire:oauth:exchanged:";
|
|
1118
1242
|
const FaireOauthCallback = () => {
|
|
1119
1243
|
const [params] = reactRouterDom.useSearchParams();
|
|
@@ -1192,21 +1316,23 @@ const routeModule = {
|
|
|
1192
1316
|
{
|
|
1193
1317
|
Component: FaireSettingsPage,
|
|
1194
1318
|
path: "/settings/faire",
|
|
1195
|
-
handle: handle$3
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1319
|
+
handle: handle$3,
|
|
1320
|
+
children: [
|
|
1321
|
+
{
|
|
1322
|
+
Component: FaireBulkPage,
|
|
1323
|
+
path: "/settings/faire/bulk",
|
|
1324
|
+
handle: handle$2
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
Component: FaireSyncSettingsDrawer,
|
|
1328
|
+
path: "/settings/faire/settings",
|
|
1329
|
+
handle: handle$1
|
|
1330
|
+
}
|
|
1331
|
+
]
|
|
1201
1332
|
},
|
|
1202
1333
|
{
|
|
1203
1334
|
Component: FaireSyncDetailPage,
|
|
1204
1335
|
path: "/settings/faire/:id",
|
|
1205
|
-
handle: handle$1
|
|
1206
|
-
},
|
|
1207
|
-
{
|
|
1208
|
-
Component: FaireSyncSettingsDrawer,
|
|
1209
|
-
path: "/settings/faire/settings",
|
|
1210
1336
|
handle
|
|
1211
1337
|
},
|
|
1212
1338
|
{
|