@develemit/storefront 0.3.0 → 0.6.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/README.md +58 -0
- 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/commerce-client.d.ts +9 -2
- package/dist/client/errors.d.ts +5 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +56 -23
- package/dist/client/index.js.map +1 -1
- package/dist/components/cart-summary/cart-summary.d.ts +8 -1
- package/dist/components/cart-summary/discount-field.d.ts +13 -0
- package/dist/components/cart-summary/discount-types.d.ts +28 -0
- package/dist/components/cart-summary/use-discount.d.ts +14 -0
- package/dist/components/checkout-success/checkout-success-view.d.ts +13 -0
- package/dist/components/checkout-success/labels.d.ts +12 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +357 -40
- package/dist/index.js.map +1 -1
- package/dist/styles.css +63 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -131,6 +131,12 @@ sold or held by another buyer; its `productIds` lists them so the cart can drop
|
|
|
131
131
|
them. Send only ids and quantities: the server prices every line itself.
|
|
132
132
|
Redirect the shopper to the returned `url`.
|
|
133
133
|
|
|
134
|
+
Discount codes: `client.previewDiscount({ code, items })` returns
|
|
135
|
+
`{ code, discountCents, subtotalCents, currency }` or throws
|
|
136
|
+
`DiscountInvalidError` with a `reason` (`not_found`, `not_started`, `expired`,
|
|
137
|
+
`min_subtotal`, `exhausted`, `currency`). `createCheckout(items, { discountCode })`
|
|
138
|
+
sends the code and throws the same error if it was rejected at checkout.
|
|
139
|
+
|
|
134
140
|
## Images in production
|
|
135
141
|
|
|
136
142
|
The default image adapter is a plain `<img loading="lazy" decoding="async">`
|
|
@@ -235,6 +241,58 @@ touches `window` during render.
|
|
|
235
241
|
one-of-a-kind items can only be added once. Every label is overridable
|
|
236
242
|
through a `labels` prop.
|
|
237
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
|
+
```
|
|
288
|
+
|
|
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
|
+
|
|
238
296
|
### Polish hooks
|
|
239
297
|
|
|
240
298
|
- `CartDrawer` `slots={{ empty, summaryNote }}`: `empty` replaces the
|
|
@@ -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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Category, CheckoutRequest, CheckoutResponse, OrderSummary, ProductCardPage, Product, ProductListQuery } from '@develemit/commerce-contracts';
|
|
1
|
+
import { Category, CheckoutRequest, CheckoutResponse, DiscountPreview, OrderSummary, ProductCardPage, Product, ProductListQuery } from '@develemit/commerce-contracts';
|
|
2
2
|
export interface CommerceClientOptions {
|
|
3
3
|
baseUrl: string;
|
|
4
4
|
fetch?: typeof fetch;
|
|
@@ -7,7 +7,14 @@ export interface CommerceClient {
|
|
|
7
7
|
listProducts(query?: ProductListQuery): Promise<ProductCardPage>;
|
|
8
8
|
getProduct(slug: string): Promise<Product>;
|
|
9
9
|
listCategories(): Promise<Category[]>;
|
|
10
|
-
createCheckout(items: CheckoutRequest['items']): Promise<CheckoutResponse>;
|
|
10
|
+
createCheckout(items: CheckoutRequest['items'], options?: CreateCheckoutOptions): Promise<CheckoutResponse>;
|
|
11
|
+
previewDiscount(input: {
|
|
12
|
+
code: string;
|
|
13
|
+
items: CheckoutRequest['items'];
|
|
14
|
+
}): Promise<DiscountPreview>;
|
|
11
15
|
getOrderBySession(sessionId: string): Promise<OrderSummary>;
|
|
12
16
|
}
|
|
17
|
+
export interface CreateCheckoutOptions {
|
|
18
|
+
discountCode?: string;
|
|
19
|
+
}
|
|
13
20
|
export declare function createCommerceClient({ baseUrl, fetch: fetchImpl, }: CommerceClientOptions): CommerceClient;
|
package/dist/client/errors.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DiscountRejectionReason } from '@develemit/commerce-contracts';
|
|
1
2
|
export declare class CommerceApiError extends Error {
|
|
2
3
|
readonly status: number;
|
|
3
4
|
readonly url: string;
|
|
@@ -10,3 +11,7 @@ export declare class CheckoutConflictError extends CommerceApiError {
|
|
|
10
11
|
readonly productIds: string[];
|
|
11
12
|
constructor(url: string, productIds: string[]);
|
|
12
13
|
}
|
|
14
|
+
export declare class DiscountInvalidError extends CommerceApiError {
|
|
15
|
+
readonly reason: DiscountRejectionReason;
|
|
16
|
+
constructor(url: string, reason: DiscountRejectionReason);
|
|
17
|
+
}
|
package/dist/client/index.d.ts
CHANGED
package/dist/client/index.js
CHANGED
|
@@ -1,64 +1,97 @@
|
|
|
1
|
-
import { categorySchema as e, checkoutConflictSchema as t, checkoutResponseSchema as n,
|
|
1
|
+
import { categorySchema as e, checkoutConflictSchema as t, checkoutResponseSchema as n, discountPreviewSchema as r, discountRejectionSchema as i, orderSummarySchema as a, productCardPageSchema as o, productSchema as s } from "@develemit/commerce-contracts";
|
|
2
2
|
// src/client/errors.ts
|
|
3
|
-
var
|
|
3
|
+
var c = class extends Error {
|
|
4
4
|
status;
|
|
5
5
|
url;
|
|
6
6
|
constructor(e, t, n) {
|
|
7
7
|
super(n ?? `Commerce API request failed (${e}): ${t}`), this.status = e, this.url = t, this.name = "CommerceApiError";
|
|
8
8
|
}
|
|
9
|
-
},
|
|
9
|
+
}, l = class extends c {
|
|
10
10
|
constructor(e) {
|
|
11
11
|
super(404, e, `Not found: ${e}`), this.name = "NotFoundError";
|
|
12
12
|
}
|
|
13
|
-
},
|
|
13
|
+
}, u = class extends c {
|
|
14
14
|
productIds;
|
|
15
15
|
constructor(e, t) {
|
|
16
16
|
super(409, e, `Some items are no longer available: ${t.join(", ")}`), this.productIds = t, this.name = "CheckoutConflictError";
|
|
17
17
|
}
|
|
18
|
+
}, d = class extends c {
|
|
19
|
+
reason;
|
|
20
|
+
constructor(e, t) {
|
|
21
|
+
super(422, e, `Discount code rejected: ${t}`), this.reason = t, this.name = "DiscountInvalidError";
|
|
22
|
+
}
|
|
18
23
|
};
|
|
19
24
|
// src/client/commerce-client.ts
|
|
20
|
-
function
|
|
21
|
-
let
|
|
22
|
-
async function
|
|
23
|
-
if (t.status === 404) throw new
|
|
24
|
-
if (!t.ok) throw new
|
|
25
|
+
function f({ baseUrl: f, fetch: p = (...e) => globalThis.fetch(...e) }) {
|
|
26
|
+
let m = f.replace(/\/+$/, "");
|
|
27
|
+
async function h(e, t, n) {
|
|
28
|
+
if (t.status === 404) throw new l(e);
|
|
29
|
+
if (!t.ok) throw new c(t.status, e);
|
|
25
30
|
return n.parse(await t.json());
|
|
26
31
|
}
|
|
27
|
-
async function
|
|
28
|
-
let n = `${
|
|
29
|
-
return
|
|
32
|
+
async function g(e, t) {
|
|
33
|
+
let n = `${m}${e}`;
|
|
34
|
+
return h(n, await p(n, { headers: { accept: "application/json" } }), t);
|
|
30
35
|
}
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
function _(e, t) {
|
|
37
|
+
return p(e, {
|
|
33
38
|
method: "POST",
|
|
34
39
|
headers: {
|
|
35
40
|
accept: "application/json",
|
|
36
41
|
"content-type": "application/json"
|
|
37
42
|
},
|
|
38
|
-
body: JSON.stringify(
|
|
43
|
+
body: JSON.stringify(t)
|
|
39
44
|
});
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
}
|
|
46
|
+
async function v(e, t) {
|
|
47
|
+
if (t.status !== 422) return;
|
|
48
|
+
let n = i.safeParse(await t.json().catch(() => null));
|
|
49
|
+
if (n.success) throw new d(e, n.data.reason);
|
|
50
|
+
}
|
|
51
|
+
async function y(e, { discountCode: r } = {}) {
|
|
52
|
+
let i = `${m}/checkout`, a = await _(i, r ? {
|
|
53
|
+
items: e,
|
|
54
|
+
discountCode: r
|
|
55
|
+
} : { items: e });
|
|
56
|
+
if (a.status === 409) throw new u(i, t.parse(await a.json()).unavailableProductIds);
|
|
57
|
+
return await v(i, a), h(i, a, n);
|
|
58
|
+
}
|
|
59
|
+
async function b(e) {
|
|
60
|
+
let t = `${m}/checkout/discount/preview`, n = await _(t, e);
|
|
61
|
+
return await v(t, n), h(t, n, r);
|
|
42
62
|
}
|
|
43
63
|
return {
|
|
44
64
|
async listProducts(e = {}) {
|
|
45
65
|
let t = new URLSearchParams();
|
|
46
66
|
e.category && t.set("category", e.category), e.featured !== void 0 && t.set("featured", String(e.featured)), e.limit !== void 0 && t.set("limit", String(e.limit)), e.cursor && t.set("cursor", e.cursor);
|
|
47
67
|
let n = t.toString();
|
|
48
|
-
return
|
|
68
|
+
return g(`/catalog/products${n ? `?${n}` : ""}`, o);
|
|
49
69
|
},
|
|
50
70
|
getProduct(e) {
|
|
51
|
-
return
|
|
71
|
+
return g(`/catalog/products/${encodeURIComponent(e)}`, s);
|
|
52
72
|
},
|
|
53
73
|
listCategories() {
|
|
54
|
-
return
|
|
74
|
+
return g("/catalog/categories", e.array());
|
|
55
75
|
},
|
|
56
|
-
createCheckout:
|
|
76
|
+
createCheckout: y,
|
|
77
|
+
previewDiscount: b,
|
|
57
78
|
getOrderBySession(e) {
|
|
58
|
-
return
|
|
79
|
+
return g(`/orders/session/${encodeURIComponent(e)}`, a);
|
|
59
80
|
}
|
|
60
81
|
};
|
|
61
82
|
}
|
|
62
|
-
|
|
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 };
|
|
63
96
|
|
|
64
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","createCommerceClient","fetchImpl","args","root","baseUrl","readJson","res","schema","get","path","createCheckout","checkoutConflictSchema","checkoutResponseSchema","params","query","qs","productCardPageSchema","slug","productSchema","categorySchema","sessionId","orderSummarySchema"],"sources":["../../src/client/errors.ts","../../src/client/commerce-client.ts"],"sourcesContent":["
|
|
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,10 +1,17 @@
|
|
|
1
|
+
import { CartSummaryDiscount, DiscountLabels } from './discount-types';
|
|
2
|
+
export type { CartSummaryDiscount, DiscountPreviewResult, } from './discount-types';
|
|
3
|
+
export { discountReasonMessage } from './discount-types';
|
|
1
4
|
export interface CartSummaryLabels {
|
|
2
5
|
subtotal: string;
|
|
6
|
+
discount: Partial<Omit<DiscountLabels, 'reasons'>> & {
|
|
7
|
+
reasons?: Partial<DiscountLabels['reasons']>;
|
|
8
|
+
};
|
|
3
9
|
}
|
|
4
10
|
export interface CartSummaryProps {
|
|
5
11
|
onCheckout?: () => void;
|
|
6
12
|
checkoutLabel?: string;
|
|
7
13
|
checkoutDisabled?: boolean;
|
|
8
14
|
labels?: Partial<CartSummaryLabels>;
|
|
15
|
+
discount?: CartSummaryDiscount;
|
|
9
16
|
}
|
|
10
|
-
export declare function CartSummary({ onCheckout, checkoutLabel, checkoutDisabled, labels, }: CartSummaryProps): import('react').JSX.Element;
|
|
17
|
+
export declare function CartSummary({ onCheckout, checkoutLabel, checkoutDisabled, labels, discount, }: CartSummaryProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DiscountLabels } from './discount-types';
|
|
2
|
+
import { AppliedDiscount, DiscountError } from './use-discount';
|
|
3
|
+
export interface DiscountFieldProps {
|
|
4
|
+
labels: DiscountLabels;
|
|
5
|
+
applied: AppliedDiscount | null;
|
|
6
|
+
error: DiscountError | null;
|
|
7
|
+
pending: boolean;
|
|
8
|
+
currency: string;
|
|
9
|
+
locale: string;
|
|
10
|
+
onApply: (code: string) => void;
|
|
11
|
+
onRemove: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function DiscountField({ labels, applied, error, pending, currency, locale, onApply, onRemove, }: DiscountFieldProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DiscountRejectionReason } from '@develemit/commerce-contracts';
|
|
2
|
+
export type DiscountPreviewResult = {
|
|
3
|
+
ok: true;
|
|
4
|
+
code: string;
|
|
5
|
+
discountCents: number;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
reason: DiscountRejectionReason;
|
|
9
|
+
};
|
|
10
|
+
export interface CartSummaryDiscount {
|
|
11
|
+
onPreview: (code: string, items: {
|
|
12
|
+
productId: string;
|
|
13
|
+
quantity: number;
|
|
14
|
+
}[]) => Promise<DiscountPreviewResult>;
|
|
15
|
+
onApply?: (code: string | null) => void;
|
|
16
|
+
debounceMs?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface DiscountLabels {
|
|
19
|
+
toggle: string;
|
|
20
|
+
field: string;
|
|
21
|
+
apply: string;
|
|
22
|
+
remove: string;
|
|
23
|
+
line: string;
|
|
24
|
+
reasons: Record<DiscountRejectionReason, string>;
|
|
25
|
+
unavailable: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const defaultDiscountLabels: DiscountLabels;
|
|
28
|
+
export declare function discountReasonMessage(reason: DiscountRejectionReason, overrides?: Partial<DiscountLabels['reasons']>): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DiscountRejectionReason } from '@develemit/commerce-contracts';
|
|
2
|
+
import { CartSummaryDiscount } from './discount-types';
|
|
3
|
+
export interface AppliedDiscount {
|
|
4
|
+
code: string;
|
|
5
|
+
discountCents: number;
|
|
6
|
+
}
|
|
7
|
+
export type DiscountError = DiscountRejectionReason | 'unavailable';
|
|
8
|
+
export declare function useDiscount(discount: CartSummaryDiscount | undefined): {
|
|
9
|
+
applied: AppliedDiscount | null;
|
|
10
|
+
error: DiscountError | null;
|
|
11
|
+
pending: boolean;
|
|
12
|
+
apply: (code: string) => Promise<void>;
|
|
13
|
+
remove: () => void;
|
|
14
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { OrderAddress, OrderSummary } from '@develemit/commerce-contracts';
|
|
2
|
+
import { CheckoutSuccessLabels } from './labels';
|
|
3
|
+
export interface CheckoutSuccessViewProps {
|
|
4
|
+
order: OrderSummary & {
|
|
5
|
+
shippingAddress?: OrderAddress | null;
|
|
6
|
+
};
|
|
7
|
+
locale?: string;
|
|
8
|
+
labels?: Partial<Omit<CheckoutSuccessLabels, 'status'>> & {
|
|
9
|
+
status?: Partial<CheckoutSuccessLabels['status']>;
|
|
10
|
+
};
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function CheckoutSuccessView({ order, locale, labels, className, }: CheckoutSuccessViewProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { OrderStatus } from '@develemit/commerce-contracts';
|
|
2
|
+
export interface CheckoutSuccessLabels {
|
|
3
|
+
heading: string;
|
|
4
|
+
subtotal: string;
|
|
5
|
+
discount: string;
|
|
6
|
+
shipping: string;
|
|
7
|
+
total: string;
|
|
8
|
+
shipTo: string;
|
|
9
|
+
tracking: string;
|
|
10
|
+
status: Record<OrderStatus, string>;
|
|
11
|
+
}
|
|
12
|
+
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';
|