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