react-shopwave-connect 0.4.5 → 0.4.6

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 CHANGED
@@ -191,18 +191,18 @@ await updateMerchant({ imageIds: setMerchantImage(merchant?.imageIds, "square",
191
191
  getMerchantImageUrl(saved.imageIds, "square"); // HTTPS URL for <img src>
192
192
  ```
193
193
 
194
- The shapes below are the ones adminV1 saves and adminV2 reads, so all three admins share one record (confirmed on the live API with merchant 5644, 30 Sep 2026):
194
+ The shapes below are the ones Shopwave's existing admin apps save and read, so every client shares one record (confirmed on the live API, 30 Sep 2026):
195
195
 
196
196
  | Field | Shape |
197
197
  |---|---|
198
198
  | `imageIds` | `{ logo: { receipt, square }, feature: [ … ] }` — **file names** on write (the uploader's `fileName`), **full `http://static.merchantstack.com/…` URLs** on read |
199
- | `colours` | `{ primary: { main, highlight, contrast } }` (hex strings; adminV2 uses `primary.main` behind the logo) |
199
+ | `colours` | `{ primary: { main, highlight, contrast } }` (hex strings; `primary.main` is the colour shown behind the logo) |
200
200
  | `links` | `{ website: { home }, social: { twitter, facebook, instagram } }` |
201
201
 
202
202
  - **Merchant fields** (`GET`/`POST /merchant`): `id`, `name`, `description`, `companyNumber`, `vatNumber`, `categoryId`, `estAnnualRevenue`, `note`, and the three objects above. The API returns `{}` for an empty object.
203
203
  - **What the live API does on `POST /merchant`** (checked 30 Sep 2026): it replaces the record. A scalar field left out (`companyNumber`, `vatNumber`, `note`, …) becomes `null`; an object field left out (`colours`, `links`, `imageIds`) is kept, and one that is sent replaces the stored one (`{}` clears it). It answers **205 with an empty body**. It silently cuts `companyNumber` to 11 characters and `vatNumber` to 9 (a UK VAT number without `GB`).
204
204
  - **`updateMerchant(patch, { merge })`**: `merge` (default `true`) reads first and merges the scalar fields, so nothing is wiped. Object fields (`MERCHANT_OBJECT_FIELDS`) are sent only when the patch has them, and then replace the stored one — to change one image, pass the whole `imageIds` (`setMerchantImage` does that). `imageIds` is always sent as file names (`toStoredImageIds`), so you can pass back what you read. It refuses a different merchant `id`, never sends `createdDate`/`modifiedDate`, rejects values longer than `MERCHANT_FIELD_MAX_LENGTH` (`validateMerchant(m)` lists them) instead of letting the API cut them, and returns the merchant **read back after the save**, so you see exactly what was kept. `merge: false` sends only the patch (which must include `id`, and clears the scalar fields you leave out).
205
- - **Merchant image slots** (`MERCHANT_IMAGE_SLOTS`): the API doesn't resize, and adminV1 accepts only these exact sizes.
205
+ - **Merchant image slots** (`MERCHANT_IMAGE_SLOTS`): the API doesn't resize, and Shopwave's existing admin accepts only these exact sizes.
206
206
 
207
207
  | Slot | Stored at | Upload kind | Size |
208
208
  |---|---|---|---|
@@ -210,13 +210,13 @@ The shapes below are the ones adminV1 saves and adminV2 reads, so all three admi
210
210
  | `square` | `imageIds.logo.square` | `merchant` | 1000 × 1000 |
211
211
  | `featured` | `imageIds.feature[0]` | `merchantFeature` | 1200 × 600 |
212
212
 
213
- The API builds each image URL from its key, not from where the file was uploaded (`logo.*` → `…/merchant/<id>/logo/<file>`, `feature` → `…/feature/<file>`), so the featured image must be uploaded as `merchantFeature`. `POST /merchant` stores all three objects with the SDK's `{ merchant: … }` body; adminV1's `{ merchants: { "0": … } }` form isn't needed.
213
+ The API builds each image URL from its key, not from where the file was uploaded (`logo.*` → `…/merchant/<id>/logo/<file>`, `feature` → `…/feature/<file>`), so the featured image must be uploaded as `merchantFeature`. `POST /merchant` stores all three objects with the SDK's `{ merchant: … }` body; the existing admin's `{ merchants: { "0": … } }` form isn't needed.
214
214
 
215
215
  `uploadMerchantImage(file, slot, { fit = true })` scales and centre-crops the image to the slot's size in the browser (`fitImageToSize`, `coverCrop`) and uploads it with the slot's kind; `fit: false` sends the file as-is. `getMerchantImage` / `getMerchantImageUrl` / `setMerchantImage` read and write a slot; `imageFileName(url)` gives the file name in a URL. Hook: `useUploadMerchantImage()`.
216
216
  - **Links:** `merchantLinksToForm(links)` gives flat fields (`website`, `twitter`, `facebook`, `instagram`) from the stored shape, flat keys or the old array form; `merchantLinksFromForm(fields)` builds the stored shape; `normalizeMerchantLinks(links)` converts any of them to the stored shape. Colour keys: `MERCHANT_COLOUR_KEYS`.
217
- - **User** (`GET /user`): the live API returns `id`, `firstName`, `lastName`, `email` and `employee: { merchantId, roleId, stores }` — no `createdDate` and **no `merchant`**, although the reference lists them. There's no option to include it (adminV1 also reads `GET /merchant` separately and attaches it to its session). Read the merchant with `fetchMerchant()`.
217
+ - **User** (`GET /user`): the live API returns `id`, `firstName`, `lastName`, `email` and `employee: { merchantId, roleId, stores }` — no `createdDate` and **no `merchant`**, although the reference lists them. There's no option to include it (Shopwave's existing admin also reads `GET /merchant` separately and attaches it to its session). Read the merchant with `fetchMerchant()`.
218
218
  - **`uploadImage(file, { kind, prefix?, fileName? })`** posts the file to `/api/upload`, which `PUT`s it to Shopwave's `/uploader` (multipart `file`, header `contentType: <kind>`). Kinds: `merchant`, `merchantFeature`, `product`, `user`, `applicationLogo`, `applicationImages` (the last two sent as the API's own spelling, `applicaionLogo`/`applicaionImages`). Live: 201 `{ fileName, path }`. Returns `{ id, url, path }`: `id` is the generated file name (what records store), `path` the URL as the API gave it (`http://static.merchantstack.com/images/…`, which has no HTTPS endpoint), and `url` the same file over HTTPS via `getImageUrl(path)` (`SHOPWAVE_IMAGE_BASE_URL`, the S3 bucket behind the CDN). The upload alone changes no record.
219
- - **`prefix`** (optional) is the image's role, sent as the uploader's `prefix` header: `product`, `product_thumbnail`, `variant`, `variant_thumbnail` (`UPLOAD_PREFIXES`). The uploader then names the file `<prefix>_<hash>.<ext>` (live, 5644, 4 Nov 2026: `product_thumbnail_1bdd….png`), so the role travels with the file; `productImageRole(nameOrUrl)` reads it back (`null` for a name without one). Leave it out for images without a role (merchant logos). An unknown prefix throws before any request; the route answers 400 to one.
219
+ - **`prefix`** (optional) is the image's role, sent as the uploader's `prefix` header: `product`, `product_thumbnail`, `variant`, `variant_thumbnail` (`UPLOAD_PREFIXES`). The uploader then names the file `<prefix>_<hash>.<ext>` (live, 4 Nov 2026: `product_thumbnail_<hash>.png`), so the role travels with the file; `productImageRole(nameOrUrl)` reads it back (`null` for a name without one). Leave it out for images without a role (merchant logos). An unknown prefix throws before any request; the route answers 400 to one.
220
220
  - **Product images:** `images` is a list — **full URLs on read** (`http://static.merchantstack.com/images/merchant/<id>/product/<file>`), **file names on write**. The uploader takes one file per request, so upload each file on its own (`uploadImage(file, { kind: "product", prefix })`) and save the product with all the names. `saveProduct` sends `images` as file names (`toStoredProductImages`), so URLs read back can be passed straight in; leaving `images` out keeps them.
221
221
 
222
222
  - **Image rules (browser).** Shopwave doesn't resize uploads, so prepare each image before `uploadImage` with `prepareImage(file, rule, { type?, quality? })`. A rule is an exact size or a shape plus limits; the image is scaled down and centre-cropped in one draw (never stretched), and one that already fits is returned untouched.
@@ -530,7 +530,7 @@ Options: `apiVersion` (default `"2.0"`), `fetch`, `entities` (override a route/h
530
530
 
531
531
  ### Moving an existing app over
532
532
 
533
- - Keep `redirectUri` equal to the URL already registered on the auth server (AdminUI: `…/auth`) and mount `handlers.auth` at that path.
533
+ - Keep `redirectUri` equal to the URL already registered on the auth server and mount `handlers.auth` at that path.
534
534
  - Keep `cookieName` the same and use the same `SESSION_SECRET` value as the old iron-session password — current users stay logged in; their old token shape is read and upgraded on the next refresh.
535
535
  - Per-tool auth routes can go: link to `auth.loginPath("/tools/where-to-next")` instead.
536
536
  - `core.logout()` / `useLogout()` still work (`DELETE /api/session`), but prefer linking to `logoutPath()` so the auth-server session ends too.
@@ -561,7 +561,7 @@ Unit tests (offline, no credentials) cover the OAuth client, token handling, the
561
561
  npm test # = npm run test:unit
562
562
  ```
563
563
 
564
- Integration tests use [Vitest](https://vitest.dev) and run against **an app that mounts the SDK routes** (e.g. AdminUI on `npm run dev`), which forwards to the Shopwave API. They cover create → read (by id and by filter) → update → delete with the typed functions for Products and Categories, create → read → role update → retire for Employees, read-only checks for Consumers, plus unknown ids and the legacy `submitEntity` body. They create and delete real records on the account behind the token.
564
+ Integration tests use [Vitest](https://vitest.dev) and run against **an app that mounts the SDK routes** (for example your app on `npm run dev`), which forwards to the Shopwave API. They cover create → read (by id and by filter) → update → delete with the typed functions for Products and Categories, create → read → role update → retire for Employees, read-only checks for Consumers, plus unknown ids and the legacy `submitEntity` body. They create and delete real records on the account behind the token.
565
565
 
566
566
  ### Environment Variables
567
567
 
@@ -574,7 +574,7 @@ Integration tests use [Vitest](https://vitest.dev) and run against **an app that
574
574
 
575
575
  ```bash
576
576
  # Set environment variables
577
- # with the app running (AdminUI: npm run dev)
577
+ # with the app running (npm run dev)
578
578
  export API_URL=http://localhost:3000
579
579
  export API_TOKEN=<bare access token>
580
580
 
@@ -875,7 +875,7 @@ const STATIC_HOST = /^https?:\/\/static\.merchantstack\.com\/images\//i;
875
875
  /**
876
876
  * Turns an image path from the API into a URL a page can load over HTTPS:
877
877
  * `http://static.merchantstack.com/images/x.png` → `<base>/x.png`, and a
878
- * relative path (`merchant/5644/logo/x.png`) → `<base>/merchant/5644/logo/x.png`.
878
+ * relative path (`merchant/123/logo/x.png`) → `<base>/merchant/123/logo/x.png`.
879
879
  * Other absolute URLs are returned unchanged; empty input gives `""`.
880
880
  */
881
881
  function getImageUrl(path, base = SHOPWAVE_IMAGE_BASE_URL) {
@@ -927,7 +927,7 @@ const MERCHANT_IMAGE_SLOT_KEYS = [
927
927
  "featured"
928
928
  ];
929
929
  /**
930
- * Where each slot lives, its upload kind and its exact size (from adminV1's
930
+ * Where each slot lives, its upload kind and its exact size (from the existing admin's
931
931
  * merchant settings page).
932
932
  */
933
933
  const MERCHANT_IMAGE_SLOTS = Object.freeze({
@@ -1018,7 +1018,7 @@ function toStored(value) {
1018
1018
  }
1019
1019
  /**
1020
1020
  * The `imageIds` to send on a save: every URL is turned back into its file
1021
- * name (what adminV1 does with images you didn't change) and empty values
1021
+ * name (what the existing admin does with images you didn't change) and empty values
1022
1022
  * are dropped. No images gives `{}`.
1023
1023
  */
1024
1024
  function toStoredImageIds(imageIds) {
@@ -1039,7 +1039,7 @@ async function uploadMerchantImage(file, slot, options = {}) {
1039
1039
  //#endregion
1040
1040
  //#region src/core/productImages.ts
1041
1041
  /**
1042
- * Product images, as the live API stores them (merchant 5644, 4 Nov 2026):
1042
+ * Product images, as the live API stores them (4 Nov 2026):
1043
1043
  * - A product's `images` is a list. **Reads return full URLs**
1044
1044
  * (`http://static.merchantstack.com/images/merchant/<id>/product/<file>`);
1045
1045
  * **writes take the uploader's file names** ({@link toStoredProductImages};
@@ -1441,9 +1441,9 @@ function trimmed(value) {
1441
1441
  /**
1442
1442
  * Reads `merchant.links` as flat fields (`{ website, twitter, facebook,
1443
1443
  * instagram }`), whatever shape it was saved in:
1444
- * - the nested shape adminV1 saves (`{ website: { home }, social: { … } }`);
1444
+ * - the nested shape the existing admin saves (`{ website: { home }, social: { … } }`);
1445
1445
  * - flat keys (`{ website: "…" }`, what SDK 0.4.0 pre-releases sent);
1446
- * - an array in {@link MERCHANT_LINK_KEYS} order (older AdminUI versions).
1446
+ * - an array in {@link MERCHANT_LINK_KEYS} order (older app versions).
1447
1447
  * Empty values are dropped.
1448
1448
  */
1449
1449
  function merchantLinksToForm(links) {
@@ -1480,7 +1480,7 @@ function merchantLinksFromForm(form) {
1480
1480
  }
1481
1481
  return out;
1482
1482
  }
1483
- /** Reads any stored or older `links` value into the nested shape adminV1 saves. */
1483
+ /** Reads any stored or older `links` value into the nested shape the existing admin saves. */
1484
1484
  function normalizeMerchantLinks(links) {
1485
1485
  return merchantLinksFromForm(merchantLinksToForm(links));
1486
1486
  }