react-shopwave-connect 0.4.3 → 0.4.5

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
@@ -215,7 +215,34 @@ The shapes below are the ones adminV1 saves and adminV2 reads, so all three admi
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
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()`.
218
- - **`uploadImage(file, { kind, 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.
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.
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
+
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.
223
+
224
+ ```ts
225
+ import { prepareImage, ImageTooSmallError, uploadImage } from "react-shopwave-connect";
226
+
227
+ const rule = { aspectRatio: "100:77", minWidth: 600, minHeight: 462, maxLongSide: 2000 };
228
+ try {
229
+ const ready = await prepareImage(file, rule); // 6000 × 4000 → 2000 × 1540
230
+ await uploadImage(ready, { kind: "product", prefix: "product" });
231
+ } catch (error) {
232
+ if (error instanceof ImageTooSmallError) showWarning(error.required, error.actual); // e.g. 600 × 462 vs 500 × 385
233
+ else throw error;
234
+ }
235
+ ```
236
+
237
+ | Rule field | Meaning |
238
+ |---|---|
239
+ | `width` + `height` | Exact output size (the ratio follows); can't be combined with the others |
240
+ | `aspectRatio` | Shape to crop to: `"100:77"`, `"100/77"`, `[100, 77]` or `1.3` |
241
+ | `minWidth`, `minHeight` | Smaller crops are refused with `ImageTooSmallError` (`required`, `actual`) |
242
+ | `maxWidth`, `maxHeight`, `maxLongSide` | Larger crops are scaled down to fit |
243
+ | `allowUpscale` | Enlarge a too-small image instead of refusing it (default `false`) |
244
+
245
+ Output keeps the source type when it's PNG, JPEG or WebP (JPEG/WebP at quality 0.9), else PNG. `planImageFit(size, rule)` gives the crop and output size without touching an image (pure); `checkImageRule(rule)` and `parseAspectRatio(value)` throw `ImageRuleError` for a rule that contradicts itself.
219
246
 
220
247
  `SHOPWAVE_RESOURCES` (merchant, user) and `SHOPWAVE_UPLOAD` are shared with the server routes, like `SHOPWAVE_ENTITIES`.
221
248
 
@@ -226,13 +253,13 @@ The shapes below are the ones adminV1 saves and adminV2 reads, so all three admi
226
253
  | category | `fetchCategories`, `fetchCategory`, `saveCategory`, `deleteCategory` |
227
254
  | consumer | `fetchConsumers`, `fetchConsumer` (read-only) |
228
255
  | employee | `fetchEmployees`, `fetchEmployee`, `saveEmployee`, `deleteEmployee` |
229
- | product | `fetchProducts`, `fetchProductsMap` (batched, keyed), `fetchProduct`, `saveProduct`, `deleteProduct`; prices `toProductInstancesForSave`, `findRepricedInstances`, `isSameInstancePrice`; tag lists `fetchProductTags`, `fetchProductInstanceTags` (`string[]`), `normalizeTagList` |
256
+ | product | `fetchProducts`, `fetchProductsMap` (batched, keyed), `fetchProduct`, `saveProduct`, `deleteProduct`; prices `toProductInstancesForSave`, `findRepricedInstances`, `isSameInstancePrice`; images `productImageRole`, `toStoredProductImages`; tag lists `fetchProductTags`, `fetchProductInstanceTags` (`string[]`), `normalizeTagList` |
230
257
  | promotion | `fetchPromotions`, `fetchPromotion`, `savePromotion`, `deletePromotion` |
231
258
  | store | `fetchStores` (now with `storeIds`), `fetchStore`, `saveStore`, `deleteStore` |
232
259
  | report | `fetchReport` |
233
260
  | merchant | `fetchMerchant`, `updateMerchant`, `validateMerchant`; links `merchantLinksToForm`, `merchantLinksFromForm`, `normalizeMerchantLinks`; images `MERCHANT_IMAGE_SLOTS`, `uploadMerchantImage`, `getMerchantImage`, `getMerchantImageUrl`, `setMerchantImage`, `toStoredImageIds`, `imageFileName` |
234
261
  | user | `fetchUser` |
235
- | upload | `uploadImage`, `getImageUrl`, `fitImageToSize`, `readImageSize`, `coverCrop` |
262
+ | upload | `uploadImage`, `getImageUrl`, `fitImageToSize`, `readImageSize`, `coverCrop`; `UPLOAD_KINDS`, `UPLOAD_PREFIXES`, `isUploadPrefix`; image rules `prepareImage`, `planImageFit`, `checkImageRule`, `parseAspectRatio`, `ImageTooSmallError`, `ImageRuleError` |
236
263
  | session | `fetchSession`, `loginPath`, `logoutPath`, `logout` |
237
264
  | entity | `saveEntity`, `saveEntities`, `deleteEntityById`, `fetchEntityById`; low-level `submitEntity` / `deleteEntity` (raw endpoint) |
238
265
  | errors | `ShopwaveApiError`, `getApiErrorMap`, `assertNoApiErrors` |
@@ -477,7 +504,7 @@ export const { GET } = shopwave.lookup("productInstanceTags");
477
504
  - `lookup(kind)`: the read-only lists in `SHOPWAVE_LOOKUPS`. `productTags` → `GET /product/tags`, `productInstanceTags` → `GET /product/instance/tags`; both answer `{ tags: string[] }`. They take no parameters, so `extras` aren't forwarded. The app routes are `products/tags` and `products/instance-tags`: static segments, so they sit beside `products/[id]`.
478
505
 
479
506
  - `resource("merchant")`: `PUT`/`POST` take `{ merchant: {…} }` or the bare object and send every field on as `postBody`. The `id` is required (400 without it), because a POST without one would create a second merchant. `resource("user")` answers 405 to writes.
480
- - `upload()`: takes multipart `file` + `kind`, checks the size (default 10 MB → 413) and type (default `image/*` → 415), and `PUT`s only the file to `/uploader` with the `contentType` header. `extras` are ignored on uploads. Shopwave's reply (201 `{ fileName, path }`) is passed through.
507
+ - `upload()`: takes multipart `file` + `kind` and an optional `prefix`, checks the size (default 10 MB → 413), type (default `image/*` → 415) and prefix (one of `UPLOAD_PREFIXES`, else 400), and `PUT`s only the file to `/uploader` with the `contentType` header, plus the `prefix` header when one was given. `extras` are ignored on uploads. Shopwave's reply (201 `{ fileName, path }`) is passed through.
481
508
 
482
509
  Entities: `product`, `category`, `store`, `promotion`, `employee` (DELETE retires via `exitDate`), `promotion` (DELETE expires it: the whole record saved back with `endDate`, and a not-yet-started `startDate`, set to now), `consumer` (mounted at `/api/consumer`; GET only, writes answer 405). For anything else, `shopwave.forward(request, { method, path, headers, postBody })` does the same token/refresh/encoding work for a custom handler.
483
510