@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,424 @@
1
+ import type { HeadDescriptor, HeadResult } from '../../utils/head';
2
+ import { mergeHead, resolveHead } from '../../utils/head';
3
+ import { buildBaseHead } from '../../utils/baseHead';
4
+ import { FALLBACK_LOCALE } from '../../providers/locale-constants';
5
+ import { getTwilightContext } from '../../twilight/context';
6
+ import type { TwilightContextMatch } from '../../twilight/ensure-context';
7
+ import { ensureTwilightContextFromMatches } from '../../twilight/ensure-context';
8
+ import type { StoreContext } from '../../api/store';
9
+ import type { AppSnippet } from '../../api/apps';
10
+ import { SettingsError } from '../../utils/errors';
11
+ import { renderAppScopeInstallScript } from '../../utils/app-scope';
12
+ import { renderThemeHookSlotsScript } from '../../utils/theme-hook-slots';
13
+ import { escapeForInlineScript } from '../../utils/inline-script';
14
+ import { readEnv, renderRuntimeEnvScript } from '../../utils/runtime-env';
15
+
16
+ const SALLA_ICONS_CSS_URL = 'https://cdn.assets.salla.network/static/fonts/sallaicons.css?v=2.0.5';
17
+ const LIT_VERSION = '3.3.3';
18
+ const LIT_CDN_BASE = `https://cdn.assets.salla.network/static/lit/${LIT_VERSION}`;
19
+
20
+ /** Bare-specifier map for the lit build the engine's `<salla-*>` web components resolve against. */
21
+ const LIT_IMPORTS: Record<string, string> = {
22
+ lit: `${LIT_CDN_BASE}/lit.js`,
23
+ 'lit/': `${LIT_CDN_BASE}/`,
24
+ 'lit-html': `${LIT_CDN_BASE}/lit-html/lit-html.js`,
25
+ 'lit-html/': `${LIT_CDN_BASE}/lit-html/`,
26
+ 'lit-element': `${LIT_CDN_BASE}/lit-element/lit-element.js`,
27
+ 'lit-element/': `${LIT_CDN_BASE}/lit-element/`,
28
+ '@lit/reactive-element': `${LIT_CDN_BASE}/reactive-element/reactive-element.js`,
29
+ '@lit/reactive-element/': `${LIT_CDN_BASE}/reactive-element/`,
30
+ };
31
+
32
+ export const TICKETING_SCRIPT_URL =
33
+ 'https://cdn.assets.salla.network/latest/@salla.sa/ui-store-ticketing-system/ticketing-system.es.js';
34
+
35
+ const TWILIGHT_RELEASE_CDN = 'https://cdn.salla.network/js/twilight';
36
+ const TWILIGHT_DEV_CDN = 'https://cdn.assets.salla.network/dev/@salla.sa/twilight';
37
+ // The oldest Twilight release this engine works with. Raise it whenever the engine
38
+ // starts relying on something newer; the reason lives in that commit. Today's floor:
39
+ // components ship their own CSS, which themes no longer compile (SallaApp/twilight#2107).
40
+ const MIN_TWILIGHT_VERSION = '2.14.538';
41
+ // Last resort when no override and no platform pin, and the substitute for a pin below the floor.
42
+ const DEFAULT_TWILIGHT_VERSION = '2.14.584';
43
+ const releaseUrl = (version: string): string =>
44
+ `${TWILIGHT_RELEASE_CDN}/${version}/twilight.esm.js`;
45
+ // The head is built more than once per request (early hints, then the page); say it once.
46
+ const warnedPins = new Set<string>();
47
+
48
+ /** `x.y.z` release pins only; dev shas and other strings are loaded as given. */
49
+ function belowMinimum(version: string): boolean {
50
+ return (
51
+ /^\d+\.\d+\.\d+$/.test(version) &&
52
+ version.localeCompare(MIN_TWILIGHT_VERSION, undefined, { numeric: true }) < 0
53
+ );
54
+ }
55
+
56
+ /** The `x.y.z` release segment of a release-CDN `.../twilight.esm.js` URL, if present. */
57
+ function releaseVersionInUrl(url: string): string | null {
58
+ const match = url.match(/\/(\d+\.\d+\.\d+)\/twilight\.esm\.js(?:[?#]|$)/);
59
+ return match ? match[1] : null;
60
+ }
61
+
62
+ /**
63
+ * `VITE_TWILIGHT_URL`/`VITE_TWILIGHT_VERSION` outrank everything below, including
64
+ * `theme.twilight.version`'s own `belowMinimum` guard — a stale override wins
65
+ * silently otherwise (TD-16375: a scaffold-time pin outlived the engine raising
66
+ * MIN_TWILIGHT_VERSION, so every new theme rendered without component CSS and
67
+ * nothing said why). Only a dev-mode nudge, not a swap: an env override is a
68
+ * deliberate choice, so unlike the settings-pin path below this never substitutes
69
+ * the engine default underneath the developer.
70
+ */
71
+ function warnIfOverrideBelowMinimum(source: string, version: string): void {
72
+ // Worker logs in production should stay quiet -- this is a developer-directed nudge,
73
+ // not a user-facing or production-monitored signal. import.meta.env.DEV is a static
74
+ // false in production builds, so this branch (and the console.warn below) is
75
+ // tree-shaken out entirely, same as DevSettingsWidget / dev-settings-store.ts.
76
+ if (!import.meta.env.DEV) return;
77
+ if (!belowMinimum(version)) return;
78
+ const key = `${source}:${version}`;
79
+ if (warnedPins.has(key)) return;
80
+ warnedPins.add(key);
81
+ console.warn(
82
+ `[twilight] ${source}=${version} is below the engine minimum ${MIN_TWILIGHT_VERSION} — components may load without their CSS (SallaApp/twilight#2107). Unset it or bump it to ${MIN_TWILIGHT_VERSION}+.`
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Which twilight.esm.js the storefront loads, in priority order:
88
+ * 1. `VITE_TWILIGHT_URL` — full local/deployment override.
89
+ * 2. `VITE_TWILIGHT_VERSION` — dev-CDN build override.
90
+ * 3. `theme.twilight.version` from the settings endpoint — the platform-pinned
91
+ * release used when no environment override is configured.
92
+ * 4. The engine's default release, also used when the pin is below the engine minimum.
93
+ */
94
+ export function resolveTwilightSdkUrl(settings?: StoreContext): string {
95
+ const urlOverride = readEnv('VITE_TWILIGHT_URL');
96
+ if (urlOverride) {
97
+ const version = releaseVersionInUrl(urlOverride);
98
+ if (version) warnIfOverrideBelowMinimum('VITE_TWILIGHT_URL', version);
99
+ return urlOverride;
100
+ }
101
+
102
+ const versionOverride = readEnv('VITE_TWILIGHT_VERSION');
103
+ if (versionOverride) {
104
+ warnIfOverrideBelowMinimum('VITE_TWILIGHT_VERSION', versionOverride);
105
+ return `${TWILIGHT_DEV_CDN}/${versionOverride}/twilight.esm.js`;
106
+ }
107
+
108
+ const pinnedVersion = settings?.theme?.twilight?.version;
109
+ if (pinnedVersion && belowMinimum(pinnedVersion)) {
110
+ if (!warnedPins.has(pinnedVersion)) {
111
+ warnedPins.add(pinnedVersion);
112
+ console.warn(
113
+ `[twilight] theme.twilight.version ${pinnedVersion} is below the engine minimum ${MIN_TWILIGHT_VERSION}; loading ${DEFAULT_TWILIGHT_VERSION} instead`
114
+ );
115
+ }
116
+ return releaseUrl(DEFAULT_TWILIGHT_VERSION);
117
+ }
118
+ if (pinnedVersion) return releaseUrl(pinnedVersion);
119
+
120
+ return releaseUrl(DEFAULT_TWILIGHT_VERSION);
121
+ }
122
+
123
+ // On a page that preloads its LCP image, the icon font (~270 KB) applies once the first screen's
124
+ // priority images have painted, so it doesn't compete with them; other pages swap on load.
125
+ const MEDIA_SWAP_SCRIPT = `
126
+ (function(){var d=document,s=function(l){if(l.sheet||"all"===l.media)l.media="all";else l.onload=function(){this.media="all"}},f=d.getElementById("salla-theme-font"),i=d.getElementById("salla-icons-css");f&&s(f);if(!i)return;if(!d.querySelector('link[rel="preload"][as="image"]'))return s(i);var g=function(){g=function(){};s(i)},p=function(){requestAnimationFrame(function(){setTimeout(function(){g()})});setTimeout(function(){g()},100)};d.addEventListener("DOMContentLoaded",function(){var m=[].filter.call(d.querySelectorAll('img[fetchpriority="high"]'),function(x){return!x.complete}),n=m.length;if(!n)return p();m.forEach(function(x){var o=function(){--n||p()};x.addEventListener("load",o,{once:!0});x.addEventListener("error",o,{once:!0})});setTimeout(function(){g()},2500)})})();
127
+ `.trim();
128
+
129
+ const LOADING_CSS = `
130
+ .loading-overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#fff;z-index:9999}
131
+ .loading-spinner{animation:spin 1s linear infinite;border-radius:9999px;height:3rem;width:3rem;border-bottom:2px solid var(--color-primary,#000)}
132
+ @keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
133
+ html,body{margin:0;padding:0;height:100%}
134
+ `.trim();
135
+
136
+ const SALLA_INIT_SCRIPT = `
137
+ window.salla = window.Salla = window.Salla || window.salla || {};
138
+ window.Salla.onReady = window.Salla.onReady || function(callback) {
139
+ return new Promise(function(resolve) {
140
+ if (window.Salla.status === 'ready') {
141
+ var config = window.salla && window.salla.config && window.salla.config.all();
142
+ resolve(callback ? callback(config) : config);
143
+ } else {
144
+ window.addEventListener('twilight::initiated', function(event) {
145
+ resolve(callback ? callback(event.detail) : event.detail);
146
+ }, { once: true });
147
+ }
148
+ });
149
+ };
150
+ `.trim();
151
+
152
+ /**
153
+ * Distinct snippet-host origins to `preconnect` (warm DNS/TCP/TLS during parse). From the real
154
+ * URLs (dev/prod-correct), deduped and capped at 4. No `crossOrigin`: snippets are no-cors fetches.
155
+ */
156
+ export function snippetPreconnectOrigins(snippets: readonly AppSnippet[] | undefined): string[] {
157
+ if (!snippets?.length) return [];
158
+ const origins = new Set<string>();
159
+ for (const app of snippets) {
160
+ if (app.status !== 'enabled') continue;
161
+ for (const s of app.snippets) {
162
+ if (!s.url) continue;
163
+ try {
164
+ origins.add(new URL(s.url).origin);
165
+ } catch {
166
+ /* skip a malformed snippet url rather than breaking the head */
167
+ }
168
+ if (origins.size >= 4) return [...origins];
169
+ }
170
+ }
171
+ return [...origins];
172
+ }
173
+
174
+ export function rootHead(ctx: {
175
+ params: Record<string, string>;
176
+ matches?: readonly TwilightContextMatch[];
177
+ }): HeadResult {
178
+ // Re-run on the client during hydration, where the twilight module store may
179
+ // still be empty. Recover it from the resolved match context so the head is
180
+ // built from real settings instead of falling through to 'Store Unavailable'.
181
+ ensureTwilightContextFromMatches(ctx.matches);
182
+
183
+ let settings: StoreContext | undefined;
184
+ try {
185
+ settings = getTwilightContext().settings;
186
+ } catch (e) {
187
+ if (e instanceof SettingsError) {
188
+ return resolveHead({
189
+ title: 'Store Unavailable',
190
+ });
191
+ }
192
+ throw e;
193
+ }
194
+ const locale = ctx.params.locale || FALLBACK_LOCALE;
195
+ const parts = deriveHeadParts(settings);
196
+
197
+ return resolveHead(
198
+ mergeHead(buildBaseHead(settings, locale), {
199
+ meta: buildRootMeta(parts),
200
+ links: buildRootLinks(parts),
201
+ styles: buildRootStyles(parts),
202
+ importMap: [{ imports: LIT_IMPORTS }],
203
+ scripts: buildRootScripts(parts),
204
+ })
205
+ );
206
+ }
207
+
208
+ /** Everything the root head's tag builders read, derived once from the store settings. */
209
+ interface RootHeadParts {
210
+ store: StoreContext['store'] | undefined;
211
+ favicon?: string;
212
+ primaryColor?: string;
213
+ fontUrl?: string;
214
+ fontUrlWithDisplay?: string;
215
+ isGoogleFont?: boolean;
216
+ fontFamilyName?: string;
217
+ customJs?: string;
218
+ customCss?: string;
219
+ isCustomCssUrl?: boolean;
220
+ isCustomJsUrl?: boolean;
221
+ twilightSdkUrl: string;
222
+ snippetPreconnects: Array<{ rel: 'preconnect'; href: string }>;
223
+ appScopeInstallScript: string;
224
+ themeHookSlotsScript: string | null;
225
+ runtimeEnvScript: string | null;
226
+ }
227
+
228
+ /** Google Fonts only honours `display=swap` as a query param; other hosts get the @font-face
229
+ * override instead (see buildRootStyles). */
230
+ function deriveFontParts(theme?: StoreContext['theme']) {
231
+ const font = theme?.font;
232
+ const fontUrl = font?.url || font?.path;
233
+ const isGoogleFont = fontUrl?.includes('fonts.googleapis.com');
234
+ const needsDisplay = Boolean(isGoogleFont && fontUrl && !fontUrl.includes('display='));
235
+ const separator = fontUrl?.includes('?') ? '&' : '?';
236
+
237
+ return {
238
+ fontUrl,
239
+ isGoogleFont,
240
+ fontFamilyName: font?.family_name,
241
+ fontUrlWithDisplay: needsDisplay ? `${fontUrl}${separator}display=swap` : fontUrl,
242
+ };
243
+ }
244
+
245
+ /** Theme customization JS/CSS, each either a URL to load or an inline blob to embed. */
246
+ function deriveCustomizationParts(theme?: StoreContext['theme']) {
247
+ const customization = theme?.customization;
248
+ const customJs = customization?.js?.trim();
249
+ const customCss = customization?.css?.trim();
250
+ const isUrl = (value?: string): boolean =>
251
+ Boolean(value?.startsWith('http') || value?.startsWith('//'));
252
+
253
+ return {
254
+ customJs,
255
+ customCss,
256
+ isCustomCssUrl: isUrl(customCss),
257
+ isCustomJsUrl: isUrl(customJs),
258
+ };
259
+ }
260
+
261
+ function deriveHeadParts(settings?: StoreContext): RootHeadParts {
262
+ const store = settings?.store;
263
+
264
+ return {
265
+ store,
266
+ favicon: store?.icon,
267
+ primaryColor: settings?.theme?.color?.primary,
268
+ ...deriveFontParts(settings?.theme),
269
+ ...deriveCustomizationParts(settings?.theme),
270
+ twilightSdkUrl: resolveTwilightSdkUrl(settings),
271
+ // Warm the connection to the snippet host(s) during head parse, so the body's deferred
272
+ // `<script src>` snippet tags fetch off an already-open socket instead of DNS+TCP+TLS.
273
+ snippetPreconnects: snippetPreconnectOrigins(getTwilightContext().appsSnippets).map((href) => ({
274
+ rel: 'preconnect' as const,
275
+ href,
276
+ })),
277
+ // Install `Salla.appScope` inline at parse time, before the SDK is ready — else a snippet's
278
+ // onReady callback calls it too early and throws. Always emitted, even when the settings
279
+ // prefetch failed: the CDN snippet tags ship regardless (they come from the *snippets*
280
+ // endpoint), so the factory has to exist for them. `installAppScope` then seeds the real
281
+ // buckets into this install once the client query resolves.
282
+ appScopeInstallScript: renderAppScopeInstallScript(
283
+ getTwilightContext().appsSettings ?? { apps: {} }
284
+ ),
285
+ // Whitelist the theme's own hook slots at parse time too, for the same reason: apps register
286
+ // their `onReady` callbacks from parser-inserted snippet tags, long before React hydrates.
287
+ themeHookSlotsScript: renderThemeHookSlotsScript(),
288
+ // Env this render resolved (worker `vars` live in server-only `process.env`), handed to the
289
+ // client so it doesn't silently fall back to the production defaults. Re-rendered on the
290
+ // client during hydration, where it resolves to the same values or to nothing.
291
+ runtimeEnvScript: renderRuntimeEnvScript(),
292
+ };
293
+ }
294
+
295
+ function buildRootMeta(p: RootHeadParts): NonNullable<HeadDescriptor['meta']> {
296
+ return [
297
+ { name: 'robots', content: 'index, follow' },
298
+ { name: 'msapplication-TileColor', content: '#ffffff' },
299
+ ...(p.favicon ? [{ name: 'msapplication-TileImage', content: p.favicon }] : []),
300
+ ...(p.primaryColor
301
+ ? [
302
+ { name: 'theme-color', content: p.primaryColor },
303
+ { name: 'msapplication-navbutton-color', content: p.primaryColor },
304
+ { name: 'apple-mobile-web-app-status-bar-style', content: p.primaryColor },
305
+ ]
306
+ : []),
307
+ ];
308
+ }
309
+
310
+ function buildRootLinks(p: RootHeadParts): NonNullable<HeadDescriptor['links']> {
311
+ return [
312
+ { rel: 'preconnect', href: 'https://api.salla.dev' },
313
+ { rel: 'preconnect', href: 'https://cdn.salla.sa' },
314
+ { rel: 'preconnect', href: 'https://cdn.assets.salla.network', crossOrigin: 'anonymous' },
315
+ // Warm the installed apps' CDN snippet host(s) — see snippetPreconnectOrigins.
316
+ ...p.snippetPreconnects,
317
+ ...(p.fontUrl
318
+ ? [
319
+ {
320
+ rel: 'preconnect' as const,
321
+ href: 'https://fonts.gstatic.com',
322
+ crossOrigin: 'anonymous' as const,
323
+ },
324
+ ]
325
+ : []),
326
+ { rel: 'modulepreload', href: p.twilightSdkUrl },
327
+ ...(p.fontUrlWithDisplay
328
+ ? [
329
+ {
330
+ id: 'salla-theme-font',
331
+ rel: 'stylesheet',
332
+ href: p.fontUrlWithDisplay,
333
+ media: 'print',
334
+ },
335
+ ]
336
+ : []),
337
+ ...(p.fontUrlWithDisplay && !p.isGoogleFont && p.fontFamilyName
338
+ ? [
339
+ {
340
+ id: 'salla-font-display-override',
341
+ rel: 'preload',
342
+ as: 'style',
343
+ href: p.fontUrlWithDisplay,
344
+ },
345
+ ]
346
+ : []),
347
+ ...(p.favicon ? [{ rel: 'icon', href: p.favicon }] : []),
348
+ ...(p.favicon
349
+ ? [{ id: 'salla-apple-icon', rel: 'apple-touch-icon-precomposed', href: p.favicon }]
350
+ : []),
351
+ ...(p.isCustomCssUrl && p.customCss
352
+ ? [{ id: 'salla-custom-css', rel: 'stylesheet', href: p.customCss }]
353
+ : []),
354
+ {
355
+ id: 'salla-icons-css',
356
+ rel: 'stylesheet',
357
+ href: SALLA_ICONS_CSS_URL,
358
+ media: 'print',
359
+ },
360
+ // {
361
+ // id: 'intl-tel-input-css',
362
+ // rel: 'stylesheet',
363
+ // href: 'https://cdn.jsdelivr.net/npm/intl-tel-input@26.8.0/build/css/intlTelInput.css',
364
+ // },
365
+ ...(p.isCustomJsUrl && p.customJs
366
+ ? [{ id: 'salla-custom-js-preload', rel: 'preload', as: 'script', href: p.customJs }]
367
+ : []),
368
+ ];
369
+ }
370
+
371
+ // Hoistable (href + precedence): the web components insert their own <style> near the top of
372
+ // <head> before hydration, which a positionally hydrated <style> would be matched against.
373
+ const hoisted = (id: string, children: string) => ({
374
+ id,
375
+ href: id,
376
+ precedence: 'default',
377
+ children,
378
+ });
379
+
380
+ function buildRootStyles(p: RootHeadParts): NonNullable<HeadDescriptor['styles']> {
381
+ return [
382
+ hoisted('twilight-loading', LOADING_CSS),
383
+ ...(!p.isGoogleFont && p.fontFamilyName
384
+ ? [
385
+ hoisted(
386
+ 'salla-font-display-swap',
387
+ `@font-face{font-family:"${p.fontFamilyName}";font-display:swap;src:local("${p.fontFamilyName}")}`
388
+ ),
389
+ ]
390
+ : []),
391
+ ...(!p.isCustomCssUrl && p.customCss ? [hoisted('salla-custom-css', p.customCss)] : []),
392
+ ];
393
+ }
394
+
395
+ function buildRootScripts(p: RootHeadParts): NonNullable<HeadDescriptor['scripts']> {
396
+ return [
397
+ // First: the API base / store identifier this render used, so the hydrated client
398
+ // resolves the same ones (worker `vars` never reach the browser bundle on their own).
399
+ ...(p.runtimeEnvScript ? [{ id: 'salla-runtime-env', children: p.runtimeEnvScript }] : []),
400
+ { id: 'twilight-init', children: SALLA_INIT_SCRIPT },
401
+ // After twilight-init (which defines window.Salla) and before the SDK module, so the
402
+ // appScope factory exists ahead of any snippet's onReady callback firing.
403
+ { id: 'salla-app-scope', children: p.appScopeInstallScript },
404
+ // Arms the hook-slot whitelist push here, so its `twilight::initiated` listener is
405
+ // registered before any app snippet's — listeners fire in registration order.
406
+ ...(p.themeHookSlotsScript
407
+ ? [{ id: 'salla-theme-hook-slots', children: p.themeHookSlotsScript }]
408
+ : []),
409
+ { id: 'salla-async-css', children: MEDIA_SWAP_SCRIPT },
410
+ // The SDK and ticketing modules are rendered at the end of <body> (SallaSdkScripts).
411
+ ...(p.customJs ? [buildCustomJsScript(p.customJs, Boolean(p.isCustomJsUrl))] : []),
412
+ ];
413
+ }
414
+
415
+ /** A URL customization is loaded after SDK init; an inline one is wrapped so a syntax/runtime
416
+ * error in partner JS warns instead of taking the page down. */
417
+ function buildCustomJsScript(customJs: string, isUrl: boolean) {
418
+ return {
419
+ id: 'salla-custom-js',
420
+ children: isUrl
421
+ ? `window.addEventListener('twilight::initiated',function(){var s=document.createElement('script');s.src=${escapeForInlineScript(JSON.stringify(customJs))};s.async=true;document.head.appendChild(s)},{once:true});`
422
+ : `try { ${customJs} } catch(e) { console.warn("[ThemeCustomization] Custom JS error:", e); }`,
423
+ };
424
+ }
@@ -0,0 +1,2 @@
1
+ export { rootHead } from './head';
2
+ export { rootBeforeLoad, type BeforeLoadResult } from './beforeLoad';
@@ -0,0 +1,82 @@
1
+ import { useEffect } from 'react';
2
+ import { redirect } from '../providers/NavigationProvider';
3
+ import { FALLBACK_LOCALE } from '../providers/locale-constants';
4
+ import { clearAuthTokenCookie } from '../utils/cookies';
5
+
6
+ export interface SlugBrandParams {
7
+ /** SEO slug segment (ignored — only here to match the route pattern). */
8
+ slug: string;
9
+ /** Brand numeric ID to redirect to. */
10
+ id: string;
11
+ /** Active locale from the `/$locale` parent layout (e.g. `'ar'`, `'en'`). */
12
+ locale?: string;
13
+ }
14
+
15
+ export async function slugBrandRedirectLoader(ctx: { params: SlugBrandParams }): Promise<never> {
16
+ const locale = ctx.params.locale || FALLBACK_LOCALE;
17
+ throw redirect(`/${locale}/brands/${ctx.params.id}`);
18
+ }
19
+
20
+ export interface PendingOrdersRedirectParams {
21
+ /** Active locale from the `/$locale` parent layout (e.g. `'ar'`, `'en'`). */
22
+ locale?: string;
23
+ }
24
+
25
+ export async function pendingOrdersRedirectLoader(
26
+ ctx: { params: PendingOrdersRedirectParams } = { params: {} }
27
+ ): Promise<never> {
28
+ const locale = ctx.params.locale || FALLBACK_LOCALE;
29
+ throw redirect(`/${locale}/account/orders?status=pending`);
30
+ }
31
+
32
+ /** `/account/rating`: the loyalty "rate your orders" link. Twig Raed has no rating page and sends it to the orders list. */
33
+ export async function ratingRedirectLoader(
34
+ ctx: { params: PendingOrdersRedirectParams } = { params: {} }
35
+ ): Promise<never> {
36
+ const locale = ctx.params.locale || FALLBACK_LOCALE;
37
+ throw redirect(`/${locale}/account/orders`);
38
+ }
39
+
40
+ /** A Twig customer URL (`/profile`, `/orders`, …) kept working as its `/account/…` page. */
41
+ export async function accountRedirectLoader(ctx: {
42
+ params: PendingOrdersRedirectParams;
43
+ page: string;
44
+ /** The incoming query string (`?status=pending`), kept on the account page. */
45
+ search?: string;
46
+ }): Promise<never> {
47
+ // No locale in the URL stays without one: the locale layout adds it only where the store has several.
48
+ const prefix = ctx.params.locale ? `/${ctx.params.locale}` : '';
49
+ throw redirect(`${prefix}/account/${ctx.page}${ctx.search ?? ''}`, { statusCode: 301 });
50
+ }
51
+
52
+ /** `/latest-products`, `/most-sales-products` and `/offers` now live at `/products?source=`. */
53
+ export async function productsSourceRedirectLoader(ctx: {
54
+ params: PendingOrdersRedirectParams;
55
+ source: 'latest' | 'sales' | 'offers';
56
+ search?: string;
57
+ }): Promise<never> {
58
+ const prefix = ctx.params.locale ? `/${ctx.params.locale}` : '';
59
+ const query = new URLSearchParams(ctx.search);
60
+ query.delete('source');
61
+ const rest = query.toString();
62
+ throw redirect(`${prefix}/products?source=${ctx.source}${rest ? `&${rest}` : ''}`, {
63
+ statusCode: 301,
64
+ });
65
+ }
66
+
67
+ /**
68
+ * `/logout` — where the SDK sends the browser once `salla.auth.logout()` has run
69
+ * (`salla.url.get('logout?from=…')`). The Twig storefront serves that URL server-side. Here
70
+ * it renders nothing and, in the browser, removes the theme's own `token` cookie (the SDK's
71
+ * `cookie.clearAll()` leaves it) and then does a full navigation home. It has to be a
72
+ * component, not a loader redirect: a loader runs during server rendering, where the cookie
73
+ * can't be cleared, so the redirect would leave the visitor signed in on the next request.
74
+ */
75
+ export function LogoutRedirect(): null {
76
+ useEffect(() => {
77
+ clearAuthTokenCookie();
78
+ // `/{store base}/{locale}/logout?from=…` → `/{store base}/{locale}`
79
+ window.location.replace(window.location.href.replace(/\/logout\/?(\?[^#]*)?(#.*)?$/, ''));
80
+ }, []);
81
+ return null;
82
+ }
@@ -0,0 +1,12 @@
1
+ import { SallaReviewsPage } from '@salla.sa/twilight-components-react/reviews-page';
2
+ import type { ReviewsPageProps } from './types';
3
+ import { Breadcrumb } from '../../components/common';
4
+
5
+ export function ReviewsPage({ page }: ReviewsPageProps) {
6
+ return (
7
+ <div className="container mb-20">
8
+ <Breadcrumb page={page} />
9
+ <SallaReviewsPage />
10
+ </div>
11
+ );
12
+ }
@@ -0,0 +1,80 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { SallaComments } from '@salla.sa/twilight-components-react/comments';
3
+ import { useTranslation } from '../../providers/I18nProvider';
4
+ import { useComments } from '../../hooks/useComments';
5
+ import type { TestimonialsPageProps } from './types';
6
+ import { Breadcrumb } from '../../components/common';
7
+
8
+ const buildSortOptions = (t: (key: string, fb: string) => string) => [
9
+ { id: 'latest', label: t('pages.testimonials.sort_by_date_desc', 'Latest') },
10
+ { id: 'oldest', label: t('pages.testimonials.sort_by_date_asc', 'Oldest') },
11
+ {
12
+ id: 'top_rating',
13
+ label: t('pages.testimonials.sort_by_rating_desc', 'Top rated'),
14
+ },
15
+ {
16
+ id: 'bottom_rating',
17
+ label: t('pages.testimonials.sort_by_rating_asc', 'Low rated'),
18
+ },
19
+ ];
20
+
21
+ export function TestimonialsPage({ page }: TestimonialsPageProps) {
22
+ const { t } = useTranslation();
23
+ const { commentsKey } = useComments();
24
+
25
+ // Sync sort state with URL
26
+ const [selectedSort, setSelectedSort] = useState(() => {
27
+ const params = new URLSearchParams(window.location.search);
28
+ return params.get('sort') || 'latest';
29
+ });
30
+
31
+ // Update URL when sort changes
32
+ useEffect(() => {
33
+ const url = new URL(window.location.href);
34
+ url.searchParams.set('sort', selectedSort);
35
+ window.history.replaceState(null, '', url.toString());
36
+ }, [selectedSort]);
37
+
38
+ return (
39
+ <div className="container">
40
+ <Breadcrumb page={page} />
41
+
42
+ <div className="flex justify-center">
43
+ <div className="w-full lg:w-10/12 bg-white rounded p-6 lg:p-8 mt-4 lg:mt-12">
44
+ <div className="mb-4 sm:mb-6 flex justify-between items-center">
45
+ <h1 className="font-bold text-2xl">{page.title}</h1>
46
+ <div className="flex items-center">
47
+ <label
48
+ className="hidden sm:block rtl:ml-3 ltr:mr-3 whitespace-nowrap"
49
+ htmlFor="testimonials-filter"
50
+ >
51
+ {t('pages.categories.sorting', 'Sort by')}
52
+ </label>
53
+ <select
54
+ id="testimonials-filter"
55
+ className="form-input pt-0 pb-1 rtl:pl-10 ltr:pr-10 bg-white text-gray-900"
56
+ value={selectedSort}
57
+ onChange={(e) => setSelectedSort(e.target.value)}
58
+ >
59
+ {buildSortOptions(t).map((opt) => (
60
+ <option key={opt.id} value={opt.id}>
61
+ {opt.label}
62
+ </option>
63
+ ))}
64
+ </select>
65
+ </div>
66
+ </div>
67
+
68
+ {/* Key prop forces reload when sort changes or new comment added */}
69
+ <SallaComments
70
+ key={`${selectedSort}-${commentsKey}`}
71
+ testimonials
72
+ // drifted wrapper types require itemId; testimonials mode has none
73
+ itemId={undefined as never}
74
+ sort={selectedSort as any}
75
+ />
76
+ </div>
77
+ </div>
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,80 @@
1
+ import type { HeadDescriptor } from '../../utils/head';
2
+ import { buildHreflangAlternates } from '../../utils/baseHead';
3
+ import { lazy } from 'react';
4
+ import { reviewsLoader, testimonialsLoader } from './loader';
5
+ import type { ReviewsPageProps, TestimonialsPageProps } from './types';
6
+ import type { TwilightContext } from '../../twilight/context';
7
+ import { RouteId } from '../../router/route-ids';
8
+
9
+ // Lazy-load component for code-splitting
10
+ const LazyTestimonialsPage = lazy(() =>
11
+ import('./TestimonialsPage').then((m) => ({ default: m.TestimonialsPage }))
12
+ );
13
+
14
+ const LazyReviewsPage = lazy(() =>
15
+ import('./ReviewsPage').then((m) => ({ default: m.ReviewsPage }))
16
+ );
17
+
18
+ export const TestimonialsPage = LazyTestimonialsPage;
19
+ export const ReviewsPage = LazyReviewsPage;
20
+ export { reviewsLoader, testimonialsLoader } from './loader';
21
+ export type { ReviewsPageProps, TestimonialsPageProps } from './types';
22
+
23
+ async function loader(
24
+ ctx: { locale?: string },
25
+ extend?: (
26
+ data: TestimonialsPageProps,
27
+ ctx: { params: Record<string, never> }
28
+ ) => Promise<Record<string, unknown>> | Record<string, unknown>
29
+ ): Promise<TestimonialsPageProps> {
30
+ const data = await testimonialsLoader({ locale: ctx.locale ?? 'ar' });
31
+
32
+ if (extend) {
33
+ const extra = await extend(data, { params: {} });
34
+ return { ...data, ...extra } as TestimonialsPageProps;
35
+ }
36
+ return data;
37
+ }
38
+
39
+ function head(ctx: TwilightContext, data: TestimonialsPageProps): HeadDescriptor {
40
+ const base: HeadDescriptor = {
41
+ title: data.page.title,
42
+ description: data.page.description,
43
+ alternateLanguages: buildHreflangAlternates(ctx.settings, ctx.location.pathname),
44
+ };
45
+
46
+ return base;
47
+ }
48
+
49
+ export const Testimonials = {
50
+ id: RouteId.TESTIMONIALS,
51
+ loader,
52
+ head,
53
+ Component: LazyTestimonialsPage,
54
+ } as const;
55
+
56
+ async function loadReviews(
57
+ _ctx?: { locale?: string },
58
+ extend?: (
59
+ data: ReviewsPageProps,
60
+ ctx: { params: Record<string, never> }
61
+ ) => Promise<Record<string, unknown>> | Record<string, unknown>
62
+ ): Promise<ReviewsPageProps> {
63
+ const data = await reviewsLoader();
64
+
65
+ if (extend) {
66
+ const extra = await extend(data, { params: {} });
67
+ return { ...data, ...extra } as ReviewsPageProps;
68
+ }
69
+ return data;
70
+ }
71
+
72
+ export const Reviews = {
73
+ id: RouteId.REVIEWS,
74
+ loader: loadReviews,
75
+ head: (ctx: TwilightContext, data: ReviewsPageProps): HeadDescriptor => ({
76
+ title: data.page.title,
77
+ alternateLanguages: buildHreflangAlternates(ctx.settings, ctx.location.pathname),
78
+ }),
79
+ Component: LazyReviewsPage,
80
+ } as const;