@salla.sa/twilight-theme-engine 1.0.84 → 1.0.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AddToCartForm-JUMFLSKN.js → AddToCartForm-5X2WZDWR.js} +3 -3
- package/dist/{AddToCartForm-JUMFLSKN.js.map → AddToCartForm-5X2WZDWR.js.map} +1 -1
- package/dist/{BlogAuthorPage-LFAUMKC7.js → BlogAuthorPage-LO7FSSJR.js} +12 -10
- package/dist/BlogAuthorPage-LO7FSSJR.js.map +1 -0
- package/dist/{BlogCategoryPage-NWYFFPCD.js → BlogCategoryPage-KKFQ3PGR.js} +12 -10
- package/dist/BlogCategoryPage-KKFQ3PGR.js.map +1 -0
- package/dist/{BlogPage-BOHXFV7E.js → BlogPage-VLF3NSGE.js} +11 -9
- package/dist/BlogPage-VLF3NSGE.js.map +1 -0
- package/dist/{BlogSinglePage-FYEXQPMB.js → BlogSinglePage-AVK535IW.js} +11 -9
- package/dist/BlogSinglePage-AVK535IW.js.map +1 -0
- package/dist/{BlogTagPage-DAM3RNVA.js → BlogTagPage-F3T4D7RH.js} +12 -10
- package/dist/BlogTagPage-F3T4D7RH.js.map +1 -0
- package/dist/{BrandsPage-GAKQCE6O.js → BrandsPage-LVSBZSVC.js} +11 -9
- package/dist/BrandsPage-LVSBZSVC.js.map +1 -0
- package/dist/{Breadcrumb-NAYSZNRV.js → Breadcrumb-VLFTXRFC.js} +11 -9
- package/dist/Breadcrumb-VLFTXRFC.js.map +1 -0
- package/dist/{CartItem-ZRVLIZT3.js → CartItem-G3H34GW6.js} +11 -9
- package/dist/CartItem-G3H34GW6.js.map +1 -0
- package/dist/{CartSummary-U2TVLG35.js → CartSummary-VH37RPUI.js} +11 -9
- package/dist/CartSummary-VH37RPUI.js.map +1 -0
- package/dist/{Copyright-2LSBADZD.js → Copyright-HE5ZWINZ.js} +3 -3
- package/dist/{Copyright-2LSBADZD.js.map → Copyright-HE5ZWINZ.js.map} +1 -1
- package/dist/{CustomerLayout-WSKXWI7D.js → CustomerLayout-LKBLOAVW.js} +11 -9
- package/dist/CustomerLayout-LKBLOAVW.js.map +1 -0
- package/dist/{Footer-ONNRJU4G.js → Footer-HHXAST72.js} +5 -5
- package/dist/{Footer-ONNRJU4G.js.map → Footer-HHXAST72.js.map} +1 -1
- package/dist/{Header-C6VXLLUC.js → Header-GEVZM2YG.js} +8 -6
- package/dist/{Header-C6VXLLUC.js.map → Header-GEVZM2YG.js.map} +1 -1
- package/dist/{LoyaltyPage-IG5KSYNO.js → LoyaltyPage-M5CT3CFD.js} +11 -9
- package/dist/LoyaltyPage-M5CT3CFD.js.map +1 -0
- package/dist/{LoyaltyPointsPage-RKO56ZER.js → LoyaltyPointsPage-SDJB52U6.js} +10 -3
- package/dist/LoyaltyPointsPage-SDJB52U6.js.map +1 -0
- package/dist/{MainMenu-USOYTKPU.js → MainMenu-I7EYS752.js} +3 -3
- package/dist/{MainMenu-USOYTKPU.js.map → MainMenu-I7EYS752.js.map} +1 -1
- package/dist/{MasterLayout-2W24ACEK.js → MasterLayout-QDK4T3NE.js} +11 -9
- package/dist/{MasterLayout-2W24ACEK.js.map → MasterLayout-QDK4T3NE.js.map} +1 -1
- package/dist/{MobileMenu-E4X2GOZ3.js → MobileMenu-7VTCWAVJ.js} +11 -9
- package/dist/MobileMenu-7VTCWAVJ.js.map +1 -0
- package/dist/{NotificationsPage-BJ5XVWEG.js → NotificationsPage-QCABE6QD.js} +11 -9
- package/dist/NotificationsPage-QCABE6QD.js.map +1 -0
- package/dist/{OrderSinglePage-544E36FJ.js → OrderSinglePage-2HVBKBG4.js} +11 -9
- package/dist/OrderSinglePage-2HVBKBG4.js.map +1 -0
- package/dist/{OrdersPage-AFBWVTZU.js → OrdersPage-5MNCPCM3.js} +11 -9
- package/dist/OrdersPage-5MNCPCM3.js.map +1 -0
- package/dist/{PageSingle-H2EJIL74.js → PageSingle-EN5XIM2B.js} +3 -3
- package/dist/{PageSingle-H2EJIL74.js.map → PageSingle-EN5XIM2B.js.map} +1 -1
- package/dist/{ProductDetails-SDZHALU6.js → ProductDetails-UEWQPPDZ.js} +13 -6
- package/dist/ProductDetails-UEWQPPDZ.js.map +1 -0
- package/dist/{ProfilePage-V5KV6GQK.js → ProfilePage-TZSU3GHZ.js} +11 -9
- package/dist/ProfilePage-TZSU3GHZ.js.map +1 -0
- package/dist/{ReviewsPage-CXKJABXP.js → ReviewsPage-WYZ6CHF2.js} +3 -3
- package/dist/{ReviewsPage-CXKJABXP.js.map → ReviewsPage-WYZ6CHF2.js.map} +1 -1
- package/dist/{SeoCartWidget-JAV6M3X5.js → SeoCartWidget-5JR5RXQM.js} +11 -9
- package/dist/SeoCartWidget-5JR5RXQM.js.map +1 -0
- package/dist/{SettingsPage-43SUCLQR.js → SettingsPage-DIDRQXMM.js} +11 -9
- package/dist/SettingsPage-DIDRQXMM.js.map +1 -0
- package/dist/{StoreClosedNotification-MINBJOGZ.js → StoreClosedNotification-C2G32E34.js} +12 -10
- package/dist/{StoreClosedNotification-MINBJOGZ.js.map → StoreClosedNotification-C2G32E34.js.map} +1 -1
- package/dist/{TestimonialsPage-RLLIDLOA.js → TestimonialsPage-XPHFRBZ7.js} +3 -3
- package/dist/{TestimonialsPage-RLLIDLOA.js.map → TestimonialsPage-XPHFRBZ7.js.map} +1 -1
- package/dist/{ThankYouPage-52SSZ7FV.js → ThankYouPage-UA6YYFB2.js} +11 -9
- package/dist/ThankYouPage-UA6YYFB2.js.map +1 -0
- package/dist/{WalletPage-4AN4UJ52.js → WalletPage-AJVV7LVL.js} +11 -9
- package/dist/WalletPage-AJVV7LVL.js.map +1 -0
- package/dist/{WishlistPage-L6IYJUJZ.js → WishlistPage-UW6LJLSW.js} +11 -9
- package/dist/WishlistPage-UW6LJLSW.js.map +1 -0
- package/dist/api/advertisement.d.ts +33 -0
- package/dist/api/advertisement.d.ts.map +1 -0
- package/dist/api/advertisement.js +8 -0
- package/dist/api/advertisement.js.map +1 -0
- package/dist/boot/critical-style.d.ts +3 -0
- package/dist/boot/critical-style.d.ts.map +1 -0
- package/dist/boot/index.d.ts +15 -0
- package/dist/boot/index.d.ts.map +1 -0
- package/dist/boot.d.ts +1 -0
- package/dist/boot.js +69 -0
- package/dist/boot.js.map +1 -0
- package/dist/{chunk-R5BQDCDK.js → chunk-2HCCIHU7.js} +4 -4
- package/dist/{chunk-R5BQDCDK.js.map → chunk-2HCCIHU7.js.map} +1 -1
- package/dist/chunk-4FJGZQD3.js +6 -0
- package/dist/chunk-4FJGZQD3.js.map +1 -0
- package/dist/{chunk-UNEZZMQA.js → chunk-4WVJJ7SN.js} +20 -4
- package/dist/chunk-4WVJJ7SN.js.map +1 -0
- package/dist/{chunk-FFWSQJLV.js → chunk-BAYGWTNM.js} +3 -3
- package/dist/{chunk-FFWSQJLV.js.map → chunk-BAYGWTNM.js.map} +1 -1
- package/dist/{chunk-WSRVPR7V.js → chunk-CLBFF2CY.js} +3 -3
- package/dist/{chunk-WSRVPR7V.js.map → chunk-CLBFF2CY.js.map} +1 -1
- package/dist/chunk-DBW7XXNE.js +103 -0
- package/dist/chunk-DBW7XXNE.js.map +1 -0
- package/dist/{chunk-CCUE2FMU.js → chunk-DBXH3EMJ.js} +3 -3
- package/dist/{chunk-CCUE2FMU.js.map → chunk-DBXH3EMJ.js.map} +1 -1
- package/dist/{chunk-MECSOKS7.js → chunk-E26YG3AO.js} +4 -4
- package/dist/{chunk-MECSOKS7.js.map → chunk-E26YG3AO.js.map} +1 -1
- package/dist/{chunk-22SH7OVF.js → chunk-E7WWO3V3.js} +3 -3
- package/dist/{chunk-22SH7OVF.js.map → chunk-E7WWO3V3.js.map} +1 -1
- package/dist/{chunk-IC6XW3PD.js → chunk-HQ7WA37L.js} +7 -7
- package/dist/{chunk-IC6XW3PD.js.map → chunk-HQ7WA37L.js.map} +1 -1
- package/dist/chunk-IVBOLREE.js +29 -0
- package/dist/chunk-IVBOLREE.js.map +1 -0
- package/dist/{chunk-RRGEASAL.js → chunk-KP3VYG4F.js} +6 -6
- package/dist/{chunk-RRGEASAL.js.map → chunk-KP3VYG4F.js.map} +1 -1
- package/dist/{chunk-EFF2WO6W.js → chunk-PWTXR4RR.js} +10 -10
- package/dist/{chunk-EFF2WO6W.js.map → chunk-PWTXR4RR.js.map} +1 -1
- package/dist/chunk-QJYUTRAQ.js +14 -0
- package/dist/{chunk-4EA56R6I.js.map → chunk-QJYUTRAQ.js.map} +1 -1
- package/dist/{chunk-YFQUSAET.js → chunk-U55ZJC2X.js} +4 -4
- package/dist/{chunk-YFQUSAET.js.map → chunk-U55ZJC2X.js.map} +1 -1
- package/dist/{chunk-R54GSKJS.js → chunk-WSDG4ZRI.js} +30 -16
- package/dist/chunk-WSDG4ZRI.js.map +1 -0
- package/dist/{chunk-3PWARKZX.js → chunk-X54QP332.js} +8 -8
- package/dist/chunk-X54QP332.js.map +1 -0
- package/dist/components/cart/index.js +1 -1
- package/dist/components/common/index.js +1 -1
- package/dist/components/drawer/index.js +9 -7
- package/dist/components/home/HomePageRenderer.d.ts +6 -0
- package/dist/components/home/HomePageRenderer.d.ts.map +1 -1
- package/dist/components/home/index.js +11 -9
- package/dist/components/layout/PageAdvertisements.d.ts +7 -0
- package/dist/components/layout/PageAdvertisements.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/layout/index.js +8 -1
- package/dist/components/product/index.js +10 -8
- package/dist/components/store/index.js +11 -9
- package/dist/components/store/index.js.map +1 -1
- package/dist/{document-class-SD6BUMZN.js → document-class-7AXAAWGZ.js} +6 -5
- package/dist/{document-class-SD6BUMZN.js.map → document-class-7AXAAWGZ.js.map} +1 -1
- package/dist/hooks/index.js +9 -7
- package/dist/hooks/useGtm.js +9 -7
- package/dist/hooks/useMoney.js +9 -7
- package/dist/hooks/useStore.js +9 -7
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/{not-found-TUU7WQLL.js → not-found-3EEMQZMU.js} +11 -9
- package/dist/not-found-3EEMQZMU.js.map +1 -0
- package/dist/providers/index.js +9 -7
- package/dist/routes/account/orders.js +1 -1
- package/dist/routes/account.js +6 -6
- package/dist/routes/blog.js +5 -5
- package/dist/routes/brands.js +1 -1
- package/dist/routes/cart.js +13 -11
- package/dist/routes/cart.js.map +1 -1
- package/dist/routes/home.js +12 -10
- package/dist/routes/loyalty.js +2 -2
- package/dist/routes/page.js +1 -1
- package/dist/routes/product-listing.js +11 -9
- package/dist/routes/product-listing.js.map +1 -1
- package/dist/routes/product.js +11 -9
- package/dist/routes/product.js.map +1 -1
- package/dist/routes/root/beforeLoad.d.ts +2 -2
- package/dist/routes/root/beforeLoad.d.ts.map +1 -1
- package/dist/routes/root/head.d.ts.map +1 -1
- package/dist/routes/testimonials.js +2 -2
- package/dist/routes/thank-you.js +1 -1
- package/dist/skeleton.js +12 -10
- package/dist/tanstack/document-class.d.ts.map +1 -1
- package/dist/tanstack/index.d.ts +1 -0
- package/dist/tanstack/index.d.ts.map +1 -1
- package/dist/tanstack/stylesheet.d.ts +15 -0
- package/dist/tanstack/stylesheet.d.ts.map +1 -0
- package/dist/tanstack.js +34 -5
- package/dist/tanstack.js.map +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/vite/index.js +1 -1
- package/package.json +12 -3
- package/tailwind/components/Component.tsx.txt +21 -0
- package/tailwind/components/ComponentRegistry.ts.txt +66 -0
- package/tailwind/components/cart/CartItem.tsx.txt +211 -0
- package/tailwind/components/cart/CartSummary.tsx.txt +269 -0
- package/tailwind/components/cart/PreOrderNotification.tsx.txt +16 -0
- package/tailwind/components/cart/PriceQuoteButton.tsx.txt +21 -0
- package/tailwind/components/cart/SeoCartWidget.tsx.txt +99 -0
- package/tailwind/components/cart/index.ts.txt +11 -0
- package/tailwind/components/cart/lazy.ts.txt +15 -0
- package/tailwind/components/collapse/Collapse.tsx.txt +95 -0
- package/tailwind/components/collapse/index.ts.txt +41 -0
- package/tailwind/components/common/Breadcrumb.tsx.txt +27 -0
- package/tailwind/components/common/BreadcrumbJsonLd.tsx.txt +40 -0
- package/tailwind/components/common/BreadcrumbTrail.tsx.txt +97 -0
- package/tailwind/components/common/CurrencySymbol.tsx.txt +8 -0
- package/tailwind/components/common/DeferredData.tsx.txt +122 -0
- package/tailwind/components/common/ErrorPage.tsx.txt +69 -0
- package/tailwind/components/common/Image.tsx.txt +119 -0
- package/tailwind/components/common/Link.tsx.txt +36 -0
- package/tailwind/components/common/Link.types.ts.txt +85 -0
- package/tailwind/components/common/NoContent.tsx.txt +27 -0
- package/tailwind/components/common/RenderWhenVisible.tsx.txt +92 -0
- package/tailwind/components/common/index.ts.txt +42 -0
- package/tailwind/components/common/lazy.ts.txt +28 -0
- package/tailwind/components/defineComponent.ts.txt +32 -0
- package/tailwind/components/drawer/Drawer.tsx.txt +219 -0
- package/tailwind/components/drawer/index.ts.txt +38 -0
- package/tailwind/components/dropdown/Dropdown.tsx.txt +166 -0
- package/tailwind/components/dropdown/index.ts.txt +40 -0
- package/tailwind/components/home/BundleComponent.tsx.txt +35 -0
- package/tailwind/components/home/ComponentErrorBoundary.tsx.txt +98 -0
- package/tailwind/components/home/FeaturedProductsStyle1.tsx.txt +125 -0
- package/tailwind/components/home/FeaturedProductsStyle2.tsx.txt +127 -0
- package/tailwind/components/home/FeaturedProductsStyle3.tsx.txt +71 -0
- package/tailwind/components/home/FixedBanner.tsx.txt +68 -0
- package/tailwind/components/home/FixedProducts.tsx.txt +72 -0
- package/tailwind/components/home/HomePageRenderer.tsx.txt +284 -0
- package/tailwind/components/home/PageBlocks.tsx.txt +24 -0
- package/tailwind/components/home/ParallaxBackground.tsx.txt +50 -0
- package/tailwind/components/home/PhotosSlider.tsx.txt +61 -0
- package/tailwind/components/home/ProductsSlider.tsx.txt +87 -0
- package/tailwind/components/home/ReorderProducts.tsx.txt +38 -0
- package/tailwind/components/home/SquarePhotos.tsx.txt +68 -0
- package/tailwind/components/home/StoreFeatures.tsx.txt +34 -0
- package/tailwind/components/home/Testimonials.tsx.txt +36 -0
- package/tailwind/components/home/Youtube.tsx.txt +48 -0
- package/tailwind/components/home/index.ts.txt +50 -0
- package/tailwind/components/home/lazy.ts.txt +17 -0
- package/tailwind/components/home/register.ts.txt +44 -0
- package/tailwind/components/home/reorder-hooks.ts.txt +52 -0
- package/tailwind/components/home/types.ts.txt +4 -0
- package/tailwind/components/layout/Copyright.tsx.txt +23 -0
- package/tailwind/components/layout/CustomerLayout.tsx.txt +91 -0
- package/tailwind/components/layout/Footer.tsx.txt +343 -0
- package/tailwind/components/layout/Header.tsx.txt +154 -0
- package/tailwind/components/layout/MasterLayout.tsx.txt +63 -0
- package/tailwind/components/layout/PageAdvertisements.tsx.txt +131 -0
- package/tailwind/components/layout/WidgetHead.tsx.txt +53 -0
- package/tailwind/components/layout/index.ts.txt +27 -0
- package/tailwind/components/layout/lazy.ts.txt +25 -0
- package/tailwind/components/layout/types.ts.txt +11 -0
- package/tailwind/components/modal/ImageModal.tsx.txt +70 -0
- package/tailwind/components/modal/Modal.tsx.txt +245 -0
- package/tailwind/components/modal/index.ts.txt +39 -0
- package/tailwind/components/modal/lazy.ts.txt +10 -0
- package/tailwind/components/navigation/MainMenu.tsx.txt +94 -0
- package/tailwind/components/navigation/MobileMenu.tsx.txt +160 -0
- package/tailwind/components/navigation/index.ts.txt +4 -0
- package/tailwind/components/navigation/lazy.ts.txt +11 -0
- package/tailwind/components/product/AddToCartForm.tsx.txt +365 -0
- package/tailwind/components/product/ProductCard.tsx.txt +305 -0
- package/tailwind/components/product/ProductDetails.tsx.txt +267 -0
- package/tailwind/components/product/ProductGallery.tsx.txt +234 -0
- package/tailwind/components/product/index.ts.txt +15 -0
- package/tailwind/components/product/lazy.ts.txt +9 -0
- package/tailwind/components/snippets/AppsSnippets.tsx.txt +225 -0
- package/tailwind/components/snippets/SallaSdkScripts.tsx.txt +14 -0
- package/tailwind/components/snippets/index.ts.txt +4 -0
- package/tailwind/components/store/StoreClosedNotification.tsx.txt +73 -0
- package/tailwind/components/store/index.ts.txt +8 -0
- package/tailwind/components/store/lazy.ts.txt +8 -0
- package/tailwind/components/toast/Toaster.tsx.txt +177 -0
- package/tailwind/components/toast/index.ts.txt +10 -0
- package/tailwind/components/toast/types.ts.txt +38 -0
- package/tailwind/components/toast/useToast.ts.txt +61 -0
- package/tailwind/components/tracking/GoogleTag.tsx.txt +77 -0
- package/tailwind/components/tracking/SiftSnippet.tsx.txt +49 -0
- package/tailwind/components/tracking/TwilightBundles.tsx.txt +67 -0
- package/tailwind/components/tracking/index.ts.txt +11 -0
- package/tailwind/components/tracking/lazy.ts.txt +11 -0
- package/tailwind/components/useComponent.ts.txt +12 -0
- package/tailwind/routes/account/index.tsx.txt +16 -0
- package/tailwind/routes/account/notifications/NotificationsPage.tsx.txt +54 -0
- package/tailwind/routes/account/notifications/index.tsx.txt +39 -0
- package/tailwind/routes/account/notifications/loader.ts.txt +22 -0
- package/tailwind/routes/account/notifications/types.ts.txt +18 -0
- package/tailwind/routes/account/orders/OrderSinglePage.tsx.txt +181 -0
- package/tailwind/routes/account/orders/OrdersPage.tsx.txt +28 -0
- package/tailwind/routes/account/orders/index.tsx.txt +86 -0
- package/tailwind/routes/account/orders/loader.ts.txt +66 -0
- package/tailwind/routes/account/orders/single/OrderActions.tsx.txt +197 -0
- package/tailwind/routes/account/orders/single/OrderHeader.tsx.txt +143 -0
- package/tailwind/routes/account/orders/single/OrderItemRow.tsx.txt +209 -0
- package/tailwind/routes/account/orders/single/OrderSections.tsx.txt +215 -0
- package/tailwind/routes/account/orders/single/types.ts.txt +38 -0
- package/tailwind/routes/account/orders/types.ts.txt +29 -0
- package/tailwind/routes/account/profile/ProfilePage.tsx.txt +30 -0
- package/tailwind/routes/account/profile/index.tsx.txt +39 -0
- package/tailwind/routes/account/profile/loader.ts.txt +16 -0
- package/tailwind/routes/account/profile/types.ts.txt +5 -0
- package/tailwind/routes/account/settings/SettingsPage.tsx.txt +13 -0
- package/tailwind/routes/account/settings/index.tsx.txt +39 -0
- package/tailwind/routes/account/settings/loader.ts.txt +13 -0
- package/tailwind/routes/account/settings/types.ts.txt +5 -0
- package/tailwind/routes/account/types.ts.txt +320 -0
- package/tailwind/routes/account/wallet/WalletPage.tsx.txt +195 -0
- package/tailwind/routes/account/wallet/index.tsx.txt +38 -0
- package/tailwind/routes/account/wallet/loader.ts.txt +27 -0
- package/tailwind/routes/account/wallet/types.ts.txt +11 -0
- package/tailwind/routes/account/wishlist/WishlistPage.tsx.txt +314 -0
- package/tailwind/routes/account/wishlist/WishlistShare.tsx.txt +248 -0
- package/tailwind/routes/account/wishlist/index.tsx.txt +40 -0
- package/tailwind/routes/account/wishlist/loader.ts.txt +26 -0
- package/tailwind/routes/account/wishlist/types.ts.txt +9 -0
- package/tailwind/routes/blog/BlogAuthorPage.tsx.txt +82 -0
- package/tailwind/routes/blog/BlogCategoryPage.tsx.txt +84 -0
- package/tailwind/routes/blog/BlogPage.tsx.txt +191 -0
- package/tailwind/routes/blog/BlogSinglePage.tsx.txt +168 -0
- package/tailwind/routes/blog/BlogTagPage.tsx.txt +84 -0
- package/tailwind/routes/blog/BlogTags.tsx.txt +54 -0
- package/tailwind/routes/blog/components.tsx.txt +87 -0
- package/tailwind/routes/blog/index.tsx.txt +238 -0
- package/tailwind/routes/blog/loader.ts.txt +173 -0
- package/tailwind/routes/blog/types.ts.txt +106 -0
- package/tailwind/routes/brands/BrandsPage.tsx.txt +179 -0
- package/tailwind/routes/brands/index.tsx.txt +47 -0
- package/tailwind/routes/brands/loader.ts.txt +14 -0
- package/tailwind/routes/brands/types.ts.txt +53 -0
- package/tailwind/routes/cart/CartPage.tsx.txt +112 -0
- package/tailwind/routes/cart/index.tsx.txt +39 -0
- package/tailwind/routes/cart/loader.ts.txt +15 -0
- package/tailwind/routes/cart/types.ts.txt +6 -0
- package/tailwind/routes/home/HomePage.tsx.txt +53 -0
- package/tailwind/routes/home/index.tsx.txt +56 -0
- package/tailwind/routes/home/loader.ts.txt +24 -0
- package/tailwind/routes/home/types.ts.txt +11 -0
- package/tailwind/routes/index.ts.txt +94 -0
- package/tailwind/routes/lazy.ts.txt +105 -0
- package/tailwind/routes/loyalty/LoyaltyPage.tsx.txt +17 -0
- package/tailwind/routes/loyalty/LoyaltyPointsPage.tsx.txt +11 -0
- package/tailwind/routes/loyalty/index.tsx.txt +81 -0
- package/tailwind/routes/loyalty/loader.ts.txt +53 -0
- package/tailwind/routes/loyalty/types.ts.txt +109 -0
- package/tailwind/routes/not-found.tsx.txt +50 -0
- package/tailwind/routes/page/PageSingle.tsx.txt +34 -0
- package/tailwind/routes/page/index.tsx.txt +60 -0
- package/tailwind/routes/page/loader.ts.txt +19 -0
- package/tailwind/routes/page/types.ts.txt +34 -0
- package/tailwind/routes/product/ProductPage.tsx.txt +77 -0
- package/tailwind/routes/product/index.tsx.txt +157 -0
- package/tailwind/routes/product/loader.ts.txt +35 -0
- package/tailwind/routes/product/types.ts.txt +6 -0
- package/tailwind/routes/product-listing/ProductListPage.tsx.txt +321 -0
- package/tailwind/routes/product-listing/index.tsx.txt +95 -0
- package/tailwind/routes/product-listing/loader.ts.txt +323 -0
- package/tailwind/routes/product-listing/types.ts.txt +96 -0
- package/tailwind/routes/product-listing/useAppliedFilters.ts.txt +130 -0
- package/tailwind/routes/product-short-link.ts.txt +104 -0
- package/tailwind/routes/root/beforeLoad.ts.txt +354 -0
- package/tailwind/routes/root/head.ts.txt +424 -0
- package/tailwind/routes/root/index.ts.txt +2 -0
- package/tailwind/routes/seo-redirects.ts.txt +82 -0
- package/tailwind/routes/testimonials/ReviewsPage.tsx.txt +12 -0
- package/tailwind/routes/testimonials/TestimonialsPage.tsx.txt +80 -0
- package/tailwind/routes/testimonials/index.tsx.txt +80 -0
- package/tailwind/routes/testimonials/loader.ts.txt +29 -0
- package/tailwind/routes/testimonials/types.ts.txt +9 -0
- package/tailwind/routes/thank-you/ThankYouPage.tsx.txt +241 -0
- package/tailwind/routes/thank-you/index.tsx.txt +49 -0
- package/tailwind/routes/thank-you/loader.ts.txt +40 -0
- package/tailwind/routes/thank-you/types.ts.txt +16 -0
- package/tailwind/routes/types.ts.txt +41 -0
- package/dist/BlogAuthorPage-LFAUMKC7.js.map +0 -1
- package/dist/BlogCategoryPage-NWYFFPCD.js.map +0 -1
- package/dist/BlogPage-BOHXFV7E.js.map +0 -1
- package/dist/BlogSinglePage-FYEXQPMB.js.map +0 -1
- package/dist/BlogTagPage-DAM3RNVA.js.map +0 -1
- package/dist/BrandsPage-GAKQCE6O.js.map +0 -1
- package/dist/Breadcrumb-NAYSZNRV.js.map +0 -1
- package/dist/CartItem-ZRVLIZT3.js.map +0 -1
- package/dist/CartSummary-U2TVLG35.js.map +0 -1
- package/dist/CustomerLayout-WSKXWI7D.js.map +0 -1
- package/dist/LoyaltyPage-IG5KSYNO.js.map +0 -1
- package/dist/LoyaltyPointsPage-RKO56ZER.js.map +0 -1
- package/dist/MobileMenu-E4X2GOZ3.js.map +0 -1
- package/dist/NotificationsPage-BJ5XVWEG.js.map +0 -1
- package/dist/OrderSinglePage-544E36FJ.js.map +0 -1
- package/dist/OrdersPage-AFBWVTZU.js.map +0 -1
- package/dist/ProductDetails-SDZHALU6.js.map +0 -1
- package/dist/ProfilePage-V5KV6GQK.js.map +0 -1
- package/dist/SeoCartWidget-JAV6M3X5.js.map +0 -1
- package/dist/SettingsPage-43SUCLQR.js.map +0 -1
- package/dist/ThankYouPage-52SSZ7FV.js.map +0 -1
- package/dist/WalletPage-4AN4UJ52.js.map +0 -1
- package/dist/WishlistPage-L6IYJUJZ.js.map +0 -1
- package/dist/chunk-3PWARKZX.js.map +0 -1
- package/dist/chunk-4EA56R6I.js +0 -14
- package/dist/chunk-R54GSKJS.js.map +0 -1
- package/dist/chunk-UNEZZMQA.js.map +0 -1
- package/dist/not-found-TUU7WQLL.js.map +0 -1
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useIsHome } from '../../providers/NavigationProvider';
|
|
3
|
+
import { SallaCartSummary } from '@salla.sa/twilight-components-react/cart-summary';
|
|
4
|
+
import { SallaContacts } from '@salla.sa/twilight-components-react/contacts';
|
|
5
|
+
import { SallaLocalizationModal } from '@salla.sa/twilight-components-react/localization-modal';
|
|
6
|
+
import { SallaMenu } from '@salla.sa/twilight-components-react/menu';
|
|
7
|
+
import { SallaSearch } from '@salla.sa/twilight-components-react/search';
|
|
8
|
+
import { SallaUserMenu } from '@salla.sa/twilight-components-react/user-menu';
|
|
9
|
+
import { useTwilight } from '../../providers/twilight-context';
|
|
10
|
+
import { useTranslation } from '../../providers/I18nProvider';
|
|
11
|
+
import { HookSlot } from '../../hooks/HookSlot';
|
|
12
|
+
import { MainMenu, MobileMenu } from '../navigation';
|
|
13
|
+
import { Image } from '../common';
|
|
14
|
+
import { PageAdvertisements } from './PageAdvertisements';
|
|
15
|
+
import type { Cart } from '../../types';
|
|
16
|
+
|
|
17
|
+
interface HeaderProps {
|
|
18
|
+
cart?: Cart;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Header(_props: HeaderProps) {
|
|
22
|
+
const { currency, theme, store } = useTwilight();
|
|
23
|
+
const { locale, languageName } = useTranslation();
|
|
24
|
+
const settings = theme?.settings || {};
|
|
25
|
+
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
26
|
+
|
|
27
|
+
// Check if we're on homepage
|
|
28
|
+
const isHomepage = useIsHome();
|
|
29
|
+
|
|
30
|
+
const StoreTitle = isHomepage ? 'h1' : 'span';
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<header className="store-header" suppressHydrationWarning>
|
|
34
|
+
<HookSlot name="header:start" />
|
|
35
|
+
<div className="advertisement-slot">
|
|
36
|
+
<PageAdvertisements />
|
|
37
|
+
</div>
|
|
38
|
+
{/* Top Nav */}
|
|
39
|
+
<div className={`top-navbar${settings.topnav_is_dark ? ' top-navbar--dark' : ''}`}>
|
|
40
|
+
<div className="container flex justify-between">
|
|
41
|
+
<div className="flex-1 flex items-center gap-2">
|
|
42
|
+
{/* Footer Menu */}
|
|
43
|
+
{settings.important_links && <SallaMenu source="footer" topnav />}
|
|
44
|
+
|
|
45
|
+
{/* Language & Currency */}
|
|
46
|
+
<div className="header-buttons">
|
|
47
|
+
{(store?.settings?.is_multilingual || store?.settings?.currencies_enabled) && (
|
|
48
|
+
<>
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
onClick={() => window.salla?.event.dispatch('localization::open')}
|
|
52
|
+
className="btn--rounded-gray basis-0"
|
|
53
|
+
>
|
|
54
|
+
<span>{languageName}</span>
|
|
55
|
+
<span className="mx-1.5">|</span>
|
|
56
|
+
<span className={`flag iti__flag iti__${store?.country.toLowerCase()}`}></span>
|
|
57
|
+
|
|
58
|
+
<span>
|
|
59
|
+
{store?.settings?.use_sar_symbol && currency?.code === 'SAR' ? (
|
|
60
|
+
<i className="sicon-sar"></i>
|
|
61
|
+
) : (
|
|
62
|
+
<>{currency?.symbol}</>
|
|
63
|
+
)}
|
|
64
|
+
</span>
|
|
65
|
+
</button>
|
|
66
|
+
{/* TODO:: support switch the lang without hit the web API, by set it in the sdk and local storage */}
|
|
67
|
+
{/* TODO:: Support switch the curency without hit the web API, by set it in the sdk and local storage */}
|
|
68
|
+
<SallaLocalizationModal
|
|
69
|
+
language={locale}
|
|
70
|
+
currency={currency?.code || 'SAR'}
|
|
71
|
+
suppressHydrationWarning
|
|
72
|
+
/>
|
|
73
|
+
</>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Scopes / Branches */}
|
|
78
|
+
{store?.scope && (
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
className="btn--rounded-gray"
|
|
82
|
+
onClick={() =>
|
|
83
|
+
window.salla?.event.dispatch('scopes::open', {
|
|
84
|
+
mode: 'default',
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
>
|
|
88
|
+
<i className="sicon-location rtl:ml-1 ltr:mr-1"></i>
|
|
89
|
+
{store.scope.name}
|
|
90
|
+
</button>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{/* Search bar */}
|
|
94
|
+
<div className="header-search flex-1">
|
|
95
|
+
{/* todo:: use a search icon insted with open the search popup */}
|
|
96
|
+
<SallaSearch inline oval height={36} />
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<SallaContacts contacts={store?.contacts} contactsTitle="N/A" isHeader />
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* Main Nav */}
|
|
105
|
+
<div
|
|
106
|
+
id="mainnav"
|
|
107
|
+
className={`main-nav-container shadow-default bg-white flex items-center relative z-50${settings.header_is_sticky ? ' sticky top-0' : ''}`}
|
|
108
|
+
>
|
|
109
|
+
<div className="inner bg-inherit w-full">
|
|
110
|
+
<div className="container">
|
|
111
|
+
<div className="flex items-stretch justify-between relative">
|
|
112
|
+
<div className="flex items-center">
|
|
113
|
+
<a
|
|
114
|
+
className="lg:hidden mburger mburger--collapse leading-none rtl:ml-4 ltr:mr-4"
|
|
115
|
+
href="#mobile-menu"
|
|
116
|
+
aria-label="Open-menu"
|
|
117
|
+
onClick={(e) => {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
setIsMobileMenuOpen(true);
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
<i className="sicon-menu text-primary text-2xl"></i>
|
|
123
|
+
</a>
|
|
124
|
+
<a className="navbar-brand" href={store?.url || '/'}>
|
|
125
|
+
<div className="logo-wrapper">
|
|
126
|
+
<Image
|
|
127
|
+
src={store?.logo}
|
|
128
|
+
alt={`${store?.name || 'Store'} logo`}
|
|
129
|
+
priority
|
|
130
|
+
width={120}
|
|
131
|
+
height={48}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
<StoreTitle className="sr-only">{store?.name || 'Store'}</StoreTitle>
|
|
135
|
+
</a>
|
|
136
|
+
<MainMenu />
|
|
137
|
+
</div>
|
|
138
|
+
<div className="flex items-center justify-end my-2.5">
|
|
139
|
+
<SallaUserMenu avatarOnly showHeader className="header-btn" />
|
|
140
|
+
<SallaCartSummary className="ml-4 rtl:ml-[unset] rtl:mr-4">
|
|
141
|
+
<i slot="icon" className="header-btn__icon icon sicon-shopping-bag"></i>
|
|
142
|
+
</SallaCartSummary>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{/* Mobile Menu */}
|
|
150
|
+
<MobileMenu isOpen={isMobileMenuOpen} onClose={() => setIsMobileMenuOpen(false)} />
|
|
151
|
+
<HookSlot name="header:end" />
|
|
152
|
+
</header>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { lazy, Suspense } from 'react';
|
|
2
|
+
import { Header } from './Header';
|
|
3
|
+
import { Footer } from './Footer';
|
|
4
|
+
import type { LayoutProps } from '../../types';
|
|
5
|
+
import { useTwilight } from '../../providers/twilight-context';
|
|
6
|
+
import { useUser } from '../../hooks';
|
|
7
|
+
|
|
8
|
+
const SallaLoginModal = lazy(() =>
|
|
9
|
+
import('@salla.sa/twilight-components-react/login-modal').then((m) => ({
|
|
10
|
+
default: m.SallaLoginModal,
|
|
11
|
+
}))
|
|
12
|
+
);
|
|
13
|
+
const SallaScopes = lazy(() =>
|
|
14
|
+
import('@salla.sa/twilight-components-react/scopes').then((m) => ({
|
|
15
|
+
default: m.SallaScopes,
|
|
16
|
+
}))
|
|
17
|
+
);
|
|
18
|
+
const SallaOfferModal = lazy(() =>
|
|
19
|
+
import('@salla.sa/twilight-components-react/offer-modal').then((m) => ({
|
|
20
|
+
default: m.SallaOfferModal,
|
|
21
|
+
}))
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export function MasterLayout({ children }: LayoutProps) {
|
|
25
|
+
const { store } = useTwilight();
|
|
26
|
+
const { isLoggedIn } = useUser();
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
<div className="app-inner flex flex-col min-h-full">
|
|
31
|
+
<Header />
|
|
32
|
+
<main id="main-content" role="main" className="flex-1">
|
|
33
|
+
{children}
|
|
34
|
+
</main>
|
|
35
|
+
<Footer />
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<Suspense>
|
|
39
|
+
<SallaOfferModal />
|
|
40
|
+
</Suspense>
|
|
41
|
+
|
|
42
|
+
{!isLoggedIn && (
|
|
43
|
+
<Suspense>
|
|
44
|
+
<SallaLoginModal
|
|
45
|
+
isEmailAllowed={store?.settings?.auth?.email_allowed}
|
|
46
|
+
isMobileAllowed={store?.settings?.auth?.mobile_allowed}
|
|
47
|
+
isEmailRequired={store?.settings?.auth?.is_email_required}
|
|
48
|
+
suppressHydrationWarning
|
|
49
|
+
/>
|
|
50
|
+
</Suspense>
|
|
51
|
+
)}
|
|
52
|
+
|
|
53
|
+
{store?.scope && (
|
|
54
|
+
<Suspense>
|
|
55
|
+
<SallaScopes
|
|
56
|
+
selection={store?.scope?.display_as === 'popup' ? 'mandatory' : 'optional'}
|
|
57
|
+
suppressHydrationWarning
|
|
58
|
+
/>
|
|
59
|
+
</Suspense>
|
|
60
|
+
)}
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
2
|
+
import { useQuery } from '@tanstack/react-query';
|
|
3
|
+
import { advertisement, advertisementsFor, type Advertisement } from '../../api/advertisement';
|
|
4
|
+
import { whenSallaReady } from '../../utils/sdk-helpers';
|
|
5
|
+
import { useTranslation } from '../../providers/I18nProvider';
|
|
6
|
+
import { getTwilightContext, subscribeTwilight } from '../../twilight/context';
|
|
7
|
+
|
|
8
|
+
const currentPageSlug = () => getTwilightContext().page?.slug;
|
|
9
|
+
|
|
10
|
+
/** The key `<salla-advertisement>` marks a closed ad with, through `salla.storage` (store.js, JSON). */
|
|
11
|
+
const dismissedKey = (id: number) => `statusAd-${id}`;
|
|
12
|
+
|
|
13
|
+
/** Stable while the query has no data, so the effect below doesn't rerun on every render. */
|
|
14
|
+
const NO_ADS: Advertisement[] = [];
|
|
15
|
+
|
|
16
|
+
/** Hides the ads this visitor closed before the first paint; the effect then drops them. */
|
|
17
|
+
const HIDE_DISMISSED_SCRIPT =
|
|
18
|
+
"(function(){try{if(window.self!==window.top)return;document.currentScript.parentNode.querySelectorAll('.s-advertisement[data-id]').forEach(function(e){if(localStorage.getItem('statusAd-'+e.getAttribute('data-id')))e.hidden=true})}catch(e){}})();";
|
|
19
|
+
|
|
20
|
+
function isDismissed(id: number): boolean {
|
|
21
|
+
try {
|
|
22
|
+
return Boolean(window.localStorage.getItem(dismissedKey(id)));
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function dismiss(id: number): void {
|
|
29
|
+
const storage = window.salla?.storage;
|
|
30
|
+
if (typeof storage?.set === 'function') storage.set(dismissedKey(id), 'dismissed');
|
|
31
|
+
else {
|
|
32
|
+
try {
|
|
33
|
+
window.localStorage.setItem(dismissedKey(id), JSON.stringify('dismissed'));
|
|
34
|
+
} catch {
|
|
35
|
+
// Storage disabled: the ad hides for this page view only.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ClientView {
|
|
41
|
+
/** The theme editor's iframe, or the SDK's debug flag: every ad shows, closed or not. */
|
|
42
|
+
preview: boolean;
|
|
43
|
+
/** Inside the Salla mobile app, which shows no ads. */
|
|
44
|
+
inApp: boolean;
|
|
45
|
+
dismissed: Set<number>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The merchant's announcement bars, in place of `<salla-advertisement>`. Same data, rules and
|
|
50
|
+
* markup, but rendered on the server (the ads are prefetched in the root `beforeLoad`), so the
|
|
51
|
+
* bar is in the first paint instead of pushing the page down when the web component fetches it.
|
|
52
|
+
*/
|
|
53
|
+
export function PageAdvertisements() {
|
|
54
|
+
const { t } = useTranslation();
|
|
55
|
+
// The deepest route loader's `page`, the page `salla.config` reports.
|
|
56
|
+
const pageSlug = useSyncExternalStore(subscribeTwilight, currentPageSlug, currentPageSlug);
|
|
57
|
+
// The query's `retry: 0` only bounds the server wait; the browser may retry.
|
|
58
|
+
const { data: ads = NO_ADS } = useQuery({ ...advertisement.queries.list(), retry: 2 });
|
|
59
|
+
const [view, setView] = useState<ClientView | null>(null);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const dismissed = new Set(ads.filter((ad) => isDismissed(ad.id)).map((ad) => ad.id));
|
|
63
|
+
setView({ preview: window.self !== window.top, inApp: false, dismissed });
|
|
64
|
+
return whenSallaReady((sdk) => {
|
|
65
|
+
const config = sdk.config as { isMobileApp?: () => boolean; isDebug?: () => boolean };
|
|
66
|
+
const inApp = Boolean(config?.isMobileApp?.());
|
|
67
|
+
const debug = Boolean(config?.isDebug?.());
|
|
68
|
+
if (inApp || debug) {
|
|
69
|
+
setView((current) =>
|
|
70
|
+
current ? { ...current, inApp, preview: current.preview || debug } : current
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}, [ads]);
|
|
75
|
+
|
|
76
|
+
if (view?.inApp) return null;
|
|
77
|
+
const shown = view?.preview
|
|
78
|
+
? ads
|
|
79
|
+
: advertisementsFor(ads, pageSlug).filter((ad) => !view?.dismissed.has(ad.id));
|
|
80
|
+
if (!shown.length) return null;
|
|
81
|
+
|
|
82
|
+
const close = (id: number) => {
|
|
83
|
+
dismiss(id);
|
|
84
|
+
setView((current) =>
|
|
85
|
+
current ? { ...current, dismissed: new Set(current.dismissed).add(id) } : current
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
{shown.map((ad) => (
|
|
92
|
+
<div
|
|
93
|
+
key={ad.id}
|
|
94
|
+
className="s-advertisement"
|
|
95
|
+
data-id={ad.id}
|
|
96
|
+
style={{ backgroundColor: ad.colors?.bg, color: ad.colors?.text }}
|
|
97
|
+
>
|
|
98
|
+
<div className="s-advertisement-content container flex justify-between items-center py-2.5">
|
|
99
|
+
<span className="s-advertisement-content-main flex-1 font-bold text-center text-sm">
|
|
100
|
+
{/* Rendered without an icon too, as the SDK does: its margin is part of the layout. */}
|
|
101
|
+
<i
|
|
102
|
+
className={`s-advertisement-content-icon inline-block align-middle me-2 text-base ${ad.icon ?? ''}`}
|
|
103
|
+
/>
|
|
104
|
+
{ad.url ? (
|
|
105
|
+
<a
|
|
106
|
+
href={ad.url}
|
|
107
|
+
target={ad.target ?? undefined}
|
|
108
|
+
rel={ad.target === '_blank' ? 'noopener' : undefined}
|
|
109
|
+
>
|
|
110
|
+
{ad.description}
|
|
111
|
+
</a>
|
|
112
|
+
) : (
|
|
113
|
+
ad.description
|
|
114
|
+
)}
|
|
115
|
+
</span>
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
className="s-advertisement-action px-1"
|
|
119
|
+
aria-label={t('common.buttons.close', 'Close')}
|
|
120
|
+
onClick={() => close(ad.id)}
|
|
121
|
+
>
|
|
122
|
+
{/* `inline-block`: before the icon CSS loads, an empty inline `<i>` leaves the button 0px tall. */}
|
|
123
|
+
<i className="sicon-cancel inline-block" />
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
))}
|
|
128
|
+
<script suppressHydrationWarning>{HIDE_DISMISSED_SCRIPT}</script>
|
|
129
|
+
</>
|
|
130
|
+
);
|
|
131
|
+
}
|