@salla.sa/twilight-theme-engine 1.0.85 → 1.0.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AddToCartForm-B5C2M4OZ.js → AddToCartForm-5X2WZDWR.js} +3 -3
- package/dist/{AddToCartForm-B5C2M4OZ.js.map → AddToCartForm-5X2WZDWR.js.map} +1 -1
- package/dist/{BlogAuthorPage-GTF4UGSS.js → BlogAuthorPage-LO7FSSJR.js} +12 -10
- package/dist/BlogAuthorPage-LO7FSSJR.js.map +1 -0
- package/dist/{BlogCategoryPage-TN7Y4NBP.js → BlogCategoryPage-KKFQ3PGR.js} +12 -10
- package/dist/BlogCategoryPage-KKFQ3PGR.js.map +1 -0
- package/dist/{BlogPage-TDB3M3PI.js → BlogPage-VLF3NSGE.js} +11 -9
- package/dist/BlogPage-VLF3NSGE.js.map +1 -0
- package/dist/{BlogSinglePage-4A52Q5QW.js → BlogSinglePage-AVK535IW.js} +11 -9
- package/dist/BlogSinglePage-AVK535IW.js.map +1 -0
- package/dist/{BlogTagPage-TOVPBETA.js → BlogTagPage-F3T4D7RH.js} +12 -10
- package/dist/BlogTagPage-F3T4D7RH.js.map +1 -0
- package/dist/{BrandsPage-VUDGYSQI.js → BrandsPage-LVSBZSVC.js} +11 -9
- package/dist/BrandsPage-LVSBZSVC.js.map +1 -0
- package/dist/{Breadcrumb-CI3SN5YR.js → Breadcrumb-VLFTXRFC.js} +11 -9
- package/dist/Breadcrumb-VLFTXRFC.js.map +1 -0
- package/dist/{CartItem-BHLEURAB.js → CartItem-G3H34GW6.js} +11 -9
- package/dist/CartItem-G3H34GW6.js.map +1 -0
- package/dist/{CartSummary-QERVESYN.js → CartSummary-VH37RPUI.js} +11 -9
- package/dist/CartSummary-VH37RPUI.js.map +1 -0
- package/dist/{Copyright-RL6ALKRC.js → Copyright-HE5ZWINZ.js} +3 -3
- package/dist/{Copyright-RL6ALKRC.js.map → Copyright-HE5ZWINZ.js.map} +1 -1
- package/dist/{CustomerLayout-VRZUWGQK.js → CustomerLayout-LKBLOAVW.js} +11 -9
- package/dist/CustomerLayout-LKBLOAVW.js.map +1 -0
- package/dist/{Footer-ZWBZCMDL.js → Footer-HHXAST72.js} +5 -5
- package/dist/{Footer-ZWBZCMDL.js.map → Footer-HHXAST72.js.map} +1 -1
- package/dist/{Header-WNK4O66B.js → Header-GEVZM2YG.js} +8 -6
- package/dist/{Header-WNK4O66B.js.map → Header-GEVZM2YG.js.map} +1 -1
- package/dist/{LoyaltyPage-CGTHWJ76.js → LoyaltyPage-M5CT3CFD.js} +11 -9
- package/dist/LoyaltyPage-M5CT3CFD.js.map +1 -0
- package/dist/{LoyaltyPointsPage-QSU6K3PW.js → LoyaltyPointsPage-SDJB52U6.js} +10 -3
- package/dist/LoyaltyPointsPage-SDJB52U6.js.map +1 -0
- package/dist/{MainMenu-V3T7F32R.js → MainMenu-I7EYS752.js} +3 -3
- package/dist/{MainMenu-V3T7F32R.js.map → MainMenu-I7EYS752.js.map} +1 -1
- package/dist/{MasterLayout-XQF644LD.js → MasterLayout-QDK4T3NE.js} +11 -9
- package/dist/{MasterLayout-XQF644LD.js.map → MasterLayout-QDK4T3NE.js.map} +1 -1
- package/dist/{MobileMenu-U4BVQYKH.js → MobileMenu-7VTCWAVJ.js} +11 -9
- package/dist/MobileMenu-7VTCWAVJ.js.map +1 -0
- package/dist/{NotificationsPage-ZRRQY7TF.js → NotificationsPage-QCABE6QD.js} +11 -9
- package/dist/NotificationsPage-QCABE6QD.js.map +1 -0
- package/dist/{OrderSinglePage-O3XYVMG5.js → OrderSinglePage-2HVBKBG4.js} +11 -9
- package/dist/OrderSinglePage-2HVBKBG4.js.map +1 -0
- package/dist/{OrdersPage-CMXZF5MI.js → OrdersPage-5MNCPCM3.js} +11 -9
- package/dist/OrdersPage-5MNCPCM3.js.map +1 -0
- package/dist/{PageSingle-IIG2US43.js → PageSingle-EN5XIM2B.js} +3 -3
- package/dist/{PageSingle-IIG2US43.js.map → PageSingle-EN5XIM2B.js.map} +1 -1
- package/dist/{ProductDetails-65GDABI7.js → ProductDetails-UEWQPPDZ.js} +13 -6
- package/dist/ProductDetails-UEWQPPDZ.js.map +1 -0
- package/dist/{ProfilePage-SNJ4WXU4.js → ProfilePage-TZSU3GHZ.js} +11 -9
- package/dist/ProfilePage-TZSU3GHZ.js.map +1 -0
- package/dist/{ReviewsPage-UT7UT55J.js → ReviewsPage-WYZ6CHF2.js} +3 -3
- package/dist/{ReviewsPage-UT7UT55J.js.map → ReviewsPage-WYZ6CHF2.js.map} +1 -1
- package/dist/{SeoCartWidget-DBJAXJKT.js → SeoCartWidget-5JR5RXQM.js} +11 -9
- package/dist/SeoCartWidget-5JR5RXQM.js.map +1 -0
- package/dist/{SettingsPage-FLTIOPU5.js → SettingsPage-DIDRQXMM.js} +11 -9
- package/dist/SettingsPage-DIDRQXMM.js.map +1 -0
- package/dist/{StoreClosedNotification-ZT3QKDKS.js → StoreClosedNotification-C2G32E34.js} +12 -10
- package/dist/{StoreClosedNotification-ZT3QKDKS.js.map → StoreClosedNotification-C2G32E34.js.map} +1 -1
- package/dist/{TestimonialsPage-CU7HKU4O.js → TestimonialsPage-XPHFRBZ7.js} +3 -3
- package/dist/{TestimonialsPage-CU7HKU4O.js.map → TestimonialsPage-XPHFRBZ7.js.map} +1 -1
- package/dist/{ThankYouPage-5X6B4X3T.js → ThankYouPage-UA6YYFB2.js} +11 -9
- package/dist/ThankYouPage-UA6YYFB2.js.map +1 -0
- package/dist/{WalletPage-H2BKIDPA.js → WalletPage-AJVV7LVL.js} +11 -9
- package/dist/WalletPage-AJVV7LVL.js.map +1 -0
- package/dist/{WishlistPage-RS5UCIQ3.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-SE5JYWOK.js → chunk-2HCCIHU7.js} +4 -4
- package/dist/{chunk-SE5JYWOK.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-SDFDS4Q3.js → chunk-4WVJJ7SN.js} +20 -4
- package/dist/chunk-4WVJJ7SN.js.map +1 -0
- package/dist/{chunk-NXQJA7D3.js → chunk-BAYGWTNM.js} +3 -3
- package/dist/{chunk-NXQJA7D3.js.map → chunk-BAYGWTNM.js.map} +1 -1
- package/dist/{chunk-OWX2CPNQ.js → chunk-CLBFF2CY.js} +3 -3
- package/dist/{chunk-OWX2CPNQ.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-KHBSGD2D.js → chunk-DBXH3EMJ.js} +3 -3
- package/dist/{chunk-KHBSGD2D.js.map → chunk-DBXH3EMJ.js.map} +1 -1
- package/dist/{chunk-JRNH3FUF.js → chunk-E26YG3AO.js} +4 -4
- package/dist/{chunk-JRNH3FUF.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-UJKFBZFJ.js → chunk-HQ7WA37L.js} +7 -7
- package/dist/{chunk-UJKFBZFJ.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-6SWDSRIL.js → chunk-KP3VYG4F.js} +6 -6
- package/dist/{chunk-6SWDSRIL.js.map → chunk-KP3VYG4F.js.map} +1 -1
- package/dist/{chunk-4CZRUAZB.js → chunk-PWTXR4RR.js} +10 -10
- package/dist/{chunk-4CZRUAZB.js.map → chunk-PWTXR4RR.js.map} +1 -1
- package/dist/chunk-QJYUTRAQ.js +14 -0
- package/dist/{chunk-7WAXN2PP.js.map → chunk-QJYUTRAQ.js.map} +1 -1
- package/dist/{chunk-JO7JWV34.js → chunk-U55ZJC2X.js} +4 -4
- package/dist/{chunk-JO7JWV34.js.map → chunk-U55ZJC2X.js.map} +1 -1
- package/dist/{chunk-SX5MF2YA.js → chunk-WSDG4ZRI.js} +4 -4
- package/dist/{chunk-SX5MF2YA.js.map → chunk-WSDG4ZRI.js.map} +1 -1
- package/dist/{chunk-NZVR4OF7.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/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-67HTP45G.js → document-class-7AXAAWGZ.js} +6 -5
- package/dist/{document-class-67HTP45G.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-QTHBFPNT.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/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-GTF4UGSS.js.map +0 -1
- package/dist/BlogCategoryPage-TN7Y4NBP.js.map +0 -1
- package/dist/BlogPage-TDB3M3PI.js.map +0 -1
- package/dist/BlogSinglePage-4A52Q5QW.js.map +0 -1
- package/dist/BlogTagPage-TOVPBETA.js.map +0 -1
- package/dist/BrandsPage-VUDGYSQI.js.map +0 -1
- package/dist/Breadcrumb-CI3SN5YR.js.map +0 -1
- package/dist/CartItem-BHLEURAB.js.map +0 -1
- package/dist/CartSummary-QERVESYN.js.map +0 -1
- package/dist/CustomerLayout-VRZUWGQK.js.map +0 -1
- package/dist/LoyaltyPage-CGTHWJ76.js.map +0 -1
- package/dist/LoyaltyPointsPage-QSU6K3PW.js.map +0 -1
- package/dist/MobileMenu-U4BVQYKH.js.map +0 -1
- package/dist/NotificationsPage-ZRRQY7TF.js.map +0 -1
- package/dist/OrderSinglePage-O3XYVMG5.js.map +0 -1
- package/dist/OrdersPage-CMXZF5MI.js.map +0 -1
- package/dist/ProductDetails-65GDABI7.js.map +0 -1
- package/dist/ProfilePage-SNJ4WXU4.js.map +0 -1
- package/dist/SeoCartWidget-DBJAXJKT.js.map +0 -1
- package/dist/SettingsPage-FLTIOPU5.js.map +0 -1
- package/dist/ThankYouPage-5X6B4X3T.js.map +0 -1
- package/dist/WalletPage-H2BKIDPA.js.map +0 -1
- package/dist/WishlistPage-RS5UCIQ3.js.map +0 -1
- package/dist/chunk-7WAXN2PP.js +0 -14
- package/dist/chunk-NZVR4OF7.js.map +0 -1
- package/dist/chunk-SDFDS4Q3.js.map +0 -1
- package/dist/not-found-QTHBFPNT.js.map +0 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
import { useQuery } from '@tanstack/react-query';
|
|
3
|
+
import { apps, type AppSnippetRow } from '../../api/apps';
|
|
4
|
+
import { installAppScope } from '../../utils/app-scope';
|
|
5
|
+
import { applyThemeHookSlots } from '../../utils/theme-hook-slots';
|
|
6
|
+
import { getTwilightContext } from '../../twilight/context';
|
|
7
|
+
|
|
8
|
+
/** How long to wait on the SDK / settings before injecting anyway. */
|
|
9
|
+
const READY_TIMEOUT_MS = 2500;
|
|
10
|
+
|
|
11
|
+
// Module-level so StrictMode double-effects and remounts can't inject twice per page load.
|
|
12
|
+
let injected = false;
|
|
13
|
+
|
|
14
|
+
/** A snippet plus its app's scope id — the key that resolves its config bucket. */
|
|
15
|
+
type OwnedSnippet = AppSnippetRow & { appScopeId: string };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Appends raw snippet HTML to `target`, recreating each `<script>` — innerHTML-parsed
|
|
19
|
+
* scripts are flagged "already started" and never run.
|
|
20
|
+
*/
|
|
21
|
+
export function injectHtml(html: string, target: HTMLElement) {
|
|
22
|
+
const tpl = document.createElement('template');
|
|
23
|
+
tpl.innerHTML = html;
|
|
24
|
+
tpl.content.querySelectorAll('script').forEach((inert) => {
|
|
25
|
+
// importmap scripts can't be added post-init; the engine already injects its own.
|
|
26
|
+
if (inert.type === 'importmap') {
|
|
27
|
+
inert.remove();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const script = document.createElement('script');
|
|
31
|
+
for (const attr of Array.from(inert.attributes)) {
|
|
32
|
+
script.setAttribute(attr.name, attr.value);
|
|
33
|
+
}
|
|
34
|
+
script.text = inert.text;
|
|
35
|
+
inert.replaceWith(script);
|
|
36
|
+
});
|
|
37
|
+
target.appendChild(tpl.content);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Replaces `{{customer.x}}` placeholders with SDK config `user.*` values. Non-`customer.`
|
|
42
|
+
* params are left untouched, matching the classic themes.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveCustomerParams(content: string, params?: string[]): string {
|
|
45
|
+
if (!params?.length) return content;
|
|
46
|
+
for (const param of params) {
|
|
47
|
+
if (!param.startsWith('customer.')) continue;
|
|
48
|
+
const value = String(window.salla?.config?.get(param.replace(/^customer\./, 'user.')) ?? '');
|
|
49
|
+
// Tolerate inner whitespace (`{{ customer.email }}`); function replacement keeps `value`
|
|
50
|
+
// literal so a `$&` in customer data isn't expanded.
|
|
51
|
+
const escaped = param.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
52
|
+
const placeholder = new RegExp(`\\{\\{\\s*${escaped}\\s*\\}\\}`, 'g');
|
|
53
|
+
content = content.replace(placeholder, () => value);
|
|
54
|
+
}
|
|
55
|
+
return content;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Resolves after `ms`, so a promise that may never settle can't block injection. */
|
|
59
|
+
const timeout = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* `onReady` hangs forever if the SDK never initializes; race a timeout so a broken SDK
|
|
63
|
+
* degrades to unsubstituted placeholders instead of dropping every snippet.
|
|
64
|
+
*/
|
|
65
|
+
const waitForSdk = (): Promise<unknown> => {
|
|
66
|
+
const ready = window.salla?.onReady();
|
|
67
|
+
return ready ? Promise.race([ready, timeout(READY_TIMEOUT_MS)]) : Promise.resolve();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Holds the snippets' onReady callbacks until all are injected, as on Twig, where they parse before ready. */
|
|
71
|
+
export function withDeferredOnReady(inject: () => void): void {
|
|
72
|
+
const sdk = window.salla;
|
|
73
|
+
const onReady = sdk?.onReady;
|
|
74
|
+
if (!sdk || typeof onReady !== 'function') return inject();
|
|
75
|
+
|
|
76
|
+
const queued: Array<() => void> = [];
|
|
77
|
+
sdk.onReady = (callback) =>
|
|
78
|
+
new Promise<void>((resolve, reject) => {
|
|
79
|
+
queued.push(() => onReady.call(sdk, callback).then(resolve, reject));
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
inject();
|
|
83
|
+
} finally {
|
|
84
|
+
sdk.onReady = onReady;
|
|
85
|
+
}
|
|
86
|
+
queued.forEach((run) => run());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Whitelists hook slots, installs the config sandbox, then injects the inline snippets.
|
|
91
|
+
* Runs once per page load, after the SDK is ready.
|
|
92
|
+
*/
|
|
93
|
+
function mountSnippets(
|
|
94
|
+
owned: OwnedSnippet[],
|
|
95
|
+
clientSnippets: OwnedSnippet[],
|
|
96
|
+
buckets: Record<string, Record<string, unknown>>
|
|
97
|
+
): void {
|
|
98
|
+
// Backstop for the inline head copy (see renderThemeHookSlotsScript) — that one runs at
|
|
99
|
+
// parse time, ahead of the snippet scripts; this only covers a head that didn't render.
|
|
100
|
+
applyThemeHookSlots();
|
|
101
|
+
|
|
102
|
+
// Install before any snippet resolves config. The inline SSR-head install normally got there
|
|
103
|
+
// first (with these buckets, or with none when its prefetch failed), so this either installs
|
|
104
|
+
// the factory or seeds the buckets into it. Pass the complete map once.
|
|
105
|
+
installAppScope({ apps: buckets });
|
|
106
|
+
|
|
107
|
+
// Flag every CDN scope with a missing bucket (SSR-rendered ones included).
|
|
108
|
+
const unresolved = new Set<string>();
|
|
109
|
+
for (const s of owned) {
|
|
110
|
+
if (s.url && !(s.appScopeId in buckets)) unresolved.add(s.appScopeId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
withDeferredOnReady(() => {
|
|
114
|
+
for (const snippet of clientSnippets) {
|
|
115
|
+
// Honour `place`: a head-placed snippet is often a <meta>/<link>/<style> (site
|
|
116
|
+
// verification, canonical, preload) that crawlers only read inside <head>.
|
|
117
|
+
const target = snippet.place?.includes('head') ? document.head : document.body;
|
|
118
|
+
try {
|
|
119
|
+
injectHtml(resolveCustomerParams(snippet.content, snippet.customer_parameters), target);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(`[salla] failed to inject snippet for app ${snippet.appScopeId}`, error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (unresolved.size) {
|
|
127
|
+
// One line per page, not per snippet; expected during settings-endpoint rollout.
|
|
128
|
+
console.warn(
|
|
129
|
+
`[salla] app settings missing for ${unresolved.size} app(s); their snippets ran without app.* config`,
|
|
130
|
+
[...unresolved]
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A CDN snippet reduced to what its SSR `<script>` tag needs. */
|
|
136
|
+
interface SsrCdnScript {
|
|
137
|
+
key: string;
|
|
138
|
+
url: string;
|
|
139
|
+
snippetId?: number;
|
|
140
|
+
appScopeId: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Mounts installed apps' snippets once per page load:
|
|
145
|
+
* - **CDN** — rendered as SSR `<script>` tags in <body> (see return), so they exist on first
|
|
146
|
+
* paint. `place` doesn't apply: they're deferred scripts, which run in order either way.
|
|
147
|
+
* - **inline** — injected client-side in the effect, into `place`'s target (head or body).
|
|
148
|
+
*/
|
|
149
|
+
export function AppsSnippets() {
|
|
150
|
+
// From twilight context, not useQuery: React Query isn't dehydrated here, so useQuery has no
|
|
151
|
+
// SSR data — but the CDN <script> tags must render server-side.
|
|
152
|
+
const ssrSnippets = getTwilightContext().appsSnippets;
|
|
153
|
+
|
|
154
|
+
// Client fallback for when the SSR prefetch failed or timed out (it fails open, non-fatal).
|
|
155
|
+
// Without it a single blip on `apps/snippets` would drop every installed app's snippets for
|
|
156
|
+
// the whole page load — no CDN tags, no inline scripts, no hook slots. Retries here because
|
|
157
|
+
// the query's own `retry: 0` exists only to bound the SSR wait.
|
|
158
|
+
const { data: fallbackSnippets } = useQuery({
|
|
159
|
+
...apps.queries.snippets(),
|
|
160
|
+
enabled: !ssrSnippets,
|
|
161
|
+
retry: 2,
|
|
162
|
+
});
|
|
163
|
+
const data = ssrSnippets ?? fallbackSnippets;
|
|
164
|
+
|
|
165
|
+
const { data: settings, isSuccess, isError } = useQuery({ ...apps.queries.settings(), retry: 2 });
|
|
166
|
+
|
|
167
|
+
// Fail open: inject once settings settle either way, so snippets that never read `app.*`
|
|
168
|
+
// aren't dropped when the bucket is empty.
|
|
169
|
+
const settingsSettled = isSuccess || isError;
|
|
170
|
+
|
|
171
|
+
// Each enabled app's snippets, tagged with its scope id (the encoded `id`). Older payloads
|
|
172
|
+
// lacking `id` get a deterministic per-app fallback sharing one empty bucket.
|
|
173
|
+
const owned = useMemo<OwnedSnippet[]>(
|
|
174
|
+
() =>
|
|
175
|
+
(data ?? [])
|
|
176
|
+
.filter((app) => app.status === 'enabled')
|
|
177
|
+
.flatMap((app) =>
|
|
178
|
+
app.snippets.map((s) => ({ ...s, appScopeId: app.id ?? `unresolved-${app.app_id}` }))
|
|
179
|
+
),
|
|
180
|
+
[data]
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Every CDN snippet → a real `<script>` tag in the SSR body (rendered below).
|
|
184
|
+
const ssrCdnScripts = useMemo<SsrCdnScript[]>(
|
|
185
|
+
() =>
|
|
186
|
+
owned
|
|
187
|
+
.filter((s) => s.url)
|
|
188
|
+
.map((s, i) => ({
|
|
189
|
+
key: `${s.appScopeId}:${s.id ?? i}`,
|
|
190
|
+
url: s.url as string,
|
|
191
|
+
snippetId: s.id,
|
|
192
|
+
appScopeId: s.appScopeId,
|
|
193
|
+
})),
|
|
194
|
+
[owned]
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (injected || !data || !settingsSettled) return;
|
|
199
|
+
injected = true;
|
|
200
|
+
|
|
201
|
+
// Client-injected: inline snippets only. Every CDN snippet is SSR-rendered (below).
|
|
202
|
+
const clientSnippets = owned.filter((s) => !s.url);
|
|
203
|
+
|
|
204
|
+
// Always wait for the SDK: a snippet body calls the global `salla`/`Salla` (config, hooks,
|
|
205
|
+
// event bus) whether or not it declares customer parameters, and those are only populated
|
|
206
|
+
// after init. The wait is timeout-raced, so a broken SDK still can't strand injection.
|
|
207
|
+
void waitForSdk().then(() => mountSnippets(owned, clientSnippets, settings?.apps ?? {}));
|
|
208
|
+
}, [data, owned, settings, settingsSettled]);
|
|
209
|
+
|
|
210
|
+
// CDN snippets as classic `defer` scripts: non-blocking, in-order, kept in <body> (React hoists
|
|
211
|
+
// only `async` to <head>), and still exposing `document.currentScript` for the wrapper.
|
|
212
|
+
return (
|
|
213
|
+
<>
|
|
214
|
+
{ssrCdnScripts.map((s) => (
|
|
215
|
+
<script
|
|
216
|
+
key={s.key}
|
|
217
|
+
src={s.url}
|
|
218
|
+
defer
|
|
219
|
+
data-scope-id={s.appScopeId}
|
|
220
|
+
data-snippet-id={s.snippetId != null ? String(s.snippetId) : ''}
|
|
221
|
+
/>
|
|
222
|
+
))}
|
|
223
|
+
</>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StoreContext } from '../../api/store';
|
|
2
|
+
import { resolveTwilightSdkUrl, TICKETING_SCRIPT_URL } from '../../routes/root/head';
|
|
3
|
+
|
|
4
|
+
/** SDK modules at the end of <body>, ahead of the snippets: same run order as from <head>, later download. */
|
|
5
|
+
export function SallaSdkScripts({ settings }: { settings?: StoreContext }) {
|
|
6
|
+
return (
|
|
7
|
+
<>
|
|
8
|
+
<script id="salla-sdk" type="module" src={resolveTwilightSdkUrl(settings)} />
|
|
9
|
+
{settings?.store?.settings?.ticketing_system_enabled && (
|
|
10
|
+
<script id="salla-ticketing-script" type="module" src={TICKETING_SCRIPT_URL} />
|
|
11
|
+
)}
|
|
12
|
+
</>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Component - injects installed marketplace apps' storefront snippets (inline + CDN).
|
|
2
|
+
// `injectHtml` / `resolveCustomerParams` stay internal to the component (tests import them
|
|
3
|
+
// from the module directly) — themes only ever render <AppsSnippets />.
|
|
4
|
+
export { AppsSnippets } from './AppsSnippets';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useIsProduct } from '../../providers/NavigationProvider';
|
|
3
|
+
import { useTwilight } from '../../providers/TwilightProvider';
|
|
4
|
+
import { useOpeningHours } from '../../hooks/useOpeningHours';
|
|
5
|
+
import { useTranslation } from '../../providers/I18nProvider';
|
|
6
|
+
|
|
7
|
+
export interface StoreClosedNotificationProps {
|
|
8
|
+
/** Whether to show the notification (defaults to true when store is closed on product page) */
|
|
9
|
+
show?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* StoreClosedNotification - Shows a notification bar when the store is closed.
|
|
14
|
+
*
|
|
15
|
+
* Mirrors PHP ReceivingOrdersTimesWidget:
|
|
16
|
+
* - Only shows on product pages
|
|
17
|
+
* - Only shows when store is closed for receiving orders
|
|
18
|
+
* - Displays when the store will open next
|
|
19
|
+
* - Has a close button to dismiss
|
|
20
|
+
*
|
|
21
|
+
* Injected via hook registry at body:start.
|
|
22
|
+
*/
|
|
23
|
+
export function StoreClosedNotification(_props: StoreClosedNotificationProps) {
|
|
24
|
+
const { store } = useTwilight();
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
const { isOpen, isEnabled, nextOpenFormatted } = useOpeningHours(store.settings?.opening_hours);
|
|
27
|
+
const [dismissed, setDismissed] = useState(false);
|
|
28
|
+
|
|
29
|
+
// Check if we're on a product page
|
|
30
|
+
const isProductPage = useIsProduct();
|
|
31
|
+
|
|
32
|
+
// Conditions: product page, opening hours enabled, store closed, not dismissed
|
|
33
|
+
if (!isProductPage || !isEnabled || isOpen || dismissed || !nextOpenFormatted) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handleClose = (e: React.MouseEvent) => {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
setDismissed(true);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className="store-notify"
|
|
45
|
+
id="store-closed-notification"
|
|
46
|
+
data-testid="store-closed-notification"
|
|
47
|
+
style={{
|
|
48
|
+
backgroundColor: '#d63031',
|
|
49
|
+
color: '#fff',
|
|
50
|
+
padding: '10px 15px',
|
|
51
|
+
display: 'flex',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
justifyContent: 'center',
|
|
54
|
+
position: 'relative',
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<a
|
|
58
|
+
href="#"
|
|
59
|
+
id="notify-close"
|
|
60
|
+
onClick={handleClose}
|
|
61
|
+
style={{ position: 'absolute', left: 15, color: '#fff', textDecoration: 'none' }}
|
|
62
|
+
>
|
|
63
|
+
<i className="sicon-cancel" />
|
|
64
|
+
</a>
|
|
65
|
+
<p style={{ margin: 0, textAlign: 'center', flex: 1 }}>
|
|
66
|
+
{t('store.closed_notification', {
|
|
67
|
+
defaultValue: `المتجر حاليا مغلق لاستقبال الطلبات وسيتم فتحه (${nextOpenFormatted})`,
|
|
68
|
+
nextOpen: nextOpenFormatted,
|
|
69
|
+
})}
|
|
70
|
+
</p>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Toaster as SonnerToaster, toast } from 'sonner';
|
|
3
|
+
import { useTwilight } from '../../providers/twilight-context';
|
|
4
|
+
import { isNativeApp } from '../../native-app';
|
|
5
|
+
import type { ToastPosition } from './types';
|
|
6
|
+
|
|
7
|
+
const toastClassNames = {
|
|
8
|
+
toast: 'group bg-white border-2 rounded-xl shadow-2xl p-4 font-primary',
|
|
9
|
+
title: 'text-base font-bold',
|
|
10
|
+
description: 'text-sm mt-1 text-gray-600',
|
|
11
|
+
actionButton: 'bg-primary text-white px-3 py-1 rounded',
|
|
12
|
+
cancelButton: 'bg-gray-100 px-3 py-1 rounded',
|
|
13
|
+
success: 'border-green-500 [&_svg]:text-green-500 [&_aside]:text-green-700',
|
|
14
|
+
error: 'border-red-500 [&_svg]:text-red-500 [&_aside]:text-red-700',
|
|
15
|
+
warning: 'border-yellow-500 [&_svg]:text-yellow-500 [&_aside]:text-yellow-700',
|
|
16
|
+
info: 'border-blue-500 [&_svg]:text-blue-500 [&_aside]:text-blue-700',
|
|
17
|
+
loading: 'border-gray-400',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type RawMessage = string | { title?: string; text?: string; icon?: string };
|
|
21
|
+
type ToastVariant = 'success' | 'error' | 'warning' | 'info';
|
|
22
|
+
|
|
23
|
+
const MOBILE_BREAKPOINT = 768;
|
|
24
|
+
const NATIVE_ALERT_EVENT = 'ui::alert';
|
|
25
|
+
|
|
26
|
+
function dispatchNativeAlert(type: ToastVariant, message: string, title?: string) {
|
|
27
|
+
const salla = window.salla;
|
|
28
|
+
if (!isNativeApp() || !salla?.event?.dispatch) return false;
|
|
29
|
+
|
|
30
|
+
salla.event.dispatch(NATIVE_ALERT_EVENT, {
|
|
31
|
+
type,
|
|
32
|
+
message,
|
|
33
|
+
...(title ? { title } : {}),
|
|
34
|
+
});
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function Toaster({
|
|
39
|
+
mobilePosition = 'bottom-center',
|
|
40
|
+
addToCartToast = true,
|
|
41
|
+
}: {
|
|
42
|
+
mobilePosition?: ToastPosition;
|
|
43
|
+
/** Show the default add-to-cart toast. Themes whose cart badge is the feedback can turn it off. */
|
|
44
|
+
addToCartToast?: boolean;
|
|
45
|
+
}) {
|
|
46
|
+
const { theme } = useTwilight();
|
|
47
|
+
const themeRef = useRef(theme);
|
|
48
|
+
themeRef.current = theme;
|
|
49
|
+
const flashShownRef = useRef(false);
|
|
50
|
+
|
|
51
|
+
const isRtl = theme.is_rtl;
|
|
52
|
+
const desktopPosition: ToastPosition = isRtl ? 'top-left' : 'top-right';
|
|
53
|
+
|
|
54
|
+
const [position, setPosition] = useState<ToastPosition>(() => {
|
|
55
|
+
if (typeof window === 'undefined') return desktopPosition;
|
|
56
|
+
return window.innerWidth < MOBILE_BREAKPOINT ? mobilePosition : desktopPosition;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const handleResize = () => {
|
|
61
|
+
setPosition(window.innerWidth < MOBILE_BREAKPOINT ? mobilePosition : desktopPosition);
|
|
62
|
+
};
|
|
63
|
+
window.addEventListener('resize', handleResize);
|
|
64
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
65
|
+
}, [desktopPosition, mobilePosition]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const salla = window.salla;
|
|
69
|
+
if (!salla) return;
|
|
70
|
+
let active = true;
|
|
71
|
+
|
|
72
|
+
const handleNotify = (message: RawMessage, type: string, data?: unknown) => {
|
|
73
|
+
const eventData = data as { data?: { googleTags?: { event?: string } } } | undefined;
|
|
74
|
+
const shouldSuppressAddToCartToast =
|
|
75
|
+
(!addToCartToast || themeRef.current.settings?.enable_add_product_toast) &&
|
|
76
|
+
eventData?.data?.googleTags?.event === 'addToCart';
|
|
77
|
+
let title: string;
|
|
78
|
+
let toastType: ToastVariant;
|
|
79
|
+
|
|
80
|
+
if (typeof message === 'object' && message !== null) {
|
|
81
|
+
title = message.title || message.text || '';
|
|
82
|
+
toastType = (message.icon || type || 'info') as ToastVariant;
|
|
83
|
+
} else {
|
|
84
|
+
title = message;
|
|
85
|
+
toastType = (type || 'info') as ToastVariant;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Suppress the default addToCart toast when the theme opted out (addToCartToast=false)
|
|
89
|
+
// or renders its own rich toast (enable_add_product_toast) so they don't double up.
|
|
90
|
+
if (shouldSuppressAddToCartToast) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (dispatchNativeAlert(toastType, title)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (toastType === 'success') {
|
|
99
|
+
toast.success(title, { duration: 3000 });
|
|
100
|
+
} else if (toastType === 'error') {
|
|
101
|
+
toast.error(title, { duration: 3000 });
|
|
102
|
+
} else if (toastType === 'warning') {
|
|
103
|
+
toast.warning(title, { duration: 3000 });
|
|
104
|
+
} else {
|
|
105
|
+
toast.info(title, { duration: 3000 });
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Before the SDK loads `salla` is the head stub, and the SDK's default notifier is alert().
|
|
110
|
+
void (async () => {
|
|
111
|
+
try {
|
|
112
|
+
await salla.onReady?.();
|
|
113
|
+
} catch {
|
|
114
|
+
// onReady() may fail during partial initialization; the toast still beats alert().
|
|
115
|
+
}
|
|
116
|
+
if (active) salla.notify?.setNotifier?.(handleNotify);
|
|
117
|
+
})();
|
|
118
|
+
return () => {
|
|
119
|
+
active = false;
|
|
120
|
+
salla.notify?.setNotifier?.(() => {});
|
|
121
|
+
};
|
|
122
|
+
}, [addToCartToast]);
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (flashShownRef.current || typeof window === 'undefined') return;
|
|
126
|
+
|
|
127
|
+
const params = new URLSearchParams(window.location.search);
|
|
128
|
+
const successMsg = params.get('success');
|
|
129
|
+
const dangerMsg = params.get('danger');
|
|
130
|
+
|
|
131
|
+
if (!successMsg && !dangerMsg) return;
|
|
132
|
+
|
|
133
|
+
flashShownRef.current = true;
|
|
134
|
+
|
|
135
|
+
const message = successMsg || dangerMsg || '';
|
|
136
|
+
const toastType = dangerMsg ? 'error' : 'success';
|
|
137
|
+
|
|
138
|
+
const url = new URL(window.location.href);
|
|
139
|
+
url.searchParams.delete('success');
|
|
140
|
+
url.searchParams.delete('danger');
|
|
141
|
+
window.history.replaceState({}, '', url.toString());
|
|
142
|
+
|
|
143
|
+
const timer = setTimeout(() => {
|
|
144
|
+
if (dispatchNativeAlert(toastType, message)) return;
|
|
145
|
+
|
|
146
|
+
if (toastType === 'success') {
|
|
147
|
+
toast.success(message, { duration: 3000 });
|
|
148
|
+
} else {
|
|
149
|
+
toast.error(message, { duration: 3000 });
|
|
150
|
+
}
|
|
151
|
+
}, 100);
|
|
152
|
+
|
|
153
|
+
return () => clearTimeout(timer);
|
|
154
|
+
}, []);
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<SonnerToaster
|
|
158
|
+
position={position}
|
|
159
|
+
// Keep sonner's default gaps (24px desktop / 16px mobile) but clear the
|
|
160
|
+
// native status bar inset (--app-safe-area-top, set by useSafeAreaInset)
|
|
161
|
+
// so top-positioned toasts aren't covered in the app webview.
|
|
162
|
+
offset={{ top: 'calc(var(--app-safe-area-top, 0px) + 24px)' }}
|
|
163
|
+
mobileOffset={{ top: 'calc(var(--app-safe-area-top, 0px) + 16px)' }}
|
|
164
|
+
toastOptions={{
|
|
165
|
+
classNames: toastClassNames,
|
|
166
|
+
style: {
|
|
167
|
+
fontFamily: 'var(--font-main, inherit)',
|
|
168
|
+
},
|
|
169
|
+
}}
|
|
170
|
+
style={{
|
|
171
|
+
fontFamily: 'var(--font-main, inherit)',
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type { ToasterProps } from 'sonner';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ExternalToast } from 'sonner';
|
|
2
|
+
|
|
3
|
+
export type ToastType = 'success' | 'error' | 'warning' | 'info' | 'loading';
|
|
4
|
+
|
|
5
|
+
export type ToastPosition =
|
|
6
|
+
| 'top-left'
|
|
7
|
+
| 'top-right'
|
|
8
|
+
| 'bottom-left'
|
|
9
|
+
| 'bottom-right'
|
|
10
|
+
| 'top-center'
|
|
11
|
+
| 'bottom-center';
|
|
12
|
+
|
|
13
|
+
export interface ToastOptions extends ExternalToast {
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ToastUpdateOptions extends ToastOptions {
|
|
18
|
+
title?: string;
|
|
19
|
+
type?: ToastType;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ToastPromiseOptions<T = unknown> {
|
|
23
|
+
loading: string;
|
|
24
|
+
success: string | ((data: T) => string);
|
|
25
|
+
error: string | ((error: Error) => string);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ToastFn {
|
|
29
|
+
(title: string, options?: ToastOptions): string | number;
|
|
30
|
+
success: (title: string, options?: ToastOptions) => string | number;
|
|
31
|
+
error: (title: string, options?: ToastOptions) => string | number;
|
|
32
|
+
warning: (title: string, options?: ToastOptions) => string | number;
|
|
33
|
+
info: (title: string, options?: ToastOptions) => string | number;
|
|
34
|
+
loading: (title: string, options?: ToastOptions) => string | number;
|
|
35
|
+
update: (id: string | number, options: ToastUpdateOptions) => void;
|
|
36
|
+
dismiss: (id?: string | number) => void;
|
|
37
|
+
promise: <T>(promise: Promise<T>, options: ToastPromiseOptions<T>) => Promise<T>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { toast as sonnerToast } from 'sonner';
|
|
2
|
+
import type { ToastT } from 'sonner';
|
|
3
|
+
import type { ToastFn, ToastOptions, ToastUpdateOptions, ToastPromiseOptions } from './types';
|
|
4
|
+
|
|
5
|
+
export const useToast: ToastFn = Object.assign(
|
|
6
|
+
(title: string, options?: ToastOptions) => sonnerToast(title, options),
|
|
7
|
+
{
|
|
8
|
+
success: (title: string, options?: ToastOptions) => sonnerToast.success(title, options),
|
|
9
|
+
error: (title: string, options?: ToastOptions) => sonnerToast.error(title, options),
|
|
10
|
+
warning: (title: string, options?: ToastOptions) => sonnerToast.warning(title, options),
|
|
11
|
+
info: (title: string, options?: ToastOptions) => sonnerToast.info(title, options),
|
|
12
|
+
loading: (title: string, options?: ToastOptions) => sonnerToast.loading(title, options),
|
|
13
|
+
update: (id: string | number, options: ToastUpdateOptions) => {
|
|
14
|
+
// sonner updates an existing toast when `id` is passed in the options;
|
|
15
|
+
// the first argument is the message (title), not the id.
|
|
16
|
+
const { title, type, ...rest } = options;
|
|
17
|
+
const opts = { ...rest, id };
|
|
18
|
+
// sonner overwrites the title with the message, so reuse the current one when none is given.
|
|
19
|
+
const message =
|
|
20
|
+
title ??
|
|
21
|
+
sonnerToast.getToasts().find((t): t is ToastT => t.id === id && 'title' in t)?.title;
|
|
22
|
+
if (type === 'success') {
|
|
23
|
+
sonnerToast.success(message, opts);
|
|
24
|
+
} else if (type === 'error') {
|
|
25
|
+
sonnerToast.error(message, opts);
|
|
26
|
+
} else if (type === 'warning') {
|
|
27
|
+
sonnerToast.warning(message, opts);
|
|
28
|
+
} else if (type === 'info') {
|
|
29
|
+
sonnerToast.info(message, opts);
|
|
30
|
+
} else if (type === 'loading') {
|
|
31
|
+
sonnerToast.loading(message, opts);
|
|
32
|
+
} else {
|
|
33
|
+
sonnerToast(message, opts);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
dismiss: (id?: string | number) => {
|
|
37
|
+
if (id !== undefined) {
|
|
38
|
+
sonnerToast.dismiss(id);
|
|
39
|
+
} else {
|
|
40
|
+
sonnerToast.dismiss();
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
promise: <T>(promise: Promise<T>, options: ToastPromiseOptions<T>): Promise<T> => {
|
|
44
|
+
sonnerToast.promise(promise, {
|
|
45
|
+
loading: options.loading,
|
|
46
|
+
success: options.success,
|
|
47
|
+
error: options.error,
|
|
48
|
+
});
|
|
49
|
+
return promise;
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
) as ToastFn;
|
|
53
|
+
|
|
54
|
+
export { useToast as toast };
|
|
55
|
+
export type {
|
|
56
|
+
ToastOptions,
|
|
57
|
+
ToastType,
|
|
58
|
+
ToastPosition,
|
|
59
|
+
ToastUpdateOptions,
|
|
60
|
+
ToastPromiseOptions,
|
|
61
|
+
} from './types';
|