react-shopwave-connect 0.4.5 → 0.4.7
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/README.md +19 -11
- package/dist/core/index.cjs +281 -137
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +307 -160
- package/dist/core/index.d.ts +307 -160
- package/dist/core/index.js +276 -138
- package/dist/core/index.js.map +1 -1
- package/dist/hooks/index.cjs +268 -115
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +96 -29
- package/dist/hooks/index.d.ts +96 -29
- package/dist/hooks/index.js +268 -116
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +301 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +316 -160
- package/dist/index.d.ts +316 -160
- package/dist/index.js +295 -138
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +69 -2
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +36 -5
- package/dist/next/index.d.ts +36 -5
- package/dist/next/index.js +69 -2
- package/dist/next/index.js.map +1 -1
- package/dist/server/index.cjs +70 -2
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +45 -8
- package/dist/server/index.d.ts +45 -8
- package/dist/server/index.js +70 -3
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -1
package/dist/hooks/index.cjs
CHANGED
|
@@ -203,12 +203,15 @@ async function apiSend(path, method, payload, options = {}) {
|
|
|
203
203
|
/**
|
|
204
204
|
* DELETE helper. Shopwave answers deletes with 205 and an empty body; if a body
|
|
205
205
|
* does come back and carries API errors, they are thrown. Returns the parsed
|
|
206
|
-
* body (`null` when empty).
|
|
206
|
+
* body (`null` when empty). `extras`, when given, travel in the `extras` header
|
|
207
|
+
* (the server forwards only keys its delete route allows).
|
|
207
208
|
*/
|
|
208
|
-
async function apiDelete(path, options = {}) {
|
|
209
|
+
async function apiDelete(path, options = {}, extras) {
|
|
210
|
+
const headers = baseHeaders(options);
|
|
211
|
+
if (extras) headers.extras = JSON.stringify(buildExtras(extras));
|
|
209
212
|
const { status, body } = await send(path, {
|
|
210
213
|
method: "DELETE",
|
|
211
|
-
headers
|
|
214
|
+
headers
|
|
212
215
|
}, options);
|
|
213
216
|
if (getApiErrorMap(body)) throw toShopwaveApiError(status, body);
|
|
214
217
|
return body;
|
|
@@ -395,115 +398,6 @@ function fetchProductInstanceTags(options = {}) {
|
|
|
395
398
|
return fetchLookup("productInstanceTags", options);
|
|
396
399
|
}
|
|
397
400
|
|
|
398
|
-
//#endregion
|
|
399
|
-
//#region src/core/promotion.ts
|
|
400
|
-
/**
|
|
401
|
-
* Fetches promotions. Returns the flattened `Promotion[]`.
|
|
402
|
-
*/
|
|
403
|
-
async function fetchPromotions(params = {}, options = {}) {
|
|
404
|
-
const extras = { deleted: params.deleted ?? false };
|
|
405
|
-
if (params.promotionIds) extras.promotionIds = params.promotionIds;
|
|
406
|
-
if (params.imminent !== void 0) extras.imminent = params.imminent;
|
|
407
|
-
if (params.active !== void 0) extras.active = params.active;
|
|
408
|
-
if (params.expired !== void 0) extras.expired = params.expired;
|
|
409
|
-
const json = await apiGet("/api/promotions", extras, withToken(options, params.token));
|
|
410
|
-
assertNoApiErrors(json, 200);
|
|
411
|
-
return Object.values(json?.promotions ?? {});
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
//#endregion
|
|
415
|
-
//#region src/core/store.ts
|
|
416
|
-
/**
|
|
417
|
-
* Fetches stores. Returns the flattened `Store[]`.
|
|
418
|
-
*/
|
|
419
|
-
async function fetchStores(params = {}, options = {}) {
|
|
420
|
-
const extras = { deleted: params.deleted ?? false };
|
|
421
|
-
if (params.storeIds) extras.storeIds = params.storeIds;
|
|
422
|
-
const json = await apiGet("/api/stores", extras, withToken(options, params.token));
|
|
423
|
-
assertNoApiErrors(json, 200);
|
|
424
|
-
return Object.values(json?.stores ?? {});
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
//#endregion
|
|
428
|
-
//#region src/core/report.ts
|
|
429
|
-
/**
|
|
430
|
-
* Runs one or more report queries and returns the `reports` map keyed by query
|
|
431
|
-
* name. The query is JSON-encoded into the `extras.query` header, matching the
|
|
432
|
-
* original endpoint contract.
|
|
433
|
-
*/
|
|
434
|
-
async function fetchReport(query, options = {}) {
|
|
435
|
-
const json = await apiGet("/api/report", { query: JSON.stringify(query) }, options);
|
|
436
|
-
assertNoApiErrors(json, 200);
|
|
437
|
-
return json?.reports ?? {};
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
//#endregion
|
|
441
|
-
//#region src/core/session.ts
|
|
442
|
-
/**
|
|
443
|
-
* Reads the current login status from `GET /api/session`.
|
|
444
|
-
*
|
|
445
|
-
* Works with the new `react-shopwave-connect/next` session route
|
|
446
|
-
* (`{ loggedIn }`) and with older routes that returned the raw session
|
|
447
|
-
* (`{ token: {...} }`) — in that case only a boolean is surfaced.
|
|
448
|
-
*/
|
|
449
|
-
async function fetchSession(options = {}) {
|
|
450
|
-
const doFetch = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
451
|
-
if (!doFetch) throw new Error("No fetch implementation available. Pass `options.fetch`.");
|
|
452
|
-
const response = await doFetch(`${options.baseUrl ?? ""}/api/session`, {
|
|
453
|
-
method: "GET",
|
|
454
|
-
headers: { Accept: "application/json" },
|
|
455
|
-
cache: "no-store",
|
|
456
|
-
credentials: "same-origin",
|
|
457
|
-
signal: options.signal
|
|
458
|
-
});
|
|
459
|
-
if (response.status === 401) return { loggedIn: false };
|
|
460
|
-
if (!response.ok) throw new Error(`Session request failed (${response.status})`);
|
|
461
|
-
const json = await response.json();
|
|
462
|
-
if (json && typeof json.loggedIn === "boolean") return {
|
|
463
|
-
loggedIn: json.loggedIn,
|
|
464
|
-
expiresAt: typeof json.expiresAt === "number" ? json.expiresAt : void 0
|
|
465
|
-
};
|
|
466
|
-
return { loggedIn: Boolean(json && json.token) };
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Ends the current server session (no redirect).
|
|
470
|
-
*
|
|
471
|
-
* Prefer navigating to {@link logoutPath} when using
|
|
472
|
-
* `react-shopwave-connect/next`: it also signs the user out of the auth server.
|
|
473
|
-
*/
|
|
474
|
-
async function logout(options = {}) {
|
|
475
|
-
await apiDelete("/api/session?action=logout", options);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
//#endregion
|
|
479
|
-
//#region src/core/entity.ts
|
|
480
|
-
/**
|
|
481
|
-
* Low-level, untyped mutations: the caller supplies the route and payload.
|
|
482
|
-
*
|
|
483
|
-
* Prefer the typed functions in `./entities` (`saveProduct`, `deleteCategory`,
|
|
484
|
-
* …): they know each route, build the request envelope, check the response and
|
|
485
|
-
* return the saved entity with its id. Keep these for routes that have no typed
|
|
486
|
-
* function yet (e.g. `/api/merchant`).
|
|
487
|
-
*/
|
|
488
|
-
/**
|
|
489
|
-
* Deletes `/api/{resourcePath}/{entityId}`.
|
|
490
|
-
*/
|
|
491
|
-
async function deleteEntity(resourcePath, entityId, options = {}) {
|
|
492
|
-
if (entityId == null || entityId === "") throw new Error("entityId is missing");
|
|
493
|
-
await apiDelete(`/api/${resourcePath}/${entityId}`, options);
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Sends a create/update mutation. Returns the response's `result` field when it
|
|
497
|
-
* has one (older app routes wrapped their answer that way), otherwise the whole
|
|
498
|
-
* parsed response body. Throws `ShopwaveApiError` on HTTP or API errors.
|
|
499
|
-
*/
|
|
500
|
-
async function submitEntity({ endpoint, method = "POST", payload }, options = {}) {
|
|
501
|
-
const json = await apiSend(endpoint, method, payload, options);
|
|
502
|
-
assertNoApiErrors(json, 200);
|
|
503
|
-
if (json && typeof json === "object" && "result" in json) return json.result;
|
|
504
|
-
return json ?? void 0;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
401
|
//#endregion
|
|
508
402
|
//#region src/core/imageFit.ts
|
|
509
403
|
/**
|
|
@@ -599,7 +493,7 @@ const STATIC_HOST = /^https?:\/\/static\.merchantstack\.com\/images\//i;
|
|
|
599
493
|
/**
|
|
600
494
|
* Turns an image path from the API into a URL a page can load over HTTPS:
|
|
601
495
|
* `http://static.merchantstack.com/images/x.png` → `<base>/x.png`, and a
|
|
602
|
-
* relative path (`merchant/
|
|
496
|
+
* relative path (`merchant/123/logo/x.png`) → `<base>/merchant/123/logo/x.png`.
|
|
603
497
|
* Other absolute URLs are returned unchanged; empty input gives `""`.
|
|
604
498
|
*/
|
|
605
499
|
function getImageUrl(path, base = SHOPWAVE_IMAGE_BASE_URL) {
|
|
@@ -645,7 +539,7 @@ async function uploadImage(file, params, options = {}) {
|
|
|
645
539
|
//#endregion
|
|
646
540
|
//#region src/core/merchantImages.ts
|
|
647
541
|
/**
|
|
648
|
-
* Where each slot lives, its upload kind and its exact size (from
|
|
542
|
+
* Where each slot lives, its upload kind and its exact size (from the existing admin's
|
|
649
543
|
* merchant settings page).
|
|
650
544
|
*/
|
|
651
545
|
const MERCHANT_IMAGE_SLOTS = Object.freeze({
|
|
@@ -695,7 +589,7 @@ function toStored(value) {
|
|
|
695
589
|
}
|
|
696
590
|
/**
|
|
697
591
|
* The `imageIds` to send on a save: every URL is turned back into its file
|
|
698
|
-
* name (what
|
|
592
|
+
* name (what the existing admin does with images you didn't change) and empty values
|
|
699
593
|
* are dropped. No images gives `{}`.
|
|
700
594
|
*/
|
|
701
595
|
function toStoredImageIds(imageIds) {
|
|
@@ -713,6 +607,245 @@ async function uploadMerchantImage(file, slot, options = {}) {
|
|
|
713
607
|
return uploadImage(fit ? await fitImageToSize(file, def.width, def.height, fitOptions) : file, { kind: def.uploadKind }, requestOptions);
|
|
714
608
|
}
|
|
715
609
|
|
|
610
|
+
//#endregion
|
|
611
|
+
//#region src/core/entities.ts
|
|
612
|
+
/**
|
|
613
|
+
* Addressing per entity, following the Shopwave API reference
|
|
614
|
+
* (https://developer.merchantstack.com/api-reference.html) and checked against
|
|
615
|
+
* the live API where noted:
|
|
616
|
+
* - category/product/store: GET filtered by `<x>Ids`, POST upsert, DELETE with `<x>Id` → 205.
|
|
617
|
+
* - promotion: no DELETE; promotions are never deleted, only expired. Expiring
|
|
618
|
+
* reads the promotion and saves the whole record back with `endDate` = now (and
|
|
619
|
+
* `startDate` = now too if it hadn't started); the live API rejects a partial
|
|
620
|
+
* `{ id, endDate }` save with error 899. Dates are UTC `YYYY-MM-DD HH:mm:ss`.
|
|
621
|
+
* - employee: POST updates `roleId`, `joinedDate`, `exitDate` (names can't be
|
|
622
|
+
* changed); no DELETE (live API: "Cannot DELETE /employee") → retired via `exitDate`.
|
|
623
|
+
* - consumer: read-only (`GET` with `ids`, comma-separated); live POST → 404.
|
|
624
|
+
* - productComponent: modifier groups, `/product/component` (live, staging, 11 Nov 2026). The
|
|
625
|
+
* API reference lists no read filters; these are what the live API does:
|
|
626
|
+
* - Reads filter by `componentIds` (group ids) or `productIds` (the parent product; option
|
|
627
|
+
* products don't match). `productComponentIds` is ignored on reads: every group comes back.
|
|
628
|
+
* `deleted: false` gives live groups only, `true` deleted groups only, no header both.
|
|
629
|
+
* No match → 204 with an empty body. No order, sort, priority or position field anywhere.
|
|
630
|
+
* - Groups are keyed by id; `products` (option products) by product id; `productInstances`
|
|
631
|
+
* (option rows) by row id. Reads reformat values: price `"100"` → `"100.0"`,
|
|
632
|
+
* vatPercentage `"0.2000"` → `"0.200"`. `tags` and `name` are stored as sent (no trimming;
|
|
633
|
+
* a `tags` array is joined with commas; `tags` null or left out → null).
|
|
634
|
+
* - POST upserts (201, echoed under the ref). For each option product in `products`, its rows
|
|
635
|
+
* are replaced: every row sent is created afresh with a new id (a row `id` is ignored, also
|
|
636
|
+
* on a price change) and rows left out are removed. Option products left out of the save
|
|
637
|
+
* are untouched, and an empty `productInstances` removes nothing. A save without `products`
|
|
638
|
+
* updates only the group's own fields. The echo isn't proof of what was stored: renames of
|
|
639
|
+
* groups created moments earlier by a test run echoed the new name but never stuck (re-read
|
|
640
|
+
* for 90 s), while older groups, and groups created in Shopwave's own admin, renamed at
|
|
641
|
+
* once. Cause not yet known.
|
|
642
|
+
* - DELETE with `productComponentIds` (plural, comma list) → 205, soft delete. Adding
|
|
643
|
+
* `productComponentInstanceIds` deletes just those rows (205); they stay in live reads with
|
|
644
|
+
* `deleteDate` set. The singular `productComponentId` hangs (504 after 60 s).
|
|
645
|
+
*/
|
|
646
|
+
const SHOPWAVE_ENTITIES = Object.freeze({
|
|
647
|
+
product: {
|
|
648
|
+
route: "products",
|
|
649
|
+
collection: "products",
|
|
650
|
+
upstream: "product",
|
|
651
|
+
idsHeader: "productIds",
|
|
652
|
+
idHeader: "productId",
|
|
653
|
+
writable: true,
|
|
654
|
+
deleteMode: "delete"
|
|
655
|
+
},
|
|
656
|
+
category: {
|
|
657
|
+
route: "categories",
|
|
658
|
+
collection: "categories",
|
|
659
|
+
upstream: "category",
|
|
660
|
+
idsHeader: "categoryIds",
|
|
661
|
+
idHeader: "categoryId",
|
|
662
|
+
writable: true,
|
|
663
|
+
deleteMode: "delete"
|
|
664
|
+
},
|
|
665
|
+
store: {
|
|
666
|
+
route: "stores",
|
|
667
|
+
collection: "stores",
|
|
668
|
+
upstream: "store",
|
|
669
|
+
idsHeader: "storeIds",
|
|
670
|
+
idHeader: "storeId",
|
|
671
|
+
writable: true,
|
|
672
|
+
deleteMode: "delete"
|
|
673
|
+
},
|
|
674
|
+
promotion: {
|
|
675
|
+
route: "promotions",
|
|
676
|
+
collection: "promotions",
|
|
677
|
+
upstream: "promotion",
|
|
678
|
+
idsHeader: "promotionIds",
|
|
679
|
+
idHeader: "promotionId",
|
|
680
|
+
writable: true,
|
|
681
|
+
deleteMode: "retire",
|
|
682
|
+
retireField: "endDate",
|
|
683
|
+
retireWithRecord: true,
|
|
684
|
+
retireStartField: "startDate"
|
|
685
|
+
},
|
|
686
|
+
employee: {
|
|
687
|
+
route: "employees",
|
|
688
|
+
collection: "employees",
|
|
689
|
+
upstream: "employee",
|
|
690
|
+
idsHeader: "employeeIds",
|
|
691
|
+
idHeader: "employeeId",
|
|
692
|
+
writable: true,
|
|
693
|
+
deleteMode: "retire",
|
|
694
|
+
retireField: "exitDate"
|
|
695
|
+
},
|
|
696
|
+
consumer: {
|
|
697
|
+
route: "consumer",
|
|
698
|
+
collection: "consumers",
|
|
699
|
+
upstream: "consumer",
|
|
700
|
+
idsHeader: "ids",
|
|
701
|
+
idHeader: "consumerId",
|
|
702
|
+
writable: false,
|
|
703
|
+
deleteMode: "none"
|
|
704
|
+
},
|
|
705
|
+
productComponent: {
|
|
706
|
+
route: "product-components",
|
|
707
|
+
collection: "productComponents",
|
|
708
|
+
upstream: "product/component",
|
|
709
|
+
idsHeader: "componentIds",
|
|
710
|
+
idHeader: "productComponentIds",
|
|
711
|
+
writable: true,
|
|
712
|
+
deleteMode: "delete",
|
|
713
|
+
childIdsHeader: "productComponentInstanceIds"
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
//#endregion
|
|
718
|
+
//#region src/core/productComponent.ts
|
|
719
|
+
const isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
720
|
+
/**
|
|
721
|
+
* Reads product components, optionally filtered by parent product and/or group id.
|
|
722
|
+
* With no filter it returns every group on the merchant. Returns `[]` when nothing matches
|
|
723
|
+
* (the API answers 204), and when a filter list is empty (no request is made).
|
|
724
|
+
*
|
|
725
|
+
* Rows deleted with {@link deleteProductComponentInstances} are still returned, with
|
|
726
|
+
* `deleteDate` set; skip them when listing options.
|
|
727
|
+
*/
|
|
728
|
+
async function fetchProductComponents(params = {}, options = {}) {
|
|
729
|
+
const def = SHOPWAVE_ENTITIES.productComponent;
|
|
730
|
+
if (params.productIds?.length === 0 || params.productComponentIds?.length === 0) return [];
|
|
731
|
+
const extras = { deleted: params.deleted ?? false };
|
|
732
|
+
if (params.productIds) extras.productIds = params.productIds;
|
|
733
|
+
if (params.productComponentIds) extras[def.idsHeader] = params.productComponentIds;
|
|
734
|
+
const json = await apiGet(`/api/${def.route}`, extras, options);
|
|
735
|
+
assertNoApiErrors(json, 200);
|
|
736
|
+
const map = json?.[def.collection];
|
|
737
|
+
return isObject(map) ? Object.values(map) : [];
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/core/promotion.ts
|
|
742
|
+
/**
|
|
743
|
+
* Fetches promotions. Returns the flattened `Promotion[]`.
|
|
744
|
+
*/
|
|
745
|
+
async function fetchPromotions(params = {}, options = {}) {
|
|
746
|
+
const extras = { deleted: params.deleted ?? false };
|
|
747
|
+
if (params.promotionIds) extras.promotionIds = params.promotionIds;
|
|
748
|
+
if (params.imminent !== void 0) extras.imminent = params.imminent;
|
|
749
|
+
if (params.active !== void 0) extras.active = params.active;
|
|
750
|
+
if (params.expired !== void 0) extras.expired = params.expired;
|
|
751
|
+
const json = await apiGet("/api/promotions", extras, withToken(options, params.token));
|
|
752
|
+
assertNoApiErrors(json, 200);
|
|
753
|
+
return Object.values(json?.promotions ?? {});
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
//#endregion
|
|
757
|
+
//#region src/core/store.ts
|
|
758
|
+
/**
|
|
759
|
+
* Fetches stores. Returns the flattened `Store[]`.
|
|
760
|
+
*/
|
|
761
|
+
async function fetchStores(params = {}, options = {}) {
|
|
762
|
+
const extras = { deleted: params.deleted ?? false };
|
|
763
|
+
if (params.storeIds) extras.storeIds = params.storeIds;
|
|
764
|
+
const json = await apiGet("/api/stores", extras, withToken(options, params.token));
|
|
765
|
+
assertNoApiErrors(json, 200);
|
|
766
|
+
return Object.values(json?.stores ?? {});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
//#endregion
|
|
770
|
+
//#region src/core/report.ts
|
|
771
|
+
/**
|
|
772
|
+
* Runs one or more report queries and returns the `reports` map keyed by query
|
|
773
|
+
* name. The query is JSON-encoded into the `extras.query` header, matching the
|
|
774
|
+
* original endpoint contract.
|
|
775
|
+
*/
|
|
776
|
+
async function fetchReport(query, options = {}) {
|
|
777
|
+
const json = await apiGet("/api/report", { query: JSON.stringify(query) }, options);
|
|
778
|
+
assertNoApiErrors(json, 200);
|
|
779
|
+
return json?.reports ?? {};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
//#endregion
|
|
783
|
+
//#region src/core/session.ts
|
|
784
|
+
/**
|
|
785
|
+
* Reads the current login status from `GET /api/session`.
|
|
786
|
+
*
|
|
787
|
+
* Works with the new `react-shopwave-connect/next` session route
|
|
788
|
+
* (`{ loggedIn }`) and with older routes that returned the raw session
|
|
789
|
+
* (`{ token: {...} }`) — in that case only a boolean is surfaced.
|
|
790
|
+
*/
|
|
791
|
+
async function fetchSession(options = {}) {
|
|
792
|
+
const doFetch = options.fetch ?? (typeof fetch !== "undefined" ? fetch : void 0);
|
|
793
|
+
if (!doFetch) throw new Error("No fetch implementation available. Pass `options.fetch`.");
|
|
794
|
+
const response = await doFetch(`${options.baseUrl ?? ""}/api/session`, {
|
|
795
|
+
method: "GET",
|
|
796
|
+
headers: { Accept: "application/json" },
|
|
797
|
+
cache: "no-store",
|
|
798
|
+
credentials: "same-origin",
|
|
799
|
+
signal: options.signal
|
|
800
|
+
});
|
|
801
|
+
if (response.status === 401) return { loggedIn: false };
|
|
802
|
+
if (!response.ok) throw new Error(`Session request failed (${response.status})`);
|
|
803
|
+
const json = await response.json();
|
|
804
|
+
if (json && typeof json.loggedIn === "boolean") return {
|
|
805
|
+
loggedIn: json.loggedIn,
|
|
806
|
+
expiresAt: typeof json.expiresAt === "number" ? json.expiresAt : void 0
|
|
807
|
+
};
|
|
808
|
+
return { loggedIn: Boolean(json && json.token) };
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Ends the current server session (no redirect).
|
|
812
|
+
*
|
|
813
|
+
* Prefer navigating to {@link logoutPath} when using
|
|
814
|
+
* `react-shopwave-connect/next`: it also signs the user out of the auth server.
|
|
815
|
+
*/
|
|
816
|
+
async function logout(options = {}) {
|
|
817
|
+
await apiDelete("/api/session?action=logout", options);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
//#endregion
|
|
821
|
+
//#region src/core/entity.ts
|
|
822
|
+
/**
|
|
823
|
+
* Low-level, untyped mutations: the caller supplies the route and payload.
|
|
824
|
+
*
|
|
825
|
+
* Prefer the typed functions in `./entities` (`saveProduct`, `deleteCategory`,
|
|
826
|
+
* …): they know each route, build the request envelope, check the response and
|
|
827
|
+
* return the saved entity with its id. Keep these for routes that have no typed
|
|
828
|
+
* function yet (e.g. `/api/merchant`).
|
|
829
|
+
*/
|
|
830
|
+
/**
|
|
831
|
+
* Deletes `/api/{resourcePath}/{entityId}`.
|
|
832
|
+
*/
|
|
833
|
+
async function deleteEntity(resourcePath, entityId, options = {}) {
|
|
834
|
+
if (entityId == null || entityId === "") throw new Error("entityId is missing");
|
|
835
|
+
await apiDelete(`/api/${resourcePath}/${entityId}`, options);
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Sends a create/update mutation. Returns the response's `result` field when it
|
|
839
|
+
* has one (older app routes wrapped their answer that way), otherwise the whole
|
|
840
|
+
* parsed response body. Throws `ShopwaveApiError` on HTTP or API errors.
|
|
841
|
+
*/
|
|
842
|
+
async function submitEntity({ endpoint, method = "POST", payload }, options = {}) {
|
|
843
|
+
const json = await apiSend(endpoint, method, payload, options);
|
|
844
|
+
assertNoApiErrors(json, 200);
|
|
845
|
+
if (json && typeof json === "object" && "result" in json) return json.result;
|
|
846
|
+
return json ?? void 0;
|
|
847
|
+
}
|
|
848
|
+
|
|
716
849
|
//#endregion
|
|
717
850
|
//#region src/core/basket.ts
|
|
718
851
|
const DEFAULT_BASKET_FILTERS = {
|
|
@@ -1227,6 +1360,25 @@ function useProductInstanceTags(options) {
|
|
|
1227
1360
|
}), [options?.baseUrl, options?.token], options?.enabled ?? true);
|
|
1228
1361
|
}
|
|
1229
1362
|
|
|
1363
|
+
//#endregion
|
|
1364
|
+
//#region src/hooks/useProductComponent.ts
|
|
1365
|
+
/**
|
|
1366
|
+
* Auto-fetches product components (modifier groups) on mount and whenever the params change:
|
|
1367
|
+
* a product's groups with `{ productIds: [id] }`, one group with `{ productComponentIds: [id] }`,
|
|
1368
|
+
* or every group with no filter. Option rows with a `deleteDate` are included; skip them when
|
|
1369
|
+
* listing options.
|
|
1370
|
+
*/
|
|
1371
|
+
function useProductComponent(params = {}, options) {
|
|
1372
|
+
return useQuery((signal) => fetchProductComponents(params, {
|
|
1373
|
+
...options,
|
|
1374
|
+
signal
|
|
1375
|
+
}), [
|
|
1376
|
+
JSON.stringify(params),
|
|
1377
|
+
options?.baseUrl,
|
|
1378
|
+
options?.token
|
|
1379
|
+
]);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1230
1382
|
//#endregion
|
|
1231
1383
|
//#region src/hooks/usePromotion.ts
|
|
1232
1384
|
/**
|
|
@@ -1545,6 +1697,7 @@ exports.useEmployee = useEmployee;
|
|
|
1545
1697
|
exports.useLogout = useLogout;
|
|
1546
1698
|
exports.useMerchant = useMerchant;
|
|
1547
1699
|
exports.useProduct = useProduct;
|
|
1700
|
+
exports.useProductComponent = useProductComponent;
|
|
1548
1701
|
exports.useProductInstanceTags = useProductInstanceTags;
|
|
1549
1702
|
exports.useProductTags = useProductTags;
|
|
1550
1703
|
exports.usePromotion = usePromotion;
|