@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,27 @@
1
+ import type { WalletPageProps } from './types';
2
+ import type { LoaderContext } from '../../types';
3
+ import { wallet } from '../../../api/wallet';
4
+ import { getTwilightContext } from '../../../twilight/context';
5
+ import { orUnauthorized, unauthorized } from '../../../providers/NavigationProvider';
6
+ import { getAuthToken } from '../../../api/client';
7
+ import { RouteId } from '../../../router/route-ids';
8
+
9
+ export async function walletLoader(ctx: LoaderContext): Promise<WalletPageProps> {
10
+ if (!getAuthToken()) throw unauthorized();
11
+ const pageNum = Number(ctx.search?.page) || 1;
12
+ const data = await orUnauthorized(wallet.list({ page: pageNum }));
13
+
14
+ const i18n = getTwilightContext().i18n;
15
+ return {
16
+ page: {
17
+ title: i18n.t('common.titles.wallet') || 'Wallet',
18
+ slug: RouteId.CUSTOMER_WALLET,
19
+ },
20
+ query: { page: pageNum },
21
+ transactions: data.items,
22
+ balance: data.balance,
23
+ pagination: {
24
+ next: data.next,
25
+ },
26
+ };
27
+ }
@@ -0,0 +1,11 @@
1
+ import type { Page } from '../../../types';
2
+ import type { RoutePagination, PageQuery } from '../../types';
3
+ import type { TransactionItem } from '../../../api/wallet';
4
+
5
+ export interface WalletPageProps {
6
+ page: Page;
7
+ query: PageQuery;
8
+ transactions: TransactionItem[];
9
+ balance: number;
10
+ pagination: RoutePagination;
11
+ }
@@ -0,0 +1,314 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';
2
+ import { useQueryClient } from '@tanstack/react-query';
3
+ import { ItemsList } from '@salla.sa/twilight-components-react';
4
+ import { SallaAddProductButton } from '@salla.sa/twilight-components-react/add-product-button';
5
+ import { SallaButton } from '@salla.sa/twilight-components-react/button';
6
+ import { wishlist } from '../../../api/wishlist';
7
+ import type { Product } from '../../../types';
8
+ import { HookSlot, useWishlist, useMoney } from '../../../hooks';
9
+ import { useTranslation } from '../../../providers/I18nProvider';
10
+ import type { WishlistPageProps } from './types';
11
+ import { WishlistShareBar, useWishlistShare } from './WishlistShare';
12
+ import { NoContent, Image } from '../../../components/common';
13
+ import { useLocation } from '../../../providers/NavigationProvider';
14
+ import { onSallaEvent } from '../../../utils/sdk-helpers';
15
+
16
+ interface WishlistCardSelection {
17
+ /** "Share specific items" mode: the card shows a checkbox and toggles on click. */
18
+ selecting: boolean;
19
+ selected: boolean;
20
+ onToggle: (id: number) => void;
21
+ }
22
+
23
+ // The loaded component copies host attributes (not properties) onto its inner salla-button.
24
+ const setAddButtonAttributes = (el: HTMLElement | null) => {
25
+ el?.setAttribute('loader-position', 'center');
26
+ el?.setAttribute('fill', 'outline');
27
+ };
28
+
29
+ const REMOVING_MS = 300;
30
+
31
+ const REMOVING_STYLE: CSSProperties = {
32
+ opacity: 0,
33
+ transform: 'scale(0.95)',
34
+ transition: `opacity ${REMOVING_MS}ms ease-in-out, transform ${REMOVING_MS}ms ease-in-out`,
35
+ };
36
+
37
+ function WishlistCard({
38
+ product: prod,
39
+ selection,
40
+ removing,
41
+ onRemoved,
42
+ }: {
43
+ product: Product;
44
+ selection?: WishlistCardSelection;
45
+ /** Fades the card out, then calls `onRemoved` once the fade ends. */
46
+ removing?: boolean;
47
+ onRemoved?: (id: number) => void;
48
+ }) {
49
+ const { remove } = useWishlist();
50
+ const { format } = useMoney();
51
+
52
+ // Drops the card even when `transitionend` never fires (hidden tab, overridden styles).
53
+ useEffect(() => {
54
+ if (!removing) return;
55
+ const timer = setTimeout(() => onRemoved?.(Number(prod.id)), REMOVING_MS + 50);
56
+ return () => clearTimeout(timer);
57
+ }, [removing, onRemoved, prod.id]);
58
+
59
+ const handleRemove = () => {
60
+ if (prod.id) {
61
+ remove(Number(prod.id));
62
+ }
63
+ };
64
+
65
+ const displayPrice = prod.sale_price ?? prod.price;
66
+ const regularPrice = prod.regular_price;
67
+
68
+ return (
69
+ <div
70
+ id={`wishlist-product-${prod.id}`}
71
+ className={`product-entry product-entry--wishlist${selection?.selected ? ' is-selected' : ''}`}
72
+ style={removing ? REMOVING_STYLE : undefined}
73
+ onTransitionEnd={(event) => {
74
+ if (removing && event.target === event.currentTarget && event.propertyName === 'opacity') {
75
+ onRemoved?.(Number(prod.id));
76
+ }
77
+ }}
78
+ >
79
+ {selection?.selecting && (
80
+ <input
81
+ type="checkbox"
82
+ className="s-wishlist-select shrink-0 rtl:ml-4 ltr:mr-4"
83
+ aria-label={prod.name}
84
+ checked={selection.selected}
85
+ onChange={() => selection.onToggle(Number(prod.id))}
86
+ />
87
+ )}
88
+ <div className="flex items-center mb-4 sm:mb-0">
89
+ <a href={prod.url} className="product-entry__image">
90
+ <Image
91
+ className="object-cover w-full h-full"
92
+ src={prod.image?.url}
93
+ alt={prod.image?.alt ?? prod.name}
94
+ aspectRatio="1/1"
95
+ />
96
+ </a>
97
+ <div className="flex-1 rtl:pr-5 ltr:pl-5">
98
+ <h3 className="text-sm text-gray-800 leading-6 mb-1.5 rtl:pl-5 ltr:pr-5 rtl:md:pl-8 ltr:md:pr-8 line-clamp-1">
99
+ <a href={prod.url}>{prod.name}</a>
100
+ </h3>
101
+ <div className="w-full center-between">
102
+ {prod.is_on_sale && regularPrice ? (
103
+ <div className="space-x-1 rtl:space-x-reverse">
104
+ <h4 className="inline-block text-sm font-bold text-red-400">
105
+ {format(displayPrice)}
106
+ </h4>
107
+ <span className="text-sm text-gray-500 line-through">{format(regularPrice)}</span>
108
+ </div>
109
+ ) : (
110
+ <h4 className="text-sm font-bold">{format(displayPrice)}</h4>
111
+ )}
112
+ </div>
113
+ </div>
114
+ </div>
115
+ <div className="flex items-center space-x-4 rtl:space-x-reverse">
116
+ <SallaAddProductButton
117
+ ref={setAddButtonAttributes}
118
+ productStatus={prod.status}
119
+ productId={prod.id}
120
+ productType={prod.type}
121
+ loaderPosition="center"
122
+ fill="outline"
123
+ className="flex-grow w-full sm:grow-0 md:w-40"
124
+ />
125
+ <SallaButton
126
+ loaderPosition="center"
127
+ shape="icon"
128
+ size="small"
129
+ color="danger"
130
+ className="btn--delete"
131
+ onClick={handleRemove}
132
+ >
133
+ <i className="sicon-cancel" />
134
+ </SallaButton>
135
+ </div>
136
+ </div>
137
+ );
138
+ }
139
+
140
+ /** The loaded wishlist pages, reloaded from the server once every loaded card is removed (like Twig). */
141
+ function useWishlistItems(
142
+ { query, products, pagination }: WishlistPageProps,
143
+ onRemoved: (id: number) => void
144
+ ) {
145
+ const queryClient = useQueryClient();
146
+ // Track the next page to load (starts at page 2 since page 1 is SSR)
147
+ const nextPageRef = useRef(query.page + 1);
148
+ const [removedIds, setRemovedIds] = useState<ReadonlySet<number>>(() => new Set());
149
+ const [removingIds, setRemovingIds] = useState<ReadonlySet<number>>(() => new Set());
150
+ const [reloaded, setReloaded] = useState<{
151
+ items: Product[];
152
+ next: string | null;
153
+ generation: number;
154
+ } | null>(null);
155
+ const loadedRef = useRef({
156
+ ids: products.map((prod) => Number(prod.id)),
157
+ hasMore: !!pagination?.next,
158
+ });
159
+
160
+ // A `?page=` navigation keeps this instance, so drop what the previous page removed or reloaded.
161
+ const [loadedProducts, setLoadedProducts] = useState(products);
162
+ if (loadedProducts !== products) {
163
+ setLoadedProducts(products);
164
+ setRemovedIds(new Set());
165
+ setRemovingIds(new Set());
166
+ setReloaded(null);
167
+ }
168
+
169
+ useEffect(() => {
170
+ nextPageRef.current = query.page + 1;
171
+ loadedRef.current = {
172
+ ids: products.map((prod) => Number(prod.id)),
173
+ hasMore: !!pagination?.next,
174
+ };
175
+ }, [products, query.page, pagination?.next]);
176
+
177
+ useEffect(() => {
178
+ const handleRemoved = (_response: unknown, id: unknown) => {
179
+ const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
180
+ (reduceMotion ? setRemovedIds : setRemovingIds)((current) =>
181
+ new Set(current).add(Number(id))
182
+ );
183
+ onRemoved(Number(id));
184
+ };
185
+
186
+ return onSallaEvent('wishlist::removed', handleRemoved);
187
+ }, [onRemoved]);
188
+
189
+ useEffect(() => {
190
+ const { ids, hasMore } = loadedRef.current;
191
+ if (!ids.length || !ids.every((id) => removedIds.has(id))) return;
192
+
193
+ const reset = (items: Product[], next: string | null) => {
194
+ nextPageRef.current = query.page + 1;
195
+ loadedRef.current = { ids: items.map((prod) => Number(prod.id)), hasMore: !!next };
196
+ setRemovedIds(new Set());
197
+ setRemovingIds(new Set());
198
+ setReloaded((current) => ({ items, next, generation: (current?.generation ?? 0) + 1 }));
199
+ };
200
+
201
+ if (!hasMore) {
202
+ reset([], null);
203
+ return;
204
+ }
205
+
206
+ let cancelled = false;
207
+ queryClient
208
+ .fetchQuery({ ...wishlist.queries.list({ page: query.page }), staleTime: 0 })
209
+ .then((result) => {
210
+ if (!cancelled) reset(result.items, result.next);
211
+ })
212
+ // A failed reload keeps the list as it is; later pages still load on scroll.
213
+ .catch(() => undefined);
214
+ return () => {
215
+ cancelled = true;
216
+ };
217
+ }, [removedIds, query.page, products, queryClient]);
218
+
219
+ const loader = async () => {
220
+ const pageToLoad = nextPageRef.current;
221
+ const result = await wishlist.list({ page: pageToLoad });
222
+
223
+ nextPageRef.current = pageToLoad + 1;
224
+ loadedRef.current = {
225
+ ids: [...loadedRef.current.ids, ...result.items.map((prod) => Number(prod.id))],
226
+ hasMore: !!result.next,
227
+ };
228
+
229
+ return {
230
+ items: result.items,
231
+ next: result.next,
232
+ };
233
+ };
234
+
235
+ const finishRemoving = useCallback((id: number) => {
236
+ setRemovedIds((current) => (current.has(id) ? current : new Set(current).add(id)));
237
+ }, []);
238
+
239
+ return {
240
+ items: reloaded ? reloaded.items : products,
241
+ next: reloaded ? reloaded.next : pagination?.next,
242
+ generation: reloaded?.generation ?? 0,
243
+ removedIds,
244
+ removingIds,
245
+ finishRemoving,
246
+ loader,
247
+ };
248
+ }
249
+
250
+ export function WishlistPage(props: WishlistPageProps) {
251
+ const { t } = useTranslation();
252
+ const { pathname, searchStr } = useLocation();
253
+ const shareState = useWishlistShare();
254
+ const selectedIdSet = useMemo(() => new Set(shareState.selectedIds), [shareState.selectedIds]);
255
+ const { items, next, generation, removedIds, removingIds, finishRemoving, loader } =
256
+ useWishlistItems(props, shareState.deselect);
257
+ const hasRemaining = items.some((prod) => !removedIds.has(Number(prod.id)));
258
+
259
+ return (
260
+ <div className="space-y-5" id="wishlist">
261
+ {/* The SDK unconditionally auto-renders its own `<salla-wishlist-actions>` into this
262
+ slot on every `customer.wishlist` page (tools/twilight's init-static-sections.js),
263
+ but that widget only works alongside a sibling `<salla-products-list>` web
264
+ component -- this page renders its products via the React-native `ItemsList`
265
+ instead, so the SDK's widget never finds one and sits permanently disabled.
266
+ `WishlistShareBar` below is this page's own, fully working replacement for the
267
+ exact same "Share" feature. The slot itself stays (removing it would also drop
268
+ whatever themes/apps register here via the hook registry, not just this widget);
269
+ `#wishlist > salla-wishlist-actions` (user-pages.scss) hides just the dead
270
+ SDK widget, which the SDK always inserts as this element's first child. */}
271
+ <HookSlot name="customer:wishlist.items.start" />
272
+ <WishlistShareBar state={shareState} hasProducts={hasRemaining || !!next} />
273
+ <ItemsList<Product>
274
+ key={generation}
275
+ items={items}
276
+ loader={loader}
277
+ mode={next ? 'infinite' : 'static'}
278
+ pageUrl={pathname + searchStr}
279
+ className="s-products-list"
280
+ itemsClassName="s-products-list-wrapper s-products-list-row-cards"
281
+ empty={
282
+ <div className="text-center py-16">
283
+ <NoContent
284
+ icon="sicon-heart"
285
+ message={t('common.errors.no_wishlist', "You don't have any wishes")}
286
+ actionLabel={t('pages.offer.continue_shopping', 'Continue shopping')}
287
+ actionUrl="/"
288
+ />
289
+ </div>
290
+ }
291
+ t={(key, fallback) => t(key, fallback || key)}
292
+ >
293
+ {(items) => {
294
+ const remaining = items.filter((item) => !removedIds.has(Number(item.id)));
295
+ shareState.loadedIdsRef.current = remaining.map((item) => Number(item.id));
296
+ return remaining.map((prod) => (
297
+ <WishlistCard
298
+ key={prod.id}
299
+ product={prod}
300
+ removing={removingIds.has(Number(prod.id))}
301
+ onRemoved={finishRemoving}
302
+ selection={{
303
+ selecting: shareState.selecting,
304
+ selected: selectedIdSet.has(Number(prod.id)),
305
+ onToggle: shareState.toggleSelected,
306
+ }}
307
+ />
308
+ ));
309
+ }}
310
+ </ItemsList>
311
+ <HookSlot name="customer:wishlist.items.end" />
312
+ </div>
313
+ );
314
+ }
@@ -0,0 +1,248 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { SallaButton } from '@salla.sa/twilight-components-react/button';
3
+ // The eager (non-deferred) export: the share dialog is opened imperatively through a ref, and the
4
+ // deferred wrapper doesn't attach it until it has hydrated — a click before that would do nothing.
5
+ import { SallaSocialShareCore as SallaSocialShare } from '@salla.sa/twilight-components-react/social-share';
6
+ import { useTranslation } from '../../../providers/I18nProvider';
7
+
8
+ type ShareResponse = { data?: { url?: string }; url?: string } | undefined;
9
+ type SocialShareElement = HTMLElement & {
10
+ url?: string;
11
+ refresh?: () => Promise<void>;
12
+ open?: () => Promise<void>;
13
+ close?: () => Promise<void>;
14
+ };
15
+
16
+ /**
17
+ * Share state for the wishlist page: the "Share" dropdown (share the whole list / share
18
+ * specific items), the selection used by "specific items", and the share request itself.
19
+ * Mirrors what the SDK's `<salla-wishlist-actions>` does, but drives the page's own React
20
+ * list — that component needs a sibling `<salla-products-list>`, which this page doesn't render.
21
+ */
22
+ export function useWishlistShare() {
23
+ const [menuOpen, setMenuOpen] = useState(false);
24
+ const [selecting, setSelecting] = useState(false);
25
+ const [selectedIds, setSelectedIds] = useState<number[]>([]);
26
+ const [sharing, setSharing] = useState(false);
27
+ const [shareUrl, setShareUrl] = useState('');
28
+ /** Ids of every product currently rendered (later cursor pages included), for "select all". */
29
+ const loadedIdsRef = useRef<number[]>([]);
30
+ const socialShareRef = useRef<SocialShareElement | null>(null);
31
+
32
+ const share = useCallback(async (ids: number[], selectAll: boolean) => {
33
+ if (!window.salla?.wishlist?.share) return;
34
+ setSharing(true);
35
+ try {
36
+ const response = (await window.salla.wishlist.share(ids, selectAll)) as ShareResponse;
37
+ const url = response?.data?.url || response?.url || '';
38
+ if (!url) return;
39
+ setShareUrl(url);
40
+ const element = socialShareRef.current;
41
+ if (element) {
42
+ element.url = url;
43
+ await element.refresh?.();
44
+ await element.open?.();
45
+ }
46
+ } catch {
47
+ // The SDK reports a failed share itself (notification); nothing to recover here.
48
+ } finally {
49
+ setSharing(false);
50
+ }
51
+ }, []);
52
+
53
+ const shareAll = useCallback(() => {
54
+ setMenuOpen(false);
55
+ void share([], true);
56
+ }, [share]);
57
+
58
+ const shareSelected = useCallback(() => {
59
+ if (selectedIds.length) void share(selectedIds, false);
60
+ }, [share, selectedIds]);
61
+
62
+ const startSelecting = useCallback(() => {
63
+ setMenuOpen(false);
64
+ setSelecting(true);
65
+ }, []);
66
+
67
+ const cancelSelecting = useCallback(() => {
68
+ setSelecting(false);
69
+ setSelectedIds([]);
70
+ setShareUrl('');
71
+ void socialShareRef.current?.close?.();
72
+ }, []);
73
+
74
+ const toggleSelected = useCallback((id: number) => {
75
+ setShareUrl('');
76
+ setSelectedIds((current) =>
77
+ current.includes(id) ? current.filter((existing) => existing !== id) : [...current, id]
78
+ );
79
+ }, []);
80
+
81
+ const selectedIdsRef = useRef(selectedIds);
82
+ useEffect(() => {
83
+ selectedIdsRef.current = selectedIds;
84
+ }, [selectedIds]);
85
+
86
+ const deselect = useCallback((id: number) => {
87
+ if (!selectedIdsRef.current.includes(id)) return;
88
+ // A link built for the old selection would still offer the removed product.
89
+ setShareUrl('');
90
+ const element = socialShareRef.current;
91
+ if (element) {
92
+ element.url = undefined;
93
+ void element.close?.();
94
+ }
95
+ setSelectedIds((current) => current.filter((existing) => existing !== id));
96
+ }, []);
97
+
98
+ const toggleAll = useCallback(() => {
99
+ setShareUrl('');
100
+ setSelectedIds((current) =>
101
+ current.length && current.length === loadedIdsRef.current.length
102
+ ? []
103
+ : [...loadedIdsRef.current]
104
+ );
105
+ }, []);
106
+
107
+ return {
108
+ menuOpen,
109
+ setMenuOpen,
110
+ selecting,
111
+ selectedIds,
112
+ sharing,
113
+ shareUrl,
114
+ loadedIdsRef,
115
+ socialShareRef,
116
+ shareAll,
117
+ shareSelected,
118
+ startSelecting,
119
+ cancelSelecting,
120
+ toggleSelected,
121
+ toggleAll,
122
+ deselect,
123
+ };
124
+ }
125
+
126
+ export type WishlistShareState = ReturnType<typeof useWishlistShare>;
127
+
128
+ /** The bar above the wishlist: the "Share" dropdown, or — while picking products — the selection controls. */
129
+ export function WishlistShareBar({
130
+ state,
131
+ hasProducts,
132
+ }: {
133
+ state: WishlistShareState;
134
+ hasProducts: boolean;
135
+ }) {
136
+ const { t } = useTranslation();
137
+ const rootRef = useRef<HTMLDivElement>(null);
138
+ const { menuOpen, setMenuOpen } = state;
139
+
140
+ // Close the dropdown on an outside click or Escape.
141
+ useEffect(() => {
142
+ if (!menuOpen) return;
143
+ const onPointerDown = (event: MouseEvent) => {
144
+ if (!rootRef.current?.contains(event.target as Node)) setMenuOpen(false);
145
+ };
146
+ const onKeyDown = (event: KeyboardEvent) => {
147
+ if (event.key === 'Escape') setMenuOpen(false);
148
+ };
149
+ document.addEventListener('mousedown', onPointerDown);
150
+ document.addEventListener('keydown', onKeyDown);
151
+ return () => {
152
+ document.removeEventListener('mousedown', onPointerDown);
153
+ document.removeEventListener('keydown', onKeyDown);
154
+ };
155
+ }, [menuOpen, setMenuOpen]);
156
+
157
+ const selectedCount = state.selectedIds.length;
158
+ const allSelected = selectedCount > 0 && selectedCount === state.loadedIdsRef.current.length;
159
+
160
+ return (
161
+ <div ref={rootRef} className="s-wishlist-actions">
162
+ <div className="s-wishlist-actions-bar flex items-center justify-end gap-3 mb-3">
163
+ {state.selecting ? (
164
+ <div className="s-wishlist-actions-selection flex items-center gap-3">
165
+ <label className="s-wishlist-actions-select-all inline-flex items-center gap-2 text-sm">
166
+ <input
167
+ type="checkbox"
168
+ checked={allSelected}
169
+ ref={(input) => {
170
+ if (input) input.indeterminate = selectedCount > 0 && !allSelected;
171
+ }}
172
+ onChange={state.toggleAll}
173
+ />
174
+ {t('pages.wishlist.select_all', 'Select all')}
175
+ </label>
176
+ <SallaButton
177
+ color="primary"
178
+ disabled={selectedCount === 0 || state.sharing}
179
+ onClick={state.shareSelected}
180
+ >
181
+ {t('pages.wishlist.share', 'Share')}
182
+ {selectedCount > 0 ? ` (${selectedCount})` : ''}
183
+ </SallaButton>
184
+ <SallaButton fill="outline" color="light" onClick={state.cancelSelecting}>
185
+ {t('common.elements.cancel', 'Cancel')}
186
+ </SallaButton>
187
+ </div>
188
+ ) : (
189
+ <div className="s-wishlist-actions-dropdown relative">
190
+ <SallaButton
191
+ color="primary"
192
+ fill="outline"
193
+ disabled={!hasProducts || state.sharing}
194
+ aria-haspopup="true"
195
+ aria-expanded={menuOpen}
196
+ onClick={() => setMenuOpen(!menuOpen)}
197
+ >
198
+ <span className="flex items-center justify-center gap-2">
199
+ <i className="sicon-share-alt" aria-hidden="true" />
200
+ {t('pages.wishlist.share', 'Share')}
201
+ <i className="sicon-keyboard_arrow_down" aria-hidden="true" />
202
+ </span>
203
+ </SallaButton>
204
+ {menuOpen && (
205
+ <ul
206
+ role="menu"
207
+ className="s-wishlist-actions-menu absolute z-10 mt-2 min-w-48 rounded-md bg-white py-2 shadow-default rtl:right-0 ltr:left-0"
208
+ >
209
+ <li role="none">
210
+ <button
211
+ type="button"
212
+ role="menuitem"
213
+ className="s-wishlist-actions-item block w-full px-4 py-2 text-start text-sm text-gray-700 hover:bg-gray-50"
214
+ onClick={state.shareAll}
215
+ >
216
+ {t('pages.wishlist.share_all', 'Share the list')}
217
+ </button>
218
+ </li>
219
+ <li role="none">
220
+ <button
221
+ type="button"
222
+ role="menuitem"
223
+ className="s-wishlist-actions-item block w-full px-4 py-2 text-start text-sm text-gray-700 hover:bg-gray-50"
224
+ onClick={state.startSelecting}
225
+ >
226
+ {t('pages.wishlist.share_specific', 'Share specific items')}
227
+ </button>
228
+ </li>
229
+ </ul>
230
+ )}
231
+ </div>
232
+ )}
233
+ </div>
234
+ {/* Same setup as the SDK's <salla-wishlist-actions>: the centered modal variant, opened by the
235
+ dropdown rather than by the component's own round share button, so the default
236
+ `widget` slot (that button) is replaced by an empty anchor. */}
237
+ <SallaSocialShare
238
+ ref={state.socialShareRef as never}
239
+ url={state.shareUrl || undefined}
240
+ mode="modal"
241
+ modalTitle={t('pages.wishlist.share_list_title', 'Share wishlist')}
242
+ className="s-wishlist-actions-social"
243
+ >
244
+ <span slot="widget" className="s-wishlist-actions-social-anchor" aria-hidden="true" />
245
+ </SallaSocialShare>
246
+ </div>
247
+ );
248
+ }
@@ -0,0 +1,40 @@
1
+ import type { HeadDescriptor } from '../../../utils/head';
2
+ import { buildHreflangAlternates } from '../../../utils/baseHead';
3
+ import { lazy } from 'react';
4
+ import { wishlistLoader } from './loader';
5
+ import type { WishlistPageProps } from './types';
6
+ import type { LoaderContext } from '../../types';
7
+ import type { TwilightContext } from '../../../twilight/context';
8
+ import { RouteId } from '../../../router/route-ids';
9
+
10
+ const LazyWishlistPage = lazy(() =>
11
+ import('./WishlistPage').then((m) => ({ default: m.WishlistPage }))
12
+ );
13
+
14
+ export const WishlistPage = LazyWishlistPage;
15
+ export { wishlistLoader } from './loader';
16
+ export type { WishlistPageProps } from './types';
17
+
18
+ export const Wishlist = {
19
+ id: RouteId.CUSTOMER_WISHLIST,
20
+ loader: async (
21
+ ctx: LoaderContext,
22
+ extend?: (
23
+ data: WishlistPageProps,
24
+ ctx: { params: Record<string, never> }
25
+ ) => Promise<Record<string, unknown>> | Record<string, unknown>
26
+ ): Promise<WishlistPageProps> => {
27
+ const data = await wishlistLoader(ctx);
28
+ if (extend) {
29
+ const extra = await extend(data, { params: {} });
30
+ return { ...data, ...extra } as WishlistPageProps;
31
+ }
32
+ return data;
33
+ },
34
+ head: (ctx: TwilightContext, data: WishlistPageProps): HeadDescriptor => ({
35
+ title: data.page.title,
36
+ description: data.page.description,
37
+ alternateLanguages: buildHreflangAlternates(ctx.settings, ctx.location.pathname),
38
+ }),
39
+ Component: LazyWishlistPage,
40
+ } as const;
@@ -0,0 +1,26 @@
1
+ import type { WishlistPageProps } from './types';
2
+ import type { LoaderContext } from '../../types';
3
+ import { wishlist } from '../../../api/wishlist';
4
+ import { getTwilightContext } from '../../../twilight/context';
5
+ import { orUnauthorized, unauthorized } from '../../../providers/NavigationProvider';
6
+ import { getAuthToken } from '../../../api/client';
7
+ import { RouteId } from '../../../router/route-ids';
8
+
9
+ export async function wishlistLoader(ctx: LoaderContext): Promise<WishlistPageProps> {
10
+ if (!getAuthToken()) throw unauthorized();
11
+ const pageNum = Number(ctx.search?.page) || 1;
12
+ const data = await orUnauthorized(wishlist.list({ page: pageNum }));
13
+
14
+ const i18n = getTwilightContext().i18n;
15
+ return {
16
+ page: {
17
+ title: i18n.t('common.titles.wishlist') || 'Wishlist',
18
+ slug: RouteId.CUSTOMER_WISHLIST,
19
+ },
20
+ query: { page: pageNum },
21
+ products: data.items,
22
+ pagination: {
23
+ next: data.next,
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,9 @@
1
+ import type { Page, Product } from '../../../types';
2
+ import type { RoutePagination, PageQuery } from '../../types';
3
+
4
+ export interface WishlistPageProps {
5
+ page: Page;
6
+ query: PageQuery;
7
+ products: Product[];
8
+ pagination: RoutePagination;
9
+ }