@jytextiles/medusa-plugin-faire-store-sync 0.2.8 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,9 +63,14 @@ const faireApi = {
63
63
  getSync: (id) => sdk.client.fetch(`/admin/faire/syncs/${id}`),
64
64
  retrySync: (id) => sdk.client.fetch(`/admin/faire/syncs/${id}`, { method: "POST" }),
65
65
  brand: () => sdk.client.fetch("/admin/faire/brand"),
66
- taxonomy: () => sdk.client.fetch(
67
- "/admin/faire/taxonomy"
68
- ),
66
+ taxonomy: (opts = {}) => {
67
+ var _a;
68
+ const query = {};
69
+ if (opts.q) query.q = opts.q;
70
+ if (opts.limit !== void 0) query.limit = String(opts.limit);
71
+ if ((_a = opts.ids) == null ? void 0 : _a.length) query.ids = opts.ids.join(",");
72
+ return sdk.client.fetch("/admin/faire/taxonomy", { query });
73
+ },
69
74
  products: (opts = {}) => {
70
75
  const query = {};
71
76
  if (opts.limit !== void 0) query.limit = String(opts.limit);
@@ -74,7 +79,7 @@ const faireApi = {
74
79
  return sdk.client.fetch("/admin/faire/products", { query });
75
80
  }
76
81
  };
77
- const MAX_RENDERED = 60;
82
+ const PICKER_LIMIT = 50;
78
83
  const badgeColor = (status) => {
79
84
  switch (status) {
80
85
  case "synced":
@@ -90,7 +95,7 @@ const badgeColor = (status) => {
90
95
  }
91
96
  };
92
97
  const FaireProductWidget = ({ data }) => {
93
- var _a, _b, _c, _d;
98
+ var _a, _b, _c, _d, _e, _f, _g, _h;
94
99
  const queryClient = reactQuery.useQueryClient();
95
100
  const productKey = ["faire", "product-status", data.id];
96
101
  const statusQuery = reactQuery.useQuery({
@@ -137,38 +142,46 @@ const FaireProductWidget = ({ data }) => {
137
142
  const productType = (_d = data.type) == null ? void 0 : _d.value;
138
143
  const [pickerOpen, setPickerOpen] = react.useState(false);
139
144
  const [pickerSearch, setPickerSearch] = react.useState("");
140
- const [pinnedOverride, setPinnedOverride] = react.useState(
141
- void 0
142
- );
145
+ const [debouncedSearch, setDebouncedSearch] = react.useState("");
146
+ react.useEffect(() => {
147
+ const t = setTimeout(() => setDebouncedSearch(pickerSearch.trim()), 250);
148
+ return () => clearTimeout(t);
149
+ }, [pickerSearch]);
150
+ const [pinnedOverride, setPinnedOverride] = react.useState(void 0);
143
151
  const taxonomyQuery = reactQuery.useQuery({
144
- queryKey: ["faire", "taxonomy"],
152
+ queryKey: ["faire", "taxonomy", "search", debouncedSearch],
145
153
  enabled: pickerOpen,
146
- queryFn: async () => (await faireApi.taxonomy()).taxonomy ?? []
154
+ placeholderData: reactQuery.keepPreviousData,
155
+ queryFn: async () => faireApi.taxonomy({
156
+ q: debouncedSearch || void 0,
157
+ limit: PICKER_LIMIT
158
+ })
147
159
  });
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]);
160
+ const results = ((_e = taxonomyQuery.data) == null ? void 0 : _e.taxonomy) ?? [];
161
+ const matchCount = ((_f = taxonomyQuery.data) == null ? void 0 : _f.count) ?? 0;
162
+ const pinnedId = pinnedOverride !== void 0 ? pinnedOverride.id ?? void 0 : metadata.faire_taxonomy_type_id;
163
+ const pinnedNameFromMeta = pinnedOverride !== void 0 ? pinnedOverride.name ?? void 0 : metadata.faire_taxonomy_type_name;
164
+ const pinnedResolveQuery = reactQuery.useQuery({
165
+ queryKey: ["faire", "taxonomy", "resolve", pinnedId],
166
+ enabled: !!pinnedId && !pinnedNameFromMeta && /^tt_/.test(String(pinnedId)),
167
+ queryFn: async () => (await faireApi.taxonomy({ ids: [pinnedId] })).taxonomy
168
+ });
169
+ const pinnedName = pinnedNameFromMeta ?? (pinnedId && /^tt_/.test(String(pinnedId)) ? (_h = (_g = pinnedResolveQuery.data) == null ? void 0 : _g[0]) == null ? void 0 : _h.name : pinnedId ? String(pinnedId) : void 0);
156
170
  const resolvedSource = pinnedId ? { label: pinnedName || String(pinnedId), from: "pinned" } : metadata.faire_category ? { label: String(metadata.faire_category), from: "metadata" } : productType ? { label: productType, from: "type" } : { label: "Account fallback", from: "fallback" };
157
- const filteredTaxonomy = react.useMemo(() => {
158
- const q = pickerSearch.trim().toLowerCase();
159
- const list = q ? taxonomy.filter((t) => t.name.toLowerCase().includes(q)) : taxonomy;
160
- return list.slice(0, MAX_RENDERED);
161
- }, [taxonomy, pickerSearch]);
162
171
  const saveCategory = reactQuery.useMutation({
163
- mutationFn: (value) => sdk.admin.product.update(data.id, {
164
- metadata: { ...metadata, faire_taxonomy_type_id: value }
172
+ mutationFn: (choice) => sdk.admin.product.update(data.id, {
173
+ metadata: {
174
+ ...metadata,
175
+ faire_taxonomy_type_id: (choice == null ? void 0 : choice.id) ?? null,
176
+ faire_taxonomy_type_name: (choice == null ? void 0 : choice.name) ?? null
177
+ }
165
178
  }),
166
- onSuccess: (_res, value) => {
167
- setPinnedOverride(value);
179
+ onSuccess: (_res, choice) => {
180
+ setPinnedOverride({ id: (choice == null ? void 0 : choice.id) ?? null, name: (choice == null ? void 0 : choice.name) ?? null });
168
181
  queryClient.invalidateQueries({ queryKey: ["product", data.id] });
169
182
  setPickerOpen(false);
170
183
  setPickerSearch("");
171
- ui.toast.success(value ? "Faire category set" : "Faire category cleared");
184
+ ui.toast.success(choice ? "Faire category set" : "Faire category cleared");
172
185
  },
173
186
  onError: (err) => ui.toast.error("Failed to save category", { description: err == null ? void 0 : err.message })
174
187
  });
@@ -251,15 +264,11 @@ const FaireProductWidget = ({ data }) => {
251
264
  }
252
265
  ),
253
266
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 overflow-y-auto", children: [
254
- taxonomyQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle px-1 py-2", children: "Loading categories…" }) : 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(
267
+ taxonomyQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle px-1 py-2", children: "Loading categories…" }) : results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle px-1 py-2", children: debouncedSearch ? `No categories match “${debouncedSearch}”.` : "No categories available." }) : results.map((t) => /* @__PURE__ */ jsxRuntime.jsxs(
259
268
  "button",
260
269
  {
261
270
  type: "button",
262
- onClick: () => saveCategory.mutate(t.id),
271
+ onClick: () => saveCategory.mutate(t),
263
272
  disabled: saveCategory.isPending,
264
273
  className: ui.clx(
265
274
  "flex items-center justify-between rounded-md px-3 py-2 text-left text-sm",
@@ -273,12 +282,12 @@ const FaireProductWidget = ({ data }) => {
273
282
  },
274
283
  t.id
275
284
  )),
276
- !taxonomyQuery.isLoading && taxonomy.length > filteredTaxonomy.length && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", className: "text-ui-fg-muted px-1 py-1", children: [
285
+ !taxonomyQuery.isLoading && matchCount > results.length && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", className: "text-ui-fg-muted px-1 py-1", children: [
277
286
  "Showing ",
278
- filteredTaxonomy.length,
287
+ results.length,
279
288
  " of ",
280
- taxonomy.length,
281
- " — refine your search to narrow down."
289
+ matchCount,
290
+ " matches — refine your search to narrow down."
282
291
  ] })
283
292
  ] })
284
293
  ] }),
@@ -476,24 +485,28 @@ const FaireSettingsPage = () => {
476
485
  ] }),
477
486
  connected ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
478
487
  /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: "green", children: "Connected" }),
479
- /* @__PURE__ */ jsxRuntime.jsx(
480
- ui.Button,
481
- {
482
- size: "small",
483
- variant: "secondary",
484
- onClick: () => navigate("/settings/faire/bulk"),
485
- children: "Bulk sync"
486
- }
487
- ),
488
- /* @__PURE__ */ jsxRuntime.jsx(
489
- ui.Button,
490
- {
491
- size: "small",
492
- variant: "secondary",
493
- onClick: () => navigate("/settings/faire/settings"),
494
- children: "Sync settings"
495
- }
496
- ),
488
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
489
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "small", variant: "secondary", children: [
490
+ "Sync",
491
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ChevronDownMini, {})
492
+ ] }) }),
493
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { children: [
494
+ /* @__PURE__ */ jsxRuntime.jsx(
495
+ ui.DropdownMenu.Item,
496
+ {
497
+ onClick: () => navigate("/settings/faire/bulk"),
498
+ children: "Bulk sync"
499
+ }
500
+ ),
501
+ /* @__PURE__ */ jsxRuntime.jsx(
502
+ ui.DropdownMenu.Item,
503
+ {
504
+ onClick: () => navigate("/settings/faire/settings"),
505
+ children: "Sync settings"
506
+ }
507
+ )
508
+ ] })
509
+ ] }),
497
510
  /* @__PURE__ */ jsxRuntime.jsx(
498
511
  ui.Button,
499
512
  {
@@ -606,6 +619,213 @@ const config = adminSdk.defineRouteConfig({
606
619
  const handle$3 = {
607
620
  breadcrumb: () => "Faire Sync"
608
621
  };
622
+ const STATUS_KEY = ["faire", "status"];
623
+ const TAXONOMY_KEY = ["faire", "taxonomy"];
624
+ const PARENT$1 = "/settings/faire";
625
+ const NONE = "__none__";
626
+ const cleanSettings = (form) => {
627
+ const out = { ...form };
628
+ for (const key of [
629
+ "default_brand_id",
630
+ "default_category"
631
+ ]) {
632
+ if (!out[key] || out[key] === NONE) out[key] = null;
633
+ }
634
+ if (out.default_wholesale_markup_percent === "" || out.default_wholesale_markup_percent === NONE) {
635
+ out.default_wholesale_markup_percent = null;
636
+ }
637
+ if (out.default_lead_time_days === "" || out.default_lead_time_days === NONE) {
638
+ out.default_lead_time_days = null;
639
+ }
640
+ return out;
641
+ };
642
+ const FaireSyncSettingsDrawer = () => {
643
+ const navigate = reactRouterDom.useNavigate();
644
+ const queryClient = reactQuery.useQueryClient();
645
+ const [open, setOpen] = react.useState(true);
646
+ const [form, setForm] = react.useState({});
647
+ const close = () => {
648
+ setOpen(false);
649
+ navigate(PARENT$1);
650
+ };
651
+ const statusQuery = reactQuery.useQuery({
652
+ queryKey: STATUS_KEY,
653
+ queryFn: () => faireApi.status()
654
+ });
655
+ const status = statusQuery.data;
656
+ const connected = !!(status == null ? void 0 : status.connected);
657
+ const taxonomyQuery = reactQuery.useQuery({
658
+ queryKey: TAXONOMY_KEY,
659
+ queryFn: async () => (await faireApi.taxonomy().catch(() => ({ taxonomy: [] }))).taxonomy || []
660
+ });
661
+ const taxonomy = taxonomyQuery.data ?? [];
662
+ react.useEffect(() => {
663
+ if (status == null ? void 0 : status.settings) setForm(status.settings);
664
+ }, [status == null ? void 0 : status.settings]);
665
+ const saveMutation = reactQuery.useMutation({
666
+ mutationFn: (payload) => faireApi.saveSettings(payload),
667
+ onSuccess: () => {
668
+ queryClient.invalidateQueries({ queryKey: STATUS_KEY });
669
+ ui.toast.success("Sync settings saved");
670
+ close();
671
+ },
672
+ onError: (err) => ui.toast.error("Failed to save settings", { description: err.message })
673
+ });
674
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer, { open, onOpenChange: (o) => o ? setOpen(true) : close(), children: [
675
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
676
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Header, { children: [
677
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Sync settings" }),
678
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Description, { children: "Publish readiness and the defaults applied to every product pushed to Faire." })
679
+ ] }),
680
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-col gap-6 overflow-y-auto", children: statusQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-10 w-full" }, i)) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
681
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
682
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
683
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Publish readiness" }),
684
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Faire needs a brand and a wholesale-pricing strategy to publish products. Missing fields sync products as drafts." })
685
+ ] }),
686
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
687
+ /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.connected, label: "Faire connected" }),
688
+ /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.brand, label: "Brand configured" }),
689
+ /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.wholesale_pricing, label: "Wholesale pricing" }),
690
+ /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.taxonomy, label: "Default category" })
691
+ ] })
692
+ ] }),
693
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
694
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
695
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Sync defaults" }),
696
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Applied to every product unless overridden in product metadata." })
697
+ ] }),
698
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4", children: [
699
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Brand ID", children: /* @__PURE__ */ jsxRuntime.jsx(
700
+ ui.Input,
701
+ {
702
+ value: String(form.default_brand_id ?? ""),
703
+ onChange: (e) => setForm({ ...form, default_brand_id: e.target.value || null }),
704
+ disabled: !connected,
705
+ placeholder: "b_..."
706
+ }
707
+ ) }),
708
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Wholesale markup % (off retail)", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Wholesale price = retail × (100 − markup%) / 100. E.g. 50 → half of retail.", children: /* @__PURE__ */ jsxRuntime.jsx(
709
+ ui.Input,
710
+ {
711
+ type: "number",
712
+ value: String(form.default_wholesale_markup_percent ?? ""),
713
+ onChange: (e) => setForm({
714
+ ...form,
715
+ default_wholesale_markup_percent: e.target.value ? Number(e.target.value) : null
716
+ }),
717
+ disabled: !connected,
718
+ placeholder: "e.g. 50"
719
+ }
720
+ ) }) }),
721
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default min order quantity", children: /* @__PURE__ */ jsxRuntime.jsx(
722
+ ui.Input,
723
+ {
724
+ type: "number",
725
+ value: String(form.default_min_order_quantity ?? 1),
726
+ onChange: (e) => setForm({
727
+ ...form,
728
+ default_min_order_quantity: e.target.value ? Number(e.target.value) : 1
729
+ }),
730
+ disabled: !connected
731
+ }
732
+ ) }),
733
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default lead time (days)", children: /* @__PURE__ */ jsxRuntime.jsx(
734
+ ui.Input,
735
+ {
736
+ type: "number",
737
+ value: String(form.default_lead_time_days ?? ""),
738
+ onChange: (e) => setForm({
739
+ ...form,
740
+ default_lead_time_days: e.target.value ? Number(e.target.value) : null
741
+ }),
742
+ disabled: !connected,
743
+ placeholder: "e.g. 14"
744
+ }
745
+ ) }),
746
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Fallback category (taxonomy)", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Faire requires a category per product. Resolution order: product metadata → the product's Type (matched by name) → this fallback. Set the product Type or pick a category on the product page for accuracy.", children: /* @__PURE__ */ jsxRuntime.jsx(
747
+ SelectField,
748
+ {
749
+ value: String(form.default_category ?? ""),
750
+ onValueChange: (v) => setForm({ ...form, default_category: v || null }),
751
+ disabled: !connected,
752
+ options: taxonomy.map((t) => ({ value: t.id, label: t.name }))
753
+ }
754
+ ) }) }),
755
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Follow product status", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "If on, published Medusa products are published on Faire; draft products sync as drafts.", children: /* @__PURE__ */ jsxRuntime.jsx(
756
+ ui.Switch,
757
+ {
758
+ checked: form.follow_product_status !== false,
759
+ onCheckedChange: (v) => setForm({ ...form, follow_product_status: v })
760
+ }
761
+ ) }) }),
762
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Auto publish", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Publish products to Faire automatically on sync (otherwise create as draft).", children: /* @__PURE__ */ jsxRuntime.jsx(
763
+ ui.Switch,
764
+ {
765
+ checked: !!form.auto_publish,
766
+ onCheckedChange: (v) => setForm({ ...form, auto_publish: v })
767
+ }
768
+ ) }) })
769
+ ] })
770
+ ] })
771
+ ] }) }),
772
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
773
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: close, children: "Cancel" }),
774
+ /* @__PURE__ */ jsxRuntime.jsx(
775
+ ui.Button,
776
+ {
777
+ onClick: () => saveMutation.mutate(cleanSettings(form)),
778
+ isLoading: saveMutation.isPending,
779
+ disabled: !connected,
780
+ children: "Save settings"
781
+ }
782
+ )
783
+ ] })
784
+ ] }),
785
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
786
+ ] });
787
+ };
788
+ const Field = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
789
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
790
+ children
791
+ ] });
792
+ const SelectField = ({
793
+ value,
794
+ onValueChange,
795
+ options,
796
+ disabled
797
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(
798
+ ui.Select,
799
+ {
800
+ value: value ? value : NONE,
801
+ onValueChange: (v) => onValueChange(v === NONE ? "" : v),
802
+ disabled,
803
+ size: "small",
804
+ children: [
805
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Not set" }) }),
806
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
807
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: NONE, children: "Not set" }),
808
+ options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: o.value, children: o.label }, o.value))
809
+ ] })
810
+ ]
811
+ }
812
+ );
813
+ const ChecklistItem = ({ ok, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
814
+ "div",
815
+ {
816
+ className: ui.clx(
817
+ "flex items-center gap-2 rounded-lg border px-3 py-2",
818
+ ok ? "border-ui-tag-green-border bg-ui-tag-green-bg" : "border-ui-tag-red-border bg-ui-tag-red-bg"
819
+ ),
820
+ children: [
821
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: ok ? "green" : "red", children: ok ? "Ready" : "Missing" }),
822
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: label })
823
+ ]
824
+ }
825
+ );
826
+ const handle$2 = {
827
+ breadcrumb: () => "Sync settings"
828
+ };
609
829
  const Root = ({ prev = "..", children }) => {
610
830
  const navigate = reactRouterDom.useNavigate();
611
831
  const [open, setOpen] = react.useState(false);
@@ -915,215 +1135,8 @@ const FaireBulkPage = () => {
915
1135
  /* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
916
1136
  ] });
917
1137
  };
918
- const handle$2 = {
919
- breadcrumb: () => "Bulk sync"
920
- };
921
- const STATUS_KEY = ["faire", "status"];
922
- const TAXONOMY_KEY = ["faire", "taxonomy"];
923
- const PARENT$1 = "/settings/faire";
924
- const NONE = "__none__";
925
- const cleanSettings = (form) => {
926
- const out = { ...form };
927
- for (const key of [
928
- "default_brand_id",
929
- "default_category"
930
- ]) {
931
- if (!out[key] || out[key] === NONE) out[key] = null;
932
- }
933
- if (out.default_wholesale_markup_percent === "" || out.default_wholesale_markup_percent === NONE) {
934
- out.default_wholesale_markup_percent = null;
935
- }
936
- if (out.default_lead_time_days === "" || out.default_lead_time_days === NONE) {
937
- out.default_lead_time_days = null;
938
- }
939
- return out;
940
- };
941
- const FaireSyncSettingsDrawer = () => {
942
- const navigate = reactRouterDom.useNavigate();
943
- const queryClient = reactQuery.useQueryClient();
944
- const [open, setOpen] = react.useState(true);
945
- const [form, setForm] = react.useState({});
946
- const close = () => {
947
- setOpen(false);
948
- navigate(PARENT$1);
949
- };
950
- const statusQuery = reactQuery.useQuery({
951
- queryKey: STATUS_KEY,
952
- queryFn: () => faireApi.status()
953
- });
954
- const status = statusQuery.data;
955
- const connected = !!(status == null ? void 0 : status.connected);
956
- const taxonomyQuery = reactQuery.useQuery({
957
- queryKey: TAXONOMY_KEY,
958
- queryFn: async () => (await faireApi.taxonomy().catch(() => ({ taxonomy: [] }))).taxonomy || []
959
- });
960
- const taxonomy = taxonomyQuery.data ?? [];
961
- react.useEffect(() => {
962
- if (status == null ? void 0 : status.settings) setForm(status.settings);
963
- }, [status == null ? void 0 : status.settings]);
964
- const saveMutation = reactQuery.useMutation({
965
- mutationFn: (payload) => faireApi.saveSettings(payload),
966
- onSuccess: () => {
967
- queryClient.invalidateQueries({ queryKey: STATUS_KEY });
968
- ui.toast.success("Sync settings saved");
969
- close();
970
- },
971
- onError: (err) => ui.toast.error("Failed to save settings", { description: err.message })
972
- });
973
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer, { open, onOpenChange: (o) => o ? setOpen(true) : close(), children: [
974
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
975
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Header, { children: [
976
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Sync settings" }),
977
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Description, { children: "Publish readiness and the defaults applied to every product pushed to Faire." })
978
- ] }),
979
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-col gap-6 overflow-y-auto", children: statusQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-10 w-full" }, i)) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
980
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
981
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
982
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Publish readiness" }),
983
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Faire needs a brand and a wholesale-pricing strategy to publish products. Missing fields sync products as drafts." })
984
- ] }),
985
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
986
- /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.connected, label: "Faire connected" }),
987
- /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.brand, label: "Brand configured" }),
988
- /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.wholesale_pricing, label: "Wholesale pricing" }),
989
- /* @__PURE__ */ jsxRuntime.jsx(ChecklistItem, { ok: status == null ? void 0 : status.readiness.taxonomy, label: "Default category" })
990
- ] })
991
- ] }),
992
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
993
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
994
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Sync defaults" }),
995
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Applied to every product unless overridden in product metadata." })
996
- ] }),
997
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4", children: [
998
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Brand ID", children: /* @__PURE__ */ jsxRuntime.jsx(
999
- ui.Input,
1000
- {
1001
- value: String(form.default_brand_id ?? ""),
1002
- onChange: (e) => setForm({ ...form, default_brand_id: e.target.value || null }),
1003
- disabled: !connected,
1004
- placeholder: "b_..."
1005
- }
1006
- ) }),
1007
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Wholesale markup % (off retail)", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Wholesale price = retail × (100 − markup%) / 100. E.g. 50 → half of retail.", children: /* @__PURE__ */ jsxRuntime.jsx(
1008
- ui.Input,
1009
- {
1010
- type: "number",
1011
- value: String(form.default_wholesale_markup_percent ?? ""),
1012
- onChange: (e) => setForm({
1013
- ...form,
1014
- default_wholesale_markup_percent: e.target.value ? Number(e.target.value) : null
1015
- }),
1016
- disabled: !connected,
1017
- placeholder: "e.g. 50"
1018
- }
1019
- ) }) }),
1020
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default min order quantity", children: /* @__PURE__ */ jsxRuntime.jsx(
1021
- ui.Input,
1022
- {
1023
- type: "number",
1024
- value: String(form.default_min_order_quantity ?? 1),
1025
- onChange: (e) => setForm({
1026
- ...form,
1027
- default_min_order_quantity: e.target.value ? Number(e.target.value) : 1
1028
- }),
1029
- disabled: !connected
1030
- }
1031
- ) }),
1032
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Default lead time (days)", children: /* @__PURE__ */ jsxRuntime.jsx(
1033
- ui.Input,
1034
- {
1035
- type: "number",
1036
- value: String(form.default_lead_time_days ?? ""),
1037
- onChange: (e) => setForm({
1038
- ...form,
1039
- default_lead_time_days: e.target.value ? Number(e.target.value) : null
1040
- }),
1041
- disabled: !connected,
1042
- placeholder: "e.g. 14"
1043
- }
1044
- ) }),
1045
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Fallback category (taxonomy)", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Faire requires a category per product. Resolution order: product metadata → the product's Type (matched by name) → this fallback. Set the product Type or pick a category on the product page for accuracy.", children: /* @__PURE__ */ jsxRuntime.jsx(
1046
- SelectField,
1047
- {
1048
- value: String(form.default_category ?? ""),
1049
- onValueChange: (v) => setForm({ ...form, default_category: v || null }),
1050
- disabled: !connected,
1051
- options: taxonomy.map((t) => ({ value: t.id, label: t.name }))
1052
- }
1053
- ) }) }),
1054
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Follow product status", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "If on, published Medusa products are published on Faire; draft products sync as drafts.", children: /* @__PURE__ */ jsxRuntime.jsx(
1055
- ui.Switch,
1056
- {
1057
- checked: form.follow_product_status !== false,
1058
- onCheckedChange: (v) => setForm({ ...form, follow_product_status: v })
1059
- }
1060
- ) }) }),
1061
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Auto publish", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { content: "Publish products to Faire automatically on sync (otherwise create as draft).", children: /* @__PURE__ */ jsxRuntime.jsx(
1062
- ui.Switch,
1063
- {
1064
- checked: !!form.auto_publish,
1065
- onCheckedChange: (v) => setForm({ ...form, auto_publish: v })
1066
- }
1067
- ) }) })
1068
- ] })
1069
- ] })
1070
- ] }) }),
1071
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
1072
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: close, children: "Cancel" }),
1073
- /* @__PURE__ */ jsxRuntime.jsx(
1074
- ui.Button,
1075
- {
1076
- onClick: () => saveMutation.mutate(cleanSettings(form)),
1077
- isLoading: saveMutation.isPending,
1078
- disabled: !connected,
1079
- children: "Save settings"
1080
- }
1081
- )
1082
- ] })
1083
- ] }),
1084
- /* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
1085
- ] });
1086
- };
1087
- const Field = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
1088
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: label }),
1089
- children
1090
- ] });
1091
- const SelectField = ({
1092
- value,
1093
- onValueChange,
1094
- options,
1095
- disabled
1096
- }) => /* @__PURE__ */ jsxRuntime.jsxs(
1097
- ui.Select,
1098
- {
1099
- value: value ? value : NONE,
1100
- onValueChange: (v) => onValueChange(v === NONE ? "" : v),
1101
- disabled,
1102
- size: "small",
1103
- children: [
1104
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Not set" }) }),
1105
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
1106
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: NONE, children: "Not set" }),
1107
- options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: o.value, children: o.label }, o.value))
1108
- ] })
1109
- ]
1110
- }
1111
- );
1112
- const ChecklistItem = ({ ok, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
1113
- "div",
1114
- {
1115
- className: ui.clx(
1116
- "flex items-center gap-2 rounded-lg border px-3 py-2",
1117
- ok ? "border-ui-tag-green-border bg-ui-tag-green-bg" : "border-ui-tag-red-border bg-ui-tag-red-bg"
1118
- ),
1119
- children: [
1120
- /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: ok ? "green" : "red", children: ok ? "Ready" : "Missing" }),
1121
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: label })
1122
- ]
1123
- }
1124
- );
1125
1138
  const handle$1 = {
1126
- breadcrumb: () => "Sync settings"
1139
+ breadcrumb: () => "Bulk sync"
1127
1140
  };
1128
1141
  const PARENT = "/settings/faire";
1129
1142
  const STATUS_COLORS = {
@@ -1319,13 +1332,13 @@ const routeModule = {
1319
1332
  handle: handle$3,
1320
1333
  children: [
1321
1334
  {
1322
- Component: FaireBulkPage,
1323
- path: "/settings/faire/bulk",
1335
+ Component: FaireSyncSettingsDrawer,
1336
+ path: "/settings/faire/settings",
1324
1337
  handle: handle$2
1325
1338
  },
1326
1339
  {
1327
- Component: FaireSyncSettingsDrawer,
1328
- path: "/settings/faire/settings",
1340
+ Component: FaireBulkPage,
1341
+ path: "/settings/faire/bulk",
1329
1342
  handle: handle$1
1330
1343
  }
1331
1344
  ]