@jytextiles/medusa-plugin-faire-store-sync 0.1.0

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.
Files changed (104) hide show
  1. package/.medusa/server/src/__tests__/ingest-faire-order-support.spec.js +74 -0
  2. package/.medusa/server/src/__tests__/webhook.spec.js +77 -0
  3. package/.medusa/server/src/admin/index.js +960 -0
  4. package/.medusa/server/src/admin/index.mjs +959 -0
  5. package/.medusa/server/src/api/admin/faire/auth/authorize/route.js +12 -0
  6. package/.medusa/server/src/api/admin/faire/auth/callback/route.js +25 -0
  7. package/.medusa/server/src/api/admin/faire/auth/disconnect/route.js +12 -0
  8. package/.medusa/server/src/api/admin/faire/brand/route.js +14 -0
  9. package/.medusa/server/src/api/admin/faire/ingest/orders/[batchId]/route.js +27 -0
  10. package/.medusa/server/src/api/admin/faire/ingest/orders/route.js +20 -0
  11. package/.medusa/server/src/api/admin/faire/products/route.js +16 -0
  12. package/.medusa/server/src/api/admin/faire/settings/route.js +19 -0
  13. package/.medusa/server/src/api/admin/faire/status/product/[id]/route.js +30 -0
  14. package/.medusa/server/src/api/admin/faire/status/route.js +46 -0
  15. package/.medusa/server/src/api/admin/faire/sync/bulk/[batchId]/route.js +29 -0
  16. package/.medusa/server/src/api/admin/faire/sync/bulk/route.js +20 -0
  17. package/.medusa/server/src/api/admin/faire/sync/product/[id]/route.js +14 -0
  18. package/.medusa/server/src/api/admin/faire/syncs/[id]/route.js +32 -0
  19. package/.medusa/server/src/api/admin/faire/syncs/route.js +18 -0
  20. package/.medusa/server/src/api/webhooks/faire/route.js +125 -0
  21. package/.medusa/server/src/jobs/pull-faire-orders.js +32 -0
  22. package/.medusa/server/src/jobs/refresh-faire-token.js +25 -0
  23. package/.medusa/server/src/lib/faire-client.js +321 -0
  24. package/.medusa/server/src/lib/types.js +10 -0
  25. package/.medusa/server/src/lib/webhook.js +58 -0
  26. package/.medusa/server/src/links/product-faire-sync-link.js +57 -0
  27. package/.medusa/server/src/modules/faire-sync/index.js +15 -0
  28. package/.medusa/server/src/modules/faire-sync/migrations/Migration20260707211346.js +34 -0
  29. package/.medusa/server/src/modules/faire-sync/models/faire-order.js +21 -0
  30. package/.medusa/server/src/modules/faire-sync/models/faire-sync-account.js +17 -0
  31. package/.medusa/server/src/modules/faire-sync/models/faire-sync-batch.js +18 -0
  32. package/.medusa/server/src/modules/faire-sync/models/faire-sync-record.js +22 -0
  33. package/.medusa/server/src/modules/faire-sync/models/faire-sync-settings.js +26 -0
  34. package/.medusa/server/src/modules/faire-sync/models/faire-webhook-event.js +21 -0
  35. package/.medusa/server/src/modules/faire-sync/service.js +222 -0
  36. package/.medusa/server/src/subscribers/faire-inventory-sync.js +87 -0
  37. package/.medusa/server/src/subscribers/faire-listing-events.js +46 -0
  38. package/.medusa/server/src/subscribers/faire-order-ingest.js +54 -0
  39. package/.medusa/server/src/subscribers/faire-order-lifecycle.js +71 -0
  40. package/.medusa/server/src/subscribers/faire-product-status-sync.js +71 -0
  41. package/.medusa/server/src/workflows/index.js +21 -0
  42. package/.medusa/server/src/workflows/ingest-faire-order-support.js +76 -0
  43. package/.medusa/server/src/workflows/ingest-faire-order.js +148 -0
  44. package/.medusa/server/src/workflows/ingest-faire-orders-bulk.js +124 -0
  45. package/.medusa/server/src/workflows/refresh-faire-token.js +20 -0
  46. package/.medusa/server/src/workflows/sync-product-to-faire.js +262 -0
  47. package/.medusa/server/src/workflows/sync-products-to-faire.js +90 -0
  48. package/LICENSE +21 -0
  49. package/README.md +180 -0
  50. package/package.json +84 -0
  51. package/scripts/postbuild.js +36 -0
  52. package/src/__tests__/ingest-faire-order-support.spec.ts +77 -0
  53. package/src/__tests__/webhook.spec.ts +81 -0
  54. package/src/admin/lib/api.ts +88 -0
  55. package/src/admin/routes/settings/faire/[id]/page.tsx +184 -0
  56. package/src/admin/routes/settings/faire/hooks/use-faire-sync-columns.tsx +80 -0
  57. package/src/admin/routes/settings/faire/page.tsx +445 -0
  58. package/src/admin/routes/settings/faire/settings/page.tsx +286 -0
  59. package/src/admin/routes/settings/oauth/faire/callback/page.tsx +67 -0
  60. package/src/admin/widgets/faire-product-widget.tsx +180 -0
  61. package/src/api/admin/faire/auth/authorize/route.ts +10 -0
  62. package/src/api/admin/faire/auth/callback/route.ts +29 -0
  63. package/src/api/admin/faire/auth/disconnect/route.ts +10 -0
  64. package/src/api/admin/faire/brand/route.ts +12 -0
  65. package/src/api/admin/faire/ingest/orders/[batchId]/route.ts +31 -0
  66. package/src/api/admin/faire/ingest/orders/route.ts +29 -0
  67. package/src/api/admin/faire/products/route.ts +14 -0
  68. package/src/api/admin/faire/settings/route.ts +17 -0
  69. package/src/api/admin/faire/status/product/[id]/route.ts +30 -0
  70. package/src/api/admin/faire/status/route.ts +47 -0
  71. package/src/api/admin/faire/sync/bulk/[batchId]/route.ts +33 -0
  72. package/src/api/admin/faire/sync/bulk/route.ts +27 -0
  73. package/src/api/admin/faire/sync/product/[id]/route.ts +13 -0
  74. package/src/api/admin/faire/syncs/[id]/route.ts +32 -0
  75. package/src/api/admin/faire/syncs/route.ts +17 -0
  76. package/src/api/webhooks/faire/route.ts +133 -0
  77. package/src/jobs/pull-faire-orders.ts +30 -0
  78. package/src/jobs/refresh-faire-token.ts +23 -0
  79. package/src/lib/faire-client.ts +436 -0
  80. package/src/lib/types.ts +117 -0
  81. package/src/lib/webhook.ts +85 -0
  82. package/src/links/product-faire-sync-link.ts +56 -0
  83. package/src/modules/faire-sync/index.ts +11 -0
  84. package/src/modules/faire-sync/migrations/.snapshot-medusa-faire-sync.json +1486 -0
  85. package/src/modules/faire-sync/migrations/Migration20260707211346.ts +43 -0
  86. package/src/modules/faire-sync/models/faire-order.ts +20 -0
  87. package/src/modules/faire-sync/models/faire-sync-account.ts +16 -0
  88. package/src/modules/faire-sync/models/faire-sync-batch.ts +17 -0
  89. package/src/modules/faire-sync/models/faire-sync-record.ts +21 -0
  90. package/src/modules/faire-sync/models/faire-sync-settings.ts +25 -0
  91. package/src/modules/faire-sync/models/faire-webhook-event.ts +20 -0
  92. package/src/modules/faire-sync/service.ts +252 -0
  93. package/src/subscribers/faire-inventory-sync.ts +97 -0
  94. package/src/subscribers/faire-listing-events.ts +48 -0
  95. package/src/subscribers/faire-order-ingest.ts +61 -0
  96. package/src/subscribers/faire-order-lifecycle.ts +81 -0
  97. package/src/subscribers/faire-product-status-sync.ts +76 -0
  98. package/src/workflows/index.ts +4 -0
  99. package/src/workflows/ingest-faire-order-support.ts +96 -0
  100. package/src/workflows/ingest-faire-order.ts +212 -0
  101. package/src/workflows/ingest-faire-orders-bulk.ts +168 -0
  102. package/src/workflows/refresh-faire-token.ts +28 -0
  103. package/src/workflows/sync-product-to-faire.ts +375 -0
  104. package/src/workflows/sync-products-to-faire.ts +133 -0
@@ -0,0 +1,960 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const adminSdk = require("@medusajs/admin-sdk");
4
+ const ui = require("@medusajs/ui");
5
+ const reactQuery = require("@tanstack/react-query");
6
+ const Medusa = require("@medusajs/js-sdk");
7
+ const icons = require("@medusajs/icons");
8
+ const react = require("react");
9
+ const reactRouterDom = require("react-router-dom");
10
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
+ const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
12
+ const sdk = new Medusa__default.default({
13
+ baseUrl: "/",
14
+ auth: {
15
+ type: "session"
16
+ }
17
+ });
18
+ const faireApi = {
19
+ status: () => sdk.client.fetch("/admin/faire/status"),
20
+ getSettings: () => sdk.client.fetch("/admin/faire/settings"),
21
+ saveSettings: (settings) => sdk.client.fetch("/admin/faire/settings", {
22
+ method: "POST",
23
+ body: settings
24
+ }),
25
+ authorize: () => sdk.client.fetch(
26
+ "/admin/faire/auth/authorize"
27
+ ),
28
+ callback: (code, state) => sdk.client.fetch("/admin/faire/auth/callback", {
29
+ method: "POST",
30
+ body: { code, state }
31
+ }),
32
+ disconnect: () => sdk.client.fetch("/admin/faire/auth/disconnect", { method: "POST" }),
33
+ syncProduct: (id) => sdk.client.fetch(`/admin/faire/sync/product/${id}`, { method: "POST" }),
34
+ productStatus: (id) => sdk.client.fetch(`/admin/faire/status/product/${id}`),
35
+ syncBulk: (product_ids) => sdk.client.fetch(
36
+ "/admin/faire/sync/bulk",
37
+ {
38
+ method: "POST",
39
+ body: { product_ids }
40
+ }
41
+ ),
42
+ bulkStatus: (batchId) => sdk.client.fetch(
43
+ `/admin/faire/sync/bulk/${batchId}`
44
+ ),
45
+ ingestOrders: (limit) => sdk.client.fetch(
46
+ "/admin/faire/ingest/orders",
47
+ { method: "POST", body: limit != null ? { limit } : {} }
48
+ ),
49
+ ingestStatus: (batchId) => sdk.client.fetch(
50
+ `/admin/faire/ingest/orders/${batchId}`
51
+ ),
52
+ listSyncs: (opts = {}) => {
53
+ const query = {};
54
+ if (opts.take !== void 0) query.take = String(opts.take);
55
+ if (opts.skip !== void 0) query.skip = String(opts.skip);
56
+ if (opts.status) query.status = opts.status;
57
+ return sdk.client.fetch("/admin/faire/syncs", { query });
58
+ },
59
+ getSync: (id) => sdk.client.fetch(`/admin/faire/syncs/${id}`),
60
+ retrySync: (id) => sdk.client.fetch(`/admin/faire/syncs/${id}`, { method: "POST" }),
61
+ brand: () => sdk.client.fetch("/admin/faire/brand"),
62
+ products: (opts = {}) => {
63
+ const query = {};
64
+ if (opts.limit !== void 0) query.limit = String(opts.limit);
65
+ if (opts.page !== void 0) query.page = String(opts.page);
66
+ return sdk.client.fetch("/admin/faire/products", { query });
67
+ }
68
+ };
69
+ const badgeColor = (status) => {
70
+ switch (status) {
71
+ case "synced":
72
+ case "success":
73
+ return "green";
74
+ case "failed":
75
+ return "red";
76
+ case "draft":
77
+ case "pending":
78
+ return "orange";
79
+ default:
80
+ return "grey";
81
+ }
82
+ };
83
+ const FaireProductWidget = ({ data }) => {
84
+ var _a, _b, _c;
85
+ const queryClient = reactQuery.useQueryClient();
86
+ const productKey = ["faire", "product-status", data.id];
87
+ const statusQuery = reactQuery.useQuery({
88
+ queryKey: ["faire", "status"],
89
+ queryFn: () => faireApi.status()
90
+ });
91
+ const connected = !!((_a = statusQuery.data) == null ? void 0 : _a.connected);
92
+ const readiness = (_b = statusQuery.data) == null ? void 0 : _b.readiness;
93
+ const productStatusQuery = reactQuery.useQuery({
94
+ queryKey: productKey,
95
+ queryFn: () => faireApi.productStatus(data.id),
96
+ refetchInterval: (query) => {
97
+ var _a2, _b2;
98
+ const s = (_b2 = (_a2 = query.state.data) == null ? void 0 : _a2.latest) == null ? void 0 : _b2.status;
99
+ return s === "draft" || s === "pending" ? 1e4 : false;
100
+ }
101
+ });
102
+ const latest = (_c = productStatusQuery.data) == null ? void 0 : _c.latest;
103
+ const syncMutation = reactQuery.useMutation({
104
+ mutationFn: () => faireApi.syncProduct(data.id),
105
+ onSuccess: () => {
106
+ queryClient.invalidateQueries({ queryKey: productKey });
107
+ }
108
+ });
109
+ const result = syncMutation.data ? syncMutation.data.result : null;
110
+ const error = syncMutation.error ? syncMutation.error.message || "Sync failed" : null;
111
+ const view = result ? {
112
+ published: result.published,
113
+ state: result.state,
114
+ product_url: result.product_url,
115
+ product_token: result.product_token,
116
+ warnings: result.warnings,
117
+ status: result.published ? "success" : result.state
118
+ } : latest ? {
119
+ published: latest.published,
120
+ state: latest.product_state,
121
+ product_url: latest.product_url,
122
+ product_token: latest.product_token,
123
+ warnings: latest.error_message ? String(latest.error_message).split(" | ") : void 0,
124
+ status: latest.status
125
+ } : null;
126
+ const notReady = connected && readiness && !readiness.ready_to_publish;
127
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
128
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
129
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
130
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Faire Sync" }),
131
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Sync this product to your Faire brand." })
132
+ ] }),
133
+ view && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: badgeColor(view.status), children: view.published ? "Active" : view.state || view.status })
134
+ ] }),
135
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 px-6 py-4", children: [
136
+ !connected && !statusQuery.isLoading && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "warning", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Faire is not connected. Connect it under Settings → Faire." }) }),
137
+ notReady && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "warning", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Publish readiness incomplete (brand or wholesale pricing config is missing). Syncing will still run and create the product as a draft." }) }),
138
+ error && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "error", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: error }) }),
139
+ view && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: view.published ? "success" : "warning", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
140
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: view.published ? "Published to Faire as active." : `Synced as ${view.state || view.status}.` }),
141
+ view.warnings && view.warnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: view.warnings.join(" | ") }),
142
+ view.product_url && /* @__PURE__ */ jsxRuntime.jsx(
143
+ "a",
144
+ {
145
+ href: view.product_url,
146
+ target: "_blank",
147
+ rel: "noreferrer",
148
+ className: "text-ui-fg-interactive hover:text-ui-fg-interactive-hover",
149
+ children: view.published ? "View on Faire →" : "Open in Faire →"
150
+ }
151
+ )
152
+ ] }) }),
153
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxRuntime.jsx(
154
+ ui.Tooltip,
155
+ {
156
+ content: connected ? void 0 : "Connect Faire under Settings → Faire first.",
157
+ children: /* @__PURE__ */ jsxRuntime.jsx(
158
+ ui.Button,
159
+ {
160
+ size: "small",
161
+ isLoading: syncMutation.isPending,
162
+ disabled: !connected,
163
+ onClick: () => syncMutation.mutate(),
164
+ children: view ? "Re-sync to Faire" : "Sync to Faire"
165
+ }
166
+ )
167
+ }
168
+ ) })
169
+ ] })
170
+ ] });
171
+ };
172
+ adminSdk.defineWidgetConfig({
173
+ zone: "product.details.side.before"
174
+ });
175
+ const columnHelper = ui.createDataTableColumnHelper();
176
+ const useFaireSyncColumns = () => {
177
+ return [
178
+ columnHelper.accessor("product_id", {
179
+ header: "Product",
180
+ cell: (info) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-ui-fg-subtle text-xs", children: info.getValue() })
181
+ }),
182
+ columnHelper.accessor("status", {
183
+ header: "Status",
184
+ cell: (info) => {
185
+ const status = info.getValue();
186
+ const color = status === "success" ? "green" : status === "failed" ? "red" : status === "draft" ? "orange" : "grey";
187
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color, children: status });
188
+ }
189
+ }),
190
+ columnHelper.accessor("product_state", {
191
+ header: "State",
192
+ cell: (info) => info.getValue() || "—"
193
+ }),
194
+ columnHelper.display({
195
+ id: "product",
196
+ header: "Faire product",
197
+ cell: ({ row }) => {
198
+ const r = row.original;
199
+ if (!r.product_url) return "—";
200
+ return /* @__PURE__ */ jsxRuntime.jsx(
201
+ "a",
202
+ {
203
+ href: r.product_url,
204
+ target: "_blank",
205
+ rel: "noreferrer",
206
+ onClick: (e) => e.stopPropagation(),
207
+ className: "text-ui-fg-interactive hover:text-ui-fg-interactive-hover",
208
+ children: r.product_token
209
+ }
210
+ );
211
+ }
212
+ }),
213
+ columnHelper.accessor("synced_at", {
214
+ header: "Synced",
215
+ cell: (info) => {
216
+ const v = info.getValue();
217
+ return v ? new Date(v).toLocaleString() : "—";
218
+ },
219
+ enableSorting: true,
220
+ sortLabel: "Synced",
221
+ sortAscLabel: "Oldest first",
222
+ sortDescLabel: "Newest first"
223
+ })
224
+ ];
225
+ };
226
+ const PAGE_SIZE = 10;
227
+ const STATUS_KEY$1 = ["faire", "status"];
228
+ const FaireSettingsPage = () => {
229
+ var _a, _b, _c;
230
+ const navigate = reactRouterDom.useNavigate();
231
+ const queryClient = reactQuery.useQueryClient();
232
+ const [pagination, setPagination] = react.useState({
233
+ pageIndex: 0,
234
+ pageSize: PAGE_SIZE
235
+ });
236
+ const [filtering, setFiltering] = react.useState({});
237
+ const statusQuery = reactQuery.useQuery({
238
+ queryKey: STATUS_KEY$1,
239
+ queryFn: () => faireApi.status()
240
+ });
241
+ const status = statusQuery.data;
242
+ const connected = !!(status == null ? void 0 : status.connected);
243
+ const syncsQuery = reactQuery.useQuery({
244
+ queryKey: ["faire", "syncs", pagination, filtering],
245
+ placeholderData: reactQuery.keepPreviousData,
246
+ queryFn: async () => {
247
+ var _a2;
248
+ const statusFilter = (_a2 = filtering.status) == null ? void 0 : _a2[0];
249
+ const res = await faireApi.listSyncs({
250
+ take: pagination.pageSize,
251
+ skip: pagination.pageIndex * pagination.pageSize,
252
+ status: statusFilter
253
+ });
254
+ return { syncs: res.syncs || [], count: res.count || 0 };
255
+ }
256
+ });
257
+ const connectMutation = reactQuery.useMutation({
258
+ mutationFn: () => faireApi.authorize(),
259
+ onSuccess: (res) => {
260
+ window.open(res.authorization_url, "_blank");
261
+ ui.toast.info("Faire authorization opened", {
262
+ description: "Complete the authorization in the new tab."
263
+ });
264
+ },
265
+ onError: (err) => ui.toast.error("Failed to start Faire authorization", {
266
+ description: err.message
267
+ })
268
+ });
269
+ const disconnectMutation = reactQuery.useMutation({
270
+ mutationFn: () => faireApi.disconnect(),
271
+ onSuccess: () => {
272
+ queryClient.invalidateQueries({ queryKey: STATUS_KEY$1 });
273
+ ui.toast.success("Disconnected from Faire");
274
+ },
275
+ onError: (err) => ui.toast.error("Failed to disconnect", { description: err.message })
276
+ });
277
+ const columns = useFaireSyncColumns();
278
+ const filterHelper = ui.createDataTableFilterHelper();
279
+ const filters = [
280
+ filterHelper.accessor("status", {
281
+ type: "select",
282
+ label: "Status",
283
+ options: [
284
+ { label: "Success", value: "success" },
285
+ { label: "Draft", value: "draft" },
286
+ { label: "Failed", value: "failed" },
287
+ { label: "Pending", value: "pending" },
288
+ { label: "Syncing", value: "syncing" }
289
+ ]
290
+ })
291
+ ];
292
+ const table = ui.useDataTable({
293
+ data: ((_a = syncsQuery.data) == null ? void 0 : _a.syncs) ?? [],
294
+ columns,
295
+ rowCount: ((_b = syncsQuery.data) == null ? void 0 : _b.count) ?? 0,
296
+ getRowId: (row) => row.id,
297
+ onRowClick: (_, row) => {
298
+ navigate(`/settings/faire/${row.id}`);
299
+ },
300
+ isLoading: syncsQuery.isLoading,
301
+ filters,
302
+ pagination: {
303
+ state: pagination,
304
+ onPaginationChange: setPagination
305
+ },
306
+ filtering: {
307
+ state: filtering,
308
+ onFilteringChange: setFiltering
309
+ }
310
+ });
311
+ if (statusQuery.isLoading) {
312
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
313
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
314
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
315
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-6 w-32" }),
316
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-7 w-28" })
317
+ ] }),
318
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-4 px-6 py-4", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-10 w-full" }, i)) })
319
+ ] }),
320
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-0", children: [
321
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-5 w-32" }) }),
322
+ /* @__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)) })
323
+ ] })
324
+ ] });
325
+ }
326
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
327
+ statusQuery.isError && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "error", children: ((_c = statusQuery.error) == null ? void 0 : _c.message) || "Failed to load Faire status" }),
328
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
329
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
330
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
331
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Faire Sync" }),
332
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Connect your Faire brand and sync Medusa products, inventory and orders bidirectionally." })
333
+ ] }),
334
+ connected ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
335
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: "green", children: "Connected" }),
336
+ /* @__PURE__ */ jsxRuntime.jsx(
337
+ ui.Button,
338
+ {
339
+ size: "small",
340
+ variant: "secondary",
341
+ onClick: () => navigate("/settings/faire/settings"),
342
+ children: "Sync settings"
343
+ }
344
+ ),
345
+ /* @__PURE__ */ jsxRuntime.jsx(
346
+ ui.Button,
347
+ {
348
+ size: "small",
349
+ variant: "danger",
350
+ onClick: () => disconnectMutation.mutate(),
351
+ isLoading: disconnectMutation.isPending,
352
+ children: "Disconnect"
353
+ }
354
+ )
355
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
356
+ ui.Button,
357
+ {
358
+ size: "small",
359
+ onClick: () => connectMutation.mutate(),
360
+ isLoading: connectMutation.isPending,
361
+ children: "Connect Faire"
362
+ }
363
+ )
364
+ ] }),
365
+ connected && (status == null ? void 0 : status.account) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 grid grid-cols-4 gap-4", children: [
366
+ /* @__PURE__ */ jsxRuntime.jsx(InfoField, { label: "Brand", value: status.account.brand_name }),
367
+ /* @__PURE__ */ jsxRuntime.jsx(InfoField, { label: "Brand ID", value: status.account.brand_id }),
368
+ /* @__PURE__ */ jsxRuntime.jsx(InfoField, { label: "Currency", value: status.account.currency || "—" }),
369
+ /* @__PURE__ */ jsxRuntime.jsx(
370
+ InfoField,
371
+ {
372
+ label: "Token expires",
373
+ value: status.account.token_expires_at ? new Date(status.account.token_expires_at).toLocaleString() : "Does not expire"
374
+ }
375
+ )
376
+ ] })
377
+ ] }),
378
+ /* @__PURE__ */ jsxRuntime.jsx(BulkOperations, { connected }),
379
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
380
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable.Toolbar, { className: "flex flex-col md:flex-row justify-between gap-y-4 px-6 py-4", children: [
381
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
382
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: "Recent syncs" }),
383
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Latest product sync attempts. Click a row for details." })
384
+ ] }),
385
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
386
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.FilterMenu, { tooltip: "Filter syncs" }),
387
+ /* @__PURE__ */ jsxRuntime.jsx(
388
+ ui.Button,
389
+ {
390
+ size: "small",
391
+ variant: "secondary",
392
+ onClick: () => syncsQuery.refetch(),
393
+ isLoading: syncsQuery.isFetching,
394
+ children: "Refresh"
395
+ }
396
+ )
397
+ ] })
398
+ ] }),
399
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}),
400
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
401
+ ] }) }),
402
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
403
+ ] });
404
+ };
405
+ const InfoField = ({ label, value }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
406
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
407
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: value })
408
+ ] });
409
+ const BulkOperations = ({ connected }) => {
410
+ var _a, _b, _c, _d, _e, _f;
411
+ const [productIds, setProductIds] = react.useState("");
412
+ const [pushBatch, setPushBatch] = react.useState(null);
413
+ const [pullBatch, setPullBatch] = react.useState(null);
414
+ const pushMutation = reactQuery.useMutation({
415
+ mutationFn: (ids) => faireApi.syncBulk(ids),
416
+ onSuccess: (res) => {
417
+ setPushBatch(res.batch_id);
418
+ ui.toast.success("Bulk product sync started", {
419
+ description: "Running in the background. Polling progress…"
420
+ });
421
+ },
422
+ onError: (err) => ui.toast.error("Failed to start bulk sync", { description: err.message })
423
+ });
424
+ const pullMutation = reactQuery.useMutation({
425
+ mutationFn: () => faireApi.ingestOrders(),
426
+ onSuccess: (res) => {
427
+ setPullBatch(res.batch_id);
428
+ ui.toast.success("Faire order pull started", {
429
+ description: "Running in the background. Polling progress…"
430
+ });
431
+ },
432
+ onError: (err) => ui.toast.error("Failed to start order pull", { description: err.message })
433
+ });
434
+ const pushStatus = reactQuery.useQuery({
435
+ queryKey: ["faire", "bulk", pushBatch],
436
+ enabled: !!pushBatch,
437
+ refetchInterval: (q) => {
438
+ var _a2, _b2;
439
+ return ((_b2 = (_a2 = q.state.data) == null ? void 0 : _a2.progress) == null ? void 0 : _b2.finished) ? false : 3e3;
440
+ },
441
+ queryFn: () => faireApi.bulkStatus(pushBatch)
442
+ });
443
+ const pullStatus = reactQuery.useQuery({
444
+ queryKey: ["faire", "ingest", pullBatch],
445
+ enabled: !!pullBatch,
446
+ refetchInterval: (q) => {
447
+ var _a2, _b2;
448
+ return ((_b2 = (_a2 = q.state.data) == null ? void 0 : _a2.progress) == null ? void 0 : _b2.finished) ? false : 3e3;
449
+ },
450
+ queryFn: () => faireApi.ingestStatus(pullBatch)
451
+ });
452
+ const pushProgress = (_a = pushStatus.data) == null ? void 0 : _a.progress;
453
+ const pullProgress = (_b = pullStatus.data) == null ? void 0 : _b.progress;
454
+ const handlePush = () => {
455
+ const ids = productIds.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean);
456
+ if (!ids.length) {
457
+ ui.toast.error("Enter at least one product id");
458
+ return;
459
+ }
460
+ pushMutation.mutate(ids);
461
+ };
462
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
463
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
464
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: "Bulk operations" }),
465
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Push many products to Faire, or pull wholesale orders from Faire into Medusa. Both run as long-running background workflows." })
466
+ ] }),
467
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-6", children: [
468
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
469
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
470
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Push products to Faire" }),
471
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Comma- or line-separated Medusa product IDs." })
472
+ ] }),
473
+ /* @__PURE__ */ jsxRuntime.jsx(
474
+ "textarea",
475
+ {
476
+ className: "border-ui-border-base rounded-lg border px-3 py-2 text-sm min-h-[72px]",
477
+ placeholder: "prod_01..., prod_02...",
478
+ value: productIds,
479
+ onChange: (e) => setProductIds(e.target.value),
480
+ disabled: !connected || pushMutation.isPending
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
484
+ /* @__PURE__ */ jsxRuntime.jsx(
485
+ ui.Button,
486
+ {
487
+ size: "small",
488
+ onClick: handlePush,
489
+ disabled: !connected || pushMutation.isPending,
490
+ isLoading: pushMutation.isPending,
491
+ children: "Start bulk push"
492
+ }
493
+ ),
494
+ pushBatch && pushProgress && /* @__PURE__ */ jsxRuntime.jsx(
495
+ BulkProgress,
496
+ {
497
+ label: "Push",
498
+ progress: pushProgress,
499
+ status: (_d = (_c = pushStatus.data) == null ? void 0 : _c.batch) == null ? void 0 : _d.status
500
+ }
501
+ )
502
+ ] })
503
+ ] }),
504
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
505
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
506
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Pull Faire orders into Medusa" }),
507
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Ingests all available Faire orders as Medusa orders (idempotent)." })
508
+ ] }),
509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
510
+ /* @__PURE__ */ jsxRuntime.jsx(
511
+ ui.Button,
512
+ {
513
+ size: "small",
514
+ variant: "secondary",
515
+ onClick: () => pullMutation.mutate(),
516
+ disabled: !connected || pullMutation.isPending,
517
+ isLoading: pullMutation.isPending,
518
+ children: "Start order pull"
519
+ }
520
+ ),
521
+ pullBatch && pullProgress && /* @__PURE__ */ jsxRuntime.jsx(
522
+ BulkProgress,
523
+ {
524
+ label: "Pull",
525
+ progress: pullProgress,
526
+ status: (_f = (_e = pullStatus.data) == null ? void 0 : _e.batch) == null ? void 0 : _f.status
527
+ }
528
+ )
529
+ ] })
530
+ ] })
531
+ ] })
532
+ ] });
533
+ };
534
+ const BulkProgress = ({
535
+ label,
536
+ progress,
537
+ status
538
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
539
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.StatusBadge, { color: progress.finished ? status === "failed" ? "red" : "green" : "orange", children: [
540
+ label,
541
+ ": ",
542
+ progress.done,
543
+ "/",
544
+ progress.total || "?",
545
+ " (",
546
+ progress.pct,
547
+ "%)"
548
+ ] }),
549
+ progress.finished && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: status === "failed" ? "Completed with errors" : "Done" })
550
+ ] });
551
+ const config = adminSdk.defineRouteConfig({
552
+ label: "Faire",
553
+ icon: icons.BuildingStorefront
554
+ });
555
+ const handle$2 = {
556
+ breadcrumb: () => "Faire Sync"
557
+ };
558
+ const STATUS_COLORS = {
559
+ success: "green",
560
+ failed: "red",
561
+ draft: "orange",
562
+ pending: "grey",
563
+ syncing: "grey"
564
+ };
565
+ const FaireSyncDetailPage = () => {
566
+ const { id } = reactRouterDom.useParams();
567
+ const navigate = reactRouterDom.useNavigate();
568
+ const [record, setRecord] = react.useState(null);
569
+ const [loading, setLoading] = react.useState(true);
570
+ const [retrying, setRetrying] = react.useState(false);
571
+ const [error, setError] = react.useState(null);
572
+ react.useEffect(() => {
573
+ if (!id) return;
574
+ setLoading(true);
575
+ faireApi.getSync(id).then((res) => {
576
+ setRecord(res.sync || res);
577
+ }).catch((err) => setError(err.message || "Failed to load sync record")).finally(() => setLoading(false));
578
+ }, [id]);
579
+ const handleRetry = async () => {
580
+ if (!record) return;
581
+ setRetrying(true);
582
+ setError(null);
583
+ try {
584
+ await faireApi.retrySync(record.id);
585
+ const updated = await faireApi.getSync(record.id).catch(() => null);
586
+ if (updated) setRecord(updated.sync || updated);
587
+ } catch (err) {
588
+ setError(err.message || "Retry failed");
589
+ } finally {
590
+ setRetrying(false);
591
+ }
592
+ };
593
+ if (loading) {
594
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-7 w-12 rounded-md" }) });
595
+ }
596
+ if (!record) {
597
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6 flex flex-col gap-4", children: [
598
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Sync record not found" }),
599
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => navigate("/settings/faire"), children: "Back to Faire settings" })
600
+ ] });
601
+ }
602
+ const warnings = record.error_message ? record.error_message.split(" | ") : [];
603
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
604
+ error && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-tag-red-text", children: error }) }),
605
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
606
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
607
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
608
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Sync record" }),
609
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle font-mono text-xs", children: record.id })
610
+ ] }),
611
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
612
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: STATUS_COLORS[record.status] || "grey", children: record.status }),
613
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
614
+ /* @__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, {}) }) }),
615
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { align: "end", children: [
616
+ /* @__PURE__ */ jsxRuntime.jsxs(
617
+ ui.DropdownMenu.Item,
618
+ {
619
+ className: "gap-x-2",
620
+ onClick: handleRetry,
621
+ disabled: retrying,
622
+ children: [
623
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowPath, { className: "text-ui-fg-subtle" }),
624
+ retrying ? "Re-syncing…" : "Re-sync product"
625
+ ]
626
+ }
627
+ ),
628
+ record.product_url && /* @__PURE__ */ jsxRuntime.jsxs(
629
+ ui.DropdownMenu.Item,
630
+ {
631
+ className: "gap-x-2",
632
+ onClick: () => window.open(record.product_url, "_blank"),
633
+ children: [
634
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, { className: "text-ui-fg-subtle" }),
635
+ "Open on Faire"
636
+ ]
637
+ }
638
+ )
639
+ ] })
640
+ ] })
641
+ ] })
642
+ ] }),
643
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 grid grid-cols-2 gap-4", children: [
644
+ /* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Product", value: record.product_id, mono: true }),
645
+ /* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Action", value: record.action }),
646
+ /* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Faire product token", value: record.product_token || "—", mono: true }),
647
+ /* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "State", value: record.product_state || "—" }),
648
+ /* @__PURE__ */ jsxRuntime.jsx(Detail, { label: "Published", value: record.published ? "Yes" : "No" }),
649
+ /* @__PURE__ */ jsxRuntime.jsx(
650
+ Detail,
651
+ {
652
+ label: "Synced at",
653
+ value: record.synced_at ? new Date(record.synced_at).toLocaleString() : "—"
654
+ }
655
+ )
656
+ ] })
657
+ ] }),
658
+ warnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
659
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Issues" }) }),
660
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4 flex flex-col gap-2", children: warnings.map((w, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-tag-red-text text-sm", children: w }, i)) })
661
+ ] }),
662
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
663
+ ] });
664
+ };
665
+ const Detail = ({
666
+ label,
667
+ value,
668
+ mono
669
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
670
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
671
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: mono ? "font-mono text-xs" : "", children: value })
672
+ ] });
673
+ const handle$1 = {
674
+ breadcrumb: () => "Sync record"
675
+ };
676
+ const STATUS_KEY = ["faire", "status"];
677
+ const PARENT = "/settings/faire";
678
+ const NONE = "__none__";
679
+ const cleanSettings = (form) => {
680
+ const out = { ...form };
681
+ for (const key of [
682
+ "default_brand_id",
683
+ "default_shipping_policy_id",
684
+ "default_category"
685
+ ]) {
686
+ if (!out[key] || out[key] === NONE) out[key] = null;
687
+ }
688
+ if (out.default_wholesale_markup_percent === "" || out.default_wholesale_markup_percent === NONE) {
689
+ out.default_wholesale_markup_percent = null;
690
+ }
691
+ if (out.default_lead_time_days === "" || out.default_lead_time_days === NONE) {
692
+ out.default_lead_time_days = null;
693
+ }
694
+ return out;
695
+ };
696
+ const FaireSyncSettingsDrawer = () => {
697
+ const navigate = reactRouterDom.useNavigate();
698
+ const queryClient = reactQuery.useQueryClient();
699
+ const [open, setOpen] = react.useState(true);
700
+ const [form, setForm] = react.useState({});
701
+ const close = () => {
702
+ setOpen(false);
703
+ navigate(PARENT);
704
+ };
705
+ const statusQuery = reactQuery.useQuery({
706
+ queryKey: STATUS_KEY,
707
+ queryFn: () => faireApi.status()
708
+ });
709
+ const status = statusQuery.data;
710
+ const connected = !!(status == null ? void 0 : status.connected);
711
+ react.useEffect(() => {
712
+ if (status == null ? void 0 : status.settings) setForm(status.settings);
713
+ }, [status == null ? void 0 : status.settings]);
714
+ const saveMutation = reactQuery.useMutation({
715
+ mutationFn: (payload) => faireApi.saveSettings(payload),
716
+ onSuccess: () => {
717
+ queryClient.invalidateQueries({ queryKey: STATUS_KEY });
718
+ ui.toast.success("Sync settings saved");
719
+ close();
720
+ },
721
+ onError: (err) => ui.toast.error("Failed to save settings", { description: err.message })
722
+ });
723
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer, { open, onOpenChange: (o) => o ? setOpen(true) : close(), children: [
724
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
725
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Header, { children: [
726
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Sync settings" }),
727
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Description, { children: "Publish readiness and the defaults applied to every product pushed to Faire." })
728
+ ] }),
729
+ /* @__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: [
730
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
731
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
732
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Publish readiness" }),
733
+ /* @__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." })
734
+ ] }),
735
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
736
+ /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.connected, label: "Faire connected" }),
737
+ /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.brand, label: "Brand configured" }),
738
+ /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.wholesale_pricing, label: "Wholesale pricing" }),
739
+ /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.shipping_policy, label: "Shipping policy" })
740
+ ] })
741
+ ] }),
742
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
743
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
744
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Sync defaults" }),
745
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Applied to every product unless overridden in product metadata." })
746
+ ] }),
747
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4", children: [
748
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Brand ID", children: /* @__PURE__ */ jsxRuntime.jsx(
749
+ "input",
750
+ {
751
+ className: "border-ui-border-base rounded-lg border px-3 py-2 text-sm",
752
+ value: String(form.default_brand_id ?? ""),
753
+ onChange: (e) => setForm({ ...form, default_brand_id: e.target.value || null }),
754
+ disabled: !connected,
755
+ placeholder: "b_..."
756
+ }
757
+ ) }),
758
+ /* @__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(
759
+ "input",
760
+ {
761
+ type: "number",
762
+ className: "border-ui-border-base rounded-lg border px-3 py-2 text-sm",
763
+ value: String(form.default_wholesale_markup_percent ?? ""),
764
+ onChange: (e) => setForm({
765
+ ...form,
766
+ default_wholesale_markup_percent: e.target.value ? Number(e.target.value) : null
767
+ }),
768
+ disabled: !connected,
769
+ placeholder: "e.g. 50"
770
+ }
771
+ ) }) }),
772
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default min order quantity", children: /* @__PURE__ */ jsxRuntime.jsx(
773
+ "input",
774
+ {
775
+ type: "number",
776
+ className: "border-ui-border-base rounded-lg border px-3 py-2 text-sm",
777
+ value: String(form.default_min_order_quantity ?? 1),
778
+ onChange: (e) => setForm({
779
+ ...form,
780
+ default_min_order_quantity: e.target.value ? Number(e.target.value) : 1
781
+ }),
782
+ disabled: !connected
783
+ }
784
+ ) }),
785
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default lead time (days)", children: /* @__PURE__ */ jsxRuntime.jsx(
786
+ "input",
787
+ {
788
+ type: "number",
789
+ className: "border-ui-border-base rounded-lg border px-3 py-2 text-sm",
790
+ value: String(form.default_lead_time_days ?? ""),
791
+ onChange: (e) => setForm({
792
+ ...form,
793
+ default_lead_time_days: e.target.value ? Number(e.target.value) : null
794
+ }),
795
+ disabled: !connected,
796
+ placeholder: "e.g. 14"
797
+ }
798
+ ) }),
799
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default category", children: /* @__PURE__ */ jsxRuntime.jsx(
800
+ "input",
801
+ {
802
+ className: "border-ui-border-base rounded-lg border px-3 py-2 text-sm",
803
+ value: String(form.default_category ?? ""),
804
+ onChange: (e) => setForm({ ...form, default_category: e.target.value || null }),
805
+ disabled: !connected,
806
+ placeholder: "e.g. Home Decor"
807
+ }
808
+ ) }),
809
+ /* @__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(
810
+ ui.Switch,
811
+ {
812
+ checked: form.follow_product_status !== false,
813
+ onCheckedChange: (v) => setForm({ ...form, follow_product_status: v })
814
+ }
815
+ ) }) }),
816
+ /* @__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(
817
+ ui.Switch,
818
+ {
819
+ checked: !!form.auto_publish,
820
+ onCheckedChange: (v) => setForm({ ...form, auto_publish: v })
821
+ }
822
+ ) }) })
823
+ ] })
824
+ ] })
825
+ ] }) }),
826
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
827
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: close, children: "Cancel" }),
828
+ /* @__PURE__ */ jsxRuntime.jsx(
829
+ ui.Button,
830
+ {
831
+ onClick: () => saveMutation.mutate(cleanSettings(form)),
832
+ isLoading: saveMutation.isPending,
833
+ disabled: !connected,
834
+ children: "Save settings"
835
+ }
836
+ )
837
+ ] })
838
+ ] }),
839
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
840
+ ] });
841
+ };
842
+ const Field = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
843
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
844
+ children
845
+ ] });
846
+ const ChecklistItem = ({ ok, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
847
+ "div",
848
+ {
849
+ className: ui.clx(
850
+ "flex items-center gap-2 rounded-lg border px-3 py-2",
851
+ ok ? "border-ui-tag-green-border bg-ui-tag-green-bg" : "border-ui-tag-red-border bg-ui-tag-red-bg"
852
+ ),
853
+ children: [
854
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: ok ? "green" : "red", children: ok ? "Ready" : "Missing" }),
855
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: label })
856
+ ]
857
+ }
858
+ );
859
+ const handle = {
860
+ breadcrumb: () => "Sync settings"
861
+ };
862
+ const FaireOauthCallback = () => {
863
+ const [params] = reactRouterDom.useSearchParams();
864
+ const navigate = reactRouterDom.useNavigate();
865
+ const [error, setError] = react.useState(null);
866
+ const exchangedRef = react.useRef(false);
867
+ react.useEffect(() => {
868
+ if (exchangedRef.current) return;
869
+ exchangedRef.current = true;
870
+ const code = params.get("code");
871
+ const state = params.get("state");
872
+ const errParam = params.get("error");
873
+ if (errParam) {
874
+ setError(
875
+ `Faire authorization failed: ${params.get("error_description") || errParam}`
876
+ );
877
+ return;
878
+ }
879
+ if (!code || !state) {
880
+ setError("Missing code or state in Faire callback.");
881
+ return;
882
+ }
883
+ faireApi.callback(code, state).then(() => {
884
+ navigate("/settings/faire", { replace: true });
885
+ }).catch((err) => {
886
+ setError(err.message || "Failed to complete Faire authorization.");
887
+ });
888
+ }, [params, navigate]);
889
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col items-center gap-4 p-8", children: [
890
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Connecting Faire…" }),
891
+ error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "error", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: error }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Completing authorization, please wait…" }),
892
+ error && /* @__PURE__ */ jsxRuntime.jsx(
893
+ ui.Button,
894
+ {
895
+ size: "small",
896
+ variant: "secondary",
897
+ onClick: () => navigate("/settings/faire", { replace: true }),
898
+ children: "Back to Faire settings"
899
+ }
900
+ )
901
+ ] });
902
+ };
903
+ const widgetModule = { widgets: [
904
+ {
905
+ Component: FaireProductWidget,
906
+ zone: ["product.details.side.before"],
907
+ widgetId: "Widget-50e7"
908
+ }
909
+ ] };
910
+ const routeModule = {
911
+ routes: [
912
+ {
913
+ Component: FaireSettingsPage,
914
+ path: "/settings/faire",
915
+ handle: handle$2
916
+ },
917
+ {
918
+ Component: FaireSyncDetailPage,
919
+ path: "/settings/faire/:id",
920
+ handle: handle$1
921
+ },
922
+ {
923
+ Component: FaireSyncSettingsDrawer,
924
+ path: "/settings/faire/settings",
925
+ handle
926
+ },
927
+ {
928
+ Component: FaireOauthCallback,
929
+ path: "/settings/oauth/faire/callback"
930
+ }
931
+ ]
932
+ };
933
+ const menuItemModule = {
934
+ menuItems: [
935
+ {
936
+ label: config.label,
937
+ icon: config.icon,
938
+ path: "/settings/faire",
939
+ nested: void 0,
940
+ rank: void 0,
941
+ translationNs: void 0
942
+ }
943
+ ]
944
+ };
945
+ const formModule = { customFields: {} };
946
+ const displayModule = {
947
+ displays: {}
948
+ };
949
+ const i18nModule = { resources: {} };
950
+ const layoutModule = { layouts: [] };
951
+ const plugin = {
952
+ widgetModule,
953
+ routeModule,
954
+ menuItemModule,
955
+ formModule,
956
+ displayModule,
957
+ i18nModule,
958
+ layoutModule
959
+ };
960
+ module.exports = plugin;