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

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 (390) hide show
  1. package/README.md +4 -3
  2. package/bin/twilight-check-manifest.mjs +16 -0
  3. package/dist/{AddToCartForm-B5C2M4OZ.js → AddToCartForm-P644O2QL.js} +4 -4
  4. package/dist/{AddToCartForm-B5C2M4OZ.js.map → AddToCartForm-P644O2QL.js.map} +1 -1
  5. package/dist/{BlogAuthorPage-GTF4UGSS.js → BlogAuthorPage-VWSO6MPY.js} +13 -11
  6. package/dist/BlogAuthorPage-VWSO6MPY.js.map +1 -0
  7. package/dist/{BlogCategoryPage-TN7Y4NBP.js → BlogCategoryPage-K2KSQ6T6.js} +13 -11
  8. package/dist/BlogCategoryPage-K2KSQ6T6.js.map +1 -0
  9. package/dist/{BlogPage-TDB3M3PI.js → BlogPage-XRM7VWOZ.js} +12 -10
  10. package/dist/BlogPage-XRM7VWOZ.js.map +1 -0
  11. package/dist/{BlogSinglePage-4A52Q5QW.js → BlogSinglePage-XIAXT54H.js} +12 -10
  12. package/dist/BlogSinglePage-XIAXT54H.js.map +1 -0
  13. package/dist/{BlogTagPage-TOVPBETA.js → BlogTagPage-IHOA267V.js} +13 -11
  14. package/dist/BlogTagPage-IHOA267V.js.map +1 -0
  15. package/dist/{BrandsPage-VUDGYSQI.js → BrandsPage-ZFEILRT3.js} +12 -10
  16. package/dist/BrandsPage-ZFEILRT3.js.map +1 -0
  17. package/dist/{Breadcrumb-CI3SN5YR.js → Breadcrumb-CVSISL2K.js} +12 -10
  18. package/dist/Breadcrumb-CVSISL2K.js.map +1 -0
  19. package/dist/{CartItem-BHLEURAB.js → CartItem-EOI7P3JA.js} +12 -10
  20. package/dist/CartItem-EOI7P3JA.js.map +1 -0
  21. package/dist/{CartSummary-QERVESYN.js → CartSummary-RZGUBDKJ.js} +12 -10
  22. package/dist/CartSummary-RZGUBDKJ.js.map +1 -0
  23. package/dist/{Copyright-RL6ALKRC.js → Copyright-5BLYSP24.js} +4 -4
  24. package/dist/{Copyright-RL6ALKRC.js.map → Copyright-5BLYSP24.js.map} +1 -1
  25. package/dist/{CustomerLayout-VRZUWGQK.js → CustomerLayout-6F4BJAMU.js} +12 -10
  26. package/dist/CustomerLayout-6F4BJAMU.js.map +1 -0
  27. package/dist/{Footer-ZWBZCMDL.js → Footer-NF2NLDLR.js} +6 -6
  28. package/dist/{Footer-ZWBZCMDL.js.map → Footer-NF2NLDLR.js.map} +1 -1
  29. package/dist/{Header-WNK4O66B.js → Header-6DQJ5X7J.js} +9 -7
  30. package/dist/{Header-WNK4O66B.js.map → Header-6DQJ5X7J.js.map} +1 -1
  31. package/dist/{LoyaltyPage-CGTHWJ76.js → LoyaltyPage-YHWHIWCS.js} +12 -10
  32. package/dist/LoyaltyPage-YHWHIWCS.js.map +1 -0
  33. package/dist/{LoyaltyPointsPage-QSU6K3PW.js → LoyaltyPointsPage-T6K3H5A5.js} +10 -3
  34. package/dist/LoyaltyPointsPage-T6K3H5A5.js.map +1 -0
  35. package/dist/{MainMenu-V3T7F32R.js → MainMenu-Y2TAM7DK.js} +4 -4
  36. package/dist/{MainMenu-V3T7F32R.js.map → MainMenu-Y2TAM7DK.js.map} +1 -1
  37. package/dist/{MasterLayout-XQF644LD.js → MasterLayout-GBEEFFPG.js} +12 -10
  38. package/dist/{MasterLayout-XQF644LD.js.map → MasterLayout-GBEEFFPG.js.map} +1 -1
  39. package/dist/{MobileMenu-U4BVQYKH.js → MobileMenu-RE5XVGZL.js} +12 -10
  40. package/dist/MobileMenu-RE5XVGZL.js.map +1 -0
  41. package/dist/{NotificationsPage-ZRRQY7TF.js → NotificationsPage-QAFWZAS3.js} +12 -10
  42. package/dist/NotificationsPage-QAFWZAS3.js.map +1 -0
  43. package/dist/{OrderSinglePage-O3XYVMG5.js → OrderSinglePage-T4PRXTKG.js} +12 -10
  44. package/dist/OrderSinglePage-T4PRXTKG.js.map +1 -0
  45. package/dist/{OrdersPage-CMXZF5MI.js → OrdersPage-3S6ZS56E.js} +12 -10
  46. package/dist/OrdersPage-3S6ZS56E.js.map +1 -0
  47. package/dist/{PageSingle-IIG2US43.js → PageSingle-W3UTXDOW.js} +4 -4
  48. package/dist/{PageSingle-IIG2US43.js.map → PageSingle-W3UTXDOW.js.map} +1 -1
  49. package/dist/{ProductDetails-65GDABI7.js → ProductDetails-X5A7TG3F.js} +14 -7
  50. package/dist/ProductDetails-X5A7TG3F.js.map +1 -0
  51. package/dist/{ProfilePage-SNJ4WXU4.js → ProfilePage-DUH3AILJ.js} +12 -10
  52. package/dist/ProfilePage-DUH3AILJ.js.map +1 -0
  53. package/dist/{ReviewsPage-UT7UT55J.js → ReviewsPage-2C2P7VS3.js} +4 -4
  54. package/dist/{ReviewsPage-UT7UT55J.js.map → ReviewsPage-2C2P7VS3.js.map} +1 -1
  55. package/dist/{SeoCartWidget-DBJAXJKT.js → SeoCartWidget-VYLGEW2I.js} +12 -10
  56. package/dist/SeoCartWidget-VYLGEW2I.js.map +1 -0
  57. package/dist/{SettingsPage-FLTIOPU5.js → SettingsPage-OZJEW6CW.js} +12 -10
  58. package/dist/SettingsPage-OZJEW6CW.js.map +1 -0
  59. package/dist/{StoreClosedNotification-ZT3QKDKS.js → StoreClosedNotification-QHIZBNXO.js} +13 -11
  60. package/dist/{StoreClosedNotification-ZT3QKDKS.js.map → StoreClosedNotification-QHIZBNXO.js.map} +1 -1
  61. package/dist/{TestimonialsPage-CU7HKU4O.js → TestimonialsPage-K6WJKKME.js} +4 -4
  62. package/dist/{TestimonialsPage-CU7HKU4O.js.map → TestimonialsPage-K6WJKKME.js.map} +1 -1
  63. package/dist/{ThankYouPage-5X6B4X3T.js → ThankYouPage-H6QQEI5R.js} +12 -10
  64. package/dist/ThankYouPage-H6QQEI5R.js.map +1 -0
  65. package/dist/{WalletPage-H2BKIDPA.js → WalletPage-G5IBGN6B.js} +12 -10
  66. package/dist/WalletPage-G5IBGN6B.js.map +1 -0
  67. package/dist/{WishlistPage-RS5UCIQ3.js → WishlistPage-AIOTAB6L.js} +12 -10
  68. package/dist/WishlistPage-AIOTAB6L.js.map +1 -0
  69. package/dist/api/advertisement.d.ts +33 -0
  70. package/dist/api/advertisement.d.ts.map +1 -0
  71. package/dist/api/advertisement.js +8 -0
  72. package/dist/api/advertisement.js.map +1 -0
  73. package/dist/boot/critical-style.d.ts +3 -0
  74. package/dist/boot/critical-style.d.ts.map +1 -0
  75. package/dist/boot/index.d.ts +15 -0
  76. package/dist/boot/index.d.ts.map +1 -0
  77. package/dist/boot.d.ts +1 -0
  78. package/dist/boot.js +69 -0
  79. package/dist/boot.js.map +1 -0
  80. package/dist/chunk-4FJGZQD3.js +6 -0
  81. package/dist/chunk-4FJGZQD3.js.map +1 -0
  82. package/dist/{chunk-SDFDS4Q3.js → chunk-BCJTF3V7.js} +20 -4
  83. package/dist/chunk-BCJTF3V7.js.map +1 -0
  84. package/dist/chunk-DBW7XXNE.js +103 -0
  85. package/dist/chunk-DBW7XXNE.js.map +1 -0
  86. package/dist/{chunk-22SH7OVF.js → chunk-E7WWO3V3.js} +3 -3
  87. package/dist/{chunk-22SH7OVF.js.map → chunk-E7WWO3V3.js.map} +1 -1
  88. package/dist/{chunk-SE5JYWOK.js → chunk-EZHXVHXY.js} +4 -4
  89. package/dist/{chunk-SE5JYWOK.js.map → chunk-EZHXVHXY.js.map} +1 -1
  90. package/dist/{chunk-JO7JWV34.js → chunk-FWYKOQV5.js} +5 -5
  91. package/dist/chunk-FWYKOQV5.js.map +1 -0
  92. package/dist/{chunk-JRNH3FUF.js → chunk-FYHF62KI.js} +4 -4
  93. package/dist/{chunk-JRNH3FUF.js.map → chunk-FYHF62KI.js.map} +1 -1
  94. package/dist/{chunk-UJKFBZFJ.js → chunk-GJ5IMPNY.js} +7 -7
  95. package/dist/{chunk-UJKFBZFJ.js.map → chunk-GJ5IMPNY.js.map} +1 -1
  96. package/dist/chunk-IVBOLREE.js +29 -0
  97. package/dist/chunk-IVBOLREE.js.map +1 -0
  98. package/dist/chunk-LWPKBVIE.js +14 -0
  99. package/dist/{chunk-7WAXN2PP.js.map → chunk-LWPKBVIE.js.map} +1 -1
  100. package/dist/{chunk-SX5MF2YA.js → chunk-N3BCZIGS.js} +4 -4
  101. package/dist/{chunk-SX5MF2YA.js.map → chunk-N3BCZIGS.js.map} +1 -1
  102. package/dist/{chunk-NZVR4OF7.js → chunk-NXQZ4RLM.js} +8 -8
  103. package/dist/chunk-NXQZ4RLM.js.map +1 -0
  104. package/dist/{chunk-NXQJA7D3.js → chunk-PCURRKIH.js} +3 -3
  105. package/dist/{chunk-NXQJA7D3.js.map → chunk-PCURRKIH.js.map} +1 -1
  106. package/dist/{chunk-4CZRUAZB.js → chunk-PU2LHFDQ.js} +10 -11
  107. package/dist/chunk-PU2LHFDQ.js.map +1 -0
  108. package/dist/{chunk-OWX2CPNQ.js → chunk-UQIVX4YA.js} +3 -3
  109. package/dist/{chunk-OWX2CPNQ.js.map → chunk-UQIVX4YA.js.map} +1 -1
  110. package/dist/chunk-X5NK4TZH.js +228 -0
  111. package/dist/chunk-X5NK4TZH.js.map +1 -0
  112. package/dist/{chunk-6SWDSRIL.js → chunk-XKNXYQ2K.js} +6 -6
  113. package/dist/{chunk-6SWDSRIL.js.map → chunk-XKNXYQ2K.js.map} +1 -1
  114. package/dist/{chunk-KHBSGD2D.js → chunk-XO7B3VWS.js} +3 -3
  115. package/dist/{chunk-KHBSGD2D.js.map → chunk-XO7B3VWS.js.map} +1 -1
  116. package/dist/cli/check-manifest.d.ts +10 -0
  117. package/dist/cli/check-manifest.d.ts.map +1 -0
  118. package/dist/cli/check-manifest.js +44 -0
  119. package/dist/cli/check-manifest.js.map +1 -0
  120. package/dist/components/cart/index.js +1 -1
  121. package/dist/components/common/index.js +2 -2
  122. package/dist/components/drawer/index.js +10 -8
  123. package/dist/components/home/index.js +12 -10
  124. package/dist/components/layout/Footer.d.ts.map +1 -1
  125. package/dist/components/layout/MasterLayout.d.ts.map +1 -1
  126. package/dist/components/layout/PageAdvertisements.d.ts +7 -0
  127. package/dist/components/layout/PageAdvertisements.d.ts.map +1 -0
  128. package/dist/components/layout/index.d.ts +1 -0
  129. package/dist/components/layout/index.d.ts.map +1 -1
  130. package/dist/components/layout/index.js +8 -1
  131. package/dist/components/product/index.js +11 -9
  132. package/dist/components/store/index.js +12 -10
  133. package/dist/components/store/index.js.map +1 -1
  134. package/dist/{document-class-67HTP45G.js → document-class-FRL7JHWD.js} +6 -5
  135. package/dist/{document-class-67HTP45G.js.map → document-class-FRL7JHWD.js.map} +1 -1
  136. package/dist/hooks/index.js +10 -8
  137. package/dist/hooks/useGtm.js +10 -8
  138. package/dist/hooks/useMoney.js +10 -8
  139. package/dist/hooks/useStore.js +10 -8
  140. package/dist/index.js +10 -8
  141. package/dist/index.js.map +1 -1
  142. package/dist/{not-found-QTHBFPNT.js → not-found-E6AR4CZ4.js} +12 -10
  143. package/dist/not-found-E6AR4CZ4.js.map +1 -0
  144. package/dist/providers/index.js +10 -8
  145. package/dist/routes/account/orders.js +1 -1
  146. package/dist/routes/account.js +6 -6
  147. package/dist/routes/blog.js +5 -5
  148. package/dist/routes/brands.js +1 -1
  149. package/dist/routes/cart.js +14 -12
  150. package/dist/routes/cart.js.map +1 -1
  151. package/dist/routes/home.js +13 -11
  152. package/dist/routes/loyalty.js +2 -2
  153. package/dist/routes/page.js +1 -1
  154. package/dist/routes/product-listing.js +12 -10
  155. package/dist/routes/product-listing.js.map +1 -1
  156. package/dist/routes/product.js +12 -10
  157. package/dist/routes/product.js.map +1 -1
  158. package/dist/routes/root/beforeLoad.d.ts +2 -2
  159. package/dist/routes/root/beforeLoad.d.ts.map +1 -1
  160. package/dist/routes/root/head.d.ts.map +1 -1
  161. package/dist/routes/testimonials.js +2 -2
  162. package/dist/routes/thank-you.js +1 -1
  163. package/dist/skeleton.js +13 -11
  164. package/dist/tanstack/document-class.d.ts.map +1 -1
  165. package/dist/tanstack/index.d.ts +1 -0
  166. package/dist/tanstack/index.d.ts.map +1 -1
  167. package/dist/tanstack/stylesheet.d.ts +15 -0
  168. package/dist/tanstack/stylesheet.d.ts.map +1 -0
  169. package/dist/tanstack.js +33 -4
  170. package/dist/tanstack.js.map +1 -1
  171. package/dist/utils/index.js +2 -2
  172. package/dist/vite/index.d.ts +1 -1
  173. package/dist/vite/index.d.ts.map +1 -1
  174. package/dist/vite/index.js +14 -25
  175. package/dist/vite/index.js.map +1 -1
  176. package/dist/vite/plugins/twilight-manifest.d.ts +23 -0
  177. package/dist/vite/plugins/twilight-manifest.d.ts.map +1 -1
  178. package/dist/vite/plugins/twilight-schema.plugin.d.ts.map +1 -1
  179. package/package.json +16 -3
  180. package/tailwind/components/Component.tsx.txt +21 -0
  181. package/tailwind/components/ComponentRegistry.ts.txt +66 -0
  182. package/tailwind/components/cart/CartItem.tsx.txt +211 -0
  183. package/tailwind/components/cart/CartSummary.tsx.txt +269 -0
  184. package/tailwind/components/cart/PreOrderNotification.tsx.txt +16 -0
  185. package/tailwind/components/cart/PriceQuoteButton.tsx.txt +21 -0
  186. package/tailwind/components/cart/SeoCartWidget.tsx.txt +99 -0
  187. package/tailwind/components/cart/index.ts.txt +11 -0
  188. package/tailwind/components/cart/lazy.ts.txt +15 -0
  189. package/tailwind/components/collapse/Collapse.tsx.txt +95 -0
  190. package/tailwind/components/collapse/index.ts.txt +41 -0
  191. package/tailwind/components/common/Breadcrumb.tsx.txt +27 -0
  192. package/tailwind/components/common/BreadcrumbJsonLd.tsx.txt +40 -0
  193. package/tailwind/components/common/BreadcrumbTrail.tsx.txt +97 -0
  194. package/tailwind/components/common/CurrencySymbol.tsx.txt +8 -0
  195. package/tailwind/components/common/DeferredData.tsx.txt +122 -0
  196. package/tailwind/components/common/ErrorPage.tsx.txt +69 -0
  197. package/tailwind/components/common/Image.tsx.txt +119 -0
  198. package/tailwind/components/common/Link.tsx.txt +36 -0
  199. package/tailwind/components/common/Link.types.ts.txt +85 -0
  200. package/tailwind/components/common/NoContent.tsx.txt +27 -0
  201. package/tailwind/components/common/RenderWhenVisible.tsx.txt +92 -0
  202. package/tailwind/components/common/index.ts.txt +42 -0
  203. package/tailwind/components/common/lazy.ts.txt +28 -0
  204. package/tailwind/components/defineComponent.ts.txt +32 -0
  205. package/tailwind/components/drawer/Drawer.tsx.txt +219 -0
  206. package/tailwind/components/drawer/index.ts.txt +38 -0
  207. package/tailwind/components/dropdown/Dropdown.tsx.txt +166 -0
  208. package/tailwind/components/dropdown/index.ts.txt +40 -0
  209. package/tailwind/components/home/BundleComponent.tsx.txt +35 -0
  210. package/tailwind/components/home/ComponentErrorBoundary.tsx.txt +98 -0
  211. package/tailwind/components/home/FeaturedProductsStyle1.tsx.txt +125 -0
  212. package/tailwind/components/home/FeaturedProductsStyle2.tsx.txt +127 -0
  213. package/tailwind/components/home/FeaturedProductsStyle3.tsx.txt +71 -0
  214. package/tailwind/components/home/FixedBanner.tsx.txt +68 -0
  215. package/tailwind/components/home/FixedProducts.tsx.txt +72 -0
  216. package/tailwind/components/home/HomePageRenderer.tsx.txt +284 -0
  217. package/tailwind/components/home/PageBlocks.tsx.txt +24 -0
  218. package/tailwind/components/home/ParallaxBackground.tsx.txt +50 -0
  219. package/tailwind/components/home/PhotosSlider.tsx.txt +61 -0
  220. package/tailwind/components/home/ProductsSlider.tsx.txt +87 -0
  221. package/tailwind/components/home/ReorderProducts.tsx.txt +38 -0
  222. package/tailwind/components/home/SquarePhotos.tsx.txt +68 -0
  223. package/tailwind/components/home/StoreFeatures.tsx.txt +34 -0
  224. package/tailwind/components/home/Testimonials.tsx.txt +36 -0
  225. package/tailwind/components/home/Youtube.tsx.txt +48 -0
  226. package/tailwind/components/home/index.ts.txt +50 -0
  227. package/tailwind/components/home/lazy.ts.txt +17 -0
  228. package/tailwind/components/home/register.ts.txt +44 -0
  229. package/tailwind/components/home/reorder-hooks.ts.txt +52 -0
  230. package/tailwind/components/home/types.ts.txt +4 -0
  231. package/tailwind/components/layout/Copyright.tsx.txt +23 -0
  232. package/tailwind/components/layout/CustomerLayout.tsx.txt +91 -0
  233. package/tailwind/components/layout/Footer.tsx.txt +341 -0
  234. package/tailwind/components/layout/Header.tsx.txt +154 -0
  235. package/tailwind/components/layout/MasterLayout.tsx.txt +62 -0
  236. package/tailwind/components/layout/PageAdvertisements.tsx.txt +131 -0
  237. package/tailwind/components/layout/WidgetHead.tsx.txt +53 -0
  238. package/tailwind/components/layout/index.ts.txt +27 -0
  239. package/tailwind/components/layout/lazy.ts.txt +25 -0
  240. package/tailwind/components/layout/types.ts.txt +11 -0
  241. package/tailwind/components/modal/ImageModal.tsx.txt +70 -0
  242. package/tailwind/components/modal/Modal.tsx.txt +245 -0
  243. package/tailwind/components/modal/index.ts.txt +39 -0
  244. package/tailwind/components/modal/lazy.ts.txt +10 -0
  245. package/tailwind/components/navigation/MainMenu.tsx.txt +94 -0
  246. package/tailwind/components/navigation/MobileMenu.tsx.txt +160 -0
  247. package/tailwind/components/navigation/index.ts.txt +4 -0
  248. package/tailwind/components/navigation/lazy.ts.txt +11 -0
  249. package/tailwind/components/product/AddToCartForm.tsx.txt +365 -0
  250. package/tailwind/components/product/ProductCard.tsx.txt +305 -0
  251. package/tailwind/components/product/ProductDetails.tsx.txt +267 -0
  252. package/tailwind/components/product/ProductGallery.tsx.txt +234 -0
  253. package/tailwind/components/product/index.ts.txt +15 -0
  254. package/tailwind/components/product/lazy.ts.txt +9 -0
  255. package/tailwind/components/snippets/AppsSnippets.tsx.txt +225 -0
  256. package/tailwind/components/snippets/SallaSdkScripts.tsx.txt +14 -0
  257. package/tailwind/components/snippets/index.ts.txt +4 -0
  258. package/tailwind/components/store/StoreClosedNotification.tsx.txt +73 -0
  259. package/tailwind/components/store/index.ts.txt +8 -0
  260. package/tailwind/components/store/lazy.ts.txt +8 -0
  261. package/tailwind/components/toast/Toaster.tsx.txt +177 -0
  262. package/tailwind/components/toast/index.ts.txt +10 -0
  263. package/tailwind/components/toast/types.ts.txt +38 -0
  264. package/tailwind/components/toast/useToast.ts.txt +61 -0
  265. package/tailwind/components/tracking/GoogleTag.tsx.txt +77 -0
  266. package/tailwind/components/tracking/SiftSnippet.tsx.txt +49 -0
  267. package/tailwind/components/tracking/TwilightBundles.tsx.txt +67 -0
  268. package/tailwind/components/tracking/index.ts.txt +11 -0
  269. package/tailwind/components/tracking/lazy.ts.txt +11 -0
  270. package/tailwind/components/useComponent.ts.txt +12 -0
  271. package/tailwind/routes/account/index.tsx.txt +16 -0
  272. package/tailwind/routes/account/notifications/NotificationsPage.tsx.txt +54 -0
  273. package/tailwind/routes/account/notifications/index.tsx.txt +39 -0
  274. package/tailwind/routes/account/notifications/loader.ts.txt +22 -0
  275. package/tailwind/routes/account/notifications/types.ts.txt +18 -0
  276. package/tailwind/routes/account/orders/OrderSinglePage.tsx.txt +181 -0
  277. package/tailwind/routes/account/orders/OrdersPage.tsx.txt +28 -0
  278. package/tailwind/routes/account/orders/index.tsx.txt +86 -0
  279. package/tailwind/routes/account/orders/loader.ts.txt +66 -0
  280. package/tailwind/routes/account/orders/single/OrderActions.tsx.txt +197 -0
  281. package/tailwind/routes/account/orders/single/OrderHeader.tsx.txt +143 -0
  282. package/tailwind/routes/account/orders/single/OrderItemRow.tsx.txt +209 -0
  283. package/tailwind/routes/account/orders/single/OrderSections.tsx.txt +215 -0
  284. package/tailwind/routes/account/orders/single/types.ts.txt +38 -0
  285. package/tailwind/routes/account/orders/types.ts.txt +29 -0
  286. package/tailwind/routes/account/profile/ProfilePage.tsx.txt +30 -0
  287. package/tailwind/routes/account/profile/index.tsx.txt +39 -0
  288. package/tailwind/routes/account/profile/loader.ts.txt +16 -0
  289. package/tailwind/routes/account/profile/types.ts.txt +5 -0
  290. package/tailwind/routes/account/settings/SettingsPage.tsx.txt +13 -0
  291. package/tailwind/routes/account/settings/index.tsx.txt +39 -0
  292. package/tailwind/routes/account/settings/loader.ts.txt +13 -0
  293. package/tailwind/routes/account/settings/types.ts.txt +5 -0
  294. package/tailwind/routes/account/types.ts.txt +320 -0
  295. package/tailwind/routes/account/wallet/WalletPage.tsx.txt +195 -0
  296. package/tailwind/routes/account/wallet/index.tsx.txt +38 -0
  297. package/tailwind/routes/account/wallet/loader.ts.txt +27 -0
  298. package/tailwind/routes/account/wallet/types.ts.txt +11 -0
  299. package/tailwind/routes/account/wishlist/WishlistPage.tsx.txt +314 -0
  300. package/tailwind/routes/account/wishlist/WishlistShare.tsx.txt +248 -0
  301. package/tailwind/routes/account/wishlist/index.tsx.txt +40 -0
  302. package/tailwind/routes/account/wishlist/loader.ts.txt +26 -0
  303. package/tailwind/routes/account/wishlist/types.ts.txt +9 -0
  304. package/tailwind/routes/blog/BlogAuthorPage.tsx.txt +82 -0
  305. package/tailwind/routes/blog/BlogCategoryPage.tsx.txt +84 -0
  306. package/tailwind/routes/blog/BlogPage.tsx.txt +191 -0
  307. package/tailwind/routes/blog/BlogSinglePage.tsx.txt +168 -0
  308. package/tailwind/routes/blog/BlogTagPage.tsx.txt +84 -0
  309. package/tailwind/routes/blog/BlogTags.tsx.txt +54 -0
  310. package/tailwind/routes/blog/components.tsx.txt +87 -0
  311. package/tailwind/routes/blog/index.tsx.txt +238 -0
  312. package/tailwind/routes/blog/loader.ts.txt +173 -0
  313. package/tailwind/routes/blog/types.ts.txt +106 -0
  314. package/tailwind/routes/brands/BrandsPage.tsx.txt +179 -0
  315. package/tailwind/routes/brands/index.tsx.txt +47 -0
  316. package/tailwind/routes/brands/loader.ts.txt +14 -0
  317. package/tailwind/routes/brands/types.ts.txt +53 -0
  318. package/tailwind/routes/cart/CartPage.tsx.txt +112 -0
  319. package/tailwind/routes/cart/index.tsx.txt +39 -0
  320. package/tailwind/routes/cart/loader.ts.txt +15 -0
  321. package/tailwind/routes/cart/types.ts.txt +6 -0
  322. package/tailwind/routes/home/HomePage.tsx.txt +53 -0
  323. package/tailwind/routes/home/index.tsx.txt +56 -0
  324. package/tailwind/routes/home/loader.ts.txt +24 -0
  325. package/tailwind/routes/home/types.ts.txt +11 -0
  326. package/tailwind/routes/index.ts.txt +94 -0
  327. package/tailwind/routes/lazy.ts.txt +105 -0
  328. package/tailwind/routes/loyalty/LoyaltyPage.tsx.txt +17 -0
  329. package/tailwind/routes/loyalty/LoyaltyPointsPage.tsx.txt +11 -0
  330. package/tailwind/routes/loyalty/index.tsx.txt +81 -0
  331. package/tailwind/routes/loyalty/loader.ts.txt +53 -0
  332. package/tailwind/routes/loyalty/types.ts.txt +109 -0
  333. package/tailwind/routes/not-found.tsx.txt +50 -0
  334. package/tailwind/routes/page/PageSingle.tsx.txt +34 -0
  335. package/tailwind/routes/page/index.tsx.txt +60 -0
  336. package/tailwind/routes/page/loader.ts.txt +19 -0
  337. package/tailwind/routes/page/types.ts.txt +34 -0
  338. package/tailwind/routes/product/ProductPage.tsx.txt +77 -0
  339. package/tailwind/routes/product/index.tsx.txt +157 -0
  340. package/tailwind/routes/product/loader.ts.txt +35 -0
  341. package/tailwind/routes/product/types.ts.txt +6 -0
  342. package/tailwind/routes/product-listing/ProductListPage.tsx.txt +321 -0
  343. package/tailwind/routes/product-listing/index.tsx.txt +95 -0
  344. package/tailwind/routes/product-listing/loader.ts.txt +323 -0
  345. package/tailwind/routes/product-listing/types.ts.txt +96 -0
  346. package/tailwind/routes/product-listing/useAppliedFilters.ts.txt +130 -0
  347. package/tailwind/routes/product-short-link.ts.txt +104 -0
  348. package/tailwind/routes/root/beforeLoad.ts.txt +354 -0
  349. package/tailwind/routes/root/head.ts.txt +424 -0
  350. package/tailwind/routes/root/index.ts.txt +2 -0
  351. package/tailwind/routes/seo-redirects.ts.txt +82 -0
  352. package/tailwind/routes/testimonials/ReviewsPage.tsx.txt +12 -0
  353. package/tailwind/routes/testimonials/TestimonialsPage.tsx.txt +80 -0
  354. package/tailwind/routes/testimonials/index.tsx.txt +80 -0
  355. package/tailwind/routes/testimonials/loader.ts.txt +29 -0
  356. package/tailwind/routes/testimonials/types.ts.txt +9 -0
  357. package/tailwind/routes/thank-you/ThankYouPage.tsx.txt +241 -0
  358. package/tailwind/routes/thank-you/index.tsx.txt +49 -0
  359. package/tailwind/routes/thank-you/loader.ts.txt +40 -0
  360. package/tailwind/routes/thank-you/types.ts.txt +16 -0
  361. package/tailwind/routes/types.ts.txt +41 -0
  362. package/dist/BlogAuthorPage-GTF4UGSS.js.map +0 -1
  363. package/dist/BlogCategoryPage-TN7Y4NBP.js.map +0 -1
  364. package/dist/BlogPage-TDB3M3PI.js.map +0 -1
  365. package/dist/BlogSinglePage-4A52Q5QW.js.map +0 -1
  366. package/dist/BlogTagPage-TOVPBETA.js.map +0 -1
  367. package/dist/BrandsPage-VUDGYSQI.js.map +0 -1
  368. package/dist/Breadcrumb-CI3SN5YR.js.map +0 -1
  369. package/dist/CartItem-BHLEURAB.js.map +0 -1
  370. package/dist/CartSummary-QERVESYN.js.map +0 -1
  371. package/dist/CustomerLayout-VRZUWGQK.js.map +0 -1
  372. package/dist/LoyaltyPage-CGTHWJ76.js.map +0 -1
  373. package/dist/LoyaltyPointsPage-QSU6K3PW.js.map +0 -1
  374. package/dist/MobileMenu-U4BVQYKH.js.map +0 -1
  375. package/dist/NotificationsPage-ZRRQY7TF.js.map +0 -1
  376. package/dist/OrderSinglePage-O3XYVMG5.js.map +0 -1
  377. package/dist/OrdersPage-CMXZF5MI.js.map +0 -1
  378. package/dist/ProductDetails-65GDABI7.js.map +0 -1
  379. package/dist/ProfilePage-SNJ4WXU4.js.map +0 -1
  380. package/dist/SeoCartWidget-DBJAXJKT.js.map +0 -1
  381. package/dist/SettingsPage-FLTIOPU5.js.map +0 -1
  382. package/dist/ThankYouPage-5X6B4X3T.js.map +0 -1
  383. package/dist/WalletPage-H2BKIDPA.js.map +0 -1
  384. package/dist/WishlistPage-RS5UCIQ3.js.map +0 -1
  385. package/dist/chunk-4CZRUAZB.js.map +0 -1
  386. package/dist/chunk-7WAXN2PP.js +0 -14
  387. package/dist/chunk-JO7JWV34.js.map +0 -1
  388. package/dist/chunk-NZVR4OF7.js.map +0 -1
  389. package/dist/chunk-SDFDS4Q3.js.map +0 -1
  390. package/dist/not-found-QTHBFPNT.js.map +0 -1
@@ -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;
@@ -0,0 +1,267 @@
1
+ import { Fragment, useState } from 'react';
2
+ import { HookSlot } from '../../hooks/HookSlot';
3
+ import { HookName } from '../../types/hooks';
4
+ import { useTranslation } from '../../providers/I18nProvider';
5
+ import { getActiveCurrency } from '../../api/client';
6
+ import { useWishlist } from '../../hooks/useWishlist';
7
+ import { CurrencySymbol, Link, Image } from '../common';
8
+ import { SallaRatingStars } from '@salla.sa/twilight-components-react/rating-stars';
9
+ import { SallaSocialShare } from '@salla.sa/twilight-components-react/social-share';
10
+ import { SallaButton } from '@salla.sa/twilight-components-react/button';
11
+ import { SallaInstallment } from '@salla.sa/twilight-components-react/installment';
12
+ import { SallaMetadata } from '@salla.sa/twilight-components-react/metadata';
13
+ import { formatSallaPlural } from '../../utils/salla-plural';
14
+ import type { Product } from '../../types';
15
+
16
+ interface ProductDetailsProps {
17
+ product: Product;
18
+ showTags?: boolean;
19
+ }
20
+
21
+ export function ProductDetails({ product, showTags = true }: ProductDetailsProps) {
22
+ const { t } = useTranslation();
23
+ const wishlist = useWishlist();
24
+
25
+ // Context for all product hooks - contains product data
26
+ const productHookContext = { product };
27
+
28
+ return (
29
+ <>
30
+ <HookSlot name={HookName.PRODUCT_DESCRIPTION_START} context={productHookContext} />
31
+
32
+ {product.brand?.name && (
33
+ <div className="product-brand mb-5 w-12">
34
+ <Link className="brand-logo" to={product.brand.url || '#'} title={product.brand.name}>
35
+ <Image
36
+ className="max-h-full object-contain"
37
+ src={product.brand.logo}
38
+ title={product.brand.name}
39
+ alt={product.brand.name}
40
+ aspectRatio="1/1"
41
+ />
42
+ </Link>
43
+ </div>
44
+ )}
45
+
46
+ <h1 className="text-xl md:text-2xl leading-10 font-bold mb-6 text-gray-800">
47
+ {product.name}
48
+ </h1>
49
+
50
+ {product.subtitle && (
51
+ <h2 className="product-entry__sub-title text-sm text-gray-500 leading-6 mb-2.5">
52
+ {product.subtitle}
53
+ </h2>
54
+ )}
55
+
56
+ {product.rating && (
57
+ <SallaRatingStars value={product.rating.stars} reviews={product.rating.count} />
58
+ )}
59
+
60
+ {product.is_taxable && (
61
+ <small className="color-grey">{t('pages.products.tax_included', 'VAT Inclusive')}</small>
62
+ )}
63
+
64
+ <ProductPrice product={product} />
65
+
66
+ <ProductDescription product={product} />
67
+
68
+ {!!product.tags?.length && showTags && (
69
+ <div className="mb-3">
70
+ {product.tags.map((tag, index) => (
71
+ <Fragment key={tag.name}>
72
+ <Link
73
+ to={tag.url}
74
+ className="rtl:ml-2 ltr:mr-2 inline-flex text-gray-500 hover:text-primary underline text-sm mb-1"
75
+ >
76
+ {tag.name}
77
+ </Link>
78
+ {index < product.tags!.length - 1 && ','}
79
+ </Fragment>
80
+ ))}
81
+ </div>
82
+ )}
83
+
84
+ <div className="flex rtl:space-x-reverse space-x-3">
85
+ <SallaSocialShare className="inline-flex mb-5" aria-label="social share" />
86
+
87
+ <SallaButton
88
+ className={`btn--wishlist animated hidden sm:inline-flex ${wishlist.has(Number(product.id)) ? 'is-added' : 'not-added'}`}
89
+ data-id={product.id}
90
+ onClick={() => window.salla?.wishlist.toggle(product.id)}
91
+ shape="icon"
92
+ fill="outline"
93
+ color="light"
94
+ ariaLabel="add to wishlist"
95
+ >
96
+ <i className="sicon-heart"></i>
97
+ </SallaButton>
98
+ </div>
99
+
100
+ <ProductQuantityInfo product={product} />
101
+
102
+ {product.sku && (
103
+ <div className="mb-4 flex justify-between sm:grid sm:grid-cols-3 bg-white rounded-md px-4 py-2">
104
+ <div className="flex items-center">
105
+ <i className="sicon-barcode mx-1"></i>
106
+ {t('pages.products.sku', 'SKU')}
107
+ </div>
108
+ <div className="text-unicode">{product.sku}</div>
109
+ </div>
110
+ )}
111
+
112
+ {product.barcode && (
113
+ <div className="mb-4 flex justify-between sm:grid sm:grid-cols-3 bg-white rounded-md px-4 py-2">
114
+ <div className="flex items-center">
115
+ <i className="sicon-barcode mx-1"></i>
116
+ {t('pages.products.barcode')}
117
+ </div>
118
+ <div className="text-unicode">{product.barcode}</div>
119
+ </div>
120
+ )}
121
+
122
+ {product.show_availability && (
123
+ <section className="bg-white p-5 rounded-md mb-5 last:mb-0">
124
+ <div className="center-between">
125
+ <label className="flex items-center text-base">
126
+ <span className="sicon-location rtl:ml-1 ltr:mr-1"></span>
127
+ <span className="inline-block">
128
+ {t('pages.products.availability', 'Availability')}
129
+ </span>
130
+ </label>
131
+ <div className="mt-1 sm:mt-0 sm:col-span-2 text-end">
132
+ <div className="flex rtl:space-x-reverse space-x-3">
133
+ <a
134
+ href="#!"
135
+ onClick={() =>
136
+ window.salla?.event?.dispatch('scopes::open', {
137
+ mode: 'availability',
138
+ product_id: String(product.id),
139
+ })
140
+ }
141
+ className="group text-primary flex items-center justify-center"
142
+ >
143
+ <span>{t('pages.products.select_branch', 'Select Branch')}</span>
144
+ <span className="sicon-keyboard_arrow_left mr-2 transition-all group-hover:-translate-x-1"></span>
145
+ </a>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </section>
150
+ )}
151
+
152
+ <HookSlot name={HookName.PRODUCT_DESCRIPTION} context={productHookContext} />
153
+
154
+ <SallaInstallment
155
+ price={String(
156
+ typeof product.base_currency_price === 'object'
157
+ ? product.base_currency_price.amount
158
+ : product.base_currency_price
159
+ )}
160
+ />
161
+
162
+ {product.has_metadata && <SallaMetadata />}
163
+ </>
164
+ );
165
+ }
166
+
167
+ function ProductPrice({ product }: { product: Product }) {
168
+ const { t } = useTranslation();
169
+ // The API converts the amounts but still labels them in the store's own currency.
170
+ const priceCurrency = getActiveCurrency(product.currency);
171
+
172
+ return (
173
+ <div className="flex whitespace-nowrap gap-4 items-center">
174
+ <div
175
+ className={`${product.is_on_sale ? '' : 'hidden'} space-x-2 rtl:space-x-reverse whitespace-nowrap`}
176
+ >
177
+ <p className="text-red-800 font-bold text-xl inline-block">
178
+ {product.sale_price} <CurrencySymbol currency={priceCurrency} />
179
+ </p>
180
+ <span className="text-gray-500 line-through">
181
+ {product.regular_price} <CurrencySymbol currency={priceCurrency} />
182
+ </span>
183
+ </div>
184
+ <div className={`gap-4 ${product.is_on_sale ? 'hidden' : 'flex'}`}>
185
+ {product.starting_price && (
186
+ <span>{t('pages.products.starting_price', 'Starting from')}</span>
187
+ )}
188
+ <p className="font-bold text-xl inline-block">
189
+ {product.starting_price ?? product.price} <CurrencySymbol currency={priceCurrency} />
190
+ </p>
191
+ </div>
192
+ </div>
193
+ );
194
+ }
195
+
196
+ /** An `<iframe>` start tag; quoted values may hold `>`. */
197
+ const IFRAME_TAG = /<iframe\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
198
+
199
+ /** Embedded players (YouTube, Vimeo, maps) load only when scrolled near, unless the merchant set `loading`. */
200
+ function lazyIframes(html: string): string {
201
+ return html.replace(IFRAME_TAG, (tag) =>
202
+ /\sloading\s*=/i.test(tag.replace(/"[^"]*"|'[^']*'/g, '""'))
203
+ ? tag
204
+ : tag.replace(/^<iframe/i, '<iframe loading="lazy"')
205
+ );
206
+ }
207
+
208
+ function ProductDescription({ product }: { product: Product }) {
209
+ const { t } = useTranslation();
210
+ const [isExpanded, setIsExpanded] = useState(false);
211
+
212
+ if (!product.description) return null;
213
+
214
+ const description = lazyIframes(product.description.replace(/&nbsp;/g, '\n'));
215
+
216
+ if (product.has_read_more) {
217
+ return (
218
+ <div className="product__description p-2 px-5 sm:p-1 leading-7 mb-3">
219
+ <article
220
+ className="article article--main relative leading-8 overflow-hidden transition-all duration-300 py-4"
221
+ id="more-content"
222
+ style={{ maxHeight: isExpanded ? 'none' : '5.25rem' }}
223
+ dangerouslySetInnerHTML={{ __html: description }}
224
+ />
225
+ <button
226
+ type="button"
227
+ id="btn-show-more"
228
+ className={`link--primary inline-block mt-2 cursor-pointer bg-transparent border-0 p-0 ${isExpanded ? 'is-expanded' : ''}`}
229
+ onClick={() => setIsExpanded(true)}
230
+ >
231
+ {t('pages.products.read_more', 'Read more')}
232
+ </button>
233
+ </div>
234
+ );
235
+ }
236
+
237
+ return (
238
+ <div className="product__description p-2 px-5 sm:p-1 leading-7 mb-3">
239
+ <article className="article--main pb-1" dangerouslySetInnerHTML={{ __html: description }} />
240
+ </div>
241
+ );
242
+ }
243
+
244
+ function ProductQuantityInfo({ product }: { product: Product }) {
245
+ const { t } = useTranslation();
246
+ if (product.sold_quantity == null && !product.can_show_remained_quantity) {
247
+ return null;
248
+ }
249
+
250
+ return (
251
+ <div className="bg-white py-2.5 mb-5 rounded-md inline-flex text-sm">
252
+ {(product.sold_quantity ?? 0) > 0 && (
253
+ <div className="px-4 !text-red-800">
254
+ <i className="sicon-fire rtl:ml-1.5 ltr:mr-1.5"></i> {t('pages.products.sold', 'Sold')}{' '}
255
+ {/* Salla ships `sold_times` in Laravel plural format; resolve it for the count. */}
256
+ <span>{formatSallaPlural(t('pages.products.sold_times'), product.sold_quantity!)}</span>
257
+ </div>
258
+ )}
259
+ {product.can_show_remained_quantity && (
260
+ <div className="px-4 rtl:even:border-r ltr:even:border-l even:border-gray-200">
261
+ <i className="!text-red-800 sicon-box-bankers rtl:ml-1.5 ltr:mr-1.5"></i>
262
+ {t('pages.products.remained', 'Remained')}: {product.quantity}
263
+ </div>
264
+ )}
265
+ </div>
266
+ );
267
+ }