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