@jytextiles/medusa-plugin-faire-store-sync 0.2.13 → 0.2.14

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.
@@ -62,6 +62,7 @@ const faireApi = {
62
62
  },
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
+ deleteSync: (id) => sdk.client.fetch(`/admin/faire/syncs/${id}`, { method: "DELETE" }),
65
66
  brand: () => sdk.client.fetch("/admin/faire/brand"),
66
67
  taxonomy: (opts = {}) => {
67
68
  var _a;
@@ -1149,9 +1150,11 @@ const STATUS_COLORS = {
1149
1150
  const FaireSyncDetailPage = () => {
1150
1151
  const { id } = reactRouterDom.useParams();
1151
1152
  const navigate = reactRouterDom.useNavigate();
1153
+ const prompt = ui.usePrompt();
1152
1154
  const [record, setRecord] = react.useState(null);
1153
1155
  const [loading, setLoading] = react.useState(true);
1154
1156
  const [retrying, setRetrying] = react.useState(false);
1157
+ const [deleting, setDeleting] = react.useState(false);
1155
1158
  const [error, setError] = react.useState(null);
1156
1159
  react.useEffect(() => {
1157
1160
  if (!id) return;
@@ -1174,6 +1177,28 @@ const FaireSyncDetailPage = () => {
1174
1177
  setRetrying(false);
1175
1178
  }
1176
1179
  };
1180
+ const handleDelete = async () => {
1181
+ if (!record) return;
1182
+ const confirmed = await prompt({
1183
+ title: "Remove from Faire?",
1184
+ description: "Faire has no hard-delete API, so this unpublishes the product (sets it back to draft, hidden from buyers) and unlinks it from the product. Re-sync to publish it again, or delete it permanently from the Faire brand portal.",
1185
+ confirmText: "Remove",
1186
+ cancelText: "Cancel"
1187
+ });
1188
+ if (!confirmed) return;
1189
+ setDeleting(true);
1190
+ setError(null);
1191
+ try {
1192
+ await faireApi.deleteSync(record.id);
1193
+ const updated = await faireApi.getSync(record.id).catch(() => null);
1194
+ if (updated) setRecord(updated.sync || updated);
1195
+ navigate(PARENT);
1196
+ } catch (err) {
1197
+ setError(err.message || "Remove failed");
1198
+ } finally {
1199
+ setDeleting(false);
1200
+ }
1201
+ };
1177
1202
  const warnings = (record == null ? void 0 : record.error_message) ? record.error_message.split(" | ") : [];
1178
1203
  return /* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal, { prev: PARENT, children: [
1179
1204
  /* @__PURE__ */ jsxRuntime.jsxs(RouteFocusModal.Header, { children: [
@@ -1182,7 +1207,7 @@ const FaireSyncDetailPage = () => {
1182
1207
  record && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: STATUS_COLORS[record.status] || "grey", children: record.status })
1183
1208
  ] }),
1184
1209
  record && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
1185
- /* @__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, {}) }) }),
1210
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", disabled: retrying || deleting, children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
1186
1211
  /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { align: "end", children: [
1187
1212
  /* @__PURE__ */ jsxRuntime.jsxs(
1188
1213
  ui.DropdownMenu.Item,
@@ -1206,6 +1231,19 @@ const FaireSyncDetailPage = () => {
1206
1231
  "Open on Faire"
1207
1232
  ]
1208
1233
  }
1234
+ ),
1235
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
1236
+ /* @__PURE__ */ jsxRuntime.jsxs(
1237
+ ui.DropdownMenu.Item,
1238
+ {
1239
+ className: "gap-x-2 text-ui-fg-error",
1240
+ onClick: handleDelete,
1241
+ disabled: deleting,
1242
+ children: [
1243
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-error" }),
1244
+ deleting ? "Removing…" : "Remove from Faire"
1245
+ ]
1246
+ }
1209
1247
  )
1210
1248
  ] })
1211
1249
  ] }) })
@@ -1,10 +1,10 @@
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, FocusModal, Switch, Select, usePrompt, IconButton } from "@medusajs/ui";
4
4
  import { useQueryClient, useQuery, useMutation, keepPreviousData } from "@tanstack/react-query";
5
5
  import { useState, useEffect, useMemo } from "react";
6
6
  import Medusa from "@medusajs/js-sdk";
7
- import { BuildingStorefront, ChevronDownMini, EllipsisHorizontal, ArrowPath, ArrowUpRightOnBox } from "@medusajs/icons";
7
+ import { BuildingStorefront, ChevronDownMini, EllipsisHorizontal, ArrowPath, ArrowUpRightOnBox, Trash } from "@medusajs/icons";
8
8
  import { useNavigate, Outlet, useParams, useSearchParams } from "react-router-dom";
9
9
  const sdk = new Medusa({
10
10
  baseUrl: "/",
@@ -59,6 +59,7 @@ const faireApi = {
59
59
  },
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
+ deleteSync: (id) => sdk.client.fetch(`/admin/faire/syncs/${id}`, { method: "DELETE" }),
62
63
  brand: () => sdk.client.fetch("/admin/faire/brand"),
63
64
  taxonomy: (opts = {}) => {
64
65
  var _a;
@@ -1146,9 +1147,11 @@ const STATUS_COLORS = {
1146
1147
  const FaireSyncDetailPage = () => {
1147
1148
  const { id } = useParams();
1148
1149
  const navigate = useNavigate();
1150
+ const prompt = usePrompt();
1149
1151
  const [record, setRecord] = useState(null);
1150
1152
  const [loading, setLoading] = useState(true);
1151
1153
  const [retrying, setRetrying] = useState(false);
1154
+ const [deleting, setDeleting] = useState(false);
1152
1155
  const [error, setError] = useState(null);
1153
1156
  useEffect(() => {
1154
1157
  if (!id) return;
@@ -1171,6 +1174,28 @@ const FaireSyncDetailPage = () => {
1171
1174
  setRetrying(false);
1172
1175
  }
1173
1176
  };
1177
+ const handleDelete = async () => {
1178
+ if (!record) return;
1179
+ const confirmed = await prompt({
1180
+ title: "Remove from Faire?",
1181
+ description: "Faire has no hard-delete API, so this unpublishes the product (sets it back to draft, hidden from buyers) and unlinks it from the product. Re-sync to publish it again, or delete it permanently from the Faire brand portal.",
1182
+ confirmText: "Remove",
1183
+ cancelText: "Cancel"
1184
+ });
1185
+ if (!confirmed) return;
1186
+ setDeleting(true);
1187
+ setError(null);
1188
+ try {
1189
+ await faireApi.deleteSync(record.id);
1190
+ const updated = await faireApi.getSync(record.id).catch(() => null);
1191
+ if (updated) setRecord(updated.sync || updated);
1192
+ navigate(PARENT);
1193
+ } catch (err) {
1194
+ setError(err.message || "Remove failed");
1195
+ } finally {
1196
+ setDeleting(false);
1197
+ }
1198
+ };
1174
1199
  const warnings = (record == null ? void 0 : record.error_message) ? record.error_message.split(" | ") : [];
1175
1200
  return /* @__PURE__ */ jsxs(RouteFocusModal, { prev: PARENT, children: [
1176
1201
  /* @__PURE__ */ jsxs(RouteFocusModal.Header, { children: [
@@ -1179,7 +1204,7 @@ const FaireSyncDetailPage = () => {
1179
1204
  record && /* @__PURE__ */ jsx(StatusBadge, { color: STATUS_COLORS[record.status] || "grey", children: record.status })
1180
1205
  ] }),
1181
1206
  record && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1182
- /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { size: "small", variant: "transparent", disabled: retrying, children: /* @__PURE__ */ jsx(EllipsisHorizontal, {}) }) }),
1207
+ /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { size: "small", variant: "transparent", disabled: retrying || deleting, children: /* @__PURE__ */ jsx(EllipsisHorizontal, {}) }) }),
1183
1208
  /* @__PURE__ */ jsxs(DropdownMenu.Content, { align: "end", children: [
1184
1209
  /* @__PURE__ */ jsxs(
1185
1210
  DropdownMenu.Item,
@@ -1203,6 +1228,19 @@ const FaireSyncDetailPage = () => {
1203
1228
  "Open on Faire"
1204
1229
  ]
1205
1230
  }
1231
+ ),
1232
+ /* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
1233
+ /* @__PURE__ */ jsxs(
1234
+ DropdownMenu.Item,
1235
+ {
1236
+ className: "gap-x-2 text-ui-fg-error",
1237
+ onClick: handleDelete,
1238
+ disabled: deleting,
1239
+ children: [
1240
+ /* @__PURE__ */ jsx(Trash, { className: "text-ui-fg-error" }),
1241
+ deleting ? "Removing…" : "Remove from Faire"
1242
+ ]
1243
+ }
1206
1244
  )
1207
1245
  ] })
1208
1246
  ] }) })
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.POST = exports.GET = void 0;
3
+ exports.DELETE = exports.POST = exports.GET = void 0;
4
4
  const utils_1 = require("@medusajs/framework/utils");
5
5
  const faire_sync_1 = require("../../../../../modules/faire-sync");
6
6
  const sync_product_to_faire_1 = require("../../../../../workflows/sync-product-to-faire");
7
+ const remove_product_from_faire_1 = require("../../../../../workflows/remove-product-from-faire");
7
8
  // GET /admin/faire/syncs/:id — fetch a single sync record
8
9
  const GET = async (req, res) => {
9
10
  const service = req.scope.resolve(faire_sync_1.FAIRE_SYNC_MODULE);
@@ -29,4 +30,20 @@ const POST = async (req, res) => {
29
30
  res.json({ result });
30
31
  };
31
32
  exports.POST = POST;
32
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL2ZhaXJlL3N5bmNzL1tpZF0vcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EscURBQXVEO0FBQ3ZELGtFQUFxRTtBQUVyRSwwRkFBMkY7QUFFM0YsMERBQTBEO0FBQ25ELE1BQU0sR0FBRyxHQUFHLEtBQUssRUFBRSxHQUFrQixFQUFFLEdBQW1CLEVBQUUsRUFBRTtJQUNuRSxNQUFNLE9BQU8sR0FBcUIsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsOEJBQWlCLENBQUMsQ0FBQTtJQUN0RSxNQUFNLEVBQUUsRUFBRSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQTtJQUN6QixNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsTUFBTSxPQUFPLENBQUMsb0JBQW9CLENBQUMsRUFBRSxFQUFFLEVBQVMsRUFBRSxFQUFFLElBQUksRUFBRSxDQUFDLEVBQVMsQ0FBQyxDQUFBO0lBQ3RGLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNaLE1BQU0sSUFBSSxtQkFBVyxDQUFDLG1CQUFXLENBQUMsS0FBSyxDQUFDLFNBQVMsRUFBRSx1QkFBdUIsQ0FBQyxDQUFBO0lBQzdFLENBQUM7SUFDRCxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUE7QUFDNUIsQ0FBQyxDQUFBO0FBUlksUUFBQSxHQUFHLE9BUWY7QUFFRCxvRkFBb0Y7QUFDN0UsTUFBTSxJQUFJLEdBQUcsS0FBSyxFQUFFLEdBQWtCLEVBQUUsR0FBbUIsRUFBRSxFQUFFO0lBQ3BFLE1BQU0sT0FBTyxHQUFxQixHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyw4QkFBaUIsQ0FBQyxDQUFBO0lBQ3RFLE1BQU0sRUFBRSxFQUFFLEVBQUUsR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFBO0lBQ3pCLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxNQUFNLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxFQUFFLEVBQUUsRUFBUyxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBUyxDQUFDLENBQUE7SUFDdEYsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osTUFBTSxJQUFJLG1CQUFXLENBQUMsbUJBQVcsQ0FBQyxLQUFLLENBQUMsU0FBUyxFQUFFLHVCQUF1QixDQUFDLENBQUE7SUFDN0UsQ0FBQztJQUVELE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxNQUFNLElBQUEsa0RBQTBCLEVBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQztRQUNqRSxLQUFLLEVBQUUsRUFBRSxVQUFVLEVBQUcsTUFBYyxDQUFDLFVBQVUsRUFBRTtLQUNsRCxDQUFDLENBQUE7SUFFRixHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQTtBQUN0QixDQUFDLENBQUE7QUFiWSxRQUFBLElBQUksUUFhaEIifQ==
33
+ // DELETE /admin/faire/syncs/:id — remove (unpublish) the Faire product for this
34
+ // record's product and clear the local link. Faire has no hard-delete API, so
35
+ // this sets the product back to DRAFT (hidden from buyers).
36
+ const DELETE = async (req, res) => {
37
+ const service = req.scope.resolve(faire_sync_1.FAIRE_SYNC_MODULE);
38
+ const { id } = req.params;
39
+ const [record] = await service.listFaireSyncRecords({ id }, { take: 1 });
40
+ if (!record) {
41
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, "Sync record not found");
42
+ }
43
+ const { result } = await (0, remove_product_from_faire_1.removeProductFromFaireWorkflow)(req.scope).run({
44
+ input: { product_id: record.product_id },
45
+ });
46
+ res.json({ result });
47
+ };
48
+ exports.DELETE = DELETE;
49
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL2ZhaXJlL3N5bmNzL1tpZF0vcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EscURBQXVEO0FBQ3ZELGtFQUFxRTtBQUVyRSwwRkFBMkY7QUFDM0Ysa0dBQW1HO0FBRW5HLDBEQUEwRDtBQUNuRCxNQUFNLEdBQUcsR0FBRyxLQUFLLEVBQUUsR0FBa0IsRUFBRSxHQUFtQixFQUFFLEVBQUU7SUFDbkUsTUFBTSxPQUFPLEdBQXFCLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLDhCQUFpQixDQUFDLENBQUE7SUFDdEUsTUFBTSxFQUFFLEVBQUUsRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUE7SUFDekIsTUFBTSxDQUFDLE1BQU0sQ0FBQyxHQUFHLE1BQU0sT0FBTyxDQUFDLG9CQUFvQixDQUFDLEVBQUUsRUFBRSxFQUFTLEVBQUUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFTLENBQUMsQ0FBQTtJQUN0RixJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDWixNQUFNLElBQUksbUJBQVcsQ0FBQyxtQkFBVyxDQUFDLEtBQUssQ0FBQyxTQUFTLEVBQUUsdUJBQXVCLENBQUMsQ0FBQTtJQUM3RSxDQUFDO0lBQ0QsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFBO0FBQzVCLENBQUMsQ0FBQTtBQVJZLFFBQUEsR0FBRyxPQVFmO0FBRUQsb0ZBQW9GO0FBQzdFLE1BQU0sSUFBSSxHQUFHLEtBQUssRUFBRSxHQUFrQixFQUFFLEdBQW1CLEVBQUUsRUFBRTtJQUNwRSxNQUFNLE9BQU8sR0FBcUIsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsOEJBQWlCLENBQUMsQ0FBQTtJQUN0RSxNQUFNLEVBQUUsRUFBRSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQTtJQUN6QixNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsTUFBTSxPQUFPLENBQUMsb0JBQW9CLENBQUMsRUFBRSxFQUFFLEVBQVMsRUFBRSxFQUFFLElBQUksRUFBRSxDQUFDLEVBQVMsQ0FBQyxDQUFBO0lBQ3RGLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNaLE1BQU0sSUFBSSxtQkFBVyxDQUFDLG1CQUFXLENBQUMsS0FBSyxDQUFDLFNBQVMsRUFBRSx1QkFBdUIsQ0FBQyxDQUFBO0lBQzdFLENBQUM7SUFFRCxNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsTUFBTSxJQUFBLGtEQUEwQixFQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUM7UUFDakUsS0FBSyxFQUFFLEVBQUUsVUFBVSxFQUFHLE1BQWMsQ0FBQyxVQUFVLEVBQUU7S0FDbEQsQ0FBQyxDQUFBO0lBRUYsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUE7QUFDdEIsQ0FBQyxDQUFBO0FBYlksUUFBQSxJQUFJLFFBYWhCO0FBRUQsZ0ZBQWdGO0FBQ2hGLDhFQUE4RTtBQUM5RSw0REFBNEQ7QUFDckQsTUFBTSxNQUFNLEdBQUcsS0FBSyxFQUFFLEdBQWtCLEVBQUUsR0FBbUIsRUFBRSxFQUFFO0lBQ3RFLE1BQU0sT0FBTyxHQUFxQixHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyw4QkFBaUIsQ0FBQyxDQUFBO0lBQ3RFLE1BQU0sRUFBRSxFQUFFLEVBQUUsR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFBO0lBQ3pCLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxNQUFNLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxFQUFFLEVBQUUsRUFBUyxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBUyxDQUFDLENBQUE7SUFDdEYsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osTUFBTSxJQUFJLG1CQUFXLENBQUMsbUJBQVcsQ0FBQyxLQUFLLENBQUMsU0FBUyxFQUFFLHVCQUF1QixDQUFDLENBQUE7SUFDN0UsQ0FBQztJQUVELE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxNQUFNLElBQUEsMERBQThCLEVBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQztRQUNyRSxLQUFLLEVBQUUsRUFBRSxVQUFVLEVBQUcsTUFBYyxDQUFDLFVBQVUsRUFBRTtLQUNsRCxDQUFDLENBQUE7SUFFRixHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQTtBQUN0QixDQUFDLENBQUE7QUFiWSxRQUFBLE1BQU0sVUFhbEIifQ==
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removeProductFromFaireWorkflow = exports.REMOVE_PRODUCT_FROM_FAIRE = void 0;
4
+ const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
5
+ const utils_1 = require("@medusajs/framework/utils");
6
+ const faire_sync_1 = require("../modules/faire-sync");
7
+ exports.REMOVE_PRODUCT_FROM_FAIRE = "faire-remove-product";
8
+ // ── Step: unpublish the Faire product + clean up the local link/record ─────
9
+ //
10
+ // Faire's public Brand API exposes no hard-delete for products (only
11
+ // POST/PUT/GET on /products), so "remove" means unpublish: set the product's
12
+ // lifecycle_state back to DRAFT via the update endpoint, which pulls it from
13
+ // buyers' view. A true hard-delete has to be done in the Faire brand portal.
14
+ const removeFaireProductStep = (0, workflows_sdk_1.createStep)("faire-remove-product-step", async (input, { container }) => {
15
+ const service = container.resolve(faire_sync_1.FAIRE_SYNC_MODULE);
16
+ const remoteLink = container.resolve(utils_1.ContainerRegistrationKeys.LINK);
17
+ const account = await service.ensureFreshToken();
18
+ const client = service.getClient(account.auth_mode ?? "oauth");
19
+ const warnings = [];
20
+ // Find the linked Faire product for this product.
21
+ let product_token = null;
22
+ try {
23
+ const linkRows = await remoteLink.list({
24
+ [utils_1.Modules.PRODUCT]: { product_id: input.product_id },
25
+ [faire_sync_1.FAIRE_SYNC_MODULE]: { faire_sync_account_id: account.id },
26
+ });
27
+ const row = linkRows?.[0];
28
+ product_token = row?.faire_product_token ?? null;
29
+ }
30
+ catch {
31
+ // no link
32
+ }
33
+ // Unpublish (DRAFT) so it disappears from the Faire storefront. Tolerate a
34
+ // 404 — a product already gone on Faire still cleans up our side.
35
+ if (product_token) {
36
+ try {
37
+ await client.updateProduct(account.access_token, product_token, {
38
+ lifecycle_state: "DRAFT",
39
+ });
40
+ }
41
+ catch (err) {
42
+ const msg = String(err?.message || err);
43
+ if (/404|not found/i.test(msg)) {
44
+ warnings.push("Product was already removed on Faire.");
45
+ }
46
+ else {
47
+ warnings.push(`Faire unpublish failed: ${msg}`);
48
+ }
49
+ }
50
+ }
51
+ else {
52
+ warnings.push("No linked Faire product found for this product.");
53
+ }
54
+ // Drop the product↔account link so the product reads as un-synced.
55
+ try {
56
+ await remoteLink.dismiss([
57
+ {
58
+ [utils_1.Modules.PRODUCT]: { product_id: input.product_id },
59
+ [faire_sync_1.FAIRE_SYNC_MODULE]: { faire_sync_account_id: account.id },
60
+ },
61
+ ]);
62
+ }
63
+ catch {
64
+ // ignore dismiss errors
65
+ }
66
+ // Historical record of the removal.
67
+ await service.createSyncRecord({
68
+ product_id: input.product_id,
69
+ account_id: account.id,
70
+ product_token,
71
+ product_url: null,
72
+ product_state: "draft",
73
+ action: "delete",
74
+ status: "success",
75
+ published: false,
76
+ error_message: warnings.length ? warnings.join(" | ") : null,
77
+ warnings,
78
+ metadata: {},
79
+ synced_at: new Date(),
80
+ });
81
+ return new workflows_sdk_1.StepResponse({
82
+ product_id: input.product_id,
83
+ product_token,
84
+ removed: Boolean(product_token),
85
+ warnings,
86
+ });
87
+ });
88
+ exports.removeProductFromFaireWorkflow = (0, workflows_sdk_1.createWorkflow)(exports.REMOVE_PRODUCT_FROM_FAIRE, (input) => {
89
+ const result = removeFaireProductStep(input);
90
+ return new workflows_sdk_1.WorkflowResponse(result);
91
+ });
92
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVtb3ZlLXByb2R1Y3QtZnJvbS1mYWlyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy93b3JrZmxvd3MvcmVtb3ZlLXByb2R1Y3QtZnJvbS1mYWlyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxxRUFNMEM7QUFDMUMscURBQThFO0FBRTlFLHNEQUF5RDtBQUc1QyxRQUFBLHlCQUF5QixHQUFHLHNCQUFzQixDQUFBO0FBYS9ELDhFQUE4RTtBQUM5RSxFQUFFO0FBQ0YscUVBQXFFO0FBQ3JFLDZFQUE2RTtBQUM3RSw2RUFBNkU7QUFDN0UsNkVBQTZFO0FBRTdFLE1BQU0sc0JBQXNCLEdBQUcsSUFBQSwwQkFBVSxFQUN2QywyQkFBMkIsRUFDM0IsS0FBSyxFQUNILEtBQTZCLEVBQzdCLEVBQUUsU0FBUyxFQUFFLEVBQ3dCLEVBQUU7SUFDdkMsTUFBTSxPQUFPLEdBQXFCLFNBQVMsQ0FBQyxPQUFPLENBQUMsOEJBQWlCLENBQUMsQ0FBQTtJQUN0RSxNQUFNLFVBQVUsR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLGlDQUF5QixDQUFDLElBQUksQ0FBUyxDQUFBO0lBRTVFLE1BQU0sT0FBTyxHQUFHLE1BQU0sT0FBTyxDQUFDLGdCQUFnQixFQUFFLENBQUE7SUFDaEQsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBRSxPQUFPLENBQUMsU0FBZ0MsSUFBSSxPQUFPLENBQUMsQ0FBQTtJQUN0RixNQUFNLFFBQVEsR0FBYSxFQUFFLENBQUE7SUFFN0Isa0RBQWtEO0lBQ2xELElBQUksYUFBYSxHQUFrQixJQUFJLENBQUE7SUFDdkMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxRQUFRLEdBQUcsTUFBTSxVQUFVLENBQUMsSUFBSSxDQUFDO1lBQ3JDLENBQUMsZUFBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsVUFBVSxFQUFFLEtBQUssQ0FBQyxVQUFVLEVBQUU7WUFDbkQsQ0FBQyw4QkFBaUIsQ0FBQyxF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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jytextiles/medusa-plugin-faire-store-sync",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -83,6 +83,8 @@ export const faireApi = {
83
83
  getSync: (id: string) => sdk.client.fetch(`/admin/faire/syncs/${id}`),
84
84
  retrySync: (id: string) =>
85
85
  sdk.client.fetch(`/admin/faire/syncs/${id}`, { method: "POST" }),
86
+ deleteSync: (id: string) =>
87
+ sdk.client.fetch(`/admin/faire/syncs/${id}`, { method: "DELETE" }),
86
88
  brand: () => sdk.client.fetch("/admin/faire/brand"),
87
89
  taxonomy: (opts: { q?: string; limit?: number; ids?: string[] } = {}) => {
88
90
  const query: Record<string, string> = {}
@@ -8,8 +8,9 @@ import {
8
8
  StatusBadge,
9
9
  Text,
10
10
  Toaster,
11
+ usePrompt,
11
12
  } from "@medusajs/ui"
12
- import { EllipsisHorizontal, ArrowPath, ArrowUpRightOnBox } from "@medusajs/icons"
13
+ import { EllipsisHorizontal, ArrowPath, ArrowUpRightOnBox, Trash } from "@medusajs/icons"
13
14
  import { useEffect, useState } from "react"
14
15
  import { useNavigate, useParams } from "react-router-dom"
15
16
  import { faireApi } from "../../../../lib/api"
@@ -29,9 +30,11 @@ const STATUS_COLORS: Record<string, "green" | "red" | "orange" | "grey"> = {
29
30
  const FaireSyncDetailPage = () => {
30
31
  const { id } = useParams<{ id: string }>()
31
32
  const navigate = useNavigate()
33
+ const prompt = usePrompt()
32
34
  const [record, setRecord] = useState<FaireSyncRecord | null>(null)
33
35
  const [loading, setLoading] = useState(true)
34
36
  const [retrying, setRetrying] = useState(false)
37
+ const [deleting, setDeleting] = useState(false)
35
38
  const [error, setError] = useState<string | null>(null)
36
39
 
37
40
  useEffect(() => {
@@ -61,6 +64,30 @@ const FaireSyncDetailPage = () => {
61
64
  }
62
65
  }
63
66
 
67
+ const handleDelete = async () => {
68
+ if (!record) return
69
+ const confirmed = await prompt({
70
+ title: "Remove from Faire?",
71
+ description:
72
+ "Faire has no hard-delete API, so this unpublishes the product (sets it back to draft, hidden from buyers) and unlinks it from the product. Re-sync to publish it again, or delete it permanently from the Faire brand portal.",
73
+ confirmText: "Remove",
74
+ cancelText: "Cancel",
75
+ })
76
+ if (!confirmed) return
77
+ setDeleting(true)
78
+ setError(null)
79
+ try {
80
+ await faireApi.deleteSync(record.id)
81
+ const updated = await faireApi.getSync(record.id).catch(() => null)
82
+ if (updated) setRecord((updated as any).sync || updated)
83
+ navigate(PARENT)
84
+ } catch (err: any) {
85
+ setError(err.message || "Remove failed")
86
+ } finally {
87
+ setDeleting(false)
88
+ }
89
+ }
90
+
64
91
  const warnings: string[] = record?.error_message
65
92
  ? record.error_message.split(" | ")
66
93
  : []
@@ -80,7 +107,7 @@ const FaireSyncDetailPage = () => {
80
107
  <div className="flex items-center gap-2">
81
108
  <DropdownMenu>
82
109
  <DropdownMenu.Trigger asChild>
83
- <IconButton size="small" variant="transparent" disabled={retrying}>
110
+ <IconButton size="small" variant="transparent" disabled={retrying || deleting}>
84
111
  <EllipsisHorizontal />
85
112
  </IconButton>
86
113
  </DropdownMenu.Trigger>
@@ -102,6 +129,15 @@ const FaireSyncDetailPage = () => {
102
129
  Open on Faire
103
130
  </DropdownMenu.Item>
104
131
  )}
132
+ <DropdownMenu.Separator />
133
+ <DropdownMenu.Item
134
+ className="gap-x-2 text-ui-fg-error"
135
+ onClick={handleDelete}
136
+ disabled={deleting}
137
+ >
138
+ <Trash className="text-ui-fg-error" />
139
+ {deleting ? "Removing…" : "Remove from Faire"}
140
+ </DropdownMenu.Item>
105
141
  </DropdownMenu.Content>
106
142
  </DropdownMenu>
107
143
  </div>
@@ -3,6 +3,7 @@ import { MedusaError } from "@medusajs/framework/utils"
3
3
  import { FAIRE_SYNC_MODULE } from "../../../../../modules/faire-sync"
4
4
  import FaireSyncService from "../../../../../modules/faire-sync/service"
5
5
  import { syncProductToFaireWorkflow } from "../../../../../workflows/sync-product-to-faire"
6
+ import { removeProductFromFaireWorkflow } from "../../../../../workflows/remove-product-from-faire"
6
7
 
7
8
  // GET /admin/faire/syncs/:id — fetch a single sync record
8
9
  export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
@@ -30,3 +31,21 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
30
31
 
31
32
  res.json({ result })
32
33
  }
34
+
35
+ // DELETE /admin/faire/syncs/:id — remove (unpublish) the Faire product for this
36
+ // record's product and clear the local link. Faire has no hard-delete API, so
37
+ // this sets the product back to DRAFT (hidden from buyers).
38
+ export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => {
39
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
40
+ const { id } = req.params
41
+ const [record] = await service.listFaireSyncRecords({ id } as any, { take: 1 } as any)
42
+ if (!record) {
43
+ throw new MedusaError(MedusaError.Types.NOT_FOUND, "Sync record not found")
44
+ }
45
+
46
+ const { result } = await removeProductFromFaireWorkflow(req.scope).run({
47
+ input: { product_id: (record as any).product_id },
48
+ })
49
+
50
+ res.json({ result })
51
+ }
@@ -0,0 +1,121 @@
1
+ import {
2
+ createStep,
3
+ createWorkflow,
4
+ StepResponse,
5
+ WorkflowResponse,
6
+ WorkflowData,
7
+ } from "@medusajs/framework/workflows-sdk"
8
+ import { ContainerRegistrationKeys, Modules } from "@medusajs/framework/utils"
9
+ import type { Link } from "@medusajs/modules-sdk"
10
+ import { FAIRE_SYNC_MODULE } from "../modules/faire-sync"
11
+ import FaireSyncService from "../modules/faire-sync/service"
12
+
13
+ export const REMOVE_PRODUCT_FROM_FAIRE = "faire-remove-product"
14
+
15
+ export type RemoveProductFromFaireInput = {
16
+ product_id: string
17
+ }
18
+
19
+ type RemoveResult = {
20
+ product_id: string
21
+ product_token: string | null
22
+ removed: boolean
23
+ warnings: string[]
24
+ }
25
+
26
+ // ── Step: unpublish the Faire product + clean up the local link/record ─────
27
+ //
28
+ // Faire's public Brand API exposes no hard-delete for products (only
29
+ // POST/PUT/GET on /products), so "remove" means unpublish: set the product's
30
+ // lifecycle_state back to DRAFT via the update endpoint, which pulls it from
31
+ // buyers' view. A true hard-delete has to be done in the Faire brand portal.
32
+
33
+ const removeFaireProductStep = createStep(
34
+ "faire-remove-product-step",
35
+ async (
36
+ input: { product_id: string },
37
+ { container }
38
+ ): Promise<StepResponse<RemoveResult>> => {
39
+ const service: FaireSyncService = container.resolve(FAIRE_SYNC_MODULE)
40
+ const remoteLink = container.resolve(ContainerRegistrationKeys.LINK) as Link
41
+
42
+ const account = await service.ensureFreshToken()
43
+ const client = service.getClient((account.auth_mode as "oauth" | "apiKey") ?? "oauth")
44
+ const warnings: string[] = []
45
+
46
+ // Find the linked Faire product for this product.
47
+ let product_token: string | null = null
48
+ try {
49
+ const linkRows = await remoteLink.list({
50
+ [Modules.PRODUCT]: { product_id: input.product_id },
51
+ [FAIRE_SYNC_MODULE]: { faire_sync_account_id: account.id },
52
+ })
53
+ const row: any = (linkRows as any[])?.[0]
54
+ product_token = row?.faire_product_token ?? null
55
+ } catch {
56
+ // no link
57
+ }
58
+
59
+ // Unpublish (DRAFT) so it disappears from the Faire storefront. Tolerate a
60
+ // 404 — a product already gone on Faire still cleans up our side.
61
+ if (product_token) {
62
+ try {
63
+ await client.updateProduct(account.access_token, product_token, {
64
+ lifecycle_state: "DRAFT",
65
+ })
66
+ } catch (err: any) {
67
+ const msg = String(err?.message || err)
68
+ if (/404|not found/i.test(msg)) {
69
+ warnings.push("Product was already removed on Faire.")
70
+ } else {
71
+ warnings.push(`Faire unpublish failed: ${msg}`)
72
+ }
73
+ }
74
+ } else {
75
+ warnings.push("No linked Faire product found for this product.")
76
+ }
77
+
78
+ // Drop the product↔account link so the product reads as un-synced.
79
+ try {
80
+ await remoteLink.dismiss([
81
+ {
82
+ [Modules.PRODUCT]: { product_id: input.product_id },
83
+ [FAIRE_SYNC_MODULE]: { faire_sync_account_id: account.id },
84
+ },
85
+ ])
86
+ } catch {
87
+ // ignore dismiss errors
88
+ }
89
+
90
+ // Historical record of the removal.
91
+ await service.createSyncRecord({
92
+ product_id: input.product_id,
93
+ account_id: account.id,
94
+ product_token,
95
+ product_url: null,
96
+ product_state: "draft",
97
+ action: "delete",
98
+ status: "success",
99
+ published: false,
100
+ error_message: warnings.length ? warnings.join(" | ") : null,
101
+ warnings,
102
+ metadata: {},
103
+ synced_at: new Date(),
104
+ } as any)
105
+
106
+ return new StepResponse({
107
+ product_id: input.product_id,
108
+ product_token,
109
+ removed: Boolean(product_token),
110
+ warnings,
111
+ })
112
+ }
113
+ )
114
+
115
+ export const removeProductFromFaireWorkflow = createWorkflow(
116
+ REMOVE_PRODUCT_FROM_FAIRE,
117
+ (input: WorkflowData<RemoveProductFromFaireInput>) => {
118
+ const result = removeFaireProductStep(input)
119
+ return new WorkflowResponse(result)
120
+ }
121
+ )