@develemit/storefront 0.4.0 → 0.7.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 +91 -0
- package/README.md +17 -72
- package/dist/cart/clear-cart-on-mount.d.ts +1 -0
- package/dist/cart/use-checkout.d.ts +27 -0
- package/dist/client/checkout-result.d.ts +12 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +13 -1
- package/dist/client/index.js.map +1 -1
- package/dist/components/cart-summary/cart-summary.d.ts +1 -0
- package/dist/components/cart-summary/discount-types.d.ts +1 -0
- package/dist/components/checkout-success/checkout-success-view.d.ts +28 -0
- package/dist/components/checkout-success/labels.d.ts +15 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +235 -52
- package/dist/index.js.map +1 -1
- package/dist/styles.css +66 -0
- package/docs/cart.md +110 -0
- package/package.json +12 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
## 0.7.0 (2026-11-04)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- `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.
|
|
6
|
+
|
|
7
|
+
### Docs
|
|
8
|
+
|
|
9
|
+
- 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`.
|
|
10
|
+
|
|
11
|
+
## 0.6.0 (2026-11-03)
|
|
12
|
+
|
|
13
|
+
No changes; released in lockstep with the other packages.
|
|
14
|
+
|
|
15
|
+
## 0.5.0 (2026-11-02)
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- `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`.
|
|
20
|
+
- `ClearCartOnMount` empties the cart once after hydration, safe under React strict mode.
|
|
21
|
+
- `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`.
|
|
22
|
+
- `@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.
|
|
23
|
+
- 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`).
|
|
24
|
+
|
|
25
|
+
## 0.4.0 (2026-10-30)
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
- `client.previewDiscount({ code, items })` and `createCheckout(items, { discountCode })`, with a new `DiscountInvalidError` (422, carries the rejection `reason`). Exports `CreateCheckoutOptions`.
|
|
30
|
+
- `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.
|
|
31
|
+
|
|
32
|
+
## 0.3.0 (2026-10-30)
|
|
33
|
+
|
|
34
|
+
### Breaking
|
|
35
|
+
|
|
36
|
+
- `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).
|
|
37
|
+
|
|
38
|
+
### Features
|
|
39
|
+
|
|
40
|
+
- The default `renderImage` now renders `<img loading="lazy" decoding="async">`, or `loading="eager"` with `fetchpriority="high"` when `priority` is set (non-breaking behavior change).
|
|
41
|
+
|
|
42
|
+
- `ShopView` `nextHref` and `loadMoreLabel` render a server-rendered "Load more" link through `renderLink`; hosts build `nextHref` from `nextCursor`.
|
|
43
|
+
- The "Load more" link now has default `--sf-*` token styling (`.sf-shop__more`, centered in a new `.sf-shop__pager` wrapper).
|
|
44
|
+
- Exports the `ProductCardData` and `ProductCardPage` types (`ProductCardData` avoids clashing with the `ProductCard` component).
|
|
45
|
+
|
|
46
|
+
### Fixes
|
|
47
|
+
|
|
48
|
+
- Published tarballs no longer include declaration maps (`*.d.ts.map`), which pointed at `src/` files that were not shipped. JS sourcemaps now embed `sourcesContent`.
|
|
49
|
+
- 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.
|
|
50
|
+
|
|
51
|
+
## 0.2.0 (2026-10-29)
|
|
52
|
+
|
|
53
|
+
First published release. 0.1.0 was tagged but never published to npm, so installing 0.2.0 gets everything in both entries below.
|
|
54
|
+
|
|
55
|
+
### Features
|
|
56
|
+
|
|
57
|
+
- `CartDrawer` `slots` (`empty`, `summaryNote`) for a custom empty-cart state and a note under the subtotal.
|
|
58
|
+
- `CartButton` `icon`, `labelHidden` and `hideZeroCount` props.
|
|
59
|
+
- `--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"`.
|
|
60
|
+
|
|
61
|
+
- 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).
|
|
62
|
+
- `CategoryFilter` `formatLabel` and `sortCategories`; `ShopView` `allLabel`, `formatCategoryLabel` and `sortCategories`.
|
|
63
|
+
- `ShopView`, `FeaturedProducts` and `ProductDetailView` forward `labels` to the availability badge.
|
|
64
|
+
|
|
65
|
+
### Fixes
|
|
66
|
+
|
|
67
|
+
- `AvailabilityBadge` is sized to its text (`inline-size: fit-content`) instead of stretching in grid/flex columns.
|
|
68
|
+
- 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.
|
|
69
|
+
|
|
70
|
+
### Behavior changes
|
|
71
|
+
|
|
72
|
+
- An empty cart no longer renders the summary or a disabled Checkout button.
|
|
73
|
+
- Close is now an icon button (`×`) with `labels.close` as its accessible name, and Close and Remove have a 44×44px minimum target.
|
|
74
|
+
|
|
75
|
+
## 0.1.0 (2026-10-29)
|
|
76
|
+
|
|
77
|
+
First release. Brand-agnostic React UI, cart and typed API client. Peers: `react` and `react-dom` `^19`.
|
|
78
|
+
|
|
79
|
+
### Features
|
|
80
|
+
|
|
81
|
+
- Catalog components: `PriceTag`, `AvailabilityBadge`, `ProductCard`, `ProductGrid`, `CategoryFilter`, `ProductGallery`.
|
|
82
|
+
- Page views: `ShopView`, `ProductDetailView`, `FeaturedProducts`, `sortForDisplay`.
|
|
83
|
+
- Theme contract: `styles.css` ships the `--sf-*` custom properties; override them to brand the kit.
|
|
84
|
+
- Cart: `CartProvider`, `useCart`, a reducer with localStorage persistence, `AddToCartButton`, `CartDrawer`, `CartLineItem`, `CartSummary` and `CartButton`.
|
|
85
|
+
- `@develemit/storefront/client`: the typed API client (`createCommerceClient`, including `createCheckout` and `getOrderBySession`) and its error types.
|
|
86
|
+
- 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.
|
|
87
|
+
- `@develemit/storefront/format`: server-safe entry for `formatPrice` and `sortForDisplay`.
|
|
88
|
+
|
|
89
|
+
### Notes
|
|
90
|
+
|
|
91
|
+
- 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
|
|
4
|
-
|
|
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
|
|
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
|
-
##
|
|
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,74 +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
|
-
### Discount codes
|
|
245
|
-
|
|
246
|
-
`CartSummary` (and `CartDrawer` through `summary`) takes an optional `discount`
|
|
247
|
-
prop. The kit stays framework-agnostic, so the host performs the API call (a
|
|
248
|
-
server action in Next):
|
|
249
|
-
|
|
250
|
-
```tsx
|
|
251
|
-
<CartDrawer
|
|
252
|
-
summary={{
|
|
253
|
-
onCheckout: checkout,
|
|
254
|
-
discount: {
|
|
255
|
-
onPreview: async (code, items) => {
|
|
256
|
-
// call client.previewDiscount; map DiscountInvalidError to a reason
|
|
257
|
-
return { ok: true, code, discountCents: 480 };
|
|
258
|
-
// or { ok: false, reason: 'exhausted' }
|
|
259
|
-
},
|
|
260
|
-
onApply: (code) => setDiscountCode(code), // null when removed or dropped
|
|
261
|
-
},
|
|
262
|
-
}}
|
|
263
|
-
/>
|
|
264
|
-
```
|
|
226
|
+
## Cart and checkout
|
|
265
227
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
with its reason if it no longer applies. Pass the applied code to
|
|
270
|
-
`createCheckout`. Override copy with `labels.discount` (`toggle`, `field`,
|
|
271
|
-
`apply`, `remove`, `line`, `unavailable`, and `reasons` per rejection reason).
|
|
272
|
-
|
|
273
|
-
### Polish hooks
|
|
274
|
-
|
|
275
|
-
- `CartDrawer` `slots={{ empty, summaryNote }}`: `empty` replaces the
|
|
276
|
-
`labels.empty` text (for example with a link back to the shop); `summaryNote`
|
|
277
|
-
renders under the subtotal and Checkout button. An empty cart shows no
|
|
278
|
-
summary or Checkout button. Close and Remove are at least 44×44px.
|
|
279
|
-
- `CartButton` `icon` (rendered before the label), `labelHidden` (visually
|
|
280
|
-
hides the label, keeping an accessible name like "Cart, 2 items") and
|
|
281
|
-
`hideZeroCount` (omits the count at 0; the accessible name becomes
|
|
282
|
-
"Cart, empty").
|
|
283
|
-
- `--sf-motion-duration` (default `0ms`, no motion) drives the drawer's
|
|
284
|
-
slide-in and the backdrop's fade, in and out. It is forced to `0ms` under
|
|
285
|
-
`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).
|
|
286
231
|
|
|
287
232
|
### Catalog presentation hooks
|
|
288
233
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ClearCartOnMount(): null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DiscountRejectionReason } from '@develemit/commerce-contracts';
|
|
2
|
+
import { CheckoutResult } from '../client/checkout-result';
|
|
3
|
+
export interface CheckoutLabels {
|
|
4
|
+
outOfStock: string;
|
|
5
|
+
unavailable: string;
|
|
6
|
+
discountReasons?: Partial<Record<DiscountRejectionReason, string>>;
|
|
7
|
+
}
|
|
8
|
+
export declare const defaultCheckoutLabels: CheckoutLabels;
|
|
9
|
+
export interface StartCheckoutOptions {
|
|
10
|
+
discountCode?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface UseCheckoutOptions {
|
|
13
|
+
startCheckout: (items: {
|
|
14
|
+
productId: string;
|
|
15
|
+
quantity: number;
|
|
16
|
+
}[], options: StartCheckoutOptions) => Promise<CheckoutResult>;
|
|
17
|
+
onRedirect?: (url: string) => void;
|
|
18
|
+
onDiscountInvalid?: (reason: DiscountRejectionReason) => void;
|
|
19
|
+
labels?: Partial<CheckoutLabels>;
|
|
20
|
+
}
|
|
21
|
+
export interface UseCheckoutValue {
|
|
22
|
+
checkout: (options?: StartCheckoutOptions) => Promise<void>;
|
|
23
|
+
busy: boolean;
|
|
24
|
+
message: string | null;
|
|
25
|
+
clearMessage: () => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function useCheckout({ startCheckout, onRedirect, onDiscountInvalid, labels, }: UseCheckoutOptions): UseCheckoutValue;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DiscountRejectionReason } from '@develemit/commerce-contracts';
|
|
2
|
+
export type CheckoutFailure = {
|
|
3
|
+
error: 'out_of_stock';
|
|
4
|
+
productIds: string[];
|
|
5
|
+
} | {
|
|
6
|
+
error: 'discount_invalid';
|
|
7
|
+
reason: DiscountRejectionReason;
|
|
8
|
+
};
|
|
9
|
+
export type CheckoutResult = {
|
|
10
|
+
url: string;
|
|
11
|
+
} | CheckoutFailure;
|
|
12
|
+
export declare function toCheckoutResult(error: unknown): CheckoutFailure;
|
package/dist/client/index.d.ts
CHANGED
package/dist/client/index.js
CHANGED
|
@@ -80,6 +80,18 @@ function f({ baseUrl: f, fetch: p = (...e) => globalThis.fetch(...e) }) {
|
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
-
|
|
83
|
+
// src/client/checkout-result.ts
|
|
84
|
+
function p(e) {
|
|
85
|
+
if (e instanceof u) return {
|
|
86
|
+
error: "out_of_stock",
|
|
87
|
+
productIds: e.productIds
|
|
88
|
+
};
|
|
89
|
+
if (e instanceof d) return {
|
|
90
|
+
error: "discount_invalid",
|
|
91
|
+
reason: e.reason
|
|
92
|
+
};
|
|
93
|
+
throw e;
|
|
94
|
+
}
|
|
95
|
+
export { u as CheckoutConflictError, c as CommerceApiError, d as DiscountInvalidError, l as NotFoundError, f as createCommerceClient, p as toCheckoutResult };
|
|
84
96
|
|
|
85
97
|
//# sourceMappingURL=index.js.map
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["CommerceApiError","message","status","url","NotFoundError","CheckoutConflictError","productIds","DiscountInvalidError","reason","createCommerceClient","fetchImpl","args","root","baseUrl","readJson","res","schema","get","path","postJson","body","throwIfDiscountInvalid","parsed","discountRejectionSchema","createCheckout","discountCode","checkoutConflictSchema","checkoutResponseSchema","previewDiscount","input","discountPreviewSchema","params","query","qs","productCardPageSchema","slug","productSchema","categorySchema","sessionId","orderSummarySchema"],"sources":["../../src/client/errors.ts","../../src/client/commerce-client.ts"],"sourcesContent":["import type { DiscountRejectionReason } from '@develemit/commerce-contracts';\n\nexport class CommerceApiError extends Error {\n constructor(\n readonly status: number,\n readonly url: string,\n message?: string,\n ) {\n super(message ?? `Commerce API request failed (${status}): ${url}`);\n this.name = 'CommerceApiError';\n }\n}\n\nexport class NotFoundError extends CommerceApiError {\n constructor(url: string) {\n super(404, url, `Not found: ${url}`);\n this.name = 'NotFoundError';\n }\n}\n\nexport class CheckoutConflictError extends CommerceApiError {\n constructor(\n url: string,\n readonly productIds: string[],\n ) {\n super(\n 409,\n url,\n `Some items are no longer available: ${productIds.join(', ')}`,\n );\n this.name = 'CheckoutConflictError';\n }\n}\n\nexport class DiscountInvalidError extends CommerceApiError {\n constructor(\n url: string,\n readonly reason: DiscountRejectionReason,\n ) {\n super(422, url, `Discount code rejected: ${reason}`);\n this.name = 'DiscountInvalidError';\n }\n}\n","import {\n categorySchema,\n checkoutConflictSchema,\n checkoutResponseSchema,\n discountPreviewSchema,\n discountRejectionSchema,\n orderSummarySchema,\n productCardPageSchema,\n productSchema,\n type Category,\n type CheckoutRequest,\n type CheckoutResponse,\n type DiscountPreview,\n type OrderSummary,\n type ProductCardPage,\n type Product,\n type ProductListQuery,\n} from '@develemit/commerce-contracts';\nimport {\n CheckoutConflictError,\n CommerceApiError,\n DiscountInvalidError,\n NotFoundError,\n} from './errors';\n\nexport interface CommerceClientOptions {\n baseUrl: string;\n fetch?: typeof fetch;\n}\n\nexport interface CommerceClient {\n listProducts(query?: ProductListQuery): Promise<ProductCardPage>;\n getProduct(slug: string): Promise<Product>;\n listCategories(): Promise<Category[]>;\n createCheckout(\n items: CheckoutRequest['items'],\n options?: CreateCheckoutOptions,\n ): Promise<CheckoutResponse>;\n previewDiscount(input: {\n code: string;\n items: CheckoutRequest['items'];\n }): Promise<DiscountPreview>;\n getOrderBySession(sessionId: string): Promise<OrderSummary>;\n}\n\nexport interface CreateCheckoutOptions {\n discountCode?: string;\n}\n\ntype Schema<T> = { parse(data: unknown): T };\n\nexport function createCommerceClient({\n baseUrl,\n fetch: fetchImpl = (...args) => globalThis.fetch(...args),\n}: CommerceClientOptions): CommerceClient {\n const root = baseUrl.replace(/\\/+$/, '');\n\n async function readJson<T>(\n url: string,\n res: Response,\n schema: Schema<T>,\n ): Promise<T> {\n if (res.status === 404) throw new NotFoundError(url);\n if (!res.ok) throw new CommerceApiError(res.status, url);\n return schema.parse(await res.json());\n }\n\n async function get<T>(path: string, schema: Schema<T>): Promise<T> {\n const url = `${root}${path}`;\n const res = await fetchImpl(url, {\n headers: { accept: 'application/json' },\n });\n return readJson(url, res, schema);\n }\n\n function postJson(url: string, body: unknown): Promise<Response> {\n return fetchImpl(url, {\n method: 'POST',\n headers: {\n accept: 'application/json',\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n }\n\n async function throwIfDiscountInvalid(url: string, res: Response) {\n if (res.status !== 422) return;\n const parsed = discountRejectionSchema.safeParse(\n await res.json().catch(() => null),\n );\n if (parsed.success) throw new DiscountInvalidError(url, parsed.data.reason);\n }\n\n async function createCheckout(\n items: CheckoutRequest['items'],\n { discountCode }: CreateCheckoutOptions = {},\n ): Promise<CheckoutResponse> {\n const url = `${root}/checkout`;\n const res = await postJson(\n url,\n discountCode ? { items, discountCode } : { items },\n );\n if (res.status === 409) {\n const conflict = checkoutConflictSchema.parse(await res.json());\n throw new CheckoutConflictError(url, conflict.unavailableProductIds);\n }\n await throwIfDiscountInvalid(url, res);\n return readJson(url, res, checkoutResponseSchema);\n }\n\n async function previewDiscount(input: {\n code: string;\n items: CheckoutRequest['items'];\n }): Promise<DiscountPreview> {\n const url = `${root}/checkout/discount/preview`;\n const res = await postJson(url, input);\n await throwIfDiscountInvalid(url, res);\n return readJson(url, res, discountPreviewSchema);\n }\n\n return {\n async listProducts(query = {}) {\n const params = new URLSearchParams();\n if (query.category) params.set('category', query.category);\n if (query.featured !== undefined) {\n params.set('featured', String(query.featured));\n }\n if (query.limit !== undefined) params.set('limit', String(query.limit));\n if (query.cursor) params.set('cursor', query.cursor);\n const qs = params.toString();\n return get(\n `/catalog/products${qs ? `?${qs}` : ''}`,\n productCardPageSchema,\n );\n },\n getProduct(slug) {\n return get(\n `/catalog/products/${encodeURIComponent(slug)}`,\n productSchema,\n );\n },\n listCategories() {\n return get('/catalog/categories', categorySchema.array());\n },\n createCheckout,\n previewDiscount,\n getOrderBySession(sessionId) {\n return get(\n `/orders/session/${encodeURIComponent(sessionId)}`,\n orderSummarySchema,\n );\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["CommerceApiError","message","status","url","NotFoundError","CheckoutConflictError","productIds","DiscountInvalidError","reason","createCommerceClient","fetchImpl","args","root","baseUrl","readJson","res","schema","get","path","postJson","body","throwIfDiscountInvalid","parsed","discountRejectionSchema","createCheckout","discountCode","checkoutConflictSchema","checkoutResponseSchema","previewDiscount","input","discountPreviewSchema","params","query","qs","productCardPageSchema","slug","productSchema","categorySchema","sessionId","orderSummarySchema","toCheckoutResult","error"],"sources":["../../src/client/errors.ts","../../src/client/commerce-client.ts","../../src/client/checkout-result.ts"],"sourcesContent":["import type { DiscountRejectionReason } from '@develemit/commerce-contracts';\n\nexport class CommerceApiError extends Error {\n constructor(\n readonly status: number,\n readonly url: string,\n message?: string,\n ) {\n super(message ?? `Commerce API request failed (${status}): ${url}`);\n this.name = 'CommerceApiError';\n }\n}\n\nexport class NotFoundError extends CommerceApiError {\n constructor(url: string) {\n super(404, url, `Not found: ${url}`);\n this.name = 'NotFoundError';\n }\n}\n\nexport class CheckoutConflictError extends CommerceApiError {\n constructor(\n url: string,\n readonly productIds: string[],\n ) {\n super(\n 409,\n url,\n `Some items are no longer available: ${productIds.join(', ')}`,\n );\n this.name = 'CheckoutConflictError';\n }\n}\n\nexport class DiscountInvalidError extends CommerceApiError {\n constructor(\n url: string,\n readonly reason: DiscountRejectionReason,\n ) {\n super(422, url, `Discount code rejected: ${reason}`);\n this.name = 'DiscountInvalidError';\n }\n}\n","import {\n categorySchema,\n checkoutConflictSchema,\n checkoutResponseSchema,\n discountPreviewSchema,\n discountRejectionSchema,\n orderSummarySchema,\n productCardPageSchema,\n productSchema,\n type Category,\n type CheckoutRequest,\n type CheckoutResponse,\n type DiscountPreview,\n type OrderSummary,\n type ProductCardPage,\n type Product,\n type ProductListQuery,\n} from '@develemit/commerce-contracts';\nimport {\n CheckoutConflictError,\n CommerceApiError,\n DiscountInvalidError,\n NotFoundError,\n} from './errors';\n\nexport interface CommerceClientOptions {\n baseUrl: string;\n fetch?: typeof fetch;\n}\n\nexport interface CommerceClient {\n listProducts(query?: ProductListQuery): Promise<ProductCardPage>;\n getProduct(slug: string): Promise<Product>;\n listCategories(): Promise<Category[]>;\n createCheckout(\n items: CheckoutRequest['items'],\n options?: CreateCheckoutOptions,\n ): Promise<CheckoutResponse>;\n previewDiscount(input: {\n code: string;\n items: CheckoutRequest['items'];\n }): Promise<DiscountPreview>;\n getOrderBySession(sessionId: string): Promise<OrderSummary>;\n}\n\nexport interface CreateCheckoutOptions {\n discountCode?: string;\n}\n\ntype Schema<T> = { parse(data: unknown): T };\n\nexport function createCommerceClient({\n baseUrl,\n fetch: fetchImpl = (...args) => globalThis.fetch(...args),\n}: CommerceClientOptions): CommerceClient {\n const root = baseUrl.replace(/\\/+$/, '');\n\n async function readJson<T>(\n url: string,\n res: Response,\n schema: Schema<T>,\n ): Promise<T> {\n if (res.status === 404) throw new NotFoundError(url);\n if (!res.ok) throw new CommerceApiError(res.status, url);\n return schema.parse(await res.json());\n }\n\n async function get<T>(path: string, schema: Schema<T>): Promise<T> {\n const url = `${root}${path}`;\n const res = await fetchImpl(url, {\n headers: { accept: 'application/json' },\n });\n return readJson(url, res, schema);\n }\n\n function postJson(url: string, body: unknown): Promise<Response> {\n return fetchImpl(url, {\n method: 'POST',\n headers: {\n accept: 'application/json',\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n }\n\n async function throwIfDiscountInvalid(url: string, res: Response) {\n if (res.status !== 422) return;\n const parsed = discountRejectionSchema.safeParse(\n await res.json().catch(() => null),\n );\n if (parsed.success) throw new DiscountInvalidError(url, parsed.data.reason);\n }\n\n async function createCheckout(\n items: CheckoutRequest['items'],\n { discountCode }: CreateCheckoutOptions = {},\n ): Promise<CheckoutResponse> {\n const url = `${root}/checkout`;\n const res = await postJson(\n url,\n discountCode ? { items, discountCode } : { items },\n );\n if (res.status === 409) {\n const conflict = checkoutConflictSchema.parse(await res.json());\n throw new CheckoutConflictError(url, conflict.unavailableProductIds);\n }\n await throwIfDiscountInvalid(url, res);\n return readJson(url, res, checkoutResponseSchema);\n }\n\n async function previewDiscount(input: {\n code: string;\n items: CheckoutRequest['items'];\n }): Promise<DiscountPreview> {\n const url = `${root}/checkout/discount/preview`;\n const res = await postJson(url, input);\n await throwIfDiscountInvalid(url, res);\n return readJson(url, res, discountPreviewSchema);\n }\n\n return {\n async listProducts(query = {}) {\n const params = new URLSearchParams();\n if (query.category) params.set('category', query.category);\n if (query.featured !== undefined) {\n params.set('featured', String(query.featured));\n }\n if (query.limit !== undefined) params.set('limit', String(query.limit));\n if (query.cursor) params.set('cursor', query.cursor);\n const qs = params.toString();\n return get(\n `/catalog/products${qs ? `?${qs}` : ''}`,\n productCardPageSchema,\n );\n },\n getProduct(slug) {\n return get(\n `/catalog/products/${encodeURIComponent(slug)}`,\n productSchema,\n );\n },\n listCategories() {\n return get('/catalog/categories', categorySchema.array());\n },\n createCheckout,\n previewDiscount,\n getOrderBySession(sessionId) {\n return get(\n `/orders/session/${encodeURIComponent(sessionId)}`,\n orderSummarySchema,\n );\n },\n };\n}\n","import type { DiscountRejectionReason } from '@develemit/commerce-contracts';\nimport { CheckoutConflictError, DiscountInvalidError } from './errors';\n\nexport type CheckoutFailure =\n | { error: 'out_of_stock'; productIds: string[] }\n | { error: 'discount_invalid'; reason: DiscountRejectionReason };\n\nexport type CheckoutResult = { url: string } | CheckoutFailure;\n\nexport function toCheckoutResult(error: unknown): CheckoutFailure {\n if (error instanceof CheckoutConflictError) {\n return { error: 'out_of_stock', productIds: error.productIds };\n }\n if (error instanceof DiscountInvalidError) {\n return { error: 'discount_invalid', reason: error.reason };\n }\n throw error;\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CartSummaryDiscount, DiscountLabels } from './discount-types';
|
|
2
2
|
export type { CartSummaryDiscount, DiscountPreviewResult, } from './discount-types';
|
|
3
|
+
export { discountReasonMessage } from './discount-types';
|
|
3
4
|
export interface CartSummaryLabels {
|
|
4
5
|
subtotal: string;
|
|
5
6
|
discount: Partial<Omit<DiscountLabels, 'reasons'>> & {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { OrderAddress, OrderSummary } from '@develemit/commerce-contracts';
|
|
3
|
+
import { CheckoutSuccessLabels } from './labels';
|
|
4
|
+
export interface CheckoutSuccessViewProps {
|
|
5
|
+
order: OrderSummary & {
|
|
6
|
+
shippingAddress?: OrderAddress | null;
|
|
7
|
+
};
|
|
8
|
+
locale?: string;
|
|
9
|
+
labels?: Partial<Omit<CheckoutSuccessLabels, 'status'>> & {
|
|
10
|
+
status?: Partial<CheckoutSuccessLabels['status']>;
|
|
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;
|
|
27
|
+
}
|
|
28
|
+
export declare function CheckoutSuccessView({ order, locale, labels, className, slots, showReference, showSummaryHeading, continueHref, renderLink, }: CheckoutSuccessViewProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { OrderStatus } from '@develemit/commerce-contracts';
|
|
2
|
+
export interface CheckoutSuccessLabels {
|
|
3
|
+
heading: string;
|
|
4
|
+
summaryHeading: string;
|
|
5
|
+
reference: string;
|
|
6
|
+
continue: string;
|
|
7
|
+
subtotal: string;
|
|
8
|
+
discount: string;
|
|
9
|
+
shipping: string;
|
|
10
|
+
total: string;
|
|
11
|
+
shipTo: string;
|
|
12
|
+
tracking: string;
|
|
13
|
+
status: Record<OrderStatus, string>;
|
|
14
|
+
}
|
|
15
|
+
export declare const defaultCheckoutSuccessLabels: CheckoutSuccessLabels;
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,12 @@ export * from './views/sort-for-display';
|
|
|
15
15
|
export * from './cart/cart-reducer';
|
|
16
16
|
export * from './cart/cart-storage';
|
|
17
17
|
export * from './cart/cart-provider';
|
|
18
|
+
export * from './cart/use-checkout';
|
|
18
19
|
export * from './components/add-to-cart-button/add-to-cart-button';
|
|
19
20
|
export * from './components/cart-drawer/cart-drawer';
|
|
20
21
|
export * from './components/cart-line-item/cart-line-item';
|
|
21
22
|
export * from './components/cart-summary/cart-summary';
|
|
22
23
|
export * from './components/cart-button/cart-button';
|
|
24
|
+
export * from './components/checkout-success/checkout-success-view';
|
|
25
|
+
export * from './components/checkout-success/labels';
|
|
26
|
+
export * from './cart/clear-cart-on-mount';
|