@wocart/storefront 1.19.0 → 1.21.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/USAGE.md +317 -4
  3. package/dist/abandoned-checkout.d.ts +45 -0
  4. package/dist/abandoned-checkout.js +81 -3
  5. package/dist/answer-readers.d.ts +11 -4
  6. package/dist/answer-readers.js +114 -0
  7. package/dist/astro.d.ts +4 -0
  8. package/dist/astro.js +2 -0
  9. package/dist/basket.d.ts +11 -1
  10. package/dist/basket.js +17 -0
  11. package/dist/catalog.d.ts +24 -0
  12. package/dist/catalog.js +33 -0
  13. package/dist/client/orders.d.ts +10 -1
  14. package/dist/client/orders.js +8 -1
  15. package/dist/client/products.d.ts +13 -3
  16. package/dist/client/products.js +35 -12
  17. package/dist/coupon.d.ts +16 -1
  18. package/dist/coupon.js +37 -0
  19. package/dist/endpoints/storefront-routes.d.ts +8 -2
  20. package/dist/endpoints/storefront-routes.js +9 -2
  21. package/dist/index.d.ts +15 -7
  22. package/dist/index.js +9 -4
  23. package/dist/operations.d.ts +4 -0
  24. package/dist/operations.js +4 -0
  25. package/dist/order-lookup.d.ts +51 -0
  26. package/dist/order-lookup.js +44 -0
  27. package/dist/order-number.d.ts +9 -0
  28. package/dist/order-number.js +11 -0
  29. package/dist/product-view.d.ts +8 -0
  30. package/dist/product-view.js +26 -4
  31. package/dist/quote.d.ts +27 -0
  32. package/dist/quote.js +43 -0
  33. package/dist/review-capture.d.ts +77 -0
  34. package/dist/review-capture.js +172 -0
  35. package/dist/review-media.d.ts +8 -75
  36. package/dist/review-media.js +7 -170
  37. package/dist/review-session.d.ts +97 -0
  38. package/dist/review-session.js +157 -0
  39. package/dist/reviews.d.ts +33 -0
  40. package/dist/reviews.js +41 -0
  41. package/dist/routes.d.ts +2 -0
  42. package/dist/routes.js +3 -0
  43. package/dist/schemas.d.ts +98 -0
  44. package/dist/schemas.js +31 -0
  45. package/dist/seo.d.ts +47 -0
  46. package/dist/seo.js +87 -0
  47. package/dist/server.d.ts +1 -0
  48. package/dist/server.js +1 -0
  49. package/dist/sitemap.d.ts +22 -0
  50. package/dist/sitemap.js +51 -0
  51. package/dist/store-request.d.ts +96 -0
  52. package/dist/store-request.js +102 -0
  53. package/dist/types.d.ts +50 -2
  54. package/dist/utils/image.d.ts +48 -0
  55. package/dist/utils/image.js +51 -0
  56. package/package.json +1 -1
package/dist/types.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { COUPON_REFUSAL_REASONS } from "./coupon.js";
2
+ import type { PRODUCT_SORTS, SHOPPER_ORDER_STAGES } from "./catalog.js";
1
3
  import { z } from "zod";
2
4
  import { ProductSchema, ProductListSchema, CategorySchema, ProductImageSchema, ProductVariantSchema, OfferSchema, QuantityPricingSchema, PageDocumentPayloadSchema } from "./schemas.js";
3
5
  export type Product = z.infer<typeof ProductSchema>;
@@ -221,6 +223,10 @@ export interface StoreCollection {
221
223
  /** In the client's language when the store translated it, else the primary. */
222
224
  title: string;
223
225
  description: string | null;
226
+ /** How many products a shopper sees in it; absent from a server that predates it. */
227
+ productCount?: number | null | undefined;
228
+ /** Its first listed product's first photo, until a merchant can pick one; absent from a server that predates it. */
229
+ image?: ProductImage | null | undefined;
224
230
  }
225
231
  /** A navigation menu — GET /store/menus/{handle}. */
226
232
  export interface StoreMenu {
@@ -366,7 +372,7 @@ export interface CartValidation {
366
372
  * Why a code does not apply. Open: a server may name a reason this SDK has not
367
373
  * seen yet, and a theme then shows its general sentence.
368
374
  */
369
- export type CouponRefusalReason = "not_found" | "not_started" | "expired" | "used_up" | "identify_first" | "not_eligible" | "already_used" | "no_eligible_products" | "offer_applied" | "below_minimum_subtotal" | "below_minimum_quantity" | "delivery_already_free" | (string & {});
375
+ export type CouponRefusalReason = (typeof COUPON_REFUSAL_REASONS)[number] | (string & {});
370
376
  export interface CouponRefusal {
371
377
  reason: CouponRefusalReason;
372
378
  /** The dinars (`below_minimum_subtotal`) or items (`below_minimum_quantity`) still missing. */
@@ -480,6 +486,39 @@ export interface OrderTracking {
480
486
  pixelId: string | null;
481
487
  event: "Purchase" | "Lead" | null;
482
488
  }
489
+ /**
490
+ * Where an order is, in a shopper's words: `placed` (not confirmed yet),
491
+ * `confirmed`, `preparing`, `shipped`, `out_for_delivery`, `delivered`,
492
+ * `returned` (refused at the door) or `cancelled`. Open: a newer server may
493
+ * name another step, which a theme shows with its general words.
494
+ */
495
+ export type ShopperOrderStage = (typeof SHOPPER_ORDER_STAGES)[number] | (string & {});
496
+ /** What `orders.status()` answers: what a shopper may know of their order, never its address or notes. */
497
+ export interface ShopperOrderStatus {
498
+ orderNumber: string;
499
+ /** When it was placed (ISO 8601). */
500
+ placedAt: string;
501
+ stage: ShopperOrderStage;
502
+ deliveryType: "home" | "stop_desk";
503
+ /** As sold, in the catalog's language. */
504
+ lines: {
505
+ name: string;
506
+ variantLabel: string | null;
507
+ quantity: number;
508
+ }[];
509
+ /** What the shopper pays on delivery. */
510
+ total: number;
511
+ /** The carrier's tracking number once the parcel is with it; null before, or with the store's own driver. */
512
+ tracking: {
513
+ number: string;
514
+ carrier: string | null;
515
+ } | null;
516
+ }
517
+ /** What `orders.status()` sends: the order's number and the phone it was placed with. */
518
+ export interface ShopperOrderProof {
519
+ orderNumber: string;
520
+ phone: string;
521
+ }
483
522
  /** POST /store/otp/send success payload. */
484
523
  export type OtpSendResult = {
485
524
  status: "sent";
@@ -581,16 +620,25 @@ export interface ProductListFilters {
581
620
  onSale?: boolean;
582
621
  /** @deprecated Use `collection: "featured"`. */
583
622
  featured?: boolean;
623
+ /**
624
+ * The order. Absent: the listing's own — a collection's, a search's best
625
+ * match first, else newest. A server that predates sorting ignores it.
626
+ */
627
+ sort?: ProductSort;
584
628
  /** Server-side cap; the server clamps anything larger. */
585
629
  limit?: number;
586
630
  /** Products to skip — with `limit`, pages through the whole catalog. */
587
631
  offset?: number;
588
632
  }
589
- /** One page of products from `products.page()`. */
633
+ /** How a listing can be ordered: `PRODUCT_SORTS`. */
634
+ export type ProductSort = (typeof PRODUCT_SORTS)[number];
635
+ /** One page of products from `products.page()` or `products.search()`. */
590
636
  export interface ProductPage {
591
637
  items: ProductList[];
592
638
  /** True when another page follows this one. */
593
639
  hasNext: boolean;
640
+ /** How many products the whole listing holds, across its pages; null from a server that predates counts. */
641
+ total: number | null;
594
642
  }
595
643
  /** `products.reviews.list()` paging — named for the same reason. */
596
644
  export interface ReviewListPagination {
@@ -65,6 +65,54 @@ export declare function mediaImageUrl(originalUrl: string, params: MediaImagePar
65
65
  * makes an empty one), whitespace, control characters, a backslash, `?` or `#`.
66
66
  */
67
67
  export declare function mediaUrl(key: string, mediaDomain?: string): string | null;
68
+ export interface MediaImageSrcsetOptions {
69
+ /** The store's media domain; without one there is no srcset. */
70
+ mediaDomain?: string;
71
+ /** Height ÷ width: given, each size is cropped to it (`fit=cover`); absent, the photo keeps its shape (`fit=scale-down`). */
72
+ ratio?: number;
73
+ /** Encoder quality, 1–100; Cloudflare's default when absent. */
74
+ quality?: number;
75
+ }
76
+ /**
77
+ * The same photo at several widths, with width descriptors
78
+ * (`… 360w, … 720w`), for an `<img>` whose `sizes` says how wide it shows —
79
+ * a phone then downloads a phone-sized file. Each size is converted to the
80
+ * best format the browser takes (`format=auto`).
81
+ *
82
+ * @returns `null` when there is no media domain, the URL is not on it, or no
83
+ * width is a positive whole number.
84
+ */
85
+ export declare function mediaImageSrcset(src: string, widths: readonly number[], options?: MediaImageSrcsetOptions): string | null;
86
+ /** An image a merchant picked, by its media key — what a theme's `image` field resolves to. */
87
+ export interface PickedImage {
88
+ key: string;
89
+ alt?: string;
90
+ width?: number;
91
+ height?: number;
92
+ /** The point to keep in view when the photo is cropped, each axis 0–1. */
93
+ focal?: {
94
+ x: number;
95
+ y: number;
96
+ };
97
+ }
98
+ /** A picked image ready for an `<img>`: its URL, its text alternative, its size, and where a crop centres. */
99
+ export interface Picture {
100
+ src: string;
101
+ alt: string;
102
+ width: number | undefined;
103
+ height: number | undefined;
104
+ /** CSS `object-position` from the focal point (`30% 70%`); undefined without one. */
105
+ position: string | undefined;
106
+ }
107
+ /**
108
+ * A picked image as a theme draws it: its URL on the store's media domain,
109
+ * its alt text (empty when the merchant wrote none), its size, and its focal
110
+ * point as `object-position`, so a cropped photo keeps what matters in view.
111
+ *
112
+ * @returns `null` when no image is picked, the key cannot name an object, or
113
+ * the store has no media domain.
114
+ */
115
+ export declare function pictureOf(image: PickedImage | null | undefined, mediaDomain?: string): Picture | null;
68
116
  /**
69
117
  * Predefined image sizes for common use cases
70
118
  */
@@ -93,6 +93,57 @@ export function mediaUrl(key, mediaDomain) {
93
93
  const origin = mediaOriginOf(mediaDomain);
94
94
  return origin === null ? null : `${origin}/${segments.map(encodeURIComponent).join("/")}`;
95
95
  }
96
+ /**
97
+ * The same photo at several widths, with width descriptors
98
+ * (`… 360w, … 720w`), for an `<img>` whose `sizes` says how wide it shows —
99
+ * a phone then downloads a phone-sized file. Each size is converted to the
100
+ * best format the browser takes (`format=auto`).
101
+ *
102
+ * @returns `null` when there is no media domain, the URL is not on it, or no
103
+ * width is a positive whole number.
104
+ */
105
+ export function mediaImageSrcset(src, widths, options = {}) {
106
+ const sizes = [...new Set(widths.filter((width) => Number.isInteger(width) && width > 0))].sort((a, b) => a - b);
107
+ if (sizes.length === 0)
108
+ return null;
109
+ const { ratio, quality, mediaDomain } = options;
110
+ const cropped = ratio !== undefined && Number.isFinite(ratio) && ratio > 0;
111
+ const encoding = quality === undefined ? {} : { quality };
112
+ const entries = [];
113
+ for (const width of sizes) {
114
+ const url = mediaImageUrl(src, cropped
115
+ ? { width, height: Math.max(1, Math.round(width * ratio)), fit: "cover", format: "auto", ...encoding }
116
+ : { width, fit: "scale-down", format: "auto", ...encoding }, mediaDomain);
117
+ if (url === null)
118
+ return null;
119
+ entries.push(`${url} ${width}w`);
120
+ }
121
+ return entries.join(", ");
122
+ }
123
+ /**
124
+ * A picked image as a theme draws it: its URL on the store's media domain,
125
+ * its alt text (empty when the merchant wrote none), its size, and its focal
126
+ * point as `object-position`, so a cropped photo keeps what matters in view.
127
+ *
128
+ * @returns `null` when no image is picked, the key cannot name an object, or
129
+ * the store has no media domain.
130
+ */
131
+ export function pictureOf(image, mediaDomain) {
132
+ if (!image)
133
+ return null;
134
+ const src = mediaUrl(image.key, mediaDomain);
135
+ if (src === null)
136
+ return null;
137
+ const percent = (value) => `${Math.round(Math.min(1, Math.max(0, value)) * 100)}%`;
138
+ const focal = image.focal;
139
+ return {
140
+ src,
141
+ alt: image.alt ?? "",
142
+ width: image.width,
143
+ height: image.height,
144
+ position: focal && Number.isFinite(focal.x) && Number.isFinite(focal.y) ? `${percent(focal.x)} ${percent(focal.y)}` : undefined,
145
+ };
146
+ }
96
147
  /**
97
148
  * Predefined image sizes for common use cases
98
149
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wocart/storefront",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "Typed, validated SDK for Wocart cash-on-delivery storefronts: catalog reads, basket, checkout, OTP and order tracking, with Astro adapters.",
5
5
  "keywords": [
6
6
  "wocart",