@salla.sa/twilight-theme-engine 1.0.84 → 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 (377) hide show
  1. package/dist/{AddToCartForm-JUMFLSKN.js → AddToCartForm-5X2WZDWR.js} +3 -3
  2. package/dist/{AddToCartForm-JUMFLSKN.js.map → AddToCartForm-5X2WZDWR.js.map} +1 -1
  3. package/dist/{BlogAuthorPage-LFAUMKC7.js → BlogAuthorPage-LO7FSSJR.js} +12 -10
  4. package/dist/BlogAuthorPage-LO7FSSJR.js.map +1 -0
  5. package/dist/{BlogCategoryPage-NWYFFPCD.js → BlogCategoryPage-KKFQ3PGR.js} +12 -10
  6. package/dist/BlogCategoryPage-KKFQ3PGR.js.map +1 -0
  7. package/dist/{BlogPage-BOHXFV7E.js → BlogPage-VLF3NSGE.js} +11 -9
  8. package/dist/BlogPage-VLF3NSGE.js.map +1 -0
  9. package/dist/{BlogSinglePage-FYEXQPMB.js → BlogSinglePage-AVK535IW.js} +11 -9
  10. package/dist/BlogSinglePage-AVK535IW.js.map +1 -0
  11. package/dist/{BlogTagPage-DAM3RNVA.js → BlogTagPage-F3T4D7RH.js} +12 -10
  12. package/dist/BlogTagPage-F3T4D7RH.js.map +1 -0
  13. package/dist/{BrandsPage-GAKQCE6O.js → BrandsPage-LVSBZSVC.js} +11 -9
  14. package/dist/BrandsPage-LVSBZSVC.js.map +1 -0
  15. package/dist/{Breadcrumb-NAYSZNRV.js → Breadcrumb-VLFTXRFC.js} +11 -9
  16. package/dist/Breadcrumb-VLFTXRFC.js.map +1 -0
  17. package/dist/{CartItem-ZRVLIZT3.js → CartItem-G3H34GW6.js} +11 -9
  18. package/dist/CartItem-G3H34GW6.js.map +1 -0
  19. package/dist/{CartSummary-U2TVLG35.js → CartSummary-VH37RPUI.js} +11 -9
  20. package/dist/CartSummary-VH37RPUI.js.map +1 -0
  21. package/dist/{Copyright-2LSBADZD.js → Copyright-HE5ZWINZ.js} +3 -3
  22. package/dist/{Copyright-2LSBADZD.js.map → Copyright-HE5ZWINZ.js.map} +1 -1
  23. package/dist/{CustomerLayout-WSKXWI7D.js → CustomerLayout-LKBLOAVW.js} +11 -9
  24. package/dist/CustomerLayout-LKBLOAVW.js.map +1 -0
  25. package/dist/{Footer-ONNRJU4G.js → Footer-HHXAST72.js} +5 -5
  26. package/dist/{Footer-ONNRJU4G.js.map → Footer-HHXAST72.js.map} +1 -1
  27. package/dist/{Header-C6VXLLUC.js → Header-GEVZM2YG.js} +8 -6
  28. package/dist/{Header-C6VXLLUC.js.map → Header-GEVZM2YG.js.map} +1 -1
  29. package/dist/{LoyaltyPage-IG5KSYNO.js → LoyaltyPage-M5CT3CFD.js} +11 -9
  30. package/dist/LoyaltyPage-M5CT3CFD.js.map +1 -0
  31. package/dist/{LoyaltyPointsPage-RKO56ZER.js → LoyaltyPointsPage-SDJB52U6.js} +10 -3
  32. package/dist/LoyaltyPointsPage-SDJB52U6.js.map +1 -0
  33. package/dist/{MainMenu-USOYTKPU.js → MainMenu-I7EYS752.js} +3 -3
  34. package/dist/{MainMenu-USOYTKPU.js.map → MainMenu-I7EYS752.js.map} +1 -1
  35. package/dist/{MasterLayout-2W24ACEK.js → MasterLayout-QDK4T3NE.js} +11 -9
  36. package/dist/{MasterLayout-2W24ACEK.js.map → MasterLayout-QDK4T3NE.js.map} +1 -1
  37. package/dist/{MobileMenu-E4X2GOZ3.js → MobileMenu-7VTCWAVJ.js} +11 -9
  38. package/dist/MobileMenu-7VTCWAVJ.js.map +1 -0
  39. package/dist/{NotificationsPage-BJ5XVWEG.js → NotificationsPage-QCABE6QD.js} +11 -9
  40. package/dist/NotificationsPage-QCABE6QD.js.map +1 -0
  41. package/dist/{OrderSinglePage-544E36FJ.js → OrderSinglePage-2HVBKBG4.js} +11 -9
  42. package/dist/OrderSinglePage-2HVBKBG4.js.map +1 -0
  43. package/dist/{OrdersPage-AFBWVTZU.js → OrdersPage-5MNCPCM3.js} +11 -9
  44. package/dist/OrdersPage-5MNCPCM3.js.map +1 -0
  45. package/dist/{PageSingle-H2EJIL74.js → PageSingle-EN5XIM2B.js} +3 -3
  46. package/dist/{PageSingle-H2EJIL74.js.map → PageSingle-EN5XIM2B.js.map} +1 -1
  47. package/dist/{ProductDetails-SDZHALU6.js → ProductDetails-UEWQPPDZ.js} +13 -6
  48. package/dist/ProductDetails-UEWQPPDZ.js.map +1 -0
  49. package/dist/{ProfilePage-V5KV6GQK.js → ProfilePage-TZSU3GHZ.js} +11 -9
  50. package/dist/ProfilePage-TZSU3GHZ.js.map +1 -0
  51. package/dist/{ReviewsPage-CXKJABXP.js → ReviewsPage-WYZ6CHF2.js} +3 -3
  52. package/dist/{ReviewsPage-CXKJABXP.js.map → ReviewsPage-WYZ6CHF2.js.map} +1 -1
  53. package/dist/{SeoCartWidget-JAV6M3X5.js → SeoCartWidget-5JR5RXQM.js} +11 -9
  54. package/dist/SeoCartWidget-5JR5RXQM.js.map +1 -0
  55. package/dist/{SettingsPage-43SUCLQR.js → SettingsPage-DIDRQXMM.js} +11 -9
  56. package/dist/SettingsPage-DIDRQXMM.js.map +1 -0
  57. package/dist/{StoreClosedNotification-MINBJOGZ.js → StoreClosedNotification-C2G32E34.js} +12 -10
  58. package/dist/{StoreClosedNotification-MINBJOGZ.js.map → StoreClosedNotification-C2G32E34.js.map} +1 -1
  59. package/dist/{TestimonialsPage-RLLIDLOA.js → TestimonialsPage-XPHFRBZ7.js} +3 -3
  60. package/dist/{TestimonialsPage-RLLIDLOA.js.map → TestimonialsPage-XPHFRBZ7.js.map} +1 -1
  61. package/dist/{ThankYouPage-52SSZ7FV.js → ThankYouPage-UA6YYFB2.js} +11 -9
  62. package/dist/ThankYouPage-UA6YYFB2.js.map +1 -0
  63. package/dist/{WalletPage-4AN4UJ52.js → WalletPage-AJVV7LVL.js} +11 -9
  64. package/dist/WalletPage-AJVV7LVL.js.map +1 -0
  65. package/dist/{WishlistPage-L6IYJUJZ.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-R5BQDCDK.js → chunk-2HCCIHU7.js} +4 -4
  79. package/dist/{chunk-R5BQDCDK.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-UNEZZMQA.js → chunk-4WVJJ7SN.js} +20 -4
  83. package/dist/chunk-4WVJJ7SN.js.map +1 -0
  84. package/dist/{chunk-FFWSQJLV.js → chunk-BAYGWTNM.js} +3 -3
  85. package/dist/{chunk-FFWSQJLV.js.map → chunk-BAYGWTNM.js.map} +1 -1
  86. package/dist/{chunk-WSRVPR7V.js → chunk-CLBFF2CY.js} +3 -3
  87. package/dist/{chunk-WSRVPR7V.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-CCUE2FMU.js → chunk-DBXH3EMJ.js} +3 -3
  91. package/dist/{chunk-CCUE2FMU.js.map → chunk-DBXH3EMJ.js.map} +1 -1
  92. package/dist/{chunk-MECSOKS7.js → chunk-E26YG3AO.js} +4 -4
  93. package/dist/{chunk-MECSOKS7.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-IC6XW3PD.js → chunk-HQ7WA37L.js} +7 -7
  97. package/dist/{chunk-IC6XW3PD.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-RRGEASAL.js → chunk-KP3VYG4F.js} +6 -6
  101. package/dist/{chunk-RRGEASAL.js.map → chunk-KP3VYG4F.js.map} +1 -1
  102. package/dist/{chunk-EFF2WO6W.js → chunk-PWTXR4RR.js} +10 -10
  103. package/dist/{chunk-EFF2WO6W.js.map → chunk-PWTXR4RR.js.map} +1 -1
  104. package/dist/chunk-QJYUTRAQ.js +14 -0
  105. package/dist/{chunk-4EA56R6I.js.map → chunk-QJYUTRAQ.js.map} +1 -1
  106. package/dist/{chunk-YFQUSAET.js → chunk-U55ZJC2X.js} +4 -4
  107. package/dist/{chunk-YFQUSAET.js.map → chunk-U55ZJC2X.js.map} +1 -1
  108. package/dist/{chunk-R54GSKJS.js → chunk-WSDG4ZRI.js} +30 -16
  109. package/dist/chunk-WSDG4ZRI.js.map +1 -0
  110. package/dist/{chunk-3PWARKZX.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/HomePageRenderer.d.ts +6 -0
  116. package/dist/components/home/HomePageRenderer.d.ts.map +1 -1
  117. package/dist/components/home/index.js +11 -9
  118. package/dist/components/layout/PageAdvertisements.d.ts +7 -0
  119. package/dist/components/layout/PageAdvertisements.d.ts.map +1 -0
  120. package/dist/components/layout/index.d.ts +1 -0
  121. package/dist/components/layout/index.d.ts.map +1 -1
  122. package/dist/components/layout/index.js +8 -1
  123. package/dist/components/product/index.js +10 -8
  124. package/dist/components/store/index.js +11 -9
  125. package/dist/components/store/index.js.map +1 -1
  126. package/dist/{document-class-SD6BUMZN.js → document-class-7AXAAWGZ.js} +6 -5
  127. package/dist/{document-class-SD6BUMZN.js.map → document-class-7AXAAWGZ.js.map} +1 -1
  128. package/dist/hooks/index.js +9 -7
  129. package/dist/hooks/useGtm.js +9 -7
  130. package/dist/hooks/useMoney.js +9 -7
  131. package/dist/hooks/useStore.js +9 -7
  132. package/dist/index.js +9 -7
  133. package/dist/index.js.map +1 -1
  134. package/dist/{not-found-TUU7WQLL.js → not-found-3EEMQZMU.js} +11 -9
  135. package/dist/not-found-3EEMQZMU.js.map +1 -0
  136. package/dist/providers/index.js +9 -7
  137. package/dist/routes/account/orders.js +1 -1
  138. package/dist/routes/account.js +6 -6
  139. package/dist/routes/blog.js +5 -5
  140. package/dist/routes/brands.js +1 -1
  141. package/dist/routes/cart.js +13 -11
  142. package/dist/routes/cart.js.map +1 -1
  143. package/dist/routes/home.js +12 -10
  144. package/dist/routes/loyalty.js +2 -2
  145. package/dist/routes/page.js +1 -1
  146. package/dist/routes/product-listing.js +11 -9
  147. package/dist/routes/product-listing.js.map +1 -1
  148. package/dist/routes/product.js +11 -9
  149. package/dist/routes/product.js.map +1 -1
  150. package/dist/routes/root/beforeLoad.d.ts +2 -2
  151. package/dist/routes/root/beforeLoad.d.ts.map +1 -1
  152. package/dist/routes/root/head.d.ts.map +1 -1
  153. package/dist/routes/testimonials.js +2 -2
  154. package/dist/routes/thank-you.js +1 -1
  155. package/dist/skeleton.js +12 -10
  156. package/dist/tanstack/document-class.d.ts.map +1 -1
  157. package/dist/tanstack/index.d.ts +1 -0
  158. package/dist/tanstack/index.d.ts.map +1 -1
  159. package/dist/tanstack/stylesheet.d.ts +15 -0
  160. package/dist/tanstack/stylesheet.d.ts.map +1 -0
  161. package/dist/tanstack.js +34 -5
  162. package/dist/tanstack.js.map +1 -1
  163. package/dist/types/index.d.ts +5 -0
  164. package/dist/types/index.d.ts.map +1 -1
  165. package/dist/utils/index.js +1 -1
  166. package/dist/vite/index.js +1 -1
  167. package/package.json +12 -3
  168. package/tailwind/components/Component.tsx.txt +21 -0
  169. package/tailwind/components/ComponentRegistry.ts.txt +66 -0
  170. package/tailwind/components/cart/CartItem.tsx.txt +211 -0
  171. package/tailwind/components/cart/CartSummary.tsx.txt +269 -0
  172. package/tailwind/components/cart/PreOrderNotification.tsx.txt +16 -0
  173. package/tailwind/components/cart/PriceQuoteButton.tsx.txt +21 -0
  174. package/tailwind/components/cart/SeoCartWidget.tsx.txt +99 -0
  175. package/tailwind/components/cart/index.ts.txt +11 -0
  176. package/tailwind/components/cart/lazy.ts.txt +15 -0
  177. package/tailwind/components/collapse/Collapse.tsx.txt +95 -0
  178. package/tailwind/components/collapse/index.ts.txt +41 -0
  179. package/tailwind/components/common/Breadcrumb.tsx.txt +27 -0
  180. package/tailwind/components/common/BreadcrumbJsonLd.tsx.txt +40 -0
  181. package/tailwind/components/common/BreadcrumbTrail.tsx.txt +97 -0
  182. package/tailwind/components/common/CurrencySymbol.tsx.txt +8 -0
  183. package/tailwind/components/common/DeferredData.tsx.txt +122 -0
  184. package/tailwind/components/common/ErrorPage.tsx.txt +69 -0
  185. package/tailwind/components/common/Image.tsx.txt +119 -0
  186. package/tailwind/components/common/Link.tsx.txt +36 -0
  187. package/tailwind/components/common/Link.types.ts.txt +85 -0
  188. package/tailwind/components/common/NoContent.tsx.txt +27 -0
  189. package/tailwind/components/common/RenderWhenVisible.tsx.txt +92 -0
  190. package/tailwind/components/common/index.ts.txt +42 -0
  191. package/tailwind/components/common/lazy.ts.txt +28 -0
  192. package/tailwind/components/defineComponent.ts.txt +32 -0
  193. package/tailwind/components/drawer/Drawer.tsx.txt +219 -0
  194. package/tailwind/components/drawer/index.ts.txt +38 -0
  195. package/tailwind/components/dropdown/Dropdown.tsx.txt +166 -0
  196. package/tailwind/components/dropdown/index.ts.txt +40 -0
  197. package/tailwind/components/home/BundleComponent.tsx.txt +35 -0
  198. package/tailwind/components/home/ComponentErrorBoundary.tsx.txt +98 -0
  199. package/tailwind/components/home/FeaturedProductsStyle1.tsx.txt +125 -0
  200. package/tailwind/components/home/FeaturedProductsStyle2.tsx.txt +127 -0
  201. package/tailwind/components/home/FeaturedProductsStyle3.tsx.txt +71 -0
  202. package/tailwind/components/home/FixedBanner.tsx.txt +68 -0
  203. package/tailwind/components/home/FixedProducts.tsx.txt +72 -0
  204. package/tailwind/components/home/HomePageRenderer.tsx.txt +284 -0
  205. package/tailwind/components/home/PageBlocks.tsx.txt +24 -0
  206. package/tailwind/components/home/ParallaxBackground.tsx.txt +50 -0
  207. package/tailwind/components/home/PhotosSlider.tsx.txt +61 -0
  208. package/tailwind/components/home/ProductsSlider.tsx.txt +87 -0
  209. package/tailwind/components/home/ReorderProducts.tsx.txt +38 -0
  210. package/tailwind/components/home/SquarePhotos.tsx.txt +68 -0
  211. package/tailwind/components/home/StoreFeatures.tsx.txt +34 -0
  212. package/tailwind/components/home/Testimonials.tsx.txt +36 -0
  213. package/tailwind/components/home/Youtube.tsx.txt +48 -0
  214. package/tailwind/components/home/index.ts.txt +50 -0
  215. package/tailwind/components/home/lazy.ts.txt +17 -0
  216. package/tailwind/components/home/register.ts.txt +44 -0
  217. package/tailwind/components/home/reorder-hooks.ts.txt +52 -0
  218. package/tailwind/components/home/types.ts.txt +4 -0
  219. package/tailwind/components/layout/Copyright.tsx.txt +23 -0
  220. package/tailwind/components/layout/CustomerLayout.tsx.txt +91 -0
  221. package/tailwind/components/layout/Footer.tsx.txt +343 -0
  222. package/tailwind/components/layout/Header.tsx.txt +154 -0
  223. package/tailwind/components/layout/MasterLayout.tsx.txt +63 -0
  224. package/tailwind/components/layout/PageAdvertisements.tsx.txt +131 -0
  225. package/tailwind/components/layout/WidgetHead.tsx.txt +53 -0
  226. package/tailwind/components/layout/index.ts.txt +27 -0
  227. package/tailwind/components/layout/lazy.ts.txt +25 -0
  228. package/tailwind/components/layout/types.ts.txt +11 -0
  229. package/tailwind/components/modal/ImageModal.tsx.txt +70 -0
  230. package/tailwind/components/modal/Modal.tsx.txt +245 -0
  231. package/tailwind/components/modal/index.ts.txt +39 -0
  232. package/tailwind/components/modal/lazy.ts.txt +10 -0
  233. package/tailwind/components/navigation/MainMenu.tsx.txt +94 -0
  234. package/tailwind/components/navigation/MobileMenu.tsx.txt +160 -0
  235. package/tailwind/components/navigation/index.ts.txt +4 -0
  236. package/tailwind/components/navigation/lazy.ts.txt +11 -0
  237. package/tailwind/components/product/AddToCartForm.tsx.txt +365 -0
  238. package/tailwind/components/product/ProductCard.tsx.txt +305 -0
  239. package/tailwind/components/product/ProductDetails.tsx.txt +267 -0
  240. package/tailwind/components/product/ProductGallery.tsx.txt +234 -0
  241. package/tailwind/components/product/index.ts.txt +15 -0
  242. package/tailwind/components/product/lazy.ts.txt +9 -0
  243. package/tailwind/components/snippets/AppsSnippets.tsx.txt +225 -0
  244. package/tailwind/components/snippets/SallaSdkScripts.tsx.txt +14 -0
  245. package/tailwind/components/snippets/index.ts.txt +4 -0
  246. package/tailwind/components/store/StoreClosedNotification.tsx.txt +73 -0
  247. package/tailwind/components/store/index.ts.txt +8 -0
  248. package/tailwind/components/store/lazy.ts.txt +8 -0
  249. package/tailwind/components/toast/Toaster.tsx.txt +177 -0
  250. package/tailwind/components/toast/index.ts.txt +10 -0
  251. package/tailwind/components/toast/types.ts.txt +38 -0
  252. package/tailwind/components/toast/useToast.ts.txt +61 -0
  253. package/tailwind/components/tracking/GoogleTag.tsx.txt +77 -0
  254. package/tailwind/components/tracking/SiftSnippet.tsx.txt +49 -0
  255. package/tailwind/components/tracking/TwilightBundles.tsx.txt +67 -0
  256. package/tailwind/components/tracking/index.ts.txt +11 -0
  257. package/tailwind/components/tracking/lazy.ts.txt +11 -0
  258. package/tailwind/components/useComponent.ts.txt +12 -0
  259. package/tailwind/routes/account/index.tsx.txt +16 -0
  260. package/tailwind/routes/account/notifications/NotificationsPage.tsx.txt +54 -0
  261. package/tailwind/routes/account/notifications/index.tsx.txt +39 -0
  262. package/tailwind/routes/account/notifications/loader.ts.txt +22 -0
  263. package/tailwind/routes/account/notifications/types.ts.txt +18 -0
  264. package/tailwind/routes/account/orders/OrderSinglePage.tsx.txt +181 -0
  265. package/tailwind/routes/account/orders/OrdersPage.tsx.txt +28 -0
  266. package/tailwind/routes/account/orders/index.tsx.txt +86 -0
  267. package/tailwind/routes/account/orders/loader.ts.txt +66 -0
  268. package/tailwind/routes/account/orders/single/OrderActions.tsx.txt +197 -0
  269. package/tailwind/routes/account/orders/single/OrderHeader.tsx.txt +143 -0
  270. package/tailwind/routes/account/orders/single/OrderItemRow.tsx.txt +209 -0
  271. package/tailwind/routes/account/orders/single/OrderSections.tsx.txt +215 -0
  272. package/tailwind/routes/account/orders/single/types.ts.txt +38 -0
  273. package/tailwind/routes/account/orders/types.ts.txt +29 -0
  274. package/tailwind/routes/account/profile/ProfilePage.tsx.txt +30 -0
  275. package/tailwind/routes/account/profile/index.tsx.txt +39 -0
  276. package/tailwind/routes/account/profile/loader.ts.txt +16 -0
  277. package/tailwind/routes/account/profile/types.ts.txt +5 -0
  278. package/tailwind/routes/account/settings/SettingsPage.tsx.txt +13 -0
  279. package/tailwind/routes/account/settings/index.tsx.txt +39 -0
  280. package/tailwind/routes/account/settings/loader.ts.txt +13 -0
  281. package/tailwind/routes/account/settings/types.ts.txt +5 -0
  282. package/tailwind/routes/account/types.ts.txt +320 -0
  283. package/tailwind/routes/account/wallet/WalletPage.tsx.txt +195 -0
  284. package/tailwind/routes/account/wallet/index.tsx.txt +38 -0
  285. package/tailwind/routes/account/wallet/loader.ts.txt +27 -0
  286. package/tailwind/routes/account/wallet/types.ts.txt +11 -0
  287. package/tailwind/routes/account/wishlist/WishlistPage.tsx.txt +314 -0
  288. package/tailwind/routes/account/wishlist/WishlistShare.tsx.txt +248 -0
  289. package/tailwind/routes/account/wishlist/index.tsx.txt +40 -0
  290. package/tailwind/routes/account/wishlist/loader.ts.txt +26 -0
  291. package/tailwind/routes/account/wishlist/types.ts.txt +9 -0
  292. package/tailwind/routes/blog/BlogAuthorPage.tsx.txt +82 -0
  293. package/tailwind/routes/blog/BlogCategoryPage.tsx.txt +84 -0
  294. package/tailwind/routes/blog/BlogPage.tsx.txt +191 -0
  295. package/tailwind/routes/blog/BlogSinglePage.tsx.txt +168 -0
  296. package/tailwind/routes/blog/BlogTagPage.tsx.txt +84 -0
  297. package/tailwind/routes/blog/BlogTags.tsx.txt +54 -0
  298. package/tailwind/routes/blog/components.tsx.txt +87 -0
  299. package/tailwind/routes/blog/index.tsx.txt +238 -0
  300. package/tailwind/routes/blog/loader.ts.txt +173 -0
  301. package/tailwind/routes/blog/types.ts.txt +106 -0
  302. package/tailwind/routes/brands/BrandsPage.tsx.txt +179 -0
  303. package/tailwind/routes/brands/index.tsx.txt +47 -0
  304. package/tailwind/routes/brands/loader.ts.txt +14 -0
  305. package/tailwind/routes/brands/types.ts.txt +53 -0
  306. package/tailwind/routes/cart/CartPage.tsx.txt +112 -0
  307. package/tailwind/routes/cart/index.tsx.txt +39 -0
  308. package/tailwind/routes/cart/loader.ts.txt +15 -0
  309. package/tailwind/routes/cart/types.ts.txt +6 -0
  310. package/tailwind/routes/home/HomePage.tsx.txt +53 -0
  311. package/tailwind/routes/home/index.tsx.txt +56 -0
  312. package/tailwind/routes/home/loader.ts.txt +24 -0
  313. package/tailwind/routes/home/types.ts.txt +11 -0
  314. package/tailwind/routes/index.ts.txt +94 -0
  315. package/tailwind/routes/lazy.ts.txt +105 -0
  316. package/tailwind/routes/loyalty/LoyaltyPage.tsx.txt +17 -0
  317. package/tailwind/routes/loyalty/LoyaltyPointsPage.tsx.txt +11 -0
  318. package/tailwind/routes/loyalty/index.tsx.txt +81 -0
  319. package/tailwind/routes/loyalty/loader.ts.txt +53 -0
  320. package/tailwind/routes/loyalty/types.ts.txt +109 -0
  321. package/tailwind/routes/not-found.tsx.txt +50 -0
  322. package/tailwind/routes/page/PageSingle.tsx.txt +34 -0
  323. package/tailwind/routes/page/index.tsx.txt +60 -0
  324. package/tailwind/routes/page/loader.ts.txt +19 -0
  325. package/tailwind/routes/page/types.ts.txt +34 -0
  326. package/tailwind/routes/product/ProductPage.tsx.txt +77 -0
  327. package/tailwind/routes/product/index.tsx.txt +157 -0
  328. package/tailwind/routes/product/loader.ts.txt +35 -0
  329. package/tailwind/routes/product/types.ts.txt +6 -0
  330. package/tailwind/routes/product-listing/ProductListPage.tsx.txt +321 -0
  331. package/tailwind/routes/product-listing/index.tsx.txt +95 -0
  332. package/tailwind/routes/product-listing/loader.ts.txt +323 -0
  333. package/tailwind/routes/product-listing/types.ts.txt +96 -0
  334. package/tailwind/routes/product-listing/useAppliedFilters.ts.txt +130 -0
  335. package/tailwind/routes/product-short-link.ts.txt +104 -0
  336. package/tailwind/routes/root/beforeLoad.ts.txt +354 -0
  337. package/tailwind/routes/root/head.ts.txt +424 -0
  338. package/tailwind/routes/root/index.ts.txt +2 -0
  339. package/tailwind/routes/seo-redirects.ts.txt +82 -0
  340. package/tailwind/routes/testimonials/ReviewsPage.tsx.txt +12 -0
  341. package/tailwind/routes/testimonials/TestimonialsPage.tsx.txt +80 -0
  342. package/tailwind/routes/testimonials/index.tsx.txt +80 -0
  343. package/tailwind/routes/testimonials/loader.ts.txt +29 -0
  344. package/tailwind/routes/testimonials/types.ts.txt +9 -0
  345. package/tailwind/routes/thank-you/ThankYouPage.tsx.txt +241 -0
  346. package/tailwind/routes/thank-you/index.tsx.txt +49 -0
  347. package/tailwind/routes/thank-you/loader.ts.txt +40 -0
  348. package/tailwind/routes/thank-you/types.ts.txt +16 -0
  349. package/tailwind/routes/types.ts.txt +41 -0
  350. package/dist/BlogAuthorPage-LFAUMKC7.js.map +0 -1
  351. package/dist/BlogCategoryPage-NWYFFPCD.js.map +0 -1
  352. package/dist/BlogPage-BOHXFV7E.js.map +0 -1
  353. package/dist/BlogSinglePage-FYEXQPMB.js.map +0 -1
  354. package/dist/BlogTagPage-DAM3RNVA.js.map +0 -1
  355. package/dist/BrandsPage-GAKQCE6O.js.map +0 -1
  356. package/dist/Breadcrumb-NAYSZNRV.js.map +0 -1
  357. package/dist/CartItem-ZRVLIZT3.js.map +0 -1
  358. package/dist/CartSummary-U2TVLG35.js.map +0 -1
  359. package/dist/CustomerLayout-WSKXWI7D.js.map +0 -1
  360. package/dist/LoyaltyPage-IG5KSYNO.js.map +0 -1
  361. package/dist/LoyaltyPointsPage-RKO56ZER.js.map +0 -1
  362. package/dist/MobileMenu-E4X2GOZ3.js.map +0 -1
  363. package/dist/NotificationsPage-BJ5XVWEG.js.map +0 -1
  364. package/dist/OrderSinglePage-544E36FJ.js.map +0 -1
  365. package/dist/OrdersPage-AFBWVTZU.js.map +0 -1
  366. package/dist/ProductDetails-SDZHALU6.js.map +0 -1
  367. package/dist/ProfilePage-V5KV6GQK.js.map +0 -1
  368. package/dist/SeoCartWidget-JAV6M3X5.js.map +0 -1
  369. package/dist/SettingsPage-43SUCLQR.js.map +0 -1
  370. package/dist/ThankYouPage-52SSZ7FV.js.map +0 -1
  371. package/dist/WalletPage-4AN4UJ52.js.map +0 -1
  372. package/dist/WishlistPage-L6IYJUJZ.js.map +0 -1
  373. package/dist/chunk-3PWARKZX.js.map +0 -1
  374. package/dist/chunk-4EA56R6I.js +0 -14
  375. package/dist/chunk-R54GSKJS.js.map +0 -1
  376. package/dist/chunk-UNEZZMQA.js.map +0 -1
  377. package/dist/not-found-TUU7WQLL.js.map +0 -1
@@ -0,0 +1,82 @@
1
+ import { useRef, useCallback } from 'react';
2
+ import { ItemsList } from '@salla.sa/twilight-components-react';
3
+ import { HookSlot } from '../../hooks';
4
+ import { useTranslation } from '../../providers/I18nProvider';
5
+ import { useLocation } from '../../providers/NavigationProvider';
6
+ import { Breadcrumb, NoContent } from '../../components/common';
7
+ import { blog } from '../../api/blog';
8
+ import { BlogArticleCard, BlogListLayout } from './components';
9
+ import type { BlogAuthorLoaderData, ArticleSummary } from './types';
10
+
11
+ const EMPTY_CATEGORIES: BlogAuthorLoaderData['categories'] = [];
12
+ const EMPTY_ARTICLES: ArticleSummary[] = [];
13
+
14
+ export function BlogAuthorPage({
15
+ page,
16
+ author,
17
+ categories = EMPTY_CATEGORIES,
18
+ articles = EMPTY_ARTICLES,
19
+ cursor,
20
+ }: BlogAuthorLoaderData) {
21
+ const { t } = useTranslation();
22
+ const { pathname, searchStr } = useLocation();
23
+ const nextCursorRef = useRef<string | null>(cursor?.next ?? null);
24
+
25
+ const loader = useCallback(async () => {
26
+ const nextUrl = nextCursorRef.current;
27
+ const cursorMatch = nextUrl?.match(/[?&]cursor=([^&]+)/);
28
+ const cursorValue = cursorMatch ? decodeURIComponent(cursorMatch[1]) : (nextUrl ?? undefined);
29
+
30
+ const result = await blog.articles.list({ author: String(page.id), cursor: cursorValue });
31
+ nextCursorRef.current = result.cursor?.next ?? null;
32
+
33
+ return {
34
+ items: result.data,
35
+ next: result.cursor?.next ?? null,
36
+ };
37
+ }, [page.id]);
38
+
39
+ if (articles.length === 0) {
40
+ return (
41
+ <div className="container mb-8 sm:mb-24">
42
+ <Breadcrumb page={page} />
43
+ <BlogListLayout categories={categories}>
44
+ <HookSlot name="blog:author.start" />
45
+ <NoContent icon="sicon-inbox" message={t('pages.blog_categories.no_articles')} />
46
+ <HookSlot name="blog:author.end" />
47
+ </BlogListLayout>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ return (
53
+ <div className="container mb-8 sm:mb-24">
54
+ <Breadcrumb page={page} />
55
+
56
+ <BlogListLayout categories={categories}>
57
+ <HookSlot name="blog:author.start" />
58
+
59
+ <div className="mb-6">
60
+ <h1 className="font-bold text-xl">{author.name}</h1>
61
+ </div>
62
+
63
+ <ItemsList<ArticleSummary>
64
+ items={articles}
65
+ loader={cursor?.next ? loader : undefined}
66
+ mode={cursor?.next ? 'infinite' : 'static'}
67
+ pageUrl={pathname + searchStr}
68
+ className="s-blog-list"
69
+ itemsClassName="grid md:grid-cols-2 gap-2.5 md:gap-8"
70
+ empty={<NoContent icon="sicon-inbox" message={t('pages.blog_categories.no_articles')} />}
71
+ t={(key, fallback) => t(key, fallback || key)}
72
+ >
73
+ {(items) =>
74
+ items.map((article) => <BlogArticleCard key={article.id} article={article} />)
75
+ }
76
+ </ItemsList>
77
+
78
+ <HookSlot name="blog:author.end" />
79
+ </BlogListLayout>
80
+ </div>
81
+ );
82
+ }
@@ -0,0 +1,84 @@
1
+ import { useRef, useCallback } from 'react';
2
+ import { ItemsList } from '@salla.sa/twilight-components-react';
3
+ import { HookSlot } from '../../hooks';
4
+ import { useTranslation } from '../../providers/I18nProvider';
5
+ import { useLocation } from '../../providers/NavigationProvider';
6
+ import { Breadcrumb, NoContent } from '../../components/common';
7
+ import { blog } from '../../api/blog';
8
+ import { BlogArticleCard, BlogListLayout } from './components';
9
+ import type { BlogCategoryLoaderData, ArticleSummary } from './types';
10
+
11
+ const EMPTY_CATEGORIES: BlogCategoryLoaderData['categories'] = [];
12
+ const EMPTY_ARTICLES: ArticleSummary[] = [];
13
+
14
+ export function BlogCategoryPage({
15
+ page,
16
+ blogCategory,
17
+ categories = EMPTY_CATEGORIES,
18
+ articles = EMPTY_ARTICLES,
19
+ cursor,
20
+ }: BlogCategoryLoaderData) {
21
+ const { t } = useTranslation();
22
+ const { pathname, searchStr } = useLocation();
23
+ const nextCursorRef = useRef<string | null>(cursor?.next ?? null);
24
+
25
+ const loader = useCallback(async () => {
26
+ const nextUrl = nextCursorRef.current;
27
+ const cursorMatch = nextUrl?.match(/[?&]cursor=([^&]+)/);
28
+ const cursorValue = cursorMatch ? decodeURIComponent(cursorMatch[1]) : (nextUrl ?? undefined);
29
+
30
+ const result = await blog.articles.list({ category: blogCategory.id, cursor: cursorValue });
31
+ nextCursorRef.current = result.cursor?.next ?? null;
32
+
33
+ return {
34
+ items: result.data,
35
+ next: result.cursor?.next ?? null,
36
+ };
37
+ }, [blogCategory.id]);
38
+
39
+ if (articles.length === 0) {
40
+ return (
41
+ <div className="container mb-8 sm:mb-24">
42
+ <Breadcrumb page={page} />
43
+ <BlogListLayout categories={categories}>
44
+ <HookSlot name="blog:category.start" />
45
+ <NoContent icon="sicon-inbox" message={t('pages.blog_categories.no_articles')} />
46
+ <HookSlot name="blog:category.end" />
47
+ </BlogListLayout>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ return (
53
+ <div className="container mb-8 sm:mb-24">
54
+ <Breadcrumb page={page} />
55
+
56
+ <BlogListLayout categories={categories}>
57
+ <HookSlot name="blog:category.start" />
58
+
59
+ <div className="mb-6">
60
+ <h1 className="font-bold text-xl">{blogCategory.name}</h1>
61
+ </div>
62
+
63
+ <ItemsList<ArticleSummary>
64
+ items={articles}
65
+ loader={cursor?.next ? loader : undefined}
66
+ mode={cursor?.next ? 'infinite' : 'static'}
67
+ pageUrl={pathname + searchStr}
68
+ className="s-blog-list"
69
+ itemsClassName="grid md:grid-cols-2 gap-2.5 md:gap-8"
70
+ empty={<NoContent icon="sicon-inbox" message={t('pages.blog_categories.no_articles')} />}
71
+ t={(key, fallback) => t(key, fallback || key)}
72
+ >
73
+ {(items) =>
74
+ items.map((article) => (
75
+ <BlogArticleCard key={article.id} article={article} showAuthor />
76
+ ))
77
+ }
78
+ </ItemsList>
79
+
80
+ <HookSlot name="blog:category.end" />
81
+ </BlogListLayout>
82
+ </div>
83
+ );
84
+ }
@@ -0,0 +1,191 @@
1
+ import { SallaSlider } from '@salla.sa/twilight-components-react/slider';
2
+ import { HookSlot, useDate } from '../../hooks';
3
+ import { useTranslation } from '../../providers/I18nProvider';
4
+ import type { BlogPageProps, ArticleSummary } from './types';
5
+ import { Breadcrumb, NoContent, Link, Image } from '../../components/common';
6
+ import { BlogTags } from './BlogTags';
7
+
8
+ const EMPTY_CATEGORIES: NonNullable<BlogPageProps['categories']> = [];
9
+ const EMPTY_ARTICLES: ArticleSummary[] = [];
10
+ const EMPTY_SLIDES: NonNullable<BlogPageProps['slides']> = [];
11
+
12
+ export function BlogPage({
13
+ page,
14
+ categories = EMPTY_CATEGORIES,
15
+ articles = EMPTY_ARTICLES,
16
+ slides = EMPTY_SLIDES,
17
+ }: BlogPageProps) {
18
+ const { t } = useTranslation();
19
+ const { format: formatDate } = useDate();
20
+
21
+ return (
22
+ <div className="container mb-8 sm:mb-24">
23
+ <div className="center-between">
24
+ <Breadcrumb page={page} />
25
+ </div>
26
+
27
+ <div className="grid grid-cols-1 lg:grid-cols-8 gap-8">
28
+ <nav aria-label="Sidebar" className="hidden lg:block lg:sticky shrink-0 top-24 col-span-2">
29
+ <h2 className="font-bold mb-3">{t('pages.blog_categories.categories')}</h2>
30
+ <ul>
31
+ {categories.map((category) => (
32
+ <li key={category.id} className={category.is_current ? 'text-primary' : ''}>
33
+ <Link className="py-3 block text-sm hover:text-primary" to={category.url}>
34
+ {category.name}
35
+ </Link>
36
+ </li>
37
+ ))}
38
+ </ul>
39
+ </nav>
40
+
41
+ <div className="main-content lg:col-span-6">
42
+ <HookSlot name="blog:start" />
43
+
44
+ {slides.length > 0 && (
45
+ <SallaSlider
46
+ id="blog-home-slider"
47
+ type="blog"
48
+ autoPlay
49
+ autoHeight
50
+ showControls={false}
51
+ pagination
52
+ className="blog-slider block rounded overflow-hidden mb-2.5 lg:mb-8"
53
+ sliderConfig={{ watchOverflow: true }}
54
+ >
55
+ <div slot="items">
56
+ {slides.map((article, index) => (
57
+ <div
58
+ key={article.id}
59
+ className="swiper-slide has-overlay blog-slider__slide h-[300px] md:h-[400px] relative bg-border-color flex justify-center"
60
+ >
61
+ <Image
62
+ className="object-cover h-full w-full"
63
+ src={article.image}
64
+ alt={article.name}
65
+ aspectRatio="3/1"
66
+ priority={index === 0}
67
+ />
68
+ <div className="absolute z-10 bottom-0 rtl:right-0 ltr:left-0 p-4 pb-10 rtl:lg:pr-10 ltr:lg:pl-10 lg:pb-20">
69
+ <div className="blog-slider__content relative">
70
+ <div className="z-20 w-full md:w-5/6 lg:w-6/12 text-white">
71
+ <div className="block-slide-anime">
72
+ <div className="mb-2.5 rounded-md inline-flex text-sm rtl:space-x-reverse space-x-5">
73
+ <div className="flex items-center">
74
+ <i className="sicon-calendar-date rtl:ml-1 ltr:mr-1"></i>
75
+ <span className="whitespace-nowrap">
76
+ {formatDate(article.created_at)}
77
+ </span>
78
+ </div>
79
+ <Link
80
+ to={article.author?.url || '#'}
81
+ className="flex items-center hover:underline"
82
+ >
83
+ <i className="sicon-user rtl:ml-1 ltr:mr-1"></i>
84
+ <span className="whitespace-nowrap">{article.author?.name}</span>
85
+ </Link>
86
+ </div>
87
+ <h3 className="text-sm font-bold leading-normal mb-4">
88
+ <Link to={article.url}>{article.name}</Link>
89
+ </h3>
90
+ </div>
91
+ <p className="line-clamp-2 block-slide-anime description">
92
+ {article.description}
93
+ </p>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ ))}
99
+ </div>
100
+ </SallaSlider>
101
+ )}
102
+
103
+ {!slides.length && (
104
+ <div className="sr-only lg:not-sr-only">
105
+ <h1 className="blog-category__title font-bold text-lg mb-8">
106
+ {t('blocks.footer.blog')}
107
+ </h1>
108
+ </div>
109
+ )}
110
+
111
+ {slides.length > 0 && <h1 className="sr-only">{t('blocks.footer.blog')}</h1>}
112
+
113
+ {articles.length > 0 ? (
114
+ <>
115
+ <HookSlot name="blog:items.start" />
116
+ <div className="grid md:grid-cols-2 gap-2.5 md:gap-8">
117
+ {articles.map((article) => (
118
+ <div
119
+ key={article.id}
120
+ className="list-block post-entry relative h-full transition-shadow duration-500 bg-white hover:shadow-default rounded-lg justify-around overflow-hidden flex flex-col"
121
+ >
122
+ <Link
123
+ to={article.url}
124
+ className="post-entry__image relative overflow-hidden hover:opacity-90 w-full h-56 bg-border-color flex items-center justify-center"
125
+ >
126
+ <Image
127
+ className="h-full w-full object-cover"
128
+ src={article.image}
129
+ alt={article.name}
130
+ aspectRatio="16/9"
131
+ />
132
+ </Link>
133
+
134
+ <div className="flex-1 px-5 pt-5 pb-7">
135
+ <div className="mb-2.5 text-gray-500 rounded-md inline-flex text-sm rtl:space-x-reverse space-x-5">
136
+ <div className="flex items-center">
137
+ <i className="sicon-calendar-date rtl:ml-1 ltr:mr-1"></i>
138
+ <span>{formatDate(article.created_at)}</span>
139
+ </div>
140
+ <Link
141
+ to={article.author?.url || '#'}
142
+ className="flex items-center hover:text-dark"
143
+ >
144
+ <i className="sicon-user rtl:ml-1 ltr:mr-1"></i>
145
+ <span>{article.author?.name}</span>
146
+ </Link>
147
+ </div>
148
+
149
+ <h3 className="post-entry__title text-sm font-bold text-gray-800 leading-6 mb-2.5">
150
+ <Link to={article.url}>{article.name}</Link>
151
+ </h3>
152
+ <p className="text-sm text-gray-500 leading-6 mb-2.5">
153
+ {article.description}
154
+ </p>
155
+
156
+ {((article.stats?.likes ?? 0) > 0 || (article.stats?.comments ?? 0) > 0) && (
157
+ <div className="post-entry__icons flex items-center gap-2.5 text-gray-500 text-sm">
158
+ {(article.stats?.likes ?? 0) > 0 && (
159
+ <div className="flex items-center gap-1">
160
+ <i className="sicon-thumbs-up"></i>
161
+ {article.stats?.likes}
162
+ </div>
163
+ )}
164
+ {(article.stats?.comments ?? 0) > 0 && (
165
+ <div className="flex items-center gap-1">
166
+ <i className="sicon-chat"></i>
167
+ {article.stats?.comments}
168
+ </div>
169
+ )}
170
+ </div>
171
+ )}
172
+ </div>
173
+
174
+ {article.tags && <BlogTags tags={article.tags} />}
175
+ </div>
176
+ ))}
177
+ </div>
178
+ <HookSlot name="blog:items.end" />
179
+ </>
180
+ ) : (
181
+ !slides.length && (
182
+ <NoContent icon="sicon-inbox" message={t('pages.blog_categories.no_articles')} />
183
+ )
184
+ )}
185
+
186
+ <HookSlot name="blog:end" />
187
+ </div>
188
+ </div>
189
+ </div>
190
+ );
191
+ }
@@ -0,0 +1,168 @@
1
+ import { SallaComments } from '@salla.sa/twilight-components-react/comments';
2
+ import { HookSlot, useAsset, useDate } from '../../hooks';
3
+ import { useBlogLike } from '../../hooks/useBlogLike';
4
+ import { useComments } from '../../hooks/useComments';
5
+ import { useTranslation } from '../../providers/I18nProvider';
6
+ import type { BlogSinglePageProps } from './types';
7
+ import { Breadcrumb, Image, Link } from '../../components/common';
8
+ import { useTwilight } from '../../providers/twilight-context';
9
+
10
+ export function BlogSinglePage({ page, article, related = [] }: BlogSinglePageProps) {
11
+ const { t } = useTranslation();
12
+ const { store } = useTwilight();
13
+ const { cdn } = useAsset();
14
+ const { format: formatDate } = useDate();
15
+ const { active, count, toggle, loading } = useBlogLike(
16
+ Number(article.id) || 0,
17
+ article.stats?.likes ?? 0
18
+ );
19
+ const { commentsKey } = useComments();
20
+
21
+ return (
22
+ <div key={article.id} className="container mb-8 sm:mb-24">
23
+ <Breadcrumb page={page} />
24
+
25
+ <div className="md:flex items-start">
26
+ <div className="main-content blog-category md:w-96 flex-1 bg-white lg:p-8 rounded-md mb-10 md:mb-0">
27
+ <HookSlot name="blog:single.start" />
28
+
29
+ <h1 className="font-bold text-3xl mb-5 leading-10">{article.name}</h1>
30
+
31
+ <div className="mb-10 text-gray-500 rounded-md inline-flex text-sm rtl:space-x-reverse space-x-5">
32
+ <div className="flex items-center">
33
+ <i className="sicon-calendar-date rtl:ml-1 ltr:mr-1"></i>
34
+ <span>{formatDate(article.created_at)}</span>
35
+ </div>
36
+ <Link to={article.author?.url || '#'} className="flex items-center hover:text-dark">
37
+ <i className="sicon-user rtl:ml-1 ltr:mr-1"></i>
38
+ <span>{article.author?.name}</span>
39
+ </Link>
40
+ </div>
41
+
42
+ {article.image && (
43
+ <Image
44
+ src={article.image}
45
+ alt={article.name}
46
+ className="h-80 mb-10 w-full rounded-md"
47
+ aspectRatio="16/9"
48
+ priority
49
+ noWrapper
50
+ />
51
+ )}
52
+
53
+ {store.settings?.blog?.allow_likes_and_comments && (
54
+ <div className="flex items-center gap-2.5 text-gray-500 text-base mb-2">
55
+ <button
56
+ type="button"
57
+ id="blog-like"
58
+ data-blog-id={article.id}
59
+ className={`flex items-center gap-1 transition-colors ${active ? 'text-primary font-semibold liked' : ''}`}
60
+ onClick={toggle}
61
+ disabled={loading}
62
+ >
63
+ {loading ? (
64
+ <span className="loader loader--small"></span>
65
+ ) : (
66
+ <i className={`sicon-thumbs-up ${active ? 'text-primary' : ''}`}></i>
67
+ )}
68
+ <span>{count}</span>
69
+ </button>
70
+ {(article.stats?.comments ?? 0) > 0 && (
71
+ <div className="flex items-center gap-1">
72
+ <i className="sicon-chat text-primary"></i>
73
+ {article.stats?.comments}
74
+ </div>
75
+ )}
76
+ </div>
77
+ )}
78
+
79
+ <article
80
+ className="leading-7 text-sm mb-10"
81
+ dangerouslySetInnerHTML={{ __html: article.description?.replace(/&nbsp;/g, ' ') || '' }}
82
+ />
83
+
84
+ {article.tags && article.tags.length > 0 && (
85
+ <div className="p-4 flex flex-wrap items-center border-t border-gray-200">
86
+ {article.tags.map((tag) => (
87
+ <Link
88
+ key={tag.id}
89
+ to={tag.url}
90
+ className="py-2 px-4 rounded-large inline-flex items-center text-gray-500 hover:text-dark justify-center text-sm"
91
+ >
92
+ <i className="font-medium sicon-tag rtl:ml-1.5 ltr:mr-1.5"></i>
93
+ <span className="fix-align whitespace-nowrap">{tag.name}</span>
94
+ </Link>
95
+ ))}
96
+ </div>
97
+ )}
98
+
99
+ {store.settings?.blog?.allow_likes_and_comments && (
100
+ <SallaComments
101
+ key={commentsKey}
102
+ itemId={Number(article.id) || 0}
103
+ type={'blog' as any}
104
+ />
105
+ )}
106
+
107
+ <HookSlot name="blog:single.end" />
108
+ </div>
109
+
110
+ <aside
111
+ aria-label="Sidebar"
112
+ className="sticky shrink-0 top-24 md:w-96 rtl:lg:mr-8 ltr:lg:ml-8 pb-11"
113
+ >
114
+ {related.length > 0 && (
115
+ <>
116
+ <h2 className="font-bold mb-3">{t('pages.blog_articles.related')}</h2>
117
+
118
+ <HookSlot name="blog:single.related.start" />
119
+
120
+ {related.map((relatedArticle) => (
121
+ <div
122
+ key={relatedArticle.id}
123
+ className="mt-5 flex items-center bg-white p-5 rounded-md"
124
+ >
125
+ <div className="shrink-0">
126
+ <Link
127
+ to={relatedArticle.url}
128
+ className="bg-border-color h-24 w-24 rounded-md overflow-hidden flex items-center justify-center"
129
+ >
130
+ <span className="sr-only">{relatedArticle.name}</span>
131
+ {relatedArticle.image ? (
132
+ <Image
133
+ className="h-full w-full object-cover"
134
+ src={relatedArticle.image}
135
+ alt={relatedArticle.name}
136
+ aspectRatio="1/1"
137
+ />
138
+ ) : (
139
+ <Image
140
+ className="sm:h-full w-10 object-contain opacity-60"
141
+ src={cdn('/images/placeholder.png')}
142
+ alt="post image"
143
+ />
144
+ )}
145
+ </Link>
146
+ </div>
147
+ <div className="rtl:mr-3 ltr:ml-3">
148
+ <div className="flex items-center text-sm text-gray-500 mb-2">
149
+ <i className="sicon-calendar-date rtl:ml-1 ltr:mr-1 rtl:pl-px ltr:pr-px"></i>
150
+ <time>{formatDate(relatedArticle.created_at)}</time>
151
+ </div>
152
+ <h3 className="text-sm font-bold text-gray-800 leading-6">
153
+ <Link to={relatedArticle.url} className="hover:underline">
154
+ {relatedArticle.name}
155
+ </Link>
156
+ </h3>
157
+ </div>
158
+ </div>
159
+ ))}
160
+
161
+ <HookSlot name="blog:single.related.end" />
162
+ </>
163
+ )}
164
+ </aside>
165
+ </div>
166
+ </div>
167
+ );
168
+ }
@@ -0,0 +1,84 @@
1
+ import { useRef, useCallback } from 'react';
2
+ import { ItemsList } from '@salla.sa/twilight-components-react';
3
+ import { HookSlot } from '../../hooks';
4
+ import { useTranslation } from '../../providers/I18nProvider';
5
+ import { useLocation } from '../../providers/NavigationProvider';
6
+ import { Breadcrumb, NoContent } from '../../components/common';
7
+ import { blog } from '../../api/blog';
8
+ import { BlogArticleCard, BlogListLayout } from './components';
9
+ import type { BlogTagLoaderData, ArticleSummary } from './types';
10
+
11
+ const EMPTY_CATEGORIES: BlogTagLoaderData['categories'] = [];
12
+ const EMPTY_ARTICLES: ArticleSummary[] = [];
13
+
14
+ export function BlogTagPage({
15
+ page,
16
+ blogTag,
17
+ categories = EMPTY_CATEGORIES,
18
+ articles = EMPTY_ARTICLES,
19
+ cursor,
20
+ }: BlogTagLoaderData) {
21
+ const { t } = useTranslation();
22
+ const { pathname, searchStr } = useLocation();
23
+ const nextCursorRef = useRef<string | null>(cursor?.next ?? null);
24
+
25
+ const loader = useCallback(async () => {
26
+ const nextUrl = nextCursorRef.current;
27
+ const cursorMatch = nextUrl?.match(/[?&]cursor=([^&]+)/);
28
+ const cursorValue = cursorMatch ? decodeURIComponent(cursorMatch[1]) : (nextUrl ?? undefined);
29
+
30
+ const result = await blog.articles.list({ tag: String(blogTag.id), cursor: cursorValue });
31
+ nextCursorRef.current = result.cursor?.next ?? null;
32
+
33
+ return {
34
+ items: result.data,
35
+ next: result.cursor?.next ?? null,
36
+ };
37
+ }, [blogTag.id]);
38
+
39
+ if (articles.length === 0) {
40
+ return (
41
+ <div className="container mb-8 sm:mb-24">
42
+ <Breadcrumb page={page} />
43
+ <BlogListLayout categories={categories}>
44
+ <HookSlot name="blog:tag.start" />
45
+ <NoContent icon="sicon-inbox" message={t('pages.blog_categories.no_articles')} />
46
+ <HookSlot name="blog:tag.end" />
47
+ </BlogListLayout>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ return (
53
+ <div className="container mb-8 sm:mb-24">
54
+ <Breadcrumb page={page} />
55
+
56
+ <BlogListLayout categories={categories}>
57
+ <HookSlot name="blog:tag.start" />
58
+
59
+ <div className="mb-6">
60
+ <h1 className="font-bold text-xl">{blogTag.name}</h1>
61
+ </div>
62
+
63
+ <ItemsList<ArticleSummary>
64
+ items={articles}
65
+ loader={cursor?.next ? loader : undefined}
66
+ mode={cursor?.next ? 'infinite' : 'static'}
67
+ pageUrl={pathname + searchStr}
68
+ className="s-blog-list"
69
+ itemsClassName="grid md:grid-cols-2 gap-2.5 md:gap-8"
70
+ empty={<NoContent icon="sicon-inbox" message={t('pages.blog_categories.no_articles')} />}
71
+ t={(key, fallback) => t(key, fallback || key)}
72
+ >
73
+ {(items) =>
74
+ items.map((article) => (
75
+ <BlogArticleCard key={article.id} article={article} showAuthor />
76
+ ))
77
+ }
78
+ </ItemsList>
79
+
80
+ <HookSlot name="blog:tag.end" />
81
+ </BlogListLayout>
82
+ </div>
83
+ );
84
+ }
@@ -0,0 +1,54 @@
1
+ import { useState } from 'react';
2
+ import { Link } from '../../components/common';
3
+ import { useTranslation } from '../../providers/I18nProvider';
4
+ import type { BlogTag } from './types';
5
+
6
+ /** Tags shown on a blog card before the rest fold behind a "+N" toggle. */
7
+ export const BLOG_CARD_VISIBLE_TAGS = 3;
8
+
9
+ interface BlogTagsProps {
10
+ tags: BlogTag[];
11
+ /** How many tags to show before folding the rest. */
12
+ visible?: number;
13
+ }
14
+
15
+ /**
16
+ * The tag row of a blog card. Articles can carry dozens of tags, which used to wrap into
17
+ * a wall of links and stretch the card; show the first few as compact chips and fold the
18
+ * rest behind a "+N" toggle that expands them in place (and "Show less" to fold back).
19
+ * Nothing folds when the tags fit.
20
+ */
21
+ export function BlogTags({ tags, visible = BLOG_CARD_VISIBLE_TAGS }: BlogTagsProps) {
22
+ const { t } = useTranslation();
23
+ const [expanded, setExpanded] = useState(false);
24
+
25
+ if (tags.length === 0) return null;
26
+
27
+ const hiddenCount = Math.max(0, tags.length - visible);
28
+ const shown = expanded ? tags : tags.slice(0, visible);
29
+
30
+ return (
31
+ <div className="s-blog-tags p-4 flex flex-wrap items-center gap-2 border-t border-gray-200">
32
+ {shown.map((tag) => (
33
+ <Link
34
+ key={tag.id}
35
+ to={tag.url}
36
+ className="s-blog-tags-item py-1 px-3 rounded-full border border-gray-200 inline-flex items-center max-w-full text-gray-500 hover:text-dark hover:border-gray-300 text-xs"
37
+ >
38
+ <i className="font-medium sicon-tag rtl:ml-1.5 ltr:mr-1.5"></i>
39
+ <span className="fix-align truncate">{tag.name}</span>
40
+ </Link>
41
+ ))}
42
+ {hiddenCount > 0 && (
43
+ <button
44
+ type="button"
45
+ className="s-blog-tags-more py-1 px-3 rounded-full text-xs font-medium text-primary hover:opacity-80"
46
+ aria-expanded={expanded}
47
+ onClick={() => setExpanded((open) => !open)}
48
+ >
49
+ {expanded ? t('common.elements.show_less', 'Show less') : `+${hiddenCount}`}
50
+ </button>
51
+ )}
52
+ </div>
53
+ );
54
+ }