@fayz-ai/storefront 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/README.md +3 -3
  2. package/dist/app/createStorefrontApp.d.ts +65 -0
  3. package/dist/app/createStorefrontApp.d.ts.map +1 -0
  4. package/dist/{define.d.ts → app/define.d.ts} +3 -3
  5. package/dist/app/define.d.ts.map +1 -0
  6. package/dist/app/hydration.d.ts +37 -0
  7. package/dist/app/hydration.d.ts.map +1 -0
  8. package/dist/app/prerender.d.ts +57 -0
  9. package/dist/app/prerender.d.ts.map +1 -0
  10. package/dist/{router.d.ts → app/router.d.ts} +140 -113
  11. package/dist/app/router.d.ts.map +1 -0
  12. package/dist/{scaffold.d.ts → app/scaffold.d.ts} +2 -2
  13. package/dist/app/scaffold.d.ts.map +1 -0
  14. package/dist/app/serving.d.ts +70 -0
  15. package/dist/app/serving.d.ts.map +1 -0
  16. package/dist/app/static-render.d.ts +46 -0
  17. package/dist/app/static-render.d.ts.map +1 -0
  18. package/dist/blocks/block-at-path.d.ts +59 -0
  19. package/dist/blocks/block-at-path.d.ts.map +1 -0
  20. package/dist/blocks/page-blocks.d.ts +163 -0
  21. package/dist/blocks/page-blocks.d.ts.map +1 -0
  22. package/dist/blocks/page-data.d.ts +105 -0
  23. package/dist/blocks/page-data.d.ts.map +1 -0
  24. package/dist/{blocks.d.ts → blocks/registry.d.ts} +1 -1
  25. package/dist/blocks/registry.d.ts.map +1 -0
  26. package/dist/{sections.d.ts → blocks/sections.d.ts} +40 -2
  27. package/dist/blocks/sections.d.ts.map +1 -0
  28. package/dist/chunk-6B7H3IZ6.js +10232 -0
  29. package/dist/chunk-6B7H3IZ6.js.map +1 -0
  30. package/dist/chunk-6IXTENLB.js +162 -0
  31. package/dist/chunk-6IXTENLB.js.map +1 -0
  32. package/dist/components/PaymentTerms.d.ts +34 -0
  33. package/dist/components/PaymentTerms.d.ts.map +1 -0
  34. package/dist/components/SmoothImage.d.ts +1 -1
  35. package/dist/components/SmoothImage.d.ts.map +1 -1
  36. package/dist/components/account/CustomerAccountShell.d.ts.map +1 -0
  37. package/dist/components/account/SignInModal.d.ts.map +1 -0
  38. package/dist/components/cart/CartDrawer.d.ts.map +1 -0
  39. package/dist/components/cart/QuantityInput.d.ts.map +1 -0
  40. package/dist/components/checkout/DeliveryEstimator.d.ts.map +1 -0
  41. package/dist/components/collection/CategoryShowcase.d.ts +44 -0
  42. package/dist/components/collection/CategoryShowcase.d.ts.map +1 -0
  43. package/dist/components/collection/FiltersPanel.d.ts +19 -0
  44. package/dist/components/collection/FiltersPanel.d.ts.map +1 -0
  45. package/dist/components/collection/ProductGrid.d.ts.map +1 -0
  46. package/dist/components/layout/PageBodyBlocks.d.ts +40 -0
  47. package/dist/components/layout/PageBodyBlocks.d.ts.map +1 -0
  48. package/dist/components/layout/SearchOverlay.d.ts +13 -0
  49. package/dist/components/layout/SearchOverlay.d.ts.map +1 -0
  50. package/dist/components/layout/StorefrontErrorBoundary.d.ts.map +1 -0
  51. package/dist/components/layout/StorefrontFooter.d.ts.map +1 -0
  52. package/dist/components/layout/StorefrontHeader.d.ts.map +1 -0
  53. package/dist/components/layout/Toaster.d.ts.map +1 -0
  54. package/dist/components/marketing/CollectionSections.d.ts +463 -0
  55. package/dist/components/marketing/CollectionSections.d.ts.map +1 -0
  56. package/dist/components/marketing/ConversionSections.d.ts +221 -0
  57. package/dist/components/marketing/ConversionSections.d.ts.map +1 -0
  58. package/dist/components/marketing/EditorialSections.d.ts +248 -0
  59. package/dist/components/marketing/EditorialSections.d.ts.map +1 -0
  60. package/dist/components/marketing/EntityList.d.ts +97 -0
  61. package/dist/components/marketing/EntityList.d.ts.map +1 -0
  62. package/dist/components/marketing/FormBlock.d.ts +167 -0
  63. package/dist/components/marketing/FormBlock.d.ts.map +1 -0
  64. package/dist/components/marketing/HeroSection.d.ts +125 -0
  65. package/dist/components/marketing/HeroSection.d.ts.map +1 -0
  66. package/dist/components/marketing/MediaCarousel.d.ts +135 -0
  67. package/dist/components/marketing/MediaCarousel.d.ts.map +1 -0
  68. package/dist/components/marketing/MiscSections.d.ts +252 -0
  69. package/dist/components/marketing/MiscSections.d.ts.map +1 -0
  70. package/dist/components/order/OrderTrackingTimeline.d.ts.map +1 -0
  71. package/dist/components/product/ProductCard.d.ts +14 -0
  72. package/dist/components/product/ProductCard.d.ts.map +1 -0
  73. package/dist/components/product/ProductEnquiryForm.d.ts.map +1 -0
  74. package/dist/components/product/ProductGallery.d.ts +36 -0
  75. package/dist/components/product/ProductGallery.d.ts.map +1 -0
  76. package/dist/components/{ProductOptionSelector.d.ts → product/ProductOptionSelector.d.ts} +1 -1
  77. package/dist/components/product/ProductOptionSelector.d.ts.map +1 -0
  78. package/dist/components/product/ProductRail.d.ts +93 -0
  79. package/dist/components/product/ProductRail.d.ts.map +1 -0
  80. package/dist/components/product/ProductReviews.d.ts +39 -0
  81. package/dist/components/product/ProductReviews.d.ts.map +1 -0
  82. package/dist/components/product/ProductSlider.d.ts +169 -0
  83. package/dist/components/product/ProductSlider.d.ts.map +1 -0
  84. package/dist/components/product/ProductSpecs.d.ts.map +1 -0
  85. package/dist/components/product/RelatedProducts.d.ts.map +1 -0
  86. package/dist/{auth.d.ts → data/auth.d.ts} +9 -0
  87. package/dist/data/auth.d.ts.map +1 -0
  88. package/dist/data/facets.d.ts +39 -0
  89. package/dist/data/facets.d.ts.map +1 -0
  90. package/dist/data/loaders.d.ts +56 -0
  91. package/dist/data/loaders.d.ts.map +1 -0
  92. package/dist/data/order-status.d.ts.map +1 -0
  93. package/dist/data/product-options.d.ts +36 -0
  94. package/dist/data/product-options.d.ts.map +1 -0
  95. package/dist/diagnostics/seo-audit.d.ts +54 -0
  96. package/dist/diagnostics/seo-audit.d.ts.map +1 -0
  97. package/dist/diagnostics/seo-audit.js +3 -0
  98. package/dist/diagnostics/seo-audit.js.map +1 -0
  99. package/dist/document/change.d.ts +100 -0
  100. package/dist/document/change.d.ts.map +1 -0
  101. package/dist/{config.d.ts → document/config.d.ts} +126 -7
  102. package/dist/document/config.d.ts.map +1 -0
  103. package/dist/document/describe.d.ts +135 -0
  104. package/dist/document/describe.d.ts.map +1 -0
  105. package/dist/document/overlay.d.ts +58 -0
  106. package/dist/document/overlay.d.ts.map +1 -0
  107. package/dist/{presets.d.ts → document/presets.d.ts} +2 -2
  108. package/dist/document/presets.d.ts.map +1 -0
  109. package/dist/document/render.d.ts +171 -0
  110. package/dist/document/render.d.ts.map +1 -0
  111. package/dist/document/round-trip.d.ts +46 -0
  112. package/dist/document/round-trip.d.ts.map +1 -0
  113. package/dist/document/sections.d.ts +194 -0
  114. package/dist/document/sections.d.ts.map +1 -0
  115. package/dist/document/upgrade.d.ts +112 -0
  116. package/dist/document/upgrade.d.ts.map +1 -0
  117. package/dist/document/validate.d.ts +24 -0
  118. package/dist/document/validate.d.ts.map +1 -0
  119. package/dist/{component-contracts.d.ts → extend/component-contracts.d.ts} +74 -22
  120. package/dist/extend/component-contracts.d.ts.map +1 -0
  121. package/dist/extend/component-override.d.ts +38 -0
  122. package/dist/extend/component-override.d.ts.map +1 -0
  123. package/dist/extend/component-selectors.d.ts.map +1 -0
  124. package/dist/extend/editor-bridge.d.ts +59 -0
  125. package/dist/extend/editor-bridge.d.ts.map +1 -0
  126. package/dist/extend/extension-points.d.ts +103 -0
  127. package/dist/extend/extension-points.d.ts.map +1 -0
  128. package/dist/extend/slots.d.ts +216 -0
  129. package/dist/extend/slots.d.ts.map +1 -0
  130. package/dist/hooks/index.d.ts +25 -6
  131. package/dist/hooks/index.d.ts.map +1 -1
  132. package/dist/hooks/use-address.d.ts +26 -0
  133. package/dist/hooks/use-address.d.ts.map +1 -0
  134. package/dist/hooks/use-cart.d.ts +3 -0
  135. package/dist/hooks/use-cart.d.ts.map +1 -0
  136. package/dist/hooks/use-catalog.d.ts +24 -0
  137. package/dist/hooks/use-catalog.d.ts.map +1 -0
  138. package/dist/hooks/use-categories.d.ts +19 -0
  139. package/dist/hooks/use-categories.d.ts.map +1 -0
  140. package/dist/hooks/use-checkout.d.ts +41 -0
  141. package/dist/hooks/use-checkout.d.ts.map +1 -0
  142. package/dist/hooks/use-discount-validator.d.ts +20 -0
  143. package/dist/hooks/use-discount-validator.d.ts.map +1 -0
  144. package/dist/hooks/use-enquiry.d.ts +7 -0
  145. package/dist/hooks/use-enquiry.d.ts.map +1 -0
  146. package/dist/hooks/{useMyOrders.d.ts → use-my-orders.d.ts} +1 -1
  147. package/dist/hooks/use-my-orders.d.ts.map +1 -0
  148. package/dist/hooks/use-order.d.ts +17 -0
  149. package/dist/hooks/use-order.d.ts.map +1 -0
  150. package/dist/hooks/use-payment-methods.d.ts +19 -0
  151. package/dist/hooks/use-payment-methods.d.ts.map +1 -0
  152. package/dist/hooks/use-policies.d.ts +25 -0
  153. package/dist/hooks/use-policies.d.ts.map +1 -0
  154. package/dist/hooks/use-product.d.ts +15 -0
  155. package/dist/hooks/use-product.d.ts.map +1 -0
  156. package/dist/hooks/{useProducts.d.ts → use-products.d.ts} +1 -1
  157. package/dist/hooks/use-products.d.ts.map +1 -0
  158. package/dist/hooks/use-region.d.ts +15 -0
  159. package/dist/hooks/use-region.d.ts.map +1 -0
  160. package/dist/hooks/use-shipping.d.ts +29 -0
  161. package/dist/hooks/use-shipping.d.ts.map +1 -0
  162. package/dist/hooks/use-storefront-actions.d.ts +12 -0
  163. package/dist/hooks/use-storefront-actions.d.ts.map +1 -0
  164. package/dist/hooks/use-storefront-head.d.ts +19 -0
  165. package/dist/hooks/use-storefront-head.d.ts.map +1 -0
  166. package/dist/hooks/use-storefront.d.ts +3 -0
  167. package/dist/hooks/use-storefront.d.ts.map +1 -0
  168. package/dist/index.d.ts +123 -54
  169. package/dist/index.d.ts.map +1 -1
  170. package/dist/index.js +1311 -5012
  171. package/dist/index.js.map +1 -1
  172. package/dist/lib/format.d.ts.map +1 -0
  173. package/dist/lib/motion.d.ts.map +1 -0
  174. package/dist/lib/seo.d.ts +26 -0
  175. package/dist/lib/seo.d.ts.map +1 -0
  176. package/dist/{testids.d.ts → lib/testids.d.ts} +21 -0
  177. package/dist/lib/testids.d.ts.map +1 -0
  178. package/dist/{theme.d.ts → lib/theme.d.ts} +41 -3
  179. package/dist/lib/theme.d.ts.map +1 -0
  180. package/dist/render.d.ts +5 -0
  181. package/dist/render.d.ts.map +1 -0
  182. package/dist/render.js +359 -0
  183. package/dist/render.js.map +1 -0
  184. package/dist/stores/cart.store.d.ts +6 -2
  185. package/dist/stores/cart.store.d.ts.map +1 -1
  186. package/dist/stores/catalog.store.d.ts +11 -0
  187. package/dist/stores/catalog.store.d.ts.map +1 -1
  188. package/dist/views/CatalogPage.d.ts.map +1 -0
  189. package/dist/views/CheckoutPage.d.ts.map +1 -0
  190. package/dist/views/ContentPage.d.ts +11 -0
  191. package/dist/views/ContentPage.d.ts.map +1 -0
  192. package/dist/views/HomePage.d.ts +9 -0
  193. package/dist/views/HomePage.d.ts.map +1 -0
  194. package/dist/views/MyPurchasesPage.d.ts.map +1 -0
  195. package/dist/views/NotFoundPage.d.ts.map +1 -0
  196. package/dist/{pages → views}/OrderConfirmationPage.d.ts.map +1 -1
  197. package/dist/{pages → views}/PolicyPage.d.ts.map +1 -1
  198. package/dist/views/ProductDetailPage.d.ts.map +1 -0
  199. package/dist/views/StorefrontPage.d.ts +35 -0
  200. package/dist/views/StorefrontPage.d.ts.map +1 -0
  201. package/dist/workflows/checkout.d.ts +1 -1
  202. package/dist/workflows/checkout.d.ts.map +1 -1
  203. package/package.json +21 -14
  204. package/dist/auth.d.ts.map +0 -1
  205. package/dist/blocks.d.ts.map +0 -1
  206. package/dist/component-contracts.d.ts.map +0 -1
  207. package/dist/component-selectors.d.ts.map +0 -1
  208. package/dist/components/CartDrawer.d.ts.map +0 -1
  209. package/dist/components/CustomerAccountShell.d.ts.map +0 -1
  210. package/dist/components/DeliveryEstimator.d.ts.map +0 -1
  211. package/dist/components/FiltersPanel.d.ts +0 -3
  212. package/dist/components/FiltersPanel.d.ts.map +0 -1
  213. package/dist/components/OrderTrackingTimeline.d.ts.map +0 -1
  214. package/dist/components/ProductCard.d.ts +0 -4
  215. package/dist/components/ProductCard.d.ts.map +0 -1
  216. package/dist/components/ProductEnquiryForm.d.ts.map +0 -1
  217. package/dist/components/ProductGallery.d.ts +0 -22
  218. package/dist/components/ProductGallery.d.ts.map +0 -1
  219. package/dist/components/ProductGrid.d.ts.map +0 -1
  220. package/dist/components/ProductOptionSelector.d.ts.map +0 -1
  221. package/dist/components/ProductSpecs.d.ts.map +0 -1
  222. package/dist/components/QuantityInput.d.ts.map +0 -1
  223. package/dist/components/RelatedProducts.d.ts.map +0 -1
  224. package/dist/components/SignInModal.d.ts.map +0 -1
  225. package/dist/components/StorefrontErrorBoundary.d.ts.map +0 -1
  226. package/dist/components/StorefrontFooter.d.ts.map +0 -1
  227. package/dist/components/StorefrontHeader.d.ts.map +0 -1
  228. package/dist/components/Toaster.d.ts.map +0 -1
  229. package/dist/components/sections/CategoryShowcase.d.ts +0 -6
  230. package/dist/components/sections/CategoryShowcase.d.ts.map +0 -1
  231. package/dist/components/sections/HeroSection.d.ts +0 -9
  232. package/dist/components/sections/HeroSection.d.ts.map +0 -1
  233. package/dist/components/sections/MediaCarousel.d.ts +0 -11
  234. package/dist/components/sections/MediaCarousel.d.ts.map +0 -1
  235. package/dist/components/sections/MiscSections.d.ts +0 -33
  236. package/dist/components/sections/MiscSections.d.ts.map +0 -1
  237. package/dist/components/sections/ProductRail.d.ts +0 -9
  238. package/dist/components/sections/ProductRail.d.ts.map +0 -1
  239. package/dist/components/sections/ProductSlider.d.ts +0 -15
  240. package/dist/components/sections/ProductSlider.d.ts.map +0 -1
  241. package/dist/config.d.ts.map +0 -1
  242. package/dist/createStorefrontApp.d.ts +0 -28
  243. package/dist/createStorefrontApp.d.ts.map +0 -1
  244. package/dist/define.d.ts.map +0 -1
  245. package/dist/format.d.ts.map +0 -1
  246. package/dist/hooks/useCategories.d.ts +0 -6
  247. package/dist/hooks/useCategories.d.ts.map +0 -1
  248. package/dist/hooks/useDiscountValidator.d.ts +0 -14
  249. package/dist/hooks/useDiscountValidator.d.ts.map +0 -1
  250. package/dist/hooks/useMyOrders.d.ts.map +0 -1
  251. package/dist/hooks/useProduct.d.ts +0 -7
  252. package/dist/hooks/useProduct.d.ts.map +0 -1
  253. package/dist/hooks/useProducts.d.ts.map +0 -1
  254. package/dist/hooks/useStorefront.d.ts +0 -35
  255. package/dist/hooks/useStorefront.d.ts.map +0 -1
  256. package/dist/hooks/useStorefrontHead.d.ts +0 -11
  257. package/dist/hooks/useStorefrontHead.d.ts.map +0 -1
  258. package/dist/motion.d.ts.map +0 -1
  259. package/dist/order-status.d.ts.map +0 -1
  260. package/dist/pages/CatalogPage.d.ts.map +0 -1
  261. package/dist/pages/CheckoutPage.d.ts.map +0 -1
  262. package/dist/pages/HomePage.d.ts +0 -9
  263. package/dist/pages/HomePage.d.ts.map +0 -1
  264. package/dist/pages/MyPurchasesPage.d.ts.map +0 -1
  265. package/dist/pages/NotFoundPage.d.ts.map +0 -1
  266. package/dist/pages/ProductDetailPage.d.ts.map +0 -1
  267. package/dist/presets.d.ts.map +0 -1
  268. package/dist/product-options.d.ts +0 -12
  269. package/dist/product-options.d.ts.map +0 -1
  270. package/dist/router.d.ts.map +0 -1
  271. package/dist/scaffold.d.ts.map +0 -1
  272. package/dist/sections.d.ts.map +0 -1
  273. package/dist/testids.d.ts.map +0 -1
  274. package/dist/theme.d.ts.map +0 -1
  275. package/src/auth.ts +0 -227
  276. package/src/blocks.tsx +0 -51
  277. package/src/catalog.ts +0 -3
  278. package/src/component-contracts.ts +0 -154
  279. package/src/component-selectors.ts +0 -31
  280. package/src/components/CartDrawer.tsx +0 -279
  281. package/src/components/CustomerAccountShell.tsx +0 -102
  282. package/src/components/DeliveryEstimator.tsx +0 -157
  283. package/src/components/FiltersPanel.tsx +0 -109
  284. package/src/components/OrderTrackingTimeline.tsx +0 -135
  285. package/src/components/Price.tsx +0 -32
  286. package/src/components/ProductCard.tsx +0 -120
  287. package/src/components/ProductEnquiryForm.tsx +0 -175
  288. package/src/components/ProductGallery.tsx +0 -174
  289. package/src/components/ProductGrid.tsx +0 -43
  290. package/src/components/ProductOptionSelector.tsx +0 -50
  291. package/src/components/ProductSpecs.tsx +0 -75
  292. package/src/components/QuantityInput.tsx +0 -52
  293. package/src/components/RelatedProducts.tsx +0 -40
  294. package/src/components/SignInModal.tsx +0 -120
  295. package/src/components/SmoothImage.tsx +0 -59
  296. package/src/components/StorefrontErrorBoundary.tsx +0 -51
  297. package/src/components/StorefrontFooter.tsx +0 -110
  298. package/src/components/StorefrontHeader.tsx +0 -477
  299. package/src/components/Toaster.tsx +0 -60
  300. package/src/components/sections/CategoryShowcase.tsx +0 -80
  301. package/src/components/sections/HeroSection.tsx +0 -193
  302. package/src/components/sections/MediaCarousel.tsx +0 -141
  303. package/src/components/sections/MiscSections.tsx +0 -158
  304. package/src/components/sections/ProductRail.tsx +0 -81
  305. package/src/components/sections/ProductSlider.tsx +0 -119
  306. package/src/config.ts +0 -229
  307. package/src/createStorefrontApp.tsx +0 -247
  308. package/src/define.ts +0 -34
  309. package/src/format.ts +0 -7
  310. package/src/hooks/index.ts +0 -6
  311. package/src/hooks/useCategories.ts +0 -26
  312. package/src/hooks/useDiscountValidator.ts +0 -43
  313. package/src/hooks/useMyOrders.ts +0 -41
  314. package/src/hooks/useProduct.ts +0 -36
  315. package/src/hooks/useProducts.ts +0 -39
  316. package/src/hooks/useStorefront.ts +0 -58
  317. package/src/hooks/useStorefrontHead.ts +0 -25
  318. package/src/index.ts +0 -202
  319. package/src/motion.tsx +0 -94
  320. package/src/order-status.ts +0 -33
  321. package/src/pages/CatalogPage.tsx +0 -118
  322. package/src/pages/CheckoutPage.tsx +0 -689
  323. package/src/pages/HomePage.tsx +0 -16
  324. package/src/pages/MyPurchasesPage.tsx +0 -311
  325. package/src/pages/NotFoundPage.tsx +0 -25
  326. package/src/pages/OrderConfirmationPage.tsx +0 -190
  327. package/src/pages/PolicyPage.tsx +0 -47
  328. package/src/pages/ProductDetailPage.tsx +0 -264
  329. package/src/presets.ts +0 -324
  330. package/src/product-options.ts +0 -63
  331. package/src/router.ts +0 -49
  332. package/src/scaffold.tsx +0 -115
  333. package/src/sections.ts +0 -116
  334. package/src/shop.ts +0 -4
  335. package/src/stores/cart.store.ts +0 -185
  336. package/src/stores/catalog.store.ts +0 -39
  337. package/src/stores/delivery.store.ts +0 -152
  338. package/src/stores/session.store.ts +0 -28
  339. package/src/stores/toast.store.ts +0 -43
  340. package/src/testids.ts +0 -136
  341. package/src/theme.ts +0 -194
  342. package/src/ui.ts +0 -5
  343. package/src/workflows/checkout.ts +0 -119
  344. /package/dist/components/{CustomerAccountShell.d.ts → account/CustomerAccountShell.d.ts} +0 -0
  345. /package/dist/components/{SignInModal.d.ts → account/SignInModal.d.ts} +0 -0
  346. /package/dist/components/{CartDrawer.d.ts → cart/CartDrawer.d.ts} +0 -0
  347. /package/dist/components/{QuantityInput.d.ts → cart/QuantityInput.d.ts} +0 -0
  348. /package/dist/components/{DeliveryEstimator.d.ts → checkout/DeliveryEstimator.d.ts} +0 -0
  349. /package/dist/components/{ProductGrid.d.ts → collection/ProductGrid.d.ts} +0 -0
  350. /package/dist/components/{StorefrontErrorBoundary.d.ts → layout/StorefrontErrorBoundary.d.ts} +0 -0
  351. /package/dist/components/{StorefrontFooter.d.ts → layout/StorefrontFooter.d.ts} +0 -0
  352. /package/dist/components/{StorefrontHeader.d.ts → layout/StorefrontHeader.d.ts} +0 -0
  353. /package/dist/components/{Toaster.d.ts → layout/Toaster.d.ts} +0 -0
  354. /package/dist/components/{OrderTrackingTimeline.d.ts → order/OrderTrackingTimeline.d.ts} +0 -0
  355. /package/dist/components/{ProductEnquiryForm.d.ts → product/ProductEnquiryForm.d.ts} +0 -0
  356. /package/dist/components/{ProductSpecs.d.ts → product/ProductSpecs.d.ts} +0 -0
  357. /package/dist/components/{RelatedProducts.d.ts → product/RelatedProducts.d.ts} +0 -0
  358. /package/dist/{order-status.d.ts → data/order-status.d.ts} +0 -0
  359. /package/dist/{component-selectors.d.ts → extend/component-selectors.d.ts} +0 -0
  360. /package/dist/{format.d.ts → lib/format.d.ts} +0 -0
  361. /package/dist/{motion.d.ts → lib/motion.d.ts} +0 -0
  362. /package/dist/{pages → views}/CatalogPage.d.ts +0 -0
  363. /package/dist/{pages → views}/CheckoutPage.d.ts +0 -0
  364. /package/dist/{pages → views}/MyPurchasesPage.d.ts +0 -0
  365. /package/dist/{pages → views}/NotFoundPage.d.ts +0 -0
  366. /package/dist/{pages → views}/OrderConfirmationPage.d.ts +0 -0
  367. /package/dist/{pages → views}/PolicyPage.d.ts +0 -0
  368. /package/dist/{pages → views}/ProductDetailPage.d.ts +0 -0
@@ -1,689 +0,0 @@
1
- import React, { useEffect, useState } from 'react'
2
- import { CreditCard, Lock, PackageCheck, ShieldCheck } from 'lucide-react'
3
- import { getShopProvider } from '@fayz-ai/shop/runtime'
4
- import { formatPostalCode, normalizePostalCode, lookupPostalCode } from '@fayz-ai/core'
5
- import type { CustomerAddress, PaymentMethodKind } from '@fayz-ai/shop/types'
6
- import { prefersReducedMotion } from '../motion'
7
- import {
8
- useCartStore,
9
- selectSubtotal,
10
- selectDiscountTotal,
11
- selectShipping,
12
- selectTotal,
13
- } from '../stores/cart.store'
14
- import { useSessionStore } from '../stores/session.store'
15
- import { useDeliveryStore } from '../stores/delivery.store'
16
- import { useStorefrontConfig } from '../config'
17
- import { Link, navigateTo } from '../router'
18
- import { formatMoney } from '../format'
19
- import { TID } from '../testids'
20
- import { placeStorefrontOrder } from '../workflows/checkout'
21
- import { useDiscountValidator } from '../hooks/useDiscountValidator'
22
- import { toast } from '../stores/toast.store'
23
- import { useStorefrontHead } from '../hooks/useStorefrontHead'
24
- import { SignInModal } from '../components/SignInModal'
25
- import { SmoothImage } from '../components/SmoothImage'
26
-
27
- interface CheckoutForm {
28
- email: string
29
- name: string
30
- street: string
31
- city: string
32
- zip: string
33
- number: string
34
- complement: string
35
- district: string
36
- state: string
37
- }
38
-
39
- type AddressMode = 'saved' | 'new'
40
-
41
- /**
42
- * There is no payment service provider connected, so the checkout does not ask
43
- * for a card number. It used to, prefilled with 4242 4242 4242 4242, and the
44
- * digits were validated and then thrown away — a PAN typed into React state
45
- * that reaches no processor is a liability with no upside. The buyer states how
46
- * they intend to pay; the order stays `pending` until the merchant confirms the
47
- * money arrived (shop_confirm_payment, which since 0019 only they can call).
48
- */
49
- const PAYMENT_LABELS: Record<PaymentMethodKind, { label: string; hint: string }> = {
50
- pix: { label: 'Pix', hint: 'Você recebe a chave para pagar após confirmar o pedido' },
51
- credit_card: { label: 'Cartão de crédito', hint: 'Maquininha na entrega' },
52
- debit_card: { label: 'Cartão de débito', hint: 'Maquininha na entrega' },
53
- boleto: { label: 'Boleto', hint: 'Enviado por e-mail após a confirmação' },
54
- cash: { label: 'Dinheiro', hint: 'Pagamento na entrega' },
55
- other: { label: 'Combinar com a loja', hint: 'A loja entra em contato para acertar o pagamento' },
56
- }
57
-
58
- const PROCESSING_STEPS = [
59
- { icon: Lock, label: 'Validando seus dados...' },
60
- { icon: PackageCheck, label: 'Registrando seu pedido...' },
61
- ]
62
-
63
- const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))
64
-
65
- function ProcessingOverlay({ step }: { step: number }) {
66
- const current = PROCESSING_STEPS[Math.min(step, PROCESSING_STEPS.length - 1)]!
67
- const Icon = current.icon
68
- return (
69
- <div
70
- data-testid={TID.checkoutProcessing}
71
- className="fixed inset-0 z-50 flex animate-fade-in flex-col items-center justify-center bg-background/95 backdrop-blur-sm"
72
- role="status"
73
- aria-live="polite"
74
- >
75
- <div className="relative flex h-24 w-24 items-center justify-center">
76
- <span className="absolute inset-0 animate-spin rounded-full border-[3px] border-muted border-t-primary" style={{ animationDuration: '1.1s' }} />
77
- <Icon key={step} className="h-9 w-9 animate-pop-in text-primary" />
78
- </div>
79
- <p key={`label-${step}`} className="mt-6 animate-fade-up font-medium">{current.label}</p>
80
- <p className="mt-8 text-xs text-muted-foreground">Pagamento seguro - não feche esta janela</p>
81
- </div>
82
- )
83
- }
84
-
85
- function field(
86
- label: string,
87
- testId: string,
88
- value: string,
89
- onChange: (value: string) => void,
90
- props?: React.InputHTMLAttributes<HTMLInputElement>,
91
- ) {
92
- return (
93
- <label className="block">
94
- <span className="sr-only">{label}</span>
95
- <input
96
- data-testid={testId}
97
- aria-label={label}
98
- value={value}
99
- onChange={(event) => onChange(event.target.value)}
100
- className="w-full rounded-lg border bg-background px-3.5 py-3 text-sm outline-none transition focus:border-primary focus:ring-2 focus:ring-primary/10"
101
- {...props}
102
- />
103
- </label>
104
- )
105
- }
106
-
107
- export function CheckoutPage() {
108
- const config = useStorefrontConfig()
109
- const cart = useCartStore()
110
- const session = useSessionStore()
111
- const delivery = useDeliveryStore()
112
- const validateDiscount = useDiscountValidator()
113
- useStorefrontHead({ title: `Checkout — ${config.name}` })
114
-
115
- const paymentMethods = config.payments.methods
116
- const [selectedAddressId, setSelectedAddressId] = useState('new')
117
- const [savedAddresses, setSavedAddresses] = useState<CustomerAddress[]>([])
118
- // 'new' until the address book actually loads: an empty book must show an
119
- // empty form, never a placeholder address belonging to nobody.
120
- const [addressMode, setAddressMode] = useState<AddressMode>('new')
121
- const [paymentMethod, setPaymentMethod] = useState<PaymentMethodKind>(paymentMethods[0] ?? 'pix')
122
- const [form, setForm] = useState<CheckoutForm>({
123
- email: session.email ?? '',
124
- name: session.name ?? '',
125
- street: '',
126
- city: '',
127
- zip: '',
128
- number: '',
129
- complement: '',
130
- district: '',
131
- state: '',
132
- })
133
- const [error, setError] = useState<string | null>(null)
134
- const [discountCode, setDiscountCode] = useState('')
135
- const [discountError, setDiscountError] = useState<string | null>(null)
136
- const [discountSuccess, setDiscountSuccess] = useState<string | null>(null)
137
- const [applyingDiscount, setApplyingDiscount] = useState(false)
138
- const [placing, setPlacing] = useState(false)
139
- const [processingStep, setProcessingStep] = useState(0)
140
- const [showSignin, setShowSignin] = useState(false)
141
- const [zipLookup, setZipLookup] = useState<'idle' | 'loading' | 'found' | 'not-found'>('idle')
142
-
143
- const subtotal = selectSubtotal(cart)
144
- const discountTotal = selectDiscountTotal(cart)
145
- const shipping = selectShipping(cart, config)
146
- const total = selectTotal(cart, config)
147
- const money = (value: number) => formatMoney(value, config.currency, config.locale)
148
-
149
- useEffect(() => {
150
- if (cart.lines.length === 0 && !placing) navigateTo(config.catalogPath)
151
- }, [cart.lines.length, config.catalogPath, placing])
152
-
153
-
154
- const set = (key: keyof CheckoutForm) => (value: string) => setForm((current) => ({ ...current, [key]: value }))
155
-
156
- /**
157
- * Editing the CEP re-fetches the address and refills what the postal service
158
- * knows, leaving number and complement alone — those are the buyer's and must
159
- * survive a correction to the postcode.
160
- *
161
- * Only what the lookup can supply is overwritten. Silently keeping a stale
162
- * street from a previous CEP is worse than an empty one: the parcel goes to
163
- * the wrong place and everything on screen looks filled in.
164
- */
165
- async function setZip(value: string) {
166
- const masked = formatPostalCode(value)
167
- setForm((current) => ({ ...current, zip: masked }))
168
- if (normalizePostalCode(masked).length !== 8) {
169
- setZipLookup('idle')
170
- return
171
- }
172
- setZipLookup('loading')
173
- try {
174
- const found = await lookupPostalCode(masked)
175
- if (!found) {
176
- setZipLookup('not-found')
177
- return
178
- }
179
- setZipLookup('found')
180
- setForm((current) => ({
181
- ...current,
182
- street: found.street || current.street,
183
- district: found.district || current.district,
184
- city: found.city,
185
- state: found.state,
186
- }))
187
- } catch {
188
- // Offline or the provider is down — the buyer can still type it out.
189
- setZipLookup('idle')
190
- }
191
- }
192
-
193
- async function applyDiscountCode() {
194
- setApplyingDiscount(true)
195
- setDiscountError(null)
196
- setDiscountSuccess(null)
197
- try {
198
- const appliedCode = discountCode.trim().toUpperCase()
199
- const result = await validateDiscount(discountCode)
200
- if (result.valid) {
201
- cart.applyDiscount(appliedCode, result.percent)
202
- setDiscountCode(appliedCode)
203
- setDiscountSuccess(`${appliedCode} aplicado: ${result.percent}% de desconto.`)
204
- toast.success('Cupom aplicado!', `${appliedCode} • ${result.percent}% de desconto`)
205
- } else {
206
- setDiscountError(result.message ?? 'Cupom inválido.')
207
- toast.error('Cupom inválido', result.message ?? 'Verifique o código e tente novamente.')
208
- }
209
- } finally {
210
- setApplyingDiscount(false)
211
- }
212
- }
213
-
214
- function clearDiscountCode() {
215
- cart.clearDiscount()
216
- setDiscountCode('')
217
- setDiscountSuccess(null)
218
- setDiscountError(null)
219
- }
220
-
221
- // The signed-in shopper's real address book. RLS scopes it to them, so a
222
- // guest (or a customer whose account was never linked to an auth identity)
223
- // simply gets nothing back and types their address.
224
- useEffect(() => {
225
- const customerId = session.customerId
226
- if (!customerId) {
227
- setSavedAddresses([])
228
- setAddressMode('new')
229
- return
230
- }
231
- let cancelled = false
232
- const provider = getShopProvider()
233
- void provider
234
- .listCustomerAddresses?.(customerId)
235
- .then((addresses) => {
236
- if (cancelled || addresses.length === 0) return
237
- setSavedAddresses(addresses)
238
- applySavedAddress(addresses[0]!)
239
- })
240
- .catch(() => {})
241
- return () => {
242
- cancelled = true
243
- }
244
- }, [session.customerId])
245
-
246
- /**
247
- * The CEP given back on the product page lands here.
248
- *
249
- * This is the payoff of the whole delivery flow: street, district, city and
250
- * UF arrive filled and the buyer types a house number. It only fires when no
251
- * saved address was selected — a signed-in shopper's own address book still
252
- * wins over a postcode lookup, because it also knows their number and
253
- * complement, which no postal service can supply.
254
- */
255
- useEffect(() => {
256
- if (addressMode !== 'new') return
257
- const looked = delivery.address
258
- if (!looked) return
259
- setForm((current) => {
260
- if (current.zip.trim() || current.street.trim()) return current
261
- return {
262
- ...current,
263
- zip: formatPostalCode(looked.postalCode),
264
- street: looked.street,
265
- district: looked.district,
266
- city: looked.city,
267
- state: looked.state,
268
- }
269
- })
270
- }, [delivery.address, addressMode])
271
-
272
- function applySavedAddress(address: CustomerAddress) {
273
- setAddressMode('saved')
274
- setSelectedAddressId(address.id)
275
- setForm((current) => ({
276
- ...current,
277
- street: address.street,
278
- city: address.city,
279
- zip: address.postalCode,
280
- number: address.number ?? '',
281
- complement: address.complement ?? '',
282
- district: address.district ?? '',
283
- state: address.state,
284
- }))
285
- }
286
-
287
- function selectAddress(addressId: string) {
288
- const address = savedAddresses.find((item) => item.id === addressId)
289
- if (address) applySavedAddress(address)
290
- }
291
-
292
- function addNewAddress() {
293
- setAddressMode('new')
294
- setSelectedAddressId('new')
295
- setForm((current) => ({ ...current, street: '', city: '', zip: '', number: '', complement: '', district: '', state: '' }))
296
- }
297
-
298
- function validate(): boolean {
299
- setError(null)
300
- if (!form.email.trim() || !/.+@.+\..+/.test(form.email)) {
301
- setError('Informe um e-mail válido.')
302
- return false
303
- }
304
- if (!form.name.trim()) {
305
- setError('Informe seu nome completo.')
306
- return false
307
- }
308
- // Number and district are required because a courier cannot deliver without
309
- // them; the old form collected only street/city/CEP and logistics had to guess.
310
- if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
311
- setError('Preencha ou selecione o endereço de entrega.')
312
- return false
313
- }
314
- // Called out on its own: with the CEP filling everything else, the number is
315
- // usually the only thing missing, and "preencha o endereço" sent people
316
- // hunting through fields that were already correct.
317
- if (!form.number.trim()) {
318
- setError('Falta o número do endereço.')
319
- return false
320
- }
321
- // UF is required now: 117 of the 266 addresses already in the pool have no
322
- // state, and a carrier cannot quote or ship without one.
323
- if (form.state.trim().length !== 2) {
324
- setError('Informe a UF com duas letras (ex.: RJ).')
325
- return false
326
- }
327
- // Coverage. shop_place_order refuses an unserved postal code anyway (0021),
328
- // so this only turns a raw SQL error into a sentence the buyer can act on —
329
- // the rule itself lives on the server, not here.
330
- if (delivery.status === 'unserved' && delivery.postalCode === normalizePostalCode(form.zip)) {
331
- setError('Ainda não entregamos nesse CEP. Tente outro endereço.')
332
- return false
333
- }
334
- return true
335
- }
336
-
337
- async function placeOrder() {
338
- if (!validate()) return
339
-
340
- setPlacing(true)
341
- setProcessingStep(0)
342
- const stepMs = prefersReducedMotion() ? 120 : 600
343
- const stepTimers = [
344
- setTimeout(() => setProcessingStep(1), stepMs),
345
- setTimeout(() => setProcessingStep(2), stepMs * 2),
346
- ]
347
- const minDuration = sleep(stepMs * 2)
348
- try {
349
- const { order, customerId } = await placeStorefrontOrder({
350
- config,
351
- cart,
352
- session,
353
- customer: { email: form.email, name: form.name },
354
- address: { street: form.street, city: form.city, zip: form.zip },
355
- shippingAddress: {
356
- postalCode: form.zip.trim(),
357
- street: form.street.trim(),
358
- number: form.number.trim() || undefined,
359
- complement: form.complement.trim() || undefined,
360
- district: form.district.trim() || undefined,
361
- city: form.city.trim(),
362
- state: form.state.trim().toUpperCase(),
363
- },
364
- // The method the buyer actually chose, which opens the ledger row in
365
- // public.transactions with the right kind.
366
- paymentMethod,
367
- // Never marked paid from the browser. It used to be, through an RPC that
368
- // was granted to anon — so any buyer holding their own order id could
369
- // declare it settled. The order is now born `pending` and only the
370
- // merchant (or a PSP webhook) can confirm the money arrived.
371
- })
372
- // placeStorefrontOrder resolves/creates the customer record but doesn't persist
373
- // it — without this, "My purchases" right after checkout queries by email, which
374
- // anon can't read under RLS, and silently comes back empty.
375
- if (customerId) session.setSession({ customerId, email: form.email, name: form.name })
376
-
377
- await minDuration
378
- cart.clear()
379
- navigateTo(`/order/${order.id}`)
380
- } catch (error) {
381
- setError(error instanceof Error ? error.message : 'Não foi possível concluir o pedido.')
382
- setPlacing(false)
383
- } finally {
384
- stepTimers.forEach(clearTimeout)
385
- }
386
- }
387
-
388
- const primaryCta = placing ? 'Processando...' : `Comprar agora - ${money(total)}`
389
-
390
- return (
391
- <main className="min-h-screen bg-background">
392
- {placing && <ProcessingOverlay step={processingStep} />}
393
- {showSignin && !session.email && (
394
- <SignInModal
395
- defaultEmail={form.email}
396
- onClose={() => setShowSignin(false)}
397
- onSignedIn={(signed) =>
398
- setForm((current) => ({ ...current, email: signed.email ?? current.email, name: signed.name || current.name }))
399
- }
400
- />
401
- )}
402
- <header className="border-b bg-background">
403
- <div className="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-6 sm:px-6">
404
- <Link to={config.catalogPath} className="text-sm text-muted-foreground hover:text-foreground">
405
- Voltar à loja
406
- </Link>
407
- <div className="text-center">
408
- <div className="sf-heading text-2xl font-bold tracking-tight">{config.logo ?? config.name}</div>
409
- <div className="mt-2 flex items-center justify-center gap-2 text-xs text-muted-foreground">
410
- <Lock className="h-3.5 w-3.5" />
411
- Checkout seguro
412
- </div>
413
- </div>
414
- <a href="mailto:suporte@example.com" className="text-sm text-muted-foreground hover:text-foreground">
415
- Ajuda
416
- </a>
417
- </div>
418
- </header>
419
-
420
- <div className="mx-auto grid max-w-6xl grid-cols-1 gap-8 px-4 py-8 sm:px-6 lg:grid-cols-5 lg:px-6">
421
- <section className="lg:col-span-3 lg:pr-10">
422
- <nav className="mb-8 flex flex-wrap items-center gap-2 text-sm text-muted-foreground" aria-label="Checkout steps">
423
- <span className="font-medium text-foreground">Informações</span>
424
- <span>/</span>
425
- <span>Entrega</span>
426
- <span>/</span>
427
- <span>Pagamento</span>
428
- </nav>
429
-
430
- <div className="space-y-8">
431
- <section>
432
- <div className="mb-3 flex items-center justify-between gap-4">
433
- <h1 className="text-xl font-semibold tracking-tight">Informações de contato</h1>
434
- {session.email ? (
435
- <span className="text-sm text-muted-foreground">Conectado como {session.email}</span>
436
- ) : (
437
- <button
438
- type="button"
439
- onClick={() => setShowSignin((value) => !value)}
440
- className="text-sm font-medium text-primary hover:underline"
441
- >
442
- Já tem conta? Entrar
443
- </button>
444
- )}
445
- </div>
446
- <div className="grid gap-3 sm:grid-cols-2">
447
- {field('E-mail', TID.checkoutEmail, form.email, set('email'), {
448
- type: 'email',
449
- placeholder: 'E-mail ou telefone',
450
- })}
451
- {field('Nome completo', TID.checkoutName, form.name, set('name'), {
452
- placeholder: 'Nome completo',
453
- })}
454
- </div>
455
- </section>
456
-
457
- <section>
458
- <h2 className="mb-3 text-xl font-semibold tracking-tight">Endereço de entrega</h2>
459
- <div className="mb-3 grid gap-3">
460
- {savedAddresses.map((address) => (
461
- <div
462
- key={address.id}
463
- className={`group relative rounded-lg border p-3 pr-11 text-left text-sm transition hover:bg-muted/40 ${
464
- selectedAddressId === address.id ? 'border-primary bg-primary/5' : 'border-border bg-background'
465
- }`}
466
- >
467
- <button type="button" onClick={() => selectAddress(address.id)} className="block w-full text-left">
468
- <span className="flex items-center justify-between gap-3 font-semibold">
469
- {address.label}
470
- {addressMode === 'saved' && selectedAddressId === address.id && (
471
- <span className="rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary">Selecionado</span>
472
- )}
473
- </span>
474
- <span className="mt-1 block text-muted-foreground">
475
- {[address.street, address.number].filter(Boolean).join(', ')}
476
- {address.complement ? ` — ${address.complement}` : ''}
477
- </span>
478
- <span className="block text-muted-foreground">
479
- {[address.district, address.city, address.state].filter(Boolean).join(' · ')} — {address.postalCode}
480
- </span>
481
- </button>
482
- </div>
483
- ))}
484
- <button
485
- type="button"
486
- onClick={addNewAddress}
487
- className={`rounded-lg border border-dashed p-3 text-left text-sm transition hover:bg-muted/40 ${
488
- addressMode === 'new' ? 'border-primary bg-primary/5' : 'border-border bg-muted/20'
489
- }`}
490
- >
491
- <span className="block font-semibold text-primary">
492
- {savedAddresses.length > 0 ? '+ Adicionar novo endereço' : 'Informe o endereço de entrega'}
493
- </span>
494
- <span className="mt-1 block text-muted-foreground">Preencher outro endereço para esta compra</span>
495
- </button>
496
- </div>
497
- {addressMode === 'new' && (
498
- <div className="grid gap-3">
499
- {/* CEP first, because it FILLS the fields below it. With it at
500
- the bottom the buyer typed street, district, city and UF by
501
- hand and only then reached the one field that would have
502
- supplied all four — and editing it later has to refill
503
- them, which reads as the form rewriting itself. */}
504
- <div className="grid gap-1 sm:grid-cols-[200px_1fr] sm:items-center">
505
- {field('CEP', TID.checkoutZip, form.zip, setZip, {
506
- placeholder: 'CEP',
507
- inputMode: 'numeric',
508
- autoComplete: 'postal-code',
509
- })}
510
- <p className="px-1 text-xs text-muted-foreground">
511
- {zipLookup === 'loading' ? 'Buscando endereço…'
512
- : zipLookup === 'not-found' ? 'CEP não encontrado — preencha o endereço à mão.'
513
- : 'Preenchemos o endereço para você.'}
514
- </p>
515
- </div>
516
- {field('Endereço', TID.checkoutStreet, form.street, set('street'), { placeholder: 'Rua, avenida…' })}
517
- <div className="grid grid-cols-2 gap-3">
518
- {field('Número', 'checkout-number', form.number, set('number'), { placeholder: 'Número' })}
519
- {field('Complemento', 'checkout-complement', form.complement, set('complement'), { placeholder: 'Apto, bloco (opcional)' })}
520
- </div>
521
- <div className="grid grid-cols-2 gap-3">
522
- {field('Bairro', 'checkout-district', form.district, set('district'), { placeholder: 'Bairro' })}
523
- {field('UF', 'checkout-state', form.state, set('state'), { placeholder: 'UF' })}
524
- </div>
525
- {field('Cidade', TID.checkoutCity, form.city, set('city'), { placeholder: 'Cidade' })}
526
- </div>
527
- )}
528
- </section>
529
-
530
- <section>
531
- <h2 className="mb-3 text-xl font-semibold tracking-tight">Pagamento</h2>
532
- <div className="mb-3 grid gap-3" role="radiogroup" aria-label="Forma de pagamento">
533
- {paymentMethods.map((method) => {
534
- const copy = PAYMENT_LABELS[method]
535
- const selected = paymentMethod === method
536
- return (
537
- <button
538
- key={method}
539
- type="button"
540
- role="radio"
541
- aria-checked={selected}
542
- data-testid={`checkout-payment-${method}`}
543
- onClick={() => setPaymentMethod(method)}
544
- className={`rounded-lg border p-3 text-left text-sm transition hover:bg-muted/40 ${
545
- selected ? 'border-primary bg-primary/5' : 'border-border bg-background'
546
- }`}
547
- >
548
- <span className="flex items-center justify-between gap-3 font-semibold">
549
- <span className="flex items-center gap-2"><CreditCard className="h-4 w-4" />{copy.label}</span>
550
- {selected && (
551
- <span className="rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary">Selecionado</span>
552
- )}
553
- </span>
554
- <span className="mt-1 block text-muted-foreground">{copy.hint}</span>
555
- </button>
556
- )
557
- })}
558
- </div>
559
- <p className="mt-2 text-xs text-muted-foreground">
560
- Nenhuma cobrança é feita agora. Seu pedido é registrado e a loja confirma o pagamento.
561
- </p>
562
- </section>
563
-
564
- {error && (
565
- <p data-testid={TID.checkoutError} className="rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive">
566
- {error}
567
- </p>
568
- )}
569
-
570
- </div>
571
-
572
- <footer className="mt-10 flex flex-wrap gap-4 border-t pt-5 text-xs text-muted-foreground">
573
- <a href="#/privacy" className="hover:text-foreground">Privacidade</a>
574
- <a href="#/terms" className="hover:text-foreground">Termos</a>
575
- <a href="#/refunds" className="hover:text-foreground">Trocas e devoluções</a>
576
- </footer>
577
- </section>
578
-
579
- <aside className="rounded-xl border bg-muted/20 p-5 lg:sticky lg:top-6 lg:col-span-2 lg:self-start">
580
- <h2 className="sr-only">Resumo do pedido</h2>
581
- <ul className="space-y-4">
582
- {cart.lines.map((line) => (
583
- <li key={line.lineId ?? line.productId} className="flex gap-3 text-sm">
584
- <div className="relative h-16 w-16 flex-none overflow-hidden rounded-lg border bg-background">
585
- {line.imageUrl && <SmoothImage src={line.imageUrl} alt={line.name} className="h-full w-full object-cover" />}
586
- <span className="absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-muted-foreground px-1.5 text-[10px] font-bold text-background">
587
- {line.quantity}
588
- </span>
589
- </div>
590
- <div className="min-w-0 flex-1">
591
- <p className="truncate font-medium">{line.name}</p>
592
- {line.sku && <p className="text-xs text-muted-foreground">{line.sku}</p>}
593
- {line.optionsLabel && <p className="text-xs text-muted-foreground">{line.optionsLabel}</p>}
594
- </div>
595
- <span data-price={(line.unitPrice * line.quantity).toFixed(2)}>
596
- {money(line.unitPrice * line.quantity)}
597
- </span>
598
- </li>
599
- ))}
600
- </ul>
601
- {config.features.discounts && (
602
- <div className="mt-5 space-y-2 border-t pt-5">
603
- <div className="flex gap-2">
604
- <input
605
- data-testid={TID.discountInput}
606
- type="text"
607
- placeholder="Cupom de desconto"
608
- value={discountCode}
609
- onChange={(event) => {
610
- setDiscountCode(event.target.value)
611
- setDiscountSuccess(null)
612
- setDiscountError(null)
613
- }}
614
- className="min-w-0 flex-1 rounded-lg border bg-background px-3 py-2 text-sm uppercase"
615
- />
616
- <button
617
- type="button"
618
- data-testid={TID.discountApply}
619
- onClick={applyDiscountCode}
620
- disabled={applyingDiscount || !discountCode.trim()}
621
- className="rounded-lg border px-3 py-2 text-sm font-semibold hover:bg-muted disabled:opacity-50"
622
- >
623
- Aplicar
624
- </button>
625
- </div>
626
- {discountError && (
627
- <p data-testid={TID.discountError} className="text-xs text-destructive">
628
- {discountError}
629
- </p>
630
- )}
631
- {discountSuccess && (
632
- <p className="text-xs font-semibold text-emerald-700">
633
- {discountSuccess}
634
- </p>
635
- )}
636
- </div>
637
- )}
638
- <dl className="mt-8 space-y-3 border-t pt-6 text-sm">
639
- <div className="flex justify-between">
640
- <dt className="text-muted-foreground">Subtotal</dt>
641
- <dd data-price={subtotal.toFixed(2)}>{money(subtotal)}</dd>
642
- </div>
643
- {discountTotal > 0 && (
644
- <div className="flex justify-between text-emerald-700" data-testid={TID.discountRow}>
645
- <dt>
646
- Desconto{' '}
647
- <button
648
- type="button"
649
- onClick={clearDiscountCode}
650
- className="text-xs text-muted-foreground underline"
651
- >
652
- ({cart.discountCode} ×)
653
- </button>
654
- </dt>
655
- <dd data-price={discountTotal.toFixed(2)}>-{money(discountTotal)}</dd>
656
- </div>
657
- )}
658
- <div className="flex justify-between">
659
- <dt className="text-muted-foreground">Entrega</dt>
660
- <dd data-price={shipping.toFixed(2)}>{shipping === 0 ? 'Grátis' : money(shipping)}</dd>
661
- </div>
662
- <div className="flex justify-between border-t pt-5 text-lg font-bold">
663
- <dt>Total</dt>
664
- <dd data-price={total.toFixed(2)}>{money(total)}</dd>
665
- </div>
666
- </dl>
667
- <div className="mt-6 flex items-center gap-2 text-xs text-muted-foreground">
668
- <ShieldCheck className="h-4 w-4 text-primary" />
669
- Compra protegida e dados criptografados.
670
- </div>
671
- <div className="mt-6 grid gap-3 border-t pt-5">
672
- <button
673
- type="button"
674
- data-testid={TID.placeOrder}
675
- onClick={placeOrder}
676
- disabled={placing}
677
- className="rounded-lg bg-primary px-6 py-4 text-sm font-bold text-primary-foreground transition hover:opacity-90 disabled:opacity-60"
678
- >
679
- {primaryCta}
680
- </button>
681
- <Link to={config.catalogPath} className="text-center text-sm text-muted-foreground hover:text-foreground">
682
- Voltar ao carrinho
683
- </Link>
684
- </div>
685
- </aside>
686
- </div>
687
- </main>
688
- )
689
- }