@thorprovider/blocks 3.0.2 → 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 (234) hide show
  1. package/README.md +61 -21
  2. package/dist/blocks/designs/checkout-commerce-v1/block.d.ts +49 -0
  3. package/dist/blocks/designs/checkout-commerce-v1/block.d.ts.map +1 -0
  4. package/dist/blocks/designs/checkout-commerce-v1/block.js +2487 -0
  5. package/dist/blocks/designs/checkout-commerce-v1/block.js.map +1 -0
  6. package/dist/blocks/designs/checkout-commerce-v1/block.mjs +2467 -0
  7. package/dist/blocks/designs/checkout-commerce-v1/block.mjs.map +1 -0
  8. package/dist/blocks/designs/checkout-commerce-v1/data-context.d.ts +8 -0
  9. package/dist/blocks/designs/checkout-commerce-v1/data-context.d.ts.map +1 -0
  10. package/dist/blocks/designs/checkout-commerce-v1/data-context.js +44 -0
  11. package/dist/blocks/designs/checkout-commerce-v1/data-context.js.map +1 -0
  12. package/dist/blocks/designs/checkout-commerce-v1/data-context.mjs +19 -0
  13. package/dist/blocks/designs/checkout-commerce-v1/data-context.mjs.map +1 -0
  14. package/dist/blocks/designs/checkout-commerce-v1/index.d.ts +3 -0
  15. package/dist/blocks/designs/checkout-commerce-v1/index.d.ts.map +1 -0
  16. package/dist/blocks/designs/checkout-commerce-v1/index.js +36 -0
  17. package/dist/blocks/designs/checkout-commerce-v1/index.js.map +1 -0
  18. package/dist/blocks/designs/checkout-commerce-v1/index.mjs +6 -0
  19. package/dist/blocks/designs/checkout-commerce-v1/index.mjs.map +1 -0
  20. package/dist/blocks/designs/marketplace-product-card/block.d.ts +12 -0
  21. package/dist/blocks/designs/marketplace-product-card/block.d.ts.map +1 -0
  22. package/dist/blocks/designs/marketplace-product-card/block.js +243 -0
  23. package/dist/blocks/designs/marketplace-product-card/block.js.map +1 -0
  24. package/dist/blocks/designs/marketplace-product-card/block.mjs +233 -0
  25. package/dist/blocks/designs/marketplace-product-card/block.mjs.map +1 -0
  26. package/dist/blocks/designs/marketplace-product-card/index.d.ts +3 -0
  27. package/dist/blocks/designs/marketplace-product-card/index.d.ts.map +1 -0
  28. package/dist/blocks/designs/marketplace-product-card/index.js +36 -0
  29. package/dist/blocks/designs/marketplace-product-card/index.js.map +1 -0
  30. package/dist/blocks/designs/marketplace-product-card/index.mjs +6 -0
  31. package/dist/blocks/designs/marketplace-product-card/index.mjs.map +1 -0
  32. package/dist/blocks/designs/marketplace-product-card/normalize.d.ts +13 -0
  33. package/dist/blocks/designs/marketplace-product-card/normalize.d.ts.map +1 -0
  34. package/dist/blocks/designs/marketplace-product-card/normalize.js +36 -0
  35. package/dist/blocks/designs/marketplace-product-card/normalize.js.map +1 -0
  36. package/dist/blocks/designs/marketplace-product-card/normalize.mjs +12 -0
  37. package/dist/blocks/designs/marketplace-product-card/normalize.mjs.map +1 -0
  38. package/dist/blocks/designs/marketplace-product-card/variants.d.ts +72 -0
  39. package/dist/blocks/designs/marketplace-product-card/variants.d.ts.map +1 -0
  40. package/dist/blocks/designs/marketplace-product-card/variants.js +123 -0
  41. package/dist/blocks/designs/marketplace-product-card/variants.js.map +1 -0
  42. package/dist/blocks/designs/marketplace-product-card/variants.mjs +89 -0
  43. package/dist/blocks/designs/marketplace-product-card/variants.mjs.map +1 -0
  44. package/dist/blocks/e-commerce/account/account-frame.d.ts +38 -0
  45. package/dist/blocks/e-commerce/account/account-frame.d.ts.map +1 -0
  46. package/dist/blocks/e-commerce/account/account-frame.js +102 -0
  47. package/dist/blocks/e-commerce/account/account-frame.js.map +1 -0
  48. package/dist/blocks/e-commerce/account/account-frame.mjs +78 -0
  49. package/dist/blocks/e-commerce/account/account-frame.mjs.map +1 -0
  50. package/dist/blocks/e-commerce/account/address-book.d.ts +79 -0
  51. package/dist/blocks/e-commerce/account/address-book.d.ts.map +1 -0
  52. package/dist/blocks/e-commerce/account/address-book.js +250 -0
  53. package/dist/blocks/e-commerce/account/address-book.js.map +1 -0
  54. package/dist/blocks/e-commerce/account/address-book.mjs +225 -0
  55. package/dist/blocks/e-commerce/account/address-book.mjs.map +1 -0
  56. package/dist/blocks/e-commerce/account/profile-form.d.ts +35 -0
  57. package/dist/blocks/e-commerce/account/profile-form.d.ts.map +1 -0
  58. package/dist/blocks/e-commerce/account/profile-form.js +100 -0
  59. package/dist/blocks/e-commerce/account/profile-form.js.map +1 -0
  60. package/dist/blocks/e-commerce/account/profile-form.mjs +76 -0
  61. package/dist/blocks/e-commerce/account/profile-form.mjs.map +1 -0
  62. package/dist/blocks/e-commerce/checkout-forms/checkbox-check.js +1 -1
  63. package/dist/blocks/e-commerce/checkout-forms/checkbox-check.js.map +1 -1
  64. package/dist/blocks/e-commerce/checkout-forms/checkbox-check.mjs +1 -1
  65. package/dist/blocks/e-commerce/checkout-forms/checkbox-check.mjs.map +1 -1
  66. package/dist/blocks/e-commerce/checkout-forms/checkout-address-equality.d.ts +11 -0
  67. package/dist/blocks/e-commerce/checkout-forms/checkout-address-equality.d.ts.map +1 -0
  68. package/dist/blocks/e-commerce/checkout-forms/checkout-address-equality.js +46 -0
  69. package/dist/blocks/e-commerce/checkout-forms/checkout-address-equality.js.map +1 -0
  70. package/dist/blocks/e-commerce/checkout-forms/checkout-address-equality.mjs +22 -0
  71. package/dist/blocks/e-commerce/checkout-forms/checkout-address-equality.mjs.map +1 -0
  72. package/dist/blocks/e-commerce/checkout-forms/checkout-address-fields.d.ts +28 -0
  73. package/dist/blocks/e-commerce/checkout-forms/checkout-address-fields.d.ts.map +1 -0
  74. package/dist/blocks/e-commerce/checkout-forms/checkout-address-fields.js +278 -0
  75. package/dist/blocks/e-commerce/checkout-forms/checkout-address-fields.js.map +1 -0
  76. package/dist/blocks/e-commerce/checkout-forms/checkout-address-fields.mjs +260 -0
  77. package/dist/blocks/e-commerce/checkout-forms/checkout-address-fields.mjs.map +1 -0
  78. package/dist/blocks/e-commerce/checkout-forms/checkout-contact-step.d.ts +12 -9
  79. package/dist/blocks/e-commerce/checkout-forms/checkout-contact-step.d.ts.map +1 -1
  80. package/dist/blocks/e-commerce/checkout-forms/checkout-contact-step.js +49 -71
  81. package/dist/blocks/e-commerce/checkout-forms/checkout-contact-step.js.map +1 -1
  82. package/dist/blocks/e-commerce/checkout-forms/checkout-contact-step.mjs +50 -72
  83. package/dist/blocks/e-commerce/checkout-forms/checkout-contact-step.mjs.map +1 -1
  84. package/dist/blocks/e-commerce/checkout-forms/checkout-country-field.d.ts.map +1 -1
  85. package/dist/blocks/e-commerce/checkout-forms/checkout-country-field.js +6 -3
  86. package/dist/blocks/e-commerce/checkout-forms/checkout-country-field.js.map +1 -1
  87. package/dist/blocks/e-commerce/checkout-forms/checkout-country-field.mjs +6 -3
  88. package/dist/blocks/e-commerce/checkout-forms/checkout-country-field.mjs.map +1 -1
  89. package/dist/blocks/e-commerce/checkout-forms/checkout-id-context.d.ts +7 -0
  90. package/dist/blocks/e-commerce/checkout-forms/checkout-id-context.d.ts.map +1 -0
  91. package/dist/blocks/e-commerce/checkout-forms/checkout-id-context.js +41 -0
  92. package/dist/blocks/e-commerce/checkout-forms/checkout-id-context.js.map +1 -0
  93. package/dist/blocks/e-commerce/checkout-forms/checkout-id-context.mjs +16 -0
  94. package/dist/blocks/e-commerce/checkout-forms/checkout-id-context.mjs.map +1 -0
  95. package/dist/blocks/e-commerce/checkout-forms/checkout-location-select.d.ts +24 -0
  96. package/dist/blocks/e-commerce/checkout-forms/checkout-location-select.d.ts.map +1 -0
  97. package/dist/blocks/e-commerce/checkout-forms/checkout-location-select.js +75 -0
  98. package/dist/blocks/e-commerce/checkout-forms/checkout-location-select.js.map +1 -0
  99. package/dist/blocks/e-commerce/checkout-forms/checkout-location-select.mjs +58 -0
  100. package/dist/blocks/e-commerce/checkout-forms/checkout-location-select.mjs.map +1 -0
  101. package/dist/blocks/e-commerce/checkout-forms/checkout-order-summary.d.ts +3 -2
  102. package/dist/blocks/e-commerce/checkout-forms/checkout-order-summary.d.ts.map +1 -1
  103. package/dist/blocks/e-commerce/checkout-forms/checkout-order-summary.js +9 -2
  104. package/dist/blocks/e-commerce/checkout-forms/checkout-order-summary.js.map +1 -1
  105. package/dist/blocks/e-commerce/checkout-forms/checkout-order-summary.mjs +9 -2
  106. package/dist/blocks/e-commerce/checkout-forms/checkout-order-summary.mjs.map +1 -1
  107. package/dist/blocks/e-commerce/checkout-forms/checkout-payment-step.d.ts +23 -8
  108. package/dist/blocks/e-commerce/checkout-forms/checkout-payment-step.d.ts.map +1 -1
  109. package/dist/blocks/e-commerce/checkout-forms/checkout-payment-step.js +136 -31
  110. package/dist/blocks/e-commerce/checkout-forms/checkout-payment-step.js.map +1 -1
  111. package/dist/blocks/e-commerce/checkout-forms/checkout-payment-step.mjs +143 -32
  112. package/dist/blocks/e-commerce/checkout-forms/checkout-payment-step.mjs.map +1 -1
  113. package/dist/blocks/e-commerce/checkout-forms/checkout-review-step.d.ts +13 -7
  114. package/dist/blocks/e-commerce/checkout-forms/checkout-review-step.d.ts.map +1 -1
  115. package/dist/blocks/e-commerce/checkout-forms/checkout-review-step.js +179 -28
  116. package/dist/blocks/e-commerce/checkout-forms/checkout-review-step.js.map +1 -1
  117. package/dist/blocks/e-commerce/checkout-forms/checkout-review-step.mjs +180 -29
  118. package/dist/blocks/e-commerce/checkout-forms/checkout-review-step.mjs.map +1 -1
  119. package/dist/blocks/e-commerce/checkout-forms/checkout-saved-addresses.d.ts +20 -0
  120. package/dist/blocks/e-commerce/checkout-forms/checkout-saved-addresses.d.ts.map +1 -0
  121. package/dist/blocks/e-commerce/checkout-forms/checkout-saved-addresses.js +199 -0
  122. package/dist/blocks/e-commerce/checkout-forms/checkout-saved-addresses.js.map +1 -0
  123. package/dist/blocks/e-commerce/checkout-forms/checkout-saved-addresses.mjs +175 -0
  124. package/dist/blocks/e-commerce/checkout-forms/checkout-saved-addresses.mjs.map +1 -0
  125. package/dist/blocks/e-commerce/checkout-forms/checkout-section-nav.d.ts +14 -0
  126. package/dist/blocks/e-commerce/checkout-forms/checkout-section-nav.d.ts.map +1 -0
  127. package/dist/blocks/e-commerce/checkout-forms/checkout-section-nav.js +136 -0
  128. package/dist/blocks/e-commerce/checkout-forms/checkout-section-nav.js.map +1 -0
  129. package/dist/blocks/e-commerce/checkout-forms/checkout-section-nav.mjs +109 -0
  130. package/dist/blocks/e-commerce/checkout-forms/checkout-section-nav.mjs.map +1 -0
  131. package/dist/blocks/e-commerce/checkout-forms/checkout-shipping-step.d.ts +32 -10
  132. package/dist/blocks/e-commerce/checkout-forms/checkout-shipping-step.d.ts.map +1 -1
  133. package/dist/blocks/e-commerce/checkout-forms/checkout-shipping-step.js +265 -147
  134. package/dist/blocks/e-commerce/checkout-forms/checkout-shipping-step.js.map +1 -1
  135. package/dist/blocks/e-commerce/checkout-forms/checkout-shipping-step.mjs +272 -148
  136. package/dist/blocks/e-commerce/checkout-forms/checkout-shipping-step.mjs.map +1 -1
  137. package/dist/blocks/e-commerce/checkout-forms/checkout-step-header.d.ts +13 -0
  138. package/dist/blocks/e-commerce/checkout-forms/checkout-step-header.d.ts.map +1 -0
  139. package/dist/blocks/e-commerce/checkout-forms/checkout-step-header.js +74 -0
  140. package/dist/blocks/e-commerce/checkout-forms/checkout-step-header.js.map +1 -0
  141. package/dist/blocks/e-commerce/checkout-forms/checkout-step-header.mjs +50 -0
  142. package/dist/blocks/e-commerce/checkout-forms/checkout-step-header.mjs.map +1 -0
  143. package/dist/blocks/e-commerce/checkout-forms/checkout-step-summary.d.ts +7 -0
  144. package/dist/blocks/e-commerce/checkout-forms/checkout-step-summary.d.ts.map +1 -0
  145. package/dist/blocks/e-commerce/checkout-forms/checkout-step-summary.js +91 -0
  146. package/dist/blocks/e-commerce/checkout-forms/checkout-step-summary.js.map +1 -0
  147. package/dist/blocks/e-commerce/checkout-forms/checkout-step-summary.mjs +65 -0
  148. package/dist/blocks/e-commerce/checkout-forms/checkout-step-summary.mjs.map +1 -0
  149. package/dist/blocks/e-commerce/checkout-forms/checkout.d.ts +174 -44
  150. package/dist/blocks/e-commerce/checkout-forms/checkout.d.ts.map +1 -1
  151. package/dist/blocks/e-commerce/checkout-forms/checkout.js +532 -108
  152. package/dist/blocks/e-commerce/checkout-forms/checkout.js.map +1 -1
  153. package/dist/blocks/e-commerce/checkout-forms/checkout.mjs +542 -109
  154. package/dist/blocks/e-commerce/checkout-forms/checkout.mjs.map +1 -1
  155. package/dist/blocks/e-commerce/navigation/storefront-navbar-cart-sheet.d.ts.map +1 -1
  156. package/dist/blocks/e-commerce/navigation/storefront-navbar-cart-sheet.js +44 -20
  157. package/dist/blocks/e-commerce/navigation/storefront-navbar-cart-sheet.js.map +1 -1
  158. package/dist/blocks/e-commerce/navigation/storefront-navbar-cart-sheet.mjs +44 -20
  159. package/dist/blocks/e-commerce/navigation/storefront-navbar-cart-sheet.mjs.map +1 -1
  160. package/dist/blocks/e-commerce/navigation/storefront-navbar.d.ts.map +1 -1
  161. package/dist/blocks/e-commerce/navigation/storefront-navbar.js +21 -10
  162. package/dist/blocks/e-commerce/navigation/storefront-navbar.js.map +1 -1
  163. package/dist/blocks/e-commerce/navigation/storefront-navbar.mjs +21 -10
  164. package/dist/blocks/e-commerce/navigation/storefront-navbar.mjs.map +1 -1
  165. package/dist/blocks/e-commerce/product-list/product-card.d.ts.map +1 -1
  166. package/dist/blocks/e-commerce/product-list/product-card.js +36 -9
  167. package/dist/blocks/e-commerce/product-list/product-card.js.map +1 -1
  168. package/dist/blocks/e-commerce/product-list/product-card.mjs +36 -9
  169. package/dist/blocks/e-commerce/product-list/product-card.mjs.map +1 -1
  170. package/dist/blocks/e-commerce/product-list/product-showcase-1.d.ts.map +1 -1
  171. package/dist/blocks/e-commerce/product-list/product-showcase-1.js +7 -2
  172. package/dist/blocks/e-commerce/product-list/product-showcase-1.js.map +1 -1
  173. package/dist/blocks/e-commerce/product-list/product-showcase-1.mjs +7 -2
  174. package/dist/blocks/e-commerce/product-list/product-showcase-1.mjs.map +1 -1
  175. package/dist/blocks/e-commerce/product-overview/product-quick-view-dialog.d.ts.map +1 -1
  176. package/dist/blocks/e-commerce/product-overview/product-quick-view-dialog.js +5 -1
  177. package/dist/blocks/e-commerce/product-overview/product-quick-view-dialog.js.map +1 -1
  178. package/dist/blocks/e-commerce/product-overview/product-quick-view-dialog.mjs +5 -1
  179. package/dist/blocks/e-commerce/product-overview/product-quick-view-dialog.mjs.map +1 -1
  180. package/dist/blocks/e-commerce/storefront-hero/storefront-hero.d.ts.map +1 -1
  181. package/dist/blocks/e-commerce/storefront-hero/storefront-hero.js +71 -51
  182. package/dist/blocks/e-commerce/storefront-hero/storefront-hero.js.map +1 -1
  183. package/dist/blocks/e-commerce/storefront-hero/storefront-hero.mjs +71 -51
  184. package/dist/blocks/e-commerce/storefront-hero/storefront-hero.mjs.map +1 -1
  185. package/dist/blocks/marketing/banners/banner-1.d.ts.map +1 -1
  186. package/dist/blocks/marketing/banners/banner-1.js +15 -27
  187. package/dist/blocks/marketing/banners/banner-1.js.map +1 -1
  188. package/dist/blocks/marketing/banners/banner-1.mjs +16 -28
  189. package/dist/blocks/marketing/banners/banner-1.mjs.map +1 -1
  190. package/dist/index.d.ts +16 -4
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.js +19 -0
  193. package/dist/index.js.map +1 -1
  194. package/dist/index.mjs +13 -0
  195. package/dist/index.mjs.map +1 -1
  196. package/dist/lib/contracts/checkout-commerce-v1.d.ts +2350 -0
  197. package/dist/lib/contracts/checkout-commerce-v1.d.ts.map +1 -0
  198. package/dist/lib/contracts/checkout-commerce-v1.js +417 -0
  199. package/dist/lib/contracts/checkout-commerce-v1.js.map +1 -0
  200. package/dist/lib/contracts/checkout-commerce-v1.mjs +392 -0
  201. package/dist/lib/contracts/checkout-commerce-v1.mjs.map +1 -0
  202. package/dist/lib/contracts/marketplace-product-card.d.ts +324 -0
  203. package/dist/lib/contracts/marketplace-product-card.d.ts.map +1 -0
  204. package/dist/lib/contracts/marketplace-product-card.js +85 -0
  205. package/dist/lib/contracts/marketplace-product-card.js.map +1 -0
  206. package/dist/lib/contracts/marketplace-product-card.mjs +61 -0
  207. package/dist/lib/contracts/marketplace-product-card.mjs.map +1 -0
  208. package/dist/lib/contracts.d.ts +2289 -178
  209. package/dist/lib/contracts.d.ts.map +1 -1
  210. package/dist/lib/contracts.js +332 -25
  211. package/dist/lib/contracts.js.map +1 -1
  212. package/dist/lib/contracts.mjs +278 -5
  213. package/dist/lib/contracts.mjs.map +1 -1
  214. package/dist/lib/money-schema.d.ts +19 -0
  215. package/dist/lib/money-schema.d.ts.map +1 -0
  216. package/dist/lib/money-schema.js +33 -0
  217. package/dist/lib/money-schema.js.map +1 -0
  218. package/dist/lib/money-schema.mjs +9 -0
  219. package/dist/lib/money-schema.mjs.map +1 -0
  220. package/dist/lib/motion.d.ts +29 -0
  221. package/dist/lib/motion.d.ts.map +1 -0
  222. package/dist/lib/motion.js +65 -0
  223. package/dist/lib/motion.js.map +1 -0
  224. package/dist/lib/motion.mjs +41 -0
  225. package/dist/lib/motion.mjs.map +1 -0
  226. package/dist/ui/accordion.d.ts +3 -1
  227. package/dist/ui/accordion.d.ts.map +1 -1
  228. package/dist/ui/accordion.js +21 -5
  229. package/dist/ui/accordion.js.map +1 -1
  230. package/dist/ui/accordion.mjs +21 -5
  231. package/dist/ui/accordion.mjs.map +1 -1
  232. package/dist/ui/input-group.d.ts +1 -1
  233. package/package.json +18 -3
  234. package/dist/.tsbuildinfo +0 -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