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