@ecomiq/storefront 3.0.1 → 3.1.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 (81) hide show
  1. package/README.md +21 -0
  2. package/dist/{account-Dx4e3syi.js → account-CZZ-ku6L.js} +4 -4
  3. package/dist/account.d.ts +9 -0
  4. package/dist/analytics/{analytics-CG1Qagwl.js → analytics-CiG_O37p.js} +4 -4
  5. package/dist/analytics/{config-DJr-GRhs.js → config-DN5B1GOG.js} +3 -3
  6. package/dist/analytics/{consent-B13rBpBs.js → consent-D0-iQ4bg.js} +1 -1
  7. package/dist/analytics/{consent-banner-9QvRRy8v.js → consent-banner-D36fpU89.js} +1 -1
  8. package/dist/analytics/{cookies-pLozllyB.js → cookies-CWsi-zSd.js} +1 -1
  9. package/dist/analytics/{events-Dt81tIp1.js → events-CWmKY7oX.js} +2 -1
  10. package/dist/analytics/{tracker-p4yaoOyk.js → tracker-DpfDB2sY.js} +1 -1
  11. package/dist/{bank-accounts-DlEczQQ5.js → bank-accounts-MpKIxHaX.js} +1 -1
  12. package/dist/{captcha-DAqaIx8m.js → captcha-CZkpghTO.js} +1 -1
  13. package/dist/{cart-actions-Bsub_FFG.js → cart-actions-IKRYGqfj.js} +32 -9
  14. package/dist/{cart-form-y9P1r_EX.js → cart-form-Bq_B-Rh6.js} +6 -4
  15. package/dist/cart-form.d.ts +2 -1
  16. package/dist/{cart-summary-ur2VWKr-.js → cart-summary-B6vtRGBO.js} +6 -1
  17. package/dist/cart-summary.d.ts +2 -1
  18. package/dist/{catalog-hooks-B7ZP4wYY.js → catalog-hooks-DjmpXROv.js} +1 -1
  19. package/dist/{checkout-DVWSXANi.js → checkout-DsI1MY14.js} +6 -6
  20. package/dist/{complaint-book-BvwLvfx0.js → complaint-book-aAQfGBvo.js} +2 -2
  21. package/dist/{culqi-authentication-BGr20vAJ.js → culqi-authentication-DHxjPJRE.js} +1 -1
  22. package/dist/{customer-document-fields-C1K-_DAD.js → customer-document-fields-BzLdztjM.js} +1 -1
  23. package/dist/{customer-login-form-C7AC6vXd.js → customer-login-form-k_q7_7FX.js} +2 -2
  24. package/dist/{customer-registration-form-BEm_NEfP.js → customer-registration-form-PrBqUuI9.js} +1 -1
  25. package/dist/entries/account.js +5 -5
  26. package/dist/entries/analytics.js +4 -4
  27. package/dist/entries/cart.d.ts +2 -1
  28. package/dist/entries/cart.js +6 -5
  29. package/dist/entries/checkout.js +8 -8
  30. package/dist/entries/order.js +8 -8
  31. package/dist/{form-contracts-BNpHaTGJ.js → form-contracts-0R112spH.js} +11 -4
  32. package/dist/form-contracts.d.ts +3 -3
  33. package/dist/index.js +29 -29
  34. package/dist/{invoice-fields-BG0HjL6T.js → invoice-fields-BzDfV1nR.js} +1 -1
  35. package/dist/{mercadopago-dialog-UTQev0MG.js → mercadopago-dialog-CpGlgKBl.js} +2 -2
  36. package/dist/{optimistic-cart-BveVOHuh.js → optimistic-cart-DcJ8-tCw.js} +1 -1
  37. package/dist/{order-display-DTt5WBbc.js → order-display-CUXVESaF.js} +19 -10
  38. package/dist/{order-draft-DxV1Uhm3.js → order-draft-D8sqUUNp.js} +1 -1
  39. package/dist/{order-payment-continuation-D_d5PGA4.js → order-payment-continuation-CS6omD7E.js} +3 -3
  40. package/dist/{order-payment-in-review-x4gdLFHZ.js → order-payment-in-review-D5Y8bjGA.js} +1 -1
  41. package/dist/{order-status-BJXLJwxq.js → order-status-QsmEO-89.js} +3 -3
  42. package/dist/{page-seo-a5FJl8x5.js → page-seo-BKG-Ta6t.js} +3 -3
  43. package/dist/{pages-Ca_Gt6mq.js → pages-DyItxkdE.js} +4 -4
  44. package/dist/{payment-dialog-frame-B-Q_xQ94.js → payment-dialog-frame-xtidt_eM.js} +1 -1
  45. package/dist/personalization-BSVlktZP.js +94 -0
  46. package/dist/personalization-fields-CwBR8oGr.js +363 -0
  47. package/dist/personalization-fields.d.ts +17 -0
  48. package/dist/{preview-BiIBif3C.js → preview-JWdMO6kX.js} +6 -4
  49. package/dist/{preview-address-Cf0sJ-9p.js → preview-address-bvieGEKb.js} +1 -1
  50. package/dist/{preview-cart-Dg2CVktS.js → preview-cart-COIP3Vfh.js} +3 -2
  51. package/dist/{public-pages-XtwlaLwt.js → public-pages-Byh01h-6.js} +2 -2
  52. package/dist/{registration-CpDcv8ql.js → registration-h_00PAhj.js} +3 -3
  53. package/dist/{route-text-DcJw2SsK.js → route-text-B6pWdevi.js} +1 -1
  54. package/dist/{section-preview-CmsMl3yu.js → section-preview-BiYfeE-d.js} +2 -2
  55. package/dist/sections/account-addresses.js +2 -2
  56. package/dist/sections/account-order.js +11 -6
  57. package/dist/sections/account-orders.js +3 -3
  58. package/dist/sections/account-profile.js +2 -2
  59. package/dist/sections/checkout.js +18 -11
  60. package/dist/sections/complaint-book.js +3 -3
  61. package/dist/sections/design-system.js +1 -1
  62. package/dist/sections/draft-invoice.js +7 -2
  63. package/dist/{sections-CNUMWTF_.js → sections-CgpjubkB.js} +1 -1
  64. package/dist/{seo-GpDRhAAA.js → seo-D9UdUMg9.js} +2 -2
  65. package/dist/{session-CKvmFJyg.js → session-DR_9-bcf.js} +30 -6
  66. package/dist/{store-8Q-_LFrQ.js → store-uU2gWdMY.js} +2 -2
  67. package/dist/{storefront-language-DrMjVbmk.js → storefront-language-Bnt-TU2F.js} +1 -1
  68. package/dist/{storefront-text-D25ae1qf.js → storefront-text-Co7iUnLM.js} +2 -2
  69. package/dist/{storefront-text-en-ljjjSS4q.js → storefront-text-en-DzYmdOuY.js} +6 -1
  70. package/dist/{storefront-text-pt-BtHaJLcp.js → storefront-text-pt-CizClH0I.js} +6 -1
  71. package/dist/{stripe-dialog-Ev9s3Z62.js → stripe-dialog-D88qfkUS.js} +2 -2
  72. package/dist/{templates-CAqoNyTW.js → templates-AgGupnPA.js} +2 -2
  73. package/dist/{urls-CpGQPxvf.js → urls-D4uinJVd.js} +1 -1
  74. package/dist/{use-checkout-CZcxXJXi.js → use-checkout-Jdk8P12A.js} +1 -1
  75. package/dist/{use-checkout-payment-9R-zacrJ.js → use-checkout-payment-BGX-BE6D.js} +1 -1
  76. package/dist/{voucher-payment-CVFDZ1L8.js → voucher-payment-B3o9ybg6.js} +1 -1
  77. package/dist/{worker-CNi-gzCu.js → worker-cbXmLvfR.js} +2 -2
  78. package/package.json +2 -2
  79. package/src/sections/account-order.tsx +4 -2
  80. package/src/sections/checkout.tsx +4 -1
  81. package/src/sections/draft-invoice.tsx +2 -0
@@ -0,0 +1,363 @@
1
+ import { modifierOptionField, modifierQuantityField, modifierValueField } from "./personalization-BSVlktZP.js";
2
+ import { useStorefrontText } from "./storefront-language-Bnt-TU2F.js";
3
+ import { modifierLabel } from "@ecomiq/core";
4
+ import { useId, useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ // src/personalization-fields.tsx
7
+ const MAX_VALUE_LENGTH = 500;
8
+ const SAFE_FONT_URL = /^https:\/\/[\w\-.~:/?#[\]@!$*+,;=%]+$/;
9
+ const FIELD = "h-11 w-full rounded-md border border-input bg-background px-3 text-base text-foreground outline-none transition-colors focus:border-ring focus:ring-1 focus:ring-ring md:text-sm";
10
+ const TILE = "peer-checked:ring-2 peer-checked:ring-primary peer-checked:ring-offset-2 peer-focus-visible:ring-2 peer-focus-visible:ring-ring";
11
+ const STEP = "grid size-8 place-items-center rounded-md border border-input bg-background text-base leading-none disabled:opacity-40";
12
+ function initialState(key, groups) {
13
+ const chosen = {};
14
+ for (const group of groups) {
15
+ if (group.selectionType === "text") continue;
16
+ const marked = group.options.filter((option) => option.isDefault);
17
+ const picked = group.selectionType === "single" ? marked.slice(0, 1) : marked;
18
+ chosen[group.id] = Object.fromEntries(picked.map((option) => [option.id, 1]));
19
+ }
20
+ return {
21
+ key,
22
+ values: {},
23
+ chosen
24
+ };
25
+ }
26
+ function extraOf(groups, { values, chosen }) {
27
+ let extra = 0;
28
+ for (const group of groups) {
29
+ if (group.selectionType === "text") {
30
+ if ((values[group.id] ?? "").trim() !== "") extra += group.priceDelta;
31
+ continue;
32
+ }
33
+ for (const option of group.options) extra += option.priceDelta * (chosen[group.id]?.[option.id] ?? 0);
34
+ }
35
+ return Math.round(extra * 100) / 100;
36
+ }
37
+ function minimum(group) {
38
+ return group.isRequired ? Math.max(1, group.minSelections) : group.minSelections;
39
+ }
40
+ function fontFamily(option) {
41
+ return `ecomiq-modifier-${option.id}`;
42
+ }
43
+ function FontFaces({ groups }) {
44
+ const rules = groups.filter((group) => group.display === "font").flatMap((group) => group.options).filter((option) => option.fileUrl !== null && SAFE_FONT_URL.test(option.fileUrl)).map((option) => `@font-face{font-family:"${fontFamily(option)}";src:url("${option.fileUrl}");font-display:swap}`);
45
+ if (rules.length === 0) return null;
46
+ return /* @__PURE__ */ jsx("style", { children: rules.join("") });
47
+ }
48
+ function OptionName({ group, option }) {
49
+ return /* @__PURE__ */ jsx("span", {
50
+ style: group.display === "font" ? { fontFamily: fontFamily(option) } : void 0,
51
+ children: option.name
52
+ });
53
+ }
54
+ function Tile({ group, option, children }) {
55
+ if (option !== null && group.display === "color" && option.color !== null) return /* @__PURE__ */ jsx("span", {
56
+ "aria-hidden": true,
57
+ className: `block size-8 rounded-full border border-input ${TILE}`,
58
+ style: { background: option.color }
59
+ });
60
+ if (option !== null && group.display === "image" && option.fileUrl !== null) return /* @__PURE__ */ jsx("img", {
61
+ src: option.fileUrl,
62
+ alt: "",
63
+ width: 56,
64
+ height: 56,
65
+ loading: "lazy",
66
+ decoding: "async",
67
+ className: `block size-14 rounded-md border border-input object-cover ${TILE}`
68
+ });
69
+ return /* @__PURE__ */ jsx("span", {
70
+ className: `flex h-10 items-center rounded-md border border-input bg-background px-3 text-sm ${TILE}`,
71
+ children
72
+ });
73
+ }
74
+ /** Fields for the product's customizations, with the names `cartAction` reads. Render them inside the `CartForm` or pass its `id` in `form`. */
75
+ function PersonalizationFields({ groups, form, formatPrice, className = "" }) {
76
+ const storefrontTranslate = useStorefrontText();
77
+ const prefix = useId();
78
+ const key = groups.map((group) => group.id).join(" ");
79
+ const [state, setState] = useState(() => initialState(key, groups));
80
+ if (state.key !== key) setState(initialState(key, groups));
81
+ if (groups.length === 0) return null;
82
+ const write = (groupId, value) => setState({
83
+ ...state,
84
+ values: {
85
+ ...state.values,
86
+ [groupId]: value
87
+ }
88
+ });
89
+ const choose = (groupId, options) => setState({
90
+ ...state,
91
+ chosen: {
92
+ ...state.chosen,
93
+ [groupId]: options
94
+ }
95
+ });
96
+ const plus = (value) => formatPrice && value > 0 ? `+ ${formatPrice(value)}` : "";
97
+ const extra = extraOf(groups, state);
98
+ const rules = (group) => {
99
+ const notes = [];
100
+ if (minimum(group) === 0) notes.push(storefrontTranslate("Opcional"));
101
+ if (group.selectionType === "multiple" && minimum(group) > 1) notes.push(storefrontTranslate("Elige al menos {value0}", { value0: minimum(group) }));
102
+ if (group.selectionType === "multiple" && group.maxSelections !== null) notes.push(storefrontTranslate("Elige hasta {value0}", { value0: group.maxSelections }));
103
+ return notes.length === 0 ? null : /* @__PURE__ */ jsxs("span", {
104
+ className: "font-normal text-muted-foreground",
105
+ children: [" · ", notes.join(" · ")]
106
+ });
107
+ };
108
+ const help = (group) => group.description ? /* @__PURE__ */ jsx("p", {
109
+ className: "text-xs text-muted-foreground",
110
+ children: group.description
111
+ }) : null;
112
+ return /* @__PURE__ */ jsxs("div", {
113
+ className: `grid gap-5 ${className}`,
114
+ children: [
115
+ /* @__PURE__ */ jsx(FontFaces, { groups }),
116
+ groups.map((group) => {
117
+ if (group.selectionType === "text") {
118
+ const id = `${prefix}-${group.id}`;
119
+ const value = state.values[group.id] ?? "";
120
+ const meta = [group.maxLength === null ? "" : `${value.length}/${group.maxLength}`, plus(group.priceDelta)].filter(Boolean).join(" · ");
121
+ return /* @__PURE__ */ jsxs("div", {
122
+ className: "grid gap-1.5",
123
+ children: [
124
+ /* @__PURE__ */ jsxs("div", {
125
+ className: "flex items-baseline justify-between gap-3",
126
+ children: [/* @__PURE__ */ jsxs("label", {
127
+ htmlFor: id,
128
+ className: "text-sm font-medium",
129
+ children: [group.name, rules(group)]
130
+ }), meta ? /* @__PURE__ */ jsx("span", {
131
+ className: "shrink-0 text-xs tabular-nums text-muted-foreground",
132
+ children: meta
133
+ }) : null]
134
+ }),
135
+ /* @__PURE__ */ jsx("input", {
136
+ id,
137
+ name: modifierValueField(group.id),
138
+ form,
139
+ value,
140
+ required: group.isRequired,
141
+ maxLength: group.maxLength ?? MAX_VALUE_LENGTH,
142
+ autoComplete: "off",
143
+ onChange: (event) => write(group.id, event.currentTarget.value),
144
+ className: FIELD
145
+ }),
146
+ help(group)
147
+ ]
148
+ }, group.id);
149
+ }
150
+ const picked = state.chosen[group.id] ?? {};
151
+ if (group.selectionType === "single") {
152
+ const current = Object.keys(picked)[0] ?? "";
153
+ const tiles = group.display === "color" || group.display === "image";
154
+ const name = group.options.find((option) => option.id === current)?.name;
155
+ const radio = (option) => /* @__PURE__ */ jsx("input", {
156
+ type: "radio",
157
+ name: modifierOptionField(group.id),
158
+ value: option?.id ?? "",
159
+ form,
160
+ required: option !== null && minimum(group) > 0,
161
+ checked: current === (option?.id ?? ""),
162
+ onChange: () => choose(group.id, option === null ? {} : { [option.id]: 1 }),
163
+ className: tiles ? "peer sr-only" : "accent-primary"
164
+ });
165
+ return /* @__PURE__ */ jsxs("fieldset", {
166
+ className: "grid gap-2",
167
+ children: [
168
+ /* @__PURE__ */ jsxs("legend", {
169
+ className: "mb-2 text-sm font-medium",
170
+ children: [group.name, tiles && name ? /* @__PURE__ */ jsxs("span", {
171
+ className: "font-normal text-muted-foreground",
172
+ children: [": ", name]
173
+ }) : rules(group)]
174
+ }),
175
+ /* @__PURE__ */ jsxs("div", {
176
+ className: tiles ? "flex flex-wrap gap-2" : "grid gap-2",
177
+ children: [minimum(group) > 0 ? null : tiles ? /* @__PURE__ */ jsxs("label", {
178
+ className: "cursor-pointer",
179
+ children: [radio(null), /* @__PURE__ */ jsx(Tile, {
180
+ group,
181
+ option: null,
182
+ children: storefrontTranslate("Sin elegir")
183
+ })]
184
+ }) : /* @__PURE__ */ jsxs("label", {
185
+ className: "flex items-center gap-2 text-sm",
186
+ children: [radio(null), storefrontTranslate("Sin elegir")]
187
+ }), group.options.map((option) => tiles ? /* @__PURE__ */ jsxs("label", {
188
+ title: [option.name, plus(option.priceDelta)].filter(Boolean).join(" "),
189
+ className: "cursor-pointer",
190
+ children: [
191
+ radio(option),
192
+ /* @__PURE__ */ jsx(Tile, {
193
+ group,
194
+ option,
195
+ children: option.name
196
+ }),
197
+ /* @__PURE__ */ jsx("span", {
198
+ className: "sr-only",
199
+ children: option.name
200
+ })
201
+ ]
202
+ }, option.id) : /* @__PURE__ */ jsxs("label", {
203
+ className: "flex items-center gap-2 text-sm",
204
+ children: [
205
+ radio(option),
206
+ /* @__PURE__ */ jsx(OptionName, {
207
+ group,
208
+ option
209
+ }),
210
+ /* @__PURE__ */ jsx("span", {
211
+ className: "text-muted-foreground",
212
+ children: plus(option.priceDelta)
213
+ })
214
+ ]
215
+ }, option.id))]
216
+ }),
217
+ help(group)
218
+ ]
219
+ }, group.id);
220
+ }
221
+ const full = group.maxSelections !== null && Object.keys(picked).length >= group.maxSelections;
222
+ return /* @__PURE__ */ jsxs("fieldset", {
223
+ className: "grid gap-2",
224
+ children: [
225
+ /* @__PURE__ */ jsxs("legend", {
226
+ className: "mb-2 text-sm font-medium",
227
+ children: [group.name, rules(group)]
228
+ }),
229
+ group.options.map((option) => {
230
+ const quantity = picked[option.id];
231
+ const others = Object.fromEntries(Object.entries(picked).filter(([optionId]) => optionId !== option.id));
232
+ const swatch = group.display === "color" && option.color !== null;
233
+ const image = group.display === "image" && option.fileUrl !== null;
234
+ return /* @__PURE__ */ jsxs("div", {
235
+ className: "flex min-h-9 items-center gap-3",
236
+ children: [/* @__PURE__ */ jsxs("label", {
237
+ className: "flex flex-1 items-center gap-2 text-sm has-[:disabled]:opacity-50",
238
+ children: [
239
+ /* @__PURE__ */ jsx("input", {
240
+ type: "checkbox",
241
+ name: modifierOptionField(group.id),
242
+ value: option.id,
243
+ form,
244
+ checked: quantity !== void 0,
245
+ disabled: quantity === void 0 && full,
246
+ onChange: (event) => choose(group.id, event.currentTarget.checked ? {
247
+ ...others,
248
+ [option.id]: 1
249
+ } : others),
250
+ className: "size-4 accent-primary"
251
+ }),
252
+ swatch ? /* @__PURE__ */ jsx("span", {
253
+ "aria-hidden": true,
254
+ className: "size-5 rounded-full border border-input",
255
+ style: { background: option.color ?? void 0 }
256
+ }) : null,
257
+ image ? /* @__PURE__ */ jsx("img", {
258
+ src: option.fileUrl ?? void 0,
259
+ alt: "",
260
+ width: 32,
261
+ height: 32,
262
+ loading: "lazy",
263
+ decoding: "async",
264
+ className: "size-8 rounded border border-input object-cover"
265
+ }) : null,
266
+ /* @__PURE__ */ jsx(OptionName, {
267
+ group,
268
+ option
269
+ }),
270
+ /* @__PURE__ */ jsx("span", {
271
+ className: "text-muted-foreground",
272
+ children: plus(option.priceDelta)
273
+ })
274
+ ]
275
+ }), quantity !== void 0 && option.maxQuantity > 1 ? /* @__PURE__ */ jsxs("div", {
276
+ className: "flex items-center gap-1.5",
277
+ children: [
278
+ /* @__PURE__ */ jsx("input", {
279
+ type: "hidden",
280
+ name: modifierQuantityField(group.id, option.id),
281
+ form,
282
+ value: quantity
283
+ }),
284
+ /* @__PURE__ */ jsx("button", {
285
+ type: "button",
286
+ "aria-label": storefrontTranslate("Quitar uno de {value0}", { value0: option.name }),
287
+ disabled: quantity <= 1,
288
+ onClick: () => choose(group.id, {
289
+ ...picked,
290
+ [option.id]: quantity - 1
291
+ }),
292
+ className: STEP,
293
+ children: "−"
294
+ }),
295
+ /* @__PURE__ */ jsx("span", {
296
+ "aria-live": "polite",
297
+ className: "w-6 text-center text-sm tabular-nums",
298
+ children: quantity
299
+ }),
300
+ /* @__PURE__ */ jsx("button", {
301
+ type: "button",
302
+ "aria-label": storefrontTranslate("Agregar uno de {value0}", { value0: option.name }),
303
+ disabled: quantity >= option.maxQuantity,
304
+ onClick: () => choose(group.id, {
305
+ ...picked,
306
+ [option.id]: quantity + 1
307
+ }),
308
+ className: STEP,
309
+ children: "+"
310
+ })
311
+ ]
312
+ }) : null]
313
+ }, option.id);
314
+ }),
315
+ help(group)
316
+ ]
317
+ }, group.id);
318
+ }),
319
+ formatPrice && extra > 0 ? /* @__PURE__ */ jsxs("p", {
320
+ className: "flex items-baseline justify-between gap-3 text-sm",
321
+ children: [/* @__PURE__ */ jsx("span", {
322
+ className: "text-muted-foreground",
323
+ children: storefrontTranslate("Personalización")
324
+ }), /* @__PURE__ */ jsxs("span", {
325
+ className: "font-medium tabular-nums",
326
+ children: ["+ ", formatPrice(extra)]
327
+ })]
328
+ }) : null
329
+ ]
330
+ });
331
+ }
332
+ /** What the buyer chose for a cart, order or draft line: one row per customization, with its extra. */
333
+ function ItemPersonalization({ modifiers, formatPrice, className = "" }) {
334
+ if (modifiers.length === 0) return null;
335
+ return /* @__PURE__ */ jsx("dl", {
336
+ className: `grid gap-0.5 text-xs ${className}`,
337
+ children: modifiers.map((modifier, index) => {
338
+ const extra = Math.round(modifier.priceDelta * modifier.quantity * 100) / 100;
339
+ return /* @__PURE__ */ jsxs("div", {
340
+ className: "flex flex-wrap gap-x-1",
341
+ children: [
342
+ /* @__PURE__ */ jsxs("dt", {
343
+ className: "text-muted-foreground",
344
+ children: [modifier.modifierGroupName, ":"]
345
+ }),
346
+ /* @__PURE__ */ jsx("dd", {
347
+ className: "font-medium break-words",
348
+ children: modifierLabel(modifier)
349
+ }),
350
+ formatPrice && extra > 0 ? /* @__PURE__ */ jsxs("dd", {
351
+ className: "text-muted-foreground tabular-nums",
352
+ children: [
353
+ "(+ ",
354
+ formatPrice(extra),
355
+ ")"
356
+ ]
357
+ }) : null
358
+ ]
359
+ }, `${modifier.modifierGroupId}-${modifier.modifierOptionId ?? index}`);
360
+ })
361
+ });
362
+ }
363
+ export { ItemPersonalization, PersonalizationFields };
@@ -0,0 +1,17 @@
1
+ import { ItemModifier, ModifierGroupItem } from "@ecomiq/core";
2
+ export type PersonalizationFieldsProps = {
3
+ groups: readonly ModifierGroupItem[];
4
+ /** `id` of the `CartForm` when the fields are rendered outside it. */
5
+ form?: string;
6
+ /** Formats the extras. Without it, no prices are shown. */
7
+ formatPrice?: (value: number) => string;
8
+ className?: string;
9
+ };
10
+ /** Fields for the product's customizations, with the names `cartAction` reads. Render them inside the `CartForm` or pass its `id` in `form`. */
11
+ export declare function PersonalizationFields({ groups, form, formatPrice, className }: PersonalizationFieldsProps): import("react").JSX.Element | null;
12
+ /** What the buyer chose for a cart, order or draft line: one row per customization, with its extra. */
13
+ export declare function ItemPersonalization({ modifiers, formatPrice, className }: {
14
+ modifiers: readonly ItemModifier[];
15
+ formatPrice?: (value: number) => string;
16
+ className?: string;
17
+ }): import("react").JSX.Element | null;
@@ -1,5 +1,5 @@
1
- import { previewAddress } from "./preview-address-Cf0sJ-9p.js";
2
- import { paymentMethods, previewCart } from "./preview-cart-Dg2CVktS.js";
1
+ import { previewAddress } from "./preview-address-bvieGEKb.js";
2
+ import { paymentMethods, previewCart } from "./preview-cart-COIP3Vfh.js";
3
3
  // src/preview.ts
4
4
  const PREVIEW_TOKEN = "ecomiq-editor-preview";
5
5
  const PREVIEW_REVIEW = "preview-review";
@@ -138,7 +138,8 @@ function items(orderId, products, sample) {
138
138
  trackingLink: null,
139
139
  shippedAt: sample.step === "confirmed" ? null : days(3),
140
140
  deliveredAt: sample.step === "delivered" ? days(1) : null,
141
- returnableQuantity: sample.step === "delivered" ? index + 1 : 0
141
+ returnableQuantity: sample.step === "delivered" ? index + 1 : 0,
142
+ modifiers: []
142
143
  }));
143
144
  }
144
145
  async function orders(client) {
@@ -299,7 +300,8 @@ async function draft(client) {
299
300
  subTotal: item.total,
300
301
  discount: 0,
301
302
  total: item.total,
302
- requiresShipping: true
303
+ requiresShipping: true,
304
+ modifiers: []
303
305
  }));
304
306
  const subTotal = items.reduce((sum, item) => sum + item.total, 0);
305
307
  return {
@@ -1,4 +1,4 @@
1
- import { loadStorefrontTranslations, storefrontText } from "./storefront-text-D25ae1qf.js";
1
+ import { loadStorefrontTranslations, storefrontText } from "./storefront-text-Co7iUnLM.js";
2
2
  // src/preview-address.ts
3
3
  async function previewAddress(store) {
4
4
  const translations = await loadStorefrontTranslations(store.language);
@@ -1,4 +1,4 @@
1
- import { previewAddress } from "./preview-address-Cf0sJ-9p.js";
1
+ import { previewAddress } from "./preview-address-bvieGEKb.js";
2
2
  // src/preview-cart.ts
3
3
  const PREVIEW_CART_TOKEN = "ecomiq-editor-preview-cart";
4
4
  const BUYER = {
@@ -101,7 +101,8 @@ function line(id, product, quantity) {
101
101
  requiresShipping: true,
102
102
  sellerId: null,
103
103
  sellerName: null,
104
- orderBumpId: null
104
+ orderBumpId: null,
105
+ modifiers: []
105
106
  };
106
107
  }
107
108
  function rememberProduct(product) {
@@ -1,6 +1,6 @@
1
1
  import { text } from "./form-BtQcbXlJ.js";
2
- import { customer, customerHeaders } from "./session-CKvmFJyg.js";
3
- import { found, store, storeHeaders } from "./store-8Q-_LFrQ.js";
2
+ import { customer, customerHeaders } from "./session-DR_9-bcf.js";
3
+ import { found, store, storeHeaders } from "./store-uU2gWdMY.js";
4
4
  import { data, redirect } from "react-router";
5
5
  import { ApiError, getOrderReviewContext, getOrderTracking, sanitizeHtml } from "@ecomiq/core";
6
6
  import { z } from "zod";
@@ -1,8 +1,8 @@
1
1
  import { safeNext, text } from "./form-BtQcbXlJ.js";
2
2
  import { rejectCrossOriginAction, rejectCustomerCsrf } from "./request-security-CRsRMgwj.js";
3
- import { customer, customerHeaders } from "./session-CKvmFJyg.js";
4
- import { store } from "./store-8Q-_LFrQ.js";
5
- import { worker } from "./worker-CNi-gzCu.js";
3
+ import { customer, customerHeaders } from "./session-DR_9-bcf.js";
4
+ import { store } from "./store-uU2gWdMY.js";
5
+ import { worker } from "./worker-cbXmLvfR.js";
6
6
  import { data, redirect } from "react-router";
7
7
  import { ApiError, CustomerRegistrationInputSchema } from "@ecomiq/core";
8
8
  // src/registration.ts
@@ -1,4 +1,4 @@
1
- import { storefrontText } from "./storefront-text-D25ae1qf.js";
1
+ import { storefrontText } from "./storefront-text-Co7iUnLM.js";
2
2
  import { z } from "zod";
3
3
  // src/route-text.ts
4
4
  const RootLanguage = z.object({ shop: z.object({ translations: z.record(z.string(), z.string()).default({}) }) });
@@ -1,5 +1,5 @@
1
- import { loadSections, withSections } from "./sections-CNUMWTF_.js";
2
- import { worker } from "./worker-CNi-gzCu.js";
1
+ import { loadSections, withSections } from "./sections-CgpjubkB.js";
2
+ import { worker } from "./worker-cbXmLvfR.js";
3
3
  import { data, useLoaderData } from "react-router";
4
4
  import { createElement, useState } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
@@ -1,7 +1,7 @@
1
- import { useStorefrontText } from "../storefront-language-DrMjVbmk.js";
1
+ import { useStorefrontText } from "../storefront-language-Bnt-TU2F.js";
2
2
  import { AddressForm } from "../account-forms-AVk71ko7.js";
3
3
  import { addressDefaults } from "../address-defaults-BqjRhS82.js";
4
- import { placeValue, useGeoCascade } from "../catalog-hooks-B7ZP4wYY.js";
4
+ import { placeValue, useGeoCascade } from "../catalog-hooks-DjmpXROv.js";
5
5
  import { useNavigation } from "react-router";
6
6
  import { useState } from "react";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -1,12 +1,13 @@
1
1
  import { CustomerCsrfField } from "../customer-csrf-C1oDX-Py.js";
2
2
  import { money } from "../money-DtGOtDri.js";
3
- import { orderProgress } from "../order-status-BJXLJwxq.js";
4
- import { useStorefrontLocale, useStorefrontText } from "../storefront-language-DrMjVbmk.js";
5
- import { PayButton, PaymentPreview, ReorderButton, useOrderStepDetail } from "../order-display-DTt5WBbc.js";
3
+ import { orderProgress } from "../order-status-QsmEO-89.js";
4
+ import { useStorefrontLocale, useStorefrontText } from "../storefront-language-Bnt-TU2F.js";
5
+ import { PayButton, PaymentPreview, ReorderButton, useOrderStepDetail } from "../order-display-CUXVESaF.js";
6
6
  import { orderStepLabel, orderStepOf, paymentStatusLabel } from "../order-view-9IlBg5AF.js";
7
- import { orderSummaryLines } from "../cart-summary-ur2VWKr-.js";
7
+ import { orderSummaryLines } from "../cart-summary-B6vtRGBO.js";
8
+ import { ItemPersonalization } from "../personalization-fields-CwBR8oGr.js";
8
9
  import { Form, Link, useNavigation } from "react-router";
9
- import { formatOrderDate } from "@ecomiq/core";
10
+ import { formatOrderDate, lineUnitPrice } from "@ecomiq/core";
10
11
  import { useEffect, useState } from "react";
11
12
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
13
  import { Button } from "@theme/ui/button";
@@ -129,10 +130,14 @@ function ShipmentCard({ shipment, currency }) {
129
130
  className: "text-sm text-muted-foreground",
130
131
  children: item.variantTitle
131
132
  }) : null,
133
+ /* @__PURE__ */ jsx(ItemPersonalization, {
134
+ modifiers: item.modifiers,
135
+ formatPrice: (value) => money(value, currency, locale)
136
+ }),
132
137
  /* @__PURE__ */ jsxs("p", {
133
138
  className: "text-sm text-muted-foreground",
134
139
  children: [
135
- money(item.unitPrice, currency, locale),
140
+ money(lineUnitPrice(item), currency, locale),
136
141
  " × ",
137
142
  item.quantity
138
143
  ]
@@ -1,8 +1,8 @@
1
1
  import { money } from "../money-DtGOtDri.js";
2
- import { useStorefrontLocale, useStorefrontText } from "../storefront-language-DrMjVbmk.js";
3
- import { PayButton, PaymentPreview, ReorderButton, useOrderHeadline, useOrderStepDetail } from "../order-display-DTt5WBbc.js";
2
+ import { useStorefrontLocale, useStorefrontText } from "../storefront-language-Bnt-TU2F.js";
3
+ import { PayButton, PaymentPreview, ReorderButton, useOrderHeadline, useOrderStepDetail } from "../order-display-CUXVESaF.js";
4
4
  import { mainPayment, orderStepLabel, orderStepOf } from "../order-view-9IlBg5AF.js";
5
- import { orderFiltersQuery } from "../account-Dx4e3syi.js";
5
+ import { orderFiltersQuery } from "../account-CZZ-ku6L.js";
6
6
  import { useOrderPages } from "../order-pages-Dm6l5m0m.js";
7
7
  import { Form, Link, useLocation, useNavigate } from "react-router";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1,5 +1,5 @@
1
- import { useStorefrontText } from "../storefront-language-DrMjVbmk.js";
2
- import { CustomerDocumentFields } from "../customer-document-fields-C1K-_DAD.js";
1
+ import { useStorefrontText } from "../storefront-language-Bnt-TU2F.js";
2
+ import { CustomerDocumentFields } from "../customer-document-fields-BzLdztjM.js";
3
3
  import { ProfileForm } from "../account-forms-AVk71ko7.js";
4
4
  import { useNavigation } from "react-router";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,19 +1,21 @@
1
- import { BankAccountList } from "../bank-accounts-DlEczQQ5.js";
2
- import { useStorefrontText } from "../storefront-language-DrMjVbmk.js";
1
+ import { BankAccountList } from "../bank-accounts-MpKIxHaX.js";
2
+ import { useStorefrontText } from "../storefront-language-Bnt-TU2F.js";
3
3
  import { PaymentLogo } from "../payment-logo-BoPaGJc_.js";
4
4
  import { money } from "../money-DtGOtDri.js";
5
5
  import { section, setting } from "../settings-B0NO2A6Y.js";
6
- import { placeValue, useGeoCascade } from "../catalog-hooks-B7ZP4wYY.js";
6
+ import { placeValue, useGeoCascade } from "../catalog-hooks-DjmpXROv.js";
7
7
  import { CheckoutForm, OrderBumpForm, PromoCodeForm } from "../checkout-form-CbOIdENC.js";
8
- import { CulqiAuthentication } from "../culqi-authentication-BGr20vAJ.js";
9
- import { InvoiceFields } from "../invoice-fields-BG0HjL6T.js";
10
- import { MercadoPagoDialog } from "../mercadopago-dialog-UTQev0MG.js";
8
+ import { CulqiAuthentication } from "../culqi-authentication-DHxjPJRE.js";
9
+ import { InvoiceFields } from "../invoice-fields-BzDfV1nR.js";
10
+ import { MercadoPagoDialog } from "../mercadopago-dialog-CpGlgKBl.js";
11
11
  import { PaymentMethods } from "../payment-methods-B8ocTgFZ.js";
12
- import { StripeAuthentication, StripeDialog, findStripeWallet, useAvailableStripeWallets } from "../stripe-dialog-Ev9s3Z62.js";
13
- import { summaryLines } from "../cart-summary-ur2VWKr-.js";
14
- import { useCheckout } from "../use-checkout-CZcxXJXi.js";
15
- import { useCheckoutPayment } from "../use-checkout-payment-9R-zacrJ.js";
12
+ import { StripeAuthentication, StripeDialog, findStripeWallet, useAvailableStripeWallets } from "../stripe-dialog-D88qfkUS.js";
13
+ import { summaryLines } from "../cart-summary-B6vtRGBO.js";
14
+ import { useCheckout } from "../use-checkout-Jdk8P12A.js";
15
+ import { useCheckoutPayment } from "../use-checkout-payment-BGX-BE6D.js";
16
+ import { ItemPersonalization } from "../personalization-fields-CwBR8oGr.js";
16
17
  import { Link, useRouteLoaderData } from "react-router";
18
+ import { lineTotal } from "@ecomiq/core";
17
19
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
18
20
  import { Button } from "@theme/ui/button";
19
21
  // src/sections/checkout.tsx
@@ -375,6 +377,11 @@ function Checkout({ data, result, ...root }) {
375
377
  className: "text-xs text-muted-foreground",
376
378
  children: item.variantLabel
377
379
  }) : null,
380
+ /* @__PURE__ */ jsx(ItemPersonalization, {
381
+ modifiers: item.modifiers,
382
+ formatPrice: format,
383
+ className: "mt-0.5"
384
+ }),
378
385
  item.sellerName ? /* @__PURE__ */ jsxs("span", {
379
386
  className: "text-xs text-muted-foreground",
380
387
  children: [
@@ -387,7 +394,7 @@ function Checkout({ data, result, ...root }) {
387
394
  }),
388
395
  /* @__PURE__ */ jsx("span", {
389
396
  className: "tabular-nums",
390
- children: format(item.unitPrice * item.quantity)
397
+ children: format(lineTotal(item))
391
398
  })
392
399
  ]
393
400
  }, item.id))
@@ -1,7 +1,7 @@
1
- import { Captcha } from "../captcha-DAqaIx8m.js";
1
+ import { Captcha } from "../captcha-CZkpghTO.js";
2
2
  import { COMPLAINT_DOCUMENT_TYPES, identityDocumentRule } from "../documents-T1mzc_j8.js";
3
- import { useStorefrontText } from "../storefront-language-DrMjVbmk.js";
4
- import { complaintBookAttachmentError, complaintBookNumber } from "../complaint-book-BvwLvfx0.js";
3
+ import { useStorefrontText } from "../storefront-language-Bnt-TU2F.js";
4
+ import { complaintBookAttachmentError, complaintBookNumber } from "../complaint-book-aAQfGBvo.js";
5
5
  import { CustomerCsrfField } from "../customer-csrf-C1oDX-Py.js";
6
6
  import { money } from "../money-DtGOtDri.js";
7
7
  import { Form, useNavigation } from "react-router";
@@ -1,4 +1,4 @@
1
- import { useStorefrontText } from "../storefront-language-DrMjVbmk.js";
1
+ import { useStorefrontText } from "../storefront-language-Bnt-TU2F.js";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Button } from "@theme/ui/button";
4
4
  import { Input } from "@theme/ui/input";
@@ -1,8 +1,9 @@
1
- import { useStorefrontText } from "../storefront-language-DrMjVbmk.js";
1
+ import { useStorefrontText } from "../storefront-language-Bnt-TU2F.js";
2
2
  import { CustomerCsrfField } from "../customer-csrf-C1oDX-Py.js";
3
3
  import { money } from "../money-DtGOtDri.js";
4
4
  import { setting } from "../settings-B0NO2A6Y.js";
5
- import { orderSummaryLines } from "../cart-summary-ur2VWKr-.js";
5
+ import { orderSummaryLines } from "../cart-summary-B6vtRGBO.js";
6
+ import { ItemPersonalization } from "../personalization-fields-CwBR8oGr.js";
6
7
  import { Form, Link, useNavigation } from "react-router";
7
8
  import { jsx, jsxs } from "react/jsx-runtime";
8
9
  import { Button } from "@theme/ui/button";
@@ -79,6 +80,10 @@ function DraftInvoice({ loaderData, actionData, ...root }) {
79
80
  className: "text-sm text-muted-foreground",
80
81
  children: item.subtitle
81
82
  }) : null,
83
+ /* @__PURE__ */ jsx(ItemPersonalization, {
84
+ modifiers: item.modifiers,
85
+ formatPrice: price
86
+ }),
82
87
  /* @__PURE__ */ jsxs("span", {
83
88
  className: "text-sm text-muted-foreground tabular-nums",
84
89
  children: ["×", item.quantity]
@@ -1,5 +1,5 @@
1
1
  import "./source-marker-DhqSkW7n.js";
2
- import { store } from "./store-8Q-_LFrQ.js";
2
+ import { store } from "./store-uU2gWdMY.js";
3
3
  import { data, useLoaderData } from "react-router";
4
4
  import { Suspense, createElement, use } from "react";
5
5
  // src/sections.ts
@@ -1,5 +1,5 @@
1
- import { store } from "./store-8Q-_LFrQ.js";
2
- import { absoluteUrl, canonicalOrigin } from "./urls-CpGQPxvf.js";
1
+ import { store } from "./store-uU2gWdMY.js";
2
+ import { absoluteUrl, canonicalOrigin } from "./urls-D4uinJVd.js";
3
3
  // src/seo.ts
4
4
  const PRIVATE_PATHS = [
5
5
  "/cart",