@salla.sa/twilight-theme-engine 1.0.85 → 1.0.86

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 (372) hide show
  1. package/dist/{AddToCartForm-B5C2M4OZ.js → AddToCartForm-5X2WZDWR.js} +3 -3
  2. package/dist/{AddToCartForm-B5C2M4OZ.js.map → AddToCartForm-5X2WZDWR.js.map} +1 -1
  3. package/dist/{BlogAuthorPage-GTF4UGSS.js → BlogAuthorPage-LO7FSSJR.js} +12 -10
  4. package/dist/BlogAuthorPage-LO7FSSJR.js.map +1 -0
  5. package/dist/{BlogCategoryPage-TN7Y4NBP.js → BlogCategoryPage-KKFQ3PGR.js} +12 -10
  6. package/dist/BlogCategoryPage-KKFQ3PGR.js.map +1 -0
  7. package/dist/{BlogPage-TDB3M3PI.js → BlogPage-VLF3NSGE.js} +11 -9
  8. package/dist/BlogPage-VLF3NSGE.js.map +1 -0
  9. package/dist/{BlogSinglePage-4A52Q5QW.js → BlogSinglePage-AVK535IW.js} +11 -9
  10. package/dist/BlogSinglePage-AVK535IW.js.map +1 -0
  11. package/dist/{BlogTagPage-TOVPBETA.js → BlogTagPage-F3T4D7RH.js} +12 -10
  12. package/dist/BlogTagPage-F3T4D7RH.js.map +1 -0
  13. package/dist/{BrandsPage-VUDGYSQI.js → BrandsPage-LVSBZSVC.js} +11 -9
  14. package/dist/BrandsPage-LVSBZSVC.js.map +1 -0
  15. package/dist/{Breadcrumb-CI3SN5YR.js → Breadcrumb-VLFTXRFC.js} +11 -9
  16. package/dist/Breadcrumb-VLFTXRFC.js.map +1 -0
  17. package/dist/{CartItem-BHLEURAB.js → CartItem-G3H34GW6.js} +11 -9
  18. package/dist/CartItem-G3H34GW6.js.map +1 -0
  19. package/dist/{CartSummary-QERVESYN.js → CartSummary-VH37RPUI.js} +11 -9
  20. package/dist/CartSummary-VH37RPUI.js.map +1 -0
  21. package/dist/{Copyright-RL6ALKRC.js → Copyright-HE5ZWINZ.js} +3 -3
  22. package/dist/{Copyright-RL6ALKRC.js.map → Copyright-HE5ZWINZ.js.map} +1 -1
  23. package/dist/{CustomerLayout-VRZUWGQK.js → CustomerLayout-LKBLOAVW.js} +11 -9
  24. package/dist/CustomerLayout-LKBLOAVW.js.map +1 -0
  25. package/dist/{Footer-ZWBZCMDL.js → Footer-HHXAST72.js} +5 -5
  26. package/dist/{Footer-ZWBZCMDL.js.map → Footer-HHXAST72.js.map} +1 -1
  27. package/dist/{Header-WNK4O66B.js → Header-GEVZM2YG.js} +8 -6
  28. package/dist/{Header-WNK4O66B.js.map → Header-GEVZM2YG.js.map} +1 -1
  29. package/dist/{LoyaltyPage-CGTHWJ76.js → LoyaltyPage-M5CT3CFD.js} +11 -9
  30. package/dist/LoyaltyPage-M5CT3CFD.js.map +1 -0
  31. package/dist/{LoyaltyPointsPage-QSU6K3PW.js → LoyaltyPointsPage-SDJB52U6.js} +10 -3
  32. package/dist/LoyaltyPointsPage-SDJB52U6.js.map +1 -0
  33. package/dist/{MainMenu-V3T7F32R.js → MainMenu-I7EYS752.js} +3 -3
  34. package/dist/{MainMenu-V3T7F32R.js.map → MainMenu-I7EYS752.js.map} +1 -1
  35. package/dist/{MasterLayout-XQF644LD.js → MasterLayout-QDK4T3NE.js} +11 -9
  36. package/dist/{MasterLayout-XQF644LD.js.map → MasterLayout-QDK4T3NE.js.map} +1 -1
  37. package/dist/{MobileMenu-U4BVQYKH.js → MobileMenu-7VTCWAVJ.js} +11 -9
  38. package/dist/MobileMenu-7VTCWAVJ.js.map +1 -0
  39. package/dist/{NotificationsPage-ZRRQY7TF.js → NotificationsPage-QCABE6QD.js} +11 -9
  40. package/dist/NotificationsPage-QCABE6QD.js.map +1 -0
  41. package/dist/{OrderSinglePage-O3XYVMG5.js → OrderSinglePage-2HVBKBG4.js} +11 -9
  42. package/dist/OrderSinglePage-2HVBKBG4.js.map +1 -0
  43. package/dist/{OrdersPage-CMXZF5MI.js → OrdersPage-5MNCPCM3.js} +11 -9
  44. package/dist/OrdersPage-5MNCPCM3.js.map +1 -0
  45. package/dist/{PageSingle-IIG2US43.js → PageSingle-EN5XIM2B.js} +3 -3
  46. package/dist/{PageSingle-IIG2US43.js.map → PageSingle-EN5XIM2B.js.map} +1 -1
  47. package/dist/{ProductDetails-65GDABI7.js → ProductDetails-UEWQPPDZ.js} +13 -6
  48. package/dist/ProductDetails-UEWQPPDZ.js.map +1 -0
  49. package/dist/{ProfilePage-SNJ4WXU4.js → ProfilePage-TZSU3GHZ.js} +11 -9
  50. package/dist/ProfilePage-TZSU3GHZ.js.map +1 -0
  51. package/dist/{ReviewsPage-UT7UT55J.js → ReviewsPage-WYZ6CHF2.js} +3 -3
  52. package/dist/{ReviewsPage-UT7UT55J.js.map → ReviewsPage-WYZ6CHF2.js.map} +1 -1
  53. package/dist/{SeoCartWidget-DBJAXJKT.js → SeoCartWidget-5JR5RXQM.js} +11 -9
  54. package/dist/SeoCartWidget-5JR5RXQM.js.map +1 -0
  55. package/dist/{SettingsPage-FLTIOPU5.js → SettingsPage-DIDRQXMM.js} +11 -9
  56. package/dist/SettingsPage-DIDRQXMM.js.map +1 -0
  57. package/dist/{StoreClosedNotification-ZT3QKDKS.js → StoreClosedNotification-C2G32E34.js} +12 -10
  58. package/dist/{StoreClosedNotification-ZT3QKDKS.js.map → StoreClosedNotification-C2G32E34.js.map} +1 -1
  59. package/dist/{TestimonialsPage-CU7HKU4O.js → TestimonialsPage-XPHFRBZ7.js} +3 -3
  60. package/dist/{TestimonialsPage-CU7HKU4O.js.map → TestimonialsPage-XPHFRBZ7.js.map} +1 -1
  61. package/dist/{ThankYouPage-5X6B4X3T.js → ThankYouPage-UA6YYFB2.js} +11 -9
  62. package/dist/ThankYouPage-UA6YYFB2.js.map +1 -0
  63. package/dist/{WalletPage-H2BKIDPA.js → WalletPage-AJVV7LVL.js} +11 -9
  64. package/dist/WalletPage-AJVV7LVL.js.map +1 -0
  65. package/dist/{WishlistPage-RS5UCIQ3.js → WishlistPage-UW6LJLSW.js} +11 -9
  66. package/dist/WishlistPage-UW6LJLSW.js.map +1 -0
  67. package/dist/api/advertisement.d.ts +33 -0
  68. package/dist/api/advertisement.d.ts.map +1 -0
  69. package/dist/api/advertisement.js +8 -0
  70. package/dist/api/advertisement.js.map +1 -0
  71. package/dist/boot/critical-style.d.ts +3 -0
  72. package/dist/boot/critical-style.d.ts.map +1 -0
  73. package/dist/boot/index.d.ts +15 -0
  74. package/dist/boot/index.d.ts.map +1 -0
  75. package/dist/boot.d.ts +1 -0
  76. package/dist/boot.js +69 -0
  77. package/dist/boot.js.map +1 -0
  78. package/dist/{chunk-SE5JYWOK.js → chunk-2HCCIHU7.js} +4 -4
  79. package/dist/{chunk-SE5JYWOK.js.map → chunk-2HCCIHU7.js.map} +1 -1
  80. package/dist/chunk-4FJGZQD3.js +6 -0
  81. package/dist/chunk-4FJGZQD3.js.map +1 -0
  82. package/dist/{chunk-SDFDS4Q3.js → chunk-4WVJJ7SN.js} +20 -4
  83. package/dist/chunk-4WVJJ7SN.js.map +1 -0
  84. package/dist/{chunk-NXQJA7D3.js → chunk-BAYGWTNM.js} +3 -3
  85. package/dist/{chunk-NXQJA7D3.js.map → chunk-BAYGWTNM.js.map} +1 -1
  86. package/dist/{chunk-OWX2CPNQ.js → chunk-CLBFF2CY.js} +3 -3
  87. package/dist/{chunk-OWX2CPNQ.js.map → chunk-CLBFF2CY.js.map} +1 -1
  88. package/dist/chunk-DBW7XXNE.js +103 -0
  89. package/dist/chunk-DBW7XXNE.js.map +1 -0
  90. package/dist/{chunk-KHBSGD2D.js → chunk-DBXH3EMJ.js} +3 -3
  91. package/dist/{chunk-KHBSGD2D.js.map → chunk-DBXH3EMJ.js.map} +1 -1
  92. package/dist/{chunk-JRNH3FUF.js → chunk-E26YG3AO.js} +4 -4
  93. package/dist/{chunk-JRNH3FUF.js.map → chunk-E26YG3AO.js.map} +1 -1
  94. package/dist/{chunk-22SH7OVF.js → chunk-E7WWO3V3.js} +3 -3
  95. package/dist/{chunk-22SH7OVF.js.map → chunk-E7WWO3V3.js.map} +1 -1
  96. package/dist/{chunk-UJKFBZFJ.js → chunk-HQ7WA37L.js} +7 -7
  97. package/dist/{chunk-UJKFBZFJ.js.map → chunk-HQ7WA37L.js.map} +1 -1
  98. package/dist/chunk-IVBOLREE.js +29 -0
  99. package/dist/chunk-IVBOLREE.js.map +1 -0
  100. package/dist/{chunk-6SWDSRIL.js → chunk-KP3VYG4F.js} +6 -6
  101. package/dist/{chunk-6SWDSRIL.js.map → chunk-KP3VYG4F.js.map} +1 -1
  102. package/dist/{chunk-4CZRUAZB.js → chunk-PWTXR4RR.js} +10 -10
  103. package/dist/{chunk-4CZRUAZB.js.map → chunk-PWTXR4RR.js.map} +1 -1
  104. package/dist/chunk-QJYUTRAQ.js +14 -0
  105. package/dist/{chunk-7WAXN2PP.js.map → chunk-QJYUTRAQ.js.map} +1 -1
  106. package/dist/{chunk-JO7JWV34.js → chunk-U55ZJC2X.js} +4 -4
  107. package/dist/{chunk-JO7JWV34.js.map → chunk-U55ZJC2X.js.map} +1 -1
  108. package/dist/{chunk-SX5MF2YA.js → chunk-WSDG4ZRI.js} +4 -4
  109. package/dist/{chunk-SX5MF2YA.js.map → chunk-WSDG4ZRI.js.map} +1 -1
  110. package/dist/{chunk-NZVR4OF7.js → chunk-X54QP332.js} +8 -8
  111. package/dist/chunk-X54QP332.js.map +1 -0
  112. package/dist/components/cart/index.js +1 -1
  113. package/dist/components/common/index.js +1 -1
  114. package/dist/components/drawer/index.js +9 -7
  115. package/dist/components/home/index.js +11 -9
  116. package/dist/components/layout/PageAdvertisements.d.ts +7 -0
  117. package/dist/components/layout/PageAdvertisements.d.ts.map +1 -0
  118. package/dist/components/layout/index.d.ts +1 -0
  119. package/dist/components/layout/index.d.ts.map +1 -1
  120. package/dist/components/layout/index.js +8 -1
  121. package/dist/components/product/index.js +10 -8
  122. package/dist/components/store/index.js +11 -9
  123. package/dist/components/store/index.js.map +1 -1
  124. package/dist/{document-class-67HTP45G.js → document-class-7AXAAWGZ.js} +6 -5
  125. package/dist/{document-class-67HTP45G.js.map → document-class-7AXAAWGZ.js.map} +1 -1
  126. package/dist/hooks/index.js +9 -7
  127. package/dist/hooks/useGtm.js +9 -7
  128. package/dist/hooks/useMoney.js +9 -7
  129. package/dist/hooks/useStore.js +9 -7
  130. package/dist/index.js +9 -7
  131. package/dist/index.js.map +1 -1
  132. package/dist/{not-found-QTHBFPNT.js → not-found-3EEMQZMU.js} +11 -9
  133. package/dist/not-found-3EEMQZMU.js.map +1 -0
  134. package/dist/providers/index.js +9 -7
  135. package/dist/routes/account/orders.js +1 -1
  136. package/dist/routes/account.js +6 -6
  137. package/dist/routes/blog.js +5 -5
  138. package/dist/routes/brands.js +1 -1
  139. package/dist/routes/cart.js +13 -11
  140. package/dist/routes/cart.js.map +1 -1
  141. package/dist/routes/home.js +12 -10
  142. package/dist/routes/loyalty.js +2 -2
  143. package/dist/routes/page.js +1 -1
  144. package/dist/routes/product-listing.js +11 -9
  145. package/dist/routes/product-listing.js.map +1 -1
  146. package/dist/routes/product.js +11 -9
  147. package/dist/routes/product.js.map +1 -1
  148. package/dist/routes/root/beforeLoad.d.ts +2 -2
  149. package/dist/routes/root/beforeLoad.d.ts.map +1 -1
  150. package/dist/routes/root/head.d.ts.map +1 -1
  151. package/dist/routes/testimonials.js +2 -2
  152. package/dist/routes/thank-you.js +1 -1
  153. package/dist/skeleton.js +12 -10
  154. package/dist/tanstack/document-class.d.ts.map +1 -1
  155. package/dist/tanstack/index.d.ts +1 -0
  156. package/dist/tanstack/index.d.ts.map +1 -1
  157. package/dist/tanstack/stylesheet.d.ts +15 -0
  158. package/dist/tanstack/stylesheet.d.ts.map +1 -0
  159. package/dist/tanstack.js +34 -5
  160. package/dist/tanstack.js.map +1 -1
  161. package/dist/utils/index.js +1 -1
  162. package/dist/vite/index.js +1 -1
  163. package/package.json +12 -3
  164. package/tailwind/components/Component.tsx.txt +21 -0
  165. package/tailwind/components/ComponentRegistry.ts.txt +66 -0
  166. package/tailwind/components/cart/CartItem.tsx.txt +211 -0
  167. package/tailwind/components/cart/CartSummary.tsx.txt +269 -0
  168. package/tailwind/components/cart/PreOrderNotification.tsx.txt +16 -0
  169. package/tailwind/components/cart/PriceQuoteButton.tsx.txt +21 -0
  170. package/tailwind/components/cart/SeoCartWidget.tsx.txt +99 -0
  171. package/tailwind/components/cart/index.ts.txt +11 -0
  172. package/tailwind/components/cart/lazy.ts.txt +15 -0
  173. package/tailwind/components/collapse/Collapse.tsx.txt +95 -0
  174. package/tailwind/components/collapse/index.ts.txt +41 -0
  175. package/tailwind/components/common/Breadcrumb.tsx.txt +27 -0
  176. package/tailwind/components/common/BreadcrumbJsonLd.tsx.txt +40 -0
  177. package/tailwind/components/common/BreadcrumbTrail.tsx.txt +97 -0
  178. package/tailwind/components/common/CurrencySymbol.tsx.txt +8 -0
  179. package/tailwind/components/common/DeferredData.tsx.txt +122 -0
  180. package/tailwind/components/common/ErrorPage.tsx.txt +69 -0
  181. package/tailwind/components/common/Image.tsx.txt +119 -0
  182. package/tailwind/components/common/Link.tsx.txt +36 -0
  183. package/tailwind/components/common/Link.types.ts.txt +85 -0
  184. package/tailwind/components/common/NoContent.tsx.txt +27 -0
  185. package/tailwind/components/common/RenderWhenVisible.tsx.txt +92 -0
  186. package/tailwind/components/common/index.ts.txt +42 -0
  187. package/tailwind/components/common/lazy.ts.txt +28 -0
  188. package/tailwind/components/defineComponent.ts.txt +32 -0
  189. package/tailwind/components/drawer/Drawer.tsx.txt +219 -0
  190. package/tailwind/components/drawer/index.ts.txt +38 -0
  191. package/tailwind/components/dropdown/Dropdown.tsx.txt +166 -0
  192. package/tailwind/components/dropdown/index.ts.txt +40 -0
  193. package/tailwind/components/home/BundleComponent.tsx.txt +35 -0
  194. package/tailwind/components/home/ComponentErrorBoundary.tsx.txt +98 -0
  195. package/tailwind/components/home/FeaturedProductsStyle1.tsx.txt +125 -0
  196. package/tailwind/components/home/FeaturedProductsStyle2.tsx.txt +127 -0
  197. package/tailwind/components/home/FeaturedProductsStyle3.tsx.txt +71 -0
  198. package/tailwind/components/home/FixedBanner.tsx.txt +68 -0
  199. package/tailwind/components/home/FixedProducts.tsx.txt +72 -0
  200. package/tailwind/components/home/HomePageRenderer.tsx.txt +284 -0
  201. package/tailwind/components/home/PageBlocks.tsx.txt +24 -0
  202. package/tailwind/components/home/ParallaxBackground.tsx.txt +50 -0
  203. package/tailwind/components/home/PhotosSlider.tsx.txt +61 -0
  204. package/tailwind/components/home/ProductsSlider.tsx.txt +87 -0
  205. package/tailwind/components/home/ReorderProducts.tsx.txt +38 -0
  206. package/tailwind/components/home/SquarePhotos.tsx.txt +68 -0
  207. package/tailwind/components/home/StoreFeatures.tsx.txt +34 -0
  208. package/tailwind/components/home/Testimonials.tsx.txt +36 -0
  209. package/tailwind/components/home/Youtube.tsx.txt +48 -0
  210. package/tailwind/components/home/index.ts.txt +50 -0
  211. package/tailwind/components/home/lazy.ts.txt +17 -0
  212. package/tailwind/components/home/register.ts.txt +44 -0
  213. package/tailwind/components/home/reorder-hooks.ts.txt +52 -0
  214. package/tailwind/components/home/types.ts.txt +4 -0
  215. package/tailwind/components/layout/Copyright.tsx.txt +23 -0
  216. package/tailwind/components/layout/CustomerLayout.tsx.txt +91 -0
  217. package/tailwind/components/layout/Footer.tsx.txt +343 -0
  218. package/tailwind/components/layout/Header.tsx.txt +154 -0
  219. package/tailwind/components/layout/MasterLayout.tsx.txt +63 -0
  220. package/tailwind/components/layout/PageAdvertisements.tsx.txt +131 -0
  221. package/tailwind/components/layout/WidgetHead.tsx.txt +53 -0
  222. package/tailwind/components/layout/index.ts.txt +27 -0
  223. package/tailwind/components/layout/lazy.ts.txt +25 -0
  224. package/tailwind/components/layout/types.ts.txt +11 -0
  225. package/tailwind/components/modal/ImageModal.tsx.txt +70 -0
  226. package/tailwind/components/modal/Modal.tsx.txt +245 -0
  227. package/tailwind/components/modal/index.ts.txt +39 -0
  228. package/tailwind/components/modal/lazy.ts.txt +10 -0
  229. package/tailwind/components/navigation/MainMenu.tsx.txt +94 -0
  230. package/tailwind/components/navigation/MobileMenu.tsx.txt +160 -0
  231. package/tailwind/components/navigation/index.ts.txt +4 -0
  232. package/tailwind/components/navigation/lazy.ts.txt +11 -0
  233. package/tailwind/components/product/AddToCartForm.tsx.txt +365 -0
  234. package/tailwind/components/product/ProductCard.tsx.txt +305 -0
  235. package/tailwind/components/product/ProductDetails.tsx.txt +267 -0
  236. package/tailwind/components/product/ProductGallery.tsx.txt +234 -0
  237. package/tailwind/components/product/index.ts.txt +15 -0
  238. package/tailwind/components/product/lazy.ts.txt +9 -0
  239. package/tailwind/components/snippets/AppsSnippets.tsx.txt +225 -0
  240. package/tailwind/components/snippets/SallaSdkScripts.tsx.txt +14 -0
  241. package/tailwind/components/snippets/index.ts.txt +4 -0
  242. package/tailwind/components/store/StoreClosedNotification.tsx.txt +73 -0
  243. package/tailwind/components/store/index.ts.txt +8 -0
  244. package/tailwind/components/store/lazy.ts.txt +8 -0
  245. package/tailwind/components/toast/Toaster.tsx.txt +177 -0
  246. package/tailwind/components/toast/index.ts.txt +10 -0
  247. package/tailwind/components/toast/types.ts.txt +38 -0
  248. package/tailwind/components/toast/useToast.ts.txt +61 -0
  249. package/tailwind/components/tracking/GoogleTag.tsx.txt +77 -0
  250. package/tailwind/components/tracking/SiftSnippet.tsx.txt +49 -0
  251. package/tailwind/components/tracking/TwilightBundles.tsx.txt +67 -0
  252. package/tailwind/components/tracking/index.ts.txt +11 -0
  253. package/tailwind/components/tracking/lazy.ts.txt +11 -0
  254. package/tailwind/components/useComponent.ts.txt +12 -0
  255. package/tailwind/routes/account/index.tsx.txt +16 -0
  256. package/tailwind/routes/account/notifications/NotificationsPage.tsx.txt +54 -0
  257. package/tailwind/routes/account/notifications/index.tsx.txt +39 -0
  258. package/tailwind/routes/account/notifications/loader.ts.txt +22 -0
  259. package/tailwind/routes/account/notifications/types.ts.txt +18 -0
  260. package/tailwind/routes/account/orders/OrderSinglePage.tsx.txt +181 -0
  261. package/tailwind/routes/account/orders/OrdersPage.tsx.txt +28 -0
  262. package/tailwind/routes/account/orders/index.tsx.txt +86 -0
  263. package/tailwind/routes/account/orders/loader.ts.txt +66 -0
  264. package/tailwind/routes/account/orders/single/OrderActions.tsx.txt +197 -0
  265. package/tailwind/routes/account/orders/single/OrderHeader.tsx.txt +143 -0
  266. package/tailwind/routes/account/orders/single/OrderItemRow.tsx.txt +209 -0
  267. package/tailwind/routes/account/orders/single/OrderSections.tsx.txt +215 -0
  268. package/tailwind/routes/account/orders/single/types.ts.txt +38 -0
  269. package/tailwind/routes/account/orders/types.ts.txt +29 -0
  270. package/tailwind/routes/account/profile/ProfilePage.tsx.txt +30 -0
  271. package/tailwind/routes/account/profile/index.tsx.txt +39 -0
  272. package/tailwind/routes/account/profile/loader.ts.txt +16 -0
  273. package/tailwind/routes/account/profile/types.ts.txt +5 -0
  274. package/tailwind/routes/account/settings/SettingsPage.tsx.txt +13 -0
  275. package/tailwind/routes/account/settings/index.tsx.txt +39 -0
  276. package/tailwind/routes/account/settings/loader.ts.txt +13 -0
  277. package/tailwind/routes/account/settings/types.ts.txt +5 -0
  278. package/tailwind/routes/account/types.ts.txt +320 -0
  279. package/tailwind/routes/account/wallet/WalletPage.tsx.txt +195 -0
  280. package/tailwind/routes/account/wallet/index.tsx.txt +38 -0
  281. package/tailwind/routes/account/wallet/loader.ts.txt +27 -0
  282. package/tailwind/routes/account/wallet/types.ts.txt +11 -0
  283. package/tailwind/routes/account/wishlist/WishlistPage.tsx.txt +314 -0
  284. package/tailwind/routes/account/wishlist/WishlistShare.tsx.txt +248 -0
  285. package/tailwind/routes/account/wishlist/index.tsx.txt +40 -0
  286. package/tailwind/routes/account/wishlist/loader.ts.txt +26 -0
  287. package/tailwind/routes/account/wishlist/types.ts.txt +9 -0
  288. package/tailwind/routes/blog/BlogAuthorPage.tsx.txt +82 -0
  289. package/tailwind/routes/blog/BlogCategoryPage.tsx.txt +84 -0
  290. package/tailwind/routes/blog/BlogPage.tsx.txt +191 -0
  291. package/tailwind/routes/blog/BlogSinglePage.tsx.txt +168 -0
  292. package/tailwind/routes/blog/BlogTagPage.tsx.txt +84 -0
  293. package/tailwind/routes/blog/BlogTags.tsx.txt +54 -0
  294. package/tailwind/routes/blog/components.tsx.txt +87 -0
  295. package/tailwind/routes/blog/index.tsx.txt +238 -0
  296. package/tailwind/routes/blog/loader.ts.txt +173 -0
  297. package/tailwind/routes/blog/types.ts.txt +106 -0
  298. package/tailwind/routes/brands/BrandsPage.tsx.txt +179 -0
  299. package/tailwind/routes/brands/index.tsx.txt +47 -0
  300. package/tailwind/routes/brands/loader.ts.txt +14 -0
  301. package/tailwind/routes/brands/types.ts.txt +53 -0
  302. package/tailwind/routes/cart/CartPage.tsx.txt +112 -0
  303. package/tailwind/routes/cart/index.tsx.txt +39 -0
  304. package/tailwind/routes/cart/loader.ts.txt +15 -0
  305. package/tailwind/routes/cart/types.ts.txt +6 -0
  306. package/tailwind/routes/home/HomePage.tsx.txt +53 -0
  307. package/tailwind/routes/home/index.tsx.txt +56 -0
  308. package/tailwind/routes/home/loader.ts.txt +24 -0
  309. package/tailwind/routes/home/types.ts.txt +11 -0
  310. package/tailwind/routes/index.ts.txt +94 -0
  311. package/tailwind/routes/lazy.ts.txt +105 -0
  312. package/tailwind/routes/loyalty/LoyaltyPage.tsx.txt +17 -0
  313. package/tailwind/routes/loyalty/LoyaltyPointsPage.tsx.txt +11 -0
  314. package/tailwind/routes/loyalty/index.tsx.txt +81 -0
  315. package/tailwind/routes/loyalty/loader.ts.txt +53 -0
  316. package/tailwind/routes/loyalty/types.ts.txt +109 -0
  317. package/tailwind/routes/not-found.tsx.txt +50 -0
  318. package/tailwind/routes/page/PageSingle.tsx.txt +34 -0
  319. package/tailwind/routes/page/index.tsx.txt +60 -0
  320. package/tailwind/routes/page/loader.ts.txt +19 -0
  321. package/tailwind/routes/page/types.ts.txt +34 -0
  322. package/tailwind/routes/product/ProductPage.tsx.txt +77 -0
  323. package/tailwind/routes/product/index.tsx.txt +157 -0
  324. package/tailwind/routes/product/loader.ts.txt +35 -0
  325. package/tailwind/routes/product/types.ts.txt +6 -0
  326. package/tailwind/routes/product-listing/ProductListPage.tsx.txt +321 -0
  327. package/tailwind/routes/product-listing/index.tsx.txt +95 -0
  328. package/tailwind/routes/product-listing/loader.ts.txt +323 -0
  329. package/tailwind/routes/product-listing/types.ts.txt +96 -0
  330. package/tailwind/routes/product-listing/useAppliedFilters.ts.txt +130 -0
  331. package/tailwind/routes/product-short-link.ts.txt +104 -0
  332. package/tailwind/routes/root/beforeLoad.ts.txt +354 -0
  333. package/tailwind/routes/root/head.ts.txt +424 -0
  334. package/tailwind/routes/root/index.ts.txt +2 -0
  335. package/tailwind/routes/seo-redirects.ts.txt +82 -0
  336. package/tailwind/routes/testimonials/ReviewsPage.tsx.txt +12 -0
  337. package/tailwind/routes/testimonials/TestimonialsPage.tsx.txt +80 -0
  338. package/tailwind/routes/testimonials/index.tsx.txt +80 -0
  339. package/tailwind/routes/testimonials/loader.ts.txt +29 -0
  340. package/tailwind/routes/testimonials/types.ts.txt +9 -0
  341. package/tailwind/routes/thank-you/ThankYouPage.tsx.txt +241 -0
  342. package/tailwind/routes/thank-you/index.tsx.txt +49 -0
  343. package/tailwind/routes/thank-you/loader.ts.txt +40 -0
  344. package/tailwind/routes/thank-you/types.ts.txt +16 -0
  345. package/tailwind/routes/types.ts.txt +41 -0
  346. package/dist/BlogAuthorPage-GTF4UGSS.js.map +0 -1
  347. package/dist/BlogCategoryPage-TN7Y4NBP.js.map +0 -1
  348. package/dist/BlogPage-TDB3M3PI.js.map +0 -1
  349. package/dist/BlogSinglePage-4A52Q5QW.js.map +0 -1
  350. package/dist/BlogTagPage-TOVPBETA.js.map +0 -1
  351. package/dist/BrandsPage-VUDGYSQI.js.map +0 -1
  352. package/dist/Breadcrumb-CI3SN5YR.js.map +0 -1
  353. package/dist/CartItem-BHLEURAB.js.map +0 -1
  354. package/dist/CartSummary-QERVESYN.js.map +0 -1
  355. package/dist/CustomerLayout-VRZUWGQK.js.map +0 -1
  356. package/dist/LoyaltyPage-CGTHWJ76.js.map +0 -1
  357. package/dist/LoyaltyPointsPage-QSU6K3PW.js.map +0 -1
  358. package/dist/MobileMenu-U4BVQYKH.js.map +0 -1
  359. package/dist/NotificationsPage-ZRRQY7TF.js.map +0 -1
  360. package/dist/OrderSinglePage-O3XYVMG5.js.map +0 -1
  361. package/dist/OrdersPage-CMXZF5MI.js.map +0 -1
  362. package/dist/ProductDetails-65GDABI7.js.map +0 -1
  363. package/dist/ProfilePage-SNJ4WXU4.js.map +0 -1
  364. package/dist/SeoCartWidget-DBJAXJKT.js.map +0 -1
  365. package/dist/SettingsPage-FLTIOPU5.js.map +0 -1
  366. package/dist/ThankYouPage-5X6B4X3T.js.map +0 -1
  367. package/dist/WalletPage-H2BKIDPA.js.map +0 -1
  368. package/dist/WishlistPage-RS5UCIQ3.js.map +0 -1
  369. package/dist/chunk-7WAXN2PP.js +0 -14
  370. package/dist/chunk-NZVR4OF7.js.map +0 -1
  371. package/dist/chunk-SDFDS4Q3.js.map +0 -1
  372. package/dist/not-found-QTHBFPNT.js.map +0 -1
@@ -0,0 +1,365 @@
1
+ import { SallaAddProductButton } from '@salla.sa/twilight-components-react/add-product-button';
2
+ import { SallaButton } from '@salla.sa/twilight-components-react/button';
3
+ import { SallaCountDown } from '@salla.sa/twilight-components-react/count-down';
4
+ import { SallaFileUpload } from '@salla.sa/twilight-components-react/file-upload';
5
+ import { SallaGifting } from '@salla.sa/twilight-components-react/gifting';
6
+ import { SallaMultipleBundleProduct } from '@salla.sa/twilight-components-react/multiple-bundle-product';
7
+ // Use the eager (non-deferred) Core export: product options are an essential form
8
+ // control, and the deferred wrapper's visibility-triggered hydration races with the
9
+ // gallery's image layout shift and gets stuck on the skeleton, so options never appear.
10
+ import { SallaProductOptionsCore as SallaProductOptions } from '@salla.sa/twilight-components-react/product-options';
11
+ import { SallaProductSizeGuide } from '@salla.sa/twilight-components-react/product-size-guide';
12
+ import { SallaQuantityInput } from '@salla.sa/twilight-components-react/quantity-input';
13
+ import { useHostAttributes } from '@salla.sa/twilight-theme-engine/hooks/useHostAttributes';
14
+ import { useTranslation } from '@salla.sa/twilight-theme-engine/i18n';
15
+ import { getActiveCurrency } from '@salla.sa/twilight-theme-engine/api/client';
16
+ import type { ReactNode } from 'react';
17
+ import { useCallback, useEffect, useState } from 'react';
18
+ import type { Product } from '../../types';
19
+ import { CurrencySymbol } from '../common';
20
+
21
+ interface AddToCartFormProps {
22
+ product: Product;
23
+ stickyAddToCart?: boolean;
24
+ giftingIntro?: string;
25
+ formStartSlot?: ReactNode;
26
+ formEndSlot?: ReactNode;
27
+ }
28
+
29
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
30
+ e.preventDefault();
31
+ // Native event, like Twig's onsubmit: the SDK spins its submitter's salla-button until the add settles.
32
+ window.salla?.form.onSubmit('cart.addItem', e.nativeEvent);
33
+ };
34
+
35
+ const handleChange = (e: React.FormEvent<HTMLFormElement>) => {
36
+ const form = e.currentTarget;
37
+ // Not reportValidity(): it focuses and scrolls to the first empty required option mid-edit.
38
+ const isComplete = Array.from(form.elements).every(
39
+ (el) => !(el as HTMLInputElement).willValidate || (el as HTMLInputElement).validity.valid
40
+ );
41
+ if (isComplete) {
42
+ window.salla?.product?.getPrice(new FormData(form));
43
+ }
44
+ };
45
+
46
+ export function AddToCartForm({
47
+ product,
48
+ stickyAddToCart,
49
+ giftingIntro,
50
+ formStartSlot,
51
+ formEndSlot,
52
+ }: AddToCartFormProps) {
53
+ const { t } = useTranslation();
54
+ // The API converts the amounts but still labels them in the store's own currency.
55
+ const priceCurrency = getActiveCurrency(product.currency);
56
+ const [activeCollapse, setActiveCollapse] = useState<string | null>(null);
57
+
58
+ // stable ref: an inline arrow re-runs on every render and resets the user's selection
59
+ const optionsRef = useCallback(
60
+ (el: { setOptionsData?: (data: never[]) => void } | null) =>
61
+ void el?.setOptionsData?.(product.options as never[]),
62
+ [product.options]
63
+ );
64
+
65
+ // salla-product-options reads its `options` prop only in its constructor (no
66
+ // @Watch), and on first load that constructor runs before sdk.init has set
67
+ // page.id — so its getDetails(page.id) fallback fetches nothing and the options
68
+ // never reflect. Seed page.id (=== product.id) and wait for the SDK to be ready
69
+ // before mounting the element so its fallback resolves on first load too.
70
+ const [optionsReady, setOptionsReady] = useState(false);
71
+ useEffect(() => {
72
+ const salla = window.salla;
73
+ if (!salla) return;
74
+ let alive = true;
75
+ salla.config?.set?.('page.id', product.id);
76
+ Promise.resolve(salla.onReady?.()).finally(() => {
77
+ if (alive) setOptionsReady(true);
78
+ });
79
+ return () => {
80
+ alive = false;
81
+ };
82
+ }, [product.id]);
83
+
84
+ const handleCollapseToggle = useCallback((id: string) => {
85
+ setActiveCollapse((current) => (current === id ? null : id));
86
+ }, []);
87
+
88
+ const hasPreorder = product.has_preorder_campaign && product.preorder;
89
+ const hasWeightOrSizeGuide = product.weight || product.has_size_guide;
90
+ const hasAttachments = product.can_add_note || product.can_upload_file;
91
+ const amount =
92
+ typeof product.base_currency_price === 'object'
93
+ ? product.base_currency_price.amount
94
+ : product.base_currency_price;
95
+
96
+ const quantityRef = useHostAttributes({
97
+ max: product.max_quantity,
98
+ value: 1,
99
+ name: 'quantity',
100
+ });
101
+ const addButtonRef = useHostAttributes({
102
+ amount,
103
+ 'loader-position': 'end',
104
+ type: 'submit',
105
+ width: 'wide',
106
+ 'required-shipping': product.is_require_shipping,
107
+ 'is-subscribed': product.notify_availability?.subscribed,
108
+ });
109
+
110
+ return (
111
+ <form
112
+ className="form product-form"
113
+ encType="multipart/form-data"
114
+ method="post"
115
+ onSubmit={handleSubmit}
116
+ onChange={handleChange}
117
+ >
118
+ <input type="hidden" name="id" value={product.id} />
119
+
120
+ {formStartSlot}
121
+
122
+ {optionsReady && product.options && product.options.length > 0 && (
123
+ <SallaProductOptions
124
+ key={product.id}
125
+ // constructor-only options parse: push data via ref on client remount
126
+ ref={optionsRef}
127
+ options={JSON.stringify(product.options)}
128
+ productId={product.id}
129
+ />
130
+ )}
131
+
132
+ {product.has_bundle_products && (
133
+ <SallaMultipleBundleProduct bundleSections={JSON.stringify(product)} />
134
+ )}
135
+
136
+ {hasWeightOrSizeGuide && (
137
+ <section className="bg-white p-5 rounded-md mb-5">
138
+ {product.weight && (
139
+ <div
140
+ className={`center-between ${product.has_size_guide ? 'pb-5 mb-5 border-b-[1px]' : ''}`}
141
+ >
142
+ <b className="form-label rtl:space-x-reverse space-x-1">
143
+ <i className="sicon-luggage-cart"></i>
144
+ <span>{t('pages.products.weight', 'Weight')}</span>
145
+ </b>
146
+ <span className="product-weight text-sm">{product.weight}</span>
147
+ </div>
148
+ )}
149
+ {product.has_size_guide && (
150
+ <>
151
+ <div className="center-between">
152
+ <b className="form-label rtl:space-x-reverse space-x-1">
153
+ <i className="sicon-pencil-ruler"></i>
154
+ <span>{t('pages.products.size_guides', 'Size Guides')}</span>
155
+ </b>
156
+ <SallaButton
157
+ shape="link"
158
+ color="primary"
159
+ className="!p-0 text-sm with-arrow"
160
+ onClick={() =>
161
+ window.salla?.event?.dispatch('size-guide::open', String(product.id))
162
+ }
163
+ >
164
+ {t('pages.products.show_size_guides', 'Show Size Guides')}
165
+ <i className="sicon-keyboard_arrow_left"></i>
166
+ </SallaButton>
167
+ </div>
168
+ <SallaProductSizeGuide />
169
+ </>
170
+ )}
171
+ </section>
172
+ )}
173
+
174
+ {hasAttachments && (
175
+ <section className="bg-white p-5 rounded-md mb-5">
176
+ <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start">
177
+ <div className="form-label">
178
+ <b className="block">{t('pages.products.attachments', 'Attachments')}</b>
179
+ </div>
180
+ <div className="mt-1 sm:mt-0 sm:col-span-2 text-end">
181
+ <div className="flex rtl:space-x-reverse space-x-3">
182
+ {product.can_add_note && (
183
+ <button
184
+ type="button"
185
+ onClick={() => handleCollapseToggle(`note_${product.id}`)}
186
+ className={`btn-tab btn--collapse ${activeCollapse === `note_${product.id}` ? 'is-opened' : 'is-closed'}`}
187
+ >
188
+ <i className="font-medium sicon-chat-conversation-alt rtl:ml-1.5 ltr:mr-1.5"></i>
189
+ <span className="fix-align">{t('pages.products.add_note', 'Add Note')}</span>
190
+ </button>
191
+ )}
192
+ {product.can_upload_file && (
193
+ <button
194
+ type="button"
195
+ onClick={() => handleCollapseToggle(`file_${product.id}`)}
196
+ className={`btn-tab btn--collapse ${activeCollapse === `file_${product.id}` ? 'is-opened' : 'is-closed'}`}
197
+ >
198
+ <i className="font-medium sicon-paperclip rtl:ml-1.5 ltr:mr-1.5"></i>
199
+ <span className="fix-align">{t('pages.products.add_file', 'Add File')}</span>
200
+ </button>
201
+ )}
202
+ </div>
203
+
204
+ {product.can_add_note && (
205
+ <div
206
+ className={`collapse-content ${activeCollapse === `note_${product.id}` ? 'is-opened' : 'is-closed'}`}
207
+ >
208
+ <div className="pt-4">
209
+ <textarea
210
+ className="animated animatedfadeInDown fadeInDown form-input h-16 bg-gray-50 block"
211
+ placeholder={t('pages.products.notes_placeholder', 'Add your notes here...')}
212
+ name="notes"
213
+ cols={30}
214
+ rows={10}
215
+ defaultValue={product.notes}
216
+ ></textarea>
217
+ </div>
218
+ </div>
219
+ )}
220
+
221
+ {product.can_upload_file && (
222
+ <div
223
+ className={`collapse-content ${activeCollapse === `file_${product.id}` ? 'is-opened' : 'is-closed'}`}
224
+ >
225
+ <div className="pt-4 px-1">
226
+ <SallaFileUpload
227
+ accept="image/png, image/jpeg, image/jpg, image/gif, video/*, application/pdf"
228
+ className="product-option-uploader"
229
+ >
230
+ <div className="product-option-uploader-placholder">
231
+ <span className="product-option-uploader-placholder-icon">
232
+ <i className="sicon-camera"></i>
233
+ </span>
234
+ <p className="profile-filepond-placholder-text">
235
+ {t('common.uploader.drag_and_drop', 'Drag and drop files here')}
236
+ </p>
237
+ <span className="filepond--label-action">
238
+ {t('common.uploader.browse', 'Browse')}
239
+ </span>
240
+ </div>
241
+ </SallaFileUpload>
242
+ </div>
243
+ </div>
244
+ )}
245
+ </div>
246
+ </div>
247
+ </section>
248
+ )}
249
+
250
+ {formEndSlot}
251
+
252
+ <section className="flex bg-white p-5 sm:pb-0 rounded-md rounded-b-none">
253
+ <div className="center-between w-full">
254
+ <div className="form-label">
255
+ <b className="block">{t('pages.products.price', 'Price')}</b>
256
+ </div>
257
+
258
+ <div
259
+ className={`flex whitespace-nowrap price-wrapper gap-4 items-center ${product.is_out_of_stock ? 'hidden' : ''}`}
260
+ >
261
+ <div
262
+ className={`price_is_on_sale space-x-2 rtl:space-x-reverse whitespace-nowrap ${product.is_on_sale ? '' : 'hidden'}`}
263
+ >
264
+ <span className="total-price text-red-800 font-bold text-xl inline-block">
265
+ {product.sale_price} <CurrencySymbol currency={priceCurrency} />
266
+ </span>
267
+ <span className="before-price text-gray-500 line-through">
268
+ {product.regular_price} <CurrencySymbol currency={priceCurrency} />
269
+ </span>
270
+ </div>
271
+ <div
272
+ className={`starting-or-normal-price gap-4 ${product.is_on_sale ? 'hidden' : 'flex'}`}
273
+ >
274
+ {product.starting_price && (
275
+ <span className="starting-price-title">
276
+ {t('pages.products.starting_price', 'Starting from')}
277
+ </span>
278
+ )}
279
+ <span className="total-price font-bold text-xl inline-block">
280
+ {String(product.starting_price ?? product.price)}{' '}
281
+ <CurrencySymbol currency={priceCurrency} />
282
+ </span>
283
+ </div>
284
+ </div>
285
+
286
+ <div
287
+ className={`out-of-stock min-h-7 leading-7 text-base text-red-600 !opacity-50 font-bold ${product.is_out_of_stock ? 'scale-pulse' : 'hidden'}`}
288
+ >
289
+ {t('pages.products.out_of_stock', 'Out of Stock')}
290
+ </div>
291
+ </div>
292
+ </section>
293
+
294
+ {hasPreorder ? (
295
+ <section className="mt-5 p-5">
296
+ <SallaCountDown preOrder={JSON.stringify(product.preorder)} boxed labeled />
297
+ </section>
298
+ ) : null}
299
+
300
+ <section className="sticky-product-bar bg-white p-5 rounded-md rounded-b-none">
301
+ {product.is_hidden_quantity || product.type === 'booking' ? (
302
+ <input
303
+ type="hidden"
304
+ value="1"
305
+ name="quantity"
306
+ aria-label={t('pages.products.quantity', 'Quantity')}
307
+ />
308
+ ) : (
309
+ <div className="sticky-product-bar__quantity center-between mb-5">
310
+ <span className="form-label font-bold">{t('pages.products.quantity', 'Quantity')}</span>
311
+ <SallaQuantityInput
312
+ ref={quantityRef}
313
+ max={product.max_quantity ?? undefined}
314
+ value={1}
315
+ name="quantity"
316
+ aria-label={t('pages.products.quantity', 'Quantity')}
317
+ className="border-gray-200 flex justify-end"
318
+ />
319
+ </div>
320
+ )}
321
+
322
+ <SallaAddProductButton
323
+ ref={addButtonRef}
324
+ {...(product.can_quick_buy ? { quickBuy: true } : {})}
325
+ {...(stickyAddToCart ? { supportStickyBar: true } : {})}
326
+ {...(product.is_require_shipping ? { requiredShipping: true } : {})}
327
+ {...(hasPreorder ? { hasPreOrder: true } : {})}
328
+ amount={amount}
329
+ className="mt-5 w-full sticky-product-bar__btn"
330
+ productStatus={product.status}
331
+ productType={product.type}
332
+ productId={product.id}
333
+ loaderPosition="end"
334
+ type="submit"
335
+ width="wide"
336
+ {...(product.notify_availability
337
+ ? {
338
+ ...(product.notify_availability.subscribed ? { isSubscribed: true } : {}),
339
+ channels: product.notify_availability.channels.join(','),
340
+ ...(product.notify_availability.subscribed_options
341
+ ? {
342
+ subscribedOptions: JSON.stringify(
343
+ product.notify_availability.subscribed_options
344
+ ),
345
+ }
346
+ : {}),
347
+ ...(product.notify_availability.options ? { notifyOptionsAvailability: true } : {}),
348
+ }
349
+ : {})}
350
+ >
351
+ {product.add_to_cart_label}
352
+ </SallaAddProductButton>
353
+ </section>
354
+
355
+ {product.giftable && (
356
+ <SallaGifting
357
+ className="mt-5"
358
+ widgetSubtitle={giftingIntro}
359
+ productId={Number(product.id)}
360
+ {...(product.is_require_shipping ? { physicalProducts: true } : {})}
361
+ />
362
+ )}
363
+ </form>
364
+ );
365
+ }
@@ -0,0 +1,305 @@
1
+ import { memo, useCallback, useMemo } from 'react';
2
+ import { SallaAddProductButton } from '@salla.sa/twilight-components-react/add-product-button';
3
+ import { SallaButton } from '@salla.sa/twilight-components-react/button';
4
+ import { useHostAttributes } from '@salla.sa/twilight-theme-engine/hooks/useHostAttributes';
5
+ import { useMoney } from '../../hooks/useMoney';
6
+ import { useTranslation } from '../../providers/I18nProvider';
7
+ import { useTwilight } from '../../providers/TwilightProvider';
8
+ import { useWishlist } from '../../hooks/useWishlist';
9
+ import { useAsset } from '../../hooks/useAsset';
10
+ import { useNumber } from '../../hooks/useNumber';
11
+ import { registry } from '../ComponentRegistry';
12
+ import { Image } from '../common';
13
+ import type { Product } from '../../types';
14
+
15
+ const PRODUCT_CARD_KEY = 'product:card';
16
+
17
+ const PRODUCT_IMAGE_SRCSET_WIDTHS = [150, 300, 450, 600] as const;
18
+ /** Matches `.s-products-list-vertical-cards`: 2-col below 768px, 4-col above. */
19
+ const PRODUCT_IMAGE_SIZES = '(min-width: 768px) 25vw, 50vw';
20
+ /** fullImage / minimal slots in FeaturedProductsStyle3 (`lg:grid-cols-2`). */
21
+ const PRODUCT_WIDE_IMAGE_SIZES = '(min-width: 1024px) 50vw, 100vw';
22
+
23
+ /** Product card layout variants - mutually exclusive */
24
+ export type ProductCardLayout = 'vertical' | 'horizontal' | 'fullImage' | 'minimal' | 'special';
25
+
26
+ export interface ProductCardProps {
27
+ product: Product;
28
+ layout?: ProductCardLayout;
29
+ className?: string;
30
+ withShadow?: boolean;
31
+ withQuantity?: boolean;
32
+ withoutAddButton?: boolean;
33
+ index?: number;
34
+ imagePriority?: boolean;
35
+ /** Overrides the layout-based image `sizes` hint. */
36
+ sizes?: string;
37
+ }
38
+
39
+ /**
40
+ * ProductCard - Displays a product in various layouts
41
+ *
42
+ * Layouts:
43
+ * - vertical: Default card with image top, content bottom
44
+ * - horizontal: Side-by-side image and content
45
+ * - fullImage: Full bleed image with overlay content
46
+ * - minimal: Compact version with reduced info
47
+ * - special: Featured card with quantity pie chart
48
+ *
49
+ * Features:
50
+ * - Native lazy loading for images
51
+ * - Product badges (promotion, preorder, quantity, out of stock)
52
+ * - Wishlist toggle button
53
+ * - Price display (sale, starting, regular)
54
+ * - Rating display
55
+ * - Add to cart button via SallaAddProductButton (handles labels internally)
56
+ */
57
+ const ProductCardContent = memo(function ProductCardContent({
58
+ product,
59
+ layout = 'vertical',
60
+ className = '',
61
+ withShadow = false,
62
+ withQuantity = false,
63
+ withoutAddButton = false,
64
+ index = 0,
65
+ imagePriority = false,
66
+ sizes,
67
+ }: ProductCardProps) {
68
+ const { format } = useMoney();
69
+ const { t } = useTranslation();
70
+ const { store, theme } = useTwilight();
71
+ const wishlist = useWishlist();
72
+ const { isPlaceholder } = useAsset();
73
+ const { format: formatNumber } = useNumber();
74
+
75
+ // Out-of-stock cards offer "notify me when available" instead of a dead button, unless
76
+ // the merchant switched `notify_when_available_in_card` off (Twig theme-raed parity).
77
+ const notifyInCard = theme?.settings?.notify_when_available_in_card !== false;
78
+ const addButtonStatus =
79
+ notifyInCard &&
80
+ product.is_out_of_stock &&
81
+ product.type !== 'donating' &&
82
+ product.type !== 'financial_support'
83
+ ? 'out-and-notify'
84
+ : product.status;
85
+
86
+ const addButtonRef = useHostAttributes({ fill: 'outline', width: 'wide' });
87
+
88
+ // Layout flags
89
+ const isVertical = layout === 'vertical';
90
+ const isHorizontal = layout === 'horizontal';
91
+ const isFullImage = layout === 'fullImage';
92
+ const isMinimal = layout === 'minimal';
93
+ const isSpecial = layout === 'special';
94
+
95
+ // Wishlist status
96
+ const productInWishlist = wishlist.has(Number(product.id));
97
+ const handleWishlistToggle = useCallback(() => {
98
+ wishlist.toggle(Number(product.id));
99
+ }, [wishlist, product.id]);
100
+
101
+ // Get image fit type from store settings
102
+ const fitImageHeight = store?.settings?.product?.fit_type;
103
+
104
+ // Product badge component
105
+ const getProductBadge = () => {
106
+ if (product?.preorder?.label && typeof product.preorder.label === 'string') {
107
+ return <div className="s-product-card-promotion-title">{product.preorder.label}</div>;
108
+ }
109
+ if (product.promotion_title) {
110
+ return <div className="s-product-card-promotion-title">{product.promotion_title}</div>;
111
+ }
112
+ if (withQuantity && product?.quantity) {
113
+ const formattedQty = formatNumber(product.quantity);
114
+ return (
115
+ <div className="s-product-card-quantity">
116
+ {t('pages.products.remained', 'Remained')} {formattedQty}
117
+ </div>
118
+ );
119
+ }
120
+ if (withQuantity && product?.is_out_of_stock) {
121
+ return (
122
+ <div className="s-product-card-out-badge">
123
+ {t('pages.products.out_of_stock', 'Out of Stock')}
124
+ </div>
125
+ );
126
+ }
127
+ return null;
128
+ };
129
+
130
+ // Price display component
131
+ const getProductPrice = () => {
132
+ if (product.is_on_sale && product.sale_price) {
133
+ return (
134
+ <div className="s-product-card-sale-price">
135
+ <h4>{format(product.sale_price)}</h4>
136
+ {product.regular_price && <span>{format(product.regular_price)}</span>}
137
+ </div>
138
+ );
139
+ }
140
+ if (product.starting_price) {
141
+ return (
142
+ <div className="s-product-card-starting-price">
143
+ <p>{t('pages.products.starting_price', 'Starting from')}</p>
144
+ <h4>{format(product.starting_price)}</h4>
145
+ </div>
146
+ );
147
+ }
148
+ return <h4 className="s-product-card-price">{format(product.price)}</h4>;
149
+ };
150
+
151
+ // Build card class names
152
+ const cardClassName = [
153
+ 's-product-card',
154
+ 's-product-card-entry',
155
+ isVertical ? 's-product-card-vertical' : '',
156
+ isHorizontal ? 's-product-card-horizontal' : '',
157
+ fitImageHeight && isVertical ? `s-product-card-fit-height` : '',
158
+ isSpecial ? 's-product-card-special' : '',
159
+ isFullImage ? 's-product-card-full-image' : '',
160
+ isMinimal ? 's-product-card-minimal' : '',
161
+ product?.donation ? 's-product-card-donation' : '',
162
+ withShadow ? 's-product-card-shadow' : '',
163
+ product?.is_out_of_stock ? 's-product-card-out-of-stock' : '',
164
+ className,
165
+ ]
166
+ .filter(Boolean)
167
+ .join(' ');
168
+
169
+ // Image fit class
170
+ const isProductPlaceholder = isPlaceholder(product.image?.url);
171
+ const imageFitClass = isProductPlaceholder ? 'contain' : fitImageHeight || 'cover';
172
+
173
+ return (
174
+ <div
175
+ id={`product-card-${product.id}`}
176
+ className={cardClassName}
177
+ data-product-id={product.id}
178
+ style={{
179
+ animationDelay: `${index * 100}ms`,
180
+ }}
181
+ >
182
+ <div className={isFullImage ? 's-product-card-image-full' : 's-product-card-image'}>
183
+ <a href={product.url} aria-label={product.image?.alt || product.name}>
184
+ <Image
185
+ className={`s-product-card-image-${imageFitClass}`}
186
+ src={product.image?.url}
187
+ alt={product.image?.alt ?? product.name}
188
+ width={300}
189
+ height={300}
190
+ sizes={
191
+ sizes ?? (isFullImage || isMinimal ? PRODUCT_WIDE_IMAGE_SIZES : PRODUCT_IMAGE_SIZES)
192
+ }
193
+ srcSetWidths={PRODUCT_IMAGE_SRCSET_WIDTHS}
194
+ aspectRatio="1/1"
195
+ priority={imagePriority}
196
+ />
197
+ {!isFullImage && !isMinimal && getProductBadge()}
198
+ </a>
199
+ {isFullImage && (
200
+ <a href={product.url} aria-label={product.name} className="s-product-card-overlay" />
201
+ )}
202
+ {!isHorizontal && !isFullImage && (
203
+ <SallaButton
204
+ shape="icon"
205
+ fill="outline"
206
+ color="light"
207
+ ariaLabel={t('pages.wishlist.toggle', 'Add or remove to wishlist')}
208
+ className={`s-product-card-wishlist-btn animated ${productInWishlist ? 's-product-card-wishlist-added pulse-anime' : 'not-added un-favorited'}`}
209
+ onClick={handleWishlistToggle}
210
+ data-id={product.id}
211
+ >
212
+ <i className="sicon-heart" />
213
+ </SallaButton>
214
+ )}
215
+ </div>
216
+ <div className="s-product-card-content">
217
+ {/* Special card: quantity pie */}
218
+ {isSpecial && product?.quantity && (
219
+ <div className="s-product-card-content-pie">
220
+ <span>
221
+ <b>{formatNumber(product.quantity)}</b>
222
+ {t('pages.products.remained', 'Remained')}
223
+ </span>
224
+ <svg
225
+ xmlns="http://www.w3.org/2000/svg"
226
+ viewBox="-2 -1 36 34"
227
+ className="s-product-card-content-pie-svg"
228
+ >
229
+ <circle cx="16" cy="16" r="15.9155" className="s-product-card-content-pie-svg-base" />
230
+ <circle cx="16" cy="16" r="15.9155" className="s-product-card-content-pie-svg-bar" />
231
+ </svg>
232
+ </div>
233
+ )}
234
+
235
+ <div
236
+ className={`s-product-card-content-main ${isSpecial ? 's-product-card-content-extra-padding' : ''}`}
237
+ >
238
+ <h3 className="s-product-card-content-title">
239
+ <a href={product.url}>{product.name}</a>
240
+ </h3>
241
+ {product.subtitle && !isMinimal && (
242
+ <p className="s-product-card-content-subtitle opacity-80">{product.subtitle}</p>
243
+ )}
244
+ </div>
245
+
246
+ <div
247
+ className={`s-product-card-content-sub ${isSpecial ? 's-product-card-content-extra-padding' : ''}`}
248
+ >
249
+ {!product?.donation?.can_donate && getProductPrice()}
250
+ {product.rating?.stars && (
251
+ <div className="s-product-card-rating">
252
+ <i className="sicon-star2 text-orange-300" />
253
+ <span>{product.rating.stars}</span>
254
+ </div>
255
+ )}
256
+ </div>
257
+
258
+ {!withoutAddButton && (
259
+ <div className="s-product-card-content-footer gap-2">
260
+ <SallaAddProductButton
261
+ ref={addButtonRef}
262
+ fill="outline"
263
+ width="wide"
264
+ productId={product.id}
265
+ productStatus={addButtonStatus}
266
+ productType={product.type}
267
+ hasPreOrder={product.has_preorder_campaign}
268
+ />
269
+ {(isHorizontal || isFullImage) && (
270
+ <SallaButton
271
+ shape="icon"
272
+ fill="outline"
273
+ color="light"
274
+ ariaLabel={t('pages.wishlist.toggle', 'Add or remove to wishlist')}
275
+ className={`s-product-card-wishlist-btn animated ${productInWishlist ? 's-product-card-wishlist-added pulse-anime' : 'not-added un-favorited'}`}
276
+ onClick={handleWishlistToggle}
277
+ data-id={product.id}
278
+ >
279
+ <i className="sicon-heart" />
280
+ </SallaButton>
281
+ )}
282
+ </div>
283
+ )}
284
+ </div>
285
+ </div>
286
+ );
287
+ });
288
+
289
+ export const ProductCard = memo(function ProductCard(props: ProductCardProps) {
290
+ const Component = useMemo(() => {
291
+ const hasOverride = registry.getOriginal<ProductCardProps>(PRODUCT_CARD_KEY) !== null;
292
+ if (hasOverride) {
293
+ return registry.resolve<ProductCardProps>(PRODUCT_CARD_KEY);
294
+ }
295
+ return null;
296
+ }, []);
297
+
298
+ if (Component) {
299
+ return <Component {...props} />;
300
+ }
301
+
302
+ return <ProductCardContent {...props} />;
303
+ });
304
+
305
+ export default ProductCard;