react-shopwave-connect 0.4.1 → 0.4.3

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/dist/index.cjs CHANGED
@@ -375,6 +375,106 @@ async function fetchProductsMap(productIds, options = {}, batchSize = 200) {
375
375
  return result;
376
376
  }
377
377
 
378
+ //#endregion
379
+ //#region src/core/resources.ts
380
+ /**
381
+ * - merchant: `GET /merchant`; `POST /merchant` with `{ merchant: { id, … } }`
382
+ * updates it (without an `id` the API would create a second merchant, so the
383
+ * route requires one).
384
+ * - user: `GET /user` (read-only here; the API's `POST /user` invites users and
385
+ * needs the admin scope).
386
+ */
387
+ const SHOPWAVE_RESOURCES = Object.freeze({
388
+ merchant: {
389
+ route: "merchant",
390
+ key: "merchant",
391
+ upstream: "merchant",
392
+ writable: true
393
+ },
394
+ user: {
395
+ route: "user",
396
+ key: "user",
397
+ upstream: "user",
398
+ writable: false
399
+ }
400
+ });
401
+ /** `UploadKind` → the API's `contentType` value. */
402
+ const UPLOAD_KINDS = Object.freeze({
403
+ merchant: "merchant",
404
+ merchantFeature: "merchantFeature",
405
+ product: "product",
406
+ user: "user",
407
+ applicationLogo: "applicaionLogo",
408
+ applicationImages: "applicaionImages"
409
+ });
410
+ /** How uploads are addressed (app route and Shopwave uploader). */
411
+ const SHOPWAVE_UPLOAD = Object.freeze({
412
+ route: "upload",
413
+ upstream: "uploader",
414
+ kindHeader: "contentType",
415
+ fileField: "file",
416
+ kindField: "kind"
417
+ });
418
+ function isUploadKind(value) {
419
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(UPLOAD_KINDS, value);
420
+ }
421
+ const SHOPWAVE_LOOKUPS = Object.freeze({
422
+ productTags: {
423
+ route: "products/tags",
424
+ key: "tags",
425
+ upstream: "product/tags"
426
+ },
427
+ productInstanceTags: {
428
+ route: "products/instance-tags",
429
+ key: "tags",
430
+ upstream: "product/instance/tags"
431
+ }
432
+ });
433
+
434
+ //#endregion
435
+ //#region src/core/productTags.ts
436
+ /**
437
+ * Turns a tag list from the API into clean strings: trims each tag, drops
438
+ * blanks and anything that isn't text, and removes exact duplicates (first one
439
+ * wins). The API's own order is kept. A map is accepted too (its values are
440
+ * used), in case the API ever answers keyed by id.
441
+ */
442
+ function normalizeTagList(value) {
443
+ const list = Array.isArray(value) ? value : value && typeof value === "object" ? Object.values(value) : [];
444
+ const seen = /* @__PURE__ */ new Set();
445
+ const out = [];
446
+ for (const item of list) {
447
+ if (typeof item !== "string" && typeof item !== "number") continue;
448
+ const tag = String(item).trim();
449
+ if (!tag || seen.has(tag)) continue;
450
+ seen.add(tag);
451
+ out.push(tag);
452
+ }
453
+ return out;
454
+ }
455
+ async function fetchLookup(kind, options) {
456
+ const def = SHOPWAVE_LOOKUPS[kind];
457
+ if (!def) throw new Error(`Unknown Shopwave lookup "${kind}"`);
458
+ const json = await apiGet(`/api/${def.route}`, {}, options);
459
+ assertNoApiErrors(json, 200);
460
+ return normalizeTagList(json?.[def.key]);
461
+ }
462
+ /**
463
+ * Every distinct tag used on the merchant's products (`GET /product/tags`),
464
+ * for a tag field's suggestions. Returns `[]` when there are none.
465
+ */
466
+ function fetchProductTags(options = {}) {
467
+ return fetchLookup("productTags", options);
468
+ }
469
+ /**
470
+ * Every distinct tag used on the merchant's price instances
471
+ * (`GET /product/instance/tags`): the channel names (Eat In, Takeout, …).
472
+ * Returns `[]` when there are none.
473
+ */
474
+ function fetchProductInstanceTags(options = {}) {
475
+ return fetchLookup("productInstanceTags", options);
476
+ }
477
+
378
478
  //#endregion
379
479
  //#region src/core/promotion.ts
380
480
  /** Promotion rule identifiers as stored by the Shopwave API. */
@@ -509,7 +609,10 @@ async function submitEntity({ endpoint, method = "POST", payload }, options = {}
509
609
  * (https://developer.merchantstack.com/api-reference.html) and checked against
510
610
  * the live API where noted:
511
611
  * - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
512
- * - promotion: no DELETE; "deleting" a promotion ends it by setting `endDate` to now.
612
+ * - promotion: no DELETE; promotions are never deleted, only expired. Expiring
613
+ * reads the promotion and saves the whole record back with `endDate` = now (and
614
+ * `startDate` = now too if it hadn't started); the live API rejects a partial
615
+ * `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
513
616
  * - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
514
617
  * changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
515
618
  * - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
@@ -550,7 +653,9 @@ const SHOPWAVE_ENTITIES = Object.freeze({
550
653
  idHeader: "promotionId",
551
654
  writable: true,
552
655
  deleteMode: "retire",
553
- retireField: "endDate"
656
+ retireField: "endDate",
657
+ retireWithRecord: true,
658
+ retireStartField: "startDate"
554
659
  },
555
660
  employee: {
556
661
  route: "employees",
@@ -625,7 +730,8 @@ async function saveEntity(kind, item, options = {}) {
625
730
  /**
626
731
  * Deletes one entity (a soft delete: it stays readable with `deleted: true`).
627
732
  * Employees and promotions can't be deleted in Shopwave; for them the route
628
- * sets `exitDate` / `endDate` to now instead (see `SHOPWAVE_ENTITIES`).
733
+ * sets `exitDate` / `endDate` to now instead (see `SHOPWAVE_ENTITIES`), and a
734
+ * promotion is saved back whole (see {@link deletePromotion}).
629
735
  *
630
736
  * Shopwave answers 205 with an empty body — also for ids that don't exist — so
631
737
  * a resolved promise means "the API accepted the request", not "a record was
@@ -666,7 +772,12 @@ const saveStore = (store, options) => saveEntity("store", store, options);
666
772
  const deleteStore = (id, options) => deleteEntityById("store", id, options);
667
773
  const fetchStore = (id, params, options) => fetchEntityById("store", id, params, options);
668
774
  const savePromotion = (promotion, options) => saveEntity("promotion", promotion, options);
669
- /** Ends a promotion: Shopwave has no promotion DELETE, so this sets `endDate` to now. */
775
+ /**
776
+ * Expires a promotion (Shopwave never deletes promotions). The route reads the
777
+ * promotion and saves it back with `endDate` set to now; a promotion that hasn't
778
+ * started yet gets `startDate` = `endDate` = now. Rejects if the promotion doesn't
779
+ * exist or the save fails.
780
+ */
670
781
  const deletePromotion = (id, options) => deleteEntityById("promotion", id, options);
671
782
  const fetchPromotion = (id, params, options) => fetchEntityById("promotion", id, params, options);
672
783
  /**
@@ -825,50 +936,6 @@ function buildConsumerMap(consumers) {
825
936
  return map;
826
937
  }
827
938
 
828
- //#endregion
829
- //#region src/core/resources.ts
830
- /**
831
- * - merchant: `GET /merchant`; `POST /merchant` with `{ merchant: { id, … } }`
832
- * updates it (without an `id` the API would create a second merchant, so the
833
- * route requires one).
834
- * - user: `GET /user` (read-only here; the API's `POST /user` invites users and
835
- * needs the admin scope).
836
- */
837
- const SHOPWAVE_RESOURCES = Object.freeze({
838
- merchant: {
839
- route: "merchant",
840
- key: "merchant",
841
- upstream: "merchant",
842
- writable: true
843
- },
844
- user: {
845
- route: "user",
846
- key: "user",
847
- upstream: "user",
848
- writable: false
849
- }
850
- });
851
- /** `UploadKind` → the API's `contentType` value. */
852
- const UPLOAD_KINDS = Object.freeze({
853
- merchant: "merchant",
854
- merchantFeature: "merchantFeature",
855
- product: "product",
856
- user: "user",
857
- applicationLogo: "applicaionLogo",
858
- applicationImages: "applicaionImages"
859
- });
860
- /** How uploads are addressed (app route and Shopwave uploader). */
861
- const SHOPWAVE_UPLOAD = Object.freeze({
862
- route: "upload",
863
- upstream: "uploader",
864
- kindHeader: "contentType",
865
- fileField: "file",
866
- kindField: "kind"
867
- });
868
- function isUploadKind(value) {
869
- return typeof value === "string" && Object.prototype.hasOwnProperty.call(UPLOAD_KINDS, value);
870
- }
871
-
872
939
  //#endregion
873
940
  //#region src/core/imageFit.ts
874
941
  /**
@@ -1535,6 +1602,33 @@ function useProduct(params = {}, options) {
1535
1602
  ]);
1536
1603
  }
1537
1604
 
1605
+ //#endregion
1606
+ //#region src/hooks/useProductTags.ts
1607
+ /**
1608
+ * The merchant's distinct product tags, for a tag field's suggestions.
1609
+ * `data` is `string[]` once loaded.
1610
+ *
1611
+ * @example
1612
+ * const [opened, setOpened] = useState(false);
1613
+ * const { data: tags, loading } = useProductTags({ enabled: opened });
1614
+ */
1615
+ function useProductTags(options) {
1616
+ return useQuery((signal) => fetchProductTags({
1617
+ ...options,
1618
+ signal
1619
+ }), [options?.baseUrl, options?.token], options?.enabled ?? true);
1620
+ }
1621
+ /**
1622
+ * The merchant's distinct price-instance (channel) tags: Eat In, Takeout, …
1623
+ * `data` is `string[]` once loaded.
1624
+ */
1625
+ function useProductInstanceTags(options) {
1626
+ return useQuery((signal) => fetchProductInstanceTags({
1627
+ ...options,
1628
+ signal
1629
+ }), [options?.baseUrl, options?.token], options?.enabled ?? true);
1630
+ }
1631
+
1538
1632
  //#endregion
1539
1633
  //#region src/hooks/usePromotion.ts
1540
1634
  /**
@@ -1858,6 +1952,7 @@ exports.MERCHANT_READ_ONLY_FIELDS = MERCHANT_READ_ONLY_FIELDS;
1858
1952
  exports.PromotionRuleSet = PromotionRuleSet;
1859
1953
  exports.SHOPWAVE_ENTITIES = SHOPWAVE_ENTITIES;
1860
1954
  exports.SHOPWAVE_IMAGE_BASE_URL = SHOPWAVE_IMAGE_BASE_URL;
1955
+ exports.SHOPWAVE_LOOKUPS = SHOPWAVE_LOOKUPS;
1861
1956
  exports.SHOPWAVE_RESOURCES = SHOPWAVE_RESOURCES;
1862
1957
  exports.SHOPWAVE_UPLOAD = SHOPWAVE_UPLOAD;
1863
1958
  exports.ShopwaveApiError = ShopwaveApiError;
@@ -1891,6 +1986,8 @@ exports.fetchEmployees = fetchEmployees;
1891
1986
  exports.fetchEntityById = fetchEntityById;
1892
1987
  exports.fetchMerchant = fetchMerchant;
1893
1988
  exports.fetchProduct = fetchProduct;
1989
+ exports.fetchProductInstanceTags = fetchProductInstanceTags;
1990
+ exports.fetchProductTags = fetchProductTags;
1894
1991
  exports.fetchProducts = fetchProducts;
1895
1992
  exports.fetchProductsMap = fetchProductsMap;
1896
1993
  exports.fetchPromotion = fetchPromotion;
@@ -1917,6 +2014,7 @@ exports.logoutPath = logoutPath;
1917
2014
  exports.merchantLinksFromForm = merchantLinksFromForm;
1918
2015
  exports.merchantLinksToForm = merchantLinksToForm;
1919
2016
  exports.normalizeMerchantLinks = normalizeMerchantLinks;
2017
+ exports.normalizeTagList = normalizeTagList;
1920
2018
  exports.parseBasketReportData = parseBasketReportData;
1921
2019
  exports.readImageSize = readImageSize;
1922
2020
  exports.rolesById = rolesById;
@@ -1943,6 +2041,8 @@ exports.useEmployee = useEmployee;
1943
2041
  exports.useLogout = useLogout;
1944
2042
  exports.useMerchant = useMerchant;
1945
2043
  exports.useProduct = useProduct;
2044
+ exports.useProductInstanceTags = useProductInstanceTags;
2045
+ exports.useProductTags = useProductTags;
1946
2046
  exports.usePromotion = usePromotion;
1947
2047
  exports.useReport = useReport;
1948
2048
  exports.useSession = useSession;