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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (390) hide show
  1. package/README.md +4 -3
  2. package/bin/twilight-check-manifest.mjs +16 -0
  3. package/dist/{AddToCartForm-B5C2M4OZ.js → AddToCartForm-P644O2QL.js} +4 -4
  4. package/dist/{AddToCartForm-B5C2M4OZ.js.map → AddToCartForm-P644O2QL.js.map} +1 -1
  5. package/dist/{BlogAuthorPage-GTF4UGSS.js → BlogAuthorPage-VWSO6MPY.js} +13 -11
  6. package/dist/BlogAuthorPage-VWSO6MPY.js.map +1 -0
  7. package/dist/{BlogCategoryPage-TN7Y4NBP.js → BlogCategoryPage-K2KSQ6T6.js} +13 -11
  8. package/dist/BlogCategoryPage-K2KSQ6T6.js.map +1 -0
  9. package/dist/{BlogPage-TDB3M3PI.js → BlogPage-XRM7VWOZ.js} +12 -10
  10. package/dist/BlogPage-XRM7VWOZ.js.map +1 -0
  11. package/dist/{BlogSinglePage-4A52Q5QW.js → BlogSinglePage-XIAXT54H.js} +12 -10
  12. package/dist/BlogSinglePage-XIAXT54H.js.map +1 -0
  13. package/dist/{BlogTagPage-TOVPBETA.js → BlogTagPage-IHOA267V.js} +13 -11
  14. package/dist/BlogTagPage-IHOA267V.js.map +1 -0
  15. package/dist/{BrandsPage-VUDGYSQI.js → BrandsPage-ZFEILRT3.js} +12 -10
  16. package/dist/BrandsPage-ZFEILRT3.js.map +1 -0
  17. package/dist/{Breadcrumb-CI3SN5YR.js → Breadcrumb-CVSISL2K.js} +12 -10
  18. package/dist/Breadcrumb-CVSISL2K.js.map +1 -0
  19. package/dist/{CartItem-BHLEURAB.js → CartItem-EOI7P3JA.js} +12 -10
  20. package/dist/CartItem-EOI7P3JA.js.map +1 -0
  21. package/dist/{CartSummary-QERVESYN.js → CartSummary-RZGUBDKJ.js} +12 -10
  22. package/dist/CartSummary-RZGUBDKJ.js.map +1 -0
  23. package/dist/{Copyright-RL6ALKRC.js → Copyright-5BLYSP24.js} +4 -4
  24. package/dist/{Copyright-RL6ALKRC.js.map → Copyright-5BLYSP24.js.map} +1 -1
  25. package/dist/{CustomerLayout-VRZUWGQK.js → CustomerLayout-6F4BJAMU.js} +12 -10
  26. package/dist/CustomerLayout-6F4BJAMU.js.map +1 -0
  27. package/dist/{Footer-ZWBZCMDL.js → Footer-NF2NLDLR.js} +6 -6
  28. package/dist/{Footer-ZWBZCMDL.js.map → Footer-NF2NLDLR.js.map} +1 -1
  29. package/dist/{Header-WNK4O66B.js → Header-6DQJ5X7J.js} +9 -7
  30. package/dist/{Header-WNK4O66B.js.map → Header-6DQJ5X7J.js.map} +1 -1
  31. package/dist/{LoyaltyPage-CGTHWJ76.js → LoyaltyPage-YHWHIWCS.js} +12 -10
  32. package/dist/LoyaltyPage-YHWHIWCS.js.map +1 -0
  33. package/dist/{LoyaltyPointsPage-QSU6K3PW.js → LoyaltyPointsPage-T6K3H5A5.js} +10 -3
  34. package/dist/LoyaltyPointsPage-T6K3H5A5.js.map +1 -0
  35. package/dist/{MainMenu-V3T7F32R.js → MainMenu-Y2TAM7DK.js} +4 -4
  36. package/dist/{MainMenu-V3T7F32R.js.map → MainMenu-Y2TAM7DK.js.map} +1 -1
  37. package/dist/{MasterLayout-XQF644LD.js → MasterLayout-GBEEFFPG.js} +12 -10
  38. package/dist/{MasterLayout-XQF644LD.js.map → MasterLayout-GBEEFFPG.js.map} +1 -1
  39. package/dist/{MobileMenu-U4BVQYKH.js → MobileMenu-RE5XVGZL.js} +12 -10
  40. package/dist/MobileMenu-RE5XVGZL.js.map +1 -0
  41. package/dist/{NotificationsPage-ZRRQY7TF.js → NotificationsPage-QAFWZAS3.js} +12 -10
  42. package/dist/NotificationsPage-QAFWZAS3.js.map +1 -0
  43. package/dist/{OrderSinglePage-O3XYVMG5.js → OrderSinglePage-T4PRXTKG.js} +12 -10
  44. package/dist/OrderSinglePage-T4PRXTKG.js.map +1 -0
  45. package/dist/{OrdersPage-CMXZF5MI.js → OrdersPage-3S6ZS56E.js} +12 -10
  46. package/dist/OrdersPage-3S6ZS56E.js.map +1 -0
  47. package/dist/{PageSingle-IIG2US43.js → PageSingle-W3UTXDOW.js} +4 -4
  48. package/dist/{PageSingle-IIG2US43.js.map → PageSingle-W3UTXDOW.js.map} +1 -1
  49. package/dist/{ProductDetails-65GDABI7.js → ProductDetails-X5A7TG3F.js} +14 -7
  50. package/dist/ProductDetails-X5A7TG3F.js.map +1 -0
  51. package/dist/{ProfilePage-SNJ4WXU4.js → ProfilePage-DUH3AILJ.js} +12 -10
  52. package/dist/ProfilePage-DUH3AILJ.js.map +1 -0
  53. package/dist/{ReviewsPage-UT7UT55J.js → ReviewsPage-2C2P7VS3.js} +4 -4
  54. package/dist/{ReviewsPage-UT7UT55J.js.map → ReviewsPage-2C2P7VS3.js.map} +1 -1
  55. package/dist/{SeoCartWidget-DBJAXJKT.js → SeoCartWidget-VYLGEW2I.js} +12 -10
  56. package/dist/SeoCartWidget-VYLGEW2I.js.map +1 -0
  57. package/dist/{SettingsPage-FLTIOPU5.js → SettingsPage-OZJEW6CW.js} +12 -10
  58. package/dist/SettingsPage-OZJEW6CW.js.map +1 -0
  59. package/dist/{StoreClosedNotification-ZT3QKDKS.js → StoreClosedNotification-QHIZBNXO.js} +13 -11
  60. package/dist/{StoreClosedNotification-ZT3QKDKS.js.map → StoreClosedNotification-QHIZBNXO.js.map} +1 -1
  61. package/dist/{TestimonialsPage-CU7HKU4O.js → TestimonialsPage-K6WJKKME.js} +4 -4
  62. package/dist/{TestimonialsPage-CU7HKU4O.js.map → TestimonialsPage-K6WJKKME.js.map} +1 -1
  63. package/dist/{ThankYouPage-5X6B4X3T.js → ThankYouPage-H6QQEI5R.js} +12 -10
  64. package/dist/ThankYouPage-H6QQEI5R.js.map +1 -0
  65. package/dist/{WalletPage-H2BKIDPA.js → WalletPage-G5IBGN6B.js} +12 -10
  66. package/dist/WalletPage-G5IBGN6B.js.map +1 -0
  67. package/dist/{WishlistPage-RS5UCIQ3.js → WishlistPage-AIOTAB6L.js} +12 -10
  68. package/dist/WishlistPage-AIOTAB6L.js.map +1 -0
  69. package/dist/api/advertisement.d.ts +33 -0
  70. package/dist/api/advertisement.d.ts.map +1 -0
  71. package/dist/api/advertisement.js +8 -0
  72. package/dist/api/advertisement.js.map +1 -0
  73. package/dist/boot/critical-style.d.ts +3 -0
  74. package/dist/boot/critical-style.d.ts.map +1 -0
  75. package/dist/boot/index.d.ts +15 -0
  76. package/dist/boot/index.d.ts.map +1 -0
  77. package/dist/boot.d.ts +1 -0
  78. package/dist/boot.js +69 -0
  79. package/dist/boot.js.map +1 -0
  80. package/dist/chunk-4FJGZQD3.js +6 -0
  81. package/dist/chunk-4FJGZQD3.js.map +1 -0
  82. package/dist/{chunk-SDFDS4Q3.js → chunk-BCJTF3V7.js} +20 -4
  83. package/dist/chunk-BCJTF3V7.js.map +1 -0
  84. package/dist/chunk-DBW7XXNE.js +103 -0
  85. package/dist/chunk-DBW7XXNE.js.map +1 -0
  86. package/dist/{chunk-22SH7OVF.js → chunk-E7WWO3V3.js} +3 -3
  87. package/dist/{chunk-22SH7OVF.js.map → chunk-E7WWO3V3.js.map} +1 -1
  88. package/dist/{chunk-SE5JYWOK.js → chunk-EZHXVHXY.js} +4 -4
  89. package/dist/{chunk-SE5JYWOK.js.map → chunk-EZHXVHXY.js.map} +1 -1
  90. package/dist/{chunk-JO7JWV34.js → chunk-FWYKOQV5.js} +5 -5
  91. package/dist/chunk-FWYKOQV5.js.map +1 -0
  92. package/dist/{chunk-JRNH3FUF.js → chunk-FYHF62KI.js} +4 -4
  93. package/dist/{chunk-JRNH3FUF.js.map → chunk-FYHF62KI.js.map} +1 -1
  94. package/dist/{chunk-UJKFBZFJ.js → chunk-GJ5IMPNY.js} +7 -7
  95. package/dist/{chunk-UJKFBZFJ.js.map → chunk-GJ5IMPNY.js.map} +1 -1
  96. package/dist/chunk-IVBOLREE.js +29 -0
  97. package/dist/chunk-IVBOLREE.js.map +1 -0
  98. package/dist/chunk-LWPKBVIE.js +14 -0
  99. package/dist/{chunk-7WAXN2PP.js.map → chunk-LWPKBVIE.js.map} +1 -1
  100. package/dist/{chunk-SX5MF2YA.js → chunk-N3BCZIGS.js} +4 -4
  101. package/dist/{chunk-SX5MF2YA.js.map → chunk-N3BCZIGS.js.map} +1 -1
  102. package/dist/{chunk-NZVR4OF7.js → chunk-NXQZ4RLM.js} +8 -8
  103. package/dist/chunk-NXQZ4RLM.js.map +1 -0
  104. package/dist/{chunk-NXQJA7D3.js → chunk-PCURRKIH.js} +3 -3
  105. package/dist/{chunk-NXQJA7D3.js.map → chunk-PCURRKIH.js.map} +1 -1
  106. package/dist/{chunk-4CZRUAZB.js → chunk-PU2LHFDQ.js} +10 -11
  107. package/dist/chunk-PU2LHFDQ.js.map +1 -0
  108. package/dist/{chunk-OWX2CPNQ.js → chunk-UQIVX4YA.js} +3 -3
  109. package/dist/{chunk-OWX2CPNQ.js.map → chunk-UQIVX4YA.js.map} +1 -1
  110. package/dist/chunk-X5NK4TZH.js +228 -0
  111. package/dist/chunk-X5NK4TZH.js.map +1 -0
  112. package/dist/{chunk-6SWDSRIL.js → chunk-XKNXYQ2K.js} +6 -6
  113. package/dist/{chunk-6SWDSRIL.js.map → chunk-XKNXYQ2K.js.map} +1 -1
  114. package/dist/{chunk-KHBSGD2D.js → chunk-XO7B3VWS.js} +3 -3
  115. package/dist/{chunk-KHBSGD2D.js.map → chunk-XO7B3VWS.js.map} +1 -1
  116. package/dist/cli/check-manifest.d.ts +10 -0
  117. package/dist/cli/check-manifest.d.ts.map +1 -0
  118. package/dist/cli/check-manifest.js +44 -0
  119. package/dist/cli/check-manifest.js.map +1 -0
  120. package/dist/components/cart/index.js +1 -1
  121. package/dist/components/common/index.js +2 -2
  122. package/dist/components/drawer/index.js +10 -8
  123. package/dist/components/home/index.js +12 -10
  124. package/dist/components/layout/Footer.d.ts.map +1 -1
  125. package/dist/components/layout/MasterLayout.d.ts.map +1 -1
  126. package/dist/components/layout/PageAdvertisements.d.ts +7 -0
  127. package/dist/components/layout/PageAdvertisements.d.ts.map +1 -0
  128. package/dist/components/layout/index.d.ts +1 -0
  129. package/dist/components/layout/index.d.ts.map +1 -1
  130. package/dist/components/layout/index.js +8 -1
  131. package/dist/components/product/index.js +11 -9
  132. package/dist/components/store/index.js +12 -10
  133. package/dist/components/store/index.js.map +1 -1
  134. package/dist/{document-class-67HTP45G.js → document-class-FRL7JHWD.js} +6 -5
  135. package/dist/{document-class-67HTP45G.js.map → document-class-FRL7JHWD.js.map} +1 -1
  136. package/dist/hooks/index.js +10 -8
  137. package/dist/hooks/useGtm.js +10 -8
  138. package/dist/hooks/useMoney.js +10 -8
  139. package/dist/hooks/useStore.js +10 -8
  140. package/dist/index.js +10 -8
  141. package/dist/index.js.map +1 -1
  142. package/dist/{not-found-QTHBFPNT.js → not-found-E6AR4CZ4.js} +12 -10
  143. package/dist/not-found-E6AR4CZ4.js.map +1 -0
  144. package/dist/providers/index.js +10 -8
  145. package/dist/routes/account/orders.js +1 -1
  146. package/dist/routes/account.js +6 -6
  147. package/dist/routes/blog.js +5 -5
  148. package/dist/routes/brands.js +1 -1
  149. package/dist/routes/cart.js +14 -12
  150. package/dist/routes/cart.js.map +1 -1
  151. package/dist/routes/home.js +13 -11
  152. package/dist/routes/loyalty.js +2 -2
  153. package/dist/routes/page.js +1 -1
  154. package/dist/routes/product-listing.js +12 -10
  155. package/dist/routes/product-listing.js.map +1 -1
  156. package/dist/routes/product.js +12 -10
  157. package/dist/routes/product.js.map +1 -1
  158. package/dist/routes/root/beforeLoad.d.ts +2 -2
  159. package/dist/routes/root/beforeLoad.d.ts.map +1 -1
  160. package/dist/routes/root/head.d.ts.map +1 -1
  161. package/dist/routes/testimonials.js +2 -2
  162. package/dist/routes/thank-you.js +1 -1
  163. package/dist/skeleton.js +13 -11
  164. package/dist/tanstack/document-class.d.ts.map +1 -1
  165. package/dist/tanstack/index.d.ts +1 -0
  166. package/dist/tanstack/index.d.ts.map +1 -1
  167. package/dist/tanstack/stylesheet.d.ts +15 -0
  168. package/dist/tanstack/stylesheet.d.ts.map +1 -0
  169. package/dist/tanstack.js +33 -4
  170. package/dist/tanstack.js.map +1 -1
  171. package/dist/utils/index.js +2 -2
  172. package/dist/vite/index.d.ts +1 -1
  173. package/dist/vite/index.d.ts.map +1 -1
  174. package/dist/vite/index.js +14 -25
  175. package/dist/vite/index.js.map +1 -1
  176. package/dist/vite/plugins/twilight-manifest.d.ts +23 -0
  177. package/dist/vite/plugins/twilight-manifest.d.ts.map +1 -1
  178. package/dist/vite/plugins/twilight-schema.plugin.d.ts.map +1 -1
  179. package/package.json +16 -3
  180. package/tailwind/components/Component.tsx.txt +21 -0
  181. package/tailwind/components/ComponentRegistry.ts.txt +66 -0
  182. package/tailwind/components/cart/CartItem.tsx.txt +211 -0
  183. package/tailwind/components/cart/CartSummary.tsx.txt +269 -0
  184. package/tailwind/components/cart/PreOrderNotification.tsx.txt +16 -0
  185. package/tailwind/components/cart/PriceQuoteButton.tsx.txt +21 -0
  186. package/tailwind/components/cart/SeoCartWidget.tsx.txt +99 -0
  187. package/tailwind/components/cart/index.ts.txt +11 -0
  188. package/tailwind/components/cart/lazy.ts.txt +15 -0
  189. package/tailwind/components/collapse/Collapse.tsx.txt +95 -0
  190. package/tailwind/components/collapse/index.ts.txt +41 -0
  191. package/tailwind/components/common/Breadcrumb.tsx.txt +27 -0
  192. package/tailwind/components/common/BreadcrumbJsonLd.tsx.txt +40 -0
  193. package/tailwind/components/common/BreadcrumbTrail.tsx.txt +97 -0
  194. package/tailwind/components/common/CurrencySymbol.tsx.txt +8 -0
  195. package/tailwind/components/common/DeferredData.tsx.txt +122 -0
  196. package/tailwind/components/common/ErrorPage.tsx.txt +69 -0
  197. package/tailwind/components/common/Image.tsx.txt +119 -0
  198. package/tailwind/components/common/Link.tsx.txt +36 -0
  199. package/tailwind/components/common/Link.types.ts.txt +85 -0
  200. package/tailwind/components/common/NoContent.tsx.txt +27 -0
  201. package/tailwind/components/common/RenderWhenVisible.tsx.txt +92 -0
  202. package/tailwind/components/common/index.ts.txt +42 -0
  203. package/tailwind/components/common/lazy.ts.txt +28 -0
  204. package/tailwind/components/defineComponent.ts.txt +32 -0
  205. package/tailwind/components/drawer/Drawer.tsx.txt +219 -0
  206. package/tailwind/components/drawer/index.ts.txt +38 -0
  207. package/tailwind/components/dropdown/Dropdown.tsx.txt +166 -0
  208. package/tailwind/components/dropdown/index.ts.txt +40 -0
  209. package/tailwind/components/home/BundleComponent.tsx.txt +35 -0
  210. package/tailwind/components/home/ComponentErrorBoundary.tsx.txt +98 -0
  211. package/tailwind/components/home/FeaturedProductsStyle1.tsx.txt +125 -0
  212. package/tailwind/components/home/FeaturedProductsStyle2.tsx.txt +127 -0
  213. package/tailwind/components/home/FeaturedProductsStyle3.tsx.txt +71 -0
  214. package/tailwind/components/home/FixedBanner.tsx.txt +68 -0
  215. package/tailwind/components/home/FixedProducts.tsx.txt +72 -0
  216. package/tailwind/components/home/HomePageRenderer.tsx.txt +284 -0
  217. package/tailwind/components/home/PageBlocks.tsx.txt +24 -0
  218. package/tailwind/components/home/ParallaxBackground.tsx.txt +50 -0
  219. package/tailwind/components/home/PhotosSlider.tsx.txt +61 -0
  220. package/tailwind/components/home/ProductsSlider.tsx.txt +87 -0
  221. package/tailwind/components/home/ReorderProducts.tsx.txt +38 -0
  222. package/tailwind/components/home/SquarePhotos.tsx.txt +68 -0
  223. package/tailwind/components/home/StoreFeatures.tsx.txt +34 -0
  224. package/tailwind/components/home/Testimonials.tsx.txt +36 -0
  225. package/tailwind/components/home/Youtube.tsx.txt +48 -0
  226. package/tailwind/components/home/index.ts.txt +50 -0
  227. package/tailwind/components/home/lazy.ts.txt +17 -0
  228. package/tailwind/components/home/register.ts.txt +44 -0
  229. package/tailwind/components/home/reorder-hooks.ts.txt +52 -0
  230. package/tailwind/components/home/types.ts.txt +4 -0
  231. package/tailwind/components/layout/Copyright.tsx.txt +23 -0
  232. package/tailwind/components/layout/CustomerLayout.tsx.txt +91 -0
  233. package/tailwind/components/layout/Footer.tsx.txt +341 -0
  234. package/tailwind/components/layout/Header.tsx.txt +154 -0
  235. package/tailwind/components/layout/MasterLayout.tsx.txt +62 -0
  236. package/tailwind/components/layout/PageAdvertisements.tsx.txt +131 -0
  237. package/tailwind/components/layout/WidgetHead.tsx.txt +53 -0
  238. package/tailwind/components/layout/index.ts.txt +27 -0
  239. package/tailwind/components/layout/lazy.ts.txt +25 -0
  240. package/tailwind/components/layout/types.ts.txt +11 -0
  241. package/tailwind/components/modal/ImageModal.tsx.txt +70 -0
  242. package/tailwind/components/modal/Modal.tsx.txt +245 -0
  243. package/tailwind/components/modal/index.ts.txt +39 -0
  244. package/tailwind/components/modal/lazy.ts.txt +10 -0
  245. package/tailwind/components/navigation/MainMenu.tsx.txt +94 -0
  246. package/tailwind/components/navigation/MobileMenu.tsx.txt +160 -0
  247. package/tailwind/components/navigation/index.ts.txt +4 -0
  248. package/tailwind/components/navigation/lazy.ts.txt +11 -0
  249. package/tailwind/components/product/AddToCartForm.tsx.txt +365 -0
  250. package/tailwind/components/product/ProductCard.tsx.txt +305 -0
  251. package/tailwind/components/product/ProductDetails.tsx.txt +267 -0
  252. package/tailwind/components/product/ProductGallery.tsx.txt +234 -0
  253. package/tailwind/components/product/index.ts.txt +15 -0
  254. package/tailwind/components/product/lazy.ts.txt +9 -0
  255. package/tailwind/components/snippets/AppsSnippets.tsx.txt +225 -0
  256. package/tailwind/components/snippets/SallaSdkScripts.tsx.txt +14 -0
  257. package/tailwind/components/snippets/index.ts.txt +4 -0
  258. package/tailwind/components/store/StoreClosedNotification.tsx.txt +73 -0
  259. package/tailwind/components/store/index.ts.txt +8 -0
  260. package/tailwind/components/store/lazy.ts.txt +8 -0
  261. package/tailwind/components/toast/Toaster.tsx.txt +177 -0
  262. package/tailwind/components/toast/index.ts.txt +10 -0
  263. package/tailwind/components/toast/types.ts.txt +38 -0
  264. package/tailwind/components/toast/useToast.ts.txt +61 -0
  265. package/tailwind/components/tracking/GoogleTag.tsx.txt +77 -0
  266. package/tailwind/components/tracking/SiftSnippet.tsx.txt +49 -0
  267. package/tailwind/components/tracking/TwilightBundles.tsx.txt +67 -0
  268. package/tailwind/components/tracking/index.ts.txt +11 -0
  269. package/tailwind/components/tracking/lazy.ts.txt +11 -0
  270. package/tailwind/components/useComponent.ts.txt +12 -0
  271. package/tailwind/routes/account/index.tsx.txt +16 -0
  272. package/tailwind/routes/account/notifications/NotificationsPage.tsx.txt +54 -0
  273. package/tailwind/routes/account/notifications/index.tsx.txt +39 -0
  274. package/tailwind/routes/account/notifications/loader.ts.txt +22 -0
  275. package/tailwind/routes/account/notifications/types.ts.txt +18 -0
  276. package/tailwind/routes/account/orders/OrderSinglePage.tsx.txt +181 -0
  277. package/tailwind/routes/account/orders/OrdersPage.tsx.txt +28 -0
  278. package/tailwind/routes/account/orders/index.tsx.txt +86 -0
  279. package/tailwind/routes/account/orders/loader.ts.txt +66 -0
  280. package/tailwind/routes/account/orders/single/OrderActions.tsx.txt +197 -0
  281. package/tailwind/routes/account/orders/single/OrderHeader.tsx.txt +143 -0
  282. package/tailwind/routes/account/orders/single/OrderItemRow.tsx.txt +209 -0
  283. package/tailwind/routes/account/orders/single/OrderSections.tsx.txt +215 -0
  284. package/tailwind/routes/account/orders/single/types.ts.txt +38 -0
  285. package/tailwind/routes/account/orders/types.ts.txt +29 -0
  286. package/tailwind/routes/account/profile/ProfilePage.tsx.txt +30 -0
  287. package/tailwind/routes/account/profile/index.tsx.txt +39 -0
  288. package/tailwind/routes/account/profile/loader.ts.txt +16 -0
  289. package/tailwind/routes/account/profile/types.ts.txt +5 -0
  290. package/tailwind/routes/account/settings/SettingsPage.tsx.txt +13 -0
  291. package/tailwind/routes/account/settings/index.tsx.txt +39 -0
  292. package/tailwind/routes/account/settings/loader.ts.txt +13 -0
  293. package/tailwind/routes/account/settings/types.ts.txt +5 -0
  294. package/tailwind/routes/account/types.ts.txt +320 -0
  295. package/tailwind/routes/account/wallet/WalletPage.tsx.txt +195 -0
  296. package/tailwind/routes/account/wallet/index.tsx.txt +38 -0
  297. package/tailwind/routes/account/wallet/loader.ts.txt +27 -0
  298. package/tailwind/routes/account/wallet/types.ts.txt +11 -0
  299. package/tailwind/routes/account/wishlist/WishlistPage.tsx.txt +314 -0
  300. package/tailwind/routes/account/wishlist/WishlistShare.tsx.txt +248 -0
  301. package/tailwind/routes/account/wishlist/index.tsx.txt +40 -0
  302. package/tailwind/routes/account/wishlist/loader.ts.txt +26 -0
  303. package/tailwind/routes/account/wishlist/types.ts.txt +9 -0
  304. package/tailwind/routes/blog/BlogAuthorPage.tsx.txt +82 -0
  305. package/tailwind/routes/blog/BlogCategoryPage.tsx.txt +84 -0
  306. package/tailwind/routes/blog/BlogPage.tsx.txt +191 -0
  307. package/tailwind/routes/blog/BlogSinglePage.tsx.txt +168 -0
  308. package/tailwind/routes/blog/BlogTagPage.tsx.txt +84 -0
  309. package/tailwind/routes/blog/BlogTags.tsx.txt +54 -0
  310. package/tailwind/routes/blog/components.tsx.txt +87 -0
  311. package/tailwind/routes/blog/index.tsx.txt +238 -0
  312. package/tailwind/routes/blog/loader.ts.txt +173 -0
  313. package/tailwind/routes/blog/types.ts.txt +106 -0
  314. package/tailwind/routes/brands/BrandsPage.tsx.txt +179 -0
  315. package/tailwind/routes/brands/index.tsx.txt +47 -0
  316. package/tailwind/routes/brands/loader.ts.txt +14 -0
  317. package/tailwind/routes/brands/types.ts.txt +53 -0
  318. package/tailwind/routes/cart/CartPage.tsx.txt +112 -0
  319. package/tailwind/routes/cart/index.tsx.txt +39 -0
  320. package/tailwind/routes/cart/loader.ts.txt +15 -0
  321. package/tailwind/routes/cart/types.ts.txt +6 -0
  322. package/tailwind/routes/home/HomePage.tsx.txt +53 -0
  323. package/tailwind/routes/home/index.tsx.txt +56 -0
  324. package/tailwind/routes/home/loader.ts.txt +24 -0
  325. package/tailwind/routes/home/types.ts.txt +11 -0
  326. package/tailwind/routes/index.ts.txt +94 -0
  327. package/tailwind/routes/lazy.ts.txt +105 -0
  328. package/tailwind/routes/loyalty/LoyaltyPage.tsx.txt +17 -0
  329. package/tailwind/routes/loyalty/LoyaltyPointsPage.tsx.txt +11 -0
  330. package/tailwind/routes/loyalty/index.tsx.txt +81 -0
  331. package/tailwind/routes/loyalty/loader.ts.txt +53 -0
  332. package/tailwind/routes/loyalty/types.ts.txt +109 -0
  333. package/tailwind/routes/not-found.tsx.txt +50 -0
  334. package/tailwind/routes/page/PageSingle.tsx.txt +34 -0
  335. package/tailwind/routes/page/index.tsx.txt +60 -0
  336. package/tailwind/routes/page/loader.ts.txt +19 -0
  337. package/tailwind/routes/page/types.ts.txt +34 -0
  338. package/tailwind/routes/product/ProductPage.tsx.txt +77 -0
  339. package/tailwind/routes/product/index.tsx.txt +157 -0
  340. package/tailwind/routes/product/loader.ts.txt +35 -0
  341. package/tailwind/routes/product/types.ts.txt +6 -0
  342. package/tailwind/routes/product-listing/ProductListPage.tsx.txt +321 -0
  343. package/tailwind/routes/product-listing/index.tsx.txt +95 -0
  344. package/tailwind/routes/product-listing/loader.ts.txt +323 -0
  345. package/tailwind/routes/product-listing/types.ts.txt +96 -0
  346. package/tailwind/routes/product-listing/useAppliedFilters.ts.txt +130 -0
  347. package/tailwind/routes/product-short-link.ts.txt +104 -0
  348. package/tailwind/routes/root/beforeLoad.ts.txt +354 -0
  349. package/tailwind/routes/root/head.ts.txt +424 -0
  350. package/tailwind/routes/root/index.ts.txt +2 -0
  351. package/tailwind/routes/seo-redirects.ts.txt +82 -0
  352. package/tailwind/routes/testimonials/ReviewsPage.tsx.txt +12 -0
  353. package/tailwind/routes/testimonials/TestimonialsPage.tsx.txt +80 -0
  354. package/tailwind/routes/testimonials/index.tsx.txt +80 -0
  355. package/tailwind/routes/testimonials/loader.ts.txt +29 -0
  356. package/tailwind/routes/testimonials/types.ts.txt +9 -0
  357. package/tailwind/routes/thank-you/ThankYouPage.tsx.txt +241 -0
  358. package/tailwind/routes/thank-you/index.tsx.txt +49 -0
  359. package/tailwind/routes/thank-you/loader.ts.txt +40 -0
  360. package/tailwind/routes/thank-you/types.ts.txt +16 -0
  361. package/tailwind/routes/types.ts.txt +41 -0
  362. package/dist/BlogAuthorPage-GTF4UGSS.js.map +0 -1
  363. package/dist/BlogCategoryPage-TN7Y4NBP.js.map +0 -1
  364. package/dist/BlogPage-TDB3M3PI.js.map +0 -1
  365. package/dist/BlogSinglePage-4A52Q5QW.js.map +0 -1
  366. package/dist/BlogTagPage-TOVPBETA.js.map +0 -1
  367. package/dist/BrandsPage-VUDGYSQI.js.map +0 -1
  368. package/dist/Breadcrumb-CI3SN5YR.js.map +0 -1
  369. package/dist/CartItem-BHLEURAB.js.map +0 -1
  370. package/dist/CartSummary-QERVESYN.js.map +0 -1
  371. package/dist/CustomerLayout-VRZUWGQK.js.map +0 -1
  372. package/dist/LoyaltyPage-CGTHWJ76.js.map +0 -1
  373. package/dist/LoyaltyPointsPage-QSU6K3PW.js.map +0 -1
  374. package/dist/MobileMenu-U4BVQYKH.js.map +0 -1
  375. package/dist/NotificationsPage-ZRRQY7TF.js.map +0 -1
  376. package/dist/OrderSinglePage-O3XYVMG5.js.map +0 -1
  377. package/dist/OrdersPage-CMXZF5MI.js.map +0 -1
  378. package/dist/ProductDetails-65GDABI7.js.map +0 -1
  379. package/dist/ProfilePage-SNJ4WXU4.js.map +0 -1
  380. package/dist/SeoCartWidget-DBJAXJKT.js.map +0 -1
  381. package/dist/SettingsPage-FLTIOPU5.js.map +0 -1
  382. package/dist/ThankYouPage-5X6B4X3T.js.map +0 -1
  383. package/dist/WalletPage-H2BKIDPA.js.map +0 -1
  384. package/dist/WishlistPage-RS5UCIQ3.js.map +0 -1
  385. package/dist/chunk-4CZRUAZB.js.map +0 -1
  386. package/dist/chunk-7WAXN2PP.js +0 -14
  387. package/dist/chunk-JO7JWV34.js.map +0 -1
  388. package/dist/chunk-NZVR4OF7.js.map +0 -1
  389. package/dist/chunk-SDFDS4Q3.js.map +0 -1
  390. package/dist/not-found-QTHBFPNT.js.map +0 -1
@@ -0,0 +1,21 @@
1
+ import { SallaButton } from '@salla.sa/twilight-components-react/button';
2
+ import { useTranslation } from '../../providers/I18nProvider';
3
+
4
+ const handlePriceQuote = async () => {
5
+ await window.salla?.cart.api.priceQuote?.();
6
+ };
7
+
8
+ export function PriceQuoteButton() {
9
+ const { t } = useTranslation();
10
+
11
+ return (
12
+ <SallaButton
13
+ id="request-price-quote-btn"
14
+ className="btn--oval btn--md"
15
+ fill="outline"
16
+ onClick={handlePriceQuote}
17
+ >
18
+ {t('pages.cart.request_price_quote', 'Request for Quotation')}
19
+ </SallaButton>
20
+ );
21
+ }
@@ -0,0 +1,99 @@
1
+ import { useEffect } from 'react';
2
+ import { useTwilight } from '../../providers/TwilightProvider';
3
+ import { useTranslation } from '../../providers/I18nProvider';
4
+ import type { Cart } from '../../types';
5
+ import { pushCheckoutEvent } from '../../lib/analytics/gtm';
6
+ import { onSallaEvent } from '../../utils/sdk-helpers';
7
+
8
+ // ─── Types ────────────────────────────────────────────────────────────────────
9
+
10
+ export interface SeoCartWidgetProps {
11
+ /**
12
+ * Cart data from the TanStack Router loader — no extra fetch needed.
13
+ */
14
+ cart: Cart;
15
+ /**
16
+ * Current checkout funnel step.
17
+ * Defaults to `1` (cart view) — CartPage always passes this default.
18
+ * The widget internally listens to Salla SDK events to promote to 2 / 3.
19
+ */
20
+ step?: 1 | 2 | 3;
21
+ }
22
+
23
+ // ─── Page title translation keys (mirrors PHP SeoCartWidget::$pageTitle) ─────
24
+
25
+ /**
26
+ * Matches the PHP backend exactly:
27
+ * ```php
28
+ * protected $pageTitle = [
29
+ * self::STEP_CART => 'common.titles.cart',
30
+ * self::STEP_SHIPPING => 'common.titles.shipping',
31
+ * self::STEP_PAYMENT => 'common.titles.checkout',
32
+ * ];
33
+ * ```
34
+ */
35
+ const PAGE_TITLE_KEYS: Record<1 | 2 | 3, string> = {
36
+ 1: 'common.titles.cart',
37
+ 2: 'common.titles.shipping',
38
+ 3: 'common.titles.checkout',
39
+ };
40
+
41
+ // ─── Component ────────────────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * SeoCartWidget — Null-rendering analytics side-effect component.
45
+ *
46
+ * Responsibilities:
47
+ * 1. Fires a GTM `checkout` event (step 1) on cart page load.
48
+ * 2. Listens to Salla SDK checkout overlay events to fire steps 2 & 3.
49
+ * 3. Updates `document.title` to reflect the cart context.
50
+ *
51
+ * Architecture:
52
+ * - Lives in `theme-engine` so all themes using the engine get GTM tracking.
53
+ * - Mounted directly inside `CartPage`, which already has loader data.
54
+ * - `window.dataLayer` must be initialised by the store's GTM embed snippet;
55
+ * the widget initialises it as a safe-guard but does NOT inject GTM itself.
56
+ * - Must be mounted inside `<TwilightProvider>` (reads `currency` from context).
57
+ *
58
+ * @example
59
+ * // In CartPage:
60
+ * <SeoCartWidget cart={cart} />
61
+ */
62
+ export function SeoCartWidget({ cart, step = 1 }: SeoCartWidgetProps) {
63
+ const { store, currency } = useTwilight();
64
+ const { t } = useTranslation();
65
+ const currencyCode = currency?.code ?? 'SAR';
66
+
67
+ // ── Fire GTM checkout event for step 1 (or whichever step is passed) ────────
68
+ useEffect(() => {
69
+ pushCheckoutEvent(step, cart, currencyCode);
70
+ // Re-fire if the cart id, contents, or currency changes
71
+ }, [step, cart, currencyCode]);
72
+
73
+ // ── Subscribe to Salla SDK checkout step events (steps 2 & 3) ──────────────
74
+ // Fires GTM events when the user advances through the checkout overlay.
75
+ useEffect(() => {
76
+ const onShipping = () => pushCheckoutEvent(2, cart, currencyCode);
77
+ const onPayment = () => pushCheckoutEvent(3, cart, currencyCode);
78
+
79
+ const offShipping = onSallaEvent('checkout::step.shipping', onShipping);
80
+ const offPayment = onSallaEvent('checkout::step.payment', onPayment);
81
+
82
+ return () => {
83
+ offShipping();
84
+ offPayment();
85
+ };
86
+ }, [cart, currencyCode]);
87
+
88
+ // ── Update document title — mirrors PHP SeoCartWidget::$pageTitle ───────────
89
+ // Step 1 → 'common.titles.cart' (e.g. "Cart")
90
+ // Step 2 → 'common.titles.shipping' (e.g. "Shipping")
91
+ // Step 3 → 'common.titles.checkout' (e.g. "Checkout")
92
+ useEffect(() => {
93
+ const storeTitle = store.meta?.title || store.name;
94
+ const stepTitle = t(PAGE_TITLE_KEYS[step], PAGE_TITLE_KEYS[step]);
95
+ document.title = storeTitle ? `${stepTitle} - ${storeTitle}` : stepTitle;
96
+ }, [step, store.meta?.title, store.name, t]);
97
+
98
+ return null;
99
+ }
@@ -0,0 +1,11 @@
1
+ import { LazyCartItem, LazyCartSummary, LazySeoCartWidget } from './lazy';
2
+
3
+ // Lazy-loaded cart components (code-split)
4
+ export {
5
+ LazyCartItem as CartItem,
6
+ LazyCartSummary as CartSummary,
7
+ LazySeoCartWidget as SeoCartWidget,
8
+ };
9
+
10
+ // Types are co-located with components (not in types.ts)
11
+ export type { SeoCartWidgetProps } from './SeoCartWidget';
@@ -0,0 +1,15 @@
1
+ import { lazy } from 'react';
2
+
3
+ /**
4
+ * Lazy-loaded cart components
5
+ * Use these for code-splitting cart sections
6
+ */
7
+ export const LazyCartItem = lazy(() => import('./CartItem').then((m) => ({ default: m.CartItem })));
8
+
9
+ export const LazyCartSummary = lazy(() =>
10
+ import('./CartSummary').then((m) => ({ default: m.CartSummary }))
11
+ );
12
+
13
+ export const LazySeoCartWidget = lazy(() =>
14
+ import('./SeoCartWidget').then((m) => ({ default: m.SeoCartWidget }))
15
+ );
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Collapse Component
3
+ *
4
+ * A controlled collapsible content area with smooth animation.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * function FAQItem({ question, answer }) {
9
+ * const [isOpen, setIsOpen] = useState(false);
10
+ *
11
+ * return (
12
+ * <Collapse isOpen={isOpen} onToggle={() => setIsOpen(!isOpen)}>
13
+ * <Collapse.Trigger>
14
+ * <div className="flex justify-between">
15
+ * <span>{question}</span>
16
+ * <span>{isOpen ? '−' : '+'}</span>
17
+ * </div>
18
+ * </Collapse.Trigger>
19
+ * <Collapse.Content>
20
+ * <p>{answer}</p>
21
+ * </Collapse.Content>
22
+ * </Collapse>
23
+ * );
24
+ * }
25
+ * ```
26
+ */
27
+
28
+ import { type ReactNode, type HTMLAttributes, type ButtonHTMLAttributes } from 'react';
29
+
30
+ export interface CollapseProps {
31
+ isOpen: boolean;
32
+ onToggle: () => void;
33
+ children: ReactNode;
34
+ /**
35
+ * Additional className for the collapse container
36
+ */
37
+ className?: string;
38
+ }
39
+
40
+ export function Collapse({ children, className }: CollapseProps) {
41
+ return <div className={`overflow-hidden ${className || ''}`}>{children}</div>;
42
+ }
43
+
44
+ // Trigger Component
45
+ export interface CollapseTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
46
+ children: ReactNode;
47
+ isOpen?: boolean;
48
+ onToggle?: () => void;
49
+ }
50
+
51
+ Collapse.Trigger = function CollapseTrigger({
52
+ children,
53
+ isOpen = false,
54
+ onToggle,
55
+ className,
56
+ ...props
57
+ }: CollapseTriggerProps) {
58
+ return (
59
+ <button
60
+ type="button"
61
+ onClick={onToggle}
62
+ className={`block cursor-pointer border-0 bg-transparent p-0 text-start ${className || ''}`}
63
+ aria-expanded={isOpen}
64
+ {...props}
65
+ >
66
+ {children}
67
+ </button>
68
+ );
69
+ };
70
+
71
+ // Content Component
72
+ export interface CollapseContentProps extends HTMLAttributes<HTMLDivElement> {
73
+ children: ReactNode;
74
+ isOpen?: boolean;
75
+ }
76
+
77
+ Collapse.Content = function CollapseContent({
78
+ children,
79
+ isOpen = false,
80
+ className,
81
+ ...props
82
+ }: CollapseContentProps) {
83
+ return (
84
+ <div
85
+ className={`transition-all duration-200 ease-out ${
86
+ isOpen ? 'grid grid-rows-[1fr] opacity-100' : 'grid grid-rows-[0fr] opacity-0'
87
+ } ${className || ''}`}
88
+ {...props}
89
+ >
90
+ <div className="overflow-hidden">
91
+ <div className="pb-2">{children}</div>
92
+ </div>
93
+ </div>
94
+ );
95
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Collapse Component System
3
+ *
4
+ * A controlled collapsible panel for FAQs, accordions, and expandable sections.
5
+ * Eager-loaded because it uses compound component pattern (Collapse.Trigger, Collapse.Content).
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { Collapse } from '@salla.sa/twilight-theme-engine/components/collapse';
10
+ *
11
+ * function Accordion() {
12
+ * const [openId, setOpenId] = useState(null);
13
+ *
14
+ * return (
15
+ * <div className="space-y-2">
16
+ * {items.map((item) => (
17
+ * <Collapse
18
+ * key={item.id}
19
+ * isOpen={openId === item.id}
20
+ * onToggle={() => setOpenId(openId === item.id ? null : item.id)}
21
+ * >
22
+ * <Collapse.Trigger isOpen={openId === item.id} onToggle={() => setOpenId(item.id)}>
23
+ * <h3>{item.title}</h3>
24
+ * </Collapse.Trigger>
25
+ * <Collapse.Content isOpen={openId === item.id}>
26
+ * <p>{item.content}</p>
27
+ * </Collapse.Content>
28
+ * </Collapse>
29
+ * ))}
30
+ * </div>
31
+ * );
32
+ * }
33
+ * ```
34
+ */
35
+
36
+ export {
37
+ Collapse,
38
+ type CollapseProps,
39
+ type CollapseTriggerProps,
40
+ type CollapseContentProps,
41
+ } from './Collapse';
@@ -0,0 +1,27 @@
1
+ import type { Page } from '../../types';
2
+ import { useBreadcrumbs } from '../../hooks/useBreadcrumbs';
3
+ import { useTwilight } from '../../providers/TwilightProvider';
4
+ import { BreadcrumbTrail } from './BreadcrumbTrail';
5
+ import { BreadcrumbJsonLd } from './BreadcrumbJsonLd';
6
+
7
+ export interface BreadcrumbProps {
8
+ page?: Page | null;
9
+ className?: string;
10
+ }
11
+
12
+ export function Breadcrumb({ page, className }: BreadcrumbProps) {
13
+ const { theme, store } = useTwilight();
14
+ const breadcrumbsEnabled = theme.settings.is_breadcrumbs_enabled ?? true;
15
+ const origin = store.url;
16
+
17
+ const items = useBreadcrumbs(page);
18
+
19
+ if (!breadcrumbsEnabled) return null;
20
+
21
+ return (
22
+ <>
23
+ <BreadcrumbTrail items={items} className={className} />
24
+ <BreadcrumbJsonLd items={items} origin={origin} />
25
+ </>
26
+ );
27
+ }
@@ -0,0 +1,40 @@
1
+ import type { Breadcrumb } from '../../types';
2
+
3
+ interface BreadcrumbJsonLdProps {
4
+ items: Breadcrumb[];
5
+ origin: string;
6
+ }
7
+
8
+ function toAbsoluteUrl(origin: string, path: string): string {
9
+ if (path.startsWith('http')) return path;
10
+ return origin + path;
11
+ }
12
+
13
+ export function BreadcrumbJsonLd({ items, origin }: BreadcrumbJsonLdProps) {
14
+ if (items.length === 0) return null;
15
+
16
+ const schema = {
17
+ '@context': 'https://schema.org',
18
+ '@type': 'BreadcrumbList',
19
+ itemListElement: items.map((item, index) => {
20
+ const isLast = index === items.length - 1;
21
+ const url = toAbsoluteUrl(origin, item.url);
22
+ return {
23
+ '@type': 'ListItem',
24
+ position: index + 1,
25
+ name: item.name,
26
+ ...(isLast ? { '@id': url } : { item: url }),
27
+ };
28
+ }),
29
+ };
30
+
31
+ const html = JSON.stringify(schema);
32
+
33
+ return (
34
+ <script
35
+ type="application/ld+json"
36
+ suppressHydrationWarning
37
+ dangerouslySetInnerHTML={{ __html: html }}
38
+ />
39
+ );
40
+ }
@@ -0,0 +1,97 @@
1
+ import { memo, useState, useEffect } from 'react';
2
+ import type { Breadcrumb } from '../../types';
3
+ import { Link } from './Link';
4
+
5
+ const MAX_VISIBLE_ITEMS = 4;
6
+
7
+ interface BreadcrumbTrailProps {
8
+ items: Breadcrumb[];
9
+ className?: string;
10
+ }
11
+
12
+ const ChevronIcon = () => (
13
+ <svg
14
+ version="1.1"
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ width="32"
17
+ height="32"
18
+ viewBox="0 0 32 32"
19
+ aria-hidden="true"
20
+ >
21
+ <title>keyboard_arrow_left</title>
22
+ <path d="M20.563 22.104l-1.875 1.875-8-8 8-8 1.875 1.875-6.125 6.125z"></path>
23
+ </svg>
24
+ );
25
+
26
+ function truncateItems(items: Breadcrumb[]): Breadcrumb[] {
27
+ if (items.length <= MAX_VISIBLE_ITEMS) return items;
28
+
29
+ return [items[0], { name: '…', url: '' }, ...items.slice(-2)];
30
+ }
31
+
32
+ export const BreadcrumbTrail = memo(function BreadcrumbTrail({
33
+ items,
34
+ className,
35
+ }: BreadcrumbTrailProps) {
36
+ const [expanded, setExpanded] = useState(false);
37
+
38
+ useEffect(() => {
39
+ setExpanded(false);
40
+ }, [items]);
41
+
42
+ if (items.length === 0) return null;
43
+
44
+ const displayItems = expanded ? items : truncateItems(items);
45
+
46
+ return (
47
+ <nav className={className ?? 'breadcrumbs w-full py-5'} aria-label="Breadcrumb">
48
+ <ol className="s-breadcrumb-wrapper" itemScope itemType="https://schema.org/BreadcrumbList">
49
+ {displayItems.map((item, index) => {
50
+ const isLast = index === displayItems.length - 1;
51
+ const isEllipsis = item.name === '…' && !item.url;
52
+
53
+ return (
54
+ <li
55
+ key={`${item.url}-${item.name}`}
56
+ className={['s-breadcrumb-item', isEllipsis ? 'cursor-pointer select-none' : ''].join(
57
+ ' '
58
+ )}
59
+ aria-current={isLast ? 'page' : undefined}
60
+ itemProp="itemListElement"
61
+ itemScope
62
+ itemType="https://schema.org/ListItem"
63
+ >
64
+ {isEllipsis ? (
65
+ <button
66
+ type="button"
67
+ onClick={() => {
68
+ setExpanded(true);
69
+ }}
70
+ className="inline-flex items-center gap-1 hover:underline"
71
+ aria-label="Show all breadcrumbs"
72
+ >
73
+ <span aria-hidden="true" className="text-xs">
74
+ •••
75
+ </span>
76
+ </button>
77
+ ) : isLast ? (
78
+ <span itemProp="name">{item.name}</span>
79
+ ) : (
80
+ <>
81
+ <Link to={item.url} itemProp="item">
82
+ <span itemProp="name">{item.name}</span>
83
+ </Link>
84
+ <span aria-hidden="true" className="s-breadcrumb-arrow inline-flex">
85
+ <ChevronIcon />
86
+ </span>
87
+ <meta itemProp="position" content={String(index + 1)} />
88
+ </>
89
+ )}
90
+ {isLast && <meta itemProp="position" content={String(index + 1)} />}
91
+ </li>
92
+ );
93
+ })}
94
+ </ol>
95
+ </nav>
96
+ );
97
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Renders a currency symbol.
3
+ * SAR uses the dedicated `sicon-sar` icon; all other currencies render as text.
4
+ */
5
+ export function CurrencySymbol({ currency }: { currency: string }) {
6
+ if (currency === 'SAR') return <i className="sicon-sar" aria-hidden="true" />;
7
+ return <span>{currency}</span>;
8
+ }
@@ -0,0 +1,122 @@
1
+ import { Suspense, memo, use, type ReactNode } from 'react';
2
+
3
+ export interface DeferredDataProps<T> {
4
+ promise: Promise<T> | T;
5
+ fallback: ReactNode;
6
+ children: (data: T) => ReactNode;
7
+ }
8
+
9
+ const DEFERRED_KEY = Symbol.for('twilight.deferred');
10
+
11
+ interface DeferredState<T> {
12
+ status: 'pending' | 'success' | 'error';
13
+ value?: T;
14
+ error?: unknown;
15
+ }
16
+
17
+ export interface DeferredPromise<T> extends Promise<T> {
18
+ [DEFERRED_KEY]?: DeferredState<T>;
19
+ }
20
+
21
+ const seenPromises = typeof WeakSet !== 'undefined' ? new WeakSet() : null;
22
+
23
+ function defer<T>(promise: Promise<T>): DeferredPromise<T> {
24
+ const existing = (promise as DeferredPromise<T>)[DEFERRED_KEY];
25
+ if (existing) return promise as DeferredPromise<T>;
26
+
27
+ const state: DeferredState<T> = { status: 'pending' };
28
+
29
+ const deferred = promise as DeferredPromise<T>;
30
+ deferred[DEFERRED_KEY] = state;
31
+
32
+ promise.then(
33
+ (value) => {
34
+ state.status = 'success';
35
+ state.value = value;
36
+ },
37
+ (error) => {
38
+ state.status = 'error';
39
+ state.error = error;
40
+ }
41
+ );
42
+
43
+ return deferred;
44
+ }
45
+
46
+ const DeferredRenderer = memo(function DeferredRenderer<T>({
47
+ promise,
48
+ children,
49
+ }: {
50
+ promise: DeferredPromise<T>;
51
+ children: (data: T) => ReactNode;
52
+ }) {
53
+ const state = promise[DEFERRED_KEY];
54
+
55
+ if (state?.status === 'success') {
56
+ return children(state.value!);
57
+ }
58
+
59
+ if (state?.status === 'error') {
60
+ throw state.error;
61
+ }
62
+
63
+ const data = use(promise);
64
+ return children(data);
65
+ }) as <T>(props: { promise: DeferredPromise<T>; children: (data: T) => ReactNode }) => ReactNode;
66
+
67
+ function DeferredFallback<T>({
68
+ promise,
69
+ fallback,
70
+ children,
71
+ }: {
72
+ promise: DeferredPromise<T>;
73
+ fallback: ReactNode;
74
+ children: (data: T) => ReactNode;
75
+ }) {
76
+ const state = promise[DEFERRED_KEY];
77
+ if (state?.status === 'success') {
78
+ return children(state.value!);
79
+ }
80
+ return <>{fallback}</>;
81
+ }
82
+
83
+ export function resolveDeferred<T>(
84
+ promiseOrValue: Promise<T> | T | undefined
85
+ ): T | Promise<T> | undefined {
86
+ if (promiseOrValue == null || !(promiseOrValue instanceof Promise)) return promiseOrValue;
87
+ const deferred = defer(promiseOrValue);
88
+ const state = deferred[DEFERRED_KEY];
89
+ if (state?.status === 'success') return state.value as T;
90
+ return promiseOrValue;
91
+ }
92
+
93
+ export function DeferredData<T>({ promise, fallback, children }: DeferredDataProps<T>) {
94
+ if (!(promise instanceof Promise)) {
95
+ return children(promise);
96
+ }
97
+
98
+ if (seenPromises && promise instanceof Promise) seenPromises.add(promise);
99
+
100
+ const deferred = defer(promise);
101
+ const state = deferred[DEFERRED_KEY];
102
+
103
+ if (state?.status === 'success') {
104
+ return children(state.value!);
105
+ }
106
+
107
+ if (state?.status === 'error') {
108
+ throw state.error;
109
+ }
110
+
111
+ return (
112
+ <Suspense
113
+ fallback={
114
+ <DeferredFallback promise={deferred} fallback={fallback}>
115
+ {children}
116
+ </DeferredFallback>
117
+ }
118
+ >
119
+ <DeferredRenderer promise={deferred}>{children}</DeferredRenderer>
120
+ </Suspense>
121
+ );
122
+ }
@@ -0,0 +1,69 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+ import { useTranslation } from '../../providers/I18nProvider';
3
+
4
+ const LINK_STYLE: React.CSSProperties = {
5
+ backgroundColor: 'var(--color-primary)',
6
+ color: 'var(--color-primary-reverse, #ffffff)',
7
+ };
8
+
9
+ export interface ErrorPageProps {
10
+ code?: string | number;
11
+ title?: string;
12
+ message?: string;
13
+ actionText?: string;
14
+ actionHref?: string;
15
+ showAction?: boolean;
16
+ LinkComponent?: ComponentType<{
17
+ to: string;
18
+ className?: string;
19
+ style?: React.CSSProperties;
20
+ children: ReactNode;
21
+ }>;
22
+ children?: ReactNode;
23
+ }
24
+
25
+ export function ErrorPage({
26
+ code,
27
+ title,
28
+ message,
29
+ actionText,
30
+ actionHref = '/',
31
+ showAction = true,
32
+ LinkComponent,
33
+ children,
34
+ }: ErrorPageProps) {
35
+ const { t } = useTranslation();
36
+
37
+ const resolvedTitle = title ?? t(`common.errors.${code}`) ?? 'Error';
38
+ const resolvedMessage =
39
+ message ?? t('common.errors.error_occurred') ?? 'An unexpected error occurred.';
40
+ const resolvedActionText = actionText ?? t('common.elements.back_home') ?? 'Back to Home';
41
+
42
+ const linkClassName =
43
+ 'inline-flex items-center px-6 py-3 rounded-lg transition-opacity hover:opacity-90';
44
+
45
+ return (
46
+ <div className="container py-16 text-center">
47
+ {code && (
48
+ <h1 className="text-6xl font-bold text-gray-300 mb-4" data-testid="error-code">
49
+ {code}
50
+ </h1>
51
+ )}
52
+ <h2 className="text-2xl font-semibold text-gray-700 mb-4">{resolvedTitle}</h2>
53
+ <p className="text-gray-500 mb-8">{resolvedMessage}</p>
54
+
55
+ {children}
56
+
57
+ {showAction &&
58
+ (LinkComponent ? (
59
+ <LinkComponent to={actionHref} className={linkClassName} style={LINK_STYLE}>
60
+ {resolvedActionText}
61
+ </LinkComponent>
62
+ ) : (
63
+ <a href={actionHref} className={linkClassName} style={LINK_STYLE}>
64
+ {resolvedActionText}
65
+ </a>
66
+ ))}
67
+ </div>
68
+ );
69
+ }