@thorprovider/blocks 4.0.0 → 4.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 (55) hide show
  1. package/dist/blocks/designs/checkout-commerce-v1/block.d.ts +49 -0
  2. package/dist/blocks/designs/checkout-commerce-v1/block.d.ts.map +1 -0
  3. package/dist/blocks/designs/checkout-commerce-v1/block.js +2487 -0
  4. package/dist/blocks/designs/checkout-commerce-v1/block.js.map +1 -0
  5. package/dist/blocks/designs/checkout-commerce-v1/block.mjs +2467 -0
  6. package/dist/blocks/designs/checkout-commerce-v1/block.mjs.map +1 -0
  7. package/dist/blocks/designs/checkout-commerce-v1/data-context.d.ts +8 -0
  8. package/dist/blocks/designs/checkout-commerce-v1/data-context.d.ts.map +1 -0
  9. package/dist/blocks/designs/checkout-commerce-v1/data-context.js +44 -0
  10. package/dist/blocks/designs/checkout-commerce-v1/data-context.js.map +1 -0
  11. package/dist/blocks/designs/checkout-commerce-v1/data-context.mjs +19 -0
  12. package/dist/blocks/designs/checkout-commerce-v1/data-context.mjs.map +1 -0
  13. package/dist/blocks/designs/checkout-commerce-v1/index.d.ts +3 -0
  14. package/dist/blocks/designs/checkout-commerce-v1/index.d.ts.map +1 -0
  15. package/dist/blocks/designs/checkout-commerce-v1/index.js +36 -0
  16. package/dist/blocks/designs/checkout-commerce-v1/index.js.map +1 -0
  17. package/dist/blocks/designs/checkout-commerce-v1/index.mjs +6 -0
  18. package/dist/blocks/designs/checkout-commerce-v1/index.mjs.map +1 -0
  19. package/dist/blocks/e-commerce/account/account-frame.d.ts +38 -0
  20. package/dist/blocks/e-commerce/account/account-frame.d.ts.map +1 -0
  21. package/dist/blocks/e-commerce/account/account-frame.js +102 -0
  22. package/dist/blocks/e-commerce/account/account-frame.js.map +1 -0
  23. package/dist/blocks/e-commerce/account/account-frame.mjs +78 -0
  24. package/dist/blocks/e-commerce/account/account-frame.mjs.map +1 -0
  25. package/dist/blocks/e-commerce/account/address-book.d.ts +79 -0
  26. package/dist/blocks/e-commerce/account/address-book.d.ts.map +1 -0
  27. package/dist/blocks/e-commerce/account/address-book.js +250 -0
  28. package/dist/blocks/e-commerce/account/address-book.js.map +1 -0
  29. package/dist/blocks/e-commerce/account/address-book.mjs +225 -0
  30. package/dist/blocks/e-commerce/account/address-book.mjs.map +1 -0
  31. package/dist/blocks/e-commerce/account/profile-form.d.ts +35 -0
  32. package/dist/blocks/e-commerce/account/profile-form.d.ts.map +1 -0
  33. package/dist/blocks/e-commerce/account/profile-form.js +100 -0
  34. package/dist/blocks/e-commerce/account/profile-form.js.map +1 -0
  35. package/dist/blocks/e-commerce/account/profile-form.mjs +76 -0
  36. package/dist/blocks/e-commerce/account/profile-form.mjs.map +1 -0
  37. package/dist/index.d.ts +10 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +14 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/index.mjs +9 -0
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/lib/contracts/checkout-commerce-v1.d.ts +2350 -0
  44. package/dist/lib/contracts/checkout-commerce-v1.d.ts.map +1 -0
  45. package/dist/lib/contracts/checkout-commerce-v1.js +417 -0
  46. package/dist/lib/contracts/checkout-commerce-v1.js.map +1 -0
  47. package/dist/lib/contracts/checkout-commerce-v1.mjs +392 -0
  48. package/dist/lib/contracts/checkout-commerce-v1.mjs.map +1 -0
  49. package/dist/lib/contracts.d.ts +515 -177
  50. package/dist/lib/contracts.d.ts.map +1 -1
  51. package/dist/lib/contracts.js +81 -1
  52. package/dist/lib/contracts.js.map +1 -1
  53. package/dist/lib/contracts.mjs +70 -0
  54. package/dist/lib/contracts.mjs.map +1 -1
  55. package/package.json +1 -1
@@ -0,0 +1,2467 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { BlockDataProvider, useBlockData } from "./data-context.mjs";
4
+ import { Badge } from "../../../ui/badge.mjs";
5
+ import { Button } from "../../../ui/button.mjs";
6
+ import { Card, CardContent, CardFooter, CardHeader } from "../../../ui/card.mjs";
7
+ import { PRODUCT_IMAGE_FALLBACK } from "../../../lib/contracts.mjs";
8
+ import { useEffect, useId, useRef, useState } from "react";
9
+ function money(cents, data) {
10
+ const currencyFormatter = new Intl.NumberFormat(data.meta.locale, {
11
+ style: "currency",
12
+ currency: data.meta.currency,
13
+ minimumFractionDigits: 2
14
+ });
15
+ return currencyFormatter.format(cents / 100);
16
+ }
17
+ const VARIANT_SECONDARY = "secondary";
18
+ const VARIANT_OUTLINE = "outline";
19
+ const VARIANT_GHOST = "ghost";
20
+ const VARIANT_DESTRUCTIVE = "destructive";
21
+ const EMPTY_OPTION = "";
22
+ const AUTOCOMPLETE_NAME = "name";
23
+ const AUTOCOMPLETE_EMAIL = "email";
24
+ const AUTOCOMPLETE_TEL = "tel";
25
+ function fill(template, values) {
26
+ return Object.keys(values).reduce(
27
+ (text, key) => text.split(`{${key}}`).join(String(values[key])),
28
+ template
29
+ );
30
+ }
31
+ function countLabel(count, one, many) {
32
+ return count === 1 ? one : fill(many, { count });
33
+ }
34
+ function addressHeadline(address) {
35
+ return `${address.label} \xB7 ${address.province}`;
36
+ }
37
+ function addressSummary(address) {
38
+ return [address.line1, address.line2, address.city].filter(Boolean).join(", ");
39
+ }
40
+ function pickupDetail(point) {
41
+ return [point.address, point.hours].filter(Boolean).join(" \xB7 ");
42
+ }
43
+ function unitPriceFor(line, quantity) {
44
+ let price = line.unitCents;
45
+ for (const tier of line.tiers ?? []) {
46
+ if (quantity >= tier.minQuantity) price = tier.unitCents;
47
+ }
48
+ return price;
49
+ }
50
+ function currentTierFor(line, quantity) {
51
+ let current = null;
52
+ for (const tier of line.tiers ?? []) {
53
+ if (quantity >= tier.minQuantity) current = tier;
54
+ }
55
+ return current;
56
+ }
57
+ function nextTierFor(line, quantity) {
58
+ return (line.tiers ?? []).find((tier) => tier.minQuantity > quantity) ?? null;
59
+ }
60
+ function lineNetTotal(line) {
61
+ return unitPriceFor(line, line.quantity) * line.quantity - (line.lineDiscountCents ?? 0);
62
+ }
63
+ function tierRangeLabel(tier, data) {
64
+ return tier.maxQuantity > 0 ? fill(data.labels.tierRange, { min: tier.minQuantity, max: tier.maxQuantity }) : fill(data.labels.tierRangeFrom, { min: tier.minQuantity });
65
+ }
66
+ function moveOptionFocus(event, index, options, onSelect) {
67
+ const key = event.key;
68
+ let nextIndex = -1;
69
+ if (key === "ArrowDown" || key === "ArrowRight") nextIndex = (index + 1) % options.length;
70
+ if (key === "ArrowUp" || key === "ArrowLeft") nextIndex = (index - 1 + options.length) % options.length;
71
+ if (nextIndex < 0) return;
72
+ event.preventDefault();
73
+ const next = options[nextIndex];
74
+ if (next.disabled) return;
75
+ onSelect(next.id);
76
+ const siblings = event.currentTarget.parentElement?.children;
77
+ const target = siblings ? siblings[nextIndex] : void 0;
78
+ if (target instanceof HTMLElement) target.focus();
79
+ }
80
+ function OptionGroup({
81
+ legend,
82
+ options,
83
+ selectedId,
84
+ onSelect,
85
+ className
86
+ }) {
87
+ const uid = useId();
88
+ const labelId = `${uid}-label`;
89
+ return /* @__PURE__ */ jsxs("div", { children: [
90
+ /* @__PURE__ */ jsx("p", { id: labelId, className: "text-xs font-medium text-muted-foreground", children: legend }),
91
+ /* @__PURE__ */ jsx("div", { role: "radiogroup", "aria-labelledby": labelId, className, children: options.map((option, index) => /* @__PURE__ */ jsx(
92
+ Button,
93
+ {
94
+ type: "button",
95
+ role: "radio",
96
+ "aria-checked": selectedId === option.id,
97
+ tabIndex: selectedId === option.id ? 0 : -1,
98
+ disabled: option.disabled,
99
+ variant: selectedId === option.id ? "secondary" : "outline",
100
+ className: "h-auto w-full min-w-0 justify-start whitespace-normal px-3 py-2 text-left hover:bg-muted hover:text-foreground",
101
+ onClick: () => onSelect(option.id),
102
+ onKeyDown: (event) => moveOptionFocus(event, index, options, onSelect),
103
+ children: /* @__PURE__ */ jsxs("span", { className: "flex w-full min-w-0 flex-col items-start gap-0.5", children: [
104
+ /* @__PURE__ */ jsxs("span", { className: "flex w-full min-w-0 items-center justify-between gap-2", children: [
105
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: option.title }),
106
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
107
+ option.previousMeta ? /* @__PURE__ */ jsx("span", { className: "text-xs font-normal text-muted-foreground line-through", children: option.previousMeta }) : null,
108
+ option.meta ? /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold", children: option.meta }) : null
109
+ ] })
110
+ ] }),
111
+ option.detail ? /* @__PURE__ */ jsx("span", { className: "text-xs font-normal text-muted-foreground", children: option.detail }) : null,
112
+ option.disabled && option.reason ? /* @__PURE__ */ jsx("span", { className: "text-xs font-normal text-destructive", children: option.reason }) : null
113
+ ] })
114
+ },
115
+ option.id
116
+ )) })
117
+ ] });
118
+ }
119
+ function AddressChoices({
120
+ legend,
121
+ name,
122
+ addresses,
123
+ selectedId,
124
+ onSelect,
125
+ labels
126
+ }) {
127
+ return /* @__PURE__ */ jsxs("fieldset", { children: [
128
+ /* @__PURE__ */ jsx("legend", { className: "text-sm font-medium", children: legend }),
129
+ /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-2", children: addresses.map((address) => /* @__PURE__ */ jsxs(
130
+ "label",
131
+ {
132
+ className: "flex cursor-pointer items-start gap-3 rounded-md border border-border bg-background p-3",
133
+ children: [
134
+ /* @__PURE__ */ jsx(
135
+ "input",
136
+ {
137
+ type: "radio",
138
+ name,
139
+ checked: selectedId === address.id,
140
+ onChange: () => onSelect(address.id),
141
+ className: "mt-1 size-4"
142
+ }
143
+ ),
144
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-1", children: [
145
+ /* @__PURE__ */ jsxs("span", { className: "flex flex-wrap items-center gap-2", children: [
146
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: address.label }),
147
+ address.isDefault ? /* @__PURE__ */ jsx(Badge, { variant: VARIANT_SECONDARY, children: labels.addressDefaultBadge }) : null
148
+ ] }),
149
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: address.recipient }),
150
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: addressSummary(address) }),
151
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: address.phone })
152
+ ] })
153
+ ]
154
+ },
155
+ address.id
156
+ )) })
157
+ ] });
158
+ }
159
+ function AddressForm({
160
+ labels,
161
+ countries,
162
+ authenticated,
163
+ onSave,
164
+ onCancel
165
+ }) {
166
+ const data = useBlockData();
167
+ const emptyDraft = {
168
+ firstName: "",
169
+ lastName: "",
170
+ phone: "",
171
+ countryCode: data.countries[0].code,
172
+ province: "",
173
+ city: "",
174
+ line1: "",
175
+ line2: "",
176
+ postalCode: "",
177
+ instructions: ""
178
+ };
179
+ const uid = useId();
180
+ const [draft, setDraft] = useState(emptyDraft);
181
+ const [saveToAccount, setSaveToAccount] = useState(authenticated);
182
+ const [errors, setErrors] = useState({});
183
+ const country = countries.find((item) => item.code === draft.countryCode);
184
+ const province = country?.provinces.find((item) => item.name === draft.province);
185
+ const postalRule = country?.postalCode ?? "hidden";
186
+ function setField(key, value) {
187
+ setDraft((current) => ({ ...current, [key]: value }));
188
+ }
189
+ function handleCountry(code) {
190
+ setDraft((current) => ({ ...current, countryCode: code, province: "", city: "" }));
191
+ }
192
+ function handleProvince(name) {
193
+ setDraft((current) => ({ ...current, province: name, city: "" }));
194
+ }
195
+ function handleSubmit(event) {
196
+ event.preventDefault();
197
+ const next = {};
198
+ if (!draft.firstName.trim()) next.firstName = labels.addressRequired;
199
+ if (!draft.lastName.trim()) next.lastName = labels.addressRequired;
200
+ if (!draft.phone.trim()) next.phone = labels.addressRequired;
201
+ if (!draft.countryCode) next.countryCode = labels.addressRequired;
202
+ if (!draft.province) next.province = labels.addressRequired;
203
+ if (!draft.city) next.city = labels.addressRequired;
204
+ if (!draft.line1.trim()) next.line1 = labels.addressRequired;
205
+ if (postalRule === "required" && !draft.postalCode.trim()) {
206
+ next.postalCode = labels.addressRequired;
207
+ }
208
+ setErrors(next);
209
+ if (Object.keys(next).length > 0) return;
210
+ const recipient = `${draft.firstName.trim()} ${draft.lastName.trim()}`;
211
+ onSave(
212
+ {
213
+ id: `created-${Date.now()}`,
214
+ label: saveToAccount ? fill(labels.addressNewLabel, { city: draft.city }) : labels.addressTemporary,
215
+ recipient,
216
+ line1: draft.line1.trim(),
217
+ line2: draft.line2.trim(),
218
+ city: draft.city,
219
+ province: draft.province,
220
+ postalCode: draft.postalCode.trim(),
221
+ phone: draft.phone.trim(),
222
+ isDefault: false
223
+ },
224
+ saveToAccount
225
+ );
226
+ }
227
+ function field(key, label, type, autoComplete, requiredField, span) {
228
+ const fieldId = `${uid}-${key}`;
229
+ const error = errors[key];
230
+ return /* @__PURE__ */ jsxs("div", { className: span ? "sm:col-span-2" : void 0, children: [
231
+ /* @__PURE__ */ jsx("label", { htmlFor: fieldId, className: "text-xs font-medium", children: label }),
232
+ /* @__PURE__ */ jsx(
233
+ "input",
234
+ {
235
+ id: fieldId,
236
+ type,
237
+ autoComplete,
238
+ value: draft[key],
239
+ required: requiredField,
240
+ onChange: (event) => setField(key, event.target.value),
241
+ "aria-invalid": error ? true : void 0,
242
+ "aria-describedby": error ? `${fieldId}-error` : void 0,
243
+ className: "mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm"
244
+ }
245
+ ),
246
+ error ? /* @__PURE__ */ jsx("p", { id: `${fieldId}-error`, className: "mt-1 text-xs text-destructive", children: error }) : null
247
+ ] });
248
+ }
249
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, noValidate: true, children: [
250
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: labels.addressFormTitle }),
251
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 grid gap-3 sm:grid-cols-2", children: [
252
+ field("firstName", labels.addressFirstName, "text", "given-name", true, false),
253
+ field("lastName", labels.addressLastName, "text", "family-name", true, false),
254
+ field("phone", labels.addressPhone, "tel", "tel", true, false),
255
+ /* @__PURE__ */ jsxs("div", { children: [
256
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-country`, className: "text-xs font-medium", children: labels.addressCountry }),
257
+ /* @__PURE__ */ jsxs(
258
+ "select",
259
+ {
260
+ id: `${uid}-country`,
261
+ value: draft.countryCode,
262
+ onChange: (event) => handleCountry(event.target.value),
263
+ className: "mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm",
264
+ children: [
265
+ /* @__PURE__ */ jsx("option", { value: EMPTY_OPTION, children: labels.addressSelectPlaceholder }),
266
+ countries.map((item) => /* @__PURE__ */ jsx("option", { value: item.code, children: item.name }, item.code))
267
+ ]
268
+ }
269
+ ),
270
+ errors.countryCode ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive", children: errors.countryCode }) : null
271
+ ] }),
272
+ /* @__PURE__ */ jsxs("div", { children: [
273
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-province`, className: "text-xs font-medium", children: labels.addressProvince }),
274
+ /* @__PURE__ */ jsxs(
275
+ "select",
276
+ {
277
+ id: `${uid}-province`,
278
+ value: draft.province,
279
+ onChange: (event) => handleProvince(event.target.value),
280
+ className: "mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm",
281
+ children: [
282
+ /* @__PURE__ */ jsx("option", { value: EMPTY_OPTION, children: labels.addressSelectPlaceholder }),
283
+ (country?.provinces ?? []).map((item) => /* @__PURE__ */ jsx("option", { value: item.name, children: item.name }, item.name))
284
+ ]
285
+ }
286
+ ),
287
+ errors.province ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive", children: errors.province }) : null
288
+ ] }),
289
+ /* @__PURE__ */ jsxs("div", { children: [
290
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-city`, className: "text-xs font-medium", children: labels.addressCity }),
291
+ /* @__PURE__ */ jsxs(
292
+ "select",
293
+ {
294
+ id: `${uid}-city`,
295
+ value: draft.city,
296
+ onChange: (event) => setField("city", event.target.value),
297
+ className: "mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm",
298
+ children: [
299
+ /* @__PURE__ */ jsx("option", { value: EMPTY_OPTION, children: labels.addressSelectPlaceholder }),
300
+ (province?.cities ?? []).map((item) => /* @__PURE__ */ jsx("option", { value: item, children: item }, item))
301
+ ]
302
+ }
303
+ ),
304
+ errors.city ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive", children: errors.city }) : null
305
+ ] }),
306
+ field("line1", labels.addressLine1, "text", "address-line1", true, true),
307
+ field("line2", labels.addressLine2, "text", "address-line2", false, true),
308
+ postalRule === "hidden" ? null : field(
309
+ "postalCode",
310
+ labels.addressPostal,
311
+ "text",
312
+ "postal-code",
313
+ postalRule === "required",
314
+ false
315
+ ),
316
+ /* @__PURE__ */ jsxs("div", { className: "sm:col-span-2", children: [
317
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-instructions`, className: "text-xs font-medium", children: labels.addressInstructions }),
318
+ /* @__PURE__ */ jsx(
319
+ "textarea",
320
+ {
321
+ id: `${uid}-instructions`,
322
+ rows: 2,
323
+ value: draft.instructions,
324
+ onChange: (event) => setField("instructions", event.target.value),
325
+ className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
326
+ }
327
+ )
328
+ ] })
329
+ ] }),
330
+ authenticated ? /* @__PURE__ */ jsxs("label", { className: "mt-3 flex items-center gap-2 text-sm", children: [
331
+ /* @__PURE__ */ jsx(
332
+ "input",
333
+ {
334
+ type: "checkbox",
335
+ checked: saveToAccount,
336
+ onChange: (event) => setSaveToAccount(event.target.checked),
337
+ className: "size-4"
338
+ }
339
+ ),
340
+ /* @__PURE__ */ jsx("span", { children: labels.addressSaveAccount })
341
+ ] }) : /* @__PURE__ */ jsx("p", { className: "mt-3 text-xs text-muted-foreground", children: labels.addressUseOnce }),
342
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
343
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", children: labels.addressSave }),
344
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: VARIANT_GHOST, onClick: onCancel, children: labels.addressCancel })
345
+ ] })
346
+ ] });
347
+ }
348
+ function BuyerSection({
349
+ uid,
350
+ session,
351
+ name,
352
+ email,
353
+ phone,
354
+ guestConfirmed,
355
+ authPhoneComplete,
356
+ errors,
357
+ onFieldChange,
358
+ onGuestSave,
359
+ onGuestEdit,
360
+ onAuthPhoneSave,
361
+ onRetrySession,
362
+ onStartRegistration
363
+ }) {
364
+ const data = useBlockData();
365
+ const labels = data.labels;
366
+ const isGuest = session === "guest";
367
+ const title = isGuest ? labels.buyerTitleGuest : labels.buyerTitleAuth;
368
+ return /* @__PURE__ */ jsxs("section", { "aria-labelledby": `${uid}-buyer`, "aria-busy": session === "loading", children: [
369
+ /* @__PURE__ */ jsx(
370
+ "h3",
371
+ {
372
+ id: `${uid}-buyer`,
373
+ className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground",
374
+ children: title
375
+ }
376
+ ),
377
+ session === "loading" ? /* @__PURE__ */ jsxs("div", { className: "mt-1", children: [
378
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: labels.sessionLoading }),
379
+ /* @__PURE__ */ jsx("div", { className: "mt-2 h-4 w-40 rounded bg-muted" })
380
+ ] }) : session === "error" ? /* @__PURE__ */ jsxs("div", { className: "mt-1", children: [
381
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: labels.sessionError }),
382
+ /* @__PURE__ */ jsx(
383
+ Button,
384
+ {
385
+ type: "button",
386
+ size: "xs",
387
+ variant: VARIANT_OUTLINE,
388
+ className: "mt-1.5",
389
+ onClick: onRetrySession,
390
+ children: labels.retryAction
391
+ }
392
+ )
393
+ ] }) : !isGuest ? /* @__PURE__ */ jsxs("div", { children: [
394
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm font-medium", children: name }),
395
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: [email, authPhoneComplete ? phone : ""].filter(Boolean).join(" \xB7 ") }),
396
+ !authPhoneComplete ? /* @__PURE__ */ jsxs(
397
+ "form",
398
+ {
399
+ className: "mt-2",
400
+ onSubmit: (event) => {
401
+ event.preventDefault();
402
+ onAuthPhoneSave();
403
+ },
404
+ noValidate: true,
405
+ children: [
406
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-buyer-phone`, className: "text-xs font-medium", children: labels.contactPhone }),
407
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap items-start gap-2", children: [
408
+ /* @__PURE__ */ jsx(
409
+ "input",
410
+ {
411
+ id: `${uid}-buyer-phone`,
412
+ type: "tel",
413
+ autoComplete: AUTOCOMPLETE_TEL,
414
+ value: phone,
415
+ onChange: (event) => onFieldChange("phone", event.target.value),
416
+ "aria-invalid": errors.contactPhone ? true : void 0,
417
+ "aria-describedby": errors.contactPhone ? `${uid}-buyer-phone-error` : void 0,
418
+ className: "h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-3 text-sm"
419
+ }
420
+ ),
421
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", children: labels.buyerSavePhone })
422
+ ] }),
423
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.buyerPhoneHint }),
424
+ errors.contactPhone ? /* @__PURE__ */ jsx("p", { id: `${uid}-buyer-phone-error`, className: "mt-1 text-xs text-destructive", children: errors.contactPhone }) : null
425
+ ]
426
+ }
427
+ ) : null
428
+ ] }) : guestConfirmed ? /* @__PURE__ */ jsxs("div", { children: [
429
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-start justify-between gap-2", children: [
430
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
431
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: name }),
432
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: [email, phone].filter(Boolean).join(" \xB7 ") })
433
+ ] }),
434
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: VARIANT_OUTLINE, onClick: onGuestEdit, children: labels.contactEdit })
435
+ ] }),
436
+ onStartRegistration ? /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [
437
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: labels.accountOptional }),
438
+ /* @__PURE__ */ jsx(
439
+ Button,
440
+ {
441
+ type: "button",
442
+ size: "xs",
443
+ variant: VARIANT_GHOST,
444
+ onClick: () => onStartRegistration({ name, email, phone }),
445
+ children: labels.createAccount
446
+ }
447
+ )
448
+ ] }) : null
449
+ ] }) : /* @__PURE__ */ jsxs(
450
+ "form",
451
+ {
452
+ className: "mt-2 grid gap-3 sm:grid-cols-2",
453
+ onSubmit: (event) => {
454
+ event.preventDefault();
455
+ onGuestSave();
456
+ },
457
+ noValidate: true,
458
+ children: [
459
+ /* @__PURE__ */ jsxs("div", { className: "sm:col-span-2", children: [
460
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-buyer-name`, className: "text-xs font-medium", children: labels.contactName }),
461
+ /* @__PURE__ */ jsx(
462
+ "input",
463
+ {
464
+ id: `${uid}-buyer-name`,
465
+ type: "text",
466
+ autoComplete: AUTOCOMPLETE_NAME,
467
+ value: name,
468
+ onChange: (event) => onFieldChange("name", event.target.value),
469
+ "aria-invalid": errors.contactName ? true : void 0,
470
+ "aria-describedby": errors.contactName ? `${uid}-buyer-name-error` : void 0,
471
+ className: "mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm"
472
+ }
473
+ ),
474
+ errors.contactName ? /* @__PURE__ */ jsx("p", { id: `${uid}-buyer-name-error`, className: "mt-1 text-xs text-destructive", children: errors.contactName }) : null
475
+ ] }),
476
+ /* @__PURE__ */ jsxs("div", { children: [
477
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-buyer-email`, className: "text-xs font-medium", children: labels.contactEmail }),
478
+ /* @__PURE__ */ jsx(
479
+ "input",
480
+ {
481
+ id: `${uid}-buyer-email`,
482
+ type: "email",
483
+ autoComplete: AUTOCOMPLETE_EMAIL,
484
+ value: email,
485
+ onChange: (event) => onFieldChange("email", event.target.value),
486
+ "aria-invalid": errors.contactEmail ? true : void 0,
487
+ "aria-describedby": errors.contactEmail ? `${uid}-buyer-email-error` : void 0,
488
+ className: "mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm"
489
+ }
490
+ ),
491
+ errors.contactEmail ? /* @__PURE__ */ jsx("p", { id: `${uid}-buyer-email-error`, className: "mt-1 text-xs text-destructive", children: errors.contactEmail }) : null
492
+ ] }),
493
+ /* @__PURE__ */ jsxs("div", { children: [
494
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-buyer-phone`, className: "text-xs font-medium", children: labels.contactPhone }),
495
+ /* @__PURE__ */ jsx(
496
+ "input",
497
+ {
498
+ id: `${uid}-buyer-phone`,
499
+ type: "tel",
500
+ autoComplete: AUTOCOMPLETE_TEL,
501
+ value: phone,
502
+ onChange: (event) => onFieldChange("phone", event.target.value),
503
+ "aria-invalid": errors.contactPhone ? true : void 0,
504
+ "aria-describedby": errors.contactPhone ? `${uid}-buyer-phone-error` : void 0,
505
+ className: "mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm"
506
+ }
507
+ ),
508
+ errors.contactPhone ? /* @__PURE__ */ jsx("p", { id: `${uid}-buyer-phone-error`, className: "mt-1 text-xs text-destructive", children: errors.contactPhone }) : null
509
+ ] }),
510
+ /* @__PURE__ */ jsx("div", { className: "sm:col-span-2", children: /* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", children: labels.buyerContinue }) })
511
+ ]
512
+ }
513
+ )
514
+ ] });
515
+ }
516
+ function SummaryRow({
517
+ label,
518
+ value,
519
+ emphasis
520
+ }) {
521
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [
522
+ /* @__PURE__ */ jsx("dt", { className: emphasis ? "text-sm font-semibold" : "text-sm text-muted-foreground", children: label }),
523
+ /* @__PURE__ */ jsx("dd", { className: emphasis ? "text-sm font-semibold tabular-nums" : "text-sm tabular-nums", children: value })
524
+ ] });
525
+ }
526
+ function CheckoutCommerceV1Content({
527
+ data,
528
+ onQuantityChange,
529
+ onRemoveItem,
530
+ onSelectAddress,
531
+ onCreateAddress,
532
+ onStartRegistration,
533
+ onAddAssociate,
534
+ onConfirmOrder,
535
+ onContinueShopping
536
+ }) {
537
+ const [scenarioId, setScenarioId] = useState(data.meta.initialScenarioId);
538
+ const scenario = data.scenarios.find((item) => item.id === scenarioId) ?? data.scenarios[0];
539
+ return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
540
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 rounded-lg border border-border bg-muted/40 p-3", children: [
541
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
542
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: data.meta.demoTitle }),
543
+ /* @__PURE__ */ jsx(Badge, { variant: VARIANT_OUTLINE, children: data.labels.demoBadge })
544
+ ] }),
545
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-1.5", role: "group", "aria-label": data.labels.scenarioGroup, children: data.scenarios.map((item) => /* @__PURE__ */ jsx(
546
+ Button,
547
+ {
548
+ type: "button",
549
+ size: "sm",
550
+ variant: item.id === scenario.id ? "default" : "outline",
551
+ "aria-pressed": item.id === scenario.id,
552
+ "aria-label": item.name,
553
+ onClick: () => setScenarioId(item.id),
554
+ children: item.id
555
+ },
556
+ item.id
557
+ )) }),
558
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: fill(data.meta.demoCurrent, { name: scenario.name, blurb: scenario.blurb }) }),
559
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: data.meta.demoHint })
560
+ ] }),
561
+ /* @__PURE__ */ jsx(
562
+ CheckoutExperience,
563
+ {
564
+ scenario,
565
+ onQuantityChange,
566
+ onRemoveItem,
567
+ onSelectAddress,
568
+ onCreateAddress,
569
+ onStartRegistration,
570
+ onAddAssociate,
571
+ onConfirmOrder,
572
+ onContinueShopping
573
+ },
574
+ scenario.id
575
+ )
576
+ ] });
577
+ }
578
+ function CheckoutExperience({
579
+ scenario,
580
+ onQuantityChange,
581
+ onRemoveItem,
582
+ onSelectAddress,
583
+ onCreateAddress,
584
+ onStartRegistration,
585
+ onAddAssociate,
586
+ onConfirmOrder,
587
+ onContinueShopping
588
+ }) {
589
+ const data = useBlockData();
590
+ const uid = useId();
591
+ const labels = data.labels;
592
+ const [lines, setLines] = useState(
593
+ () => scenario.groups.flatMap(
594
+ (group) => group.items.flatMap((item) => {
595
+ const mains = [{ ...item, groupId: group.id }];
596
+ const children = (item.associates ?? []).filter((associate) => (associate.initialQuantity ?? 0) > 0).map((associate) => ({
597
+ lineId: associate.lineId,
598
+ groupId: associate.groupId,
599
+ title: associate.title,
600
+ variant: associate.variant,
601
+ image: associate.image,
602
+ unitCents: associate.unitCents,
603
+ quantity: associate.initialQuantity ?? 1,
604
+ stock: associate.stock,
605
+ isAssociate: true,
606
+ parentLineId: item.lineId,
607
+ associateId: associate.id,
608
+ maxQuantity: associate.maxQuantity,
609
+ minQuantity: associate.minQuantity ?? 1,
610
+ maxPerMain: associate.maxPerMain ?? 0,
611
+ overflowPolicy: associate.overflowPolicy ?? "clamp",
612
+ regularCents: associate.regularCents,
613
+ discountLabel: associate.discountLabel,
614
+ tiers: associate.tiers,
615
+ lineDiscountCents: associate.lineDiscountCents,
616
+ lineDiscountLabel: associate.lineDiscountLabel
617
+ }));
618
+ return [...mains, ...children];
619
+ })
620
+ )
621
+ );
622
+ const [groupStates, setGroupStates] = useState(
623
+ () => scenario.groups.map((group) => ({
624
+ id: group.id,
625
+ mode: group.initialMode,
626
+ shippingOptionId: group.initialShippingOptionId,
627
+ pickupPointId: group.initialPickupPointId,
628
+ rateState: group.rateState
629
+ }))
630
+ );
631
+ const [addressBook, setAddressBook] = useState(() => scenario.customer.addresses);
632
+ const [shippingAddressId, setShippingAddressId] = useState(scenario.customer.defaultAddressId);
633
+ const [addressOpen, setAddressOpen] = useState(false);
634
+ const [addressMode, setAddressMode] = useState("list");
635
+ const [openPanel, setOpenPanel] = useState("");
636
+ const [addingAssociate, setAddingAssociate] = useState("");
637
+ const [failedAdds, setFailedAdds] = useState([]);
638
+ const [removingLine, setRemovingLine] = useState("");
639
+ const [confirmRemoveMain, setConfirmRemoveMain] = useState("");
640
+ const [associateNotice, setAssociateNotice] = useState(null);
641
+ const [assocOpen, setAssocOpen] = useState("");
642
+ const [contactName, setContactName] = useState(scenario.customer.name);
643
+ const [contactEmail, setContactEmail] = useState(scenario.customer.email);
644
+ const [contactPhone, setContactPhone] = useState(scenario.customer.phone);
645
+ const [guestConfirmed, setGuestConfirmed] = useState(false);
646
+ const [authPhoneComplete, setAuthPhoneComplete] = useState(Boolean(scenario.customer.phone));
647
+ const [sessionRetrying, setSessionRetrying] = useState(false);
648
+ const [formErrors, setFormErrors] = useState({});
649
+ const [paymentId, setPaymentId] = useState(scenario.selectedPaymentId);
650
+ const [billingSame, setBillingSame] = useState(scenario.sameAsShipping);
651
+ const [billingId, setBillingId] = useState(scenario.billingAddressId);
652
+ const [promotions, setPromotions] = useState(() => scenario.promotions);
653
+ const [couponOpen, setCouponOpen] = useState(false);
654
+ const [couponCode, setCouponCode] = useState("");
655
+ const [couponNotice, setCouponNotice] = useState(null);
656
+ const [paymentOpen, setPaymentOpen] = useState(false);
657
+ const [openTiers, setOpenTiers] = useState([]);
658
+ const [priceUpdating, setPriceUpdating] = useState("");
659
+ const priceTimer = useRef(null);
660
+ const [stockMessage, setStockMessage] = useState("");
661
+ const [paymentError, setPaymentError] = useState(false);
662
+ const [paymentRetried, setPaymentRetried] = useState(false);
663
+ const [creatingOrder, setCreatingOrder] = useState(false);
664
+ const orderTimer = useRef(null);
665
+ const [confirmation, setConfirmation] = useState(null);
666
+ const activeGroups = scenario.groups.filter(
667
+ (group) => lines.some((line) => line.groupId === group.id)
668
+ );
669
+ const session = sessionRetrying ? "loading" : scenario.customer.sessionState;
670
+ function stateFor(groupId) {
671
+ return groupStates.find((state) => state.id === groupId) ?? {
672
+ id: groupId,
673
+ mode: "shipping",
674
+ shippingOptionId: "",
675
+ pickupPointId: "",
676
+ rateState: "pending"
677
+ };
678
+ }
679
+ const shippingAddress = addressBook.find((address) => address.id === shippingAddressId) ?? null;
680
+ function coverageFor(group, addressId) {
681
+ if (!addressId) return "none";
682
+ return (group.blockedAddresses ?? []).includes(addressId) ? "none" : "ok";
683
+ }
684
+ function optionFor(group, state) {
685
+ return group.shippingOptions.find((option) => option.id === state.shippingOptionId) ?? null;
686
+ }
687
+ function pointFor(group, state) {
688
+ return group.pickupPoints.find((point) => point.id === state.pickupPointId) ?? null;
689
+ }
690
+ function readiness(group, state) {
691
+ if (state.rateState !== "ready") return false;
692
+ if (state.mode === "pickup") {
693
+ const point = pointFor(group, state);
694
+ return point !== null && point.available !== false;
695
+ }
696
+ return Boolean(shippingAddress) && coverageFor(group, shippingAddressId) === "ok" && Boolean(optionFor(group, state));
697
+ }
698
+ function feeFor(group, state) {
699
+ if (state.mode === "pickup") {
700
+ const point = pointFor(group, state);
701
+ return point ? Math.max(0, point.feeCents - (point.discountCents ?? 0)) : 0;
702
+ }
703
+ const option = optionFor(group, state);
704
+ return option ? Math.max(0, option.feeCents - (option.discountCents ?? 0)) : 0;
705
+ }
706
+ function baseFeeFor(group, state) {
707
+ if (state.mode === "pickup") return pointFor(group, state)?.feeCents ?? 0;
708
+ return optionFor(group, state)?.feeCents ?? 0;
709
+ }
710
+ function resolveRate(group, addressId) {
711
+ window.setTimeout(() => {
712
+ setGroupStates(
713
+ (current) => current.map((state) => {
714
+ if (state.id !== group.id) return state;
715
+ const covered = state.mode === "pickup" || coverageFor(group, addressId) === "ok";
716
+ return {
717
+ ...state,
718
+ rateState: covered ? "ready" : "none",
719
+ shippingOptionId: state.mode === "shipping" ? group.shippingOptions.some((option) => option.id === state.shippingOptionId) ? state.shippingOptionId : group.shippingOptions[0]?.id || "" : "",
720
+ pickupPointId: state.mode === "pickup" ? group.pickupPoints.some(
721
+ (point) => point.id === state.pickupPointId && point.available !== false
722
+ ) ? state.pickupPointId : group.pickupPoints.find((point) => point.available !== false)?.id || "" : ""
723
+ };
724
+ })
725
+ );
726
+ }, 900);
727
+ }
728
+ function recalc(group) {
729
+ setGroupStates(
730
+ (current) => current.map((state) => state.id === group.id ? { ...state, rateState: "pending" } : state)
731
+ );
732
+ resolveRate(group, shippingAddressId);
733
+ }
734
+ useEffect(() => {
735
+ scenario.groups.filter((group) => group.rateState === "pending").forEach((group) => resolveRate(group, scenario.customer.defaultAddressId));
736
+ }, []);
737
+ useEffect(
738
+ () => () => {
739
+ if (orderTimer.current) window.clearTimeout(orderTimer.current);
740
+ },
741
+ []
742
+ );
743
+ function selectMode(group, mode) {
744
+ const covered = mode === "pickup" || coverageFor(group, shippingAddressId) === "ok";
745
+ setGroupStates(
746
+ (current) => current.map(
747
+ (state) => state.id === group.id ? {
748
+ ...state,
749
+ mode,
750
+ rateState: covered ? "pending" : "none"
751
+ } : state
752
+ )
753
+ );
754
+ if (covered) resolveRate(group, shippingAddressId);
755
+ }
756
+ function chooseShippingOption(group, optionId) {
757
+ setGroupStates(
758
+ (current) => current.map(
759
+ (state) => state.id === group.id ? { ...state, mode: "shipping", shippingOptionId: optionId, rateState: "ready" } : state
760
+ )
761
+ );
762
+ setOpenPanel("");
763
+ }
764
+ function choosePickupPoint(group, pointId) {
765
+ const point = group.pickupPoints.find((item) => item.id === pointId);
766
+ if (!point || point.available === false) return;
767
+ setGroupStates(
768
+ (current) => current.map(
769
+ (state) => state.id === group.id ? { ...state, mode: "pickup", pickupPointId: pointId, rateState: "ready" } : state
770
+ )
771
+ );
772
+ setOpenPanel("");
773
+ }
774
+ function revalidateShipping(addressId) {
775
+ activeGroups.forEach((group) => {
776
+ const state = stateFor(group.id);
777
+ if (state.mode !== "shipping") return;
778
+ setGroupStates(
779
+ (current) => current.map(
780
+ (entry) => entry.id === group.id ? { ...entry, rateState: "pending" } : entry
781
+ )
782
+ );
783
+ resolveRate(group, addressId);
784
+ });
785
+ }
786
+ function selectShippingAddress(addressId) {
787
+ setShippingAddressId(addressId);
788
+ setAddressOpen(false);
789
+ setAddressMode("list");
790
+ onSelectAddress?.(addressId);
791
+ revalidateShipping(addressId);
792
+ }
793
+ function openAddressEditor() {
794
+ setAddressOpen(true);
795
+ setAddressMode(addressBook.length === 0 ? "new" : "list");
796
+ window.setTimeout(() => {
797
+ document.getElementById(`${uid}-address-panel`)?.scrollIntoView({ block: "center" });
798
+ }, 60);
799
+ }
800
+ function saveNewAddress(address, saveToAccount) {
801
+ const nextAddress = {
802
+ ...address,
803
+ label: saveToAccount ? address.label : labels.addressTemporary,
804
+ isDefault: addressBook.length === 0
805
+ };
806
+ setAddressBook((current) => [...current, nextAddress]);
807
+ setShippingAddressId(nextAddress.id);
808
+ setAddressOpen(false);
809
+ setAddressMode("list");
810
+ onCreateAddress?.({ ...nextAddress, saveToAccount });
811
+ revalidateShipping(nextAddress.id);
812
+ }
813
+ function associatesFor(mainLineId) {
814
+ for (const group of scenario.groups) {
815
+ const item = group.items.find((entry) => entry.lineId === mainLineId);
816
+ if (item?.associates) return item.associates;
817
+ }
818
+ return [];
819
+ }
820
+ function mainQuantityFor(line) {
821
+ if (!line.parentLineId) return line.quantity;
822
+ return lines.find((entry) => entry.lineId === line.parentLineId)?.quantity ?? line.quantity;
823
+ }
824
+ function associateMaxFor(line, mainQuantity) {
825
+ const base = line.maxQuantity ?? line.stock;
826
+ const perMain = line.maxPerMain ?? 0;
827
+ return Math.min(line.stock, Math.max(0, base + Math.max(0, mainQuantity - 1) * perMain));
828
+ }
829
+ function associateDefMax(def, mainQuantity) {
830
+ const perMain = def.maxPerMain ?? 0;
831
+ return Math.min(
832
+ def.stock,
833
+ Math.max(0, def.maxQuantity + Math.max(0, mainQuantity - 1) * perMain)
834
+ );
835
+ }
836
+ function associateDiscountFor(unitCents, regularCents) {
837
+ const regular = regularCents ?? 0;
838
+ if (regular <= unitCents) return null;
839
+ const amountCents = regular - unitCents;
840
+ return { amountCents, percent: Math.round(amountCents / regular * 100) };
841
+ }
842
+ function orderGroupLines(groupLines) {
843
+ const ordered = [];
844
+ const placed = /* @__PURE__ */ new Set();
845
+ for (const main of groupLines) {
846
+ if (main.isAssociate) continue;
847
+ ordered.push(main);
848
+ placed.add(main.lineId);
849
+ for (const child of groupLines) {
850
+ if (child.parentLineId === main.lineId) {
851
+ ordered.push(child);
852
+ placed.add(child.lineId);
853
+ }
854
+ }
855
+ }
856
+ for (const line of groupLines) {
857
+ if (!placed.has(line.lineId)) ordered.push(line);
858
+ }
859
+ return ordered;
860
+ }
861
+ function refreshGroup(groupId) {
862
+ const group = scenario.groups.find((entry) => entry.id === groupId);
863
+ if (!group) return;
864
+ if (stateFor(groupId).mode !== "shipping") return;
865
+ setGroupStates(
866
+ (current) => current.map((state) => state.id === groupId ? { ...state, rateState: "pending" } : state)
867
+ );
868
+ resolveRate(group, shippingAddressId);
869
+ }
870
+ function applyAssociatePolicy(nextLines, main, nextQuantity) {
871
+ let result = nextLines;
872
+ let notice = null;
873
+ const children = nextLines.filter((entry) => entry.parentLineId === main.lineId);
874
+ const removed = [];
875
+ for (const child of children) {
876
+ const max = associateMaxFor(child, nextQuantity);
877
+ const min = child.minQuantity ?? 1;
878
+ if (child.quantity <= max) continue;
879
+ if ((child.overflowPolicy ?? "clamp") === "remove" || max < min) {
880
+ result = result.filter((entry) => entry.lineId !== child.lineId);
881
+ removed.push(child);
882
+ notice = {
883
+ mainLineId: main.lineId,
884
+ text: fill(labels.associateRemoved, { name: child.title }),
885
+ error: false
886
+ };
887
+ } else {
888
+ result = result.map(
889
+ (entry) => entry.lineId === child.lineId ? { ...entry, quantity: max } : entry
890
+ );
891
+ onQuantityChange?.(child.lineId, max);
892
+ notice = {
893
+ mainLineId: main.lineId,
894
+ text: fill(labels.associateAdjusted, { name: child.title, count: max }),
895
+ error: false
896
+ };
897
+ }
898
+ }
899
+ removed.forEach((child) => {
900
+ onRemoveItem?.(child.lineId);
901
+ });
902
+ return { lines: result, notice };
903
+ }
904
+ function changeQuantity(lineId, next) {
905
+ const line = lines.find((item) => item.lineId === lineId);
906
+ if (!line) return;
907
+ if (line.isAssociate) {
908
+ const max = associateMaxFor(line, mainQuantityFor(line));
909
+ const min = line.minQuantity ?? 1;
910
+ if (min === 0 && next === 0) {
911
+ requestRemoveLine(line);
912
+ return;
913
+ }
914
+ if (next < min) return;
915
+ if (next > max) {
916
+ setAssociateNotice({
917
+ mainLineId: line.parentLineId ?? "",
918
+ text: fill(labels.associateMax, { count: max }),
919
+ error: false
920
+ });
921
+ return;
922
+ }
923
+ } else {
924
+ if (next < 1 || line.stock < 1) return;
925
+ if (next > line.stock) {
926
+ setStockMessage(fill(labels.stockMax, { name: line.title }));
927
+ return;
928
+ }
929
+ }
930
+ setStockMessage("");
931
+ setAssociateNotice(null);
932
+ const nextLines = lines.map(
933
+ (item) => item.lineId === lineId ? { ...item, quantity: next } : item
934
+ );
935
+ const adjusted = line.isAssociate ? { lines: nextLines, notice: null } : applyAssociatePolicy(nextLines, line, next);
936
+ setLines(adjusted.lines);
937
+ if (adjusted.notice) setAssociateNotice(adjusted.notice);
938
+ setPriceUpdating(lineId);
939
+ if (priceTimer.current) window.clearTimeout(priceTimer.current);
940
+ priceTimer.current = window.setTimeout(() => setPriceUpdating(""), 500);
941
+ onQuantityChange?.(lineId, next);
942
+ }
943
+ function addAssociate(main, def) {
944
+ if (addingAssociate) return;
945
+ if (def.eligible === false || def.stock < 1) return;
946
+ const max = associateDefMax(def, main.quantity);
947
+ if (max < 1) return;
948
+ setAssociateNotice(null);
949
+ setAddingAssociate(def.id);
950
+ window.setTimeout(() => {
951
+ if (def.simulateFailure && !failedAdds.includes(def.id)) {
952
+ setFailedAdds((current) => [...current, def.id]);
953
+ setAddingAssociate("");
954
+ setAssociateNotice({
955
+ mainLineId: main.lineId,
956
+ text: fill(labels.associateAddError, { name: def.title }),
957
+ error: true
958
+ });
959
+ return;
960
+ }
961
+ const quantity = Math.max(1, def.minQuantity ?? 1);
962
+ const nextLine = {
963
+ lineId: def.lineId,
964
+ groupId: def.groupId,
965
+ title: def.title,
966
+ variant: def.variant,
967
+ image: def.image,
968
+ unitCents: def.unitCents,
969
+ quantity,
970
+ stock: def.stock,
971
+ isAssociate: true,
972
+ parentLineId: main.lineId,
973
+ associateId: def.id,
974
+ maxQuantity: def.maxQuantity,
975
+ minQuantity: def.minQuantity ?? 1,
976
+ maxPerMain: def.maxPerMain ?? 0,
977
+ overflowPolicy: def.overflowPolicy ?? "clamp",
978
+ regularCents: def.regularCents,
979
+ discountLabel: def.discountLabel,
980
+ tiers: def.tiers,
981
+ lineDiscountCents: def.lineDiscountCents,
982
+ lineDiscountLabel: def.lineDiscountLabel
983
+ };
984
+ setLines(
985
+ (current) => current.some((entry) => entry.lineId === def.lineId) ? current : [...current, nextLine]
986
+ );
987
+ setAddingAssociate("");
988
+ setAssocOpen("");
989
+ onAddAssociate?.({
990
+ associateId: def.id,
991
+ parentLineId: main.lineId,
992
+ lineId: def.lineId,
993
+ groupId: def.groupId,
994
+ quantity,
995
+ unitCents: def.unitCents
996
+ });
997
+ refreshGroup(def.groupId);
998
+ }, 500);
999
+ }
1000
+ function removeWithPending(mainLineId, ids, mainName) {
1001
+ setRemovingLine(mainLineId);
1002
+ window.setTimeout(() => {
1003
+ const removed = lines.filter((entry) => ids.includes(entry.lineId));
1004
+ const groups = [...new Set(removed.map((entry) => entry.groupId))];
1005
+ setLines((current) => current.filter((entry) => !ids.includes(entry.lineId)));
1006
+ removed.forEach((entry) => onRemoveItem?.(entry.lineId));
1007
+ if (ids.length > 1) {
1008
+ setAssociateNotice({
1009
+ mainLineId: "",
1010
+ text: fill(labels.associateMainRemoved, {
1011
+ name: mainName,
1012
+ count: ids.length - 1
1013
+ }),
1014
+ error: false
1015
+ });
1016
+ }
1017
+ groups.forEach((groupId) => {
1018
+ if (lines.some((entry) => entry.groupId === groupId && !ids.includes(entry.lineId))) {
1019
+ refreshGroup(groupId);
1020
+ }
1021
+ });
1022
+ setRemovingLine("");
1023
+ setConfirmRemoveMain("");
1024
+ }, 250);
1025
+ }
1026
+ function requestRemoveLine(line) {
1027
+ if (line.isAssociate) {
1028
+ removeWithPending(line.lineId, [line.lineId], line.title);
1029
+ return;
1030
+ }
1031
+ const children = lines.filter((entry) => entry.parentLineId === line.lineId);
1032
+ if (children.length > 0) {
1033
+ setConfirmRemoveMain(line.lineId);
1034
+ return;
1035
+ }
1036
+ removeWithPending(line.lineId, [line.lineId], line.title);
1037
+ }
1038
+ function confirmRemoveMainLine(line) {
1039
+ const ids = [
1040
+ line.lineId,
1041
+ ...lines.filter((entry) => entry.parentLineId === line.lineId).map((entry) => entry.lineId)
1042
+ ];
1043
+ removeWithPending(line.lineId, ids, line.title);
1044
+ }
1045
+ function changeBuyerField(field, value) {
1046
+ const errorKey = field === "name" ? "contactName" : field === "email" ? "contactEmail" : "contactPhone";
1047
+ if (field === "name") setContactName(value);
1048
+ if (field === "email") setContactEmail(value);
1049
+ if (field === "phone") setContactPhone(value);
1050
+ setFormErrors((current) => {
1051
+ const next = { ...current };
1052
+ delete next[errorKey];
1053
+ return next;
1054
+ });
1055
+ }
1056
+ function validateGuestContact() {
1057
+ const next = {};
1058
+ if (!contactName.trim()) next.contactName = labels.contactNameMissing;
1059
+ if (!contactEmail.trim() || !contactEmail.includes("@")) next.contactEmail = labels.contactMissing;
1060
+ if (!contactPhone.trim()) next.contactPhone = labels.contactPhoneMissing;
1061
+ setFormErrors(next);
1062
+ return Object.keys(next).length === 0;
1063
+ }
1064
+ function saveGuestContact() {
1065
+ if (!validateGuestContact()) {
1066
+ setGuestConfirmed(false);
1067
+ return;
1068
+ }
1069
+ setGuestConfirmed(true);
1070
+ }
1071
+ function saveAuthPhone() {
1072
+ if (!contactPhone.trim()) {
1073
+ setFormErrors({ contactPhone: labels.contactPhoneMissing });
1074
+ return;
1075
+ }
1076
+ setFormErrors({});
1077
+ setAuthPhoneComplete(true);
1078
+ }
1079
+ function retrySession() {
1080
+ setSessionRetrying(true);
1081
+ window.setTimeout(() => setSessionRetrying(false), 800);
1082
+ }
1083
+ function toggleTiers(lineId) {
1084
+ setOpenTiers(
1085
+ (current) => current.includes(lineId) ? current.filter((value) => value !== lineId) : [...current, lineId]
1086
+ );
1087
+ }
1088
+ const subtotal = lines.reduce((sum, line) => sum + lineNetTotal(line), 0);
1089
+ const promotionsTotal = promotions.reduce((sum, promotion) => sum + promotion.amountCents, 0);
1090
+ const tax = Math.max(
1091
+ 0,
1092
+ Math.round((subtotal - promotionsTotal) * scenario.taxRateBps / 1e4)
1093
+ );
1094
+ const pickupCount = activeGroups.filter((group) => stateFor(group.id).mode === "pickup").length;
1095
+ const unitCount = lines.reduce((sum, line) => sum + line.quantity, 0);
1096
+ const hasUnavailable = lines.some((line) => line.stock < 1);
1097
+ let shippingFees = 0;
1098
+ let pickupFees = 0;
1099
+ activeGroups.forEach((group) => {
1100
+ const state = stateFor(group.id);
1101
+ if (!readiness(group, state)) return;
1102
+ if (state.mode === "pickup") pickupFees += feeFor(group, state);
1103
+ else shippingFees += feeFor(group, state);
1104
+ });
1105
+ const pendingGroups = activeGroups.filter((group) => !readiness(group, stateFor(group.id)));
1106
+ const pending = pendingGroups.length > 0;
1107
+ const total = subtotal - promotionsTotal + tax + shippingFees + pickupFees + scenario.extraCents;
1108
+ const paymentMethods = scenario.paymentIds.map((id) => data.paymentMethods.find((method) => method.id === id)).filter((method) => Boolean(method));
1109
+ const anyPickup = pickupCount > 0;
1110
+ const usable = (method) => method.enabled && !(method.kind === "cash" && anyPickup);
1111
+ const firstUsable = paymentMethods.find(usable);
1112
+ const selectedMethod = paymentMethods.find((method) => method.id === paymentId);
1113
+ const activePaymentId = selectedMethod && usable(selectedMethod) ? selectedMethod.id : firstUsable?.id ?? "";
1114
+ const activeMethod = paymentMethods.find((method) => method.id === activePaymentId) ?? null;
1115
+ const paymentNotice = selectedMethod && !usable(selectedMethod) ? selectedMethod.kind === "cash" ? labels.paymentCashPickup : selectedMethod.reason : "";
1116
+ const paymentOptions = paymentMethods.map((method) => {
1117
+ const enabled = usable(method);
1118
+ return {
1119
+ id: method.id,
1120
+ title: method.label,
1121
+ detail: method.detail,
1122
+ meta: method.kind === "cash" && !enabled ? labels.paymentUnavailable : "",
1123
+ disabled: !enabled,
1124
+ reason: enabled ? "" : method.kind === "cash" ? labels.paymentCashPickup : method.reason
1125
+ };
1126
+ });
1127
+ const appliedCoupon = promotions.find((promotion) => promotion.kind === "coupon") ?? null;
1128
+ const requiresShipping = activeGroups.some((group) => stateFor(group.id).mode === "shipping");
1129
+ const missingAddress = requiresShipping && !shippingAddress;
1130
+ const sessionBlocked = session === "loading" || session === "error";
1131
+ const canConfirm = lines.length > 0 && !pending && activePaymentId !== "" && !hasUnavailable && !sessionBlocked;
1132
+ const blockedMessage = lines.length === 0 ? labels.confirmBlockedCart : sessionBlocked ? labels.confirmBlockedSession : hasUnavailable ? labels.confirmBlockedStock : missingAddress ? labels.confirmBlockedAddress : pending ? labels.confirmBlockedRate : activePaymentId === "" ? labels.confirmBlockedPayment : labels.confirmNote;
1133
+ function buildConfirmationOrder(reference) {
1134
+ const orderGroups = activeGroups.map((group) => {
1135
+ const state = stateFor(group.id);
1136
+ const option = optionFor(group, state);
1137
+ const point = pointFor(group, state);
1138
+ const groupLines = orderGroupLines(lines.filter((line) => line.groupId === group.id));
1139
+ return {
1140
+ id: group.id,
1141
+ title: group.title,
1142
+ modeLabel: state.mode === "pickup" ? labels.modePickup : labels.modeShipping,
1143
+ methodLabel: state.mode === "pickup" ? point?.label ?? "" : option?.label ?? "",
1144
+ detailLabel: state.mode === "pickup" ? point ? pickupDetail(point) : "" : option?.eta ?? "",
1145
+ feeCents: readiness(group, state) ? feeFor(group, state) : 0,
1146
+ lines: groupLines.map((line) => ({ ...line }))
1147
+ };
1148
+ });
1149
+ const billingAddress = billingSame ? shippingAddress : addressBook.find((address) => address.id === billingId) ?? null;
1150
+ return {
1151
+ reference,
1152
+ totalCents: total,
1153
+ subtotalCents: subtotal,
1154
+ unitCount,
1155
+ promotions: promotions.map((promotion) => ({ ...promotion })),
1156
+ taxCents: tax,
1157
+ taxRateBps: scenario.taxRateBps,
1158
+ shippingCents: shippingFees,
1159
+ pickupCents: pickupFees,
1160
+ extraCents: scenario.extraCents,
1161
+ extraLabel: scenario.extraLabel,
1162
+ groups: orderGroups,
1163
+ address: requiresShipping && shippingAddress ? { ...shippingAddress } : null,
1164
+ paymentLabel: activeMethod?.label ?? "",
1165
+ paymentDetail: activeMethod?.detail ?? "",
1166
+ billingLabel: billingAddress ? billingSame && shippingAddress ? labels.confirmationBillingSame : billingAddress.label : "",
1167
+ billingDetail: billingAddress ? addressSummary(billingAddress) : "",
1168
+ customerName: contactName,
1169
+ customerEmail: contactEmail
1170
+ };
1171
+ }
1172
+ function completeOrder() {
1173
+ if (creatingOrder || orderTimer.current) return;
1174
+ const reference = data.meta.confirmationCode;
1175
+ const order = buildConfirmationOrder(reference);
1176
+ setPaymentError(false);
1177
+ setCreatingOrder(true);
1178
+ if (orderTimer.current) window.clearTimeout(orderTimer.current);
1179
+ orderTimer.current = window.setTimeout(() => {
1180
+ orderTimer.current = null;
1181
+ setCreatingOrder(false);
1182
+ setConfirmation(order);
1183
+ onConfirmOrder?.({
1184
+ reference,
1185
+ totalCents: order.totalCents,
1186
+ paymentId: activePaymentId,
1187
+ groupIds: order.groups.map((group) => group.id)
1188
+ });
1189
+ }, 700);
1190
+ }
1191
+ function handleConfirm() {
1192
+ if (!canConfirm || creatingOrder) return;
1193
+ if (session === "guest" && !validateGuestContact()) {
1194
+ setGuestConfirmed(false);
1195
+ return;
1196
+ }
1197
+ if (session === "authenticated" && !contactPhone.trim()) {
1198
+ setFormErrors({ contactPhone: labels.contactPhoneMissing });
1199
+ return;
1200
+ }
1201
+ if (sessionBlocked) return;
1202
+ setFormErrors({});
1203
+ if (session === "guest") setGuestConfirmed(true);
1204
+ if (scenario.simulateFailure && !paymentRetried) {
1205
+ setPaymentError(true);
1206
+ return;
1207
+ }
1208
+ completeOrder();
1209
+ }
1210
+ function retryPayment() {
1211
+ setPaymentRetried(true);
1212
+ setPaymentError(false);
1213
+ completeOrder();
1214
+ }
1215
+ function applyCoupon() {
1216
+ const code = couponCode.trim().toUpperCase();
1217
+ if (!code) return;
1218
+ const coupon = data.coupons.find((item) => item.code === code);
1219
+ if (!coupon) {
1220
+ setCouponNotice({ status: "invalid", message: labels.promoInvalid });
1221
+ return;
1222
+ }
1223
+ if (coupon.outcome !== "valid") {
1224
+ setCouponNotice({ status: coupon.outcome, message: coupon.message });
1225
+ return;
1226
+ }
1227
+ setPromotions((current) => [
1228
+ ...current.filter((promotion) => promotion.kind !== "coupon"),
1229
+ {
1230
+ id: `coupon-${coupon.code.toLowerCase()}`,
1231
+ label: coupon.label,
1232
+ kind: "coupon",
1233
+ amountCents: coupon.amountCents,
1234
+ code: coupon.code
1235
+ }
1236
+ ]);
1237
+ setCouponCode("");
1238
+ setCouponNotice({ status: "applied", message: fill(labels.promoApplied, { code: coupon.code }) });
1239
+ }
1240
+ function removeCoupon() {
1241
+ setPromotions((current) => current.filter((promotion) => promotion.kind !== "coupon"));
1242
+ setCouponNotice(null);
1243
+ }
1244
+ if (scenario.loading) {
1245
+ return /* @__PURE__ */ jsxs("div", { "aria-busy": "true", children: [
1246
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: data.meta.loadingLabel }),
1247
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.sessionLoading }),
1248
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-3", children: [
1249
+ /* @__PURE__ */ jsx("div", { className: "h-24 rounded-lg bg-muted" }),
1250
+ /* @__PURE__ */ jsx("div", { className: "h-24 rounded-lg bg-muted" }),
1251
+ /* @__PURE__ */ jsx("div", { className: "h-24 rounded-lg bg-muted" })
1252
+ ] })
1253
+ ] });
1254
+ }
1255
+ if (creatingOrder) {
1256
+ return /* @__PURE__ */ jsxs(
1257
+ "section",
1258
+ {
1259
+ "aria-busy": "true",
1260
+ "aria-labelledby": `${uid}-creating`,
1261
+ "aria-live": "polite",
1262
+ role: "status",
1263
+ className: "mx-auto flex w-full max-w-md flex-col items-center rounded-xl border border-border bg-card p-8 text-center",
1264
+ children: [
1265
+ /* @__PURE__ */ jsx(
1266
+ "span",
1267
+ {
1268
+ "aria-hidden": "true",
1269
+ className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted",
1270
+ children: /* @__PURE__ */ jsx("span", { className: "h-5 w-5 animate-spin rounded-full border-2 border-muted-foreground border-t-transparent" })
1271
+ }
1272
+ ),
1273
+ /* @__PURE__ */ jsx("h2", { id: `${uid}-creating`, className: "mt-4 text-xl font-semibold tracking-tight", children: labels.confirmationCreating }),
1274
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: labels.confirmationCreatingNote }),
1275
+ /* @__PURE__ */ jsx("dl", { className: "mt-4 w-full rounded-lg bg-muted/40 px-3 py-2", children: /* @__PURE__ */ jsx(SummaryRow, { label: labels.total, value: money(total, data), emphasis: true }) })
1276
+ ]
1277
+ }
1278
+ );
1279
+ }
1280
+ if (confirmation) {
1281
+ const itemsLabel = countLabel(
1282
+ confirmation.unitCount,
1283
+ labels.summaryItemOne,
1284
+ fill(labels.summaryItems, { count: confirmation.unitCount })
1285
+ );
1286
+ const deliveriesLabel = countLabel(
1287
+ confirmation.groups.length,
1288
+ labels.deliveriesOne,
1289
+ fill(labels.deliveriesMany, { count: confirmation.groups.length })
1290
+ );
1291
+ const orderSteps = [
1292
+ {
1293
+ title: labels.confirmationNextOneTitle,
1294
+ body: labels.confirmationNextOneBody
1295
+ },
1296
+ {
1297
+ title: labels.confirmationNextTwoTitle,
1298
+ body: labels.confirmationNextTwoBody
1299
+ },
1300
+ {
1301
+ title: labels.confirmationNextThreeTitle,
1302
+ body: labels.confirmationNextThreeBody
1303
+ }
1304
+ ];
1305
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto flex w-full max-w-2xl flex-col gap-4", children: [
1306
+ /* @__PURE__ */ jsxs(
1307
+ "section",
1308
+ {
1309
+ "aria-labelledby": `${uid}-confirmed`,
1310
+ className: "rounded-xl border border-success/40 bg-card p-5 sm:p-6",
1311
+ children: [
1312
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
1313
+ /* @__PURE__ */ jsx(
1314
+ "span",
1315
+ {
1316
+ "aria-hidden": "true",
1317
+ className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-success/10 text-base font-semibold text-success",
1318
+ children: labels.checkSymbol
1319
+ }
1320
+ ),
1321
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1322
+ /* @__PURE__ */ jsx(Badge, { variant: VARIANT_SECONDARY, children: data.meta.confirmationBadge }),
1323
+ /* @__PURE__ */ jsx(
1324
+ "h2",
1325
+ {
1326
+ id: `${uid}-confirmed`,
1327
+ className: "mt-2 text-xl font-semibold tracking-tight",
1328
+ children: data.meta.confirmationTitle
1329
+ }
1330
+ ),
1331
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: fill(data.meta.confirmationBody, { name: confirmation.customerName }) }),
1332
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: fill(labels.confirmationEmailNote, { email: confirmation.customerEmail }) })
1333
+ ] })
1334
+ ] }),
1335
+ /* @__PURE__ */ jsxs("dl", { className: "mt-4 grid gap-2 rounded-lg bg-muted/40 px-3 py-2 sm:grid-cols-2", children: [
1336
+ /* @__PURE__ */ jsxs("div", { children: [
1337
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: data.meta.confirmationReference }),
1338
+ /* @__PURE__ */ jsx("dd", { className: "text-sm font-medium tabular-nums", children: confirmation.reference })
1339
+ ] }),
1340
+ /* @__PURE__ */ jsxs("div", { className: "sm:text-right", children: [
1341
+ /* @__PURE__ */ jsx("dt", { className: "text-xs text-muted-foreground", children: data.meta.confirmationTotal }),
1342
+ /* @__PURE__ */ jsx("dd", { className: "text-sm font-semibold tabular-nums", children: money(confirmation.totalCents, data) })
1343
+ ] })
1344
+ ] }),
1345
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: labels.confirmationReferenceNote })
1346
+ ]
1347
+ }
1348
+ ),
1349
+ /* @__PURE__ */ jsxs(
1350
+ "section",
1351
+ {
1352
+ "aria-labelledby": `${uid}-next`,
1353
+ className: "rounded-xl border border-border bg-card p-5",
1354
+ children: [
1355
+ /* @__PURE__ */ jsx("h3", { id: `${uid}-next`, className: "text-sm font-semibold", children: labels.confirmationNextTitle }),
1356
+ /* @__PURE__ */ jsx("ol", { className: "mt-3 space-y-3", children: orderSteps.map((step, index) => /* @__PURE__ */ jsxs("li", { className: "flex gap-3", children: [
1357
+ /* @__PURE__ */ jsx(
1358
+ "span",
1359
+ {
1360
+ "aria-hidden": "true",
1361
+ className: index === 0 ? "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-success/10 text-xs font-semibold text-success" : "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground",
1362
+ children: index + 1
1363
+ }
1364
+ ),
1365
+ /* @__PURE__ */ jsxs("div", { children: [
1366
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: step.title }),
1367
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: step.body })
1368
+ ] })
1369
+ ] }, step.title)) })
1370
+ ]
1371
+ }
1372
+ ),
1373
+ /* @__PURE__ */ jsxs(
1374
+ "section",
1375
+ {
1376
+ "aria-labelledby": `${uid}-order`,
1377
+ className: "rounded-xl border border-border bg-card",
1378
+ children: [
1379
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2 border-b border-border px-4 py-3", children: [
1380
+ /* @__PURE__ */ jsx("h3", { id: `${uid}-order`, className: "text-sm font-semibold", children: labels.confirmationOrderTitle }),
1381
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: fill(labels.purchaseBreakdown, {
1382
+ items: itemsLabel,
1383
+ deliveries: deliveriesLabel
1384
+ }) })
1385
+ ] }),
1386
+ /* @__PURE__ */ jsx("div", { className: "divide-y divide-border", children: confirmation.groups.map((group) => {
1387
+ const methodLine = [group.modeLabel, group.methodLabel, group.detailLabel].filter(Boolean).join(" \xB7 ");
1388
+ return /* @__PURE__ */ jsxs("div", { className: "px-4 py-3", children: [
1389
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [
1390
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1391
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: group.title }),
1392
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: methodLine })
1393
+ ] }),
1394
+ /* @__PURE__ */ jsx("p", { className: "text-xs tabular-nums text-muted-foreground", children: group.feeCents === 0 ? labels.groupFree : money(group.feeCents, data) })
1395
+ ] }),
1396
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-2", children: group.lines.map((line) => {
1397
+ const unitPrice = unitPriceFor(line, line.quantity);
1398
+ return /* @__PURE__ */ jsx(
1399
+ "li",
1400
+ {
1401
+ className: line.isAssociate ? "border-l border-border pl-3" : "",
1402
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5", children: [
1403
+ /* @__PURE__ */ jsx(
1404
+ "img",
1405
+ {
1406
+ src: line.image ?? PRODUCT_IMAGE_FALLBACK,
1407
+ alt: line.title,
1408
+ width: 40,
1409
+ height: 40,
1410
+ className: "h-10 w-10 shrink-0 rounded-md border border-border object-cover"
1411
+ }
1412
+ ),
1413
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
1414
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
1415
+ line.title,
1416
+ line.isAssociate ? /* @__PURE__ */ jsx("span", { className: "ml-1.5 text-xs font-normal text-muted-foreground", children: labels.confirmationAssociateTag }) : null
1417
+ ] }),
1418
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: line.variant }),
1419
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: fill(labels.confirmationQuantity, {
1420
+ count: line.quantity,
1421
+ price: money(unitPrice, data)
1422
+ }) }),
1423
+ (line.lineDiscountCents ?? 0) > 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-success", children: fill(labels.lineDiscount, {
1424
+ label: line.lineDiscountLabel ?? "",
1425
+ amount: money(line.lineDiscountCents ?? 0, data)
1426
+ }) }) : null
1427
+ ] }),
1428
+ /* @__PURE__ */ jsx("p", { className: "shrink-0 text-sm font-medium tabular-nums", children: money(lineNetTotal(line), data) })
1429
+ ] })
1430
+ },
1431
+ line.lineId
1432
+ );
1433
+ }) })
1434
+ ] }, group.id);
1435
+ }) })
1436
+ ]
1437
+ }
1438
+ ),
1439
+ /* @__PURE__ */ jsxs(
1440
+ "section",
1441
+ {
1442
+ "aria-labelledby": `${uid}-details`,
1443
+ className: "rounded-xl border border-border bg-card p-4",
1444
+ children: [
1445
+ /* @__PURE__ */ jsx("h3", { id: `${uid}-details`, className: "text-sm font-semibold", children: labels.confirmationDetailsTitle }),
1446
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 divide-y divide-border", children: [
1447
+ confirmation.address ? /* @__PURE__ */ jsxs("div", { className: "py-3 first:pt-0 last:pb-0", children: [
1448
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: labels.addressDelivery }),
1449
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm font-medium", children: confirmation.address.label }),
1450
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: addressSummary(confirmation.address) }),
1451
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: `${confirmation.address.recipient} \xB7 ${confirmation.address.phone}` })
1452
+ ] }) : null,
1453
+ /* @__PURE__ */ jsxs("div", { className: "py-3 first:pt-0 last:pb-0", children: [
1454
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: labels.paymentTitle }),
1455
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm font-medium", children: confirmation.paymentLabel }),
1456
+ confirmation.paymentDetail ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: confirmation.paymentDetail }) : null
1457
+ ] }),
1458
+ confirmation.billingLabel ? /* @__PURE__ */ jsxs("div", { className: "py-3 first:pt-0 last:pb-0", children: [
1459
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: labels.billingTitle }),
1460
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm font-medium", children: confirmation.billingLabel }),
1461
+ confirmation.billingDetail ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: confirmation.billingDetail }) : null
1462
+ ] }) : null
1463
+ ] })
1464
+ ]
1465
+ }
1466
+ ),
1467
+ /* @__PURE__ */ jsxs(
1468
+ "section",
1469
+ {
1470
+ "aria-labelledby": `${uid}-totals`,
1471
+ className: "rounded-xl border border-border bg-card p-4",
1472
+ children: [
1473
+ /* @__PURE__ */ jsx("h3", { id: `${uid}-totals`, className: "text-sm font-semibold", children: labels.summaryTitle }),
1474
+ /* @__PURE__ */ jsxs("dl", { className: "mt-3 space-y-2", children: [
1475
+ /* @__PURE__ */ jsx(
1476
+ SummaryRow,
1477
+ {
1478
+ label: labels.subtotal,
1479
+ value: money(confirmation.subtotalCents, data),
1480
+ emphasis: false
1481
+ }
1482
+ ),
1483
+ confirmation.promotions.map((promotion) => /* @__PURE__ */ jsx(
1484
+ SummaryRow,
1485
+ {
1486
+ label: promotion.label,
1487
+ value: `- ${money(promotion.amountCents, data)}`,
1488
+ emphasis: false
1489
+ },
1490
+ promotion.id
1491
+ )),
1492
+ /* @__PURE__ */ jsx(
1493
+ SummaryRow,
1494
+ {
1495
+ label: fill(labels.tax, { rate: String(confirmation.taxRateBps / 100) }),
1496
+ value: money(confirmation.taxCents, data),
1497
+ emphasis: false
1498
+ }
1499
+ ),
1500
+ confirmation.shippingCents > 0 ? /* @__PURE__ */ jsx(
1501
+ SummaryRow,
1502
+ {
1503
+ label: labels.shippingCost,
1504
+ value: money(confirmation.shippingCents, data),
1505
+ emphasis: false
1506
+ }
1507
+ ) : null,
1508
+ confirmation.pickupCents > 0 ? /* @__PURE__ */ jsx(
1509
+ SummaryRow,
1510
+ {
1511
+ label: labels.pickupCost,
1512
+ value: money(confirmation.pickupCents, data),
1513
+ emphasis: false
1514
+ }
1515
+ ) : null,
1516
+ confirmation.extraCents > 0 ? /* @__PURE__ */ jsx(
1517
+ SummaryRow,
1518
+ {
1519
+ label: confirmation.extraLabel || labels.extraCost,
1520
+ value: money(confirmation.extraCents, data),
1521
+ emphasis: false
1522
+ }
1523
+ ) : null
1524
+ ] }),
1525
+ /* @__PURE__ */ jsx("dl", { className: "mt-2 border-t border-border pt-2", children: /* @__PURE__ */ jsx(
1526
+ SummaryRow,
1527
+ {
1528
+ label: labels.total,
1529
+ value: money(confirmation.totalCents, data),
1530
+ emphasis: true
1531
+ }
1532
+ ) }),
1533
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: labels.totalDemoNote })
1534
+ ]
1535
+ }
1536
+ ),
1537
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
1538
+ Button,
1539
+ {
1540
+ type: "button",
1541
+ size: "lg",
1542
+ className: "w-full sm:w-auto",
1543
+ onClick: onContinueShopping,
1544
+ children: data.meta.confirmationCta
1545
+ }
1546
+ ) })
1547
+ ] });
1548
+ }
1549
+ if (lines.length === 0) {
1550
+ return /* @__PURE__ */ jsxs(
1551
+ "section",
1552
+ {
1553
+ "aria-labelledby": `${uid}-empty`,
1554
+ className: "rounded-xl border border-border bg-card p-8 text-center",
1555
+ children: [
1556
+ /* @__PURE__ */ jsx("h2", { id: `${uid}-empty`, className: "text-xl font-semibold tracking-tight", children: data.meta.emptyTitle }),
1557
+ /* @__PURE__ */ jsx("p", { className: "mx-auto mt-2 max-w-sm text-sm text-muted-foreground", children: data.meta.emptyBody }),
1558
+ /* @__PURE__ */ jsx("div", { className: "mt-5", children: /* @__PURE__ */ jsx(Button, { type: "button", onClick: onContinueShopping, children: data.meta.emptyCta }) })
1559
+ ]
1560
+ }
1561
+ );
1562
+ }
1563
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5", children: [
1564
+ /* @__PURE__ */ jsxs("div", { children: [
1565
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold tracking-tight", children: data.meta.title }),
1566
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: fill(labels.purchaseBreakdown, {
1567
+ items: countLabel(
1568
+ unitCount,
1569
+ labels.summaryItemOne,
1570
+ fill(labels.summaryItems, { count: unitCount })
1571
+ ),
1572
+ deliveries: countLabel(
1573
+ activeGroups.length,
1574
+ labels.deliveriesOne,
1575
+ fill(labels.deliveriesMany, { count: activeGroups.length })
1576
+ )
1577
+ }) })
1578
+ ] }),
1579
+ /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-10 flex items-center justify-between gap-3 rounded-xl border border-border bg-card/95 p-3 backdrop-blur md:hidden", children: [
1580
+ /* @__PURE__ */ jsxs("div", { children: [
1581
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: pending ? labels.totalPending : labels.total }),
1582
+ /* @__PURE__ */ jsx("p", { className: "text-base font-semibold tabular-nums", children: pending ? labels.overviewPending : money(total, data) })
1583
+ ] }),
1584
+ /* @__PURE__ */ jsx(Button, { type: "button", disabled: !canConfirm || creatingOrder, onClick: handleConfirm, children: creatingOrder ? labels.confirmationCreating : labels.confirm })
1585
+ ] }),
1586
+ /* @__PURE__ */ jsxs("div", { className: "md:grid md:grid-cols-5 md:items-start md:gap-6", children: [
1587
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5 md:col-span-3", children: [
1588
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border bg-card", children: [
1589
+ /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(
1590
+ BuyerSection,
1591
+ {
1592
+ uid,
1593
+ session,
1594
+ name: contactName,
1595
+ email: contactEmail,
1596
+ phone: contactPhone,
1597
+ guestConfirmed,
1598
+ authPhoneComplete,
1599
+ errors: formErrors,
1600
+ onFieldChange: changeBuyerField,
1601
+ onGuestSave: saveGuestContact,
1602
+ onGuestEdit: () => {
1603
+ setGuestConfirmed(false);
1604
+ setFormErrors({});
1605
+ },
1606
+ onAuthPhoneSave: saveAuthPhone,
1607
+ onRetrySession: retrySession,
1608
+ onStartRegistration
1609
+ }
1610
+ ) }),
1611
+ /* @__PURE__ */ jsx("div", { className: "border-t border-border px-4 py-3", children: /* @__PURE__ */ jsxs("section", { "aria-labelledby": `${uid}-shipping-address`, children: [
1612
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
1613
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1614
+ /* @__PURE__ */ jsx(
1615
+ "h3",
1616
+ {
1617
+ id: `${uid}-shipping-address`,
1618
+ className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground",
1619
+ children: labels.addressTitle
1620
+ }
1621
+ ),
1622
+ !requiresShipping ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.pickupOnlyAddress }) : shippingAddress ? /* @__PURE__ */ jsxs(Fragment, { children: [
1623
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm font-medium", children: addressHeadline(shippingAddress) }),
1624
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: addressSummary(shippingAddress) }),
1625
+ shippingAddress.recipient !== contactName || shippingAddress.phone !== contactPhone ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: [shippingAddress.recipient, shippingAddress.phone].filter(Boolean).join(" \xB7 ") }) : null
1626
+ ] }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-destructive", children: labels.groupNeedsAddress })
1627
+ ] }),
1628
+ requiresShipping ? /* @__PURE__ */ jsx(
1629
+ Button,
1630
+ {
1631
+ type: "button",
1632
+ size: "sm",
1633
+ variant: VARIANT_OUTLINE,
1634
+ "aria-expanded": addressOpen,
1635
+ "aria-controls": `${uid}-address-panel`,
1636
+ onClick: () => addressOpen ? setAddressOpen(false) : openAddressEditor(),
1637
+ children: shippingAddress ? labels.changeAction : labels.groupAddAddress
1638
+ }
1639
+ ) : null
1640
+ ] }),
1641
+ addressOpen && requiresShipping ? /* @__PURE__ */ jsx(
1642
+ "div",
1643
+ {
1644
+ id: `${uid}-address-panel`,
1645
+ className: "mt-3 rounded-lg border border-border bg-muted/30 p-3",
1646
+ children: addressMode === "new" ? /* @__PURE__ */ jsx(
1647
+ AddressForm,
1648
+ {
1649
+ labels,
1650
+ countries: data.countries,
1651
+ authenticated: scenario.customer.sessionState === "authenticated",
1652
+ onSave: saveNewAddress,
1653
+ onCancel: () => addressBook.length === 0 ? setAddressOpen(false) : setAddressMode("list")
1654
+ }
1655
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1656
+ /* @__PURE__ */ jsx(
1657
+ AddressChoices,
1658
+ {
1659
+ legend: labels.addressSelectTitle,
1660
+ name: `${uid}-shipping-address`,
1661
+ addresses: addressBook,
1662
+ selectedId: shippingAddressId,
1663
+ onSelect: selectShippingAddress,
1664
+ labels
1665
+ }
1666
+ ),
1667
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
1668
+ /* @__PURE__ */ jsx(
1669
+ Button,
1670
+ {
1671
+ type: "button",
1672
+ size: "sm",
1673
+ variant: VARIANT_OUTLINE,
1674
+ onClick: () => setAddressMode("new"),
1675
+ children: labels.addressNew
1676
+ }
1677
+ ),
1678
+ /* @__PURE__ */ jsx(
1679
+ Button,
1680
+ {
1681
+ type: "button",
1682
+ size: "sm",
1683
+ variant: VARIANT_GHOST,
1684
+ onClick: () => setAddressOpen(false),
1685
+ children: labels.addressCancel
1686
+ }
1687
+ )
1688
+ ] })
1689
+ ] })
1690
+ }
1691
+ ) : null
1692
+ ] }) })
1693
+ ] }),
1694
+ /* @__PURE__ */ jsxs("section", { "aria-labelledby": `${uid}-groups`, children: [
1695
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [
1696
+ /* @__PURE__ */ jsx("h3", { id: `${uid}-groups`, className: "text-sm font-semibold", children: labels.groupsTitle }),
1697
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: countLabel(
1698
+ activeGroups.length,
1699
+ labels.deliveriesOne,
1700
+ fill(labels.deliveriesMany, { count: activeGroups.length })
1701
+ ) })
1702
+ ] }),
1703
+ /* @__PURE__ */ jsx("p", { "aria-live": "polite", className: "mt-1 text-xs text-destructive", children: stockMessage }),
1704
+ associateNotice && associateNotice.mainLineId === "" ? /* @__PURE__ */ jsx(
1705
+ "p",
1706
+ {
1707
+ "aria-live": "polite",
1708
+ className: associateNotice.error ? "mt-1 text-xs text-destructive" : "mt-1 text-xs text-muted-foreground",
1709
+ children: associateNotice.text
1710
+ }
1711
+ ) : null,
1712
+ /* @__PURE__ */ jsx("div", { className: "mt-3 space-y-4", children: activeGroups.map((group, index) => {
1713
+ const state = stateFor(group.id);
1714
+ const groupLines = orderGroupLines(
1715
+ lines.filter((line) => line.groupId === group.id)
1716
+ );
1717
+ const point = pointFor(group, state);
1718
+ const ready = readiness(group, state);
1719
+ const fee = ready ? feeFor(group, state) : null;
1720
+ const baseFee = baseFeeFor(group, state);
1721
+ const feeDiscount = fee !== null ? Math.max(0, baseFee - fee) : 0;
1722
+ const option = optionFor(group, state);
1723
+ const heading = fill(state.mode === "pickup" ? labels.groupPickup : labels.groupDelivery, {
1724
+ n: index + 1
1725
+ });
1726
+ const panelId = `${uid}-panel-${group.id}`;
1727
+ const editing = openPanel === group.id;
1728
+ const methodLine = state.mode === "pickup" ? [labels.modePickup, ready ? point?.label : ""].filter(Boolean).join(" \xB7 ") : [labels.modeShipping, ready ? option?.label : "", ready ? option?.eta : ""].filter(Boolean).join(" \xB7 ");
1729
+ const pickupDetailLine = state.mode === "pickup" && point ? [point.address, point.hours].filter(Boolean).join(" \xB7 ") : "";
1730
+ const needsAddress = state.mode === "shipping" && !shippingAddress;
1731
+ const ratePending = state.rateState === "pending";
1732
+ const rateError = state.rateState === "error";
1733
+ const noCoverage = state.rateState === "none";
1734
+ const shippingAvailable = group.modes.includes("shipping") && group.shippingOptions.length > 0 && Boolean(shippingAddress) && coverageFor(group, shippingAddressId) === "ok";
1735
+ const pickupAvailable = group.modes.includes("pickup") && group.pickupPoints.some((item) => item.available !== false);
1736
+ const bothModes = shippingAvailable && pickupAvailable;
1737
+ const currentModeAvailable = state.mode === "shipping" ? shippingAvailable : pickupAvailable;
1738
+ const currentChoiceCount = state.mode === "shipping" ? group.shippingOptions.length : group.pickupPoints.length;
1739
+ const canChange = bothModes || (currentModeAvailable ? currentChoiceCount > 1 : pickupAvailable || shippingAvailable);
1740
+ const panelMode = currentModeAvailable ? state.mode : pickupAvailable ? "pickup" : "shipping";
1741
+ const shippingChoices = group.shippingOptions.map((item) => {
1742
+ const discount = item.discountCents ?? 0;
1743
+ const payable = Math.max(0, item.feeCents - discount);
1744
+ return {
1745
+ id: item.id,
1746
+ title: item.label,
1747
+ detail: item.eta,
1748
+ meta: payable === 0 ? labels.groupFree : money(payable, data),
1749
+ previousMeta: discount > 0 ? money(item.feeCents, data) : "",
1750
+ disabled: false,
1751
+ reason: ""
1752
+ };
1753
+ });
1754
+ const pickupChoices = group.pickupPoints.map((item) => {
1755
+ const discount = item.discountCents ?? 0;
1756
+ const payable = Math.max(0, item.feeCents - discount);
1757
+ return {
1758
+ id: item.id,
1759
+ title: item.label,
1760
+ detail: pickupDetail(item),
1761
+ meta: payable === 0 ? labels.groupFree : money(payable, data),
1762
+ previousMeta: discount > 0 ? money(item.feeCents, data) : "",
1763
+ disabled: item.available === false,
1764
+ reason: item.available === false ? item.unavailableReason || "" : ""
1765
+ };
1766
+ });
1767
+ const modeChoices = [
1768
+ {
1769
+ id: "shipping",
1770
+ title: labels.modeShipping,
1771
+ detail: "",
1772
+ meta: "",
1773
+ disabled: false,
1774
+ reason: ""
1775
+ },
1776
+ {
1777
+ id: "pickup",
1778
+ title: labels.modePickup,
1779
+ detail: "",
1780
+ meta: "",
1781
+ disabled: false,
1782
+ reason: ""
1783
+ }
1784
+ ];
1785
+ return /* @__PURE__ */ jsxs(
1786
+ "section",
1787
+ {
1788
+ "aria-labelledby": `${uid}-group-${group.id}`,
1789
+ className: "overflow-hidden rounded-xl border border-border bg-card",
1790
+ children: [
1791
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 px-4 py-3", children: [
1792
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1793
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1794
+ /* @__PURE__ */ jsx("h4", { id: `${uid}-group-${group.id}`, className: "text-sm font-semibold", children: heading }),
1795
+ group.special ? /* @__PURE__ */ jsx(Badge, { variant: VARIANT_SECONDARY, children: labels.groupSpecial }) : null
1796
+ ] }),
1797
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: methodLine }),
1798
+ pickupDetailLine ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: pickupDetailLine }) : null
1799
+ ] }),
1800
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-end gap-1.5", children: [
1801
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold tabular-nums", children: fee === null ? labels.overviewPending : feeDiscount > 0 ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
1802
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-normal text-muted-foreground line-through", children: money(baseFee, data) }),
1803
+ /* @__PURE__ */ jsx("span", { children: fee === 0 ? labels.groupFree : money(fee, data) })
1804
+ ] }) : fee === 0 ? labels.groupFree : money(fee, data) }),
1805
+ canChange ? /* @__PURE__ */ jsx(
1806
+ Button,
1807
+ {
1808
+ type: "button",
1809
+ size: "xs",
1810
+ variant: VARIANT_OUTLINE,
1811
+ "aria-expanded": editing,
1812
+ "aria-controls": panelId,
1813
+ onClick: () => setOpenPanel(editing ? "" : group.id),
1814
+ children: labels.changeAction
1815
+ }
1816
+ ) : null
1817
+ ] })
1818
+ ] }),
1819
+ editing && canChange ? /* @__PURE__ */ jsxs("div", { id: panelId, className: "border-t border-border bg-muted/30 px-4 py-3", children: [
1820
+ bothModes ? /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(
1821
+ OptionGroup,
1822
+ {
1823
+ legend: labels.modeTitle,
1824
+ options: modeChoices,
1825
+ selectedId: state.mode,
1826
+ onSelect: (mode) => selectMode(group, mode),
1827
+ className: "grid gap-1.5 sm:grid-cols-2"
1828
+ }
1829
+ ) }) : null,
1830
+ panelMode === "shipping" ? rateError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: labels.rateError }) : ratePending ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: labels.groupRatePending }) : noCoverage ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: labels.groupNoCoverage }) : /* @__PURE__ */ jsx(
1831
+ OptionGroup,
1832
+ {
1833
+ legend: labels.groupShippingOptions,
1834
+ options: shippingChoices,
1835
+ selectedId: state.shippingOptionId,
1836
+ onSelect: (id) => chooseShippingOption(group, id),
1837
+ className: "grid gap-1.5"
1838
+ }
1839
+ ) : /* @__PURE__ */ jsxs("div", { children: [
1840
+ /* @__PURE__ */ jsx(
1841
+ OptionGroup,
1842
+ {
1843
+ legend: labels.groupPickupPoints,
1844
+ options: pickupChoices,
1845
+ selectedId: state.pickupPointId,
1846
+ onSelect: (id) => choosePickupPoint(group, id),
1847
+ className: "grid gap-1.5"
1848
+ }
1849
+ ),
1850
+ point?.note ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: point.note }) : null
1851
+ ] }),
1852
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
1853
+ Button,
1854
+ {
1855
+ type: "button",
1856
+ size: "xs",
1857
+ variant: VARIANT_GHOST,
1858
+ onClick: () => setOpenPanel(""),
1859
+ children: labels.done
1860
+ }
1861
+ ) })
1862
+ ] }) : null,
1863
+ needsAddress ? /* @__PURE__ */ jsx("p", { className: "px-4 pb-3 text-xs text-muted-foreground", children: labels.groupNoAddress }) : ratePending ? /* @__PURE__ */ jsx("p", { "aria-live": "polite", className: "px-4 pb-3 text-xs text-muted-foreground", children: labels.groupRatePending }) : rateError ? /* @__PURE__ */ jsxs("div", { className: "mx-4 mb-3 rounded-md border border-destructive/40 bg-destructive/10 p-2.5", children: [
1864
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: labels.rateError }),
1865
+ /* @__PURE__ */ jsx(
1866
+ Button,
1867
+ {
1868
+ type: "button",
1869
+ size: "xs",
1870
+ variant: VARIANT_OUTLINE,
1871
+ className: "mt-1.5",
1872
+ onClick: () => recalc(group),
1873
+ children: labels.groupRetryRate
1874
+ }
1875
+ )
1876
+ ] }) : noCoverage ? /* @__PURE__ */ jsxs("div", { className: "mx-4 mb-3 rounded-md border border-warning/40 bg-warning/10 p-2.5", children: [
1877
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: labels.groupNoCoverage }),
1878
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: labels.groupNoCoverageHint }),
1879
+ /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap gap-1.5", children: [
1880
+ group.modes.includes("pickup") ? /* @__PURE__ */ jsx(
1881
+ Button,
1882
+ {
1883
+ type: "button",
1884
+ size: "xs",
1885
+ variant: VARIANT_OUTLINE,
1886
+ onClick: () => selectMode(group, "pickup"),
1887
+ children: labels.groupPickupAvailable
1888
+ }
1889
+ ) : null,
1890
+ /* @__PURE__ */ jsx(
1891
+ Button,
1892
+ {
1893
+ type: "button",
1894
+ size: "xs",
1895
+ variant: VARIANT_GHOST,
1896
+ onClick: openAddressEditor,
1897
+ children: labels.groupChangeAddress
1898
+ }
1899
+ )
1900
+ ] })
1901
+ ] }) : null,
1902
+ group.note ? /* @__PURE__ */ jsx("p", { className: "mx-4 mb-3 rounded-md bg-muted/50 p-2 text-xs text-muted-foreground", children: group.note }) : null,
1903
+ /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border border-t border-border", children: groupLines.map((line) => {
1904
+ const unitPrice = unitPriceFor(line, line.quantity);
1905
+ const tiers = line.tiers ?? [];
1906
+ const currentTier = currentTierFor(line, line.quantity);
1907
+ const nextTier = nextTierFor(line, line.quantity);
1908
+ const tierOpen = openTiers.includes(line.lineId);
1909
+ const nextReachable = nextTier !== null && line.stock >= nextTier.minQuantity && nextTier.unitCents < unitPrice;
1910
+ const detailLine = [
1911
+ line.variant,
1912
+ fill(labels.each, { amount: money(unitPrice, data) })
1913
+ ].join(" \xB7 ");
1914
+ const unavailable = line.stock < 1;
1915
+ const isAssociate = line.isAssociate === true;
1916
+ const mainQty = isAssociate ? mainQuantityFor(line) : line.quantity;
1917
+ const maxQty = isAssociate ? associateMaxFor(line, mainQty) : line.stock;
1918
+ const minQty = isAssociate ? line.minQuantity ?? 1 : 1;
1919
+ const parentLine = line.parentLineId ? lines.find((entry) => entry.lineId === line.parentLineId) : void 0;
1920
+ const parentNote = parentLine && parentLine.groupId !== line.groupId ? fill(labels.associateOf, { name: parentLine.title }) : "";
1921
+ const relatedInGroup = Boolean(
1922
+ isAssociate && parentLine && parentLine.groupId === line.groupId
1923
+ );
1924
+ const associateDiscount = isAssociate ? associateDiscountFor(unitPrice, line.regularCents) : null;
1925
+ const regularTotal = (line.regularCents ?? 0) * line.quantity;
1926
+ const lineNet = lineNetTotal(line);
1927
+ const totalSavingCents = associateDiscount ? Math.max(0, regularTotal - lineNet) : 0;
1928
+ const totalSavingPercent = associateDiscount && regularTotal > 0 ? Math.round(totalSavingCents / regularTotal * 100) : 0;
1929
+ const associateDefs = isAssociate ? [] : associatesFor(line.lineId);
1930
+ const associateLines = isAssociate ? [] : lines.filter((entry) => entry.parentLineId === line.lineId);
1931
+ const availableAssociates = associateDefs.filter(
1932
+ (def) => !lines.some((entry) => entry.lineId === def.lineId)
1933
+ );
1934
+ const addableAssociates = availableAssociates.filter(
1935
+ (def) => def.eligible !== false && def.stock > 0 && associateDefMax(def, line.quantity) > 0
1936
+ );
1937
+ const rowNotice = associateNotice && associateNotice.mainLineId === line.lineId ? associateNotice : null;
1938
+ const confirmingMain = confirmRemoveMain === line.lineId;
1939
+ const rowRemoving = removingLine === line.lineId || isAssociate && removingLine === line.parentLineId;
1940
+ const atLimit = maxQty > 0 && line.quantity >= maxQty;
1941
+ const canDecrease = !unavailable && (isAssociate ? minQty === 0 || line.quantity > minQty : line.quantity > 1);
1942
+ const canIncrease = !unavailable && maxQty > 0 && line.quantity < maxQty;
1943
+ const addingAny = addingAssociate !== "";
1944
+ return /* @__PURE__ */ jsxs(
1945
+ "li",
1946
+ {
1947
+ "aria-busy": priceUpdating === line.lineId || rowRemoving,
1948
+ className: "px-4 py-3",
1949
+ children: [
1950
+ /* @__PURE__ */ jsxs(
1951
+ "div",
1952
+ {
1953
+ className: relatedInGroup ? "flex flex-col gap-2 border-l border-border pl-3 sm:flex-row sm:items-start sm:gap-3" : "flex flex-col gap-2 sm:flex-row sm:items-start sm:gap-3",
1954
+ children: [
1955
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 gap-3", children: [
1956
+ /* @__PURE__ */ jsx(
1957
+ "img",
1958
+ {
1959
+ src: line.image ?? PRODUCT_IMAGE_FALLBACK,
1960
+ alt: line.title,
1961
+ width: 48,
1962
+ height: 48,
1963
+ className: "h-12 w-12 shrink-0 rounded-md border border-border object-cover"
1964
+ }
1965
+ ),
1966
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
1967
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: line.title }),
1968
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: detailLine }),
1969
+ isAssociate && associateDiscount ? /* @__PURE__ */ jsxs("p", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs", children: [
1970
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground line-through tabular-nums", children: fill(labels.associatePreviousUnit, {
1971
+ amount: money(line.regularCents ?? 0, data)
1972
+ }) }),
1973
+ /* @__PURE__ */ jsx("span", { className: "text-success", children: fill(labels.associateTotalSaving, {
1974
+ amount: money(totalSavingCents, data),
1975
+ percent: totalSavingPercent
1976
+ }) })
1977
+ ] }) : null,
1978
+ parentNote ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: parentNote }) : null,
1979
+ relatedInGroup && parentLine ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: fill(labels.associateOf, { name: parentLine.title }) }) : null,
1980
+ (line.lineDiscountCents ?? 0) > 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-success", children: fill(labels.lineDiscount, {
1981
+ label: line.lineDiscountLabel ?? "",
1982
+ amount: money(line.lineDiscountCents ?? 0, data)
1983
+ }) }) : null,
1984
+ isAssociate ? /* @__PURE__ */ jsx(
1985
+ "p",
1986
+ {
1987
+ className: atLimit ? "text-xs font-medium text-foreground" : "text-xs text-muted-foreground",
1988
+ children: atLimit ? labels.associateLimitReached : fill(labels.associateMax, { count: maxQty })
1989
+ }
1990
+ ) : unavailable ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: labels.outOfStock }) : line.stock <= 3 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: fill(labels.stockLeft, { count: line.stock }) }) : null
1991
+ ] })
1992
+ ] }),
1993
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between gap-3 sm:w-28 sm:flex-col sm:items-end sm:gap-1.5", children: [
1994
+ /* @__PURE__ */ jsxs("div", { className: "sm:text-right", children: [
1995
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: labels.subtotal }),
1996
+ /* @__PURE__ */ jsxs(
1997
+ "p",
1998
+ {
1999
+ className: priceUpdating === line.lineId ? "text-sm font-semibold tabular-nums text-muted-foreground" : "text-sm font-semibold tabular-nums",
2000
+ children: [
2001
+ money(lineNetTotal(line), data),
2002
+ priceUpdating === line.lineId ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: labels.ellipsis }) : null
2003
+ ]
2004
+ }
2005
+ )
2006
+ ] }),
2007
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", children: priceUpdating === line.lineId ? labels.priceUpdating : "" }),
2008
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2009
+ /* @__PURE__ */ jsx(
2010
+ Button,
2011
+ {
2012
+ type: "button",
2013
+ size: "icon",
2014
+ variant: VARIANT_OUTLINE,
2015
+ "aria-label": fill(labels.decrease, { name: line.title }),
2016
+ disabled: !canDecrease || rowRemoving,
2017
+ onClick: () => changeQuantity(line.lineId, line.quantity - 1),
2018
+ children: labels.decreaseSymbol
2019
+ }
2020
+ ),
2021
+ /* @__PURE__ */ jsx("span", { className: "min-w-8 text-center text-sm tabular-nums", children: line.quantity }),
2022
+ /* @__PURE__ */ jsx(
2023
+ Button,
2024
+ {
2025
+ type: "button",
2026
+ size: "icon",
2027
+ variant: VARIANT_OUTLINE,
2028
+ "aria-label": fill(labels.increase, { name: line.title }),
2029
+ disabled: !canIncrease || rowRemoving,
2030
+ onClick: () => changeQuantity(line.lineId, line.quantity + 1),
2031
+ children: labels.increaseSymbol
2032
+ }
2033
+ )
2034
+ ] })
2035
+ ] })
2036
+ ]
2037
+ }
2038
+ ),
2039
+ tiers.length > 1 ? /* @__PURE__ */ jsxs("div", { className: "mt-2 rounded-lg bg-muted/40 px-3 py-2", children: [
2040
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1", children: [
2041
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: labels.tierBadge }),
2042
+ /* @__PURE__ */ jsx(
2043
+ Button,
2044
+ {
2045
+ type: "button",
2046
+ size: "xs",
2047
+ variant: VARIANT_GHOST,
2048
+ className: "px-0",
2049
+ "aria-expanded": tierOpen,
2050
+ "aria-controls": `${uid}-tiers-${line.lineId}`,
2051
+ onClick: () => toggleTiers(line.lineId),
2052
+ children: tierOpen ? labels.tierHide : labels.tierSee
2053
+ }
2054
+ )
2055
+ ] }),
2056
+ nextReachable && nextTier ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: fill(labels.tierNext, {
2057
+ count: nextTier.minQuantity - line.quantity,
2058
+ price: money(nextTier.unitCents, data)
2059
+ }) }) : null,
2060
+ tierOpen ? /* @__PURE__ */ jsx(
2061
+ "div",
2062
+ {
2063
+ id: `${uid}-tiers-${line.lineId}`,
2064
+ className: "mt-2 divide-y divide-border border-t border-border",
2065
+ children: tiers.map((tier) => {
2066
+ const tierIsCurrent = currentTier?.minQuantity === tier.minQuantity;
2067
+ return /* @__PURE__ */ jsxs(
2068
+ "div",
2069
+ {
2070
+ "aria-current": tierIsCurrent ? "true" : void 0,
2071
+ className: tierIsCurrent ? "flex items-center justify-between gap-2 py-1.5 text-xs font-medium" : "flex items-center justify-between gap-2 py-1.5 text-xs text-muted-foreground",
2072
+ children: [
2073
+ /* @__PURE__ */ jsx("span", { children: tierRangeLabel(tier, data) }),
2074
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
2075
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money(tier.unitCents, data) }),
2076
+ tierIsCurrent ? /* @__PURE__ */ jsx("span", { className: "text-xs font-normal text-muted-foreground", children: labels.tierCurrent }) : null
2077
+ ] })
2078
+ ]
2079
+ },
2080
+ tier.minQuantity
2081
+ );
2082
+ })
2083
+ }
2084
+ ) : null
2085
+ ] }) : null,
2086
+ associateDefs.length > 0 && (associateLines.length > 0 || addableAssociates.length > 0) ? /* @__PURE__ */ jsxs("div", { className: "mt-2 rounded-lg bg-muted/40 px-3 py-2", children: [
2087
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1", children: [
2088
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: associateLines.length > 0 ? fill(
2089
+ associateLines.length === 1 ? labels.associatesSummaryOne : labels.associatesSummary,
2090
+ { count: associateLines.length }
2091
+ ) : labels.associatesTitle }),
2092
+ addableAssociates.length > 0 || assocOpen === line.lineId ? /* @__PURE__ */ jsx(
2093
+ Button,
2094
+ {
2095
+ type: "button",
2096
+ size: "xs",
2097
+ variant: VARIANT_GHOST,
2098
+ className: "px-0",
2099
+ "aria-expanded": assocOpen === line.lineId,
2100
+ "aria-controls": `${uid}-associates-${line.lineId}`,
2101
+ onClick: () => setAssocOpen(assocOpen === line.lineId ? "" : line.lineId),
2102
+ children: assocOpen === line.lineId ? labels.associatesHide : labels.associatesAddMore
2103
+ }
2104
+ ) : null
2105
+ ] }),
2106
+ rowNotice ? /* @__PURE__ */ jsx(
2107
+ "p",
2108
+ {
2109
+ "aria-live": "polite",
2110
+ className: rowNotice.error ? "mt-1.5 text-xs text-destructive" : "mt-1.5 text-xs text-muted-foreground",
2111
+ children: rowNotice.text
2112
+ }
2113
+ ) : null,
2114
+ assocOpen === line.lineId ? /* @__PURE__ */ jsx(
2115
+ "div",
2116
+ {
2117
+ id: `${uid}-associates-${line.lineId}`,
2118
+ className: "mt-2 divide-y divide-border border-t border-border",
2119
+ children: availableAssociates.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-2 text-xs text-muted-foreground", children: labels.associateOptionsNone }) : availableAssociates.map((def) => {
2120
+ const defMax = associateDefMax(def, line.quantity);
2121
+ const defDiscount = associateDiscountFor(
2122
+ def.unitCents,
2123
+ def.regularCents
2124
+ );
2125
+ const blocked = def.eligible === false || def.stock < 1 || defMax < 1;
2126
+ const adding = addingAssociate === def.id;
2127
+ const status = def.stock < 1 ? labels.associateNoStock : def.eligible === false ? `${labels.associateNotEligible} \xB7 ${def.reason ?? ""}` : defMax < 1 ? labels.associateLimitReached : "";
2128
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5 py-2", children: [
2129
+ /* @__PURE__ */ jsx(
2130
+ "img",
2131
+ {
2132
+ src: def.image ?? PRODUCT_IMAGE_FALLBACK,
2133
+ alt: def.title,
2134
+ width: 36,
2135
+ height: 36,
2136
+ className: "h-9 w-9 shrink-0 rounded-md border border-border object-cover"
2137
+ }
2138
+ ),
2139
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
2140
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: def.title }),
2141
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: def.variant }),
2142
+ /* @__PURE__ */ jsxs("p", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs", children: [
2143
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money(def.unitCents, data) }),
2144
+ defDiscount ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground line-through tabular-nums", children: money(def.regularCents ?? 0, data) }) : null,
2145
+ defDiscount ? /* @__PURE__ */ jsx("span", { className: "text-success", children: fill(labels.associateSavings, {
2146
+ amount: money(defDiscount.amountCents, data),
2147
+ percent: defDiscount.percent
2148
+ }) }) : null,
2149
+ defMax > 0 ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: fill(labels.associateMax, { count: defMax }) }) : null
2150
+ ] }),
2151
+ status ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: status }) : null
2152
+ ] }),
2153
+ /* @__PURE__ */ jsx(
2154
+ Button,
2155
+ {
2156
+ type: "button",
2157
+ size: "xs",
2158
+ variant: VARIANT_OUTLINE,
2159
+ disabled: blocked || addingAny || rowRemoving,
2160
+ onClick: () => addAssociate(line, def),
2161
+ children: adding ? labels.associateAdding : labels.associateAdd
2162
+ }
2163
+ )
2164
+ ] }, def.id);
2165
+ })
2166
+ }
2167
+ ) : null
2168
+ ] }) : null,
2169
+ confirmingMain ? /* @__PURE__ */ jsxs("div", { className: "mt-2 rounded-md border border-border bg-muted/40 px-3 py-2", children: [
2170
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: fill(labels.removeMainWarning, {
2171
+ name: line.title,
2172
+ count: associateLines.length
2173
+ }) }),
2174
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
2175
+ /* @__PURE__ */ jsx(
2176
+ Button,
2177
+ {
2178
+ type: "button",
2179
+ size: "xs",
2180
+ variant: VARIANT_DESTRUCTIVE,
2181
+ disabled: rowRemoving,
2182
+ onClick: () => confirmRemoveMainLine(line),
2183
+ children: labels.removeMainConfirm
2184
+ }
2185
+ ),
2186
+ /* @__PURE__ */ jsx(
2187
+ Button,
2188
+ {
2189
+ type: "button",
2190
+ size: "xs",
2191
+ variant: VARIANT_GHOST,
2192
+ onClick: () => setConfirmRemoveMain(""),
2193
+ children: labels.associateKeep
2194
+ }
2195
+ )
2196
+ ] })
2197
+ ] }) : null,
2198
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ jsx(
2199
+ Button,
2200
+ {
2201
+ type: "button",
2202
+ size: "xs",
2203
+ variant: VARIANT_GHOST,
2204
+ className: "px-0 text-muted-foreground hover:text-destructive",
2205
+ "aria-label": fill(labels.removeNamed, { name: line.title }),
2206
+ disabled: rowRemoving,
2207
+ onClick: () => requestRemoveLine(line),
2208
+ children: rowRemoving ? labels.associateRemoving : labels.remove
2209
+ }
2210
+ ) })
2211
+ ]
2212
+ },
2213
+ line.lineId
2214
+ );
2215
+ }) })
2216
+ ]
2217
+ },
2218
+ group.id
2219
+ );
2220
+ }) })
2221
+ ] }),
2222
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border bg-card", children: [
2223
+ /* @__PURE__ */ jsxs("section", { "aria-labelledby": `${uid}-payment`, className: "px-4 py-3", children: [
2224
+ /* @__PURE__ */ jsx(
2225
+ "h3",
2226
+ {
2227
+ id: `${uid}-payment`,
2228
+ className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground",
2229
+ children: labels.paymentTitle
2230
+ }
2231
+ ),
2232
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center justify-between gap-2 rounded-lg bg-muted/40 px-3 py-2", children: [
2233
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2234
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: activeMethod ? activeMethod.label : labels.paymentUnavailable }),
2235
+ /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: activeMethod ? activeMethod.detail : labels.paymentChoose })
2236
+ ] }),
2237
+ /* @__PURE__ */ jsx(
2238
+ Button,
2239
+ {
2240
+ type: "button",
2241
+ size: "sm",
2242
+ variant: VARIANT_OUTLINE,
2243
+ "aria-expanded": paymentOpen,
2244
+ "aria-controls": `${uid}-payment-panel`,
2245
+ onClick: () => setPaymentOpen(!paymentOpen),
2246
+ children: labels.changeAction
2247
+ }
2248
+ )
2249
+ ] }),
2250
+ paymentNotice ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive", children: paymentNotice }) : null,
2251
+ paymentOpen ? /* @__PURE__ */ jsx("div", { id: `${uid}-payment-panel`, className: "mt-2", children: /* @__PURE__ */ jsx(
2252
+ OptionGroup,
2253
+ {
2254
+ legend: labels.paymentChoose,
2255
+ options: paymentOptions,
2256
+ selectedId: activePaymentId,
2257
+ onSelect: (id) => {
2258
+ setPaymentId(id);
2259
+ setPaymentError(false);
2260
+ setPaymentOpen(false);
2261
+ },
2262
+ className: "grid gap-1.5"
2263
+ }
2264
+ ) }) : null,
2265
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: labels.paymentProviderNote }),
2266
+ paymentError ? /* @__PURE__ */ jsxs("div", { role: "alert", className: "mt-3 rounded-md border border-destructive/40 bg-destructive/10 p-3", children: [
2267
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: labels.paymentErrorTitle }),
2268
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.paymentErrorBody }),
2269
+ /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: VARIANT_OUTLINE, onClick: retryPayment, children: labels.paymentRetry }) })
2270
+ ] }) : null
2271
+ ] }),
2272
+ addressBook.length > 0 ? /* @__PURE__ */ jsxs(
2273
+ "section",
2274
+ {
2275
+ "aria-labelledby": `${uid}-billing`,
2276
+ className: "border-t border-border px-4 py-3",
2277
+ children: [
2278
+ /* @__PURE__ */ jsx(
2279
+ "h3",
2280
+ {
2281
+ id: `${uid}-billing`,
2282
+ className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground",
2283
+ children: labels.billingTitle
2284
+ }
2285
+ ),
2286
+ /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxs(
2287
+ Button,
2288
+ {
2289
+ type: "button",
2290
+ role: "checkbox",
2291
+ "aria-checked": billingSame,
2292
+ variant: VARIANT_GHOST,
2293
+ size: "sm",
2294
+ onClick: () => setBillingSame(!billingSame),
2295
+ className: "w-full justify-start whitespace-normal px-0 text-left",
2296
+ children: [
2297
+ /* @__PURE__ */ jsx(
2298
+ "span",
2299
+ {
2300
+ "aria-hidden": "true",
2301
+ className: "flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-xs",
2302
+ children: billingSame ? labels.checkSymbol : null
2303
+ }
2304
+ ),
2305
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-normal", children: labels.billingSame })
2306
+ ]
2307
+ }
2308
+ ) }),
2309
+ !billingSame ? /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
2310
+ OptionGroup,
2311
+ {
2312
+ legend: labels.billingChoose,
2313
+ options: addressBook.map((address) => ({
2314
+ id: address.id,
2315
+ title: address.label,
2316
+ detail: addressSummary(address),
2317
+ meta: address.isDefault ? labels.addressDefaultBadge : "",
2318
+ disabled: false,
2319
+ reason: ""
2320
+ })),
2321
+ selectedId: billingId,
2322
+ onSelect: setBillingId,
2323
+ className: "grid gap-2"
2324
+ }
2325
+ ) }) : null
2326
+ ]
2327
+ }
2328
+ ) : null
2329
+ ] }),
2330
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: data.meta.footerNote })
2331
+ ] }),
2332
+ /* @__PURE__ */ jsx("aside", { className: "md:col-span-2 md:sticky md:top-6", "aria-label": labels.summaryTitle, children: /* @__PURE__ */ jsxs(Card, { className: "gap-3 py-0 shadow-none", children: [
2333
+ /* @__PURE__ */ jsx(CardHeader, { className: "border-b border-border px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [
2334
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: labels.summaryTitle }),
2335
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: countLabel(unitCount, labels.summaryItemOne, fill(labels.summaryItems, { count: unitCount })) })
2336
+ ] }) }),
2337
+ /* @__PURE__ */ jsxs(CardContent, { className: "px-4 py-3", children: [
2338
+ /* @__PURE__ */ jsxs("dl", { className: "space-y-2", children: [
2339
+ /* @__PURE__ */ jsx(SummaryRow, { label: labels.subtotal, value: money(subtotal, data), emphasis: false }),
2340
+ promotions.map((promotion) => /* @__PURE__ */ jsx(
2341
+ SummaryRow,
2342
+ {
2343
+ label: promotion.label,
2344
+ value: `- ${money(promotion.amountCents, data)}`,
2345
+ emphasis: false
2346
+ },
2347
+ promotion.id
2348
+ )),
2349
+ /* @__PURE__ */ jsx(
2350
+ SummaryRow,
2351
+ {
2352
+ label: fill(labels.tax, { rate: String(scenario.taxRateBps / 100) }),
2353
+ value: money(tax, data),
2354
+ emphasis: false
2355
+ }
2356
+ ),
2357
+ pending ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
2358
+ shippingFees > 0 ? /* @__PURE__ */ jsx(
2359
+ SummaryRow,
2360
+ {
2361
+ label: labels.shippingCost,
2362
+ value: money(shippingFees, data),
2363
+ emphasis: false
2364
+ }
2365
+ ) : null,
2366
+ pickupFees > 0 ? /* @__PURE__ */ jsx(
2367
+ SummaryRow,
2368
+ {
2369
+ label: labels.pickupCost,
2370
+ value: money(pickupFees, data),
2371
+ emphasis: false
2372
+ }
2373
+ ) : null,
2374
+ scenario.extraCents > 0 ? /* @__PURE__ */ jsx(
2375
+ SummaryRow,
2376
+ {
2377
+ label: scenario.extraLabel || labels.extraCost,
2378
+ value: money(scenario.extraCents, data),
2379
+ emphasis: false
2380
+ }
2381
+ ) : null
2382
+ ] })
2383
+ ] }),
2384
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 border-t border-border pt-3", children: [
2385
+ /* @__PURE__ */ jsx("dl", { children: /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [
2386
+ /* @__PURE__ */ jsx("dt", { className: "text-sm font-semibold", children: pending ? labels.totalPending : labels.total }),
2387
+ /* @__PURE__ */ jsx("dd", { className: "text-base font-semibold tabular-nums", children: pending ? labels.overviewPending : money(total, data) })
2388
+ ] }) }),
2389
+ pending ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.totalPendingNote }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.totalDemoNote })
2390
+ ] }),
2391
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: appliedCoupon ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
2392
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-success", children: fill(labels.promoApplied, { code: appliedCoupon.code }) }),
2393
+ /* @__PURE__ */ jsx(
2394
+ Button,
2395
+ {
2396
+ type: "button",
2397
+ size: "xs",
2398
+ variant: VARIANT_GHOST,
2399
+ onClick: removeCoupon,
2400
+ children: labels.remove
2401
+ }
2402
+ )
2403
+ ] }) : couponOpen ? /* @__PURE__ */ jsxs("div", { children: [
2404
+ /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-coupon`, className: "text-xs font-medium", children: labels.promoLabel }),
2405
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex gap-2", children: [
2406
+ /* @__PURE__ */ jsx(
2407
+ "input",
2408
+ {
2409
+ id: `${uid}-coupon`,
2410
+ value: couponCode,
2411
+ placeholder: labels.promoPlaceholder,
2412
+ onChange: (event) => {
2413
+ setCouponCode(event.target.value);
2414
+ setCouponNotice(null);
2415
+ },
2416
+ className: "h-9 min-w-0 flex-1 rounded-md border border-input bg-background px-3 text-sm"
2417
+ }
2418
+ ),
2419
+ /* @__PURE__ */ jsx(
2420
+ Button,
2421
+ {
2422
+ type: "button",
2423
+ size: "sm",
2424
+ variant: VARIANT_OUTLINE,
2425
+ onClick: applyCoupon,
2426
+ children: labels.promoApply
2427
+ }
2428
+ )
2429
+ ] }),
2430
+ couponNotice && couponNotice.status !== "applied" ? /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-xs text-destructive", children: couponNotice.message }) : null
2431
+ ] }) : /* @__PURE__ */ jsx(
2432
+ Button,
2433
+ {
2434
+ type: "button",
2435
+ size: "sm",
2436
+ variant: VARIANT_GHOST,
2437
+ onClick: () => setCouponOpen(true),
2438
+ children: labels.promoOpen
2439
+ }
2440
+ ) })
2441
+ ] }),
2442
+ /* @__PURE__ */ jsx(CardFooter, { className: "px-4 pb-4", children: /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
2443
+ /* @__PURE__ */ jsx(
2444
+ Button,
2445
+ {
2446
+ type: "button",
2447
+ className: "hidden w-full md:inline-flex",
2448
+ size: "lg",
2449
+ disabled: !canConfirm || creatingOrder,
2450
+ "aria-describedby": `${uid}-confirm-note`,
2451
+ onClick: handleConfirm,
2452
+ children: creatingOrder ? labels.confirmationCreating : labels.confirm
2453
+ }
2454
+ ),
2455
+ /* @__PURE__ */ jsx("p", { id: `${uid}-confirm-note`, className: "mt-2 text-xs text-muted-foreground", children: creatingOrder ? labels.confirmationCreatingNote : canConfirm ? labels.confirmNote : blockedMessage })
2456
+ ] }) })
2457
+ ] }) })
2458
+ ] })
2459
+ ] });
2460
+ }
2461
+ function CheckoutCommerceV1({ data, ...props }) {
2462
+ return /* @__PURE__ */ jsx(BlockDataProvider, { data, children: /* @__PURE__ */ jsx(CheckoutCommerceV1Content, { data, ...props }) });
2463
+ }
2464
+ export {
2465
+ CheckoutCommerceV1 as default
2466
+ };
2467
+ //# sourceMappingURL=block.mjs.map