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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. package/dist/{AddToCartForm-B5C2M4OZ.js → AddToCartForm-5X2WZDWR.js} +3 -3
  2. package/dist/{AddToCartForm-B5C2M4OZ.js.map → AddToCartForm-5X2WZDWR.js.map} +1 -1
  3. package/dist/{BlogAuthorPage-GTF4UGSS.js → BlogAuthorPage-LO7FSSJR.js} +12 -10
  4. package/dist/BlogAuthorPage-LO7FSSJR.js.map +1 -0
  5. package/dist/{BlogCategoryPage-TN7Y4NBP.js → BlogCategoryPage-KKFQ3PGR.js} +12 -10
  6. package/dist/BlogCategoryPage-KKFQ3PGR.js.map +1 -0
  7. package/dist/{BlogPage-TDB3M3PI.js → BlogPage-VLF3NSGE.js} +11 -9
  8. package/dist/BlogPage-VLF3NSGE.js.map +1 -0
  9. package/dist/{BlogSinglePage-4A52Q5QW.js → BlogSinglePage-AVK535IW.js} +11 -9
  10. package/dist/BlogSinglePage-AVK535IW.js.map +1 -0
  11. package/dist/{BlogTagPage-TOVPBETA.js → BlogTagPage-F3T4D7RH.js} +12 -10
  12. package/dist/BlogTagPage-F3T4D7RH.js.map +1 -0
  13. package/dist/{BrandsPage-VUDGYSQI.js → BrandsPage-LVSBZSVC.js} +11 -9
  14. package/dist/BrandsPage-LVSBZSVC.js.map +1 -0
  15. package/dist/{Breadcrumb-CI3SN5YR.js → Breadcrumb-VLFTXRFC.js} +11 -9
  16. package/dist/Breadcrumb-VLFTXRFC.js.map +1 -0
  17. package/dist/{CartItem-BHLEURAB.js → CartItem-G3H34GW6.js} +11 -9
  18. package/dist/CartItem-G3H34GW6.js.map +1 -0
  19. package/dist/{CartSummary-QERVESYN.js → CartSummary-VH37RPUI.js} +11 -9
  20. package/dist/CartSummary-VH37RPUI.js.map +1 -0
  21. package/dist/{Copyright-RL6ALKRC.js → Copyright-HE5ZWINZ.js} +3 -3
  22. package/dist/{Copyright-RL6ALKRC.js.map → Copyright-HE5ZWINZ.js.map} +1 -1
  23. package/dist/{CustomerLayout-VRZUWGQK.js → CustomerLayout-LKBLOAVW.js} +11 -9
  24. package/dist/CustomerLayout-LKBLOAVW.js.map +1 -0
  25. package/dist/{Footer-ZWBZCMDL.js → Footer-HHXAST72.js} +5 -5
  26. package/dist/{Footer-ZWBZCMDL.js.map → Footer-HHXAST72.js.map} +1 -1
  27. package/dist/{Header-WNK4O66B.js → Header-GEVZM2YG.js} +8 -6
  28. package/dist/{Header-WNK4O66B.js.map → Header-GEVZM2YG.js.map} +1 -1
  29. package/dist/{LoyaltyPage-CGTHWJ76.js → LoyaltyPage-M5CT3CFD.js} +11 -9
  30. package/dist/LoyaltyPage-M5CT3CFD.js.map +1 -0
  31. package/dist/{LoyaltyPointsPage-QSU6K3PW.js → LoyaltyPointsPage-SDJB52U6.js} +10 -3
  32. package/dist/LoyaltyPointsPage-SDJB52U6.js.map +1 -0
  33. package/dist/{MainMenu-V3T7F32R.js → MainMenu-I7EYS752.js} +3 -3
  34. package/dist/{MainMenu-V3T7F32R.js.map → MainMenu-I7EYS752.js.map} +1 -1
  35. package/dist/{MasterLayout-XQF644LD.js → MasterLayout-QDK4T3NE.js} +11 -9
  36. package/dist/{MasterLayout-XQF644LD.js.map → MasterLayout-QDK4T3NE.js.map} +1 -1
  37. package/dist/{MobileMenu-U4BVQYKH.js → MobileMenu-7VTCWAVJ.js} +11 -9
  38. package/dist/MobileMenu-7VTCWAVJ.js.map +1 -0
  39. package/dist/{NotificationsPage-ZRRQY7TF.js → NotificationsPage-QCABE6QD.js} +11 -9
  40. package/dist/NotificationsPage-QCABE6QD.js.map +1 -0
  41. package/dist/{OrderSinglePage-O3XYVMG5.js → OrderSinglePage-2HVBKBG4.js} +11 -9
  42. package/dist/OrderSinglePage-2HVBKBG4.js.map +1 -0
  43. package/dist/{OrdersPage-CMXZF5MI.js → OrdersPage-5MNCPCM3.js} +11 -9
  44. package/dist/OrdersPage-5MNCPCM3.js.map +1 -0
  45. package/dist/{PageSingle-IIG2US43.js → PageSingle-EN5XIM2B.js} +3 -3
  46. package/dist/{PageSingle-IIG2US43.js.map → PageSingle-EN5XIM2B.js.map} +1 -1
  47. package/dist/{ProductDetails-65GDABI7.js → ProductDetails-UEWQPPDZ.js} +13 -6
  48. package/dist/ProductDetails-UEWQPPDZ.js.map +1 -0
  49. package/dist/{ProfilePage-SNJ4WXU4.js → ProfilePage-TZSU3GHZ.js} +11 -9
  50. package/dist/ProfilePage-TZSU3GHZ.js.map +1 -0
  51. package/dist/{ReviewsPage-UT7UT55J.js → ReviewsPage-WYZ6CHF2.js} +3 -3
  52. package/dist/{ReviewsPage-UT7UT55J.js.map → ReviewsPage-WYZ6CHF2.js.map} +1 -1
  53. package/dist/{SeoCartWidget-DBJAXJKT.js → SeoCartWidget-5JR5RXQM.js} +11 -9
  54. package/dist/SeoCartWidget-5JR5RXQM.js.map +1 -0
  55. package/dist/{SettingsPage-FLTIOPU5.js → SettingsPage-DIDRQXMM.js} +11 -9
  56. package/dist/SettingsPage-DIDRQXMM.js.map +1 -0
  57. package/dist/{StoreClosedNotification-ZT3QKDKS.js → StoreClosedNotification-C2G32E34.js} +12 -10
  58. package/dist/{StoreClosedNotification-ZT3QKDKS.js.map → StoreClosedNotification-C2G32E34.js.map} +1 -1
  59. package/dist/{TestimonialsPage-CU7HKU4O.js → TestimonialsPage-XPHFRBZ7.js} +3 -3
  60. package/dist/{TestimonialsPage-CU7HKU4O.js.map → TestimonialsPage-XPHFRBZ7.js.map} +1 -1
  61. package/dist/{ThankYouPage-5X6B4X3T.js → ThankYouPage-UA6YYFB2.js} +11 -9
  62. package/dist/ThankYouPage-UA6YYFB2.js.map +1 -0
  63. package/dist/{WalletPage-H2BKIDPA.js → WalletPage-AJVV7LVL.js} +11 -9
  64. package/dist/WalletPage-AJVV7LVL.js.map +1 -0
  65. package/dist/{WishlistPage-RS5UCIQ3.js → WishlistPage-UW6LJLSW.js} +11 -9
  66. package/dist/WishlistPage-UW6LJLSW.js.map +1 -0
  67. package/dist/api/advertisement.d.ts +33 -0
  68. package/dist/api/advertisement.d.ts.map +1 -0
  69. package/dist/api/advertisement.js +8 -0
  70. package/dist/api/advertisement.js.map +1 -0
  71. package/dist/boot/critical-style.d.ts +3 -0
  72. package/dist/boot/critical-style.d.ts.map +1 -0
  73. package/dist/boot/index.d.ts +15 -0
  74. package/dist/boot/index.d.ts.map +1 -0
  75. package/dist/boot.d.ts +1 -0
  76. package/dist/boot.js +69 -0
  77. package/dist/boot.js.map +1 -0
  78. package/dist/{chunk-SE5JYWOK.js → chunk-2HCCIHU7.js} +4 -4
  79. package/dist/{chunk-SE5JYWOK.js.map → chunk-2HCCIHU7.js.map} +1 -1
  80. package/dist/chunk-4FJGZQD3.js +6 -0
  81. package/dist/chunk-4FJGZQD3.js.map +1 -0
  82. package/dist/{chunk-SDFDS4Q3.js → chunk-4WVJJ7SN.js} +20 -4
  83. package/dist/chunk-4WVJJ7SN.js.map +1 -0
  84. package/dist/{chunk-NXQJA7D3.js → chunk-BAYGWTNM.js} +3 -3
  85. package/dist/{chunk-NXQJA7D3.js.map → chunk-BAYGWTNM.js.map} +1 -1
  86. package/dist/{chunk-OWX2CPNQ.js → chunk-CLBFF2CY.js} +3 -3
  87. package/dist/{chunk-OWX2CPNQ.js.map → chunk-CLBFF2CY.js.map} +1 -1
  88. package/dist/chunk-DBW7XXNE.js +103 -0
  89. package/dist/chunk-DBW7XXNE.js.map +1 -0
  90. package/dist/{chunk-KHBSGD2D.js → chunk-DBXH3EMJ.js} +3 -3
  91. package/dist/{chunk-KHBSGD2D.js.map → chunk-DBXH3EMJ.js.map} +1 -1
  92. package/dist/{chunk-JRNH3FUF.js → chunk-E26YG3AO.js} +4 -4
  93. package/dist/{chunk-JRNH3FUF.js.map → chunk-E26YG3AO.js.map} +1 -1
  94. package/dist/{chunk-22SH7OVF.js → chunk-E7WWO3V3.js} +3 -3
  95. package/dist/{chunk-22SH7OVF.js.map → chunk-E7WWO3V3.js.map} +1 -1
  96. package/dist/{chunk-UJKFBZFJ.js → chunk-HQ7WA37L.js} +7 -7
  97. package/dist/{chunk-UJKFBZFJ.js.map → chunk-HQ7WA37L.js.map} +1 -1
  98. package/dist/chunk-IVBOLREE.js +29 -0
  99. package/dist/chunk-IVBOLREE.js.map +1 -0
  100. package/dist/{chunk-6SWDSRIL.js → chunk-KP3VYG4F.js} +6 -6
  101. package/dist/{chunk-6SWDSRIL.js.map → chunk-KP3VYG4F.js.map} +1 -1
  102. package/dist/{chunk-4CZRUAZB.js → chunk-PWTXR4RR.js} +10 -10
  103. package/dist/{chunk-4CZRUAZB.js.map → chunk-PWTXR4RR.js.map} +1 -1
  104. package/dist/chunk-QJYUTRAQ.js +14 -0
  105. package/dist/{chunk-7WAXN2PP.js.map → chunk-QJYUTRAQ.js.map} +1 -1
  106. package/dist/{chunk-JO7JWV34.js → chunk-U55ZJC2X.js} +4 -4
  107. package/dist/{chunk-JO7JWV34.js.map → chunk-U55ZJC2X.js.map} +1 -1
  108. package/dist/{chunk-SX5MF2YA.js → chunk-WSDG4ZRI.js} +4 -4
  109. package/dist/{chunk-SX5MF2YA.js.map → chunk-WSDG4ZRI.js.map} +1 -1
  110. package/dist/{chunk-NZVR4OF7.js → chunk-X54QP332.js} +8 -8
  111. package/dist/chunk-X54QP332.js.map +1 -0
  112. package/dist/components/cart/index.js +1 -1
  113. package/dist/components/common/index.js +1 -1
  114. package/dist/components/drawer/index.js +9 -7
  115. package/dist/components/home/index.js +11 -9
  116. package/dist/components/layout/PageAdvertisements.d.ts +7 -0
  117. package/dist/components/layout/PageAdvertisements.d.ts.map +1 -0
  118. package/dist/components/layout/index.d.ts +1 -0
  119. package/dist/components/layout/index.d.ts.map +1 -1
  120. package/dist/components/layout/index.js +8 -1
  121. package/dist/components/product/index.js +10 -8
  122. package/dist/components/store/index.js +11 -9
  123. package/dist/components/store/index.js.map +1 -1
  124. package/dist/{document-class-67HTP45G.js → document-class-7AXAAWGZ.js} +6 -5
  125. package/dist/{document-class-67HTP45G.js.map → document-class-7AXAAWGZ.js.map} +1 -1
  126. package/dist/hooks/index.js +9 -7
  127. package/dist/hooks/useGtm.js +9 -7
  128. package/dist/hooks/useMoney.js +9 -7
  129. package/dist/hooks/useStore.js +9 -7
  130. package/dist/index.js +9 -7
  131. package/dist/index.js.map +1 -1
  132. package/dist/{not-found-QTHBFPNT.js → not-found-3EEMQZMU.js} +11 -9
  133. package/dist/not-found-3EEMQZMU.js.map +1 -0
  134. package/dist/providers/index.js +9 -7
  135. package/dist/routes/account/orders.js +1 -1
  136. package/dist/routes/account.js +6 -6
  137. package/dist/routes/blog.js +5 -5
  138. package/dist/routes/brands.js +1 -1
  139. package/dist/routes/cart.js +13 -11
  140. package/dist/routes/cart.js.map +1 -1
  141. package/dist/routes/home.js +12 -10
  142. package/dist/routes/loyalty.js +2 -2
  143. package/dist/routes/page.js +1 -1
  144. package/dist/routes/product-listing.js +11 -9
  145. package/dist/routes/product-listing.js.map +1 -1
  146. package/dist/routes/product.js +11 -9
  147. package/dist/routes/product.js.map +1 -1
  148. package/dist/routes/root/beforeLoad.d.ts +2 -2
  149. package/dist/routes/root/beforeLoad.d.ts.map +1 -1
  150. package/dist/routes/root/head.d.ts.map +1 -1
  151. package/dist/routes/testimonials.js +2 -2
  152. package/dist/routes/thank-you.js +1 -1
  153. package/dist/skeleton.js +12 -10
  154. package/dist/tanstack/document-class.d.ts.map +1 -1
  155. package/dist/tanstack/index.d.ts +1 -0
  156. package/dist/tanstack/index.d.ts.map +1 -1
  157. package/dist/tanstack/stylesheet.d.ts +15 -0
  158. package/dist/tanstack/stylesheet.d.ts.map +1 -0
  159. package/dist/tanstack.js +34 -5
  160. package/dist/tanstack.js.map +1 -1
  161. package/dist/utils/index.js +1 -1
  162. package/dist/vite/index.js +1 -1
  163. package/package.json +12 -3
  164. package/tailwind/components/Component.tsx.txt +21 -0
  165. package/tailwind/components/ComponentRegistry.ts.txt +66 -0
  166. package/tailwind/components/cart/CartItem.tsx.txt +211 -0
  167. package/tailwind/components/cart/CartSummary.tsx.txt +269 -0
  168. package/tailwind/components/cart/PreOrderNotification.tsx.txt +16 -0
  169. package/tailwind/components/cart/PriceQuoteButton.tsx.txt +21 -0
  170. package/tailwind/components/cart/SeoCartWidget.tsx.txt +99 -0
  171. package/tailwind/components/cart/index.ts.txt +11 -0
  172. package/tailwind/components/cart/lazy.ts.txt +15 -0
  173. package/tailwind/components/collapse/Collapse.tsx.txt +95 -0
  174. package/tailwind/components/collapse/index.ts.txt +41 -0
  175. package/tailwind/components/common/Breadcrumb.tsx.txt +27 -0
  176. package/tailwind/components/common/BreadcrumbJsonLd.tsx.txt +40 -0
  177. package/tailwind/components/common/BreadcrumbTrail.tsx.txt +97 -0
  178. package/tailwind/components/common/CurrencySymbol.tsx.txt +8 -0
  179. package/tailwind/components/common/DeferredData.tsx.txt +122 -0
  180. package/tailwind/components/common/ErrorPage.tsx.txt +69 -0
  181. package/tailwind/components/common/Image.tsx.txt +119 -0
  182. package/tailwind/components/common/Link.tsx.txt +36 -0
  183. package/tailwind/components/common/Link.types.ts.txt +85 -0
  184. package/tailwind/components/common/NoContent.tsx.txt +27 -0
  185. package/tailwind/components/common/RenderWhenVisible.tsx.txt +92 -0
  186. package/tailwind/components/common/index.ts.txt +42 -0
  187. package/tailwind/components/common/lazy.ts.txt +28 -0
  188. package/tailwind/components/defineComponent.ts.txt +32 -0
  189. package/tailwind/components/drawer/Drawer.tsx.txt +219 -0
  190. package/tailwind/components/drawer/index.ts.txt +38 -0
  191. package/tailwind/components/dropdown/Dropdown.tsx.txt +166 -0
  192. package/tailwind/components/dropdown/index.ts.txt +40 -0
  193. package/tailwind/components/home/BundleComponent.tsx.txt +35 -0
  194. package/tailwind/components/home/ComponentErrorBoundary.tsx.txt +98 -0
  195. package/tailwind/components/home/FeaturedProductsStyle1.tsx.txt +125 -0
  196. package/tailwind/components/home/FeaturedProductsStyle2.tsx.txt +127 -0
  197. package/tailwind/components/home/FeaturedProductsStyle3.tsx.txt +71 -0
  198. package/tailwind/components/home/FixedBanner.tsx.txt +68 -0
  199. package/tailwind/components/home/FixedProducts.tsx.txt +72 -0
  200. package/tailwind/components/home/HomePageRenderer.tsx.txt +284 -0
  201. package/tailwind/components/home/PageBlocks.tsx.txt +24 -0
  202. package/tailwind/components/home/ParallaxBackground.tsx.txt +50 -0
  203. package/tailwind/components/home/PhotosSlider.tsx.txt +61 -0
  204. package/tailwind/components/home/ProductsSlider.tsx.txt +87 -0
  205. package/tailwind/components/home/ReorderProducts.tsx.txt +38 -0
  206. package/tailwind/components/home/SquarePhotos.tsx.txt +68 -0
  207. package/tailwind/components/home/StoreFeatures.tsx.txt +34 -0
  208. package/tailwind/components/home/Testimonials.tsx.txt +36 -0
  209. package/tailwind/components/home/Youtube.tsx.txt +48 -0
  210. package/tailwind/components/home/index.ts.txt +50 -0
  211. package/tailwind/components/home/lazy.ts.txt +17 -0
  212. package/tailwind/components/home/register.ts.txt +44 -0
  213. package/tailwind/components/home/reorder-hooks.ts.txt +52 -0
  214. package/tailwind/components/home/types.ts.txt +4 -0
  215. package/tailwind/components/layout/Copyright.tsx.txt +23 -0
  216. package/tailwind/components/layout/CustomerLayout.tsx.txt +91 -0
  217. package/tailwind/components/layout/Footer.tsx.txt +343 -0
  218. package/tailwind/components/layout/Header.tsx.txt +154 -0
  219. package/tailwind/components/layout/MasterLayout.tsx.txt +63 -0
  220. package/tailwind/components/layout/PageAdvertisements.tsx.txt +131 -0
  221. package/tailwind/components/layout/WidgetHead.tsx.txt +53 -0
  222. package/tailwind/components/layout/index.ts.txt +27 -0
  223. package/tailwind/components/layout/lazy.ts.txt +25 -0
  224. package/tailwind/components/layout/types.ts.txt +11 -0
  225. package/tailwind/components/modal/ImageModal.tsx.txt +70 -0
  226. package/tailwind/components/modal/Modal.tsx.txt +245 -0
  227. package/tailwind/components/modal/index.ts.txt +39 -0
  228. package/tailwind/components/modal/lazy.ts.txt +10 -0
  229. package/tailwind/components/navigation/MainMenu.tsx.txt +94 -0
  230. package/tailwind/components/navigation/MobileMenu.tsx.txt +160 -0
  231. package/tailwind/components/navigation/index.ts.txt +4 -0
  232. package/tailwind/components/navigation/lazy.ts.txt +11 -0
  233. package/tailwind/components/product/AddToCartForm.tsx.txt +365 -0
  234. package/tailwind/components/product/ProductCard.tsx.txt +305 -0
  235. package/tailwind/components/product/ProductDetails.tsx.txt +267 -0
  236. package/tailwind/components/product/ProductGallery.tsx.txt +234 -0
  237. package/tailwind/components/product/index.ts.txt +15 -0
  238. package/tailwind/components/product/lazy.ts.txt +9 -0
  239. package/tailwind/components/snippets/AppsSnippets.tsx.txt +225 -0
  240. package/tailwind/components/snippets/SallaSdkScripts.tsx.txt +14 -0
  241. package/tailwind/components/snippets/index.ts.txt +4 -0
  242. package/tailwind/components/store/StoreClosedNotification.tsx.txt +73 -0
  243. package/tailwind/components/store/index.ts.txt +8 -0
  244. package/tailwind/components/store/lazy.ts.txt +8 -0
  245. package/tailwind/components/toast/Toaster.tsx.txt +177 -0
  246. package/tailwind/components/toast/index.ts.txt +10 -0
  247. package/tailwind/components/toast/types.ts.txt +38 -0
  248. package/tailwind/components/toast/useToast.ts.txt +61 -0
  249. package/tailwind/components/tracking/GoogleTag.tsx.txt +77 -0
  250. package/tailwind/components/tracking/SiftSnippet.tsx.txt +49 -0
  251. package/tailwind/components/tracking/TwilightBundles.tsx.txt +67 -0
  252. package/tailwind/components/tracking/index.ts.txt +11 -0
  253. package/tailwind/components/tracking/lazy.ts.txt +11 -0
  254. package/tailwind/components/useComponent.ts.txt +12 -0
  255. package/tailwind/routes/account/index.tsx.txt +16 -0
  256. package/tailwind/routes/account/notifications/NotificationsPage.tsx.txt +54 -0
  257. package/tailwind/routes/account/notifications/index.tsx.txt +39 -0
  258. package/tailwind/routes/account/notifications/loader.ts.txt +22 -0
  259. package/tailwind/routes/account/notifications/types.ts.txt +18 -0
  260. package/tailwind/routes/account/orders/OrderSinglePage.tsx.txt +181 -0
  261. package/tailwind/routes/account/orders/OrdersPage.tsx.txt +28 -0
  262. package/tailwind/routes/account/orders/index.tsx.txt +86 -0
  263. package/tailwind/routes/account/orders/loader.ts.txt +66 -0
  264. package/tailwind/routes/account/orders/single/OrderActions.tsx.txt +197 -0
  265. package/tailwind/routes/account/orders/single/OrderHeader.tsx.txt +143 -0
  266. package/tailwind/routes/account/orders/single/OrderItemRow.tsx.txt +209 -0
  267. package/tailwind/routes/account/orders/single/OrderSections.tsx.txt +215 -0
  268. package/tailwind/routes/account/orders/single/types.ts.txt +38 -0
  269. package/tailwind/routes/account/orders/types.ts.txt +29 -0
  270. package/tailwind/routes/account/profile/ProfilePage.tsx.txt +30 -0
  271. package/tailwind/routes/account/profile/index.tsx.txt +39 -0
  272. package/tailwind/routes/account/profile/loader.ts.txt +16 -0
  273. package/tailwind/routes/account/profile/types.ts.txt +5 -0
  274. package/tailwind/routes/account/settings/SettingsPage.tsx.txt +13 -0
  275. package/tailwind/routes/account/settings/index.tsx.txt +39 -0
  276. package/tailwind/routes/account/settings/loader.ts.txt +13 -0
  277. package/tailwind/routes/account/settings/types.ts.txt +5 -0
  278. package/tailwind/routes/account/types.ts.txt +320 -0
  279. package/tailwind/routes/account/wallet/WalletPage.tsx.txt +195 -0
  280. package/tailwind/routes/account/wallet/index.tsx.txt +38 -0
  281. package/tailwind/routes/account/wallet/loader.ts.txt +27 -0
  282. package/tailwind/routes/account/wallet/types.ts.txt +11 -0
  283. package/tailwind/routes/account/wishlist/WishlistPage.tsx.txt +314 -0
  284. package/tailwind/routes/account/wishlist/WishlistShare.tsx.txt +248 -0
  285. package/tailwind/routes/account/wishlist/index.tsx.txt +40 -0
  286. package/tailwind/routes/account/wishlist/loader.ts.txt +26 -0
  287. package/tailwind/routes/account/wishlist/types.ts.txt +9 -0
  288. package/tailwind/routes/blog/BlogAuthorPage.tsx.txt +82 -0
  289. package/tailwind/routes/blog/BlogCategoryPage.tsx.txt +84 -0
  290. package/tailwind/routes/blog/BlogPage.tsx.txt +191 -0
  291. package/tailwind/routes/blog/BlogSinglePage.tsx.txt +168 -0
  292. package/tailwind/routes/blog/BlogTagPage.tsx.txt +84 -0
  293. package/tailwind/routes/blog/BlogTags.tsx.txt +54 -0
  294. package/tailwind/routes/blog/components.tsx.txt +87 -0
  295. package/tailwind/routes/blog/index.tsx.txt +238 -0
  296. package/tailwind/routes/blog/loader.ts.txt +173 -0
  297. package/tailwind/routes/blog/types.ts.txt +106 -0
  298. package/tailwind/routes/brands/BrandsPage.tsx.txt +179 -0
  299. package/tailwind/routes/brands/index.tsx.txt +47 -0
  300. package/tailwind/routes/brands/loader.ts.txt +14 -0
  301. package/tailwind/routes/brands/types.ts.txt +53 -0
  302. package/tailwind/routes/cart/CartPage.tsx.txt +112 -0
  303. package/tailwind/routes/cart/index.tsx.txt +39 -0
  304. package/tailwind/routes/cart/loader.ts.txt +15 -0
  305. package/tailwind/routes/cart/types.ts.txt +6 -0
  306. package/tailwind/routes/home/HomePage.tsx.txt +53 -0
  307. package/tailwind/routes/home/index.tsx.txt +56 -0
  308. package/tailwind/routes/home/loader.ts.txt +24 -0
  309. package/tailwind/routes/home/types.ts.txt +11 -0
  310. package/tailwind/routes/index.ts.txt +94 -0
  311. package/tailwind/routes/lazy.ts.txt +105 -0
  312. package/tailwind/routes/loyalty/LoyaltyPage.tsx.txt +17 -0
  313. package/tailwind/routes/loyalty/LoyaltyPointsPage.tsx.txt +11 -0
  314. package/tailwind/routes/loyalty/index.tsx.txt +81 -0
  315. package/tailwind/routes/loyalty/loader.ts.txt +53 -0
  316. package/tailwind/routes/loyalty/types.ts.txt +109 -0
  317. package/tailwind/routes/not-found.tsx.txt +50 -0
  318. package/tailwind/routes/page/PageSingle.tsx.txt +34 -0
  319. package/tailwind/routes/page/index.tsx.txt +60 -0
  320. package/tailwind/routes/page/loader.ts.txt +19 -0
  321. package/tailwind/routes/page/types.ts.txt +34 -0
  322. package/tailwind/routes/product/ProductPage.tsx.txt +77 -0
  323. package/tailwind/routes/product/index.tsx.txt +157 -0
  324. package/tailwind/routes/product/loader.ts.txt +35 -0
  325. package/tailwind/routes/product/types.ts.txt +6 -0
  326. package/tailwind/routes/product-listing/ProductListPage.tsx.txt +321 -0
  327. package/tailwind/routes/product-listing/index.tsx.txt +95 -0
  328. package/tailwind/routes/product-listing/loader.ts.txt +323 -0
  329. package/tailwind/routes/product-listing/types.ts.txt +96 -0
  330. package/tailwind/routes/product-listing/useAppliedFilters.ts.txt +130 -0
  331. package/tailwind/routes/product-short-link.ts.txt +104 -0
  332. package/tailwind/routes/root/beforeLoad.ts.txt +354 -0
  333. package/tailwind/routes/root/head.ts.txt +424 -0
  334. package/tailwind/routes/root/index.ts.txt +2 -0
  335. package/tailwind/routes/seo-redirects.ts.txt +82 -0
  336. package/tailwind/routes/testimonials/ReviewsPage.tsx.txt +12 -0
  337. package/tailwind/routes/testimonials/TestimonialsPage.tsx.txt +80 -0
  338. package/tailwind/routes/testimonials/index.tsx.txt +80 -0
  339. package/tailwind/routes/testimonials/loader.ts.txt +29 -0
  340. package/tailwind/routes/testimonials/types.ts.txt +9 -0
  341. package/tailwind/routes/thank-you/ThankYouPage.tsx.txt +241 -0
  342. package/tailwind/routes/thank-you/index.tsx.txt +49 -0
  343. package/tailwind/routes/thank-you/loader.ts.txt +40 -0
  344. package/tailwind/routes/thank-you/types.ts.txt +16 -0
  345. package/tailwind/routes/types.ts.txt +41 -0
  346. package/dist/BlogAuthorPage-GTF4UGSS.js.map +0 -1
  347. package/dist/BlogCategoryPage-TN7Y4NBP.js.map +0 -1
  348. package/dist/BlogPage-TDB3M3PI.js.map +0 -1
  349. package/dist/BlogSinglePage-4A52Q5QW.js.map +0 -1
  350. package/dist/BlogTagPage-TOVPBETA.js.map +0 -1
  351. package/dist/BrandsPage-VUDGYSQI.js.map +0 -1
  352. package/dist/Breadcrumb-CI3SN5YR.js.map +0 -1
  353. package/dist/CartItem-BHLEURAB.js.map +0 -1
  354. package/dist/CartSummary-QERVESYN.js.map +0 -1
  355. package/dist/CustomerLayout-VRZUWGQK.js.map +0 -1
  356. package/dist/LoyaltyPage-CGTHWJ76.js.map +0 -1
  357. package/dist/LoyaltyPointsPage-QSU6K3PW.js.map +0 -1
  358. package/dist/MobileMenu-U4BVQYKH.js.map +0 -1
  359. package/dist/NotificationsPage-ZRRQY7TF.js.map +0 -1
  360. package/dist/OrderSinglePage-O3XYVMG5.js.map +0 -1
  361. package/dist/OrdersPage-CMXZF5MI.js.map +0 -1
  362. package/dist/ProductDetails-65GDABI7.js.map +0 -1
  363. package/dist/ProfilePage-SNJ4WXU4.js.map +0 -1
  364. package/dist/SeoCartWidget-DBJAXJKT.js.map +0 -1
  365. package/dist/SettingsPage-FLTIOPU5.js.map +0 -1
  366. package/dist/ThankYouPage-5X6B4X3T.js.map +0 -1
  367. package/dist/WalletPage-H2BKIDPA.js.map +0 -1
  368. package/dist/WishlistPage-RS5UCIQ3.js.map +0 -1
  369. package/dist/chunk-7WAXN2PP.js +0 -14
  370. package/dist/chunk-NZVR4OF7.js.map +0 -1
  371. package/dist/chunk-SDFDS4Q3.js.map +0 -1
  372. package/dist/not-found-QTHBFPNT.js.map +0 -1
@@ -0,0 +1,48 @@
1
+ import { memo, useEffect, useState } from 'react';
2
+
3
+ export interface YoutubeProps {
4
+ data: {
5
+ youtube_id: string;
6
+ position?: number;
7
+ [key: string]: unknown;
8
+ };
9
+ }
10
+
11
+ export const Youtube = memo(function Youtube({ data }: YoutubeProps) {
12
+ const { youtube_id: youtubeId } = data;
13
+ const [isLoaded, setIsLoaded] = useState(false);
14
+
15
+ useEffect(() => {
16
+ if (!youtubeId) return;
17
+
18
+ // Lazy load the lite-youtube web component
19
+ let mounted = true;
20
+ import('@justinribeiro/lite-youtube')
21
+ .then(() => {
22
+ if (mounted) setIsLoaded(true);
23
+ })
24
+ .catch((err) => {
25
+ console.error('Failed to load lite-youtube:', err);
26
+ });
27
+
28
+ return () => {
29
+ mounted = false;
30
+ };
31
+ }, [youtubeId]);
32
+
33
+ if (!youtubeId) return null;
34
+
35
+ return (
36
+ <>
37
+ <div className="container">
38
+ <div className="videoWrapper">
39
+ {isLoaded ? (
40
+ <lite-youtube videoid={youtubeId} params="rel=0"></lite-youtube>
41
+ ) : (
42
+ <div className="animate-pulse bg-gray-200 w-full h-full min-h-[300px] rounded" />
43
+ )}
44
+ </div>
45
+ </div>
46
+ </>
47
+ );
48
+ });
@@ -0,0 +1,50 @@
1
+ import {
2
+ LazyProductsSlider,
3
+ LazyStoreFeatures,
4
+ LazyFixedBanner,
5
+ LazyFixedProducts,
6
+ LazyFeaturedProductsStyle1,
7
+ LazyFeaturedProductsStyle2,
8
+ LazyFeaturedProductsStyle3,
9
+ LazyParallaxBackground,
10
+ LazyPhotosSlider,
11
+ LazySquarePhotos,
12
+ LazyTestimonials,
13
+ LazyYoutube,
14
+ LazyBundleComponent,
15
+ } from './lazy';
16
+
17
+ export {
18
+ LazyProductsSlider as ProductsSlider,
19
+ LazyStoreFeatures as StoreFeatures,
20
+ LazyFixedBanner as FixedBanner,
21
+ LazyFixedProducts as FixedProducts,
22
+ LazyFeaturedProductsStyle1 as FeaturedProductsStyle1,
23
+ LazyFeaturedProductsStyle2 as FeaturedProductsStyle2,
24
+ LazyFeaturedProductsStyle3 as FeaturedProductsStyle3,
25
+ LazyParallaxBackground as ParallaxBackground,
26
+ LazyPhotosSlider as PhotosSlider,
27
+ LazySquarePhotos as SquarePhotos,
28
+ LazyTestimonials as Testimonials,
29
+ LazyYoutube as Youtube,
30
+ LazyBundleComponent as BundleComponent,
31
+ };
32
+
33
+ // Shared types
34
+ export type { AnyHomeComponent } from './types';
35
+ export type { BundleComponentProps } from './BundleComponent';
36
+
37
+ // Re-export from HomePageRenderer
38
+ export type { HomeComponentsProps, HomeComponentConfig } from './HomePageRenderer';
39
+
40
+ // Home registration (DefaultHomeComponents + registerHomeComponents)
41
+ export { DefaultHomeComponents, registerHomeComponents } from './register';
42
+
43
+ // Home page infrastructure
44
+ export { HomeComponentRenderer, registerHomeComponentConfig } from './HomePageRenderer';
45
+ export { PageBlocks } from './PageBlocks';
46
+ export type { PageBlocksProps } from './PageBlocks';
47
+ export { ComponentErrorBoundary } from './ComponentErrorBoundary';
48
+
49
+ export { ReorderProducts } from './ReorderProducts';
50
+ export { useReorderButtons } from './reorder-hooks';
@@ -0,0 +1,17 @@
1
+ import { lazy } from 'react';
2
+
3
+ export { ProductsSlider as LazyProductsSlider } from './ProductsSlider';
4
+ export { StoreFeatures as LazyStoreFeatures } from './StoreFeatures';
5
+ export { FixedBanner as LazyFixedBanner } from './FixedBanner';
6
+ export { FixedProducts as LazyFixedProducts } from './FixedProducts';
7
+ export { FeaturedProductsStyle1 as LazyFeaturedProductsStyle1 } from './FeaturedProductsStyle1';
8
+ export { FeaturedProductsStyle2 as LazyFeaturedProductsStyle2 } from './FeaturedProductsStyle2';
9
+ export { FeaturedProductsStyle3 as LazyFeaturedProductsStyle3 } from './FeaturedProductsStyle3';
10
+ export { ParallaxBackground as LazyParallaxBackground } from './ParallaxBackground';
11
+ export { PhotosSlider as LazyPhotosSlider } from './PhotosSlider';
12
+ export { SquarePhotos as LazySquarePhotos } from './SquarePhotos';
13
+ export const LazyTestimonials = lazy(() =>
14
+ import('./Testimonials').then((m) => ({ default: m.Testimonials }))
15
+ );
16
+ export { Youtube as LazyYoutube } from './Youtube';
17
+ export { BundleComponent as LazyBundleComponent } from './BundleComponent';
@@ -0,0 +1,44 @@
1
+ import {
2
+ LazyProductsSlider,
3
+ LazyStoreFeatures,
4
+ LazyFixedBanner,
5
+ LazyFixedProducts,
6
+ LazyFeaturedProductsStyle1,
7
+ LazyFeaturedProductsStyle2,
8
+ LazyFeaturedProductsStyle3,
9
+ LazyParallaxBackground,
10
+ LazyPhotosSlider,
11
+ LazySquarePhotos,
12
+ LazyTestimonials,
13
+ LazyYoutube,
14
+ LazyBundleComponent,
15
+ } from './lazy';
16
+ import type { AnyHomeComponent } from './types';
17
+ import { registry } from '../ComponentRegistry';
18
+
19
+ export const DefaultHomeComponents: Record<string, AnyHomeComponent | null> = {
20
+ 'fixed-banner': LazyFixedBanner,
21
+ 'photos-slider': LazyPhotosSlider,
22
+ testimonial: LazyTestimonials,
23
+ 'store-features': LazyStoreFeatures,
24
+ youtube: LazyYoutube,
25
+ 'square-photos': LazySquarePhotos,
26
+ 'fixed-products': LazyFixedProducts,
27
+ 'products-slider': LazyProductsSlider,
28
+ 'parallax-background': LazyParallaxBackground,
29
+ 'featured-products:style1': LazyFeaturedProductsStyle1,
30
+ 'featured-products:style2': LazyFeaturedProductsStyle2,
31
+ 'featured-products:style3': LazyFeaturedProductsStyle3,
32
+ 'bundle-component': LazyBundleComponent,
33
+ };
34
+
35
+ export function registerHomeComponents(
36
+ components: Record<string, AnyHomeComponent | null> = DefaultHomeComponents,
37
+ prefix = 'home:'
38
+ ): void {
39
+ Object.entries(components).forEach(([path, component]) => {
40
+ if (component) {
41
+ registry.register(`${prefix}${path}`, component);
42
+ }
43
+ });
44
+ }
@@ -0,0 +1,52 @@
1
+ import { useEffect } from 'react';
2
+ import { useTranslation } from '../../providers/I18nProvider';
3
+ import { whenSallaLoaded } from '../../utils/sdk-helpers';
4
+
5
+ interface ReorderLabels {
6
+ /** The add button's text in a reorder list. */
7
+ add: string;
8
+ /** Its text for an out-of-stock product. */
9
+ outOfStock: string;
10
+ }
11
+
12
+ let registered = false;
13
+
14
+ /**
15
+ * Twig's ReorderProductsWidget script: inside a reorder list the add button reads the block's
16
+ * title and adds the product as the customer last ordered it. The SDK has no way to remove a
17
+ * hook, so it is registered once per page load.
18
+ */
19
+ function registerReorderHooks(labels: ReorderLabels): void {
20
+ if (registered) return;
21
+ registered = true;
22
+ whenSallaLoaded((sdk) => {
23
+ sdk.hooks?.registerHook?.('salla-add-product-button', 'componentDidLoad', (component) => {
24
+ if (!component.host?.closest('[source="reorder"], [data-source="reorder"]')) return;
25
+ const isOutOfStock =
26
+ component.productStatus === 'out' || component.productStatus === 'out-and-notify';
27
+ const setLabel = () => component.btn?.setText(isOutOfStock ? labels.outOfStock : labels.add);
28
+ setLabel();
29
+ sdk.lang?.onLoaded?.(setLabel);
30
+ if (isOutOfStock) return;
31
+ component.addProductToCart = async (event: Event) => {
32
+ event.preventDefault();
33
+ // The click lands on the inner button, which has no product-id; without one the SDK uses the page's.
34
+ const productId = component.productId ?? component.host.getAttribute('product-id');
35
+ await sdk.order.api.reorderProduct?.(productId ?? undefined);
36
+ };
37
+ });
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Inside an element with `data-source="reorder"`, make each add button read "order again" and
43
+ * add the product as the customer last ordered it, as Twig's reorder block and page do.
44
+ */
45
+ export function useReorderButtons(enabled: boolean): void {
46
+ const { t } = useTranslation();
47
+ const add = t('pages.orders.repurchase');
48
+ const outOfStock = t('pages.products.out_of_stock');
49
+ useEffect(() => {
50
+ if (enabled) registerReorderHooks({ add, outOfStock });
51
+ }, [enabled, add, outOfStock]);
52
+ }
@@ -0,0 +1,4 @@
1
+ import type { ComponentType } from 'react';
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Dynamic registry requires `any` for data prop (specific component props can't satisfy contravariance with Record<string, unknown>)
4
+ export type AnyHomeComponent = ComponentType<{ data: any; priority?: boolean }>;
@@ -0,0 +1,23 @@
1
+ import { useTranslation } from '../../providers/I18nProvider';
2
+ import { Link } from '../common';
3
+
4
+ export interface CopyrightProps {
5
+ storeName?: string;
6
+ }
7
+
8
+ /**
9
+ * Simple copyright component - shows "Copyright | Year StoreName | Powered by Salla"
10
+ */
11
+ export function Copyright({ storeName = '' }: CopyrightProps) {
12
+ const { t } = useTranslation();
13
+ const year = new Date().getFullYear();
14
+
15
+ return (
16
+ <>
17
+ {t('blocks.footer.copyright', 'Copyright')} | {year}{' '}
18
+ <Link to="/" className="hover:text-primary">
19
+ {storeName}
20
+ </Link>{' '}
21
+ </>
22
+ );
23
+ }
@@ -0,0 +1,91 @@
1
+ import { useMemo } from 'react';
2
+ import { useUser } from '../../hooks';
3
+ import { useDocumentClass } from '../../hooks/useDocumentClass';
4
+ import { SallaFileUpload } from '@salla.sa/twilight-components-react/file-upload';
5
+ import { SallaUserMenu } from '@salla.sa/twilight-components-react/user-menu';
6
+ import { Breadcrumb } from '../common';
7
+ import type { LayoutProps } from './types';
8
+ import { useTranslation } from '../../providers/I18nProvider';
9
+ import { useLocation } from '../../providers/NavigationProvider';
10
+ import type { Page } from '../../types';
11
+ import { RouteId } from '../../router/route-ids';
12
+
13
+ interface CustomerLayoutProps extends LayoutProps {
14
+ page?: Page;
15
+ }
16
+
17
+ export function CustomerLayout({ children, page }: CustomerLayoutProps) {
18
+ const { data: user } = useUser();
19
+ const { t } = useTranslation();
20
+ const { pathname } = useLocation();
21
+
22
+ // Twig's salla-breadcrumb puts "My account" before every customer page but these two.
23
+ const crumbPage = useMemo(() => {
24
+ if (
25
+ !page ||
26
+ page.breadcrumbs?.length ||
27
+ ([RouteId.CUSTOMER_PROFILE, RouteId.CUSTOMER_WISHLIST] as string[]).includes(page.slug)
28
+ ) {
29
+ return page;
30
+ }
31
+ return {
32
+ ...page,
33
+ breadcrumbs: [
34
+ { name: t('common.titles.home'), url: '/' },
35
+ { name: t('common.titles.profile'), url: '/account/profile' },
36
+ { name: page.title, url: pathname },
37
+ ],
38
+ };
39
+ }, [page, pathname, t]);
40
+
41
+ // Marks every customer/account page so a theme can scope styles to them
42
+ // (route classes vary per page; this one is shared).
43
+ useDocumentClass({ body: { class: 'customer' } });
44
+
45
+ return (
46
+ <>
47
+ <div className="profile-header gradient-bg">
48
+ <div className="container relative flex justify-between h-full items-center">
49
+ {crumbPage && (
50
+ <Breadcrumb page={crumbPage} className="breadcrumbs w-full py-5 self-start" />
51
+ )}
52
+ </div>
53
+ </div>
54
+
55
+ {/* Main content area */}
56
+ <div className="container lg:mb-24">
57
+ <div className="flex items-start flex-col lg:flex-row justify-center">
58
+ <nav
59
+ aria-label="Sidebar"
60
+ className="shrink-0 lg:sticky top-24 lg:-mt-28 lg:shadow-default rounded-md lg:bg-white w-full lg:w-72 rtl:lg:ml-8 ltr:lg:mr-8 lg:pt-8 lg:pb-11 sidebar space-y-px"
61
+ >
62
+ {page?.slug === RouteId.CUSTOMER_PROFILE && (
63
+ <div className="text-center md:mb-9">
64
+ <div className="h-20 w-20 m-auto avatar-wrap bg-gray-100 rounded-full overflow-hidden">
65
+ <SallaFileUpload profileImage value={user?.avatar}>
66
+ <span className="avatar-placeholder flex justify-center items-center flex-col w-full h-full">
67
+ <span className="sicon-user text-2xl text-gray-400"></span>
68
+ </span>
69
+ </SallaFileUpload>
70
+ </div>
71
+ </div>
72
+ )}
73
+
74
+ <div className="hidden lg:block">
75
+ <SallaUserMenu inline />
76
+ </div>
77
+ </nav>
78
+
79
+ <div className="main-content w-full flex-1 lg:pt-10 max-w-3xl">
80
+ {page?.title && (
81
+ <h1 className="font-bold text-lg text-center sm:rtl:text-right sm:ltr:text-left mb-2">
82
+ {page.title}
83
+ </h1>
84
+ )}
85
+ {children}
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </>
90
+ );
91
+ }
@@ -0,0 +1,343 @@
1
+ import { useState } from 'react';
2
+ import { SallaAppsIcons } from '@salla.sa/twilight-components-react/apps-icons';
3
+ import { SallaSocial } from '@salla.sa/twilight-components-react/social';
4
+ import { SallaMenu } from '@salla.sa/twilight-components-react/menu';
5
+ import { SallaTrustBadges } from '@salla.sa/twilight-components-react/trust-badges';
6
+ import { SallaPayments } from '@salla.sa/twilight-components-react/payments';
7
+ import { useTwilight } from '../../providers/twilight-context';
8
+ import { useTranslation } from '../../providers/I18nProvider';
9
+ import { HookSlot } from '../../hooks/HookSlot';
10
+ import { Link, Image } from '../common';
11
+ import { ImageModal } from '../modal';
12
+
13
+ /** Salla's value-added-tax badge icon (70×70, CDN-resized) — a platform
14
+ * asset shared by every store, not specific to any one theme. */
15
+ const TAX_ICON_URL =
16
+ 'https://cdn.salla.network/cdn-cgi/image/fit=scale-down,width=70,height=70,onerror=redirect,format=auto/images/tax.png?v=2.0.5';
17
+
18
+ /**
19
+ * The store's value-added-tax registration: the tax number, plus the certificate as
20
+ * the tax icon that opens the certificate image full size in a modal. Nothing shows
21
+ * when the store has no tax number.
22
+ */
23
+ function FooterTax({ dark }: { dark: boolean }) {
24
+ const { store } = useTwilight();
25
+ const { t } = useTranslation();
26
+ const [open, setOpen] = useState(false);
27
+ const number = store.settings?.tax?.number;
28
+ const certificate = store.settings?.tax?.certificate;
29
+ if (!number) return null;
30
+
31
+ return (
32
+ <div
33
+ data-testid="store-footer-tax"
34
+ className="store-footer__tax flex items-end gap-2 rtl:space-x-reverse"
35
+ >
36
+ {certificate && (
37
+ <>
38
+ <button
39
+ type="button"
40
+ data-testid="store-footer-tax-certificate"
41
+ className="store-footer__tax-cert shrink-0"
42
+ aria-label={t('common.elements.tax_certificate', 'Tax Certificate')}
43
+ aria-haspopup="dialog"
44
+ onClick={() => setOpen(true)}
45
+ >
46
+ <img
47
+ src={TAX_ICON_URL}
48
+ alt={t('common.elements.tax_certificate', 'Tax Certificate')}
49
+ width={40}
50
+ height={50}
51
+ loading="lazy"
52
+ decoding="async"
53
+ className="w-10 rounded-sm transition-opacity hover:opacity-80"
54
+ />
55
+ </button>
56
+ <ImageModal
57
+ isOpen={open}
58
+ onClose={() => setOpen(false)}
59
+ src={certificate}
60
+ alt={t('common.elements.tax_certificate', 'Tax Certificate')}
61
+ title={t('common.elements.tax_certificate', 'Tax Certificate')}
62
+ />
63
+ </>
64
+ )}
65
+ <div>
66
+ <p className={`mb-1 text-sm opacity-70 ${dark ? 'text-white' : ''}`}>
67
+ {t('common.elements.tax_number', 'Tax Number')}
68
+ </p>
69
+ <b className="text-sm">{number}</b>
70
+ </div>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ /** Saudi Business Center (SBC) logo, CDN-versioned like the tax icon -- also a
76
+ * platform asset, not theme-specific. */
77
+ const SBC_ICON_URL = 'https://cdn.salla.network/images/sbc.png?v=2.0.5';
78
+
79
+ /**
80
+ * The store's Saudi Business Center certificate: a "Trusted by Business Platform" badge that
81
+ * opens the certificate's page on the SBC site so visitors can verify the merchant. Nothing
82
+ * shows when the store has no certificate.
83
+ */
84
+ function FooterSbc({ dark }: { dark: boolean }) {
85
+ const { store } = useTwilight();
86
+ const { t } = useTranslation();
87
+ const id = store.settings?.certificate?.id;
88
+ if (!id) return null;
89
+
90
+ return (
91
+ <a
92
+ href={`https://eauthenticate.saudibusiness.gov.sa/certificate-details/${encodeURIComponent(id)}`}
93
+ target="_blank"
94
+ rel="noopener noreferrer"
95
+ data-testid="store-footer-sbc"
96
+ className="store-footer__sbc flex items-center gap-2.5 transition-opacity hover:opacity-75"
97
+ >
98
+ <img
99
+ src={SBC_ICON_URL}
100
+ alt={`SBC ${id}`}
101
+ width={56}
102
+ height={40}
103
+ loading="lazy"
104
+ decoding="async"
105
+ className="h-10 w-14 rounded-sm"
106
+ />
107
+ <div>
108
+ <p className={`text-xs opacity-70 ${dark ? 'text-white' : ''}`}>
109
+ {t('blocks.footer.sbc_trusted_by', 'Trusted by')}
110
+ </p>
111
+ <b className="text-sm">{t('blocks.footer.sbc_platform', 'Business Platform')}</b>
112
+ </div>
113
+ </a>
114
+ );
115
+ }
116
+
117
+ /** Trust badges, the tax registration and the SBC badge, side by side (wrapping on narrow columns). */
118
+ function FooterTrust({ dark, centered }: { dark: boolean; centered: boolean }) {
119
+ return (
120
+ <div
121
+ data-testid="store-footer-trust"
122
+ className={`store-footer__trust flex flex-wrap items-end gap-x-6 gap-y-3${centered ? ' justify-center' : ''}`}
123
+ >
124
+ <FooterTax dark={dark} />
125
+ <FooterSbc dark={dark} />
126
+ <SallaTrustBadges {...(dark ? { dark: true } : {})} />
127
+ </div>
128
+ );
129
+ }
130
+
131
+ /**
132
+ * The footer's own menu of ("important") links -- skipped entirely when
133
+ * `inSheet`, for a theme that opens it from somewhere else of its own (a
134
+ * mobile account sheet, a dedicated links screen, …) instead; showing it
135
+ * again down here would just be a redundant second copy. `heading` wraps it
136
+ * in the grid layout's own `.store-footer__links` column; the centered
137
+ * layout's inline row has none.
138
+ */
139
+ function FooterLinksMenu({
140
+ inSheet,
141
+ className,
142
+ heading,
143
+ dark,
144
+ }: {
145
+ inSheet: boolean;
146
+ className: string;
147
+ heading?: string;
148
+ dark: boolean;
149
+ }) {
150
+ if (inSheet) return null;
151
+
152
+ const menu = (
153
+ <SallaMenu source="footer" useReactLink className={className} suppressHydrationWarning />
154
+ );
155
+ if (!heading) return menu;
156
+
157
+ return (
158
+ <div className="store-footer__links">
159
+ <h3 className={`mb-3 text-lg font-bold lg:mb-5 ${dark ? 'text-white' : 'text-gray-900'}`}>
160
+ {heading}
161
+ </h3>
162
+ {menu}
163
+ </div>
164
+ );
165
+ }
166
+
167
+ /** The logo link, same shape in both layouts -- only its own spacing differs. */
168
+ function FooterLogo({ className }: { className: string }) {
169
+ const { store } = useTwilight();
170
+ return (
171
+ <Link
172
+ to="/"
173
+ className={`store-footer__logo inline-flex items-center m-0 transition-opacity hover:opacity-75 ${className}`}
174
+ >
175
+ <Image
176
+ src={store.logo}
177
+ alt={store.name || 'Store'}
178
+ width={150}
179
+ height={44}
180
+ objectFit="contain"
181
+ className="h-11 w-auto max-w-[180px]"
182
+ />
183
+ </Link>
184
+ );
185
+ }
186
+
187
+ /** Merchant-authored description HTML -- mirrors the Twig theme's
188
+ * `store.description|raw`. Renders nothing when the store has none set. */
189
+ function FooterDescription({ className }: { className: string }) {
190
+ const { store } = useTwilight();
191
+ if (!store.description) return null;
192
+ return (
193
+ <div
194
+ className={`store-footer__desc opacity-80 ${className}`}
195
+ dangerouslySetInnerHTML={{ __html: store.description }}
196
+ />
197
+ );
198
+ }
199
+
200
+ /** Centered layout: logo, description, social, footer menu, trust badges and
201
+ * app buttons stacked as one centered column. */
202
+ function FooterCenteredBody({ inSheet, isDark }: { inSheet: boolean; isDark: boolean }) {
203
+ const { store } = useTwilight();
204
+ const { t } = useTranslation();
205
+
206
+ return (
207
+ <div className="container mx-auto flex max-w-2xl flex-col items-center gap-6 text-center">
208
+ <FooterLogo className="" />
209
+ <FooterDescription className="" />
210
+ <SallaSocial />
211
+
212
+ <FooterLinksMenu
213
+ inSheet={inSheet}
214
+ className="store-footer__menu store-footer__menu--inline"
215
+ dark={isDark}
216
+ />
217
+
218
+ <FooterTrust dark={isDark} centered />
219
+
220
+ <div className="store-footer__apps flex flex-col items-center gap-3">
221
+ <SallaAppsIcons
222
+ apps={store.apps}
223
+ appsTitle={t('blocks.footer.download_apps', 'Get our apps')}
224
+ />
225
+ </div>
226
+ </div>
227
+ );
228
+ }
229
+
230
+ /** Columns layout: brand (logo / description / social / trust) and app
231
+ * buttons side by side, with the footer menu as its own labeled column. */
232
+ function FooterColumnsBody({ inSheet, isDark }: { inSheet: boolean; isDark: boolean }) {
233
+ const { store } = useTwilight();
234
+ const { t } = useTranslation();
235
+
236
+ return (
237
+ <div className="container grid grid-cols-1 gap-10 sm:grid-cols-2 lg:grid-cols-4 lg:gap-16">
238
+ <div className="store-footer__brand rtl:lg:pl-16 ltr:lg:pr-16 sm:col-span-2">
239
+ <FooterLogo className="mb-5" />
240
+ <FooterDescription className="max-w-sm leading-6 my-5" />
241
+ <SallaSocial className="block mb-6" />
242
+ <FooterTrust dark={isDark} centered={false} />
243
+ </div>
244
+
245
+ <FooterLinksMenu
246
+ inSheet={inSheet}
247
+ className="store-footer__menu store-footer__menu--stacked"
248
+ heading={t('blocks.footer.important_links', 'Important links')}
249
+ dark={isDark}
250
+ />
251
+
252
+ <div className="store-footer__apps">
253
+ <SallaAppsIcons
254
+ apps={store.apps}
255
+ appsTitle={t('blocks.footer.download_apps', 'Get our apps')}
256
+ />
257
+ </div>
258
+ </div>
259
+ );
260
+ }
261
+
262
+ export interface FooterProps {
263
+ /**
264
+ * Renders as a plain `<div>` (no second `<footer>` landmark) with the
265
+ * `footer:start` / `footer:end` hook slots skipped -- for a theme that
266
+ * repeats these widgets somewhere other than the page's own footer (a
267
+ * mobile sheet, for example) rather than rendering the real page footer a
268
+ * second time.
269
+ */
270
+ inSheet?: boolean;
271
+ }
272
+
273
+ /**
274
+ * Storefront footer.
275
+ *
276
+ * Two layouts, chosen by the `footer_layout` theme setting:
277
+ * - **columns** (default): logo / description / social / trust badges, the
278
+ * footer menu ("important links"), and app-store buttons, side by side.
279
+ * - **centered**: the same pieces stacked as one centered column, with the
280
+ * footer menu laid out as an inline, pipe-separated row.
281
+ *
282
+ * Styling for the handful of pieces rendered by web components this owns no
283
+ * markup for (`<salla-social>`'s `.social-link` icons, `<salla-menu>`'s
284
+ * `.s-menu-footer-item` rows, `<salla-payments>`'s `.s-payments-list-item`
285
+ * icons) is the consuming theme's own responsibility, the same way
286
+ * `inSheet`'s own presentation is -- this component owns structure and data,
287
+ * not every pixel.
288
+ */
289
+ export function Footer({ inSheet = false }: FooterProps = {}) {
290
+ const { store, theme } = useTwilight();
291
+ const { t } = useTranslation();
292
+ const year = new Date().getFullYear();
293
+ const isCentered = theme.settings?.footer_layout === 'centered';
294
+ const isDark = !!theme.settings?.footer_is_dark;
295
+
296
+ const Root = inSheet ? 'div' : 'footer';
297
+
298
+ return (
299
+ <Root
300
+ className={`store-footer store-footer--${isCentered ? 'centered' : 'columns'}${
301
+ inSheet ? ' store-footer--sheet' : ''
302
+ } relative mt-8 sm:mt-16 ${isDark ? 'bg-[#0E0F0F] text-white' : 'bg-[#F4F7F2] text-gray-600'}`}
303
+ suppressHydrationWarning
304
+ >
305
+ {!inSheet && <HookSlot name="footer:start" />}
306
+
307
+ <div
308
+ aria-label="footer"
309
+ className={`store-footer__inner border-b py-8 lg:py-16 ${
310
+ isDark ? 'bg-[#1D1F1F] border-dashed border-white/10' : 'bg-transparent border-gray-200'
311
+ }`}
312
+ >
313
+ {isCentered ? (
314
+ <FooterCenteredBody inSheet={inSheet} isDark={isDark} />
315
+ ) : (
316
+ <FooterColumnsBody inSheet={inSheet} isDark={isDark} />
317
+ )}
318
+ </div>
319
+
320
+ <div className="store-footer__bottom container md:flex items-center justify-between gap-4 py-4 lg:py-7 text-center">
321
+ <span className={`text-sm copyright-text ${isDark ? 'text-gray-400' : 'text-gray-700'}`}>
322
+ <HookSlot
323
+ name="copyright"
324
+ context={{ storeName: store.name }}
325
+ fallback={
326
+ <>
327
+ {t('blocks.footer.copyright', 'All rights reserved')}{' '}
328
+ <Link to="/" className="transition-opacity hover:opacity-75 hover:text-primary">
329
+ {store.name}
330
+ </Link>{' '}
331
+ © {year}
332
+ </>
333
+ }
334
+ />
335
+ </span>
336
+
337
+ <SallaPayments />
338
+ </div>
339
+
340
+ {!inSheet && <HookSlot name="footer:end" />}
341
+ </Root>
342
+ );
343
+ }