storefront-layouts 1.0.3 → 1.0.4

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 (509) hide show
  1. package/dist/components/auth/LoginForm.js +2 -2
  2. package/dist/components/auth/LoginSheet.js +2 -2
  3. package/dist/components/auth/login-form-core.js +3 -3
  4. package/dist/components/auth/login-sheet-header.js +3 -3
  5. package/dist/components/auth/login-sheet-registry.js +2 -2
  6. package/dist/components/auth/login-sheet-themes/booking-agenda.theme.js +1 -1
  7. package/dist/components/auth/login-sheet-themes/booking.theme.js +1 -1
  8. package/dist/components/auth/login-sheet-themes/clothing-minimal.theme.js +1 -1
  9. package/dist/components/auth/login-sheet-themes/clothing.theme.js +1 -1
  10. package/dist/components/auth/login-sheet-themes/electronics-grid.theme.js +1 -1
  11. package/dist/components/auth/login-sheet-themes/electronics.theme.js +1 -1
  12. package/dist/components/auth/login-sheet-themes/food-modern.theme.js +1 -1
  13. package/dist/components/auth/login-sheet-themes/food.theme.js +1 -1
  14. package/dist/components/auth/login-sheet-themes/index.js +9 -9
  15. package/dist/components/auth/login-sheet-themes/motivational-speaker.theme.js +1 -1
  16. package/dist/components/auth/login-sheet-themes/theme-input-baselines.d.ts +2 -2
  17. package/dist/components/auth/login-sheet-themes/theme-input-baselines.d.ts.map +1 -1
  18. package/dist/components/auth/login-sheet-themes/theme-input-baselines.js +2 -2
  19. package/dist/components/auth/login-sheets/StoreLoginSheetShell.d.ts.map +1 -1
  20. package/dist/components/auth/login-sheets/StoreLoginSheetShell.js +6 -6
  21. package/dist/components/auth/use-login-submit.js +3 -3
  22. package/dist/components/cart/InventoryHoldCountdown.js +2 -2
  23. package/dist/components/categories/category-masonry-grid.js +3 -3
  24. package/dist/components/checkout/CheckoutFeeLines.js +1 -1
  25. package/dist/components/checkout/ShippingOptionsPanel.js +3 -3
  26. package/dist/components/learning/CertificateTemplates.js +2 -2
  27. package/dist/components/learning/CertificateViewer.js +3 -3
  28. package/dist/components/learning/MentorshipProgress.d.ts.map +1 -1
  29. package/dist/components/learning/MentorshipProgress.js +4 -4
  30. package/dist/components/product/ProductVariationSelectors.js +1 -1
  31. package/dist/components/rich-text/CategoryDescription.js +2 -2
  32. package/dist/components/rich-text/ProductDescription.js +2 -2
  33. package/dist/components/rich-text/RichTextExcerpt.js +1 -1
  34. package/dist/components/rich-text/SafeRichHtml.d.ts +8 -11
  35. package/dist/components/rich-text/SafeRichHtml.d.ts.map +1 -1
  36. package/dist/components/rich-text/SafeRichHtml.js +3 -5
  37. package/dist/components/shipping/ProductDeliveryInfoSection.d.ts.map +1 -1
  38. package/dist/components/shipping/ProductDeliveryInfoSection.js +7 -4
  39. package/dist/components/shipping/ShippingQuoteAddressForm.js +3 -3
  40. package/dist/components/ui/alert-modal.js +2 -2
  41. package/dist/components/ui/avatar-image.js +1 -1
  42. package/dist/components/ui/badge.js +1 -1
  43. package/dist/components/ui/breadcrumbs.js +1 -1
  44. package/dist/components/ui/button.d.ts +1 -1
  45. package/dist/components/ui/button.js +1 -1
  46. package/dist/components/ui/card.js +1 -1
  47. package/dist/components/ui/category-tree.js +2 -2
  48. package/dist/components/ui/checkout-button.d.ts.map +1 -1
  49. package/dist/components/ui/checkout-button.js +11 -283
  50. package/dist/components/ui/guest-checkout-modal.js +3 -3
  51. package/dist/components/ui/image-skeleton.js +2 -2
  52. package/dist/components/ui/image-with-fallback.d.ts +3 -1
  53. package/dist/components/ui/image-with-fallback.d.ts.map +1 -1
  54. package/dist/components/ui/image-with-fallback.js +55 -139
  55. package/dist/components/ui/input.js +1 -1
  56. package/dist/components/ui/limited-time-offer-badge.js +2 -2
  57. package/dist/components/ui/loading-skeleton-wrapper.js +1 -1
  58. package/dist/components/ui/modal.js +2 -2
  59. package/dist/components/ui/otp-input.js +1 -1
  60. package/dist/components/ui/out-of-stock-overlay.js +1 -1
  61. package/dist/components/ui/page-animate-wrapper.js +2 -2
  62. package/dist/components/ui/page-content-loader.js +2 -2
  63. package/dist/components/ui/page-loader.js +1 -1
  64. package/dist/components/ui/product-card.d.ts.map +1 -1
  65. package/dist/components/ui/product-card.js +17 -17
  66. package/dist/components/ui/product-rating.js +1 -1
  67. package/dist/components/ui/product-stock-badge.js +2 -2
  68. package/dist/components/ui/review-form.js +2 -2
  69. package/dist/components/ui/route-skeleton-loader.js +6 -6
  70. package/dist/components/ui/sheet.d.ts +2 -1
  71. package/dist/components/ui/sheet.d.ts.map +1 -1
  72. package/dist/components/ui/sheet.js +4 -4
  73. package/dist/components/ui/skeleton.js +1 -1
  74. package/dist/components/ui/skeletons/index.js +5 -5
  75. package/dist/components/ui/skeletons/product-grid-skeleton.js +1 -1
  76. package/dist/components/ui/store-logos.d.ts.map +1 -1
  77. package/dist/components/ui/store-logos.js +4 -4
  78. package/dist/components/ui/toast.js +1 -1
  79. package/dist/components/ui/video-player.js +2 -2
  80. package/dist/editor/alignment-schemas/booking.js +1 -1
  81. package/dist/editor/alignment-schemas/clothing-minimal.js +1 -1
  82. package/dist/editor/alignment-schemas/index.js +20 -20
  83. package/dist/editor/alignment-schemas/lazy-loader.js +1 -1
  84. package/dist/editor/alignment-schemas/metadata.js +1 -1
  85. package/dist/editor/alignment-schemas/resolve-canvas-inspector-leaf.js +3 -3
  86. package/dist/editor/blocks/index.js +3 -3
  87. package/dist/editor/blocks/migrate-clothing-minimal.js +3 -3
  88. package/dist/editor/blocks/nav-outline-utils.js +2 -2
  89. package/dist/editor/blocks/registry.js +1 -1
  90. package/dist/editor/document/adapter.js +4 -4
  91. package/dist/editor/document/block-tree.js +1 -1
  92. package/dist/editor/document/index.js +6 -6
  93. package/dist/editor/document/validate.js +1 -1
  94. package/dist/editor/index.js +12 -12
  95. package/dist/editor/layout-content-loader.js +3 -3
  96. package/dist/editor/payaza-form/ArrayItemManager.js +2 -2
  97. package/dist/editor/payaza-form/EditorCanvasPreview.js +5 -5
  98. package/dist/editor/payaza-form/FieldRenderer.js +17 -17
  99. package/dist/editor/payaza-form/IframePreviewMessageBridge.js +3 -3
  100. package/dist/editor/payaza-form/PayazaFormEditor.js +31 -31
  101. package/dist/editor/payaza-form/PayazaFormEditorFullPage.js +2 -2
  102. package/dist/editor/payaza-form/SliderArrayEditor.js +3 -3
  103. package/dist/editor/payaza-form/components/ArrayField.js +1 -1
  104. package/dist/editor/payaza-form/components/BlockStylesInspector.js +1 -1
  105. package/dist/editor/payaza-form/components/BlocksField.js +9 -9
  106. package/dist/editor/payaza-form/components/ButtonField.js +3 -3
  107. package/dist/editor/payaza-form/components/CanvasGranularInspector.js +11 -11
  108. package/dist/editor/payaza-form/components/IconField.js +2 -2
  109. package/dist/editor/payaza-form/components/ImageField.js +1 -1
  110. package/dist/editor/payaza-form/components/NumberField.js +1 -1
  111. package/dist/editor/payaza-form/components/RichTextField.js +2 -2
  112. package/dist/editor/payaza-form/components/SectionField.js +1 -1
  113. package/dist/editor/payaza-form/components/SectionSchemaInspector.js +3 -3
  114. package/dist/editor/payaza-form/components/StarRatingField.js +2 -2
  115. package/dist/editor/payaza-form/components/TextField.js +1 -1
  116. package/dist/editor/payaza-form/data-merger.js +6 -6
  117. package/dist/editor/payaza-form/data-transformer.js +3 -3
  118. package/dist/editor/payaza-form/engine.js +1 -1
  119. package/dist/editor/payaza-form/examples/custom-widgets.example.js +1 -1
  120. package/dist/editor/payaza-form/index.js +8 -8
  121. package/dist/editor/payaza-form/layout-loader.js +2 -2
  122. package/dist/editor/payaza-form/preview-message-handler.js +3 -3
  123. package/dist/editor/payaza-form/schema-bridge.js +3 -3
  124. package/dist/editor/payaza-form/widget-registry.js +1 -1
  125. package/dist/editor/shared/BlockOutlineTree.js +2 -2
  126. package/dist/editor/shared/EditorHeader.js +2 -2
  127. package/dist/editor/shared/EditorNavigator.js +2 -2
  128. package/dist/editor/shared/IframePreview.js +6 -6
  129. package/dist/editor/shared/InlineCanvasPreview.js +8 -8
  130. package/dist/editor/shared/LayoutRenderer.js +1 -1
  131. package/dist/editor/shared/ResizableSidebar.js +1 -1
  132. package/dist/editor/shared/index.js +7 -7
  133. package/dist/hooks/use-analytics.js +3 -3
  134. package/dist/hooks/use-api.js +1 -1
  135. package/dist/hooks/use-cart-api.d.ts.map +1 -1
  136. package/dist/hooks/use-cart-api.js +8 -3
  137. package/dist/hooks/use-catalog-loader.js +3 -3
  138. package/dist/hooks/use-catalog-price-range.d.ts +2 -5
  139. package/dist/hooks/use-catalog-price-range.d.ts.map +1 -1
  140. package/dist/hooks/use-catalog-price-range.js +20 -5
  141. package/dist/hooks/use-category-products.d.ts +8 -0
  142. package/dist/hooks/use-category-products.d.ts.map +1 -0
  143. package/dist/hooks/use-category-products.js +43 -0
  144. package/dist/hooks/use-checkout-fees.d.ts +3 -4
  145. package/dist/hooks/use-checkout-fees.d.ts.map +1 -1
  146. package/dist/hooks/use-checkout-fees.js +1 -1
  147. package/dist/hooks/use-content-ready.js +1 -1
  148. package/dist/hooks/use-featured-products.d.ts +3 -0
  149. package/dist/hooks/use-featured-products.d.ts.map +1 -0
  150. package/dist/hooks/use-featured-products.js +25 -0
  151. package/dist/hooks/use-multi-store-checkout-fees.d.ts +3 -4
  152. package/dist/hooks/use-multi-store-checkout-fees.d.ts.map +1 -1
  153. package/dist/hooks/use-multi-store-checkout-fees.js +1 -1
  154. package/dist/hooks/use-navigation-loading.js +1 -1
  155. package/dist/hooks/use-payaza-checkout.d.ts +1 -1
  156. package/dist/hooks/use-payaza-checkout.d.ts.map +1 -1
  157. package/dist/hooks/use-payaza-checkout.js +9 -3
  158. package/dist/hooks/use-product-brands.d.ts +5 -0
  159. package/dist/hooks/use-product-brands.d.ts.map +1 -0
  160. package/dist/hooks/use-product-brands.js +25 -0
  161. package/dist/hooks/use-product-by-slug.d.ts +7 -0
  162. package/dist/hooks/use-product-by-slug.d.ts.map +1 -0
  163. package/dist/hooks/use-product-by-slug.js +38 -0
  164. package/dist/hooks/use-product-reviews.d.ts +15 -0
  165. package/dist/hooks/use-product-reviews.d.ts.map +1 -0
  166. package/dist/hooks/use-product-reviews.js +75 -0
  167. package/dist/hooks/use-product-variation-selection.d.ts +2 -8
  168. package/dist/hooks/use-product-variation-selection.d.ts.map +1 -1
  169. package/dist/hooks/use-product-variation-selection.js +1 -1
  170. package/dist/hooks/use-products-api.js +3 -3
  171. package/dist/hooks/use-shipping-quote.js +2 -2
  172. package/dist/hooks/use-store-api.js +4 -4
  173. package/dist/hooks/usePreviewSlideSync.js +1 -1
  174. package/dist/index.d.ts +62 -74
  175. package/dist/index.d.ts.map +1 -1
  176. package/dist/index.js +141 -153
  177. package/dist/layouts/booking/components/BookingHomePage.d.ts.map +1 -1
  178. package/dist/layouts/booking/components/BookingHomePage.js +68 -74
  179. package/dist/layouts/booking/components/BookingMultilineHeading.js +1 -1
  180. package/dist/layouts/booking/components/BookingTestimonialCard.js +2 -2
  181. package/dist/layouts/booking/components/BookingTestimonialsEmptyState.js +1 -1
  182. package/dist/layouts/booking/components/booking-footer-defaults.js +2 -2
  183. package/dist/layouts/booking/index.js +8 -8
  184. package/dist/layouts/booking/pages/AboutPage.d.ts +1 -1
  185. package/dist/layouts/booking/pages/AboutPage.d.ts.map +1 -1
  186. package/dist/layouts/booking/pages/AboutPage.js +39 -60
  187. package/dist/layouts/booking/pages/BookPage.d.ts.map +1 -1
  188. package/dist/layouts/booking/pages/BookPage.js +16 -22
  189. package/dist/layouts/booking/pages/CategoriesPage.js +6 -6
  190. package/dist/layouts/booking/pages/CategoryPage.d.ts +1 -1
  191. package/dist/layouts/booking/pages/CategoryPage.d.ts.map +1 -1
  192. package/dist/layouts/booking/pages/CategoryPage.js +17 -26
  193. package/dist/layouts/booking/pages/ContactPage.d.ts +1 -1
  194. package/dist/layouts/booking/pages/ContactPage.d.ts.map +1 -1
  195. package/dist/layouts/booking/pages/ContactPage.js +5 -12
  196. package/dist/layouts/booking/pages/ProductDetailPage.d.ts +1 -1
  197. package/dist/layouts/booking/pages/ProductDetailPage.d.ts.map +1 -1
  198. package/dist/layouts/booking/pages/ProductDetailPage.js +203 -150
  199. package/dist/layouts/booking/pages/ServicesPage.d.ts +1 -1
  200. package/dist/layouts/booking/pages/ServicesPage.d.ts.map +1 -1
  201. package/dist/layouts/booking/pages/ServicesPage.js +11 -31
  202. package/dist/layouts/booking-agenda/components/BookingHomePageAgenda.d.ts.map +1 -1
  203. package/dist/layouts/booking-agenda/components/BookingHomePageAgenda.js +21 -30
  204. package/dist/layouts/booking-agenda/index.js +8 -8
  205. package/dist/layouts/booking-agenda/pages/AboutPage.d.ts +1 -1
  206. package/dist/layouts/booking-agenda/pages/AboutPage.d.ts.map +1 -1
  207. package/dist/layouts/booking-agenda/pages/AboutPage.js +11 -14
  208. package/dist/layouts/booking-agenda/pages/BookPage.d.ts.map +1 -1
  209. package/dist/layouts/booking-agenda/pages/BookPage.js +15 -21
  210. package/dist/layouts/booking-agenda/pages/CategoriesPage.js +5 -5
  211. package/dist/layouts/booking-agenda/pages/CategoryPage.d.ts +1 -1
  212. package/dist/layouts/booking-agenda/pages/CategoryPage.d.ts.map +1 -1
  213. package/dist/layouts/booking-agenda/pages/CategoryPage.js +12 -18
  214. package/dist/layouts/booking-agenda/pages/ContactPage.d.ts +1 -1
  215. package/dist/layouts/booking-agenda/pages/ContactPage.d.ts.map +1 -1
  216. package/dist/layouts/booking-agenda/pages/ContactPage.js +8 -13
  217. package/dist/layouts/booking-agenda/pages/ProductDetailPage.d.ts +1 -1
  218. package/dist/layouts/booking-agenda/pages/ProductDetailPage.d.ts.map +1 -1
  219. package/dist/layouts/booking-agenda/pages/ProductDetailPage.js +202 -149
  220. package/dist/layouts/booking-agenda/pages/ServicesPage.d.ts +1 -1
  221. package/dist/layouts/booking-agenda/pages/ServicesPage.d.ts.map +1 -1
  222. package/dist/layouts/booking-agenda/pages/ServicesPage.js +8 -23
  223. package/dist/layouts/clothing/components/ClothingHomePage.d.ts.map +1 -1
  224. package/dist/layouts/clothing/components/ClothingHomePage.js +199 -136
  225. package/dist/layouts/clothing/index.js +7 -7
  226. package/dist/layouts/clothing/pages/AboutPage.d.ts +1 -1
  227. package/dist/layouts/clothing/pages/AboutPage.d.ts.map +1 -1
  228. package/dist/layouts/clothing/pages/AboutPage.js +19 -33
  229. package/dist/layouts/clothing/pages/CategoriesPage.js +8 -8
  230. package/dist/layouts/clothing/pages/CategoryPage.d.ts +1 -1
  231. package/dist/layouts/clothing/pages/CategoryPage.d.ts.map +1 -1
  232. package/dist/layouts/clothing/pages/CategoryPage.js +15 -41
  233. package/dist/layouts/clothing/pages/ContactPage.d.ts +1 -1
  234. package/dist/layouts/clothing/pages/ContactPage.d.ts.map +1 -1
  235. package/dist/layouts/clothing/pages/ContactPage.js +5 -8
  236. package/dist/layouts/clothing/pages/ProductDetailPage.d.ts +1 -1
  237. package/dist/layouts/clothing/pages/ProductDetailPage.d.ts.map +1 -1
  238. package/dist/layouts/clothing/pages/ProductDetailPage.js +256 -167
  239. package/dist/layouts/clothing/pages/ProductsPage.d.ts +1 -1
  240. package/dist/layouts/clothing/pages/ProductsPage.d.ts.map +1 -1
  241. package/dist/layouts/clothing/pages/ProductsPage.js +125 -59
  242. package/dist/layouts/clothing/payaza-bindings.js +2 -2
  243. package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.d.ts.map +1 -1
  244. package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.js +32 -131
  245. package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.js +8 -8
  246. package/dist/layouts/clothing-minimal/index.js +7 -7
  247. package/dist/layouts/clothing-minimal/pages/AboutPage.d.ts +1 -1
  248. package/dist/layouts/clothing-minimal/pages/AboutPage.d.ts.map +1 -1
  249. package/dist/layouts/clothing-minimal/pages/AboutPage.js +37 -61
  250. package/dist/layouts/clothing-minimal/pages/CategoriesPage.js +7 -7
  251. package/dist/layouts/clothing-minimal/pages/CategoryPage.d.ts +1 -1
  252. package/dist/layouts/clothing-minimal/pages/CategoryPage.d.ts.map +1 -1
  253. package/dist/layouts/clothing-minimal/pages/CategoryPage.js +13 -18
  254. package/dist/layouts/clothing-minimal/pages/ContactPage.d.ts +1 -1
  255. package/dist/layouts/clothing-minimal/pages/ContactPage.d.ts.map +1 -1
  256. package/dist/layouts/clothing-minimal/pages/ContactPage.js +5 -8
  257. package/dist/layouts/clothing-minimal/pages/ProductDetailPage.d.ts +1 -1
  258. package/dist/layouts/clothing-minimal/pages/ProductDetailPage.d.ts.map +1 -1
  259. package/dist/layouts/clothing-minimal/pages/ProductDetailPage.js +202 -143
  260. package/dist/layouts/clothing-minimal/pages/ProductsPage.d.ts +1 -1
  261. package/dist/layouts/clothing-minimal/pages/ProductsPage.d.ts.map +1 -1
  262. package/dist/layouts/clothing-minimal/pages/ProductsPage.js +126 -57
  263. package/dist/layouts/electronics/components/ElectronicsCategoriesPage.js +7 -7
  264. package/dist/layouts/electronics/components/ElectronicsGridProductCard.js +10 -10
  265. package/dist/layouts/electronics/components/ElectronicsHomePage.d.ts.map +1 -1
  266. package/dist/layouts/electronics/components/ElectronicsHomePage.js +29 -31
  267. package/dist/layouts/electronics/components/ElectronicsPageWrapper.js +4 -4
  268. package/dist/layouts/electronics/components/ElectronicsProductsPage.d.ts.map +1 -1
  269. package/dist/layouts/electronics/components/ElectronicsProductsPage.js +81 -23
  270. package/dist/layouts/electronics/components/ElectronicsStoreFooter.js +2 -2
  271. package/dist/layouts/electronics/components/ElectronicsStoreHeader.js +6 -6
  272. package/dist/layouts/electronics/index.js +18 -18
  273. package/dist/layouts/electronics/pages/AboutPage.d.ts +1 -1
  274. package/dist/layouts/electronics/pages/AboutPage.d.ts.map +1 -1
  275. package/dist/layouts/electronics/pages/AboutPage.js +11 -14
  276. package/dist/layouts/electronics/pages/CategoriesPage.js +1 -1
  277. package/dist/layouts/electronics/pages/CategoryPage.d.ts +1 -1
  278. package/dist/layouts/electronics/pages/CategoryPage.d.ts.map +1 -1
  279. package/dist/layouts/electronics/pages/CategoryPage.js +14 -20
  280. package/dist/layouts/electronics/pages/ContactPage.d.ts +1 -1
  281. package/dist/layouts/electronics/pages/ContactPage.d.ts.map +1 -1
  282. package/dist/layouts/electronics/pages/ContactPage.js +8 -13
  283. package/dist/layouts/electronics/pages/HelpCenterPage.js +1 -1
  284. package/dist/layouts/electronics/pages/PrivacyPolicyPage.js +1 -1
  285. package/dist/layouts/electronics/pages/ProductDetailPage.d.ts +1 -1
  286. package/dist/layouts/electronics/pages/ProductDetailPage.d.ts.map +1 -1
  287. package/dist/layouts/electronics/pages/ProductDetailPage.js +201 -142
  288. package/dist/layouts/electronics/pages/ProductsPage.d.ts +1 -1
  289. package/dist/layouts/electronics/pages/ProductsPage.d.ts.map +1 -1
  290. package/dist/layouts/electronics/pages/ProductsPage.js +119 -49
  291. package/dist/layouts/electronics/pages/TermsPage.js +1 -1
  292. package/dist/layouts/electronics/pages/TrackOrderPage.js +4 -4
  293. package/dist/layouts/electronics-grid/components/ElectronicsGridProductCard.js +10 -10
  294. package/dist/layouts/electronics-grid/components/ElectronicsHomePageGrid.d.ts.map +1 -1
  295. package/dist/layouts/electronics-grid/components/ElectronicsHomePageGrid.js +75 -33
  296. package/dist/layouts/electronics-grid/index.js +14 -14
  297. package/dist/layouts/electronics-grid/pages/AboutPage.d.ts +1 -1
  298. package/dist/layouts/electronics-grid/pages/AboutPage.d.ts.map +1 -1
  299. package/dist/layouts/electronics-grid/pages/AboutPage.js +11 -14
  300. package/dist/layouts/electronics-grid/pages/CategoriesPage.js +1 -1
  301. package/dist/layouts/electronics-grid/pages/CategoryPage.d.ts +1 -1
  302. package/dist/layouts/electronics-grid/pages/CategoryPage.d.ts.map +1 -1
  303. package/dist/layouts/electronics-grid/pages/CategoryPage.js +14 -20
  304. package/dist/layouts/electronics-grid/pages/ContactPage.d.ts +1 -1
  305. package/dist/layouts/electronics-grid/pages/ContactPage.d.ts.map +1 -1
  306. package/dist/layouts/electronics-grid/pages/ContactPage.js +8 -13
  307. package/dist/layouts/electronics-grid/pages/ElectronicsGridCategoriesPage.js +6 -6
  308. package/dist/layouts/electronics-grid/pages/HelpCenterPage.js +1 -1
  309. package/dist/layouts/electronics-grid/pages/PrivacyPolicyPage.js +1 -1
  310. package/dist/layouts/electronics-grid/pages/ProductDetailPage.d.ts +1 -1
  311. package/dist/layouts/electronics-grid/pages/ProductDetailPage.d.ts.map +1 -1
  312. package/dist/layouts/electronics-grid/pages/ProductDetailPage.js +201 -142
  313. package/dist/layouts/electronics-grid/pages/ProductsPage.d.ts +1 -1
  314. package/dist/layouts/electronics-grid/pages/ProductsPage.d.ts.map +1 -1
  315. package/dist/layouts/electronics-grid/pages/ProductsPage.js +121 -52
  316. package/dist/layouts/electronics-grid/pages/TermsPage.js +1 -1
  317. package/dist/layouts/electronics-grid/pages/TrackOrderPage.js +4 -4
  318. package/dist/layouts/food/components/FoodHomePage.d.ts.map +1 -1
  319. package/dist/layouts/food/components/FoodHomePage.js +42 -41
  320. package/dist/layouts/food/index.js +8 -8
  321. package/dist/layouts/food/pages/AboutPage.d.ts +1 -1
  322. package/dist/layouts/food/pages/AboutPage.d.ts.map +1 -1
  323. package/dist/layouts/food/pages/AboutPage.js +44 -45
  324. package/dist/layouts/food/pages/CategoriesPage.js +6 -6
  325. package/dist/layouts/food/pages/CategoryPage.d.ts +1 -1
  326. package/dist/layouts/food/pages/CategoryPage.d.ts.map +1 -1
  327. package/dist/layouts/food/pages/CategoryPage.js +23 -21
  328. package/dist/layouts/food/pages/ContactPage.d.ts +1 -1
  329. package/dist/layouts/food/pages/ContactPage.d.ts.map +1 -1
  330. package/dist/layouts/food/pages/ContactPage.js +5 -7
  331. package/dist/layouts/food/pages/MenuPage.d.ts +1 -1
  332. package/dist/layouts/food/pages/MenuPage.d.ts.map +1 -1
  333. package/dist/layouts/food/pages/MenuPage.js +81 -42
  334. package/dist/layouts/food/pages/ProductDetailPage.d.ts +1 -1
  335. package/dist/layouts/food/pages/ProductDetailPage.d.ts.map +1 -1
  336. package/dist/layouts/food/pages/ProductDetailPage.js +209 -171
  337. package/dist/layouts/food/pages/ProductsPage.d.ts +1 -1
  338. package/dist/layouts/food/pages/ProductsPage.d.ts.map +1 -1
  339. package/dist/layouts/food/pages/ProductsPage.js +130 -45
  340. package/dist/layouts/food-modern/components/FoodHomePageModern.js +10 -10
  341. package/dist/layouts/food-modern/index.js +8 -8
  342. package/dist/layouts/food-modern/pages/AboutPage.d.ts +1 -1
  343. package/dist/layouts/food-modern/pages/AboutPage.d.ts.map +1 -1
  344. package/dist/layouts/food-modern/pages/AboutPage.js +11 -14
  345. package/dist/layouts/food-modern/pages/CategoriesPage.js +5 -5
  346. package/dist/layouts/food-modern/pages/CategoryPage.d.ts +1 -1
  347. package/dist/layouts/food-modern/pages/CategoryPage.d.ts.map +1 -1
  348. package/dist/layouts/food-modern/pages/CategoryPage.js +23 -20
  349. package/dist/layouts/food-modern/pages/ContactPage.d.ts +1 -1
  350. package/dist/layouts/food-modern/pages/ContactPage.d.ts.map +1 -1
  351. package/dist/layouts/food-modern/pages/ContactPage.js +5 -7
  352. package/dist/layouts/food-modern/pages/MenuPage.d.ts +1 -1
  353. package/dist/layouts/food-modern/pages/MenuPage.d.ts.map +1 -1
  354. package/dist/layouts/food-modern/pages/MenuPage.js +81 -46
  355. package/dist/layouts/food-modern/pages/ProductDetailPage.d.ts +1 -1
  356. package/dist/layouts/food-modern/pages/ProductDetailPage.d.ts.map +1 -1
  357. package/dist/layouts/food-modern/pages/ProductDetailPage.js +202 -143
  358. package/dist/layouts/food-modern/pages/ProductsPage.d.ts +1 -1
  359. package/dist/layouts/food-modern/pages/ProductsPage.d.ts.map +1 -1
  360. package/dist/layouts/food-modern/pages/ProductsPage.js +117 -33
  361. package/dist/layouts/motivational-speaker/components/MotivationalHomePage.d.ts.map +1 -1
  362. package/dist/layouts/motivational-speaker/components/MotivationalHomePage.js +32 -23
  363. package/dist/layouts/motivational-speaker/components/MotivationalSpeakerPageWrapper.js +1 -1
  364. package/dist/layouts/motivational-speaker/index.js +9 -9
  365. package/dist/layouts/motivational-speaker/pages/AboutPage.d.ts +1 -1
  366. package/dist/layouts/motivational-speaker/pages/AboutPage.d.ts.map +1 -1
  367. package/dist/layouts/motivational-speaker/pages/AboutPage.js +6 -9
  368. package/dist/layouts/motivational-speaker/pages/CategoryPage.d.ts +1 -1
  369. package/dist/layouts/motivational-speaker/pages/CategoryPage.d.ts.map +1 -1
  370. package/dist/layouts/motivational-speaker/pages/CategoryPage.js +18 -22
  371. package/dist/layouts/motivational-speaker/pages/ContactPage.d.ts +1 -1
  372. package/dist/layouts/motivational-speaker/pages/ContactPage.d.ts.map +1 -1
  373. package/dist/layouts/motivational-speaker/pages/ContactPage.js +9 -13
  374. package/dist/layouts/motivational-speaker/pages/ProductsPage.d.ts +1 -1
  375. package/dist/layouts/motivational-speaker/pages/ProductsPage.d.ts.map +1 -1
  376. package/dist/layouts/motivational-speaker/pages/ProductsPage.js +121 -52
  377. package/dist/layouts/motivational-speaker/pages/ServiceDetailPage.d.ts +1 -1
  378. package/dist/layouts/motivational-speaker/pages/ServiceDetailPage.d.ts.map +1 -1
  379. package/dist/layouts/motivational-speaker/pages/ServiceDetailPage.js +15 -24
  380. package/dist/layouts/motivational-speaker/pages/ServicesPage.d.ts +1 -1
  381. package/dist/layouts/motivational-speaker/pages/ServicesPage.d.ts.map +1 -1
  382. package/dist/layouts/motivational-speaker/pages/ServicesPage.js +8 -23
  383. package/dist/layouts/motivational-speaker/pages/SubscriptionPage.js +7 -7
  384. package/dist/layouts/shared/components/BaseStoreLayout.js +9 -9
  385. package/dist/layouts/shared/components/CartLineQuantityControl.js +1 -1
  386. package/dist/layouts/shared/components/GenericPageWrapper.js +3 -3
  387. package/dist/layouts/shared/components/ProductPriceFilterSection.js +4 -4
  388. package/dist/layouts/shared/components/PromoBanner.d.ts +1 -4
  389. package/dist/layouts/shared/components/PromoBanner.d.ts.map +1 -1
  390. package/dist/layouts/shared/components/PromoBanner.js +12 -8
  391. package/dist/layouts/shared/components/StoreFooter.js +10 -10
  392. package/dist/layouts/shared/components/StoreHeader.js +7 -7
  393. package/dist/layouts/shared/components/TestimonialCard.d.ts.map +1 -1
  394. package/dist/layouts/shared/components/TestimonialCard.js +2 -2
  395. package/dist/layouts/shared/components/headers/BookingAgendaSidebar.js +2 -2
  396. package/dist/layouts/shared/components/headers/ClothingMinimalHeader.js +11 -11
  397. package/dist/layouts/shared/components/headers/FoodModernHeader.js +7 -7
  398. package/dist/layouts/shared/index.js +22 -22
  399. package/dist/layouts/shared/pages/AccountPage.d.ts.map +1 -1
  400. package/dist/layouts/shared/pages/AccountPage.js +101 -27
  401. package/dist/layouts/shared/pages/CartPage.d.ts.map +1 -1
  402. package/dist/layouts/shared/pages/CartPage.js +39 -90
  403. package/dist/layouts/shared/pages/CheckoutPage.d.ts.map +1 -1
  404. package/dist/layouts/shared/pages/CheckoutPage.js +349 -868
  405. package/dist/layouts/shared/pages/CookiePolicyPage.js +1 -1
  406. package/dist/layouts/shared/pages/FeatureDisabledPage.js +1 -1
  407. package/dist/layouts/shared/pages/HelpCenterPage.js +1 -1
  408. package/dist/layouts/shared/pages/MaintenancePage.d.ts.map +1 -1
  409. package/dist/layouts/shared/pages/MaintenancePage.js +2 -3
  410. package/dist/layouts/shared/pages/PortfolioPage.d.ts.map +1 -1
  411. package/dist/layouts/shared/pages/PortfolioPage.js +2 -2
  412. package/dist/layouts/shared/pages/PrivacyPolicyPage.js +1 -1
  413. package/dist/layouts/shared/pages/SizeGuidePage.js +1 -1
  414. package/dist/layouts/shared/pages/StyleGuidePage.js +3 -3
  415. package/dist/layouts/shared/pages/TeamPage.d.ts.map +1 -1
  416. package/dist/layouts/shared/pages/TeamPage.js +1 -1
  417. package/dist/layouts/shared/pages/TermsPage.js +1 -1
  418. package/dist/layouts/shared/pages/TrackOrderPage.js +4 -4
  419. package/dist/layouts/shared/pages/WishlistPage.d.ts.map +1 -1
  420. package/dist/layouts/shared/pages/WishlistPage.js +44 -29
  421. package/dist/layouts/shared/wrappers/BookingAgendaLayout.d.ts.map +1 -1
  422. package/dist/layouts/shared/wrappers/BookingAgendaLayout.js +80 -35
  423. package/dist/layouts/shared/wrappers/ClothingLayout.js +8 -8
  424. package/dist/layouts/shared/wrappers/ClothingMinimalLayout.js +7 -7
  425. package/dist/layouts/shared/wrappers/ElectronicsLayout.js +5 -5
  426. package/dist/layouts/shared/wrappers/FoodModernLayout.js +6 -6
  427. package/dist/layouts/shared/wrappers/MotivationalLayout.js +9 -9
  428. package/dist/layouts/shared/wrappers/StandardLayout.js +8 -8
  429. package/dist/lib/api.d.ts +3 -4
  430. package/dist/lib/api.d.ts.map +1 -1
  431. package/dist/lib/api.js +69 -109
  432. package/dist/lib/auth-context.js +2 -2
  433. package/dist/lib/content-selectors.d.ts +1 -1
  434. package/dist/lib/content-selectors.d.ts.map +1 -1
  435. package/dist/lib/content-selectors.js +8 -0
  436. package/dist/lib/layout-config-hydration.js +2 -2
  437. package/dist/lib/loading-context.d.ts.map +1 -1
  438. package/dist/lib/loading-context.js +11 -21
  439. package/dist/lib/payaza-checkout.d.ts.map +1 -1
  440. package/dist/lib/payaza-checkout.js +2 -4
  441. package/dist/lib/payaza-public-key.d.ts +6 -0
  442. package/dist/lib/payaza-public-key.d.ts.map +1 -0
  443. package/dist/lib/payaza-public-key.js +10 -0
  444. package/dist/lib/rich-html.d.ts +31 -26
  445. package/dist/lib/rich-html.d.ts.map +1 -1
  446. package/dist/lib/rich-html.js +1 -0
  447. package/dist/lib/services/analytics.service.js +1 -1
  448. package/dist/lib/services/auth.service.js +1 -1
  449. package/dist/lib/services/booking.service.js +1 -1
  450. package/dist/lib/services/category.service.js +1 -1
  451. package/dist/lib/services/checkout.service.d.ts +3 -0
  452. package/dist/lib/services/checkout.service.d.ts.map +1 -1
  453. package/dist/lib/services/checkout.service.js +1 -1
  454. package/dist/lib/services/contact.service.js +1 -1
  455. package/dist/lib/services/customer.service.js +1 -1
  456. package/dist/lib/services/fees.service.d.ts +17 -63
  457. package/dist/lib/services/fees.service.d.ts.map +1 -1
  458. package/dist/lib/services/fees.service.js +4 -138
  459. package/dist/lib/services/index.js +13 -13
  460. package/dist/lib/services/location.service.js +1 -1
  461. package/dist/lib/services/order.service.js +1 -1
  462. package/dist/lib/services/payment.service.js +1 -1
  463. package/dist/lib/services/product.service.d.ts +7 -2
  464. package/dist/lib/services/product.service.d.ts.map +1 -1
  465. package/dist/lib/services/product.service.js +13 -1
  466. package/dist/lib/services/promo.service.js +1 -1
  467. package/dist/lib/services/review.service.js +1 -1
  468. package/dist/lib/services/service.service.js +1 -1
  469. package/dist/lib/services/shipping.service.js +1 -1
  470. package/dist/lib/services/store.service.js +1 -1
  471. package/dist/lib/store-config-utils.d.ts +1 -10
  472. package/dist/lib/store-config-utils.d.ts.map +1 -1
  473. package/dist/lib/store-config-utils.js +73 -261
  474. package/dist/lib/store-config.js +1 -1
  475. package/dist/lib/store-context.d.ts +1 -23
  476. package/dist/lib/store-context.d.ts.map +1 -1
  477. package/dist/lib/store-context.js +89 -111
  478. package/dist/lib/store-types.d.ts +9 -91
  479. package/dist/lib/store-types.d.ts.map +1 -1
  480. package/dist/lib/utils/asset-helpers.js +1 -1
  481. package/dist/lib/utils/catalog-price-bounds.d.ts +16 -6
  482. package/dist/lib/utils/catalog-price-bounds.d.ts.map +1 -1
  483. package/dist/lib/utils/catalog-price-bounds.js +58 -14
  484. package/dist/lib/utils/category-tree.d.ts +9 -0
  485. package/dist/lib/utils/category-tree.d.ts.map +1 -1
  486. package/dist/lib/utils/category-tree.js +26 -0
  487. package/dist/lib/utils/editor-preview.d.ts +5 -0
  488. package/dist/lib/utils/editor-preview.d.ts.map +1 -1
  489. package/dist/lib/utils/editor-preview.js +17 -3
  490. package/dist/lib/utils/fee-calculations.d.ts +5 -1
  491. package/dist/lib/utils/fee-calculations.d.ts.map +1 -1
  492. package/dist/lib/utils/fee-calculations.js +28 -31
  493. package/dist/lib/utils/index.d.ts +1 -1
  494. package/dist/lib/utils/index.d.ts.map +1 -1
  495. package/dist/lib/utils/index.js +4 -4
  496. package/dist/lib/utils/product-images.js +1 -1
  497. package/dist/lib/utils/product-share.js +1 -1
  498. package/dist/lib/utils/url-helpers.d.ts +6 -0
  499. package/dist/lib/utils/url-helpers.d.ts.map +1 -1
  500. package/dist/lib/utils/url-helpers.js +20 -4
  501. package/dist/preview/LayoutPreview.js +6 -6
  502. package/dist/preview/PreviewRouter.js +4 -4
  503. package/dist/preview/canvas/PreviewCanvasEnhancer.js +8 -8
  504. package/dist/preview/canvas/preview-flush-await.js +1 -1
  505. package/dist/preview/canvas/preview-isolation.js +1 -1
  506. package/dist/preview/canvas/preview-route.js +2 -2
  507. package/dist/preview/index.js +5 -5
  508. package/dist/styles/index.css +190 -149
  509. package/package.json +31 -43
@@ -1,99 +1,36 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { Button } from '../../../components/ui/button.js';
4
- import { ArrowLeft, ShieldCheck, CheckCircle, MapPin, Lock, ChevronRight, Tag, Truck, ChevronDown, Loader2, AlertCircle } from 'lucide-react';
3
+ import { Button } from '../../../components/ui/button';
4
+ import { ArrowLeft, ShieldCheck, CheckCircle, MapPin, Lock, ChevronRight, Tag, Truck, ChevronDown } from 'lucide-react';
5
5
  import Link from 'next/link';
6
6
  import Image from 'next/image';
7
- import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
8
- import { cn } from '../../../lib/utils/index.js';
9
- import { usePayazaCheckout } from '../../../hooks/use-payaza-checkout.js';
10
- import { generateCheckoutConfig } from '../../../lib/payaza-checkout.js';
11
- import { useToast } from '../../../components/ui/toast.js';
12
- import { useStore } from '../../../lib/store-context.js';
7
+ import { useState, useEffect, useRef } from 'react';
8
+ import { cn } from '../../../lib/utils';
9
+ import { usePayazaCheckout } from '../../../hooks/use-payaza-checkout';
10
+ import { generateCheckoutConfig } from '../../../lib/payaza-checkout';
11
+ import { requireCheckoutPayazaPublicKey } from '../../../lib/payaza-public-key';
12
+ import { useToast } from '../../../components/ui/toast';
13
+ import { useStore } from '../../../lib/store-context';
13
14
  import { useRouter } from 'next/navigation';
14
- import { normalizePrice, formatCurrency, getUserFriendlyErrorMessage } from '../../../lib/utils/index.js';
15
- import { checkoutService } from '../../../lib/services/checkout.service.js';
16
- import { orderService } from '../../../lib/services/order.service.js';
17
- import { paymentService, isPaymentConfirmed } from '../../../lib/services/payment.service.js';
18
- import { ApiError } from '../../../lib/api.js';
19
- import { useAuth } from '../../../lib/auth-context.js';
20
- import { customerService } from '../../../lib/services/customer.service.js';
21
- import { AddressSelector } from '../../../components/ui/address-selector.js';
22
- import { countries, getCountryByName, getCitiesByCountry, getNigerianCitiesByState } from '../../../lib/countries.js';
23
- import { useCheckoutFees } from '../../../hooks/use-checkout-fees.js';
24
- import { useMultiStoreCheckoutFees } from '../../../hooks/use-multi-store-checkout-fees.js';
25
- import { CheckoutFeeLines } from '../../../components/checkout/CheckoutFeeLines.js';
26
- import { shippingService } from '../../../lib/services/shipping.service.js';
27
- import { CHECKOUT_UNAVAILABLE_MESSAGE } from '../../../lib/checkout-availability.js';
28
- import { getCartLineImage } from '../../../lib/product-variation-utils.js';
29
- import { enrichShippingQuoteAddress, buildShippingCalculatePayload, canRequestShippingQuote, isNigeria, deriveNigerianStateFromCity, NIGERIAN_STATES } from '../../../lib/shipping-address.util.js';
30
- import { ShippingOptionsPanel } from '../../../components/checkout/ShippingOptionsPanel.js';
31
- import { CheckoutSkeleton } from '../../../components/ui/skeletons/checkout-skeleton.js';
32
- const CHECKOUT_ITEM_UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
33
- function normalizeCheckoutPayloadItem(item) {
34
- const product = item.product;
35
- return {
36
- productId: item.productId,
37
- storeId: product?.storeId,
38
- quantity: Number(item.quantity) || 1,
39
- price: normalizePrice(item.price),
40
- variationId: item.variantId && CHECKOUT_ITEM_UUID_REGEX.test(item.variantId)
41
- ? item.variantId
42
- : undefined,
43
- variantId: item.variantId ?? undefined,
44
- };
45
- }
46
- import { useAnalytics } from '../../../hooks/use-analytics.js';
47
- import { getOrCreateIdempotencyKey, resetIdempotencyKey } from '../../../lib/utils/idempotency-key.js';
48
- import { formatPhoneInput, validatePhone, getPhonePlaceholder, normalizePhoneForPayaza } from '../../../lib/utils/phone-format.js';
49
- import { saveGuestUserInfo } from '../../../lib/guest-user.js';
50
- import { InventoryHoldCountdown } from '../../../components/cart/InventoryHoldCountdown.js';
51
- import { formatCheckoutStockError, getCartLineStockInfo, isInventoryHoldExpired } from '../../../lib/inventory-utils.js';
52
- import { MerchantHandledDeliveryNotice } from '../../../components/shipping/MerchantHandledDeliveryNotice.js';
53
- import { isAutomatedShippingEnabled } from '../../../lib/shipping-settings.util.js';
54
- import { setOrderSuccessJustCompleted, isOrderSuccessPending } from '../../../lib/order-success-cart.js';
55
- import { trackStorefrontEvent } from '../../../lib/mixpanel.js';
56
- const PROMO_CODE_ENABLED = false;
57
- function isPaymentInventoryError(error) {
58
- if (!error || typeof error !== 'object')
59
- return false;
60
- const candidate = error;
61
- if (candidate.status === 409)
62
- return true;
63
- const message = (candidate.message || '').toLowerCase();
64
- return (message.includes('insufficient stock') ||
65
- message.includes('insufficient inventory') ||
66
- message.includes('out of stock'));
67
- }
68
- function buildOrderSuccessUrl(storeSlug, params) {
69
- const search = new URLSearchParams({
70
- orderId: params.orderId,
71
- ref: params.ref,
72
- });
73
- if (params.orderNumber)
74
- search.set('orderNumber', params.orderNumber);
75
- if (params.email)
76
- search.set('email', params.email);
77
- if (params.fulfillmentPending)
78
- search.set('fulfillmentPending', 'true');
79
- return `/${storeSlug}/order/success?${search.toString()}`;
80
- }
81
- function RequiredLabel({ children }) {
82
- return (_jsxs("label", { className: "text-sm font-bold text-gray-700 ml-1", children: [children, " ", _jsx("span", { className: "text-red-500", children: "*" })] }));
83
- }
15
+ import { normalizePrice, formatCurrency } from '../../../lib/utils';
16
+ import { checkoutService } from '../../../lib/services/checkout.service';
17
+ import { paymentService } from '../../../lib/services/payment.service';
18
+ import { useAuth } from '../../../lib/auth-context';
19
+ import { customerService } from '../../../lib/services/customer.service';
20
+ import { AddressSelector } from '../../../components/ui/address-selector';
21
+ import { countries, getCountryByName, getCitiesByCountry } from '../../../lib/countries';
22
+ import { calculateVAT, getVATLabel } from '../../../lib/utils/fee-calculations';
23
+ import { shippingService } from '../../../lib/services/shipping.service';
24
+ import { feesService } from '../../../lib/services/fees.service';
25
+ import { CheckoutSkeleton } from '../../../components/ui/skeletons/checkout-skeleton';
26
+ import { useAnalytics } from '../../../hooks/use-analytics';
84
27
  export function CheckoutPage({ storeConfig }) {
85
28
  const primaryColor = storeConfig.branding.primaryColor;
86
- const checkoutDisabled = storeConfig.checkoutEnabled === false;
87
- const { cart, cartTotal, buyNowItem, clearBuyNowItem, setBuyNowItem, inventoryHoldUntil, setInventoryHoldUntil, handleInventoryHoldExpired, isCartRefreshing, refreshCart, finalizeCartAfterOrder, removeOrderItemsFromCart, isCartHydrated } = useStore();
29
+ const { cart, cartTotal, buyNowItem, clearBuyNowItem, setBuyNowItem } = useStore();
88
30
  const { addToast } = useToast();
89
31
  const router = useRouter();
90
32
  const { user, isAuthenticated } = useAuth();
91
33
  const { trackEvent } = useAnalytics();
92
- const [isHoldExpired, setIsHoldExpired] = useState(false);
93
- useEffect(() => {
94
- setIsHoldExpired(isInventoryHoldExpired(inventoryHoldUntil));
95
- }, [inventoryHoldUntil]);
96
- const isHoldBlocking = !!inventoryHoldUntil && (isHoldExpired || isInventoryHoldExpired(inventoryHoldUntil));
97
34
  // Read from sessionStorage synchronously on mount if buyNowItem is not in context
98
35
  const [sessionBuyNowItem] = useState(() => {
99
36
  if (typeof window === 'undefined')
@@ -109,215 +46,47 @@ export function CheckoutPage({ storeConfig }) {
109
46
  }
110
47
  return null;
111
48
  });
112
- // State to track buy-now restoration from sessionStorage
49
+ // State to track initialization
50
+ const [isInitializing, setIsInitializing] = useState(true);
113
51
  const hasRestoredRef = useRef(false);
114
52
  // Restore buyNowItem from sessionStorage to context if needed (only once on mount)
115
53
  useEffect(() => {
54
+ // Only run restoration once
116
55
  if (hasRestoredRef.current) {
117
56
  return;
118
57
  }
119
58
  hasRestoredRef.current = true;
120
- if (!buyNowItem && sessionBuyNowItem) {
59
+ // If item is already in context, we're done
60
+ if (buyNowItem || sessionBuyNowItem) {
61
+ setIsInitializing(false);
62
+ return;
63
+ }
64
+ // If we have sessionBuyNowItem but not in context, restore it
65
+ if (sessionBuyNowItem) {
121
66
  setBuyNowItem(sessionBuyNowItem);
122
67
  }
68
+ // Add a small delay to ensure state propagation
69
+ const timer = setTimeout(() => {
70
+ setIsInitializing(false);
71
+ }, 150);
72
+ return () => clearTimeout(timer);
123
73
  // eslint-disable-next-line react-hooks/exhaustive-deps
124
- }, []);
125
- // Ensure cart is loaded before rendering checkout (API-backed stores)
74
+ }, []); // Only run once on mount
75
+ // Stop initializing if buyNowItem becomes available after mount
126
76
  useEffect(() => {
127
- if (!isCartHydrated) {
128
- void refreshCart?.();
77
+ if (buyNowItem && isInitializing) {
78
+ setIsInitializing(false);
129
79
  }
130
- }, [isCartHydrated, refreshCart]);
80
+ }, [buyNowItem, isInitializing]);
131
81
  // Use buyNowItem from context or sessionStorage, otherwise use cart
132
82
  const effectiveBuyNowItem = buyNowItem || sessionBuyNowItem;
133
83
  const checkoutItems = effectiveBuyNowItem ? [effectiveBuyNowItem] : cart;
134
- const isMarketplaceCheckout = storeConfig.layout === 'marketplace' || storeConfig.slug === 'marketplace';
135
- // Group checkout items by their real storeId regardless of context (marketplace or not) — a
136
- // cart can end up spanning more than one store, and fee calculation needs to be done per store
137
- // group either way. For marketplace checkouts specifically, the backend also creates one Order
138
- // (and settles one Payment) per store even though the customer pays once — group the summary
139
- // the same way so the customer sees this before submitting, mirroring Amazon's "split into N
140
- // shipments" pattern.
141
- const storeBreakdown = useMemo(() => {
142
- const groups = new Map();
143
- for (const item of checkoutItems) {
144
- const product = item.product;
145
- const storeId = product?.storeId || storeConfig.id;
146
- const storeName = product?.storeName || `Store ${storeId.slice(0, 8)}`;
147
- const existing = groups.get(storeId) || { storeId, storeName, subtotal: 0, itemCount: 0 };
148
- existing.subtotal += normalizePrice(item.price) * item.quantity;
149
- existing.itemCount += item.quantity;
150
- groups.set(storeId, existing);
151
- }
152
- return Array.from(groups.values());
153
- }, [checkoutItems, storeConfig.id]);
154
- // Fees must be split per store whenever the cart spans more than one — this isn't limited to
155
- // the "marketplace" virtual storefront, it's whatever the item data says.
156
- const isMultiStoreCheckout = storeBreakdown.length > 1;
157
- const checkoutStockIssues = useMemo(() => {
158
- return checkoutItems
159
- .map((item) => {
160
- const stockInfo = getCartLineStockInfo(item.product, item.quantity);
161
- const message = stockInfo.stockError || formatCheckoutStockError(item.product, item.quantity);
162
- if (!message)
163
- return null;
164
- return { id: item.id, name: item.product.name, message };
165
- })
166
- .filter((issue) => issue != null);
167
- }, [checkoutItems]);
168
- const hasCheckoutStockIssues = checkoutStockIssues.length > 0;
169
84
  const checkoutTotal = effectiveBuyNowItem
170
85
  ? effectiveBuyNowItem.price * effectiveBuyNowItem.quantity
171
86
  : cartTotal;
172
- const checkoutEntireCart = !effectiveBuyNowItem;
173
- const completingOrderRef = useRef(false);
174
- const [isCompletingOrder, setIsCompletingOrder] = useState(false);
175
- const isPostPaymentNavigationPending = () => completingOrderRef.current || isCompletingOrder || isOrderSuccessPending();
176
- const beginPostPaymentNavigation = (orderId) => {
177
- completingOrderRef.current = true;
178
- setIsCompletingOrder(true);
179
- setOrderSuccessJustCompleted(orderId);
180
- };
181
- const redirectToPaymentCallback = (transactionRef) => {
182
- beginPostPaymentNavigation(transactionRef);
183
- router.replace(buildPaymentCallbackUrl(transactionRef));
184
- };
185
- const redirectToPaymentError = (reason, transactionRef, detailMessage) => {
186
- const params = new URLSearchParams({ reason });
187
- if (transactionRef)
188
- params.set('ref', transactionRef);
189
- if (detailMessage)
190
- params.set('message', detailMessage);
191
- setLoading(false);
192
- router.replace(`/${storeConfig.slug}/payment/error?${params.toString()}`);
193
- };
194
- const completeOrderAndRedirect = async (orderId, transactionRef, orderNumber, customerEmail, fulfillmentPending) => {
195
- beginPostPaymentNavigation(orderId);
196
- if (fulfillmentPending) {
197
- addToast('Payment received. Your order is under review — we will contact you if any items are unavailable.', 'info');
198
- }
199
- router.replace(buildOrderSuccessUrl(storeConfig.slug, {
200
- orderId,
201
- ref: transactionRef,
202
- orderNumber: orderNumber || undefined,
203
- email: customerEmail || undefined,
204
- fulfillmentPending,
205
- }));
206
- const purchasedItems = checkoutItems.map((item) => ({
207
- productId: item.productId,
208
- variantId: item.variantId,
209
- quantity: item.quantity,
210
- }));
211
- try {
212
- if (finalizeCartAfterOrder) {
213
- await finalizeCartAfterOrder(purchasedItems, { checkoutEntireCart });
214
- }
215
- else if (removeOrderItemsFromCart) {
216
- await removeOrderItemsFromCart(purchasedItems);
217
- }
218
- }
219
- catch (error) {
220
- console.error('[Checkout] Failed to clear cart after order:', error);
221
- }
222
- if (effectiveBuyNowItem) {
223
- clearBuyNowItem();
224
- }
225
- };
226
87
  const [step, setStep] = useState('details');
227
88
  const [loading, setLoading] = useState(false);
228
- const lastMerchantReferenceRef = useRef(null);
229
- const lastTransactionReferenceRef = useRef(null);
230
- const lastCheckoutOrderIdRef = useRef(null);
231
- const checkoutIdempotencyKeyRef = useRef(null);
232
- const checkoutCartFingerprintRef = useRef('');
233
- const checkoutItemsFingerprint = useMemo(() => {
234
- return JSON.stringify(checkoutItems.map((item) => ({
235
- productId: item.productId,
236
- variantId: item.variantId,
237
- quantity: item.quantity,
238
- price: item.price,
239
- })));
240
- }, [checkoutItems]);
241
- useEffect(() => {
242
- if (checkoutCartFingerprintRef.current && checkoutCartFingerprintRef.current !== checkoutItemsFingerprint) {
243
- resetIdempotencyKey(checkoutIdempotencyKeyRef);
244
- }
245
- checkoutCartFingerprintRef.current = checkoutItemsFingerprint;
246
- }, [checkoutItemsFingerprint]);
247
- const handlePaymentCancelCleanup = useCallback(async () => {
248
- const orderId = lastCheckoutOrderIdRef.current;
249
- if (orderId) {
250
- try {
251
- await orderService.abandonPaymentAttempt(orderId);
252
- }
253
- catch (error) {
254
- console.error('[Checkout] Failed to abandon payment attempt:', error);
255
- }
256
- lastCheckoutOrderIdRef.current = null;
257
- }
258
- resetIdempotencyKey(checkoutIdempotencyKeyRef);
259
- lastMerchantReferenceRef.current = null;
260
- lastTransactionReferenceRef.current = null;
261
- setInventoryHoldUntil?.(null);
262
- try {
263
- await refreshCart?.();
264
- }
265
- catch (error) {
266
- console.error('[Checkout] Failed to refresh cart after payment cancel:', error);
267
- }
268
- }, [refreshCart, setInventoryHoldUntil]);
269
- const buildPaymentCallbackUrl = (transactionRef) => {
270
- const params = new URLSearchParams({
271
- reference: transactionRef,
272
- storeId: storeConfig.id,
273
- });
274
- const merchantRef = lastMerchantReferenceRef.current;
275
- if (merchantRef) {
276
- params.set('merchantReference', merchantRef);
277
- params.set('orderNumber', merchantRef);
278
- }
279
- return `/${storeConfig.slug}/payment/callback?${params.toString()}`;
280
- };
281
- const enrichPayazaCallbackData = (callbackData) => {
282
- const merchantRef = lastMerchantReferenceRef.current || undefined;
283
- const internalTrx = lastTransactionReferenceRef.current || undefined;
284
- return {
285
- ...callbackData,
286
- type: 'success',
287
- status: callbackData.status || 201,
288
- data: {
289
- ...callbackData.data,
290
- merchant_reference: callbackData.data?.merchant_reference || merchantRef,
291
- additional_details: {
292
- ...(callbackData.data?.additional_details || {}),
293
- storeId: storeConfig.id,
294
- merchantReference: merchantRef,
295
- orderNumber: merchantRef,
296
- internalTransactionReference: internalTrx,
297
- },
298
- },
299
- };
300
- };
301
- const resolvePostPaymentRefs = (response) => {
302
- const merchantRef = lastMerchantReferenceRef.current || undefined;
303
- const internalTrx = lastTransactionReferenceRef.current || undefined;
304
- const payazaRef = response.callbackData?.data?.payaza_reference ||
305
- (response.callbackData?.data?.transaction_reference?.startsWith('P-C-')
306
- ? response.callbackData.data.transaction_reference
307
- : undefined);
308
- const callbackTrx = response.callbackData?.data?.transaction_reference;
309
- const transactionRef = (callbackTrx?.startsWith('TRX-') ? callbackTrx : undefined) ||
310
- (response.transactionRef?.startsWith('TRX-') ? response.transactionRef : undefined) ||
311
- internalTrx ||
312
- response.transactionRef ||
313
- callbackTrx ||
314
- payazaRef ||
315
- '';
316
- return { transactionRef, payazaRef, merchantRef };
317
- };
318
- const tryConfirmFromReference = async (transactionRef, merchantRef) => {
319
- return paymentService.confirmPaymentFromReference(transactionRef, storeConfig.id, merchantRef);
320
- };
89
+ const [isLoaded, setIsLoaded] = useState(() => typeof window !== 'undefined');
321
90
  // Address management state
322
91
  const [savedAddresses, setSavedAddresses] = useState([]);
323
92
  const [selectedAddressId, setSelectedAddressId] = useState(null);
@@ -331,148 +100,121 @@ export function CheckoutPage({ storeConfig }) {
331
100
  const [shippingError, setShippingError] = useState(null);
332
101
  const [freeShippingEligible, setFreeShippingEligible] = useState(false);
333
102
  const [freeShippingThreshold, setFreeShippingThreshold] = useState(undefined);
334
- const [isCourierMode, setIsCourierMode] = useState(false);
335
- const [checkingDeliveryStatus, setCheckingDeliveryStatus] = useState(false);
336
- const [selectedCourier, setSelectedCourier] = useState(null);
337
- // Payaza Configuration
338
- // Fallback key only — the authoritative key is resolved per-currency by the
339
- // backend from the database-managed registry and returned by /checkout/process
340
- // as `payazaPublicKey`. Priority: backend-resolved key → storeConfig → env.
341
- const payazaPublicKey = storeConfig.payment?.payazaPublicKey || process.env.NEXT_PUBLIC_PAYAZA_PUBLIC_KEY || '';
103
+ const [feePreview, setFeePreview] = useState(null);
104
+ const [isCalculatingFees, setIsCalculatingFees] = useState(false);
342
105
  const { checkout: initiateCheckout, isLoading: isPayazaLoading } = usePayazaCheckout({
343
- publicKey: payazaPublicKey,
344
106
  onSuccess: async (response) => {
345
107
  try {
346
108
  setLoading(true);
347
- const { transactionRef, payazaRef, merchantRef } = resolvePostPaymentRefs(response);
109
+ const transactionRef = response.transactionRef || response.callbackData?.data?.transaction_reference;
348
110
  if (!transactionRef) {
349
111
  addToast('Payment successful but no transaction reference. Please contact support.', 'info');
350
112
  setLoading(false);
351
113
  return;
352
114
  }
353
- console.log('[Checkout] SDK callback received, verifying payment...', {
354
- transactionRef,
355
- payazaRef,
356
- merchantRef,
357
- });
115
+ console.log('[Checkout] SDK callback received, verifying payment...');
358
116
  addToast('Verifying payment...', 'info');
359
- // Step 1: Immediate check if payment already completed
117
+ // Step 1: Immediate check if webhook already processed payment (no delay)
360
118
  let immediateVerification;
361
119
  try {
362
- immediateVerification = await paymentService.verifyPayment(transactionRef, storeConfig.id, merchantRef);
363
- if (immediateVerification.verified && isPaymentConfirmed(immediateVerification.paymentStatus)) {
364
- console.log('[Checkout] Payment already completed, proceeding to success page');
365
- await completeOrderAndRedirect(immediateVerification.orderId || transactionRef, transactionRef, immediateVerification.orderNumber || undefined, formData.email || undefined);
120
+ immediateVerification = await paymentService.verifyPayment(transactionRef, storeConfig.id);
121
+ if (immediateVerification.verified && immediateVerification.paymentStatus === 'completed') {
122
+ console.log('[Checkout] Payment already completed by webhook, proceeding to success page');
123
+ const orderId = immediateVerification.orderId || transactionRef;
124
+ const orderNumber = immediateVerification.orderNumber || '';
125
+ router.push(`/${storeConfig.slug}/order/success?orderId=${orderId}&ref=${transactionRef}${orderNumber ? `&orderNumber=${orderNumber}` : ''}`);
366
126
  return;
367
127
  }
368
128
  }
369
129
  catch (error) {
370
130
  console.log('[Checkout] Immediate verification failed, proceeding with confirmation');
371
131
  }
372
- const confirmAndRedirect = async (confirmation) => {
373
- await completeOrderAndRedirect(confirmation.orderId, transactionRef, confirmation.orderNumber || undefined, formData.email || undefined, confirmation.fulfillmentBlocked);
374
- };
375
- let inventoryConfirmationError = null;
376
- const recordConfirmationError = (error) => {
377
- if (isPaymentInventoryError(error)) {
378
- inventoryConfirmationError = error;
379
- }
380
- };
381
- // Step 2: Confirm from enriched Payaza callback payload
132
+ // Step 2: If webhook hasn't processed yet, immediately confirm from callback data
133
+ // We have the callback data from Payaza, so we can confirm immediately instead of waiting
382
134
  if (response.callbackData) {
383
135
  console.log('[Checkout] Processing Payaza callback confirmation immediately');
384
- const enrichedCallback = enrichPayazaCallbackData(response.callbackData);
136
+ // Retry logic for payment confirmation
385
137
  const MAX_RETRIES = 3;
386
- const RETRY_DELAY = 1000;
138
+ const RETRY_DELAY = 1000; // 1 second
387
139
  let confirmation;
140
+ let lastError;
388
141
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
389
142
  try {
390
- confirmation = await paymentService.confirmPaymentFromCallback(enrichedCallback);
143
+ // Confirm payment with backend and trigger order processing
144
+ confirmation = await paymentService.confirmPaymentFromCallback(response.callbackData);
145
+ if (confirmation.alreadyProcessed) {
146
+ console.log('[Checkout] Payment already processed, redirecting to success page');
147
+ }
148
+ else {
149
+ console.log('[Checkout] Payment confirmed, order processing initiated');
150
+ }
151
+ // Success - break out of retry loop
391
152
  break;
392
153
  }
393
154
  catch (error) {
155
+ lastError = error;
394
156
  console.error(`[Checkout] Payment confirmation attempt ${attempt} failed:`, error);
395
- recordConfirmationError(error);
396
- if (inventoryConfirmationError)
397
- break;
157
+ // If this is not the last attempt, wait before retrying
398
158
  if (attempt < MAX_RETRIES) {
399
- await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * attempt));
159
+ console.log(`[Checkout] Retrying payment confirmation in ${RETRY_DELAY}ms...`);
160
+ await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * attempt)); // Exponential backoff
400
161
  }
401
162
  }
402
163
  }
403
- if (inventoryConfirmationError) {
404
- redirectToPaymentError('insufficient_stock', transactionRef, inventoryConfirmationError instanceof ApiError
405
- ? inventoryConfirmationError.message
406
- : undefined);
407
- return;
408
- }
409
- if (confirmation) {
410
- await confirmAndRedirect(confirmation);
411
- return;
412
- }
413
- }
414
- // Step 3: Confirm by transaction / merchant reference (covers transfer + P-C refs)
415
- console.log('[Checkout] Trying confirm-from-reference fallback');
416
- const refsToTry = [transactionRef, payazaRef, lastTransactionReferenceRef.current].filter((ref, index, arr) => Boolean(ref) && arr.indexOf(ref) === index);
417
- for (const ref of refsToTry) {
418
- try {
419
- const confirmation = await tryConfirmFromReference(ref, merchantRef);
420
- if (isPaymentConfirmed(confirmation.paymentStatus)) {
421
- await confirmAndRedirect(confirmation);
422
- return;
164
+ // If all retries failed, do a short poll to catch webhook processing (max 3 seconds)
165
+ if (!confirmation) {
166
+ console.log('[Checkout] Confirmation failed, doing short poll for webhook processing...');
167
+ try {
168
+ const polledVerification = await paymentService.pollPaymentStatus(transactionRef, storeConfig.id, {
169
+ maxAttempts: 3, // Poll for max 3 seconds (3 * 1s)
170
+ intervalMs: 1000, // Poll every 1 second
171
+ onProgress: (attempt, maxAttempts) => {
172
+ console.log(`[Checkout] Polling for webhook (${attempt}/${maxAttempts})...`);
173
+ }
174
+ });
175
+ if (polledVerification && polledVerification.verified && polledVerification.paymentStatus === 'completed') {
176
+ console.log('[Checkout] Payment completed by webhook, proceeding to success page');
177
+ const orderId = polledVerification.orderId || transactionRef;
178
+ const orderNumber = polledVerification.orderNumber || '';
179
+ router.push(`/${storeConfig.slug}/order/success?orderId=${orderId}&ref=${transactionRef}${orderNumber ? `&orderNumber=${orderNumber}` : ''}`);
180
+ return;
181
+ }
423
182
  }
424
- }
425
- catch (error) {
426
- console.error(`[Checkout] confirm-from-reference failed for ${ref}:`, error);
427
- recordConfirmationError(error);
428
- if (inventoryConfirmationError) {
429
- redirectToPaymentError('insufficient_stock', transactionRef, inventoryConfirmationError instanceof ApiError
430
- ? inventoryConfirmationError.message
431
- : undefined);
432
- return;
183
+ catch (pollError) {
184
+ console.error('[Checkout] Polling also failed:', pollError);
433
185
  }
434
- }
435
- }
436
- // Step 4: Poll for webhook / async processing
437
- console.log('[Checkout] Polling payment status after SDK success...');
438
- try {
439
- const polledVerification = await paymentService.pollPaymentStatus(transactionRef, storeConfig.id, {
440
- maxAttempts: 10,
441
- intervalMs: 1500,
442
- merchantReference: merchantRef,
443
- onProgress: (attempt, maxAttempts) => {
444
- console.log(`[Checkout] Polling payment status (${attempt}/${maxAttempts})...`);
445
- },
446
- });
447
- if (polledVerification?.verified && isPaymentConfirmed(polledVerification.paymentStatus)) {
448
- await completeOrderAndRedirect(polledVerification.orderId || transactionRef, transactionRef, polledVerification.orderNumber || undefined, formData.email || undefined);
186
+ // Final fallback: Redirect to callback page for manual verification
187
+ console.log('[Checkout] Redirecting to callback page for verification');
188
+ addToast('Payment successful. Verifying transaction...', 'info');
189
+ router.push(`/${storeConfig.slug}/payment/callback?reference=${transactionRef}&storeId=${storeConfig.id}`);
449
190
  return;
450
191
  }
192
+ // Success - redirect to success page
193
+ const orderId = confirmation.orderId;
194
+ const orderNumber = confirmation.orderNumber || '';
195
+ // Redirect to success page
196
+ router.push(`/${storeConfig.slug}/order/success?orderId=${orderId}&ref=${transactionRef}${orderNumber ? `&orderNumber=${orderNumber}` : ''}`);
451
197
  }
452
- catch (pollError) {
453
- console.error('[Checkout] Polling failed:', pollError);
454
- }
455
- // Step 5: Final fallback — callback page with merchant reference in URL
456
- if (inventoryConfirmationError) {
457
- redirectToPaymentError('insufficient_stock', transactionRef, inventoryConfirmationError instanceof ApiError
458
- ? inventoryConfirmationError.message
459
- : undefined);
460
- return;
198
+ else {
199
+ // Fallback: Use transaction reference to verify payment
200
+ console.log('[Checkout] No callback data, verifying payment with transaction reference:', response.transactionRef);
201
+ if (response.transactionRef) {
202
+ // Redirect to callback page for verification and confirmation
203
+ router.push(`/${storeConfig.slug}/payment/callback?reference=${response.transactionRef}&storeId=${storeConfig.id}`);
204
+ }
205
+ else {
206
+ addToast('Payment successful but unable to process. Please contact support.', 'info');
207
+ setLoading(false);
208
+ }
461
209
  }
462
- console.log('[Checkout] Redirecting to callback page for verification');
463
- addToast('Payment successful. Verifying transaction...', 'info');
464
- redirectToPaymentCallback(transactionRef);
465
210
  }
466
211
  catch (error) {
467
212
  console.error('[Checkout] Error processing payment callback:', error);
468
- const { transactionRef } = resolvePostPaymentRefs(response);
469
- if (isPaymentInventoryError(error) && transactionRef) {
470
- redirectToPaymentError('insufficient_stock', transactionRef, error instanceof ApiError ? error.message : undefined);
471
- return;
472
- }
213
+ // Fallback: Try to redirect to callback page for verification
214
+ const transactionRef = response.transactionRef || response.callbackData?.data?.transaction_reference;
473
215
  if (transactionRef) {
474
216
  addToast('Payment successful. Verifying transaction...', 'info');
475
- redirectToPaymentCallback(transactionRef);
217
+ router.push(`/${storeConfig.slug}/payment/callback?reference=${transactionRef}&storeId=${storeConfig.id}`);
476
218
  }
477
219
  else {
478
220
  addToast('Payment successful but verification failed. Please contact support with your transaction details.', 'error');
@@ -480,92 +222,78 @@ export function CheckoutPage({ storeConfig }) {
480
222
  }
481
223
  }
482
224
  },
483
- onError: async (error) => {
484
- const message = String(error || '');
485
- const shouldResetCheckoutState = /reference already/i.test(message) ||
486
- /reservation expired/i.test(message) ||
487
- /already being processed/i.test(message);
488
- if (shouldResetCheckoutState) {
489
- await handlePaymentCancelCleanup();
490
- }
225
+ onError: (error) => {
491
226
  addToast(`Checkout error: ${error}`, 'error');
492
227
  setLoading(false);
493
228
  },
494
- onClose: async () => {
495
- await handlePaymentCancelCleanup();
229
+ onClose: () => {
230
+ // onClose is only called when payment was not successful (user canceled)
231
+ // This is now handled correctly by payaza-checkout.ts tracking payment success state
496
232
  addToast('Payment canceled', 'info');
497
233
  setLoading(false);
498
234
  },
499
235
  });
236
+ useEffect(() => {
237
+ setIsLoaded(true);
238
+ }, []);
500
239
  // Track checkout_start event when checkout page loads with items
501
240
  useEffect(() => {
502
- if (!isCartHydrated || !storeConfig?.id || (checkoutItems.length === 0 && !effectiveBuyNowItem)) {
503
- return;
241
+ if (isLoaded && !isInitializing && storeConfig?.id && (checkoutItems.length > 0 || effectiveBuyNowItem)) {
242
+ const totalValue = checkoutTotal;
243
+ const itemCount = checkoutItems.length;
244
+ trackEvent({
245
+ eventType: 'checkout_start',
246
+ metadata: {
247
+ totalValue,
248
+ itemCount,
249
+ items: checkoutItems.map(item => ({
250
+ productId: item.productId,
251
+ quantity: item.quantity,
252
+ price: item.price,
253
+ })),
254
+ },
255
+ }).catch(err => console.warn('Failed to track checkout_start:', err));
504
256
  }
505
- const totalValue = checkoutTotal;
506
- const itemCount = checkoutItems.length;
507
- trackEvent({
508
- eventType: 'checkout_start',
509
- metadata: {
510
- totalValue,
511
- itemCount,
512
- items: checkoutItems.map(item => ({
513
- productId: item.productId,
514
- quantity: item.quantity,
515
- price: item.price,
516
- })),
517
- },
518
- }).catch(err => console.warn('Failed to track checkout_start:', err));
519
- trackStorefrontEvent('Checkout_started', {
520
- storeId: storeConfig.id,
521
- storeSlug: storeConfig.slug,
522
- totalValue,
523
- itemCount,
524
- items: checkoutItems.map(item => ({
525
- productId: item.productId,
526
- quantity: item.quantity,
527
- price: item.price,
528
- })),
529
- });
530
- }, [isCartHydrated, storeConfig?.id, checkoutItems, effectiveBuyNowItem, checkoutTotal, trackEvent]);
257
+ }, [isLoaded, isInitializing, storeConfig?.id, checkoutItems, effectiveBuyNowItem, checkoutTotal, trackEvent]);
531
258
  // Redirect to home if checkout items are empty (with delay to allow state propagation)
532
259
  useEffect(() => {
533
- if (!isCartHydrated) {
534
- return;
535
- }
536
- const redirectTimer = setTimeout(() => {
537
- if (isPostPaymentNavigationPending()) {
538
- return;
539
- }
540
- const hasBuyNowInStorage = typeof window !== 'undefined' && sessionStorage.getItem('buyNowItem');
541
- if (hasBuyNowInStorage) {
542
- if (!effectiveBuyNowItem) {
543
- try {
544
- const stored = sessionStorage.getItem('buyNowItem');
545
- if (stored) {
546
- const parsedItem = JSON.parse(stored);
547
- setBuyNowItem(parsedItem);
548
- return;
260
+ if (!isInitializing && isLoaded) {
261
+ // Add a delay to ensure buyNowItem state has propagated
262
+ const redirectTimer = setTimeout(() => {
263
+ // Check again if we have buyNowItem in sessionStorage (even if not in context yet)
264
+ const hasBuyNowInStorage = typeof window !== 'undefined' && sessionStorage.getItem('buyNowItem');
265
+ // Don't redirect if we have buyNowItem in sessionStorage or if checkoutItems exist
266
+ if (hasBuyNowInStorage) {
267
+ // If we have it in storage but not in state, try to restore it one more time
268
+ if (!effectiveBuyNowItem) {
269
+ try {
270
+ const stored = sessionStorage.getItem('buyNowItem');
271
+ if (stored) {
272
+ const parsedItem = JSON.parse(stored);
273
+ setBuyNowItem(parsedItem);
274
+ return; // Don't redirect, we're restoring the item
275
+ }
276
+ }
277
+ catch (error) {
278
+ console.error('Failed to restore buyNowItem:', error);
549
279
  }
550
280
  }
551
- catch (error) {
552
- console.error('Failed to restore buyNowItem:', error);
281
+ else {
282
+ return; // We have the item, don't redirect
553
283
  }
554
284
  }
555
- else {
556
- return;
285
+ // Only redirect if we truly have no items and no buyNowItem in storage
286
+ if (!checkoutItems || checkoutItems.length === 0) {
287
+ router.push(`/${storeConfig.slug}`);
557
288
  }
558
- }
559
- if (!checkoutItems || checkoutItems.length === 0) {
560
- router.push(`/${storeConfig.slug}`);
561
- }
562
- }, 400);
563
- return () => clearTimeout(redirectTimer);
564
- }, [checkoutItems, isCartHydrated, router, storeConfig.slug, effectiveBuyNowItem, setBuyNowItem]);
565
- // Clear buyNowItem when component unmounts or user navigates away (not during order completion)
289
+ }, 400);
290
+ return () => clearTimeout(redirectTimer);
291
+ }
292
+ }, [checkoutItems, isLoaded, isInitializing, router, storeConfig.slug, effectiveBuyNowItem, setBuyNowItem]);
293
+ // Clear buyNowItem when component unmounts or user navigates away
566
294
  useEffect(() => {
567
295
  return () => {
568
- if (effectiveBuyNowItem && !completingOrderRef.current) {
296
+ if (effectiveBuyNowItem) {
569
297
  clearBuyNowItem();
570
298
  }
571
299
  };
@@ -593,7 +321,6 @@ export function CheckoutPage({ storeConfig }) {
593
321
  phone: user.phone || prev.phone,
594
322
  address: user.address || prev.address,
595
323
  city: user.city || prev.city,
596
- state: deriveNigerianStateFromCity(user.city || prev.city) || prev.state || '',
597
324
  country: user.country || prev.country || 'Nigeria',
598
325
  postalCode: user.zipCode || prev.postalCode,
599
326
  }));
@@ -612,7 +339,6 @@ export function CheckoutPage({ storeConfig }) {
612
339
  phone: user.phone || prev.phone,
613
340
  address: user.address || prev.address,
614
341
  city: user.city || prev.city,
615
- state: deriveNigerianStateFromCity(user.city || prev.city) || prev.state || '',
616
342
  country: user.country || prev.country || 'Nigeria',
617
343
  postalCode: user.zipCode || prev.postalCode,
618
344
  }));
@@ -637,26 +363,15 @@ export function CheckoutPage({ storeConfig }) {
637
363
  phone: user.phone || prev.phone,
638
364
  address: user.address || prev.address,
639
365
  city: user.city || prev.city,
640
- state: deriveNigerianStateFromCity(user.city || prev.city) || prev.state || '',
641
366
  country: user.country || prev.country || 'Nigeria',
642
367
  postalCode: user.zipCode || prev.postalCode,
643
368
  }));
644
369
  }
645
370
  // eslint-disable-next-line react-hooks/exhaustive-deps
646
371
  }, [isAuthenticated, user, selectedAddressId, savedAddresses.length, loadingAddresses]);
647
- // Calculate legacy shipping when address is filled (flat-rate / zone methods)
372
+ // Calculate shipping when address is filled
648
373
  const calculateShipping = async () => {
649
- const quoteAddress = enrichShippingQuoteAddress({
650
- street: formData.address,
651
- address1: formData.address,
652
- city: formData.city,
653
- state: formData.state || formData.city,
654
- zipCode: formData.postalCode,
655
- country: formData.country,
656
- phone: formData.phone,
657
- email: formData.email,
658
- });
659
- if (!canRequestShippingQuote(quoteAddress) || checkoutItems.length === 0) {
374
+ if (!formData.city || !formData.country || checkoutItems.length === 0) {
660
375
  setShippingMethods([]);
661
376
  setSelectedShippingMethod(null);
662
377
  return;
@@ -664,22 +379,20 @@ export function CheckoutPage({ storeConfig }) {
664
379
  setIsCalculatingShipping(true);
665
380
  setShippingError(null);
666
381
  try {
667
- const result = await shippingService.calculateShipping(buildShippingCalculatePayload({
382
+ const result = await shippingService.calculateShipping({
668
383
  storeId: storeConfig.id,
669
- address: quoteAddress,
670
- customerInfo: {
671
- firstName: formData.firstName,
672
- lastName: formData.lastName,
673
- phone: formData.phone,
674
- email: formData.email,
384
+ address: {
385
+ country: formData.country,
386
+ city: formData.city,
387
+ zipCode: formData.postalCode,
675
388
  },
676
- items: checkoutItems.map((item) => ({
389
+ items: checkoutItems.map(item => ({
677
390
  productId: item.productId,
678
391
  quantity: item.quantity,
679
392
  })),
680
- subtotal,
681
- currency,
682
- }));
393
+ subtotal: subtotal,
394
+ currency: currency,
395
+ });
683
396
  // Filter out any methods with invalid costs before storing
684
397
  const validMethods = (result.methods || []).filter(method => Number.isFinite(method.cost) && method.cost >= 0);
685
398
  setShippingMethods(validMethods);
@@ -731,36 +444,61 @@ export function CheckoutPage({ storeConfig }) {
731
444
  selectedShippingMethod.cost >= 0)
732
445
  ? selectedShippingMethod.cost
733
446
  : 0;
734
- // Any cart spanning more than one real store (the marketplace virtual store or otherwise) needs
735
- // fees split per store rather than a single /fees/calculate call against storeConfig.id — that
736
- // id may not even be a real store row (e.g. the marketplace "virtual store"). The multi-store
737
- // hook fetches every store's breakdown in one batched request and sums them (mirrors how the
738
- // backend computes fees per-store during process-marketplace).
739
- const multiStoreSubtotals = useMemo(() => storeBreakdown.map((s) => ({ storeId: s.storeId, subtotal: s.subtotal })), [storeBreakdown]);
740
- const singleStoreFees = useCheckoutFees({
741
- storeId: isMultiStoreCheckout ? undefined : storeConfig.id,
742
- subtotal,
743
- currency,
744
- shippingCost: shipping,
745
- onError: (message) => addToast(message, 'error'),
746
- });
747
- const multiStoreFees = useMultiStoreCheckoutFees({
748
- stores: isMultiStoreCheckout ? multiStoreSubtotals : [],
749
- currency,
750
- onError: (message) => addToast(message, 'error'),
751
- });
752
- const { feeBreakdown, loading: feesLoading, totalPayable, displayLines } = isMultiStoreCheckout
753
- ? multiStoreFees
754
- : singleStoreFees;
447
+ // Local VAT is only a temporary fallback while the server-authoritative preview loads.
448
+ const vatAmount = calculateVAT(subtotal, storeConfig.settings?.vat, storeConfig.settings?.taxRate);
449
+ const vatLabel = getVATLabel(storeConfig.settings?.vat, storeConfig.settings?.taxRate);
450
+ // Fallback total while the server-authoritative preview loads.
755
451
  const safeSubtotal = Number.isFinite(subtotal) && subtotal >= 0 ? subtotal : 0;
756
- const total = feeBreakdown?.totalPayable ?? Math.max(safeSubtotal, totalPayable);
452
+ const safeShipping = Number.isFinite(shipping) && shipping >= 0 ? shipping : 0;
453
+ const safeVatAmount = Number.isFinite(vatAmount) && vatAmount >= 0 ? vatAmount : 0;
454
+ const isFreeOrder = safeSubtotal <= 0 && checkoutItems.length > 0;
455
+ const calculatedTotal = safeSubtotal + safeShipping + safeVatAmount;
456
+ const fallbackTotal = Math.max(safeSubtotal, calculatedTotal);
457
+ const total = feePreview?.totalPayable ?? fallbackTotal;
458
+ const customerFeeLines = (feePreview?.customerLines ?? []).filter((line) => line.key !== 'shipping');
459
+ useEffect(() => {
460
+ if (!storeConfig.id || safeSubtotal <= 0 || !currency) {
461
+ setFeePreview(null);
462
+ return;
463
+ }
464
+ let cancelled = false;
465
+ const timer = window.setTimeout(async () => {
466
+ setIsCalculatingFees(true);
467
+ try {
468
+ const response = await feesService.calculateFees({
469
+ storeId: storeConfig.id,
470
+ subtotal: safeSubtotal,
471
+ shippingCost: safeShipping,
472
+ currency,
473
+ orderType: 'product',
474
+ });
475
+ if (!cancelled)
476
+ setFeePreview(response.feeBreakdown);
477
+ }
478
+ catch (error) {
479
+ // Checkout creation recalculates fees authoritatively; preserve a usable fallback preview.
480
+ console.warn('[Checkout] Fee preview unavailable:', error);
481
+ if (!cancelled)
482
+ setFeePreview(null);
483
+ }
484
+ finally {
485
+ if (!cancelled)
486
+ setIsCalculatingFees(false);
487
+ }
488
+ }, 250);
489
+ return () => {
490
+ cancelled = true;
491
+ window.clearTimeout(timer);
492
+ };
493
+ }, [currency, safeShipping, safeSubtotal, storeConfig.id]);
757
494
  // Debug logging in development
758
495
  if (process.env.NODE_ENV === 'development' && safeSubtotal > 0 && total === 0) {
759
496
  console.warn('[CheckoutPage] Total calculation issue detected:', {
760
497
  subtotal: safeSubtotal,
761
- shipping,
498
+ shipping: safeShipping,
499
+ vatAmount: safeVatAmount,
500
+ calculatedTotal,
762
501
  total,
763
- feeBreakdown,
764
502
  checkoutTotal,
765
503
  checkoutItemsCount: checkoutItems.length,
766
504
  effectiveBuyNowItem: !!effectiveBuyNowItem,
@@ -773,7 +511,6 @@ export function CheckoutPage({ storeConfig }) {
773
511
  lastName: '',
774
512
  address: '',
775
513
  city: '',
776
- state: '',
777
514
  country: 'Nigeria',
778
515
  postalCode: '',
779
516
  phone: ''
@@ -781,81 +518,17 @@ export function CheckoutPage({ storeConfig }) {
781
518
  const [errors, setErrors] = useState({});
782
519
  const [isCountryDropdownOpen, setIsCountryDropdownOpen] = useState(false);
783
520
  const [isCityDropdownOpen, setIsCityDropdownOpen] = useState(false);
784
- const [isStateDropdownOpen, setIsStateDropdownOpen] = useState(false);
785
- const [countrySearch, setCountrySearch] = useState('');
786
- const [citySearch, setCitySearch] = useState('');
787
- const deliveryEnabled = storeConfig.features?.delivery !== false;
788
- const automatedShippingEnabled = isAutomatedShippingEnabled(storeConfig);
789
- const buildQuoteAddress = () => enrichShippingQuoteAddress({
790
- street: formData.address,
791
- address1: formData.address,
792
- city: formData.city,
793
- state: formData.state || formData.city,
794
- zipCode: formData.postalCode,
795
- country: formData.country,
796
- phone: formData.phone,
797
- email: formData.email,
798
- });
799
- const handleCourierSelect = useCallback((selection) => {
800
- setSelectedCourier(selection);
801
- if (!selection) {
802
- setSelectedShippingMethod(null);
803
- return;
804
- }
805
- setSelectedShippingMethod({
806
- code: selection.serviceCode,
807
- name: selection.courierName,
808
- cost: selection.price,
809
- estimatedDays: 0,
810
- providerKey: selection.providerKey,
811
- deliveryMethodId: selection.serviceCode,
812
- deliveryQuoteId: selection.quoteId,
813
- });
814
- }, []);
815
- // Hybrid shipping: courier quotes when providers enabled, else legacy calculate
521
+ // Debounced shipping calculation when address changes
816
522
  useEffect(() => {
817
- if (!deliveryEnabled || !automatedShippingEnabled || checkoutItems.length === 0) {
818
- setIsCourierMode(false);
819
- setShippingMethods([]);
820
- setSelectedShippingMethod(null);
821
- setSelectedCourier(null);
523
+ if (!formData.city || !formData.country || checkoutItems.length === 0) {
822
524
  return;
823
525
  }
824
- const quoteAddress = buildQuoteAddress();
825
- if (!canRequestShippingQuote(quoteAddress)) {
826
- setIsCourierMode(false);
827
- setShippingMethods([]);
828
- setSelectedShippingMethod(null);
829
- setSelectedCourier(null);
830
- return;
831
- }
832
- const timer = setTimeout(async () => {
833
- setCheckingDeliveryStatus(true);
834
- setShippingError(null);
835
- try {
836
- const status = await shippingService.getDeliveryStatus(storeConfig.id);
837
- const anyProvider = status.enabled && Object.values(status.providers || {}).some(Boolean);
838
- setIsCourierMode(anyProvider);
839
- if (anyProvider) {
840
- setShippingMethods([]);
841
- setSelectedShippingMethod(null);
842
- setSelectedCourier(null);
843
- }
844
- else {
845
- await calculateShipping();
846
- }
847
- }
848
- catch {
849
- setIsCourierMode(false);
850
- await calculateShipping();
851
- }
852
- finally {
853
- setCheckingDeliveryStatus(false);
854
- }
855
- }, 500);
526
+ const timer = setTimeout(() => {
527
+ calculateShipping();
528
+ }, 500); // Debounce 500ms
856
529
  return () => clearTimeout(timer);
857
530
  // eslint-disable-next-line react-hooks/exhaustive-deps
858
- }, [deliveryEnabled, automatedShippingEnabled, formData.address, formData.city, formData.state, formData.country, formData.postalCode, formData.email, formData.phone, formData.firstName, formData.lastName, checkoutItems.length, subtotal, currency, storeConfig.id]);
531
+ }, [formData.city, formData.country, formData.postalCode, checkoutItems.length, subtotal, currency]);
859
532
  const validateForm = () => {
860
533
  const newErrors = {};
861
534
  if (!formData.email)
@@ -868,18 +541,10 @@ export function CheckoutPage({ storeConfig }) {
868
541
  newErrors.lastName = 'Last name is required';
869
542
  if (!formData.address)
870
543
  newErrors.address = 'Address is required';
871
- if (!formData.country)
872
- newErrors.country = 'Country is required';
873
544
  if (!formData.city)
874
545
  newErrors.city = 'City is required';
875
- if (isNigeria(formData.country) && !formData.state) {
876
- newErrors.state = 'State is required for Nigeria';
877
- }
878
546
  if (!formData.phone)
879
547
  newErrors.phone = 'Phone is required';
880
- else if (!validatePhone(formData.phone)) {
881
- newErrors.phone = 'Please enter a valid phone number';
882
- }
883
548
  setErrors(newErrors);
884
549
  return Object.keys(newErrors).length === 0;
885
550
  };
@@ -891,44 +556,19 @@ export function CheckoutPage({ storeConfig }) {
891
556
  setIsPromoApplied(true);
892
557
  addToast('Promo code applied successfully!', 'success');
893
558
  };
894
- const phonePlaceholder = getPhonePlaceholder();
895
- const handlePhoneChange = (value) => {
896
- setFormData({ ...formData, phone: formatPhoneInput(value) });
897
- };
898
559
  // Handle address selection
899
560
  const handleAddressSelect = (addressId) => {
900
561
  if (addressId === null) {
901
- const profileAddress = user?.address && typeof user.address === 'object'
902
- ? user.address.address1 ||
903
- user.address.street ||
904
- ''
905
- : typeof user?.address === 'string'
906
- ? user.address
907
- : '';
908
562
  // "Add new address" selected - pre-fill with profile data if available, otherwise clear
909
563
  setFormData(prev => ({
910
564
  email: user?.email || prev.email,
911
565
  firstName: user?.firstName || prev.firstName,
912
566
  lastName: user?.lastName || prev.lastName,
913
567
  phone: user?.phone || prev.phone,
914
- address: profileAddress,
915
- city: (user?.address && typeof user.address === 'object'
916
- ? user.address.city
917
- : undefined) ||
918
- user?.city ||
919
- '',
920
- state: '',
921
- country: (user?.address && typeof user.address === 'object'
922
- ? user.address.country
923
- : undefined) ||
924
- user?.country ||
925
- prev.country ||
926
- 'Nigeria',
927
- postalCode: (user?.address && typeof user.address === 'object'
928
- ? user.address.zipCode
929
- : undefined) ||
930
- user?.zipCode ||
931
- '',
568
+ address: user?.address || '',
569
+ city: user?.city || '',
570
+ country: user?.country || prev.country || 'Nigeria',
571
+ postalCode: user?.zipCode || '',
932
572
  }));
933
573
  setSelectedAddressId(null);
934
574
  setSaveNewAddress(false);
@@ -943,7 +583,6 @@ export function CheckoutPage({ storeConfig }) {
943
583
  lastName: address.lastName,
944
584
  address: address.address1,
945
585
  city: address.city,
946
- state: address.state || '',
947
586
  country: address.country,
948
587
  postalCode: address.zipCode,
949
588
  phone: address.phone || prev.phone,
@@ -953,44 +592,11 @@ export function CheckoutPage({ storeConfig }) {
953
592
  setAddressLabel('');
954
593
  }
955
594
  };
956
- const saveCheckoutAddressIfNeeded = async () => {
957
- if (!isAuthenticated || !saveNewAddress || selectedAddressId) {
958
- return true;
959
- }
960
- try {
961
- const newAddress = await customerService.addAddress({
962
- firstName: formData.firstName.trim(),
963
- lastName: formData.lastName.trim(),
964
- address1: formData.address.trim(),
965
- address2: '',
966
- city: formData.city.trim(),
967
- state: formData.state?.trim() || undefined,
968
- zipCode: formData.postalCode?.trim() || undefined,
969
- country: formData.country.trim(),
970
- phone: formData.phone?.trim() || undefined,
971
- isDefault: savedAddresses.length === 0,
972
- label: addressLabel.trim() || undefined,
973
- }, { suppressToast: true });
974
- const addresses = await customerService.getAddresses();
975
- setSavedAddresses(addresses);
976
- handleAddressSelect(newAddress.id);
977
- addToast('Address saved successfully!', 'success');
978
- return true;
979
- }
980
- catch (addressError) {
981
- console.error('[Checkout] Failed to save address:', addressError);
982
- addToast(addressError?.message || 'Unable to save address right now. You can save it later from your account.', 'error');
983
- return false;
984
- }
985
- };
986
595
  const validateCheckoutPreFlight = () => {
987
596
  // Validate checkout items
988
597
  if (checkoutItems.length === 0) {
989
598
  return { isValid: false, error: 'No items to checkout' };
990
599
  }
991
- // Note: no local Payaza key check here — the backend resolves the key for
992
- // the order currency from the database registry and returns it from
993
- // /checkout/process. Missing keys are reported after order creation.
994
600
  // Validate form data
995
601
  if (!formData.email) {
996
602
  return { isValid: false, error: 'Email is required' };
@@ -1013,58 +619,21 @@ export function CheckoutPage({ storeConfig }) {
1013
619
  if (!formData.phone) {
1014
620
  return { isValid: false, error: 'Phone is required' };
1015
621
  }
1016
- if (!validatePhone(formData.phone)) {
1017
- return { isValid: false, error: 'Please enter a valid phone number' };
1018
- }
1019
- if (isNigeria(formData.country) && !formData.state) {
1020
- return { isValid: false, error: 'State is required for Nigeria' };
1021
- }
1022
622
  // Validate currency availability
1023
623
  const hasCurrency = storeConfig.settings?.currency || checkoutItems.some(item => item.product.currency);
1024
624
  if (!hasCurrency) {
1025
625
  console.warn('[Checkout] No currency found, will use USD fallback');
1026
626
  }
1027
- for (const item of checkoutItems) {
1028
- const stockError = formatCheckoutStockError(item.product, item.quantity);
1029
- if (stockError) {
1030
- return {
1031
- isValid: false,
1032
- error: stockError,
1033
- };
1034
- }
1035
- }
1036
627
  return { isValid: true };
1037
628
  };
1038
629
  const handlePayazaCheckout = async () => {
1039
- if (checkoutDisabled) {
1040
- addToast(CHECKOUT_UNAVAILABLE_MESSAGE, 'error');
1041
- return;
1042
- }
1043
630
  // Pre-flight validation
1044
631
  const validation = validateCheckoutPreFlight();
1045
632
  if (!validation.isValid) {
1046
633
  addToast(validation.error || 'Please complete all required fields', 'error');
1047
634
  return;
1048
635
  }
1049
- if (hasCheckoutStockIssues) {
1050
- addToast(checkoutStockIssues[0]?.message || 'One or more items are no longer available.', 'error');
1051
- refreshCart?.();
1052
- return;
1053
- }
1054
636
  setLoading(true);
1055
- try {
1056
- await refreshCart?.();
1057
- }
1058
- catch (refreshError) {
1059
- console.warn('[Checkout] Cart refresh before payment failed:', refreshError);
1060
- }
1061
- trackStorefrontEvent('Payment_method_selected', {
1062
- storeId: storeConfig.id,
1063
- storeSlug: storeConfig.slug,
1064
- paymentMethod: 'payaza',
1065
- totalValue: checkoutTotal,
1066
- itemCount: checkoutItems.length,
1067
- });
1068
637
  // DEMO MODE BYPASS
1069
638
  if (storeConfig.slug.startsWith('demo/')) {
1070
639
  setTimeout(() => {
@@ -1072,7 +641,6 @@ export function CheckoutPage({ storeConfig }) {
1072
641
  }, 1500);
1073
642
  return;
1074
643
  }
1075
- await saveCheckoutAddressIfNeeded();
1076
644
  let checkoutResponse;
1077
645
  let checkoutConfig;
1078
646
  try {
@@ -1082,101 +650,49 @@ export function CheckoutPage({ storeConfig }) {
1082
650
  itemCount: checkoutItems.length,
1083
651
  customerEmail: formData.email,
1084
652
  });
1085
- if (!isAuthenticated && formData.email && formData.firstName && formData.lastName && formData.phone) {
1086
- saveGuestUserInfo({
1087
- email: formData.email,
1088
- firstName: formData.firstName,
1089
- lastName: formData.lastName,
1090
- phone: formData.phone,
1091
- });
1092
- }
1093
653
  try {
1094
- const normalizedItems = checkoutItems.map((item) => normalizeCheckoutPayloadItem(item));
1095
- if (isMarketplaceCheckout) {
1096
- const marketplaceItems = normalizedItems.filter((item) => item.storeId);
1097
- if (marketplaceItems.length !== normalizedItems.length) {
1098
- throw new Error('Some cart items are missing store information. Please refresh your cart and try again.');
1099
- }
1100
- // Belt-and-suspenders: the backend now hard-rejects mixed-currency marketplace
1101
- // checkouts, and in the normal flow this branch is unreachable since the cart is
1102
- // already pre-filtered to one currency before this page mounts. Guard here anyway for
1103
- // any other consumer of this shared component that doesn't pre-filter, and to fail
1104
- // fast client-side instead of round-tripping to the server first.
1105
- const checkoutCurrencies = new Set(checkoutItems
1106
- .map((item) => item.currency ?? item.product?.currency)
1107
- .filter((value) => Boolean(value)));
1108
- if (checkoutCurrencies.size > 1) {
1109
- throw new Error(`Your cart has items in different currencies (${Array.from(checkoutCurrencies).join(', ')}). Please check out one currency group at a time.`);
1110
- }
1111
- checkoutResponse = await checkoutService.processMarketplaceCheckout({
1112
- items: marketplaceItems,
1113
- sessionId: typeof window !== 'undefined' ? sessionStorage.getItem('sessionId') || undefined : undefined,
1114
- customerInfo: {
1115
- email: formData.email,
1116
- firstName: formData.firstName,
1117
- lastName: formData.lastName,
1118
- phone: formData.phone,
1119
- },
1120
- shippingAddress: {
1121
- firstName: formData.firstName,
1122
- lastName: formData.lastName,
1123
- address1: formData.address,
1124
- city: formData.city,
1125
- state: formData.state || formData.city || formData.country,
1126
- zipCode: formData.postalCode,
1127
- country: formData.country,
1128
- phone: formData.phone,
1129
- },
1130
- paymentMethod: 'payaza',
1131
- customerId: isAuthenticated && user ? user.id : undefined,
1132
- }, {
1133
- idempotencyKey: getOrCreateIdempotencyKey(checkoutIdempotencyKeyRef),
1134
- });
1135
- }
1136
- else {
1137
- checkoutResponse = await checkoutService.processCheckout({
1138
- storeId: storeConfig.id,
1139
- sessionId: typeof window !== 'undefined' ? sessionStorage.getItem('sessionId') || undefined : undefined,
1140
- items: checkoutItems.map((item) => normalizeCheckoutPayloadItem(item)),
1141
- customerInfo: {
1142
- email: formData.email,
1143
- firstName: formData.firstName,
1144
- lastName: formData.lastName,
1145
- phone: formData.phone,
1146
- },
1147
- shippingAddress: {
1148
- firstName: formData.firstName,
1149
- lastName: formData.lastName,
1150
- address1: formData.address,
1151
- city: formData.city,
1152
- state: formData.state || formData.city || formData.country,
1153
- zipCode: formData.postalCode,
1154
- country: formData.country,
1155
- phone: formData.phone,
1156
- },
1157
- shippingMethod: deliveryEnabled ? selectedShippingMethod?.code : undefined,
1158
- shippingCost: deliveryEnabled ? shipping : 0,
1159
- paymentMethod: 'payaza',
1160
- deliveryProviderKey: selectedShippingMethod?.providerKey,
1161
- deliveryMethodId: selectedShippingMethod?.deliveryMethodId,
1162
- deliveryQuoteId: selectedShippingMethod?.deliveryQuoteId,
1163
- customerId: isAuthenticated && user ? user.id : undefined, // Pass customerId for authenticated users
1164
- }, {
1165
- idempotencyKey: getOrCreateIdempotencyKey(checkoutIdempotencyKeyRef),
1166
- });
1167
- }
654
+ checkoutResponse = await checkoutService.processCheckout({
655
+ storeId: storeConfig.id,
656
+ items: checkoutItems.map(item => ({
657
+ productId: item.productId,
658
+ quantity: item.quantity,
659
+ price: item.price,
660
+ variantId: item.variantId,
661
+ })),
662
+ customerInfo: {
663
+ email: formData.email,
664
+ firstName: formData.firstName,
665
+ lastName: formData.lastName,
666
+ phone: formData.phone,
667
+ },
668
+ shippingAddress: {
669
+ firstName: formData.firstName,
670
+ lastName: formData.lastName,
671
+ address1: formData.address,
672
+ city: formData.city,
673
+ state: formData.city || formData.country, // Use city as state fallback (form doesn't collect state field)
674
+ zipCode: formData.postalCode,
675
+ country: formData.country,
676
+ phone: formData.phone,
677
+ },
678
+ shippingMethod: selectedShippingMethod?.code,
679
+ shippingCost: shipping, // Use validated shipping value
680
+ paymentMethod: 'payaza',
681
+ customerId: isAuthenticated && user ? user.id : undefined, // Pass customerId for authenticated users
682
+ });
1168
683
  console.log('[Checkout] Step 1: Order created successfully', {
1169
684
  orderId: checkoutResponse.orderId,
1170
685
  orderNumber: checkoutResponse.orderNumber,
1171
686
  currency: checkoutResponse.currency,
1172
687
  transactionReference: checkoutResponse.transactionReference,
1173
688
  });
1174
- lastMerchantReferenceRef.current =
1175
- checkoutResponse.merchantReference || checkoutResponse.orderNumber || null;
1176
- lastTransactionReferenceRef.current = checkoutResponse.transactionReference || null;
1177
- lastCheckoutOrderIdRef.current = checkoutResponse.orderId || null;
1178
- if (checkoutResponse.inventoryHoldUntil) {
1179
- setInventoryHoldUntil?.(checkoutResponse.inventoryHoldUntil);
689
+ const isFreeOrderResponse = checkoutResponse.paymentNotRequired === true ||
690
+ (checkoutResponse.totalAmount != null && Number(checkoutResponse.totalAmount) <= 0);
691
+ if (isFreeOrderResponse) {
692
+ addToast('Your free order is complete!', 'success');
693
+ router.push(`/${storeConfig.slug}/order/success?orderId=${checkoutResponse.orderId}&ref=${encodeURIComponent(checkoutResponse.transactionReference || 'FREE')}${checkoutResponse.orderNumber ? `&orderNumber=${encodeURIComponent(checkoutResponse.orderNumber)}` : ''}`);
694
+ setLoading(false);
695
+ return;
1180
696
  }
1181
697
  }
1182
698
  catch (apiError) {
@@ -1190,43 +706,28 @@ export function CheckoutPage({ storeConfig }) {
1190
706
  itemCount: checkoutItems.length,
1191
707
  },
1192
708
  });
1193
- const isNetworkError = !apiError?.status ||
1194
- apiError?.status === 0 ||
1195
- apiError?.code === 'ECONNABORTED' ||
1196
- apiError?.message === 'Network Error' ||
1197
- apiError?.message?.includes('timeout');
1198
- if (isNetworkError) {
1199
- addToast(getUserFriendlyErrorMessage(apiError, 'Unable to reach the server. Please check your connection and try again.'), 'error');
709
+ if (apiError?.status === 0 || apiError?.code === 'ECONNABORTED' || apiError?.message?.includes('timeout')) {
710
+ addToast('Unable to connect to server. Please check your internet connection.', 'error');
1200
711
  }
1201
712
  else if (apiError?.status === 500) {
1202
- addToast(getUserFriendlyErrorMessage(apiError, 'A server error occurred. Please try again in a moment.'), 'error');
1203
- }
1204
- else if (apiError?.status === 400 || apiError?.status === 422) {
1205
- addToast(getUserFriendlyErrorMessage(apiError, 'Invalid checkout data. Please review your shipping information.'), 'error');
713
+ addToast('Server error occurred. Please try again later.', 'error');
1206
714
  }
1207
- else if (apiError?.status === 409) {
1208
- addToast(getUserFriendlyErrorMessage(apiError, 'One or more items are no longer available in the requested quantity.'), 'error');
1209
- refreshCart?.();
715
+ else if (apiError?.status === 400) {
716
+ addToast(apiError?.message || 'Invalid checkout data. Please review your information.', 'error');
1210
717
  }
1211
718
  else {
1212
- addToast(getUserFriendlyErrorMessage(apiError, 'Failed to create order. Please try again.'), 'error');
719
+ addToast(apiError?.message || 'Failed to create order. Please try again.', 'error');
1213
720
  }
1214
721
  setLoading(false);
1215
722
  return;
1216
723
  }
1217
- // Resolve the Payaza public key. Priority order:
1218
- // 1. Key resolved by the backend for the order currency from the
1219
- // database-managed registry (backoffice → Payment Control)
1220
- // 2. storeConfig.payment.payazaPublicKey
1221
- // 3. NEXT_PUBLIC_PAYAZA_PUBLIC_KEY build-time env var
1222
- const effectivePayazaPublicKey = checkoutResponse.payazaPublicKey || payazaPublicKey;
1223
- if (!effectivePayazaPublicKey) {
1224
- console.error('[Checkout] No Payaza public key available', {
1225
- fromBackend: !!checkoutResponse.payazaPublicKey,
1226
- fromStoreConfig: !!storeConfig.payment?.payazaPublicKey,
1227
- fromEnv: !!process.env.NEXT_PUBLIC_PAYAZA_PUBLIC_KEY,
1228
- });
1229
- addToast('Payment gateway not configured. Please contact support.', 'error');
724
+ let effectivePayazaPublicKey;
725
+ try {
726
+ effectivePayazaPublicKey = requireCheckoutPayazaPublicKey(checkoutResponse);
727
+ }
728
+ catch (error) {
729
+ console.error('[Checkout] No Payaza public key from backend', error);
730
+ addToast(error instanceof Error ? error.message : 'Payment gateway not configured. Please contact support.', 'error');
1230
731
  setLoading(false);
1231
732
  return;
1232
733
  }
@@ -1249,28 +750,24 @@ export function CheckoutPage({ storeConfig }) {
1249
750
  orderId: checkoutResponse.orderId,
1250
751
  orderNumber: checkoutResponse.orderNumber,
1251
752
  });
1252
- const orderMerchantRef = checkoutResponse.merchantReference || checkoutResponse.orderNumber || '';
1253
753
  checkoutConfig = generateCheckoutConfig(payazaItems, formData.email, effectivePayazaPublicKey, {
1254
754
  firstName: formData.firstName,
1255
755
  lastName: formData.lastName,
1256
- phone: normalizePhoneForPayaza(formData.phone),
1257
- callbackUrl: `${typeof window !== 'undefined' ? window.location.origin : ''}/${storeConfig.slug}/payment/callback?storeId=${storeConfig.id}${orderMerchantRef ? `&merchantReference=${encodeURIComponent(orderMerchantRef)}&orderNumber=${encodeURIComponent(orderMerchantRef)}` : ''}`,
756
+ phone: formData.phone,
757
+ callbackUrl: `${typeof window !== 'undefined' ? window.location.origin : ''}/${storeConfig.slug}/payment/callback?storeId=${storeConfig.id}`,
1258
758
  currency: checkoutResponse.currency || storeConfig.settings?.currency,
759
+ totalAmount: checkoutResponse.totalAmount,
760
+ merchantReference: checkoutResponse.merchantReference,
1259
761
  storeId: storeConfig.id,
1260
762
  orderId: checkoutResponse.orderId,
1261
763
  orderNumber: checkoutResponse.orderNumber,
1262
- merchantReference: checkoutResponse.merchantReference || checkoutResponse.orderNumber,
1263
- totalAmount: checkoutResponse.totalAmount ??
1264
- checkoutResponse.feeBreakdown?.totalPayable ??
1265
- checkoutResponse.feeBreakdown?.total ??
1266
- total,
1267
764
  connectionMode: checkoutResponse.payazaConnectionMode,
1268
765
  shippingAddress: {
1269
766
  firstName: formData.firstName,
1270
767
  lastName: formData.lastName,
1271
768
  address: formData.address,
1272
769
  city: formData.city,
1273
- state: formData.state || formData.city || formData.country,
770
+ state: formData.city || formData.country, // Use city as state fallback (form doesn't collect state field)
1274
771
  country: formData.country,
1275
772
  zipCode: formData.postalCode,
1276
773
  },
@@ -1296,13 +793,45 @@ export function CheckoutPage({ storeConfig }) {
1296
793
  console.log('[Checkout] Step 4: Transaction reference set', {
1297
794
  reference: checkoutConfig.reference,
1298
795
  });
1299
- // Step 5: Initiate Payaza checkout
796
+ // Step 5: Save address if requested (for authenticated users)
797
+ if (isAuthenticated && saveNewAddress && !selectedAddressId) {
798
+ try {
799
+ // Ensure required fields are never empty (backend validation requires non-empty values)
800
+ const zipCode = formData.postalCode || formData.city || '00000';
801
+ const state = formData.city || formData.country || 'N/A';
802
+ const newAddress = await customerService.addAddress({
803
+ firstName: formData.firstName,
804
+ lastName: formData.lastName,
805
+ address1: formData.address,
806
+ address2: '',
807
+ city: formData.city,
808
+ state: state,
809
+ zipCode: zipCode,
810
+ country: formData.country,
811
+ phone: formData.phone,
812
+ isDefault: savedAddresses.length === 0, // Set as default if it's the first address
813
+ label: addressLabel || undefined,
814
+ });
815
+ console.log('[Checkout] Address saved successfully');
816
+ // Refresh addresses list
817
+ const addresses = await customerService.getAddresses();
818
+ setSavedAddresses(addresses);
819
+ // Select the newly saved address (this will also sync the form with the saved address)
820
+ handleAddressSelect(newAddress.id);
821
+ addToast('Address saved successfully!', 'success');
822
+ }
823
+ catch (addressError) {
824
+ console.error('[Checkout] Failed to save address:', addressError);
825
+ addToast('Failed to save address. You can save it later from your account.', 'error');
826
+ // Don't block checkout if address save fails - just log the error
827
+ // User can save address later from their account
828
+ }
829
+ }
830
+ // Step 6: Initiate Payaza checkout
1300
831
  console.log('[Checkout] Step 6: Initiating Payaza checkout');
1301
832
  try {
1302
833
  await initiateCheckout(checkoutConfig);
1303
834
  console.log('[Checkout] Step 6: Payaza checkout initiated successfully');
1304
- // Order is created; release page-level loading — Payaza SDK overlay handles payment UI.
1305
- setLoading(false);
1306
835
  // Clear buyNowItem after successful checkout initiation
1307
836
  // The item has been processed and order created
1308
837
  if (effectiveBuyNowItem) {
@@ -1339,11 +868,7 @@ export function CheckoutPage({ storeConfig }) {
1339
868
  stack: error?.stack,
1340
869
  name: error?.name,
1341
870
  });
1342
- const isCollectionsDisabled = (error instanceof ApiError && error.status === 403) ||
1343
- error?.response?.status === 403 ||
1344
- error?.code === 'COLLECTIONS_DISABLED' ||
1345
- String(error?.message || '').toLowerCase().includes('not accepting payments');
1346
- addToast(isCollectionsDisabled ? CHECKOUT_UNAVAILABLE_MESSAGE : 'Failed to initiate checkout. Please try again.', 'error');
871
+ addToast('Failed to initiate checkout. Please try again.', 'error');
1347
872
  setLoading(false);
1348
873
  }
1349
874
  };
@@ -1365,74 +890,34 @@ export function CheckoutPage({ storeConfig }) {
1365
890
  }
1366
891
  }
1367
892
  };
1368
- // Show skeleton while cart hydrates (buy-now flow skips cart hydration)
1369
- if (!isCartHydrated && !effectiveBuyNowItem) {
893
+ // Show skeleton while initializing or if not loaded
894
+ if (isInitializing || !isLoaded) {
1370
895
  return _jsx(CheckoutSkeleton, {});
1371
896
  }
1372
- if (isPostPaymentNavigationPending()) {
1373
- return (_jsxs("div", { className: "min-h-screen bg-gray-50/50 flex flex-col items-center justify-center p-4 font-sans", children: [_jsx(Loader2, { className: "mb-4 h-10 w-10 animate-spin text-gray-400" }), _jsx("p", { className: "font-medium text-gray-600", children: "Completing your order..." })] }));
1374
- }
1375
897
  // Show empty cart message if checkout items are empty (redirect will happen after delay)
1376
898
  if (!checkoutItems || checkoutItems.length === 0) {
1377
- return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-12 px-4 sm:px-6 lg:px-8 font-sans", children: _jsx("div", { className: "container mx-auto max-w-6xl", children: _jsx("div", { className: "bg-white rounded-3xl shadow-sm border border-gray-100 p-12 text-center", children: _jsxs("div", { className: "max-w-md mx-auto space-y-6", children: [_jsx("div", { className: "w-20 h-20 mx-auto rounded-full bg-gray-100 flex items-center justify-center", children: _jsx("svg", { className: "w-10 h-10 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" }) }) }), _jsx("h2", { className: "text-2xl font-bold text-gray-900", children: "Your cart is empty" }), _jsx("p", { className: "text-gray-600", children: "Add some items to your cart before proceeding to checkout." }), _jsx("div", { className: "pt-4", children: _jsx(Link, { href: `/${storeConfig.slug}`, children: _jsxs(Button, { size: "lg", className: "bg-black hover:bg-gray-800 text-white rounded-full px-8 py-6 h-auto font-semibold shadow-lg shadow-black/10 hover:shadow-xl transition-all hover:scale-105 flex items-center gap-2 mx-auto", children: [_jsx(ArrowLeft, { className: "w-5 h-5" }), "Continue Shopping"] }) }) })] }) }) }) }));
899
+ return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-12 px-4 sm:px-6 lg:px-8 font-sans", "data-checkout-ready": true, children: _jsx("div", { className: "container mx-auto max-w-6xl", children: _jsx("div", { className: "bg-white rounded-3xl shadow-sm border border-gray-100 p-12 text-center", children: _jsxs("div", { className: "max-w-md mx-auto space-y-6", children: [_jsx("div", { className: "w-20 h-20 mx-auto rounded-full bg-gray-100 flex items-center justify-center", children: _jsx("svg", { className: "w-10 h-10 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" }) }) }), _jsx("h2", { className: "text-2xl font-bold text-gray-900", children: "Your cart is empty" }), _jsx("p", { className: "text-gray-600", children: "Add some items to your cart before proceeding to checkout." }), _jsx("div", { className: "pt-4", children: _jsx(Link, { href: `/${storeConfig.slug}`, children: _jsxs(Button, { size: "lg", className: "bg-black hover:bg-gray-800 text-white rounded-full px-8 py-6 h-auto font-semibold shadow-lg shadow-black/10 hover:shadow-xl transition-all hover:scale-105 flex items-center gap-2 mx-auto", children: [_jsx(ArrowLeft, { className: "w-5 h-5" }), "Continue Shopping"] }) }) })] }) }) }) }));
1378
900
  }
1379
- return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-12 px-4 sm:px-6 lg:px-8 font-sans", children: _jsxs("div", { className: "container mx-auto max-w-6xl", children: [_jsxs("div", { className: "mb-10 flex items-center justify-between", children: [_jsxs(Link, { href: `/${storeConfig.slug}/cart`, className: "group flex items-center gap-2 text-gray-500 hover:text-black transition-colors font-medium", children: [_jsx("div", { className: "p-2 rounded-full bg-white border border-gray-200 group-hover:border-black transition-colors", children: _jsx(ArrowLeft, { className: "w-4 h-4" }) }), "Back to Cart"] }), _jsx("div", { className: "flex items-center gap-2 text-sm font-bold", children: deliveryEnabled ? (_jsxs(_Fragment, { children: [_jsx("span", { className: cn("px-3 py-1.5 rounded-full transition-colors", step === 'details' ? "bg-black text-white" : "bg-green-100 text-green-700"), children: step === 'details' ? '1' : _jsx(CheckCircle, { className: "w-4 h-4" }) }), _jsx("span", { className: cn(step === 'details' ? "text-black" : "text-gray-500"), children: "Shipping" }), _jsx("div", { className: "w-8 h-0.5 bg-gray-200" }), _jsx("span", { className: cn("px-3 py-1.5 rounded-full transition-colors", step === 'payment' ? "bg-black text-white" : "bg-gray-200 text-gray-500"), children: "2" }), _jsx("span", { className: cn(step === 'payment' ? "text-black" : "text-gray-400"), children: "Payment" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: cn("px-3 py-1.5 rounded-full transition-colors", step === 'details' ? "bg-black text-white" : "bg-green-100 text-green-700"), children: step === 'details' ? '1' : _jsx(CheckCircle, { className: "w-4 h-4" }) }), _jsx("span", { className: cn(step === 'details' ? "text-black" : "text-gray-500"), children: "Details" }), _jsx("div", { className: "w-8 h-0.5 bg-gray-200" }), _jsx("span", { className: cn("px-3 py-1.5 rounded-full transition-colors", step === 'payment' ? "bg-black text-white" : "bg-gray-200 text-gray-500"), children: "2" }), _jsx("span", { className: cn(step === 'payment' ? "text-black" : "text-gray-400"), children: "Payment" })] })) })] }), _jsxs("div", { className: "flex flex-col lg:flex-row gap-10", children: [_jsx("div", { className: "flex-1", children: step === 'details' ? (_jsxs("form", { onSubmit: handleDetailsSubmit, className: "space-y-8 animate-fade-in", children: [_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-blue-50 text-blue-600 rounded-xl", children: _jsx(ShieldCheck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Contact Information" })] }), _jsx("div", { className: "space-y-4", children: isAuthenticated && user ? (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "p-4 bg-gray-50 rounded-xl border border-gray-200", children: [_jsx("p", { className: "text-xs font-bold text-gray-500 uppercase tracking-wider mb-2", children: "Logged in as" }), _jsxs("p", { className: "font-bold text-gray-900", children: [user.firstName, " ", user.lastName] }), _jsx("p", { className: "text-sm text-gray-600", children: user.email })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Email Address" }), _jsx("input", { type: "email", className: cn("w-full px-5 py-3.5 bg-white border-2 rounded-xl focus:outline-none focus:ring-0 transition-all font-medium", errors.email ? "border-red-500 focus:border-red-500" : "border-gray-100 focus:border-black"), placeholder: "john@example.com", value: formData.email, onChange: e => setFormData({ ...formData, email: e.target.value }) }), _jsx("p", { className: "text-xs text-gray-500 ml-1", children: "You can use a different email for this order" }), errors.email && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.email })] })] })) : (_jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Email Address" }), _jsx("input", { type: "email", className: cn("w-full px-5 py-3.5 bg-white border-2 rounded-xl focus:outline-none focus:ring-0 transition-all font-medium", errors.email ? "border-red-500 focus:border-red-500" : "border-gray-100 focus:border-black"), placeholder: "john@example.com", value: formData.email, onChange: e => setFormData({ ...formData, email: e.target.value }) }), errors.email && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.email })] })) })] }), _jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-orange-50 text-orange-600 rounded-xl", children: _jsx(MapPin, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: storeConfig.settings?.shippingEnabled === false ? 'Billing Address' : 'Shipping Address' })] }), isAuthenticated && (_jsx(AddressSelector, { addresses: savedAddresses, selectedAddressId: selectedAddressId, onSelect: handleAddressSelect, isLoading: loadingAddresses, isAuthenticated: isAuthenticated })), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-5", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "First Name" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.firstName ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), value: formData.firstName, onChange: e => setFormData({ ...formData, firstName: e.target.value }) }), errors.firstName && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.firstName })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Last Name" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.lastName ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), value: formData.lastName, onChange: e => setFormData({ ...formData, lastName: e.target.value }) }), errors.lastName && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.lastName })] }), _jsxs("div", { className: "space-y-1.5 md:col-span-2", children: [_jsx(RequiredLabel, { children: "Address" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.address ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "e.g. 12 Adeola Odeku Street, Victoria Island", value: formData.address, onChange: e => setFormData({ ...formData, address: e.target.value }) }), errors.address
1380
- ? _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.address })
1381
- : _jsx("p", { className: "text-[11px] text-gray-500 mt-1 ml-1", children: "Enter your house/flat number and street name for accurate delivery rates." })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Country" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsCountryDropdownOpen(!isCountryDropdownOpen), className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.country ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), children: [_jsx("span", { className: "flex items-center gap-2", children: (() => {
901
+ return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-12 px-4 sm:px-6 lg:px-8 font-sans", "data-checkout-ready": true, children: _jsxs("div", { className: "container mx-auto max-w-6xl", children: [_jsxs("div", { className: "mb-10 flex items-center justify-between", children: [_jsxs(Link, { href: `/${storeConfig.slug}/cart`, className: "group flex items-center gap-2 text-gray-500 hover:text-black transition-colors font-medium", children: [_jsx("div", { className: "p-2 rounded-full bg-white border border-gray-200 group-hover:border-black transition-colors", children: _jsx(ArrowLeft, { className: "w-4 h-4" }) }), "Back to Cart"] }), _jsxs("div", { className: "flex items-center gap-2 text-sm font-bold", children: [_jsx("span", { className: cn("px-3 py-1.5 rounded-full transition-colors", step === 'details' ? "bg-black text-white" : "bg-green-100 text-green-700"), children: step === 'details' ? '1' : _jsx(CheckCircle, { className: "w-4 h-4" }) }), _jsx("span", { className: cn(step === 'details' ? "text-black" : "text-gray-500"), children: "Shipping" }), _jsx("div", { className: "w-8 h-0.5 bg-gray-200" }), _jsx("span", { className: cn("px-3 py-1.5 rounded-full transition-colors", step === 'payment' ? "bg-black text-white" : "bg-gray-200 text-gray-500"), children: "2" }), _jsx("span", { className: cn(step === 'payment' ? "text-black" : "text-gray-400"), children: "Payment" })] })] }), _jsxs("div", { className: "flex flex-col lg:flex-row gap-10", children: [_jsx("div", { className: "flex-1", children: step === 'details' ? (_jsxs("form", { onSubmit: handleDetailsSubmit, className: "space-y-8 animate-fade-in", children: [_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-blue-50 text-blue-600 rounded-xl", children: _jsx(ShieldCheck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Contact Information" })] }), _jsx("div", { className: "space-y-4", children: isAuthenticated && user ? (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "p-4 bg-gray-50 rounded-xl border border-gray-200", children: [_jsx("p", { className: "text-xs font-bold text-gray-500 uppercase tracking-wider mb-2", children: "Logged in as" }), _jsxs("p", { className: "font-bold text-gray-900", children: [user.firstName, " ", user.lastName] }), _jsx("p", { className: "text-sm text-gray-600", children: user.email })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Email Address" }), _jsx("input", { type: "email", className: cn("w-full px-5 py-3.5 bg-white border-2 rounded-xl focus:outline-none focus:ring-0 transition-all font-medium", errors.email ? "border-red-500 focus:border-red-500" : "border-gray-100 focus:border-black"), placeholder: "john@example.com", value: formData.email, onChange: e => setFormData({ ...formData, email: e.target.value }) }), _jsx("p", { className: "text-xs text-gray-500 ml-1", children: "You can use a different email for this order" }), errors.email && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.email })] })] })) : (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Email Address" }), _jsx("input", { type: "email", className: cn("w-full px-5 py-3.5 bg-white border-2 rounded-xl focus:outline-none focus:ring-0 transition-all font-medium", errors.email ? "border-red-500 focus:border-red-500" : "border-gray-100 focus:border-black"), placeholder: "john@example.com", value: formData.email, onChange: e => setFormData({ ...formData, email: e.target.value }) }), errors.email && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.email })] })) })] }), _jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-orange-50 text-orange-600 rounded-xl", children: _jsx(MapPin, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: storeConfig.settings?.shippingEnabled === false ? 'Billing Address' : 'Shipping Address' })] }), isAuthenticated && (_jsx(AddressSelector, { addresses: savedAddresses, selectedAddressId: selectedAddressId, onSelect: handleAddressSelect, isLoading: loadingAddresses, isAuthenticated: isAuthenticated })), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-5", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "First Name" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.firstName ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), value: formData.firstName, onChange: e => setFormData({ ...formData, firstName: e.target.value }) }), errors.firstName && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.firstName })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Last Name" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.lastName ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), value: formData.lastName, onChange: e => setFormData({ ...formData, lastName: e.target.value }) }), errors.lastName && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.lastName })] }), _jsxs("div", { className: "space-y-1.5 md:col-span-2", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.address ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "123 Main St", value: formData.address, onChange: e => setFormData({ ...formData, address: e.target.value }) }), errors.address && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.address })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Country" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsCountryDropdownOpen(!isCountryDropdownOpen), className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.country ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), children: [_jsx("span", { className: "flex items-center gap-2", children: (() => {
1382
902
  const selectedCountry = getCountryByName(formData.country);
1383
903
  return selectedCountry ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-lg", children: selectedCountry.flag }), _jsx("span", { children: selectedCountry.name })] })) : (_jsx("span", { className: "text-gray-400", children: "Select country" }));
1384
- })() }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isCountryDropdownOpen && "transform rotate-180") })] }), isCountryDropdownOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => { setIsCountryDropdownOpen(false); setCountrySearch(''); } }), _jsxs("div", { className: "absolute z-30 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-xl overflow-hidden", children: [_jsx("div", { className: "p-2 border-b border-gray-100", children: _jsx("input", { autoFocus: true, type: "text", placeholder: "Search country...", value: countrySearch, onChange: (e) => setCountrySearch(e.target.value), className: "w-full px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:border-black", onClick: (e) => e.stopPropagation() }) }), _jsxs("div", { className: "max-h-52 overflow-y-auto", children: [countries.filter(c => c.name.toLowerCase().includes(countrySearch.toLowerCase())).map((country) => (_jsxs("button", { type: "button", onClick: () => {
1385
- setFormData(prev => ({
1386
- ...prev,
1387
- country: country.name,
1388
- city: '',
1389
- state: '',
1390
- }));
1391
- setIsCountryDropdownOpen(false);
1392
- setCountrySearch('');
1393
- setIsCityDropdownOpen(false);
1394
- setIsStateDropdownOpen(false);
1395
- }, className: cn("w-full px-5 py-3 text-left flex items-center gap-3 hover:bg-gray-50 transition-colors", formData.country === country.name && "bg-gray-50 font-medium"), children: [_jsx("span", { className: "text-lg", children: country.flag }), _jsx("span", { children: country.name })] }, country.code))), countries.filter(c => c.name.toLowerCase().includes(countrySearch.toLowerCase())).length === 0 && (_jsx("div", { className: "px-5 py-3 text-gray-400 text-sm", children: "No countries found" }))] })] })] }))] }), errors.country && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.country })] }), isNigeria(formData.country) && (_jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "State" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsStateDropdownOpen(!isStateDropdownOpen), className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.state ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), children: [_jsx("span", { className: formData.state ? "text-gray-900" : "text-gray-400", children: formData.state || 'Select state' }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isStateDropdownOpen && "transform rotate-180") })] }), isStateDropdownOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsStateDropdownOpen(false) }), _jsx("div", { className: "absolute z-30 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-xl max-h-52 overflow-y-auto", children: NIGERIAN_STATES.map((state) => (_jsx("button", { type: "button", onClick: () => {
1396
- // Clear city when state changes so stale city isn't submitted
1397
- setFormData({ ...formData, state, city: '' });
1398
- setIsStateDropdownOpen(false);
1399
- }, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.state === state && "bg-gray-50 font-medium"), children: state }, state))) })] }))] }), errors.state && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.state })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "City" }), _jsxs("div", { className: "relative", children: [_jsx("input", { type: "text", placeholder: isNigeria(formData.country) && !formData.state ? 'Select a state first' : 'e.g. Lagos, Lekki, Ikeja', disabled: isNigeria(formData.country) && !formData.state, value: formData.city, onChange: (e) => {
1400
- setFormData({ ...formData, city: e.target.value });
1401
- setCitySearch(e.target.value);
1402
- setIsCityDropdownOpen(true);
1403
- }, onFocus: () => {
1404
- if (!formData.country) {
904
+ })() }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isCountryDropdownOpen && "transform rotate-180") })] }), isCountryDropdownOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsCountryDropdownOpen(false) }), _jsx("div", { className: "absolute z-20 w-full mt-1 bg-white border-2 border-gray-100 rounded-xl shadow-xl max-h-64 overflow-y-auto", children: countries.map((country) => (_jsxs("button", { type: "button", onClick: () => {
905
+ setFormData({ ...formData, country: country.name, city: '' }); // Clear city when country changes
906
+ setIsCountryDropdownOpen(false);
907
+ setIsCityDropdownOpen(false);
908
+ }, className: cn("w-full px-5 py-3 text-left flex items-center gap-3 hover:bg-gray-50 transition-colors", formData.country === country.name && "bg-gray-50 font-medium"), children: [_jsx("span", { className: "text-lg", children: country.flag }), _jsx("span", { children: country.name })] }, country.code))) })] }))] }), errors.country && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.country })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "City" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => {
909
+ if (formData.country) {
910
+ setIsCityDropdownOpen(!isCityDropdownOpen);
911
+ }
912
+ else {
1405
913
  addToast('Please select a country first', 'error');
1406
- return;
1407
914
  }
1408
- setIsCityDropdownOpen(true);
1409
- }, onBlur: () => setTimeout(() => setIsCityDropdownOpen(false), 150), className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.city ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black", isNigeria(formData.country) && !formData.state && "bg-gray-50 text-gray-400 cursor-not-allowed") }), isCityDropdownOpen && formData.country && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsCityDropdownOpen(false) }), (() => {
1410
- const nigeriaState = isNigeria(formData.country) ? formData.state : null;
1411
- const suggestions = nigeriaState
1412
- ? getNigerianCitiesByState(nigeriaState).filter(c => c.toLowerCase().includes((formData.city || '').toLowerCase()))
1413
- : getCitiesByCountry(formData.country).filter(c => c.toLowerCase().includes((formData.city || '').toLowerCase()));
1414
- if (suggestions.length === 0)
1415
- return null;
1416
- return (_jsx("div", { className: "absolute z-30 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-xl max-h-52 overflow-y-auto", children: suggestions.map((city) => (_jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => {
1417
- setFormData({ ...formData, city });
1418
- setIsCityDropdownOpen(false);
1419
- setCitySearch('');
1420
- }, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors text-base", formData.city === city && "bg-gray-50 font-medium"), children: city }, city))) }));
1421
- })()] }))] }), errors.city
1422
- ? _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.city })
1423
- : _jsx("p", { className: "text-[11px] text-gray-500 mt-1 ml-1", children: "Type your city or town name \u2014 you can enter any location not shown in suggestions." })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Postal/Zip Code" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.postalCode ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "Optional", value: formData.postalCode, onChange: e => setFormData({ ...formData, postalCode: e.target.value }) }), errors.postalCode && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.postalCode })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Phone" }), _jsx("input", { type: "tel", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.phone ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: phonePlaceholder, value: formData.phone, onChange: e => handlePhoneChange(e.target.value) }), errors.phone && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.phone })] })] }), isAuthenticated && !selectedAddressId && (_jsxs("div", { className: "mt-6 space-y-3", children: [_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group", children: [_jsx("input", { type: "checkbox", checked: saveNewAddress, onChange: (e) => setSaveNewAddress(e.target.checked), className: "w-5 h-5 rounded border-2 border-gray-300 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsx("span", { className: "text-sm font-medium text-gray-700 group-hover:text-black transition-colors", children: "Save this address for future use" })] }), saveNewAddress && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address Label (optional)" }), _jsx("input", { type: "text", value: addressLabel, onChange: (e) => setAddressLabel(e.target.value), placeholder: "e.g., Home, Work, Office", className: "w-full px-5 py-3.5 bg-white border-2 border-gray-100 rounded-xl focus:outline-none focus:ring-0 focus:border-black transition-all font-medium" })] }))] }))] }), deliveryEnabled && !automatedShippingEnabled && (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-4", children: [_jsx("div", { className: "p-2 bg-amber-50 text-amber-700 rounded-xl", children: _jsx(Truck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Delivery" })] }), _jsx(MerchantHandledDeliveryNotice, { storeName: storeConfig.name })] })), deliveryEnabled && automatedShippingEnabled && canRequestShippingQuote(buildQuoteAddress()) && (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-purple-50 text-purple-600 rounded-xl", children: _jsx(Truck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Shipping Method" })] }), !isCourierMode && freeShippingThreshold !== undefined && !freeShippingEligible && (_jsxs("div", { className: "mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg", children: [_jsxs("p", { className: "text-xs font-medium text-blue-900 mb-1", children: ["Spend ", formatCurrency(freeShippingThreshold - subtotal, currency), " more for free shipping!"] }), _jsx("div", { className: "w-full bg-blue-200 rounded-full h-2", children: _jsx("div", { className: "bg-blue-600 h-2 rounded-full transition-all", style: {
915
+ }, disabled: !formData.country, className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.city ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black", !formData.country && "bg-gray-50 text-gray-400 cursor-not-allowed"), children: [_jsx("span", { className: formData.city ? "text-gray-900" : "text-gray-400", children: formData.city || 'Select city' }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isCityDropdownOpen && "transform rotate-180") })] }), isCityDropdownOpen && formData.country && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsCityDropdownOpen(false) }), _jsx("div", { className: "absolute z-20 w-full mt-1 bg-white border-2 border-gray-100 rounded-xl shadow-xl max-h-64 overflow-y-auto", children: getCitiesByCountry(formData.country).length > 0 ? (getCitiesByCountry(formData.country).map((city) => (_jsx("button", { type: "button", onClick: () => {
916
+ setFormData({ ...formData, city });
917
+ setIsCityDropdownOpen(false);
918
+ }, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.city === city && "bg-gray-50 font-medium"), children: city }, city)))) : (_jsx("div", { className: "px-5 py-3 text-gray-500 text-sm", children: "No cities available for this country" })) })] }))] }), errors.city && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.city })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Postal/Zip Code" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.postalCode ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "Optional", value: formData.postalCode, onChange: e => setFormData({ ...formData, postalCode: e.target.value }) }), errors.postalCode && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.postalCode })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Phone" }), _jsx("input", { type: "tel", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.phone ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), value: formData.phone, onChange: e => setFormData({ ...formData, phone: e.target.value }) }), errors.phone && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.phone })] })] }), isAuthenticated && !selectedAddressId && (_jsxs("div", { className: "mt-6 space-y-3", children: [_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group", children: [_jsx("input", { type: "checkbox", checked: saveNewAddress, onChange: (e) => setSaveNewAddress(e.target.checked), className: "w-5 h-5 rounded border-2 border-gray-300 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsx("span", { className: "text-sm font-medium text-gray-700 group-hover:text-black transition-colors", children: "Save this address for future use" })] }), saveNewAddress && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address Label (optional)" }), _jsx("input", { type: "text", value: addressLabel, onChange: (e) => setAddressLabel(e.target.value), placeholder: "e.g., Home, Work, Office", className: "w-full px-5 py-3.5 bg-white border-2 border-gray-100 rounded-xl focus:outline-none focus:ring-0 focus:border-black transition-all font-medium" })] }))] }))] }), storeConfig.settings?.shippingEnabled !== false && (formData.city && formData.country) && (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-purple-50 text-purple-600 rounded-xl", children: _jsx(Truck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Shipping Method" })] }), freeShippingThreshold !== undefined && !freeShippingEligible && (_jsxs("div", { className: "mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg", children: [_jsxs("p", { className: "text-xs font-medium text-blue-900 mb-1", children: ["Spend ", formatCurrency(freeShippingThreshold - subtotal, currency), " more for free shipping!"] }), _jsx("div", { className: "w-full bg-blue-200 rounded-full h-2", children: _jsx("div", { className: "bg-blue-600 h-2 rounded-full transition-all", style: {
1424
919
  width: `${Math.min(100, (subtotal / freeShippingThreshold) * 100)}%`,
1425
- } }) })] })), checkingDeliveryStatus || (!isCourierMode && isCalculatingShipping) ? (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-600", children: [_jsx("div", { className: "animate-spin rounded-full h-5 w-5 border-b-2 border-gray-900" }), _jsx("span", { className: "text-sm font-medium", children: "Calculating shipping options..." })] }) })) : isCourierMode ? (_jsx(ShippingOptionsPanel, { storeId: storeConfig.id, address: buildQuoteAddress(), packages: checkoutItems.map((item) => ({
1426
- weight: item.product?.weight || 0.5,
1427
- value: item.price,
1428
- name: item.product?.name,
1429
- quantity: item.quantity,
1430
- })), customerInfo: {
1431
- firstName: formData.firstName,
1432
- lastName: formData.lastName,
1433
- phone: formData.phone,
1434
- email: formData.email,
1435
- }, onSelect: handleCourierSelect, selected: selectedCourier })) : shippingError ? (_jsx("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", children: _jsx("p", { className: "text-sm text-red-600", children: shippingError }) })) : shippingMethods.length > 0 ? (_jsx("div", { className: "space-y-3", children: shippingMethods.map((method) => (_jsxs("label", { className: cn("flex items-center justify-between p-4 border-2 rounded-xl cursor-pointer transition-all", selectedShippingMethod?.code === method.code
920
+ } }) })] })), isCalculatingShipping ? (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-600", children: [_jsx("div", { className: "animate-spin rounded-full h-5 w-5 border-b-2 border-gray-900" }), _jsx("span", { className: "text-sm font-medium", children: "Calculating shipping options..." })] }) })) : shippingError ? (_jsx("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", children: _jsx("p", { className: "text-sm text-red-600", children: shippingError }) })) : shippingMethods.length > 0 ? (_jsx("div", { className: "space-y-3", children: shippingMethods.map((method) => (_jsxs("label", { className: cn("flex items-center justify-between p-4 border-2 rounded-xl cursor-pointer transition-all", selectedShippingMethod?.code === method.code
1436
921
  ? "border-black bg-gray-50"
1437
922
  : "border-gray-200 hover:border-gray-300 bg-white"), children: [_jsxs("div", { className: "flex items-center gap-4 flex-1", children: [_jsx("input", { type: "radio", name: "shippingMethod", value: method.code, checked: selectedShippingMethod?.code === method.code, onChange: () => setSelectedShippingMethod(method), className: "w-5 h-5 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "font-bold text-gray-900", children: method.name }), freeShippingEligible && method.cost === 0 && (_jsx("span", { className: "text-xs font-normal text-green-700 bg-green-100 px-2 py-0.5 rounded-full", children: "FREE" }))] }), method.description && (_jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: method.description })), method.estimatedDays && (_jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: method.estimatedDaysMin && method.estimatedDaysMax
1438
923
  ? `Estimated delivery: ${method.estimatedDaysMin}-${method.estimatedDaysMax} business days`
@@ -1440,11 +925,7 @@ export function CheckoutPage({ storeConfig }) {
1440
925
  ? 'FREE'
1441
926
  : Number.isFinite(method.cost) && method.cost >= 0
1442
927
  ? formatCurrency(method.cost, currency)
1443
- : 'Not available' }) })] }, method.code))) })) : (_jsx("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-lg text-center", children: _jsx("p", { className: "text-sm text-gray-600", children: "No shipping methods available. Please check your address." }) }))] })), _jsxs(Button, { type: "submit", size: "lg", className: "w-full h-14 text-lg font-bold rounded-2xl shadow-xl hover:shadow-2xl hover:-translate-y-0.5 transition-all text-white [&_svg]:text-white", style: { backgroundColor: primaryColor, color: '#fff' }, children: ["Continue to Payment ", _jsx(ChevronRight, { className: "ml-2 w-5 h-5" })] })] })) : (_jsxs("div", { className: "space-y-8 animate-fade-in", children: [deliveryEnabled && (_jsxs("section", { className: "bg-white p-6 rounded-3xl shadow-sm border border-gray-100 flex justify-between items-center group hover:border-gray-300 transition-colors cursor-pointer", onClick: () => setStep('details'), children: [_jsxs("div", { className: "flex gap-4 items-center", children: [_jsx("div", { className: "w-10 h-10 bg-green-50 rounded-full flex items-center justify-center border border-green-100", children: _jsx(CheckCircle, { className: "w-5 h-5 text-green-600" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-xs text-gray-500 font-bold uppercase tracking-wider mb-1", children: "Shipping To" }), _jsxs("p", { className: "font-bold text-gray-900", children: [formData.firstName, " ", formData.lastName] }), _jsxs("p", { className: "text-sm text-gray-500", children: [formData.address, ", ", formData.city] })] })] }), _jsx("span", { className: "text-sm font-bold text-blue-600 px-4 py-2 bg-blue-50 rounded-lg group-hover:bg-blue-100 transition-colors", children: "Edit" })] })), _jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-blue-50 text-blue-600 rounded-xl", children: _jsx(ShieldCheck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Payment Security" })] }), _jsx("div", { className: "bg-blue-50/50 p-6 rounded-2xl border border-blue-100 mb-6", children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx(Lock, { className: "w-5 h-5 text-blue-600 mt-1" }), _jsxs("div", { children: [_jsx("h4", { className: "font-bold text-blue-900 mb-1", children: "Encrypted Transaction" }), _jsx("p", { className: "text-sm text-blue-700/80 leading-relaxed", children: "Your payment is processed securely by Payaza. We do not store your card details." })] })] }) }), _jsxs("div", { className: "flex items-center justify-center gap-2 text-xs font-bold text-gray-400 uppercase tracking-widest", children: [_jsx(Lock, { className: "w-3 h-3" }), "256-bit SSL Secure Payment"] })] }), isHoldBlocking && (_jsxs("div", { className: "rounded-2xl border border-red-200 bg-red-50 p-4 flex gap-3", children: [_jsx(AlertCircle, { className: "w-5 h-5 text-red-700 flex-shrink-0 mt-0.5" }), _jsxs("div", { children: [_jsx("p", { className: "font-bold text-red-900 text-sm", children: "Checkout reservation expired" }), _jsx("p", { className: "mt-1 text-sm text-red-800", children: "Your stock hold has expired. Go back to cart and start checkout again to reserve items." })] })] })), hasCheckoutStockIssues && (_jsxs("div", { className: "rounded-2xl border border-amber-200 bg-amber-50 p-4 flex gap-3", children: [_jsx(AlertCircle, { className: "w-5 h-5 text-amber-700 flex-shrink-0 mt-0.5" }), _jsxs("div", { children: [_jsx("p", { className: "font-bold text-amber-900 text-sm", children: "Stock availability changed" }), _jsx("ul", { className: "mt-2 space-y-1 text-sm text-amber-800", children: checkoutStockIssues.map((issue) => (_jsx("li", { children: issue.message }, issue.id))) }), _jsx("p", { className: "mt-2 text-xs text-amber-700", children: "Update your cart before paying. Payment is only available when all items are in stock." })] })] })), _jsx(Button, { onClick: handlePayazaCheckout, size: "lg", className: "w-full h-16 text-lg font-bold rounded-2xl shadow-xl hover:shadow-2xl hover:-translate-y-0.5 transition-all text-white [&_svg]:text-white", style: { backgroundColor: primaryColor, color: '#fff' }, disabled: loading || isPayazaLoading || hasCheckoutStockIssues || isHoldBlocking || checkoutDisabled, children: loading || isPayazaLoading ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" }), _jsx("span", { children: "Processing Securely..." })] })) : checkoutDisabled ? (_jsx("span", { children: "Checkout unavailable" })) : (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Lock, { className: "w-5 h-5" }), _jsx("span", { children: "Pay Now" })] })) }), checkoutDisabled && (_jsx("p", { className: "mt-3 text-sm text-center text-gray-500", children: CHECKOUT_UNAVAILABLE_MESSAGE }))] })) }), _jsxs("div", { className: "w-full lg:w-[400px] flex-shrink-0", children: [_jsxs("div", { className: "bg-white p-8 rounded-3xl shadow-lg border border-gray-100 sticky top-8", children: [_jsx("h2", { className: "text-xl font-black text-gray-900 mb-6", children: "Order Summary" }), _jsx("div", { className: "mb-6", children: _jsx(InventoryHoldCountdown, { holdUntil: inventoryHoldUntil, onExpired: () => {
1444
- setIsHoldExpired(true);
1445
- handleInventoryHoldExpired?.();
1446
- } }) }), _jsx("div", { className: "space-y-6 mb-8 max-h-[400px] overflow-y-auto scrollbar-hide", children: checkoutItems.map((item) => {
1447
- const stockInfo = getCartLineStockInfo(item.product, item.quantity);
1448
- return (_jsxs("div", { className: "flex gap-4 group", children: [_jsxs("div", { className: "w-20 h-20 bg-gray-100 rounded-xl overflow-hidden flex-shrink-0 relative border border-gray-200", children: [_jsx(Image, { src: getCartLineImage(item) || item.product.images[0], alt: item.product.name, fill: true, className: "object-cover group-hover:scale-105 transition-transform duration-500", unoptimized: true }), _jsxs("span", { className: "absolute top-0 right-0 bg-black text-white text-[10px] font-bold px-2 py-1 rounded-bl-lg z-10", children: ["x", item.quantity] })] }), _jsxs("div", { className: "flex-1 py-1", children: [_jsx("p", { className: "font-bold text-gray-900 text-sm line-clamp-2 leading-relaxed", children: item.product.name }), item.variantName && _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: item.variantName }), stockInfo.displayLabel && (_jsx("p", { className: cn('text-xs font-semibold mt-1', stockInfo.stockError ? 'text-red-600' : 'text-amber-700'), children: stockInfo.displayLabel })), _jsx("p", { className: "text-gray-500 text-sm font-medium mt-1", children: formatCurrency(normalizePrice(item.price), item.product?.currency || currency) })] })] }, item.id));
1449
- }) }), isMarketplaceCheckout && storeBreakdown.length > 1 && (_jsxs("div", { className: "mb-6 rounded-2xl bg-gray-50 border border-gray-100 p-4", children: [_jsxs("p", { className: "text-xs font-bold text-gray-700 mb-3", children: ["Your order will be split into ", storeBreakdown.length, " separate orders \u2014 one per store"] }), _jsx("div", { className: "space-y-2", children: storeBreakdown.map((store) => (_jsxs("div", { className: "flex justify-between text-xs text-gray-600", children: [_jsxs("span", { className: "font-medium truncate pr-2", children: [store.storeName, " (", store.itemCount, " item", store.itemCount === 1 ? '' : 's', ")"] }), _jsx("span", { className: "font-bold text-gray-900 flex-shrink-0", children: formatCurrency(store.subtotal, currency) })] }, store.storeId))) })] })), _jsxs("div", { className: "space-y-4 pt-6 border-t-2 border-gray-50", children: [_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: "Subtotal" }), _jsx("span", { className: "text-gray-900 font-bold", children: formatCurrency(subtotal, currency) })] }), deliveryEnabled && (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsxs("span", { children: ["Shipping", selectedShippingMethod ? ` (${selectedShippingMethod.name})` : ''] }), _jsx("span", { className: "text-gray-900 font-bold", children: !automatedShippingEnabled ? (_jsx("span", { className: "text-amber-700", children: "Handled by merchant" })) : isCalculatingShipping ? (_jsx("span", { className: "text-gray-400", children: "Calculating..." })) : !selectedShippingMethod ? (_jsx("span", { className: "text-gray-400", children: "Not available" })) : shipping === 0 && freeShippingEligible ? (_jsx("span", { className: "text-green-600", children: "Free" })) : shipping === 0 ? ('Free') : Number.isFinite(shipping) && shipping > 0 ? (formatCurrency(shipping, currency)) : (_jsx("span", { className: "text-gray-400", children: "Not available" })) })] })), feesLoading && (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: "Fees" }), _jsx("span", { className: "text-gray-900 font-bold", children: "Calculating..." })] })), !feesLoading && (_jsx(CheckoutFeeLines, { lines: displayLines, currency: currency, excludeKeys: ['shipping'], className: "flex justify-between text-gray-500 font-medium text-sm", labelClassName: "", amountClassName: "text-gray-900 font-bold" })), _jsxs("div", { className: "flex justify-between items-end pt-4 mt-2 border-t border-gray-100", children: [_jsx("span", { className: "text-base font-bold text-gray-900", children: "Total to pay" }), _jsx("span", { className: "text-3xl font-black text-gray-900 tracking-tight", children: formatCurrency(total, currency) })] })] }), _jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100 grid grid-cols-3 gap-2 opacity-50 grayscale", children: [_jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" }), _jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" }), _jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" })] })] }), PROMO_CODE_ENABLED && (_jsxs("div", { className: "bg-white p-6 rounded-3xl shadow-lg border border-gray-100 mt-6 sticky top-[500px]", children: [_jsxs("h3", { className: "font-bold text-gray-900 mb-4 flex items-center gap-2", children: [_jsx(Tag, { className: "w-4 h-4" }), " Have a promo code?"] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { type: "text", value: promoCode, onChange: (e) => setPromoCode(e.target.value), placeholder: "Enter code", className: "flex-1 px-4 py-2 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:border-black transition-all uppercase placeholder:normal-case font-medium" }), _jsx(Button, { onClick: handleApplyPromo, disabled: !promoCode || isPromoApplied, className: "bg-black text-white rounded-xl", style: isPromoApplied ? { backgroundColor: '#22c55e' } : {}, children: isPromoApplied ? 'Applied' : 'Apply' })] }), isPromoApplied && (_jsxs("p", { className: "text-green-600 text-xs font-bold mt-2 flex items-center gap-1", children: [_jsx(CheckCircle, { className: "w-3 h-3" }), " Discount applied to total"] }))] }))] })] })] }) }));
928
+ : 'Not available' }) })] }, method.code))) })) : (_jsx("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-lg text-center", children: _jsx("p", { className: "text-sm text-gray-600", children: "No shipping methods available. Please check your address." }) }))] })), _jsxs(Button, { type: "submit", size: "lg", className: "w-full h-14 text-lg font-bold rounded-2xl shadow-xl hover:shadow-2xl hover:-translate-y-0.5 transition-all", style: { backgroundColor: primaryColor }, children: ["Continue to Payment ", _jsx(ChevronRight, { className: "ml-2 w-5 h-5" })] })] })) : (_jsxs("div", { className: "space-y-8 animate-fade-in", children: [_jsxs("section", { className: "bg-white p-6 rounded-3xl shadow-sm border border-gray-100 flex justify-between items-center group hover:border-gray-300 transition-colors cursor-pointer", onClick: () => setStep('details'), children: [_jsxs("div", { className: "flex gap-4 items-center", children: [_jsx("div", { className: "w-10 h-10 bg-green-50 rounded-full flex items-center justify-center border border-green-100", children: _jsx(CheckCircle, { className: "w-5 h-5 text-green-600" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-xs text-gray-500 font-bold uppercase tracking-wider mb-1", children: "Shipping To" }), _jsxs("p", { className: "font-bold text-gray-900", children: [formData.firstName, " ", formData.lastName] }), _jsxs("p", { className: "text-sm text-gray-500", children: [formData.address, ", ", formData.city] })] })] }), _jsx("span", { className: "text-sm font-bold text-blue-600 px-4 py-2 bg-blue-50 rounded-lg group-hover:bg-blue-100 transition-colors", children: "Edit" })] }), isFreeOrder ? (_jsxs("section", { className: "bg-green-50 p-8 rounded-3xl shadow-sm border border-green-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-4", children: [_jsx("div", { className: "p-2 bg-white text-green-600 rounded-xl", children: _jsx(CheckCircle, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Free Order" })] }), _jsx("p", { className: "text-sm text-green-800 leading-relaxed", children: "No payment is required. Complete your details and confirm to get access to your digital product." })] })) : (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-blue-50 text-blue-600 rounded-xl", children: _jsx(ShieldCheck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Payment Security" })] }), _jsx("div", { className: "bg-blue-50/50 p-6 rounded-2xl border border-blue-100 mb-6", children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx(Lock, { className: "w-5 h-5 text-blue-600 mt-1" }), _jsxs("div", { children: [_jsx("h4", { className: "font-bold text-blue-900 mb-1", children: "Encrypted Transaction" }), _jsx("p", { className: "text-sm text-blue-700/80 leading-relaxed", children: "Your payment is processed securely by Payaza. We do not store your card details." })] })] }) }), _jsxs("div", { className: "flex items-center justify-center gap-2 text-xs font-bold text-gray-400 uppercase tracking-widest", children: [_jsx(Lock, { className: "w-3 h-3" }), "256-bit SSL Secure Payment"] })] })), _jsx(Button, { onClick: handlePayazaCheckout, size: "lg", className: "w-full h-16 text-lg font-bold rounded-2xl shadow-xl hover:shadow-2xl hover:-translate-y-0.5 transition-all", style: { backgroundColor: isFreeOrder ? '#059669' : primaryColor }, disabled: loading || isPayazaLoading, children: loading || isPayazaLoading ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" }), _jsx("span", { children: isFreeOrder ? 'Completing order...' : 'Processing Securely...' })] })) : (_jsxs("div", { className: "flex items-center gap-2", children: [isFreeOrder ? _jsx(CheckCircle, { className: "w-5 h-5" }) : _jsx(Lock, { className: "w-5 h-5" }), _jsx("span", { children: isFreeOrder ? 'Complete Free Order' : 'Pay Now' })] })) })] })) }), _jsxs("div", { className: "w-full lg:w-[400px] flex-shrink-0", children: [_jsxs("div", { className: "bg-white p-8 rounded-3xl shadow-lg border border-gray-100 sticky top-8", children: [_jsx("h2", { className: "text-xl font-black text-gray-900 mb-6", children: "Order Summary" }), _jsx("div", { className: "space-y-6 mb-8 max-h-[400px] overflow-y-auto scrollbar-hide", children: checkoutItems.map((item) => (_jsxs("div", { className: "flex gap-4 group", children: [_jsxs("div", { className: "w-20 h-20 bg-gray-100 rounded-xl overflow-hidden flex-shrink-0 relative border border-gray-200", children: [_jsx(Image, { src: item.product.images[0], alt: item.product.name, fill: true, className: "object-cover group-hover:scale-105 transition-transform duration-500", unoptimized: true }), _jsxs("span", { className: "absolute top-0 right-0 bg-black text-white text-[10px] font-bold px-2 py-1 rounded-bl-lg z-10", children: ["x", item.quantity] })] }), _jsxs("div", { className: "flex-1 py-1", children: [_jsx("p", { className: "font-bold text-gray-900 text-sm line-clamp-2 leading-relaxed", children: item.product.name }), item.variantName && _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: item.variantName }), _jsx("p", { className: "text-gray-500 text-sm font-medium mt-1", children: normalizePrice(item.price) <= 0
929
+ ? 'Free'
930
+ : formatCurrency(normalizePrice(item.price), item.product?.currency || currency) })] })] }, item.id))) }), _jsxs("div", { className: "space-y-4 pt-6 border-t-2 border-gray-50", children: [_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: "Subtotal" }), _jsx("span", { className: "text-gray-900 font-bold", children: isFreeOrder ? 'Free' : formatCurrency(subtotal, currency) })] }), _jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsxs("span", { children: ["Shipping", selectedShippingMethod ? ` (${selectedShippingMethod.name})` : ''] }), _jsx("span", { className: "text-gray-900 font-bold", children: isCalculatingShipping ? (_jsx("span", { className: "text-gray-400", children: "Calculating..." })) : !selectedShippingMethod ? (_jsx("span", { className: "text-gray-400", children: "Not available" })) : shipping === 0 && freeShippingEligible ? (_jsx("span", { className: "text-green-600", children: "Free" })) : shipping === 0 ? ('Free') : Number.isFinite(shipping) && shipping > 0 ? (formatCurrency(shipping, currency)) : (_jsx("span", { className: "text-gray-400", children: "Not available" })) })] }), customerFeeLines.map((line) => (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: line.label }), _jsx("span", { className: "text-gray-900 font-bold", children: formatCurrency(line.amount, currency) })] }, line.key))), !feePreview && vatAmount > 0 && (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: vatLabel }), _jsx("span", { className: "text-gray-900 font-bold", children: formatCurrency(vatAmount, currency) })] })), _jsxs("div", { className: "flex justify-between items-end pt-4 mt-2 border-t border-gray-100", children: [_jsx("span", { className: "text-base font-bold text-gray-900", children: "Total to pay" }), _jsx("span", { className: "text-3xl font-black text-gray-900 tracking-tight", children: isCalculatingFees ? 'Calculating…' : isFreeOrder ? 'Free' : formatCurrency(total, currency) })] })] }), _jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100 grid grid-cols-3 gap-2 opacity-50 grayscale", children: [_jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" }), _jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" }), _jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" })] })] }), _jsxs("div", { className: "bg-white p-6 rounded-3xl shadow-lg border border-gray-100 mt-6 sticky top-[500px]", children: [_jsxs("h3", { className: "font-bold text-gray-900 mb-4 flex items-center gap-2", children: [_jsx(Tag, { className: "w-4 h-4" }), " Have a promo code?"] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { type: "text", value: promoCode, onChange: (e) => setPromoCode(e.target.value), placeholder: "Enter code", className: "flex-1 px-4 py-2 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:border-black transition-all uppercase placeholder:normal-case font-medium" }), _jsx(Button, { onClick: handleApplyPromo, disabled: !promoCode || isPromoApplied, className: "bg-black text-white rounded-xl", style: isPromoApplied ? { backgroundColor: '#22c55e' } : {}, children: isPromoApplied ? 'Applied' : 'Apply' })] }), isPromoApplied && (_jsxs("p", { className: "text-green-600 text-xs font-bold mt-2 flex items-center gap-1", children: [_jsx(CheckCircle, { className: "w-3 h-3" }), " Discount applied to total"] }))] })] })] })] }) }));
1450
931
  }