@develemit/storefront 0.6.0 → 0.8.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.
package/docs/cart.md ADDED
@@ -0,0 +1,111 @@
1
+ # Cart and checkout
2
+
3
+ [← @develemit/storefront](../README.md)
4
+
5
+ ## Cart
6
+
7
+ Wrap the app in `CartProvider` (inside `StorefrontProvider`). State is a
8
+ snapshot of `CartLine`s; prices and availability are re-verified server-side
9
+ at checkout. Persistence goes through a `CartStorage` (`load`/`save`); the
10
+ default is `localStorageCartStorage(key)`, which hydrates after mount and never
11
+ touches `window` during render.
12
+
13
+ ```tsx
14
+ <CartProvider>
15
+ <CartButton />
16
+ <CartDrawer summary={{ onCheckout, checkoutLabel: 'Checkout' }} />
17
+ </CartProvider>
18
+
19
+ <ProductDetailView
20
+ product={product}
21
+ slots={{ actions: <AddToCartButton product={product} /> }}
22
+ />
23
+ ```
24
+
25
+ `useCart()` exposes `add` (returns `{ added, capped, reason? }`), `remove`,
26
+ `setQuantity`, `clear`, `open`/`close`, and the derived `itemCount` and
27
+ `subtotalCents`. A product's `quantity` is the line's `maxQuantity`, so
28
+ one-of-a-kind items can only be added once. Every label is overridable
29
+ through a `labels` prop; the defaults are exported (`defaultCartDrawerLabels`,
30
+ `defaultCartLineItemLabels`, `defaultAddToCartLabels`, `defaultCheckoutLabels`,
31
+ `defaultCheckoutSuccessLabels`, `defaultAvailabilityLabels`) so you can spread
32
+ and change a few. The pure cart logic (`cartReducer`, `addLine`, `emptyCart`)
33
+ is exported for hosts that keep the cart outside React.
34
+
35
+ ## Checkout
36
+
37
+ `useCheckout({ startCheckout, onRedirect?, onDiscountInvalid?, labels? })`
38
+ wraps the host's checkout call (a server action or a fetch) and returns
39
+ `{ checkout, busy, message, clearMessage }`. `startCheckout(items, { discountCode })`
40
+ resolves to a `CheckoutResult`: `{ url }`, `{ error: 'out_of_stock', productIds }`
41
+ or `{ error: 'discount_invalid', reason }`. On the server, `toCheckoutResult(error)`
42
+ from `@develemit/storefront/client` maps `CheckoutConflictError` and
43
+ `DiscountInvalidError` to those results (other errors are rethrown). The hook
44
+ removes out-of-stock lines, sets `message`, and redirects with
45
+ `window.location.assign` unless `onRedirect` is given. Copy is overridable
46
+ through `labels` (`outOfStock`, `unavailable`, `discountReasons`).
47
+
48
+ ## Success page
49
+
50
+ `<CheckoutSuccessView order={order} locale? labels? />` renders an order from
51
+ `getOrderBySession`: lines, subtotal, discount row (only when applied),
52
+ shipping, total, ship-to address (only when you pass an order that includes
53
+ `shippingAddress`; the public order-by-session route never returns it),
54
+ tracking, and a message per `OrderStatus` (`pending` tells the buyer to expect
55
+ an email). It has no hooks, so a server component can render it. Labels live in
56
+ `labels` (including `labels.status`). `<ClearCartOnMount />` empties the cart
57
+ once, even under React strict mode. Styles are `.sf-checkout-success*`.
58
+
59
+ Extension points, all optional: `slots={{ header, beforeLines, afterTotals,
60
+ actions }}` (ReactNode) put your own content under the heading, above the
61
+ lines, below the totals, and in the footer. `showReference` adds a
62
+ `labels.reference` row (default `Order {id}`; `{id}` becomes the short
63
+ reference such as `#A1B2C3D4`), and `showSummaryHeading` adds an
64
+ `<h2>` from `labels.summaryHeading`. `continueHref` renders a link labelled
65
+ `labels.continue` unless `slots.actions` is given; pass `renderLink` to route it
66
+ through your framework's link (the view uses no hooks, so it can't read the
67
+ provider's).
68
+
69
+ ## Discount codes
70
+
71
+ `CartSummary` (and `CartDrawer` through `summary`) takes an optional `discount`
72
+ prop. The kit stays framework-agnostic, so the host performs the API call (a
73
+ server action in Next):
74
+
75
+ ```tsx
76
+ <CartDrawer
77
+ summary={{
78
+ onCheckout: checkout,
79
+ discount: {
80
+ onPreview: async (code, items) => {
81
+ // call client.previewDiscount; map DiscountInvalidError to a reason
82
+ return { ok: true, code, discountCents: 480 };
83
+ // or { ok: false, reason: 'exhausted' }
84
+ },
85
+ onApply: (code) => setDiscountCode(code), // null when removed or dropped
86
+ },
87
+ }}
88
+ />
89
+ ```
90
+
91
+ It renders a collapsible "Have a code?" field, a "Discount (CODE) −$4.80" row
92
+ with Remove, and a message for a rejected code. When the cart changes
93
+ (debounced, `debounceMs`, default 300) the code is previewed again and dropped
94
+ with its reason if it no longer applies. Pass the applied code to
95
+ `createCheckout`. `discountReasonMessage(reason, overrides?)` returns the same
96
+ text for a reason, for showing a code rejected at checkout. Override copy with `labels.discount` (`toggle`, `field`,
97
+ `apply`, `remove`, `line`, `unavailable`, and `reasons` per rejection reason).
98
+
99
+ ## Polish hooks
100
+
101
+ - `CartDrawer` `slots={{ empty, summaryNote }}`: `empty` replaces the
102
+ `labels.empty` text (for example with a link back to the shop); `summaryNote`
103
+ renders under the subtotal and Checkout button. An empty cart shows no
104
+ summary or Checkout button. Close and Remove are at least 44×44px.
105
+ - `CartButton` `icon` (rendered before the label), `labelHidden` (visually
106
+ hides the label, keeping an accessible name like "Cart, 2 items") and
107
+ `hideZeroCount` (omits the count at 0; the accessible name becomes
108
+ "Cart, empty").
109
+ - `--sf-motion-duration` (default `0ms`, no motion) drives the drawer's
110
+ slide-in and the backdrop's fade, in and out. It is forced to `0ms` under
111
+ `prefers-reduced-motion: reduce`. Example: `--sf-motion-duration: 200ms`.
package/package.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "name": "@develemit/storefront",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
+ "description": "Brand-agnostic React storefront: components, page views, cart, checkout and a typed API client.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/develemit/develemit-commerce.git",
8
+ "directory": "packages/storefront"
9
+ },
10
+ "homepage": "https://github.com/develemit/develemit-commerce/blob/main/docs/getting-started.md",
4
11
  "type": "module",
5
12
  "main": "./dist/index.js",
6
13
  "module": "./dist/index.js",
@@ -26,7 +33,9 @@
26
33
  },
27
34
  "files": [
28
35
  "dist",
29
- "!**/*.tsbuildinfo"
36
+ "docs",
37
+ "!**/*.tsbuildinfo",
38
+ "CHANGELOG.md"
30
39
  ],
31
40
  "nx": {
32
41
  "name": "storefront",
@@ -44,6 +53,6 @@
44
53
  "access": "public"
45
54
  },
46
55
  "dependencies": {
47
- "@develemit/commerce-contracts": "0.6.0"
56
+ "@develemit/commerce-contracts": "0.8.0"
48
57
  }
49
58
  }