@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/CHANGELOG.md ADDED
@@ -0,0 +1,100 @@
1
+ ## 0.8.0 (2026-11-12)
2
+
3
+ ### Features
4
+
5
+ - `ProductDetailView` gains `backHref` and `backLabel` (default "Back to the shop") for a link above the gallery. The title is now display-size and bold, the detail column spacing is looser, and a sold product mutes its gallery image like the shop card.
6
+ - **Changed:** `AddToCartButton` renders a non-interactive `.sf-sold-note` span (label `labels.sold`, default "Sold") instead of a disabled button when the product is sold. Reserved products still show the disabled button with `labels.unavailable`.
7
+ - Shop grid: more space between the category chips and the grid, equal-height cards with aligned bodies, and the empty-state content is centred.
8
+ - **Changed:** `CheckoutSuccessView` fills the `labels.reference` `{id}` placeholder with the short reference from `formatOrderRef` (for example `#A1B2C3D4`) instead of the full order id. A custom label that already contains a `#` before `{id}` now shows two; drop it.
9
+
10
+ ## 0.7.0 (2026-11-04)
11
+
12
+ ### Features
13
+
14
+ - `CheckoutSuccessView` gains `slots` (`header`, `beforeLines`, `afterTotals`, `actions`), `showReference`, `showSummaryHeading`, `continueHref` and `renderLink`, plus the labels `summaryHeading`, `reference` (`{id}` placeholder) and `continue`. All additive; the view still has no hooks.
15
+
16
+ ### Docs
17
+
18
+ - The package now ships `CHANGELOG.md` and `docs/cart.md` (cart, checkout, success page and discount codes, moved out of the README), and declares `description`, `repository` and `homepage`.
19
+
20
+ ## 0.6.0 (2026-11-03)
21
+
22
+ No changes; released in lockstep with the other packages.
23
+
24
+ ## 0.5.0 (2026-11-02)
25
+
26
+ ### Features
27
+
28
+ - `CheckoutSuccessView({ order, locale?, labels?, className? })` renders the order summary for a checkout success page (lines, discount, shipping, total, ship-to address, tracking, per-status message), themed by `--sf-*` tokens with new `.sf-checkout-success*` styles. Exports `CheckoutSuccessLabels` and `defaultCheckoutSuccessLabels`.
29
+ - `ClearCartOnMount` empties the cart once after hydration, safe under React strict mode.
30
+ - `useCheckout({ startCheckout, onRedirect?, onDiscountInvalid?, labels? })` returns `{ checkout, busy, message, clearMessage }`: busy state, removal of out-of-stock lines, discount-reason messages and the gateway redirect. Exports `CheckoutLabels`, `defaultCheckoutLabels`, `StartCheckoutOptions`, `UseCheckoutOptions` and `UseCheckoutValue`.
31
+ - `@develemit/storefront/client` exports `toCheckoutResult(error)` and the `CheckoutResult` / `CheckoutFailure` types, so a server action can turn `CheckoutConflictError` and `DiscountInvalidError` into a serializable result without importing React.
32
+ - Exports `discountReasonMessage(reason, overrides?)`, the same reason text `CartSummary` shows for a rejected code, so hosts can show it when checkout rejects a code (`DiscountInvalidError`).
33
+
34
+ ## 0.4.0 (2026-10-30)
35
+
36
+ ### Features
37
+
38
+ - `client.previewDiscount({ code, items })` and `createCheckout(items, { discountCode })`, with a new `DiscountInvalidError` (422, carries the rejection `reason`). Exports `CreateCheckoutOptions`.
39
+ - `CartSummary` takes an optional `discount={{ onPreview, onApply, debounceMs }}` prop (also through `CartDrawer`'s `summary`): a collapsible "Have a code?" field, a discount row with Remove, reason messages, and a debounced re-preview that drops a code that stops applying. Labels live under `labels.discount`. Exports `CartSummaryDiscount` and `DiscountPreviewResult`. New `.sf-cart-summary__code*` styles. Needs a server with discounts.
40
+
41
+ ## 0.3.0 (2026-10-30)
42
+
43
+ ### Breaking
44
+
45
+ - `client.listProducts(query)` returns a page, `{ items: ProductCard[], nextCursor }`, instead of an array, and accepts `limit` and `cursor`. `ProductGrid`, `ProductCard`, `ShopView` and `FeaturedProducts` take the slim `ProductCard` shape (a full `Product` is still assignable).
46
+
47
+ ### Features
48
+
49
+ - The default `renderImage` now renders `<img loading="lazy" decoding="async">`, or `loading="eager"` with `fetchpriority="high"` when `priority` is set (non-breaking behavior change).
50
+
51
+ - `ShopView` `nextHref` and `loadMoreLabel` render a server-rendered "Load more" link through `renderLink`; hosts build `nextHref` from `nextCursor`.
52
+ - The "Load more" link now has default `--sf-*` token styling (`.sf-shop__more`, centered in a new `.sf-shop__pager` wrapper).
53
+ - Exports the `ProductCardData` and `ProductCardPage` types (`ProductCardData` avoids clashing with the `ProductCard` component).
54
+
55
+ ### Fixes
56
+
57
+ - Published tarballs no longer include declaration maps (`*.d.ts.map`), which pointed at `src/` files that were not shipped. JS sourcemaps now embed `sourcesContent`.
58
+ - Built `dist` type resolution: the `client` and `format` entries now emit to `dist/<name>/index.js` beside their declarations, so `allowJs` consumers (Next) get real types instead of `any`. No API change; the `exports` subpaths are unchanged.
59
+
60
+ ## 0.2.0 (2026-10-29)
61
+
62
+ First published release. 0.1.0 was tagged but never published to npm, so installing 0.2.0 gets everything in both entries below.
63
+
64
+ ### Features
65
+
66
+ - `CartDrawer` `slots` (`empty`, `summaryNote`) for a custom empty-cart state and a note under the subtotal.
67
+ - `CartButton` `icon`, `labelHidden` and `hideZeroCount` props.
68
+ - `--sf-motion-duration` theme variable (default `0ms`) for drawer slide-in/out and backdrop fade; forced to `0ms` under `prefers-reduced-motion: reduce`. The drawer carries `data-state="open" | "closed"`.
69
+
70
+ - Catalog presentation hooks: `--sf-card-hover-lift`, `--sf-card-hover-shadow`, `--sf-card-image-hover-scale`, `--sf-sold-opacity`, `--sf-sold-filter`, `--sf-grid-columns-mobile` and `--sf-color-focus` (all default to the current look).
71
+ - `CategoryFilter` `formatLabel` and `sortCategories`; `ShopView` `allLabel`, `formatCategoryLabel` and `sortCategories`.
72
+ - `ShopView`, `FeaturedProducts` and `ProductDetailView` forward `labels` to the availability badge.
73
+
74
+ ### Fixes
75
+
76
+ - `AvailabilityBadge` is sized to its text (`inline-size: fit-content`) instead of stretching in grid/flex columns.
77
+ - Gallery slide and thumb frames are `position: relative`, so `next/image` `fill` renders correctly; the gallery and thumbs show a `--sf-color-focus` focus ring.
78
+
79
+ ### Behavior changes
80
+
81
+ - An empty cart no longer renders the summary or a disabled Checkout button.
82
+ - Close is now an icon button (`×`) with `labels.close` as its accessible name, and Close and Remove have a 44×44px minimum target.
83
+
84
+ ## 0.1.0 (2026-10-29)
85
+
86
+ First release. Brand-agnostic React UI, cart and typed API client. Peers: `react` and `react-dom` `^19`.
87
+
88
+ ### Features
89
+
90
+ - Catalog components: `PriceTag`, `AvailabilityBadge`, `ProductCard`, `ProductGrid`, `CategoryFilter`, `ProductGallery`.
91
+ - Page views: `ShopView`, `ProductDetailView`, `FeaturedProducts`, `sortForDisplay`.
92
+ - Theme contract: `styles.css` ships the `--sf-*` custom properties; override them to brand the kit.
93
+ - Cart: `CartProvider`, `useCart`, a reducer with localStorage persistence, `AddToCartButton`, `CartDrawer`, `CartLineItem`, `CartSummary` and `CartButton`.
94
+ - `@develemit/storefront/client`: the typed API client (`createCommerceClient`, including `createCheckout` and `getOrderBySession`) and its error types.
95
+ - Image priority: `ImageProps.priority` lets `renderImage` adapters mark above-the-fold images (e.g. `next/image` `priority`). `ProductCard` accepts `priority`, `ProductGrid` accepts `priorityCount` (prioritizes the first N cards; `ShopView` and `FeaturedProducts` use 6), and `ProductGallery` prioritizes the active main image but not the thumbnails.
96
+ - `@develemit/storefront/format`: server-safe entry for `formatPrice` and `sortForDisplay`.
97
+
98
+ ### Notes
99
+
100
+ - The main bundle is marked `"use client"`. Server components must import pure helpers from `@develemit/storefront/format`.
package/README.md CHANGED
@@ -1,16 +1,23 @@
1
1
  # @develemit/storefront
2
2
 
3
- Brand-agnostic storefront UI and commerce logic, built so it can be extracted
4
- and published as a standalone package.
3
+ Brand-agnostic storefront UI, cart, checkout and a typed API client for shops
4
+ backed by `@develemit/commerce-server`. New here? Start with the
5
+ [getting-started guide](https://github.com/develemit/develemit-commerce/blob/main/docs/getting-started.md).
6
+ What changed in each version is in [CHANGELOG.md](CHANGELOG.md).
7
+
8
+ ```bash
9
+ pnpm add @develemit/storefront @develemit/commerce-contracts react react-dom
10
+ ```
5
11
 
6
12
  ## Boundary rules
7
13
 
8
14
  - No client branding, copy, colors, or product data in this package.
9
15
  The consuming app supplies those through props, theme tokens, and data adapters.
10
16
  - No imports from `apps/*` or other app-specific packages.
11
- - React, React DOM, and Next are peer dependencies, never bundled.
17
+ - React and React DOM are peer dependencies, never bundled. Next is optional:
18
+ it is wired in through the adapters below, never imported.
12
19
 
13
- ## Commands
20
+ ## Developing this package
14
21
 
15
22
  - `pnpm exec nx build storefront` — library build to `packages/storefront/dist`
16
23
  - `pnpm exec nx test storefront`
@@ -49,7 +56,8 @@ the variables in the host app (or pass a `StorefrontTheme` through
49
56
  `PriceTag`, `AvailabilityBadge`, `ProductCard`, `ProductGrid`,
50
57
  `CategoryFilter`, and `ProductGallery` are styled only through the `--sf-*`
51
58
  variables. Wrap them in `StorefrontProvider` to supply framework adapters;
52
- without it they fall back to plain `<a>` and `<img>`.
59
+ without it they fall back to plain `<a>` and `<img>`. `useStorefront()` reads
60
+ the provider's config (locale, adapters, hrefs) in your own components.
53
61
 
54
62
  Example Next.js host setup (host code, not part of this package):
55
63
 
@@ -215,97 +223,11 @@ export default async function ProductPage({
215
223
  }
216
224
  ```
217
225
 
218
- ## Cart
219
-
220
- Wrap the app in `CartProvider` (inside `StorefrontProvider`). State is a
221
- snapshot of `CartLine`s; prices and availability are re-verified server-side
222
- at checkout. Persistence goes through a `CartStorage` (`load`/`save`); the
223
- default is `localStorageCartStorage(key)`, which hydrates after mount and never
224
- touches `window` during render.
225
-
226
- ```tsx
227
- <CartProvider>
228
- <CartButton />
229
- <CartDrawer summary={{ onCheckout, checkoutLabel: 'Checkout' }} />
230
- </CartProvider>
231
-
232
- <ProductDetailView
233
- product={product}
234
- slots={{ actions: <AddToCartButton product={product} /> }}
235
- />
236
- ```
237
-
238
- `useCart()` exposes `add` (returns `{ added, capped, reason? }`), `remove`,
239
- `setQuantity`, `clear`, `open`/`close`, and the derived `itemCount` and
240
- `subtotalCents`. A product's `quantity` is the line's `maxQuantity`, so
241
- one-of-a-kind items can only be added once. Every label is overridable
242
- through a `labels` prop.
243
-
244
- ### Checkout
245
-
246
- `useCheckout({ startCheckout, onRedirect?, onDiscountInvalid?, labels? })`
247
- wraps the host's checkout call (a server action or a fetch) and returns
248
- `{ checkout, busy, message, clearMessage }`. `startCheckout(items, { discountCode })`
249
- resolves to a `CheckoutResult`: `{ url }`, `{ error: 'out_of_stock', productIds }`
250
- or `{ error: 'discount_invalid', reason }`. On the server, `toCheckoutResult(error)`
251
- from `@develemit/storefront/client` maps `CheckoutConflictError` and
252
- `DiscountInvalidError` to those results (other errors are rethrown). The hook
253
- removes out-of-stock lines, sets `message`, and redirects with
254
- `window.location.assign` unless `onRedirect` is given. Copy is overridable
255
- through `labels` (`outOfStock`, `unavailable`, `discountReasons`).
256
-
257
- ### Success page
258
-
259
- `<CheckoutSuccessView order={order} locale? labels? />` renders an order from
260
- `getOrderBySession`: lines, subtotal, discount row (only when applied),
261
- shipping, total, ship-to address (when the order includes `shippingAddress`),
262
- tracking, and a message per `OrderStatus` (`pending` tells the buyer to expect
263
- an email). It has no hooks, so a server component can render it. Labels live in
264
- `labels` (including `labels.status`). `<ClearCartOnMount />` empties the cart
265
- once, even under React strict mode. Styles are `.sf-checkout-success*`.
266
-
267
- ### Discount codes
268
-
269
- `CartSummary` (and `CartDrawer` through `summary`) takes an optional `discount`
270
- prop. The kit stays framework-agnostic, so the host performs the API call (a
271
- server action in Next):
272
-
273
- ```tsx
274
- <CartDrawer
275
- summary={{
276
- onCheckout: checkout,
277
- discount: {
278
- onPreview: async (code, items) => {
279
- // call client.previewDiscount; map DiscountInvalidError to a reason
280
- return { ok: true, code, discountCents: 480 };
281
- // or { ok: false, reason: 'exhausted' }
282
- },
283
- onApply: (code) => setDiscountCode(code), // null when removed or dropped
284
- },
285
- }}
286
- />
287
- ```
226
+ ## Cart and checkout
288
227
 
289
- It renders a collapsible "Have a code?" field, a "Discount (CODE) −$4.80" row
290
- with Remove, and a message for a rejected code. When the cart changes
291
- (debounced, `debounceMs`, default 300) the code is previewed again and dropped
292
- with its reason if it no longer applies. Pass the applied code to
293
- `createCheckout`. Override copy with `labels.discount` (`toggle`, `field`,
294
- `apply`, `remove`, `line`, `unavailable`, and `reasons` per rejection reason).
295
-
296
- ### Polish hooks
297
-
298
- - `CartDrawer` `slots={{ empty, summaryNote }}`: `empty` replaces the
299
- `labels.empty` text (for example with a link back to the shop); `summaryNote`
300
- renders under the subtotal and Checkout button. An empty cart shows no
301
- summary or Checkout button. Close and Remove are at least 44×44px.
302
- - `CartButton` `icon` (rendered before the label), `labelHidden` (visually
303
- hides the label, keeping an accessible name like "Cart, 2 items") and
304
- `hideZeroCount` (omits the count at 0; the accessible name becomes
305
- "Cart, empty").
306
- - `--sf-motion-duration` (default `0ms`, no motion) drives the drawer's
307
- slide-in and the backdrop's fade, in and out. It is forced to `0ms` under
308
- `prefers-reduced-motion: reduce`. Example: `--sf-motion-duration: 200ms`.
228
+ `CartProvider`, `CartButton`, `CartDrawer`, `AddToCartButton`, `useCart`,
229
+ `useCheckout`, `CheckoutSuccessView`, `ClearCartOnMount` and discount codes are
230
+ documented in [docs/cart.md](docs/cart.md).
309
231
 
310
232
  ### Catalog presentation hooks
311
233
 
@@ -3,6 +3,7 @@ export interface AddToCartLabels {
3
3
  add: string;
4
4
  inCart: string;
5
5
  unavailable: string;
6
+ sold: string;
6
7
  }
7
8
  export declare const defaultAddToCartLabels: AddToCartLabels;
8
9
  export interface AddToCartButtonProps {
@@ -1,3 +1,4 @@
1
+ import { ReactNode } from 'react';
1
2
  import { OrderAddress, OrderSummary } from '@develemit/commerce-contracts';
2
3
  import { CheckoutSuccessLabels } from './labels';
3
4
  export interface CheckoutSuccessViewProps {
@@ -9,5 +10,19 @@ export interface CheckoutSuccessViewProps {
9
10
  status?: Partial<CheckoutSuccessLabels['status']>;
10
11
  };
11
12
  className?: string;
13
+ slots?: {
14
+ header?: ReactNode;
15
+ beforeLines?: ReactNode;
16
+ afterTotals?: ReactNode;
17
+ actions?: ReactNode;
18
+ };
19
+ showReference?: boolean;
20
+ showSummaryHeading?: boolean;
21
+ continueHref?: string;
22
+ renderLink?: (props: {
23
+ href: string;
24
+ children: ReactNode;
25
+ className?: string;
26
+ }) => ReactNode;
12
27
  }
13
- export declare function CheckoutSuccessView({ order, locale, labels, className, }: CheckoutSuccessViewProps): import('react').JSX.Element;
28
+ export declare function CheckoutSuccessView({ order, locale, labels, className, slots, showReference, showSummaryHeading, continueHref, renderLink, }: CheckoutSuccessViewProps): import('react').JSX.Element;
@@ -1,6 +1,9 @@
1
1
  import { OrderStatus } from '@develemit/commerce-contracts';
2
2
  export interface CheckoutSuccessLabels {
3
3
  heading: string;
4
+ summaryHeading: string;
5
+ reference: string;
6
+ continue: string;
4
7
  subtotal: string;
5
8
  discount: string;
6
9
  shipping: string;