@jytextiles/medusa-plugin-faire-store-sync 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +318 -31
- package/.medusa/server/src/admin/index.mjs +320 -33
- package/.medusa/server/src/modules/faire-sync/service.js +3 -3
- package/README.md +7 -33
- package/package.json +1 -1
- package/src/admin/components/route-focus-modal.tsx +51 -0
- package/src/admin/routes/settings/faire/bulk/page.tsx +263 -0
- package/src/admin/routes/settings/faire/bulk/use-bulk-product-columns.tsx +60 -0
- package/src/admin/routes/settings/faire/page.tsx +9 -1
- package/src/admin/routes/settings/oauth/faire/callback/page.tsx +63 -20
- package/src/modules/faire-sync/service.ts +2 -2
|
@@ -177,14 +177,14 @@ const FaireProductWidget = ({ data }) => {
|
|
|
177
177
|
adminSdk.defineWidgetConfig({
|
|
178
178
|
zone: "product.details.side.before"
|
|
179
179
|
});
|
|
180
|
-
const columnHelper = ui.createDataTableColumnHelper();
|
|
180
|
+
const columnHelper$1 = ui.createDataTableColumnHelper();
|
|
181
181
|
const useFaireSyncColumns = () => {
|
|
182
182
|
return [
|
|
183
|
-
columnHelper.accessor("product_id", {
|
|
183
|
+
columnHelper$1.accessor("product_id", {
|
|
184
184
|
header: "Product",
|
|
185
185
|
cell: (info) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-ui-fg-subtle text-xs", children: info.getValue() })
|
|
186
186
|
}),
|
|
187
|
-
columnHelper.accessor("status", {
|
|
187
|
+
columnHelper$1.accessor("status", {
|
|
188
188
|
header: "Status",
|
|
189
189
|
cell: (info) => {
|
|
190
190
|
const status = info.getValue();
|
|
@@ -192,11 +192,11 @@ const useFaireSyncColumns = () => {
|
|
|
192
192
|
return /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color, children: status });
|
|
193
193
|
}
|
|
194
194
|
}),
|
|
195
|
-
columnHelper.accessor("product_state", {
|
|
195
|
+
columnHelper$1.accessor("product_state", {
|
|
196
196
|
header: "State",
|
|
197
197
|
cell: (info) => info.getValue() || "—"
|
|
198
198
|
}),
|
|
199
|
-
columnHelper.display({
|
|
199
|
+
columnHelper$1.display({
|
|
200
200
|
id: "product",
|
|
201
201
|
header: "Faire product",
|
|
202
202
|
cell: ({ row }) => {
|
|
@@ -215,7 +215,7 @@ const useFaireSyncColumns = () => {
|
|
|
215
215
|
);
|
|
216
216
|
}
|
|
217
217
|
}),
|
|
218
|
-
columnHelper.accessor("synced_at", {
|
|
218
|
+
columnHelper$1.accessor("synced_at", {
|
|
219
219
|
header: "Synced",
|
|
220
220
|
cell: (info) => {
|
|
221
221
|
const v = info.getValue();
|
|
@@ -228,15 +228,15 @@ const useFaireSyncColumns = () => {
|
|
|
228
228
|
})
|
|
229
229
|
];
|
|
230
230
|
};
|
|
231
|
-
const PAGE_SIZE = 10;
|
|
231
|
+
const PAGE_SIZE$1 = 10;
|
|
232
232
|
const STATUS_KEY$1 = ["faire", "status"];
|
|
233
233
|
const FaireSettingsPage = () => {
|
|
234
234
|
var _a, _b, _c;
|
|
235
|
-
const
|
|
235
|
+
const navigate2 = reactRouterDom.useNavigate();
|
|
236
236
|
const queryClient = reactQuery.useQueryClient();
|
|
237
237
|
const [pagination, setPagination] = react.useState({
|
|
238
238
|
pageIndex: 0,
|
|
239
|
-
pageSize: PAGE_SIZE
|
|
239
|
+
pageSize: PAGE_SIZE$1
|
|
240
240
|
});
|
|
241
241
|
const [filtering, setFiltering] = react.useState({});
|
|
242
242
|
const statusQuery = reactQuery.useQuery({
|
|
@@ -300,7 +300,7 @@ const FaireSettingsPage = () => {
|
|
|
300
300
|
rowCount: ((_b = syncsQuery.data) == null ? void 0 : _b.count) ?? 0,
|
|
301
301
|
getRowId: (row) => row.id,
|
|
302
302
|
onRowClick: (_, row) => {
|
|
303
|
-
|
|
303
|
+
navigate2(`/settings/faire/${row.id}`);
|
|
304
304
|
},
|
|
305
305
|
isLoading: syncsQuery.isLoading,
|
|
306
306
|
filters,
|
|
@@ -343,7 +343,7 @@ const FaireSettingsPage = () => {
|
|
|
343
343
|
{
|
|
344
344
|
size: "small",
|
|
345
345
|
variant: "secondary",
|
|
346
|
-
onClick: () =>
|
|
346
|
+
onClick: () => navigate2("/settings/faire/settings"),
|
|
347
347
|
children: "Sync settings"
|
|
348
348
|
}
|
|
349
349
|
),
|
|
@@ -485,7 +485,17 @@ const BulkOperations = ({ connected }) => {
|
|
|
485
485
|
disabled: !connected || pushMutation.isPending
|
|
486
486
|
}
|
|
487
487
|
),
|
|
488
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
488
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
489
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
490
|
+
ui.Button,
|
|
491
|
+
{
|
|
492
|
+
size: "small",
|
|
493
|
+
variant: "secondary",
|
|
494
|
+
disabled: !connected,
|
|
495
|
+
onClick: () => navigate("/settings/faire/bulk"),
|
|
496
|
+
children: "Select products"
|
|
497
|
+
}
|
|
498
|
+
),
|
|
489
499
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
490
500
|
ui.Button,
|
|
491
501
|
{
|
|
@@ -557,9 +567,256 @@ const config = adminSdk.defineRouteConfig({
|
|
|
557
567
|
label: "Faire",
|
|
558
568
|
icon: icons.BuildingStorefront
|
|
559
569
|
});
|
|
560
|
-
const handle$
|
|
570
|
+
const handle$3 = {
|
|
561
571
|
breadcrumb: () => "Faire Sync"
|
|
562
572
|
};
|
|
573
|
+
const Root = ({ prev = "..", children }) => {
|
|
574
|
+
const navigate2 = reactRouterDom.useNavigate();
|
|
575
|
+
const [open, setOpen] = react.useState(false);
|
|
576
|
+
react.useEffect(() => {
|
|
577
|
+
setOpen(true);
|
|
578
|
+
return () => setOpen(false);
|
|
579
|
+
}, []);
|
|
580
|
+
const handleOpenChange = (next) => {
|
|
581
|
+
if (!next) {
|
|
582
|
+
document.body.style.pointerEvents = "auto";
|
|
583
|
+
navigate2(prev, { replace: true });
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
setOpen(next);
|
|
587
|
+
};
|
|
588
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Content, { className: "flex flex-col", children }) });
|
|
589
|
+
};
|
|
590
|
+
const RouteFocusModal = Object.assign(Root, {
|
|
591
|
+
Header: ui.FocusModal.Header,
|
|
592
|
+
Title: ui.FocusModal.Title,
|
|
593
|
+
Description: ui.FocusModal.Description,
|
|
594
|
+
Body: ui.FocusModal.Body,
|
|
595
|
+
Footer: ui.FocusModal.Footer,
|
|
596
|
+
Close: ui.FocusModal.Close
|
|
597
|
+
});
|
|
598
|
+
const columnHelper = ui.createDataTableColumnHelper();
|
|
599
|
+
const useBulkProductColumns = () => {
|
|
600
|
+
return [
|
|
601
|
+
columnHelper.accessor("title", {
|
|
602
|
+
header: "Product",
|
|
603
|
+
cell: ({ row, getValue }) => {
|
|
604
|
+
const p = row.original;
|
|
605
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
606
|
+
p.thumbnail ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
607
|
+
"img",
|
|
608
|
+
{
|
|
609
|
+
src: p.thumbnail,
|
|
610
|
+
alt: "",
|
|
611
|
+
className: "h-8 w-8 rounded border border-ui-border-base object-cover"
|
|
612
|
+
}
|
|
613
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 w-8 rounded border border-ui-border-base bg-ui-bg-subtle" }),
|
|
614
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
615
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: getValue() }),
|
|
616
|
+
p.handle && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-fg-subtle text-xs font-mono", children: p.handle })
|
|
617
|
+
] })
|
|
618
|
+
] });
|
|
619
|
+
}
|
|
620
|
+
}),
|
|
621
|
+
columnHelper.accessor("status", {
|
|
622
|
+
header: "Status",
|
|
623
|
+
cell: (info) => {
|
|
624
|
+
const s = info.getValue();
|
|
625
|
+
const color = s === "published" ? "green" : s === "draft" ? "orange" : "grey";
|
|
626
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color, children: s });
|
|
627
|
+
}
|
|
628
|
+
}),
|
|
629
|
+
columnHelper.accessor("updated_at", {
|
|
630
|
+
header: "Updated",
|
|
631
|
+
cell: (info) => {
|
|
632
|
+
const v = info.getValue();
|
|
633
|
+
return v ? new Date(v).toLocaleString() : "—";
|
|
634
|
+
}
|
|
635
|
+
})
|
|
636
|
+
];
|
|
637
|
+
};
|
|
638
|
+
const PAGE_SIZE = 20;
|
|
639
|
+
const PRODUCT_FIELDS = "id,title,status,thumbnail,handle,created_at,updated_at";
|
|
640
|
+
const SELECT_ALL_CAP = 2e3;
|
|
641
|
+
const listProducts = (query) => sdk.admin.product.list({
|
|
642
|
+
fields: PRODUCT_FIELDS,
|
|
643
|
+
...query
|
|
644
|
+
});
|
|
645
|
+
const FaireBulkPage = () => {
|
|
646
|
+
var _a, _b, _c, _d;
|
|
647
|
+
const queryClient = reactQuery.useQueryClient();
|
|
648
|
+
const [pagination, setPagination] = react.useState({
|
|
649
|
+
pageIndex: 0,
|
|
650
|
+
pageSize: PAGE_SIZE
|
|
651
|
+
});
|
|
652
|
+
const [filtering, setFiltering] = react.useState({});
|
|
653
|
+
const [search, setSearch] = react.useState("");
|
|
654
|
+
const [rowSelection, setRowSelection] = react.useState({});
|
|
655
|
+
const [selectAllMatching, setSelectAllMatching] = react.useState(false);
|
|
656
|
+
const statusFilter = (_a = filtering.status) == null ? void 0 : _a[0];
|
|
657
|
+
const statusQuery = reactQuery.useQuery({
|
|
658
|
+
queryKey: ["faire", "status"],
|
|
659
|
+
queryFn: () => faireApi.status()
|
|
660
|
+
});
|
|
661
|
+
const connected = !!((_b = statusQuery.data) == null ? void 0 : _b.connected);
|
|
662
|
+
const productsQuery = reactQuery.useQuery({
|
|
663
|
+
queryKey: ["faire", "bulk-products", pagination, statusFilter, search],
|
|
664
|
+
placeholderData: reactQuery.keepPreviousData,
|
|
665
|
+
queryFn: () => listProducts({
|
|
666
|
+
limit: pagination.pageSize,
|
|
667
|
+
offset: pagination.pageIndex * pagination.pageSize,
|
|
668
|
+
q: search || void 0,
|
|
669
|
+
status: statusFilter,
|
|
670
|
+
order: "-created_at"
|
|
671
|
+
})
|
|
672
|
+
});
|
|
673
|
+
const products = ((_c = productsQuery.data) == null ? void 0 : _c.products) ?? [];
|
|
674
|
+
const count = ((_d = productsQuery.data) == null ? void 0 : _d.count) ?? 0;
|
|
675
|
+
const selectedIds = react.useMemo(
|
|
676
|
+
() => Object.keys(rowSelection).filter((id) => rowSelection[id]),
|
|
677
|
+
[rowSelection]
|
|
678
|
+
);
|
|
679
|
+
const targetCount = selectAllMatching ? count : selectedIds.length;
|
|
680
|
+
const resolveTargetIds = async () => {
|
|
681
|
+
if (!selectAllMatching) return selectedIds;
|
|
682
|
+
const ids = [];
|
|
683
|
+
let offset = 0;
|
|
684
|
+
while (ids.length < count && ids.length < SELECT_ALL_CAP) {
|
|
685
|
+
const res = await listProducts({
|
|
686
|
+
limit: 200,
|
|
687
|
+
offset,
|
|
688
|
+
q: search || void 0,
|
|
689
|
+
status: statusFilter,
|
|
690
|
+
order: "-created_at"
|
|
691
|
+
});
|
|
692
|
+
const batch = res.products ?? [];
|
|
693
|
+
if (!batch.length) break;
|
|
694
|
+
ids.push(...batch.map((p) => p.id));
|
|
695
|
+
offset += batch.length;
|
|
696
|
+
}
|
|
697
|
+
return ids.slice(0, SELECT_ALL_CAP);
|
|
698
|
+
};
|
|
699
|
+
const pushMutation = reactQuery.useMutation({
|
|
700
|
+
mutationFn: async () => {
|
|
701
|
+
const ids = await resolveTargetIds();
|
|
702
|
+
if (!ids.length) throw new Error("No products to sync");
|
|
703
|
+
const res = await faireApi.syncBulk(ids);
|
|
704
|
+
return { res, queued: ids.length };
|
|
705
|
+
},
|
|
706
|
+
onSuccess: ({ res, queued }) => {
|
|
707
|
+
ui.toast.success("Bulk product sync started", {
|
|
708
|
+
description: `Queued ${queued} product(s). Batch ${res.batch_id}.` + (selectAllMatching && count > SELECT_ALL_CAP ? ` Capped at ${SELECT_ALL_CAP} — run again for the rest.` : "")
|
|
709
|
+
});
|
|
710
|
+
queryClient.invalidateQueries({ queryKey: ["faire", "syncs"] });
|
|
711
|
+
setRowSelection({});
|
|
712
|
+
setSelectAllMatching(false);
|
|
713
|
+
},
|
|
714
|
+
onError: (err) => ui.toast.error("Failed to start bulk sync", { description: err.message })
|
|
715
|
+
});
|
|
716
|
+
const columns = useBulkProductColumns();
|
|
717
|
+
const commands = react.useMemo(
|
|
718
|
+
() => [
|
|
719
|
+
{
|
|
720
|
+
label: connected ? "Push to Faire" : "Connect Faire first",
|
|
721
|
+
shortcut: "p",
|
|
722
|
+
action: () => {
|
|
723
|
+
if (!connected) {
|
|
724
|
+
ui.toast.error("Faire is not connected");
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
pushMutation.mutate();
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
label: "Clear selection",
|
|
732
|
+
shortcut: "c",
|
|
733
|
+
action: () => {
|
|
734
|
+
setRowSelection({});
|
|
735
|
+
setSelectAllMatching(false);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
],
|
|
739
|
+
[connected, pushMutation]
|
|
740
|
+
);
|
|
741
|
+
const table = ui.useDataTable({
|
|
742
|
+
data: products,
|
|
743
|
+
columns,
|
|
744
|
+
rowCount: count,
|
|
745
|
+
getRowId: (row) => row.id,
|
|
746
|
+
isLoading: productsQuery.isLoading,
|
|
747
|
+
commands,
|
|
748
|
+
rowSelection: {
|
|
749
|
+
state: rowSelection,
|
|
750
|
+
onRowSelectionChange: (updater) => {
|
|
751
|
+
setSelectAllMatching(false);
|
|
752
|
+
setRowSelection(updater);
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
pagination: { state: pagination, onPaginationChange: setPagination },
|
|
756
|
+
filtering: { state: filtering, onFilteringChange: setFiltering },
|
|
757
|
+
search: { state: search, onSearchChange: setSearch }
|
|
758
|
+
});
|
|
759
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal, { prev: "/settings/faire", children: [
|
|
760
|
+
/* @__PURE__ */ jsxRuntime.jsx(RouteFocusModal.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
761
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: "Bulk sync products to Faire" }),
|
|
762
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", size: "small", children: "Select products (or all matching) and push them to Faire as a background sync via the command bar." })
|
|
763
|
+
] }) }),
|
|
764
|
+
/* @__PURE__ */ jsxRuntime.jsx(RouteFocusModal.Body, { className: "flex flex-1 flex-col overflow-hidden p-0", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
|
|
765
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable.Toolbar, { className: "flex flex-col gap-y-3 border-b px-6 py-4", children: [
|
|
766
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2 md:flex-row md:items-center md:justify-between", children: [
|
|
767
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
768
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
769
|
+
ui.Input,
|
|
770
|
+
{
|
|
771
|
+
type: "search",
|
|
772
|
+
placeholder: "Search products…",
|
|
773
|
+
value: search,
|
|
774
|
+
onChange: (e) => setSearch(e.target.value),
|
|
775
|
+
className: "w-full md:w-72"
|
|
776
|
+
}
|
|
777
|
+
),
|
|
778
|
+
!connected && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: "orange", size: "2xsmall", children: "Not connected" })
|
|
779
|
+
] }),
|
|
780
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
781
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
782
|
+
ui.Button,
|
|
783
|
+
{
|
|
784
|
+
size: "small",
|
|
785
|
+
variant: "secondary",
|
|
786
|
+
onClick: () => productsQuery.refetch(),
|
|
787
|
+
isLoading: productsQuery.isFetching,
|
|
788
|
+
children: "Refresh"
|
|
789
|
+
}
|
|
790
|
+
),
|
|
791
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
792
|
+
ui.Button,
|
|
793
|
+
{
|
|
794
|
+
size: "small",
|
|
795
|
+
variant: selectAllMatching ? "primary" : "secondary",
|
|
796
|
+
disabled: !count,
|
|
797
|
+
onClick: () => {
|
|
798
|
+
setRowSelection({});
|
|
799
|
+
setSelectAllMatching((v) => !v);
|
|
800
|
+
},
|
|
801
|
+
children: selectAllMatching ? `All ${count} selected` : `Select all ${count}`
|
|
802
|
+
}
|
|
803
|
+
)
|
|
804
|
+
] })
|
|
805
|
+
] }),
|
|
806
|
+
targetCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
|
|
807
|
+
selectAllMatching ? `All ${count} matching product(s) will be synced` : `${targetCount} product(s) selected`,
|
|
808
|
+
pushMutation.isPending ? " — queuing…" : ""
|
|
809
|
+
] })
|
|
810
|
+
] }),
|
|
811
|
+
productsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3 p-6", children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-10 w-full" }, i)) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}),
|
|
812
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
|
|
813
|
+
] }) }),
|
|
814
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Toaster, {})
|
|
815
|
+
] });
|
|
816
|
+
};
|
|
817
|
+
const handle$2 = {
|
|
818
|
+
breadcrumb: () => "Bulk sync"
|
|
819
|
+
};
|
|
563
820
|
const STATUS_COLORS = {
|
|
564
821
|
success: "green",
|
|
565
822
|
failed: "red",
|
|
@@ -569,7 +826,7 @@ const STATUS_COLORS = {
|
|
|
569
826
|
};
|
|
570
827
|
const FaireSyncDetailPage = () => {
|
|
571
828
|
const { id } = reactRouterDom.useParams();
|
|
572
|
-
const
|
|
829
|
+
const navigate2 = reactRouterDom.useNavigate();
|
|
573
830
|
const [record, setRecord] = react.useState(null);
|
|
574
831
|
const [loading, setLoading] = react.useState(true);
|
|
575
832
|
const [retrying, setRetrying] = react.useState(false);
|
|
@@ -601,7 +858,7 @@ const FaireSyncDetailPage = () => {
|
|
|
601
858
|
if (!record) {
|
|
602
859
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6 flex flex-col gap-4", children: [
|
|
603
860
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Sync record not found" }),
|
|
604
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () =>
|
|
861
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => navigate2("/settings/faire"), children: "Back to Faire settings" })
|
|
605
862
|
] });
|
|
606
863
|
}
|
|
607
864
|
const warnings = record.error_message ? record.error_message.split(" | ") : [];
|
|
@@ -699,13 +956,13 @@ const cleanSettings = (form) => {
|
|
|
699
956
|
return out;
|
|
700
957
|
};
|
|
701
958
|
const FaireSyncSettingsDrawer = () => {
|
|
702
|
-
const
|
|
959
|
+
const navigate2 = reactRouterDom.useNavigate();
|
|
703
960
|
const queryClient = reactQuery.useQueryClient();
|
|
704
961
|
const [open, setOpen] = react.useState(true);
|
|
705
962
|
const [form, setForm] = react.useState({});
|
|
706
963
|
const close = () => {
|
|
707
964
|
setOpen(false);
|
|
708
|
-
|
|
965
|
+
navigate2(PARENT);
|
|
709
966
|
};
|
|
710
967
|
const statusQuery = reactQuery.useQuery({
|
|
711
968
|
queryKey: STATUS_KEY,
|
|
@@ -864,42 +1121,67 @@ const ChecklistItem = ({ ok, label }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
|
864
1121
|
const handle = {
|
|
865
1122
|
breadcrumb: () => "Sync settings"
|
|
866
1123
|
};
|
|
1124
|
+
const EXCHANGED_PREFIX = "faire:oauth:exchanged:";
|
|
867
1125
|
const FaireOauthCallback = () => {
|
|
868
1126
|
const [params] = reactRouterDom.useSearchParams();
|
|
869
|
-
const
|
|
1127
|
+
const navigate2 = reactRouterDom.useNavigate();
|
|
870
1128
|
const [error, setError] = react.useState(null);
|
|
871
|
-
const
|
|
1129
|
+
const [exchanging, setExchanging] = react.useState(true);
|
|
872
1130
|
react.useEffect(() => {
|
|
873
|
-
if (exchangedRef.current) return;
|
|
874
|
-
exchangedRef.current = true;
|
|
875
|
-
const code = params.get("code");
|
|
876
|
-
const state = params.get("state");
|
|
877
1131
|
const errParam = params.get("error");
|
|
878
1132
|
if (errParam) {
|
|
879
1133
|
setError(
|
|
880
1134
|
`Faire authorization failed: ${params.get("error_description") || errParam}`
|
|
881
1135
|
);
|
|
1136
|
+
setExchanging(false);
|
|
882
1137
|
return;
|
|
883
1138
|
}
|
|
1139
|
+
const code = params.get("authorization_code") || params.get("code");
|
|
1140
|
+
const state = params.get("state");
|
|
884
1141
|
if (!code || !state) {
|
|
885
1142
|
setError("Missing code or state in Faire callback.");
|
|
1143
|
+
setExchanging(false);
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (sessionStorage.getItem(EXCHANGED_PREFIX + code)) {
|
|
1147
|
+
navigate2("/settings/faire", { replace: true });
|
|
886
1148
|
return;
|
|
887
1149
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1150
|
+
sessionStorage.setItem(EXCHANGED_PREFIX + code, "1");
|
|
1151
|
+
let cancelled = false;
|
|
1152
|
+
const run = async () => {
|
|
1153
|
+
try {
|
|
1154
|
+
await faireApi.callback(code, state);
|
|
1155
|
+
if (!cancelled) navigate2("/settings/faire", { replace: true });
|
|
1156
|
+
} catch (err) {
|
|
1157
|
+
if (!cancelled) {
|
|
1158
|
+
try {
|
|
1159
|
+
const status = await faireApi.status();
|
|
1160
|
+
if (status == null ? void 0 : status.connected) {
|
|
1161
|
+
navigate2("/settings/faire", { replace: true });
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
} catch {
|
|
1165
|
+
}
|
|
1166
|
+
setError(err.message || "Failed to complete Faire authorization.");
|
|
1167
|
+
setExchanging(false);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
run();
|
|
1172
|
+
return () => {
|
|
1173
|
+
cancelled = true;
|
|
1174
|
+
};
|
|
1175
|
+
}, [params, navigate2]);
|
|
894
1176
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col items-center gap-4 p-8", children: [
|
|
895
1177
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Connecting Faire…" }),
|
|
896
|
-
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…" }),
|
|
1178
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "error", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: error }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: exchanging ? "Completing authorization, please wait…" : "Redirecting…" }),
|
|
897
1179
|
error && /* @__PURE__ */ jsxRuntime.jsx(
|
|
898
1180
|
ui.Button,
|
|
899
1181
|
{
|
|
900
1182
|
size: "small",
|
|
901
1183
|
variant: "secondary",
|
|
902
|
-
onClick: () =>
|
|
1184
|
+
onClick: () => navigate2("/settings/faire", { replace: true }),
|
|
903
1185
|
children: "Back to Faire settings"
|
|
904
1186
|
}
|
|
905
1187
|
)
|
|
@@ -917,6 +1199,11 @@ const routeModule = {
|
|
|
917
1199
|
{
|
|
918
1200
|
Component: FaireSettingsPage,
|
|
919
1201
|
path: "/settings/faire",
|
|
1202
|
+
handle: handle$3
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
Component: FaireBulkPage,
|
|
1206
|
+
path: "/settings/faire/bulk",
|
|
920
1207
|
handle: handle$2
|
|
921
1208
|
},
|
|
922
1209
|
{
|