@salla.sa/twilight-components 2.14.352 → 2.14.354
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/cjs/bell-ring-BfKPinNo.js +13 -0
- package/dist/cjs/{interfaces-CX9-6aLf.js → camera-DytepEoK.js} +0 -11
- package/dist/cjs/cancel-De6vslRA.js +13 -0
- package/dist/cjs/cart-s-x1Fshk.js +13 -0
- package/dist/cjs/check-circle2-BDvlT4_n.js +13 -0
- package/dist/cjs/{check-CLRvuniI.js → check-x3w3-gpj.js} +2 -2
- package/dist/cjs/{filepond-XtsZ6xtH.js → filepond-CuKErtOy.js} +1 -1
- package/dist/cjs/{filepond-plugin-file-poster-Bj84Ypvg.js → filepond-plugin-file-poster-d-8BSuST.js} +1 -1
- package/dist/cjs/{filepond-plugin-file-validate-size-aYfb4yYH.js → filepond-plugin-file-validate-size-zgI_JcqY.js} +1 -1
- package/dist/cjs/{filepond-plugin-file-validate-type-CJsd6rXl.js → filepond-plugin-file-validate-type-Cy8IgG2P.js} +1 -1
- package/dist/cjs/{filepond-plugin-image-edit-DRlBSg36.js → filepond-plugin-image-edit-9ZAUzCvh.js} +1 -1
- package/dist/cjs/{filepond-plugin-image-exif-orientation-SY8c6DzI.js → filepond-plugin-image-exif-orientation-DzNe_tY2.js} +1 -1
- package/dist/cjs/{filepond-plugin-image-preview-iqhJmUmU.js → filepond-plugin-image-preview-Cfna6xTB.js} +1 -1
- package/dist/{esm/gift-C0JNGIpa.js → cjs/gift-CJ-3Yw_x.js} +4 -2
- package/dist/cjs/image-BoZ6Hums.js +13 -0
- package/dist/cjs/{index-Z-cyrNSM.js → index-Ce40E8tZ.js} +136 -12
- package/dist/cjs/{index-C7-280f4.js → index-ff-xJfhj.js} +1 -1
- package/dist/cjs/interfaces-CRqrf5RX.js +15 -0
- package/dist/cjs/keyboard_arrow_down-DHJ3FFZq.js +13 -0
- package/dist/cjs/keyboard_arrow_right-BayM_Il2.js +21 -0
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/minus-CCryh1qf.js +21 -0
- package/dist/cjs/salla-accordion-body_3.cjs.entry.js +775 -0
- package/dist/cjs/salla-accordion_6.cjs.entry.js +768 -0
- package/dist/cjs/salla-add-product-button_4.cjs.entry.js +2389 -0
- package/dist/cjs/salla-advertisement.cjs.entry.js +1 -1
- package/dist/cjs/salla-alert_2.cjs.entry.js +194 -0
- package/dist/cjs/salla-app-install-alert.cjs.entry.js +8 -3
- package/dist/cjs/salla-apps-icons.cjs.entry.js +1 -1
- package/dist/cjs/salla-booking-field_7.cjs.entry.js +1565 -0
- package/dist/cjs/{salla-cart-item-offers.cjs.entry.js → salla-cart-item-offers_2.cjs.entry.js} +104 -5
- package/dist/cjs/salla-comment-form_8.cjs.entry.js +1661 -0
- package/dist/cjs/salla-conditional-offer.cjs.entry.js +1 -1
- package/dist/cjs/salla-contacts.cjs.entry.js +3 -3
- package/dist/cjs/salla-count-down_2.cjs.entry.js +302 -0
- package/dist/cjs/salla-custom-fields.cjs.entry.js +4 -3
- package/dist/cjs/salla-filters-widget.cjs.entry.js +1 -1
- package/dist/cjs/salla-filters.cjs.entry.js +1 -1
- package/dist/cjs/salla-gifting.cjs.entry.js +488 -0
- package/dist/cjs/salla-hook.cjs.entry.js +1 -1
- package/dist/cjs/salla-infinite-scroll.cjs.entry.js +91 -0
- package/dist/cjs/salla-installment.cjs.entry.js +1 -1
- package/dist/cjs/salla-list-tile.cjs.entry.js +34 -0
- package/dist/cjs/salla-localization-modal.cjs.entry.js +137 -0
- package/dist/cjs/salla-login-modal.cjs.entry.js +320 -0
- package/dist/cjs/salla-loyalty-prize-item.cjs.entry.js +1 -1
- package/dist/cjs/salla-loyalty-program.cjs.entry.js +3 -3
- package/dist/cjs/salla-loyalty.cjs.entry.js +211 -0
- package/dist/cjs/salla-maintenance-alert.cjs.entry.js +40 -0
- package/dist/cjs/salla-menu.cjs.entry.js +139 -0
- package/dist/cjs/salla-metadata.cjs.entry.js +1 -1
- package/dist/cjs/salla-multiple-bundle-product-cart_2.cjs.entry.js +220 -0
- package/dist/cjs/salla-multiple-bundle-product-options-modal_2.cjs.entry.js +598 -0
- package/dist/cjs/salla-multiple-bundle-product.cjs.entry.js +69 -0
- package/dist/cjs/salla-notification-item.cjs.entry.js +1 -1
- package/dist/cjs/salla-notifications.cjs.entry.js +1 -1
- package/dist/cjs/salla-offer-modal.cjs.entry.js +206 -0
- package/dist/cjs/salla-offer.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-details-multiple-bundle-product.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-details-options.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-details.cjs.entry.js +1 -1
- package/dist/cjs/salla-order-summary.cjs.entry.js +3 -3
- package/dist/cjs/salla-order-totals-card.cjs.entry.js +1 -1
- package/dist/cjs/salla-orders.cjs.entry.js +1 -1
- package/dist/cjs/salla-payments.cjs.entry.js +1 -1
- package/dist/cjs/salla-placeholder.cjs.entry.js +51 -0
- package/dist/cjs/salla-price-range.cjs.entry.js +1 -1
- package/dist/cjs/salla-product-size-guide.cjs.entry.js +66 -0
- package/dist/cjs/salla-products-list.cjs.entry.js +762 -0
- package/dist/cjs/salla-products-slider.cjs.entry.js +115 -0
- package/dist/cjs/salla-progress-bar.cjs.entry.js +73 -0
- package/dist/cjs/salla-quick-order.cjs.entry.js +239 -0
- package/dist/cjs/salla-rating-modal.cjs.entry.js +453 -0
- package/dist/cjs/salla-scopes.cjs.entry.js +180 -0
- package/dist/cjs/salla-search.cjs.entry.js +153 -0
- package/dist/cjs/salla-skeleton.cjs.entry.js +36 -0
- package/dist/cjs/salla-slider.cjs.entry.js +10389 -0
- package/dist/cjs/salla-social-share.cjs.entry.js +165 -0
- package/dist/cjs/salla-social.cjs.entry.js +3 -3
- package/dist/cjs/salla-tab-content_3.cjs.entry.js +157 -0
- package/dist/cjs/salla-tiered-offer.cjs.entry.js +1 -1
- package/dist/cjs/salla-tooltip.cjs.entry.js +1 -1
- package/dist/cjs/salla-trust-badges.cjs.entry.js +1 -1
- package/dist/cjs/salla-user-menu.cjs.entry.js +275 -0
- package/dist/cjs/salla-user-profile.cjs.entry.js +145 -0
- package/dist/cjs/salla-user-settings.cjs.entry.js +88 -0
- package/dist/cjs/salla-verify.cjs.entry.js +1 -1
- package/dist/cjs/salla-wallet.cjs.entry.js +1 -1
- package/dist/cjs/search-c7Aa7lM9.js +13 -0
- package/dist/cjs/{special-discount-OVG_9Kf9.js → special-discount-DC2oXurL.js} +0 -8
- package/dist/cjs/star-DGcH7Yso.js +13 -0
- package/dist/cjs/star2-R146a27p.js +13 -0
- package/dist/cjs/twilight.cjs.js +2 -2
- package/dist/cjs/{vanilla-picker-Dq7F5bE1.js → vanilla-picker-zQsXQIff.js} +1 -1
- package/dist/cjs/{whatsapp2-D7Sbg8Ey.js → whatsapp2-BdMd5Gx1.js} +2 -2
- package/dist/collection/collection-manifest.json +55 -1
- package/dist/collection/components/salla-add-product-button/salla-add-product-button.js +18 -6
- package/dist/collection/components/salla-app-install-alert/salla-app-install-alert.js +7 -2
- package/dist/collection/components/salla-color-picker/salla-color-picker.js +39 -8
- package/dist/collection/components/salla-maintenance-alert/salla-maintenance-alert.js +4 -2
- package/dist/collection/components/salla-product-options/salla-product-options.js +36 -3
- package/dist/collection/components/salla-products-list/salla-products-list.js +17 -11
- package/dist/components/index.js +2 -2
- package/dist/components/salla-add-product-button2.js +18 -6
- package/dist/components/salla-app-install-alert.js +7 -2
- package/dist/components/salla-color-picker2.js +38 -7
- package/dist/components/salla-maintenance-alert.js +4 -2
- package/dist/components/salla-product-options2.js +19 -2
- package/dist/components/salla-products-list2.js +17 -11
- package/dist/esm/bell-ring-D3mWkc-3.js +11 -0
- package/dist/esm/{interfaces-CBT_Nxny.js → camera-C6jIkM-X.js} +1 -12
- package/dist/esm/cancel-BsLF_HK7.js +11 -0
- package/dist/esm/cart-DY4LZmNP.js +11 -0
- package/dist/esm/{check-uTyAzPSy.js → check-BsXh13x8.js} +2 -2
- package/dist/esm/check-circle2-BV4kqbdL.js +11 -0
- package/dist/esm/{filepond-Dg4ZKM-u.js → filepond-DEzyRrdH.js} +1 -1
- package/dist/esm/{filepond-plugin-file-poster-BUIjdsnA.js → filepond-plugin-file-poster-DyLcCcHM.js} +1 -1
- package/dist/esm/{filepond-plugin-file-validate-size-DgZMMqmi.js → filepond-plugin-file-validate-size-Cxp5Yzea.js} +1 -1
- package/dist/esm/{filepond-plugin-file-validate-type-DA9tDSFr.js → filepond-plugin-file-validate-type-D2qNOQP4.js} +1 -1
- package/dist/esm/{filepond-plugin-image-edit-dotdnN4Z.js → filepond-plugin-image-edit-DndTlA7m.js} +1 -1
- package/dist/esm/{filepond-plugin-image-exif-orientation-oSI8aLU-.js → filepond-plugin-image-exif-orientation-CiT1CQoK.js} +1 -1
- package/dist/esm/{filepond-plugin-image-preview-DEefQK61.js → filepond-plugin-image-preview-DBG7keFZ.js} +1 -1
- package/dist/{cjs/gift-BPDUPIY_.js → esm/gift-BChI23pG.js} +2 -4
- package/dist/esm/image-C-tzSDxw.js +11 -0
- package/dist/esm/{index-D2-TtAhI.js → index-B74h9G6a.js} +137 -13
- package/dist/esm/{index-B7E8Tmgi.js → index-BLw7mdtM.js} +1 -1
- package/dist/esm/interfaces-OF8QcbMM.js +15 -0
- package/dist/esm/keyboard_arrow_down-DCZbpt2a.js +11 -0
- package/dist/esm/keyboard_arrow_right-Vqpj4CWE.js +18 -0
- package/dist/esm/loader.js +3 -3
- package/dist/esm/minus-DfeagqF1.js +18 -0
- package/dist/esm/salla-accordion-body_3.entry.js +771 -0
- package/dist/esm/salla-accordion_6.entry.js +761 -0
- package/dist/esm/salla-add-product-button_4.entry.js +2384 -0
- package/dist/esm/salla-advertisement.entry.js +1 -1
- package/dist/esm/salla-alert_2.entry.js +191 -0
- package/dist/esm/salla-app-install-alert.entry.js +8 -3
- package/dist/esm/salla-apps-icons.entry.js +1 -1
- package/dist/esm/salla-booking-field_7.entry.js +1539 -0
- package/dist/esm/{salla-cart-item-offers.entry.js → salla-cart-item-offers_2.entry.js} +104 -6
- package/dist/esm/salla-comment-form_8.entry.js +1652 -0
- package/dist/esm/salla-conditional-offer.entry.js +1 -1
- package/dist/esm/salla-contacts.entry.js +3 -3
- package/dist/esm/salla-count-down_2.entry.js +299 -0
- package/dist/esm/salla-custom-fields.entry.js +3 -2
- package/dist/esm/salla-filters-widget.entry.js +1 -1
- package/dist/esm/salla-filters.entry.js +1 -1
- package/dist/esm/salla-gifting.entry.js +486 -0
- package/dist/esm/salla-hook.entry.js +1 -1
- package/dist/esm/salla-infinite-scroll.entry.js +89 -0
- package/dist/esm/salla-installment.entry.js +1 -1
- package/dist/esm/salla-list-tile.entry.js +32 -0
- package/dist/esm/salla-localization-modal.entry.js +135 -0
- package/dist/esm/salla-login-modal.entry.js +318 -0
- package/dist/esm/salla-loyalty-prize-item.entry.js +1 -1
- package/dist/esm/salla-loyalty-program.entry.js +3 -3
- package/dist/esm/salla-loyalty.entry.js +209 -0
- package/dist/esm/salla-maintenance-alert.entry.js +38 -0
- package/dist/esm/salla-menu.entry.js +137 -0
- package/dist/esm/salla-metadata.entry.js +1 -1
- package/dist/esm/salla-multiple-bundle-product-cart_2.entry.js +217 -0
- package/dist/esm/salla-multiple-bundle-product-options-modal_2.entry.js +595 -0
- package/dist/esm/salla-multiple-bundle-product.entry.js +67 -0
- package/dist/esm/salla-notification-item.entry.js +1 -1
- package/dist/esm/salla-notifications.entry.js +1 -1
- package/dist/esm/salla-offer-modal.entry.js +204 -0
- package/dist/esm/salla-offer.entry.js +1 -1
- package/dist/esm/salla-order-details-multiple-bundle-product.entry.js +1 -1
- package/dist/esm/salla-order-details-options.entry.js +1 -1
- package/dist/esm/salla-order-details.entry.js +1 -1
- package/dist/esm/salla-order-summary.entry.js +3 -3
- package/dist/esm/salla-order-totals-card.entry.js +1 -1
- package/dist/esm/salla-orders.entry.js +1 -1
- package/dist/esm/salla-payments.entry.js +1 -1
- package/dist/esm/salla-placeholder.entry.js +49 -0
- package/dist/esm/salla-price-range.entry.js +1 -1
- package/dist/esm/salla-product-size-guide.entry.js +64 -0
- package/dist/esm/salla-products-list.entry.js +760 -0
- package/dist/esm/salla-products-slider.entry.js +113 -0
- package/dist/esm/salla-progress-bar.entry.js +71 -0
- package/dist/esm/salla-quick-order.entry.js +237 -0
- package/dist/esm/salla-rating-modal.entry.js +451 -0
- package/dist/esm/salla-scopes.entry.js +178 -0
- package/dist/esm/salla-search.entry.js +151 -0
- package/dist/esm/salla-skeleton.entry.js +34 -0
- package/dist/esm/salla-slider.entry.js +10387 -0
- package/dist/esm/salla-social-share.entry.js +163 -0
- package/dist/esm/salla-social.entry.js +2 -2
- package/dist/esm/salla-tab-content_3.entry.js +153 -0
- package/dist/esm/salla-tiered-offer.entry.js +1 -1
- package/dist/esm/salla-tooltip.entry.js +1 -1
- package/dist/esm/salla-trust-badges.entry.js +1 -1
- package/dist/esm/salla-user-menu.entry.js +273 -0
- package/dist/esm/salla-user-profile.entry.js +143 -0
- package/dist/esm/salla-user-settings.entry.js +86 -0
- package/dist/esm/salla-verify.entry.js +1 -1
- package/dist/esm/salla-wallet.entry.js +1 -1
- package/dist/esm/search-BscTeWDc.js +11 -0
- package/dist/esm/{special-discount-yRO-ZESF.js → special-discount-Ctkfc4K-.js} +1 -8
- package/dist/esm/star-ZT7ehBBk.js +11 -0
- package/dist/esm/star2-D4oPi1Ov.js +11 -0
- package/dist/esm/twilight.js +3 -3
- package/dist/esm/{vanilla-picker-DkUGzUrx.js → vanilla-picker-CtwkXTap.js} +1 -1
- package/dist/esm/{whatsapp2-DWksgowB.js → whatsapp2-CgR-T_ZS.js} +2 -2
- package/dist/twilight/{p-7b3ca138.entry.js → p-0134b4fd.entry.js} +1 -1
- package/dist/twilight/{p-0effc34b.entry.js → p-01daaaa6.entry.js} +1 -1
- package/dist/twilight/p-07da7390.entry.js +4 -0
- package/dist/twilight/{p-6ce3f119.entry.js → p-08badc32.entry.js} +1 -1
- package/dist/twilight/{p-4a594c06.entry.js → p-0aa5a12a.entry.js} +1 -1
- package/dist/twilight/{p-e73c28a8.entry.js → p-10dcd981.entry.js} +1 -1
- package/dist/twilight/{p-81df7a2e.entry.js → p-212a0710.entry.js} +1 -1
- package/dist/twilight/p-229275db.entry.js +4 -0
- package/dist/twilight/{p-fea62668.entry.js → p-232185ec.entry.js} +1 -1
- package/dist/twilight/{p-e5c01983.entry.js → p-2a927eac.entry.js} +1 -1
- package/dist/twilight/p-2d880232.entry.js +4 -0
- package/dist/twilight/p-2de9df64.entry.js +4 -0
- package/dist/twilight/p-309a0ba4.entry.js +4 -0
- package/dist/twilight/{p-6d886b96.entry.js → p-32732ca7.entry.js} +1 -1
- package/dist/twilight/{p-9d35196f.entry.js → p-32ca34ec.entry.js} +1 -1
- package/dist/twilight/{p-2aa0a4e2.entry.js → p-33093880.entry.js} +1 -1
- package/dist/twilight/p-3cffa4c9.entry.js +4 -0
- package/dist/twilight/p-3d0bb451.entry.js +4 -0
- package/dist/twilight/p-3e0d814c.entry.js +4 -0
- package/dist/twilight/p-4036d5dc.entry.js +4 -0
- package/dist/twilight/p-40fe4b01.entry.js +4 -0
- package/dist/twilight/p-47ac0ca5.entry.js +4 -0
- package/dist/twilight/{p-b9d0212c.entry.js → p-4cd9da44.entry.js} +1 -1
- package/dist/twilight/p-5c9281d3.entry.js +4 -0
- package/dist/twilight/p-5d044466.entry.js +4 -0
- package/dist/twilight/p-5d21334a.entry.js +4 -0
- package/dist/twilight/{p-e26cc25b.entry.js → p-654429df.entry.js} +1 -1
- package/dist/twilight/p-6b8453be.entry.js +4 -0
- package/dist/twilight/p-6be7bbb0.entry.js +4 -0
- package/dist/twilight/{p-67c327a8.entry.js → p-6c8f5c94.entry.js} +1 -1
- package/dist/twilight/{p-9b8f5399.entry.js → p-6fa02770.entry.js} +1 -1
- package/dist/twilight/p-7040ea33.entry.js +4 -0
- package/dist/twilight/p-8c189d76.entry.js +4 -0
- package/dist/twilight/p-8f1f052c.entry.js +4 -0
- package/dist/twilight/{p-6b0a03f6.entry.js → p-9051a540.entry.js} +1 -1
- package/dist/twilight/p-9f1c561f.entry.js +4 -0
- package/dist/twilight/{p-B0ba6Gec.js → p-B69XOH6h.js} +2 -2
- package/dist/twilight/{p-D2-TtAhI.js → p-B74h9G6a.js} +2 -2
- package/dist/twilight/{p-C0JNGIpa.js → p-BChI23pG.js} +1 -1
- package/dist/twilight/{p-DhcC83-2.js → p-BCxm-ISm.js} +1 -1
- package/dist/twilight/{p-Cmkcwiop.js → p-BPqZ249Z.js} +1 -1
- package/dist/twilight/p-BV4kqbdL.js +4 -0
- package/dist/twilight/p-BsLF_HK7.js +4 -0
- package/dist/twilight/{p-uTyAzPSy.js → p-BsXh13x8.js} +1 -1
- package/dist/twilight/p-BscTeWDc.js +4 -0
- package/dist/twilight/p-C-tzSDxw.js +4 -0
- package/dist/twilight/p-C6jIkM-X.js +4 -0
- package/dist/twilight/p-CO-PeZ27.js +4 -0
- package/dist/twilight/{p-DPqkW1aD.js → p-CQq81yb5.js} +2 -2
- package/dist/twilight/{p-DWksgowB.js → p-CgR-T_ZS.js} +1 -1
- package/dist/twilight/p-Ctkfc4K-.js +4 -0
- package/dist/twilight/p-D3mWkc-3.js +4 -0
- package/dist/twilight/p-D4oPi1Ov.js +4 -0
- package/dist/twilight/p-DCZbpt2a.js +4 -0
- package/dist/twilight/p-DY4LZmNP.js +4 -0
- package/dist/twilight/p-DfeagqF1.js +4 -0
- package/dist/twilight/{p-DJ557xys.js → p-DpsbV3x1.js} +1 -1
- package/dist/twilight/p-OF8QcbMM.js +4 -0
- package/dist/twilight/p-Vqpj4CWE.js +4 -0
- package/dist/twilight/p-ZT7ehBBk.js +4 -0
- package/dist/twilight/{p-q-O0srMP.js → p-_-aXm0Wb.js} +1 -1
- package/dist/twilight/p-a100bd38.entry.js +4 -0
- package/dist/twilight/{p-cf66ab4d.entry.js → p-a2756650.entry.js} +1 -1
- package/dist/twilight/{p-b1af2aca.entry.js → p-aab45a16.entry.js} +1 -1
- package/dist/twilight/p-aae761ff.entry.js +4 -0
- package/dist/twilight/p-afd61e47.entry.js +4 -0
- package/dist/twilight/p-b1fc6dfc.entry.js +4 -0
- package/dist/twilight/p-b206a0a1.entry.js +4 -0
- package/dist/twilight/p-b2332516.entry.js +4 -0
- package/dist/twilight/{p-6c2807c8.entry.js → p-b588ef46.entry.js} +1 -1
- package/dist/twilight/p-b71fc1b5.entry.js +4 -0
- package/dist/twilight/{p-_JhF_Kvb.js → p-bVqtOl1F.js} +1 -1
- package/dist/twilight/{p-a858523c.entry.js → p-bf65b263.entry.js} +1 -1
- package/dist/twilight/{p-d4faa0f6.entry.js → p-c56a47b0.entry.js} +1 -1
- package/dist/twilight/p-c73189e3.entry.js +4 -0
- package/dist/twilight/p-d1203242.entry.js +4 -0
- package/dist/twilight/p-d4edfed4.entry.js +4 -0
- package/dist/twilight/{p-f6ffc708.entry.js → p-d86ce978.entry.js} +1 -1
- package/dist/twilight/{p-8e7fa4a9.entry.js → p-d9e7973d.entry.js} +1 -1
- package/dist/twilight/p-e3e33238.entry.js +4 -0
- package/dist/twilight/p-e6bb7362.entry.js +4 -0
- package/dist/twilight/{p-30403778.entry.js → p-e7813c80.entry.js} +1 -1
- package/dist/twilight/p-ec2fd37e.entry.js +4 -0
- package/dist/twilight/{p-ed60a726.entry.js → p-ec77c523.entry.js} +1 -1
- package/dist/twilight/{p-b208650b.entry.js → p-efc18ce6.entry.js} +1 -1
- package/dist/twilight/p-f4039c40.entry.js +4 -0
- package/dist/twilight/p-f47b130c.entry.js +4 -0
- package/dist/twilight/{p-db526796.entry.js → p-f57ca123.entry.js} +1 -1
- package/dist/twilight/{p-933881d3.entry.js → p-f5ad6572.entry.js} +1 -1
- package/dist/twilight/p-fc65084f.entry.js +4 -0
- package/dist/twilight/p-fd81a311.entry.js +4 -0
- package/dist/twilight/{p-9fa5ffcf.entry.js → p-fe983809.entry.js} +1 -1
- package/dist/twilight/{p-Czq4p9Qp.js → p-i5J7XGS6.js} +1 -1
- package/dist/twilight/{p-C2bMx7q5.js → p-shFpk0H3.js} +1 -1
- package/dist/twilight/twilight.esm.js +1 -1
- package/dist/types/components/salla-add-product-button/salla-add-product-button.d.ts +5 -0
- package/dist/types/components/salla-app-install-alert/salla-app-install-alert.d.ts +3 -1
- package/dist/types/components/salla-color-picker/salla-color-picker.d.ts +10 -0
- package/dist/types/components/salla-product-options/salla-product-options.d.ts +8 -1
- package/dist/types/components/salla-products-list/salla-products-list.d.ts +2 -0
- package/dist/types/components.d.ts +4 -0
- package/package.json +5 -5
- package/dist/cjs/salla-accordion_62.cjs.entry.js +0 -22420
- package/dist/cjs/salla-review-card.cjs.entry.js +0 -183
- package/dist/cjs/salla-reviews-page.cjs.entry.js +0 -695
- package/dist/cjs/salla-reviews.cjs.entry.js +0 -106
- package/dist/esm/salla-accordion_62.entry.js +0 -22339
- package/dist/esm/salla-review-card.entry.js +0 -181
- package/dist/esm/salla-reviews-page.entry.js +0 -693
- package/dist/esm/salla-reviews.entry.js +0 -104
- package/dist/twilight/p-01bccbf2.entry.js +0 -4
- package/dist/twilight/p-22d83528.entry.js +0 -4
- package/dist/twilight/p-3a74b551.entry.js +0 -4
- package/dist/twilight/p-4e416704.entry.js +0 -4
- package/dist/twilight/p-5dbf4cec.entry.js +0 -4
- package/dist/twilight/p-CBT_Nxny.js +0 -4
- package/dist/twilight/p-Dz7o69vX.js +0 -4
- package/dist/twilight/p-a42d626d.entry.js +0 -4
- package/dist/twilight/p-yRO-ZESF.js +0 -4
- package/dist/cjs/{twitter-pOrUNjXi.js → facebook-DbXua6B9.js} +2 -2
- package/dist/esm/{twitter-Dz7o69vX.js → facebook-CO-PeZ27.js} +2 -2
|
@@ -0,0 +1,2389 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Crafted with ❤ by Salla
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
var index = require('./index-Ce40E8tZ.js');
|
|
7
|
+
var cart = require('./cart-s-x1Fshk.js');
|
|
8
|
+
var bellRing = require('./bell-ring-BfKPinNo.js');
|
|
9
|
+
var Helper = require('./Helper-DHRnzcFm.js');
|
|
10
|
+
var check = require('./check-x3w3-gpj.js');
|
|
11
|
+
var camera = require('./camera-DytepEoK.js');
|
|
12
|
+
require('./anime.es-BqW8JHZi.js');
|
|
13
|
+
|
|
14
|
+
var WalletIcon = `<!-- Generated by IcoMoon.io -->
|
|
15
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
|
16
|
+
<title>full-wallet</title>
|
|
17
|
+
<path d="M29 12h-26c-0.668-0.008-1.284-0.226-1.787-0.59l0.009 0.006c-0.744-0.552-1.222-1.428-1.222-2.416 0-1.657 1.343-3 2.999-3h6c0.552 0 1 0.448 1 1s-0.448 1-1 1v0h-6c-0.552 0-1 0.448-1 1 0 0.326 0.156 0.616 0.397 0.798l0.002 0.002c0.167 0.12 0.374 0.194 0.599 0.2l0.001 0h26c0.552 0 1 0.448 1 1s-0.448 1-1 1v0zM27 12c-0.552 0-1-0.448-1-1v0-3h-3c-0.552 0-1-0.448-1-1s0.448-1 1-1v0h4c0.552 0 1 0.448 1 1v0 4c0 0.552-0.448 1-1 1v0zM29 30h-26c-1.657 0-3-1.343-3-3v0-18c0-0.552 0.448-1 1-1s1 0.448 1 1v0 18c0 0.552 0.448 1 1 1v0h25v-5c0-0.552 0.448-1 1-1s1 0.448 1 1v0 6c0 0.552-0.448 1-1 1v0zM29 18c-0.552 0-1-0.448-1-1v0-6c0-0.552 0.448-1 1-1s1 0.448 1 1v0 6c0 0.552-0.448 1-1 1v0zM31 24h-7c-2.209 0-4-1.791-4-4s1.791-4 4-4v0h7c0.552 0 1 0.448 1 1v0 6c0 0.552-0.448 1-1 1v0zM24 18c-1.105 0-2 0.895-2 2s0.895 2 2 2v0h6v-4zM25 12c-0.001 0-0.001 0-0.002 0-0.389 0-0.726-0.222-0.891-0.546l-0.003-0.006-3.552-7.106-2.306 1.152c-0.13 0.066-0.284 0.105-0.447 0.105-0.552 0-1-0.448-1-1 0-0.39 0.223-0.727 0.548-0.892l0.006-0.003 3.2-1.6c0.13-0.067 0.284-0.106 0.447-0.106 0.39 0 0.727 0.223 0.892 0.548l0.003 0.006 4 8c0.067 0.13 0.106 0.285 0.106 0.448 0 0.552-0.448 1-1 1v0zM21 12c-0.001 0-0.001 0-0.002 0-0.389 0-0.726-0.222-0.891-0.546l-0.003-0.006-3.552-7.106-15.104 7.552c-0.13 0.066-0.284 0.105-0.447 0.105-0.552 0-1-0.448-1-1 0-0.39 0.223-0.727 0.548-0.892l0.006-0.003 16-8c0.13-0.067 0.284-0.106 0.447-0.106 0.39 0 0.727 0.223 0.892 0.548l0.003 0.006 4 8c0.067 0.13 0.106 0.285 0.106 0.448 0 0.552-0.448 1-1 1-0.001 0-0.001 0-0.002 0h0z"></path>
|
|
18
|
+
</svg>
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const sallaAddProductButtonCss = ":host{display:block}salla-add-product-button[width=wide]{width:100%}";
|
|
22
|
+
|
|
23
|
+
const SallaAddProductButton = class {
|
|
24
|
+
constructor(hostRef) {
|
|
25
|
+
index.registerInstance(this, hostRef);
|
|
26
|
+
this.success = index.createEvent(this, "success");
|
|
27
|
+
this.failed = index.createEvent(this, "failed");
|
|
28
|
+
this.hostAttributes = {};
|
|
29
|
+
/**
|
|
30
|
+
* Has Pre Order
|
|
31
|
+
*/
|
|
32
|
+
this.hasPreOrder = false;
|
|
33
|
+
/**
|
|
34
|
+
* Product Status.Defaults to `sale`
|
|
35
|
+
*/
|
|
36
|
+
this.productStatus = 'sale';
|
|
37
|
+
/**
|
|
38
|
+
* Product type. Defaults to `product`
|
|
39
|
+
*/
|
|
40
|
+
this.productType = 'product';
|
|
41
|
+
this.selectedOptions = [];
|
|
42
|
+
this.buyNowText = salla.lang.get('pages.products.buy_now');
|
|
43
|
+
/** Use matchMedia instead of window.innerWidth to avoid forced reflow (Lighthouse/PageSpeed) */
|
|
44
|
+
this.isDesktopViewport = typeof window !== 'undefined' ? window.matchMedia('(min-width: 768px)').matches : true;
|
|
45
|
+
this.viewportMediaQuery = null;
|
|
46
|
+
this.onViewportChange = (e) => {
|
|
47
|
+
this.isDesktopViewport = e.matches;
|
|
48
|
+
this.btn?.setText((e.matches && !!this.passedLabel) ? this.passedLabel : this.getLabel());
|
|
49
|
+
};
|
|
50
|
+
salla.lang.onLoaded(() => {
|
|
51
|
+
this.buyNowText = salla.lang.get('pages.products.buy_now');
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
getLabel() {
|
|
55
|
+
if (this.productStatus === 'sale' && this.supportStickyBar && !this.isDesktopViewport && this.showQuickBuy && this.isApplePayActive) {
|
|
56
|
+
return cart.PendingOrdersIcon;
|
|
57
|
+
}
|
|
58
|
+
if (this.hasPreOrder) {
|
|
59
|
+
return salla.lang.get('pages.products.pre_order_now');
|
|
60
|
+
}
|
|
61
|
+
if (this.productStatus === 'sale' && this.productType === 'booking') {
|
|
62
|
+
return salla.lang.get('pages.cart.book_now');
|
|
63
|
+
}
|
|
64
|
+
if (this.productStatus === 'sale') {
|
|
65
|
+
return salla.lang.get('pages.cart.add_to_cart');
|
|
66
|
+
}
|
|
67
|
+
if (this.productType !== 'donating') {
|
|
68
|
+
return salla.lang.get('pages.products.out_of_stock');
|
|
69
|
+
}
|
|
70
|
+
// donating
|
|
71
|
+
return salla.lang.get('pages.products.donation_exceed');
|
|
72
|
+
}
|
|
73
|
+
addProductToCart(event) {
|
|
74
|
+
if (this.productType === 'booking') {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
return this.addBookingProduct();
|
|
77
|
+
}
|
|
78
|
+
// we want to ignore the click action when the type of button is submit a form
|
|
79
|
+
if (this.hostAttributes.type === 'submit') {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
this.btn?.disable();
|
|
84
|
+
// Validate product options explicitly on Add to Cart click
|
|
85
|
+
const optionsEl = document.querySelector(`salla-product-options[product-id="${this.productId}"]`);
|
|
86
|
+
if (optionsEl && typeof optionsEl.validateAndScroll === 'function') {
|
|
87
|
+
return optionsEl.validateAndScroll()
|
|
88
|
+
.then(async (isValid) => {
|
|
89
|
+
// Call validate hook
|
|
90
|
+
const ctx = { isValid, component: this };
|
|
91
|
+
await salla.hooks.call('salla-add-product-button', 'validate', ctx);
|
|
92
|
+
if (!ctx.isValid) {
|
|
93
|
+
this.btn?.enable();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
return this._proceedAddToCart();
|
|
97
|
+
})
|
|
98
|
+
.catch(() => {
|
|
99
|
+
this.btn?.enable();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// Fallback if options component not found
|
|
103
|
+
return this._proceedAddToCart();
|
|
104
|
+
}
|
|
105
|
+
async _proceedAddToCart() {
|
|
106
|
+
/**
|
|
107
|
+
* by default the quick add is just an alias for add item function
|
|
108
|
+
* but its work only when the id is the only value is passed via the object
|
|
109
|
+
* so we will filter the object entities to remove null and zero values in case we don't want the normal add item action
|
|
110
|
+
*/
|
|
111
|
+
const data = Object.entries({
|
|
112
|
+
id: this.productId,
|
|
113
|
+
donation_amount: this.donatingAmount,
|
|
114
|
+
quantity: this.quantity,
|
|
115
|
+
endpoint: 'quickAdd'
|
|
116
|
+
}).reduce((a, [k, v]) => (v ? (a[k] = v, a) : a), {});
|
|
117
|
+
// Use the potentially modified data
|
|
118
|
+
return salla.cart.addItem(data)
|
|
119
|
+
.then(async (response) => {
|
|
120
|
+
this.selectedOptions = [];
|
|
121
|
+
this.btn?.enable();
|
|
122
|
+
this.success.emit(response);
|
|
123
|
+
return response;
|
|
124
|
+
})
|
|
125
|
+
.catch(error => { this.failed.emit(error); this.btn?.enable(); });
|
|
126
|
+
}
|
|
127
|
+
addBookingProduct() {
|
|
128
|
+
if (salla.config.isGuest()) {
|
|
129
|
+
salla.auth.api.setAfterLoginEvent('booking::add', this.productId);
|
|
130
|
+
salla.event.dispatch('login::open');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
return salla.booking.add(this.productId)
|
|
134
|
+
.then(resp => this.success.emit(resp))
|
|
135
|
+
.catch(error => this.failed.emit(error));
|
|
136
|
+
}
|
|
137
|
+
getBtnAttributes() {
|
|
138
|
+
for (let i = 0; i < this.host.attributes.length; i++) {
|
|
139
|
+
if (!['id', 'class'].includes(this.host.attributes[i].name)) {
|
|
140
|
+
this.hostAttributes[this.host.attributes[i].name] = this.host.attributes[i].value;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return this.hostAttributes;
|
|
144
|
+
}
|
|
145
|
+
getQuickBuyBtnAttributes() {
|
|
146
|
+
return {
|
|
147
|
+
...this.getBtnAttributes(),
|
|
148
|
+
type: this.supportStickyBar && !this.isDesktopViewport ? 'plain' : this.productType == 'donating' ? 'donate' : 'buy'
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
miniCheckoutWidget() {
|
|
152
|
+
let storeId = salla.config.get('store.id');
|
|
153
|
+
if (!storeId) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
return index.h("salla-mini-checkout-widget", { language: salla.lang.locale, "store-id": storeId, config: { user: salla.config.get('user'), failure_url: window.location.href }, products: [this.productId], api: salla.config.get('store.api'), outline: true, "form-selector": "form.product-form", class: "s-add-product-button-mini-checkout" }, index.h("div", { slot: "widget-label", class: "s-add-product-button-mini-checkout-content" }, index.h("span", { innerHTML: WalletIcon }), this.buyNowText));
|
|
157
|
+
}
|
|
158
|
+
componentWillLoad() {
|
|
159
|
+
return salla.onReady()
|
|
160
|
+
.then(() => {
|
|
161
|
+
// this to fix not added hydrated class to html element after components loaded
|
|
162
|
+
document.documentElement.classList.add('hydrated');
|
|
163
|
+
this.showQuickBuy = this.quickBuy
|
|
164
|
+
&& salla.config.get('store.settings.buy_now')
|
|
165
|
+
&& this.productStatus == 'sale'
|
|
166
|
+
&& this.productType !== 'booking';
|
|
167
|
+
this.isApplePayActive = window.ApplePaySession?.canMakePayments()
|
|
168
|
+
&& salla.config.get('store.settings.payments')?.includes('apple_pay')
|
|
169
|
+
&& salla.config.get('store.settings.is_salla_gateway', false);
|
|
170
|
+
this.passedLabel = this.host.innerHTML.replace('<!---->', '').trim();
|
|
171
|
+
if (!!this.passedLabel && this.isDesktopViewport) {
|
|
172
|
+
return this.btn?.setText(this.passedLabel);
|
|
173
|
+
}
|
|
174
|
+
if (this.host.getAttribute('type') === 'submit' && this.supportStickyBar) {
|
|
175
|
+
this.viewportMediaQuery = window.matchMedia('(min-width: 768px)');
|
|
176
|
+
this.viewportMediaQuery.addEventListener('change', this.onViewportChange);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
render() {
|
|
181
|
+
//TODO:: find a better fix, this is a patch for issue that duplicates the buttons more than twice @see the screenshot inside this folder
|
|
182
|
+
if (this.host.closest('.swiper-slide')?.classList.contains('swiper-slide-duplicate')) {
|
|
183
|
+
return '';
|
|
184
|
+
}
|
|
185
|
+
if (this.hasSubscribedOptions) {
|
|
186
|
+
return index.h(index.Host, null, index.h("salla-product-availability", { ...this.getBtnAttributes(), "is-subscribed": true }, index.h("span", { class: "s-hidden" }, index.h("slot", null))));
|
|
187
|
+
}
|
|
188
|
+
if ((this.productStatus === 'out-and-notify' && this.channels) || this.hasOutOfStockOption) {
|
|
189
|
+
return index.h(index.Host, null, index.h("salla-product-availability", { ...this.getBtnAttributes() }, index.h("span", { class: "s-hidden" }, index.h("slot", null))));
|
|
190
|
+
}
|
|
191
|
+
return index.h(index.Host, { class: {
|
|
192
|
+
's-add-product-button-with-quick-buy': this.showQuickBuy,
|
|
193
|
+
's-add-product-button-with-sticky-bar': this.supportStickyBar,
|
|
194
|
+
's-add-product-button-with-apple-pay': this.showQuickBuy && this.isApplePayActive
|
|
195
|
+
} }, index.h("div", { class: {
|
|
196
|
+
's-add-product-button-main': this.showQuickBuy,
|
|
197
|
+
'w-full': !document.getElementById('fast-checkout-js') || ['financial_support', 'donating'].includes(this.productType) // This is a temporary fix until all themes fully support the fast checkout -- To be removed later
|
|
198
|
+
} }, index.h("salla-button", { color: this.productStatus === 'sale' ? 'primary' : 'light', type: "button", fill: this.productStatus === 'sale' ? 'solid' : 'outline', ref: el => this.btn = el, onClick: event => this.addProductToCart(event), disabled: this.productStatus !== 'sale', ...this.getBtnAttributes(), "loader-position": "center" }, index.h("slot", null)), this.showQuickBuy && !!document.getElementById('fast-checkout-js') && !['financial_support', 'donating'].includes(this.productType) ? this.miniCheckoutWidget() : ''), this.showQuickBuy && this.isApplePayActive ? index.h("salla-quick-buy", { ...this.getQuickBuyBtnAttributes() }) : '');
|
|
199
|
+
}
|
|
200
|
+
async componentDidLoad() {
|
|
201
|
+
// Register this component for hooks system, already on it logic to run componentDidLoad trigger
|
|
202
|
+
await Salla.hooks.registerComponent('salla-add-product-button', this);
|
|
203
|
+
if (!this.notifyOptionsAvailability) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
salla.event.on('product-options::change', async (data) => {
|
|
207
|
+
if (!['thumbnail', 'color', 'single-option'].includes(data.option.type)) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
this.hasSubscribedOptions = false;
|
|
211
|
+
this.selectedOptions = await document.querySelector(`salla-product-options[product-id="${this.productId}"]`)?.getSelectedOptions();
|
|
212
|
+
this.hasOutOfStockOption = await document.querySelector(`salla-product-options[product-id="${this.productId}"]`)?.hasOutOfStockOption();
|
|
213
|
+
let subscribedDetails = salla.storage.get(`product-${this.productId}-subscribed-options`);
|
|
214
|
+
if (!subscribedDetails && !this.subscribedOptions || !this.hasOutOfStockOption) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (salla.config.isGuest()) {
|
|
218
|
+
const parsedSubscribedDetails = subscribedDetails ? subscribedDetails.map(ids => ids.split(',').map(id => parseInt(id))) : [];
|
|
219
|
+
this.hasSubscribedOptions = parsedSubscribedDetails.length > 0 && parsedSubscribedDetails.some(ids => ids.every(id => this.selectedOptions.some(option => option.id === id)));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
this.hasSubscribedOptions = this.subscribedOptions && this.subscribedOptions !== 'null' && this.subscribedOptions !== '[]' ? JSON.parse(this.subscribedOptions).some(ids => ids.every(id => this.selectedOptions.some(option => option.id === id))) : false;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
componentDidRender() {
|
|
227
|
+
//if label not passed, get label
|
|
228
|
+
if (!!this.passedLabel && (!this.supportStickyBar || this.isDesktopViewport)) {
|
|
229
|
+
// if passed label, set it
|
|
230
|
+
this.btn?.setText(this.passedLabel);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
this.btn?.setText(this.getLabel());
|
|
234
|
+
salla.lang.onLoaded(() => this.btn?.setText(this.getLabel()));
|
|
235
|
+
}
|
|
236
|
+
disconnectedCallback() {
|
|
237
|
+
this.viewportMediaQuery?.removeEventListener('change', this.onViewportChange);
|
|
238
|
+
}
|
|
239
|
+
get host() { return index.getElement(this); }
|
|
240
|
+
};
|
|
241
|
+
SallaAddProductButton.style = sallaAddProductButtonCss;
|
|
242
|
+
|
|
243
|
+
const sallaProductAvailabilityCss = "";
|
|
244
|
+
|
|
245
|
+
const SallaProductAvailability = class {
|
|
246
|
+
constructor(hostRef) {
|
|
247
|
+
index.registerInstance(this, hostRef);
|
|
248
|
+
this.isUser = salla.config.isUser();
|
|
249
|
+
this.translationLoaded = false;
|
|
250
|
+
/**
|
|
251
|
+
* Listen to product options availability.
|
|
252
|
+
*/
|
|
253
|
+
this.notifyOptionsAvailability = false;
|
|
254
|
+
/**
|
|
255
|
+
* is current user already subscribed
|
|
256
|
+
*/
|
|
257
|
+
this.isSubscribed = false;
|
|
258
|
+
this.handleSubmitOptions = async () => {
|
|
259
|
+
let payload = { id: this.productId };
|
|
260
|
+
if (!this.notifyOptionsAvailability) {
|
|
261
|
+
return payload;
|
|
262
|
+
}
|
|
263
|
+
let optionsElement = document.querySelector(`salla-product-options[product-id="${this.productId}"]`);
|
|
264
|
+
let options = Object.values(await optionsElement?.getSelectedOptionsData() || {});
|
|
265
|
+
//if all options not selected, show message && throw exception
|
|
266
|
+
if (options.length && !await optionsElement?.reportValidity()) {
|
|
267
|
+
let errorMessage = salla.lang.get('common.messages.required_fields');
|
|
268
|
+
salla.error(errorMessage);
|
|
269
|
+
throw errorMessage;
|
|
270
|
+
}
|
|
271
|
+
payload.options = [];
|
|
272
|
+
options.forEach(option => {
|
|
273
|
+
//inject numbers only, without zeros
|
|
274
|
+
if (option && !isNaN(option)) {
|
|
275
|
+
payload.options.push(Number(option));
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
return payload;
|
|
279
|
+
};
|
|
280
|
+
// helpers
|
|
281
|
+
this.typing = (e) => {
|
|
282
|
+
const error = e.target.nextElementSibling;
|
|
283
|
+
e.target.classList.remove('s-has-error');
|
|
284
|
+
error?.classList.contains('s-product-availability-error-msg') && (error.innerText = '');
|
|
285
|
+
e.keyCode === 13 && this.submit();
|
|
286
|
+
};
|
|
287
|
+
salla.lang.onLoaded(() => {
|
|
288
|
+
this.translationLoaded = true;
|
|
289
|
+
this.title_ = this.host.title || salla.lang.get('pages.products.notify_availability_title');
|
|
290
|
+
this.modal?.setTitle(this.title_);
|
|
291
|
+
});
|
|
292
|
+
if (!this.productId) {
|
|
293
|
+
this.productId = salla.config.get('page.id');
|
|
294
|
+
}
|
|
295
|
+
if (this.isUser)
|
|
296
|
+
return;
|
|
297
|
+
this.channelsWatcher(this.channels);
|
|
298
|
+
this.title_ = this.host.title || salla.lang.get('pages.products.notify_availability_title');
|
|
299
|
+
this.host.removeAttribute('title');
|
|
300
|
+
//todo:: fix this to cover options too
|
|
301
|
+
this.isVisitorSubscribed = !this.notifyOptionsAvailability ? salla.storage.get(`product-${this.productId}-subscribed`) : '';
|
|
302
|
+
}
|
|
303
|
+
channelsWatcher(newValue) {
|
|
304
|
+
this.channels_ = !!newValue ? newValue.split(',') : [];
|
|
305
|
+
}
|
|
306
|
+
openModel() {
|
|
307
|
+
this.handleSubmitOptions().then(isSuccess => isSuccess ? this.modal.open() : null);
|
|
308
|
+
}
|
|
309
|
+
async submit() {
|
|
310
|
+
let payload = await this.handleSubmitOptions();
|
|
311
|
+
if (this.isUser) {
|
|
312
|
+
return salla.api.product.availabilitySubscribe(payload)
|
|
313
|
+
.then(() => this.isSubscribed = true);
|
|
314
|
+
}
|
|
315
|
+
if (this.channels_.includes('sms')) {
|
|
316
|
+
let { phone, countryCode } = await this.mobileInput.getValues();
|
|
317
|
+
payload['country_code'] = countryCode;
|
|
318
|
+
payload['phone'] = phone;
|
|
319
|
+
}
|
|
320
|
+
if (this.channels_.includes('email')) {
|
|
321
|
+
this.email.value !== '' && (payload['email'] = this.email.value);
|
|
322
|
+
}
|
|
323
|
+
await this.validateform();
|
|
324
|
+
return this.btn.load()
|
|
325
|
+
.then(() => this.btn.disable())
|
|
326
|
+
.then(() => salla.api.product.availabilitySubscribe(payload))
|
|
327
|
+
.then(() => {
|
|
328
|
+
if (!this.notifyOptionsAvailability) {
|
|
329
|
+
salla.storage.set(`product-${this.productId}-subscribed`, true);
|
|
330
|
+
this.isSubscribed = true;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (payload.options.length) {
|
|
334
|
+
let options = salla.storage.get(`product-${this.productId}-subscribed-options`) || [];
|
|
335
|
+
let selectedOptionsString = payload.options.join(',');
|
|
336
|
+
if (!options.includes(selectedOptionsString)) {
|
|
337
|
+
options.push(selectedOptionsString);
|
|
338
|
+
salla.storage.set(`product-${this.productId}-subscribed-options`, options);
|
|
339
|
+
this.isSubscribed = true;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
salla.log('already subscribed to this options');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
.then(() => this.btn.stop())
|
|
347
|
+
.then(() => this.modal.close())
|
|
348
|
+
.catch(() => this.btn.stop() && this.btn.enable());
|
|
349
|
+
}
|
|
350
|
+
async validateform() {
|
|
351
|
+
try {
|
|
352
|
+
if (this.channels_.includes('email')) {
|
|
353
|
+
const isEmailValid = Helper.Helper.isValidEmail(this.email.value);
|
|
354
|
+
if (isEmailValid)
|
|
355
|
+
return;
|
|
356
|
+
!isEmailValid && this.validateField(this.email, salla.lang.get('common.elements.email_is_valid'));
|
|
357
|
+
}
|
|
358
|
+
if (this.channels_.includes('sms')) {
|
|
359
|
+
const isPhoneValid = await this.mobileInput.isValid();
|
|
360
|
+
if (isPhoneValid)
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
throw ('Please insert required fields');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
validateField(field, errorMsg) {
|
|
369
|
+
field.classList.add('s-has-error');
|
|
370
|
+
field.nextElementSibling['innerText'] = '* ' + errorMsg;
|
|
371
|
+
}
|
|
372
|
+
render() {
|
|
373
|
+
return (index.h(index.Host, { key: 'e5b3121a6e352338ff666ba419100150e838e0d2', class: "s-product-availability-wrap" }, this.isSubscribed || this.isVisitorSubscribed
|
|
374
|
+
? index.h("div", { class: "s-product-availability-subscribed" }, index.h("span", { innerHTML: bellRing.BellRing, class: "s-product-availability-subs-icon" }), salla.lang.get('pages.products.notify_availability_success'))
|
|
375
|
+
:
|
|
376
|
+
index.h("salla-button", { width: "wide", onClick: () => this.isUser ? this.submit() : this.openModel() }, salla.lang.get('pages.products.notify_availability')), this.isUser || this.isSubscribed || this.isVisitorSubscribed ? '' : this.renderModal()));
|
|
377
|
+
}
|
|
378
|
+
renderModal() {
|
|
379
|
+
return (index.h("salla-modal", { ref: modal => this.modal = modal, "modal-title": this.title_, subTitle: salla.lang.get('pages.products.notify_availability_subtitle'), width: "sm" }, index.h("span", { slot: 'icon', class: "s-product-availability-header-icon", innerHTML: bellRing.BellRing }), index.h("div", { class: "s-product-availability-body" }, this.channels_.includes('email') ? [
|
|
380
|
+
index.h("label", { class: "s-product-availability-label" }, salla.lang.get('common.elements.email')),
|
|
381
|
+
index.h("input", { class: "s-product-availability-input", onKeyDown: e => this.typing(e), placeholder: salla.lang.get('common.elements.email_placeholder') || 'your@email.com', ref: el => this.email = el, type: "email" }),
|
|
382
|
+
index.h("span", { class: "s-product-availability-error-msg" })
|
|
383
|
+
] : '', this.channels_.includes('sms') ? [
|
|
384
|
+
index.h("label", { class: "s-product-availability-label" }, salla.lang.get('common.elements.mobile')),
|
|
385
|
+
index.h("salla-tel-input", { ref: el => this.mobileInput = el, onKeyDown: e => this.typing(e) })
|
|
386
|
+
] : ''), index.h("div", { slot: "footer", class: "s-product-availability-footer" }, index.h("salla-button", { class: "modal-cancel-btn", width: "wide", color: "light", fill: "outline", onClick: () => this.modal.close() }, salla.lang.get('common.elements.cancel')), index.h("salla-button", { class: "submit-btn", "loader-position": 'center', width: "wide", ref: btn => this.btn = btn, onClick: () => this.submit() }, salla.lang.get('common.elements.submit')))));
|
|
387
|
+
}
|
|
388
|
+
get host() { return index.getElement(this); }
|
|
389
|
+
static get watchers() { return {
|
|
390
|
+
"channels": ["channelsWatcher"]
|
|
391
|
+
}; }
|
|
392
|
+
};
|
|
393
|
+
SallaProductAvailability.style = sallaProductAvailabilityCss;
|
|
394
|
+
|
|
395
|
+
var DisplayType;
|
|
396
|
+
(function (DisplayType) {
|
|
397
|
+
DisplayType["COLOR"] = "color";
|
|
398
|
+
DisplayType["DATE"] = "date";
|
|
399
|
+
DisplayType["DATETIME"] = "datetime";
|
|
400
|
+
DisplayType["DONATION"] = "donation";
|
|
401
|
+
DisplayType["IMAGE"] = "image";
|
|
402
|
+
DisplayType["MULTIPLE_OPTIONS"] = "multiple-options";
|
|
403
|
+
DisplayType["NUMBER"] = "number";
|
|
404
|
+
DisplayType["SINGLE_OPTION"] = "single-option";
|
|
405
|
+
DisplayType["DIGITAL_CARD_VALUE"] = "digital-code-value";
|
|
406
|
+
DisplayType["COUNTRY"] = "country";
|
|
407
|
+
DisplayType["SPLITTER"] = "splitter";
|
|
408
|
+
DisplayType["TEXT"] = "text";
|
|
409
|
+
DisplayType["TEXTAREA"] = "textarea";
|
|
410
|
+
DisplayType["THUMBNAIL"] = "thumbnail";
|
|
411
|
+
DisplayType["TIME"] = "time";
|
|
412
|
+
DisplayType["RADIO"] = "radio";
|
|
413
|
+
DisplayType["CHECKBOX"] = "checkbox";
|
|
414
|
+
DisplayType["MAP"] = "map";
|
|
415
|
+
DisplayType["FILE"] = "file";
|
|
416
|
+
DisplayType["COLOR_PICKER"] = "color_picker";
|
|
417
|
+
DisplayType["BOOKING"] = "booking";
|
|
418
|
+
})(DisplayType || (DisplayType = {}));
|
|
419
|
+
var Currency;
|
|
420
|
+
(function (Currency) {
|
|
421
|
+
Currency["Sar"] = "SAR";
|
|
422
|
+
})(Currency || (Currency = {}));
|
|
423
|
+
|
|
424
|
+
var FileIcon = `<!-- Generated by IcoMoon.io -->
|
|
425
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
|
426
|
+
<title>file-upload</title>
|
|
427
|
+
<path d="M21.333 24c0.341 0 0.683-0.131 0.943-0.391 0.521-0.521 0.521-1.364 0-1.885l-5.333-5.333c-0.123-0.123-0.271-0.22-0.433-0.288-0.327-0.135-0.693-0.135-1.019 0-0.163 0.068-0.311 0.165-0.433 0.288l-5.333 5.333c-0.521 0.521-0.521 1.364 0 1.885s1.364 0.521 1.885 0l3.057-3.057v10.115c0 0.736 0.597 1.333 1.333 1.333s1.333-0.597 1.333-1.333v-10.115l3.057 3.057c0.26 0.26 0.601 0.391 0.943 0.391zM28.943 9.724l-9.333-9.333c-0.249-0.251-0.589-0.391-0.943-0.391h-12c-2.205 0-4 1.795-4 4v24c0 2.205 1.795 4 4 4h4c0.736 0 1.333-0.597 1.333-1.333s-0.597-1.333-1.333-1.333h-4c-0.735 0-1.333-0.599-1.333-1.333v-24c0-0.735 0.599-1.333 1.333-1.333h11.448l8.552 8.552v16.781c0 0.735-0.599 1.333-1.333 1.333h-4c-0.736 0-1.333 0.597-1.333 1.333s0.597 1.333 1.333 1.333h4c2.205 0 4-1.795 4-4v-17.333c0-0.353-0.14-0.693-0.391-0.943z"></path>
|
|
428
|
+
</svg>
|
|
429
|
+
`;
|
|
430
|
+
|
|
431
|
+
const sallaProductOptionsCss = "";
|
|
432
|
+
|
|
433
|
+
const SallaProductOptions = class {
|
|
434
|
+
constructor(hostRef) {
|
|
435
|
+
index.registerInstance(this, hostRef);
|
|
436
|
+
this.changed = index.createEvent(this, "changed");
|
|
437
|
+
this.fileTypes = {
|
|
438
|
+
pdf: 'application/pdf',
|
|
439
|
+
png: 'image/png',
|
|
440
|
+
jpg: 'image/jpeg',
|
|
441
|
+
word: 'application/doc,application/ms-doc,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
442
|
+
exl: 'application/excel,application/vnd.ms-excel,application/x-excel,application/x-msexcel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
443
|
+
txt: 'text/plain',
|
|
444
|
+
};
|
|
445
|
+
this.outOfStockText = "";
|
|
446
|
+
this.donationAmount = salla.lang.get('pages.products.donation_amount');
|
|
447
|
+
this.selectDonationAmount = salla.lang.getWithDefault('pages.products.select_donation_amount', 'تحديد مبلغ التبرع');
|
|
448
|
+
this.selectAmount = salla.lang.getWithDefault('pages.products.select_amount', 'اختر المبلغ');
|
|
449
|
+
this.isCustomDonation = false;
|
|
450
|
+
this.selectedOptions = [];
|
|
451
|
+
this.canDisabled = false;
|
|
452
|
+
this.disableCardValue = true;
|
|
453
|
+
this.availableDigitalCardValues = [];
|
|
454
|
+
this.userInitiatedValidation = false;
|
|
455
|
+
this.isCartMode = false;
|
|
456
|
+
this.outSkus = [];
|
|
457
|
+
/**
|
|
458
|
+
* Avoid selection of previous default or selected card value option
|
|
459
|
+
* when switching between digital card country options for the 1st time
|
|
460
|
+
*/
|
|
461
|
+
this.ignoreDefaultCardValue = false;
|
|
462
|
+
/**
|
|
463
|
+
* The id of the product to which the options are going to be fetched for.
|
|
464
|
+
*/
|
|
465
|
+
this.productId = salla.config.get('page.id');
|
|
466
|
+
this.handleDonationOptions = (event, detail, type, _option) => {
|
|
467
|
+
// Donating-amount input only: onInput updates price and dispatches native change for form price watcher.
|
|
468
|
+
if (detail === 'custom' && type === 'input') {
|
|
469
|
+
const inputTarget = event.target;
|
|
470
|
+
salla.helpers.inputDigitsOnly(inputTarget);
|
|
471
|
+
salla.event.emit('product-options::donation-changed', {
|
|
472
|
+
id: this.productId,
|
|
473
|
+
price: inputTarget.value
|
|
474
|
+
});
|
|
475
|
+
inputTarget.dispatchEvent(new window.Event('change', { bubbles: true }));
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// Tab (radio) change: get value from the radio that fired.
|
|
479
|
+
const value = type === 'option' ? String(event.target?.value ?? '') : '';
|
|
480
|
+
if (type === 'option' && detail === 'custom') {
|
|
481
|
+
// "Custom" tab: stop propagation so form does not see change until user types in donating-amount input.
|
|
482
|
+
event.preventDefault();
|
|
483
|
+
event.stopPropagation();
|
|
484
|
+
event.stopImmediatePropagation();
|
|
485
|
+
}
|
|
486
|
+
this.isCustomDonation = value === 'custom';
|
|
487
|
+
if (this.donationInput) {
|
|
488
|
+
if (value === 'custom') {
|
|
489
|
+
this.donationInput.value = '';
|
|
490
|
+
this.donationInput.focus();
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
this.donationInput.value = value;
|
|
494
|
+
}
|
|
495
|
+
if (detail === 'custom') {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
salla.event.emit('product-options::donation-changed', {
|
|
499
|
+
id: this.productId,
|
|
500
|
+
price: value
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
this.hideLabel = (option) => {
|
|
505
|
+
if (option.type === DisplayType.DONATION && (option.donation && !option.donation.can_donate)) {
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
return false;
|
|
509
|
+
};
|
|
510
|
+
this.getExpireDonationMessage = (option) => {
|
|
511
|
+
if (!option.donation) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const completed = option.donation.target_amount <= option.donation.collected_amount;
|
|
515
|
+
return index.h("div", { class: { "s-product-options-donation-message": true, "s-product-options-donation-completed": completed, "s-product-options-donation-expired": !completed } }, index.h("p", null, option.donation.target_message), index.h("span", { innerHTML: completed ? salla.money(option.donation.target_amount) : '' }));
|
|
516
|
+
};
|
|
517
|
+
salla.lang.onLoaded(() => {
|
|
518
|
+
this.outOfStockText = salla.lang.get("pages.products.out_of_stock");
|
|
519
|
+
this.donationAmount = salla.lang.get('pages.products.donation_amount');
|
|
520
|
+
this.selectDonationAmount = salla.lang.getWithDefault('pages.products.select_donation_amount', 'تحديد مبلغ التبرع');
|
|
521
|
+
this.selectAmount = salla.lang.getWithDefault('pages.products.select_amount', 'اختر المبلغ');
|
|
522
|
+
});
|
|
523
|
+
if (this.options) {
|
|
524
|
+
try {
|
|
525
|
+
this.setOptionsData(Array.isArray(this.options) ? this.options : JSON.parse(this.options));
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
catch (e) {
|
|
529
|
+
salla.log('Bad json passed via options prop');
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (!Array.isArray(this.optionsData)) {
|
|
533
|
+
salla.log('Options is not an array[] ---> ', this.optionsData);
|
|
534
|
+
this.setOptionsData([]);
|
|
535
|
+
}
|
|
536
|
+
if (this.productId && !salla.url.is_page('cart')) {
|
|
537
|
+
salla.api.product.getDetails(this.productId, ['options']).then(resp => this.setOptionsData(resp.data.options));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Sets the options data for the product
|
|
542
|
+
* @param optionsData - Array of product options
|
|
543
|
+
*/
|
|
544
|
+
async setOptionsData(optionsData) {
|
|
545
|
+
this.optionsData = optionsData;
|
|
546
|
+
const that = this;
|
|
547
|
+
this.optionsData[0]?.details?.forEach(function (detail) {
|
|
548
|
+
Object.entries(detail.skus_availability || {})
|
|
549
|
+
.filter(sku => !sku[1])
|
|
550
|
+
.map(sku => that.outSkus.push(Number(sku[0])));
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Get the id's of the selected options.
|
|
555
|
+
* */
|
|
556
|
+
async getSelectedOptionsData() {
|
|
557
|
+
const selectedOptions = {};
|
|
558
|
+
const formData = this.host.getElementSallaData();
|
|
559
|
+
// Check if bundleContext is defined as a prop on the component before accessing it
|
|
560
|
+
const contextData = (typeof this.bundleContext !== 'undefined') ? this.bundleContext : null;
|
|
561
|
+
formData.forEach((value, key) => {
|
|
562
|
+
if (contextData) {
|
|
563
|
+
// Handle bundle naming convention: bundle[sectionId][index][options][optionId]
|
|
564
|
+
if (key.startsWith('bundle[') && key.includes('[options][')) {
|
|
565
|
+
const optionId = key.split('[options][')[1].replace(']', '');
|
|
566
|
+
selectedOptions[optionId] = value;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
// Handle standard naming convention: options[optionId]
|
|
571
|
+
if (key.startsWith('options[')) {
|
|
572
|
+
selectedOptions[key.replace('options[', '').replace(']', '')] = value;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
return selectedOptions;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Report options form validity.
|
|
580
|
+
* */
|
|
581
|
+
async reportValidity() {
|
|
582
|
+
const requiredElements = this.host.querySelectorAll('[required]');
|
|
583
|
+
let pass = true;
|
|
584
|
+
for (let i = 0; i < requiredElements.length; i++) {
|
|
585
|
+
//if there is only one invalid option, return false
|
|
586
|
+
if ('reportValidity' in requiredElements[i] && !requiredElements[i].reportValidity()) {
|
|
587
|
+
pass = false;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return pass;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Return true if there is any out of stock options are selected and vise versa.
|
|
594
|
+
* */
|
|
595
|
+
async hasOutOfStockOption() {
|
|
596
|
+
return this.selectedOptions.some(option => option.is_out) || (this.selectedSkus?.length && this.selectedSkus?.every(sku => this.outSkus.includes(sku)));
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Get selected options.
|
|
600
|
+
* */
|
|
601
|
+
async getSelectedOptions() {
|
|
602
|
+
return this.selectedOptions;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Get a specific option by its id.
|
|
606
|
+
* */
|
|
607
|
+
async getOption(option_id) {
|
|
608
|
+
return this.optionsData.find(option => option.id === option_id);
|
|
609
|
+
}
|
|
610
|
+
// @ts-ignore
|
|
611
|
+
invalidHandler(event, option) {
|
|
612
|
+
const closestProductOption = event.target.closest('.s-product-options-option');
|
|
613
|
+
if (!closestProductOption.classList.contains('s-product-options-option-error')) {
|
|
614
|
+
closestProductOption.classList.add('s-product-options-option-error');
|
|
615
|
+
}
|
|
616
|
+
if ((this.userInitiatedValidation || salla.helpers.isIOSDevice()) && !salla.url.is_page('cart')) {
|
|
617
|
+
const firstInvalidElement = this.host.querySelector('.s-product-options-option-error');
|
|
618
|
+
if (firstInvalidElement === closestProductOption) {
|
|
619
|
+
this.scrollToElement(closestProductOption);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
scrollToElement(element) {
|
|
624
|
+
if (!element)
|
|
625
|
+
return;
|
|
626
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
627
|
+
}
|
|
628
|
+
changedHandler(event, option, fireChangeEvent = true) {
|
|
629
|
+
const data = {
|
|
630
|
+
event: event,
|
|
631
|
+
option: option,
|
|
632
|
+
detail: null,
|
|
633
|
+
productId: this.productId
|
|
634
|
+
};
|
|
635
|
+
if (option.details) {
|
|
636
|
+
const detail = option.details.find((detail) => {
|
|
637
|
+
return Number(detail.id) === Number(event.target.value);
|
|
638
|
+
});
|
|
639
|
+
data.detail = detail;
|
|
640
|
+
}
|
|
641
|
+
if (option.type === 'country') {
|
|
642
|
+
this.handleCountryOptionChange(event, data.detail);
|
|
643
|
+
}
|
|
644
|
+
const optionElement = event.target.closest('.s-product-options-option');
|
|
645
|
+
if (event.target.value ||
|
|
646
|
+
((option.type === DisplayType.FILE || option.type === DisplayType.IMAGE) && event.type === 'added') ||
|
|
647
|
+
(option.type === DisplayType.MAP && event.type === 'selected' && (event.target.lat && event.target.lng))) {
|
|
648
|
+
setTimeout(() => {
|
|
649
|
+
optionElement.classList.remove('s-product-options-option-error');
|
|
650
|
+
}, 200);
|
|
651
|
+
}
|
|
652
|
+
if (option.type === DisplayType.DONATION) {
|
|
653
|
+
salla.event.emit('product-options::donation-changed', {
|
|
654
|
+
id: this.productId,
|
|
655
|
+
price: event.target.value
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
this.setSelectedSkus();
|
|
659
|
+
this.handleRequiredMultipleOptions(option);
|
|
660
|
+
const index = this.selectedOptions.findIndex(opt => opt.option_id === data.option.id);
|
|
661
|
+
if (data.option.type === DisplayType.MULTIPLE_OPTIONS) {
|
|
662
|
+
// Handle multiple selections
|
|
663
|
+
const detailIndex = this.selectedOptions.findIndex(opt => opt.option_id === data.option.id && opt?.id === data.detail?.id);
|
|
664
|
+
if (detailIndex > -1) {
|
|
665
|
+
// If the option is already selected, remove it (unselect)
|
|
666
|
+
this.selectedOptions.splice(detailIndex, 1);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
// If the option is not selected, add it to the selectedOptions array
|
|
670
|
+
this.selectedOptions.push({ ...data.detail, option_id: data.option.id });
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
// Handle single selection
|
|
675
|
+
if (!data.detail || Object.keys(data.detail).length === 0) {
|
|
676
|
+
// If there is no value for the single-select, remove it from the selectedOptions array
|
|
677
|
+
if (index > -1) {
|
|
678
|
+
this.selectedOptions.splice(index, 1);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
// If a value exists, update or add the selection
|
|
683
|
+
if (index > -1) {
|
|
684
|
+
// Replace the existing selection with the new one
|
|
685
|
+
this.selectedOptions[index] = { ...data.detail, option_id: data.option.id };
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
// If no selection exists for this input, add the new selection
|
|
689
|
+
this.selectedOptions.push({ ...data.detail, option_id: data.option.id });
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
// Update optionsData directly
|
|
694
|
+
this.optionsData = this.optionsData.map(opt => {
|
|
695
|
+
if (opt.id === data.option.id) {
|
|
696
|
+
return {
|
|
697
|
+
...opt,
|
|
698
|
+
details: opt.details.map(detail => ({
|
|
699
|
+
...detail,
|
|
700
|
+
is_selected: data.option.type === DisplayType.MULTIPLE_OPTIONS
|
|
701
|
+
? this.selectedOptions.some(selected => selected.id === detail.id)
|
|
702
|
+
: Number(detail.id) === Number(data.detail?.id),
|
|
703
|
+
value: data.detail?.value
|
|
704
|
+
}))
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return opt;
|
|
708
|
+
});
|
|
709
|
+
// Emit the event only if fireChangeEvent is true
|
|
710
|
+
if (fireChangeEvent) {
|
|
711
|
+
this.changed.emit(data);
|
|
712
|
+
salla.event.emit('product-options::change', data);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* loop throw all selected details, then get common sku, if it's only one, means we selected all of them;
|
|
717
|
+
*/
|
|
718
|
+
setSelectedSkus() {
|
|
719
|
+
this.selectedSkus = this.selectedOptions.map(detail => Object.keys(detail.skus_availability || {}))
|
|
720
|
+
.reduce((p, c) => p.filter(e => c.includes(e)), []) // Initialize accumulator as an empty array
|
|
721
|
+
.map(sku => Number(sku));
|
|
722
|
+
}
|
|
723
|
+
handleRequiredMultipleOptions(option) {
|
|
724
|
+
if (option.type !== DisplayType.MULTIPLE_OPTIONS || !option.required) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const optionContainer = this.host.querySelector(`[data-option-id="${option.id}"]`);
|
|
728
|
+
const hasChecked = optionContainer.querySelectorAll('input:checked').length;
|
|
729
|
+
optionContainer.querySelectorAll('input').forEach(input => input.toggleAttribute('required', !hasChecked));
|
|
730
|
+
}
|
|
731
|
+
getLatLng(value, type) {
|
|
732
|
+
return value
|
|
733
|
+
? value.split(',')[type === 'lat' ? 0 : 1]
|
|
734
|
+
: '';
|
|
735
|
+
}
|
|
736
|
+
getDisplayForType(option) {
|
|
737
|
+
if (this[`${option.type}Option`]) {
|
|
738
|
+
return this[`${option.type}Option`](option);
|
|
739
|
+
}
|
|
740
|
+
if (option.type === DisplayType.COLOR_PICKER) {
|
|
741
|
+
return this.colorPickerOption(option);
|
|
742
|
+
}
|
|
743
|
+
if (option.type === DisplayType.MULTIPLE_OPTIONS) {
|
|
744
|
+
return this.multipleOptions(option);
|
|
745
|
+
}
|
|
746
|
+
if (option.type === DisplayType.SINGLE_OPTION) {
|
|
747
|
+
return this.singleOption(option);
|
|
748
|
+
}
|
|
749
|
+
// Handle radio type as single option for bundle products
|
|
750
|
+
if (option.type === DisplayType.RADIO) {
|
|
751
|
+
return this.radioOption(option);
|
|
752
|
+
}
|
|
753
|
+
if (option.type === DisplayType.DIGITAL_CARD_VALUE) {
|
|
754
|
+
return this.digitalCardValuesOption(option);
|
|
755
|
+
}
|
|
756
|
+
if (option.type === DisplayType.COUNTRY) {
|
|
757
|
+
return this.countryOption(option);
|
|
758
|
+
}
|
|
759
|
+
if (option.type === DisplayType.BOOKING && salla.url.is_page("cart")) {
|
|
760
|
+
return index.h("salla-booking-field", { onInvalidInput: (e) => this.invalidHandler(e, option), option: option, productId: option.value });
|
|
761
|
+
}
|
|
762
|
+
salla.log(`Couldn't find options type(${option.type})😢`);
|
|
763
|
+
return '';
|
|
764
|
+
}
|
|
765
|
+
getOptionShownWhen(option) {
|
|
766
|
+
return option.visibility_condition
|
|
767
|
+
? { "data-show-when": `options[${option.visibility_condition.option}] ${option.visibility_condition.operator} ${option.visibility_condition.value}` }
|
|
768
|
+
: {};
|
|
769
|
+
}
|
|
770
|
+
getAvailableDigitalCardSKUs(detail) {
|
|
771
|
+
const digitalCardOption = this.optionsData.find(({ type }) => type === 'digital-code-value');
|
|
772
|
+
if (!digitalCardOption)
|
|
773
|
+
throw new Error('product-options:: No digital card options found');
|
|
774
|
+
const outofStockSKUs = Object.keys(detail.skus_availability).filter(key => detail.skus_availability[key] === false);
|
|
775
|
+
this.availableDigitalCardValues = digitalCardOption.details.filter((op) => {
|
|
776
|
+
return !Object.keys(op.skus_availability).filter(SKU_key => outofStockSKUs.includes(SKU_key)).length;
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
handleCountryOptionChange(event, detail) {
|
|
780
|
+
event.stopImmediatePropagation();
|
|
781
|
+
this.ignoreDefaultCardValue = true;
|
|
782
|
+
const currentCardValue = this.host.querySelector("input[data-code-value]:checked");
|
|
783
|
+
if (currentCardValue)
|
|
784
|
+
currentCardValue.checked = false;
|
|
785
|
+
const digitalCardOption = this.optionsData.find(({ type }) => type === 'digital-code-value');
|
|
786
|
+
if (!digitalCardOption)
|
|
787
|
+
throw new Error('product-options:: No digital card options found');
|
|
788
|
+
this.getAvailableDigitalCardSKUs(detail);
|
|
789
|
+
}
|
|
790
|
+
getSelectedDigitalCardOptions(option) {
|
|
791
|
+
const selectedOption = option.details.find(detail => detail.is_selected);
|
|
792
|
+
const defaultOption = option.details.find(detail => !!detail.is_default) || option.details[0]; /*option.details[0] only applys for counrty options*/
|
|
793
|
+
if (!['digital-code-value', 'country'].includes(option.type))
|
|
794
|
+
return;
|
|
795
|
+
return selectedOption || defaultOption;
|
|
796
|
+
}
|
|
797
|
+
//we need the cart Id for productOption Image
|
|
798
|
+
async componentWillLoad() {
|
|
799
|
+
if (salla.url.is_page("cart")) {
|
|
800
|
+
this.disableCardValue = false;
|
|
801
|
+
this.fillSelectedOptions();
|
|
802
|
+
}
|
|
803
|
+
if (this.config) {
|
|
804
|
+
try {
|
|
805
|
+
this.optionConfig = typeof this.config === 'string' ? JSON.parse(this.config) : this.config;
|
|
806
|
+
}
|
|
807
|
+
catch (error) {
|
|
808
|
+
console.error('Failed to parse JSON in config prop:', error);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const shouldSelectDefaultOption = this.optionsData.filter(({ type }) => ["country", "digital-card-value"].includes(type)).length > 0 && salla.url.is_page('cart');
|
|
812
|
+
if (shouldSelectDefaultOption) {
|
|
813
|
+
const countryOption = this.optionsData.find(option => option.type === 'country');
|
|
814
|
+
const defaultSelection = countryOption && this.getSelectedDigitalCardOptions(countryOption);
|
|
815
|
+
if (defaultSelection) {
|
|
816
|
+
this.getAvailableDigitalCardSKUs(defaultSelection);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
this.outOfStockText = salla.lang.get('pages.products.out_of_stock');
|
|
820
|
+
await salla.onReady();
|
|
821
|
+
this.canDisabled = !salla.config.get('store.settings.product.notify_options_availability') || salla.url.is_page('cart');
|
|
822
|
+
this.pasteHandler = this.handlePaste.bind(this);
|
|
823
|
+
document.addEventListener("paste", this.pasteHandler);
|
|
824
|
+
const needsCartId = (!salla.storage.get('cart.id') && this.optionsData.some(option => ['file', 'image'].includes(option.type)));
|
|
825
|
+
return needsCartId ? salla.api.cart.getCurrentCartId(false, "salla-product-options") : null;
|
|
826
|
+
}
|
|
827
|
+
disconnectedCallback() {
|
|
828
|
+
if (this.pasteHandler) {
|
|
829
|
+
document.removeEventListener("paste", this.pasteHandler);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* This is a workaround for a bug in iOS 26 Safari, when pasting English text to RTL inputs, it adds extra text!!
|
|
834
|
+
* To avoid any break changes, we will make it only work on these conditions:
|
|
835
|
+
* - content_copyright is on
|
|
836
|
+
* - Apple Pay is enabled (means it's iOS/safari)
|
|
837
|
+
* - Input is an input or textarea
|
|
838
|
+
* - Salla form control
|
|
839
|
+
* - Options array
|
|
840
|
+
*/
|
|
841
|
+
handlePaste(event) {
|
|
842
|
+
const target = event.target;
|
|
843
|
+
if (!Salla.helpers.isAppleDevice()
|
|
844
|
+
|| !(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)
|
|
845
|
+
|| !target.classList.contains('s-form-control')
|
|
846
|
+
|| !this.host.contains(target)) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
// Prevent default paste (to avoid Safari inserting extra content)
|
|
850
|
+
event.preventDefault();
|
|
851
|
+
// Read only the clipboard data
|
|
852
|
+
const text = event.clipboardData?.getData("text") || "";
|
|
853
|
+
// Insert it manually at cursor if you want
|
|
854
|
+
const start = target.selectionStart;
|
|
855
|
+
const end = target.selectionEnd;
|
|
856
|
+
const newValue = target.value.slice(0, start) + text + target.value.slice(end);
|
|
857
|
+
target.value = newValue;
|
|
858
|
+
// Reset cursor position
|
|
859
|
+
target.setSelectionRange(start + text.length, start + text.length);
|
|
860
|
+
try {
|
|
861
|
+
target.dispatchEvent(new window.Event('change', { bubbles: true }));
|
|
862
|
+
}
|
|
863
|
+
catch (error) {
|
|
864
|
+
Salla.log('Error on change');
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
hideDigitalCardsOptions(option) {
|
|
868
|
+
return (this.disableCardValue && option.type === DisplayType.DIGITAL_CARD_VALUE && !salla.url.is_page("cart"));
|
|
869
|
+
}
|
|
870
|
+
render() {
|
|
871
|
+
if (this.optionsData?.length === 0) {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
return (index.h(index.Host, { class: "s-product-options-wrapper" }, index.h("salla-conditional-fields", null, this.optionsData.map((option) => index.h("div", { key: option.id, class: `s-product-options-option-container${option.visibility_condition || this.hideDigitalCardsOptions(option) ? ' hidden' : ''}`, "data-option-id": option.id, ...this.getOptionShownWhen(option) }, option.name === 'splitter' ?
|
|
875
|
+
this.splitterOption()
|
|
876
|
+
: index.h("div", { class: { "s-product-options-option": true, "s-product-options-option-booking": option.type === DisplayType.BOOKING && salla.url.is_page("cart") }, "data-option-type": option.type, "data-option-required": `${option.required}` }, index.h("label", { htmlFor: this.generateInputId(option.id), class: `s-product-options-option-label ${this.hideLabel(option) ? 's-product-options-option-label-hidden' : ''}` }, index.h("b", null, option.name, option.required && index.h("span", null, " * "), " "), index.h("small", null, option.placeholder)), index.h("div", { class: `s-product-options-option-content ${this.hideLabel(option) || (option.type === DisplayType.BOOKING && salla.url.is_page("cart")) ? 's-product-options-option-content-full-width' : ''}` }, this.getDisplayForType(option))))))));
|
|
877
|
+
}
|
|
878
|
+
generateUniqueKey(defaultValue) {
|
|
879
|
+
const contextData = this.bundleContext;
|
|
880
|
+
let baseKey = this.uniqueKey ? `${defaultValue}-${this.uniqueKey}` : defaultValue;
|
|
881
|
+
if (contextData) {
|
|
882
|
+
try {
|
|
883
|
+
// Handle both string and object types
|
|
884
|
+
const context = typeof contextData === 'string'
|
|
885
|
+
? JSON.parse(contextData)
|
|
886
|
+
: contextData;
|
|
887
|
+
const { sectionId, productId } = context;
|
|
888
|
+
baseKey = `${baseKey}-bundle-${sectionId}-${productId}`;
|
|
889
|
+
}
|
|
890
|
+
catch (e) {
|
|
891
|
+
// If parsing fails, just use the base key
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return baseKey;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Generate a valid HTML id for option inputs.
|
|
898
|
+
* @param optionId - The option ID
|
|
899
|
+
* @returns The formatted id string
|
|
900
|
+
*/
|
|
901
|
+
generateInputId(optionId) {
|
|
902
|
+
return this.generateUniqueKey(`option-${optionId}`);
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Generate the correct input name based on bundle context
|
|
906
|
+
* @param optionId - The option ID
|
|
907
|
+
* @returns The formatted input name
|
|
908
|
+
*/
|
|
909
|
+
generateInputName(optionId) {
|
|
910
|
+
const contextData = this.bundleContext;
|
|
911
|
+
const baseOptionId = optionId.toString();
|
|
912
|
+
if (contextData) {
|
|
913
|
+
try {
|
|
914
|
+
// Handle both string and object types
|
|
915
|
+
const context = typeof contextData === 'string'
|
|
916
|
+
? JSON.parse(contextData)
|
|
917
|
+
: contextData;
|
|
918
|
+
const { sectionId, productId } = context;
|
|
919
|
+
return `bundle[${sectionId}][${productId}][options][${baseOptionId}]`;
|
|
920
|
+
}
|
|
921
|
+
catch (e) {
|
|
922
|
+
return `options[${baseOptionId}]`;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return `options[${baseOptionId}]`;
|
|
926
|
+
}
|
|
927
|
+
fillSelectedOptions() {
|
|
928
|
+
this.selectedOptions = this.optionsData.reduce((acc, opt) => {
|
|
929
|
+
const selectedDetails = opt.details.filter(detail => detail.is_selected);
|
|
930
|
+
const mappedDetails = selectedDetails.map(detail => ({
|
|
931
|
+
...detail,
|
|
932
|
+
option_id: opt.id
|
|
933
|
+
}));
|
|
934
|
+
return acc.concat(mappedDetails);
|
|
935
|
+
}, []);
|
|
936
|
+
}
|
|
937
|
+
async componentDidLoad() {
|
|
938
|
+
if (!this.optionsData?.length) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
// Register for hooks — theme can call enterCartMode() here
|
|
942
|
+
await Salla.hooks.registerComponent('salla-product-options', this);
|
|
943
|
+
// Handle donation options
|
|
944
|
+
const selectedDonationOption = this.optionsData.find(option => option.type === DisplayType.DONATION)?.details?.find(detail => detail.is_selected);
|
|
945
|
+
if (selectedDonationOption) {
|
|
946
|
+
setTimeout(() => {
|
|
947
|
+
salla.event.emit('product-options::donation-changed', {
|
|
948
|
+
id: this.productId,
|
|
949
|
+
price: selectedDonationOption.additional_price
|
|
950
|
+
});
|
|
951
|
+
}, 1000);
|
|
952
|
+
}
|
|
953
|
+
// Handle pre-selected options on product page
|
|
954
|
+
// Skip if in cart mode (enterCartMode called by hook) or on cart page
|
|
955
|
+
// Call salla.product.getPrice() directly to avoid triggering form validation which causes unwanted scroll
|
|
956
|
+
if (!salla.url.is_page("cart") && !this.isCartMode) {
|
|
957
|
+
const pricingOptionTypes = [
|
|
958
|
+
DisplayType.SINGLE_OPTION,
|
|
959
|
+
DisplayType.MULTIPLE_OPTIONS,
|
|
960
|
+
DisplayType.COLOR,
|
|
961
|
+
DisplayType.THUMBNAIL,
|
|
962
|
+
DisplayType.DONATION,
|
|
963
|
+
DisplayType.DIGITAL_CARD_VALUE,
|
|
964
|
+
DisplayType.COUNTRY
|
|
965
|
+
];
|
|
966
|
+
const hasPreSelectedPricingOption = this.optionsData.some(option => pricingOptionTypes.includes(option.type) &&
|
|
967
|
+
option.details?.some(detail => detail.is_selected));
|
|
968
|
+
if (hasPreSelectedPricingOption) {
|
|
969
|
+
// Use requestAnimationFrame to ensure DOM is fully painted before accessing form
|
|
970
|
+
requestAnimationFrame(() => {
|
|
971
|
+
try {
|
|
972
|
+
const form = this.host.closest('form');
|
|
973
|
+
if (!form)
|
|
974
|
+
return;
|
|
975
|
+
// Validate form belongs to this product to avoid conflicts on complex pages
|
|
976
|
+
const formData = new FormData(form);
|
|
977
|
+
const formProductId = formData.get('id') || formData.get('product_id');
|
|
978
|
+
if (formProductId && String(formProductId) !== String(this.productId))
|
|
979
|
+
return;
|
|
980
|
+
salla.product.getPrice(formData);
|
|
981
|
+
}
|
|
982
|
+
catch (e) {
|
|
983
|
+
salla.log('Error updating price for pre-selected options:', e);
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Switch the component to cart-item mode: fill selected options,
|
|
991
|
+
* enable disabled-out-of-stock, and skip product price updates.
|
|
992
|
+
* Used by themes when treating a product page as a cart item editor.
|
|
993
|
+
*/
|
|
994
|
+
async enterCartMode() {
|
|
995
|
+
this.isCartMode = true;
|
|
996
|
+
this.disableCardValue = false;
|
|
997
|
+
this.canDisabled = true;
|
|
998
|
+
this.fillSelectedOptions();
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Enable user-initiated validation mode so invalid fields will scroll into view
|
|
1002
|
+
*/
|
|
1003
|
+
async enableUserInitiatedValidation() {
|
|
1004
|
+
this.userInitiatedValidation = true;
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Validate options and trigger scrolling to the first invalid option if any
|
|
1008
|
+
*/
|
|
1009
|
+
async validateAndScroll() {
|
|
1010
|
+
await this.enableUserInitiatedValidation();
|
|
1011
|
+
return this.reportValidity();
|
|
1012
|
+
}
|
|
1013
|
+
//@ts-ignore
|
|
1014
|
+
donationOption(option, product) {
|
|
1015
|
+
return index.h("div", { class: "s-product-options-donation-wrapper" }, option.donation?.can_donate ? [
|
|
1016
|
+
option.donation ?
|
|
1017
|
+
index.h("div", { key: option.id, class: "s-product-options-donation-progress" }, index.h("salla-progress-bar", { donation: option.donation }))
|
|
1018
|
+
: '',
|
|
1019
|
+
option.details.length ?
|
|
1020
|
+
[index.h("h4", { key: option.id }, this.selectAmount), index.h("div", { key: option.id, class: "s-product-options-donation-options" }, option.details.map((detail, i) => index.h("div", { key: option.id, class: "s-product-options-donation-options-item" }, index.h("input", { id: this.generateUniqueKey(`donation-option-${i}`), type: "radio", name: "donating_option", checked: detail.is_selected, value: detail.additional_price, onChange: e => this.handleDonationOptions(e, detail, 'option', option) }), index.h("label", { htmlFor: this.generateUniqueKey(`donation-option-${i}`) }, index.h("span", { innerHTML: salla.money(detail.name) })))), option.donation?.custom_amount_enabled ?
|
|
1021
|
+
index.h("div", { class: "s-product-options-donation-options-item" }, index.h("input", { id: this.generateUniqueKey("donation-option-custom"), type: "radio", name: "donating_option", value: "custom", onChange: e => this.handleDonationOptions(e, 'custom', 'option', option) }), index.h("label", { htmlFor: this.generateUniqueKey("donation-option-custom") }, index.h("span", null, " ", this.selectDonationAmount, " ")))
|
|
1022
|
+
: '')] : '',
|
|
1023
|
+
index.h("div", { key: option.id, class: { "s-product-options-donation-input-group": true, "shown": !option.details.length || (option.details.length && this.isCustomDonation) } }, index.h("input", { type: "text", id: "donating-amount", name: "donation_amount", class: "s-form-control", ref: el => { this.donationInput = el; }, value: option.details.length
|
|
1024
|
+
&& option.details.some(detail => detail.is_selected)
|
|
1025
|
+
? option.details.find(detail => detail.is_selected).additional_price
|
|
1026
|
+
: option.value,
|
|
1027
|
+
// required
|
|
1028
|
+
placeholder: option.placeholder, onInput: e => this.handleDonationOptions(e, 'custom', 'input', option), onBlur: e => this.changedHandler(e, option), onInvalid: (e) => this.invalidHandler(e, option) }), index.h("span", { class: "s-product-options-donation-amount-currency" }, salla.config.currency(salla.config.get('user.currency_code')).symbol))
|
|
1029
|
+
] :
|
|
1030
|
+
this.getExpireDonationMessage(option));
|
|
1031
|
+
}
|
|
1032
|
+
fileUploader(option, additions = null) {
|
|
1033
|
+
return index.h("salla-file-upload", { ...(additions || {}), "payload-name": "file", value: option.value, "instant-upload": true, name: this.generateInputName(option.id), required: !option.visibility_condition && option.required, height: "120px", onAdded: (e) => this.changedHandler(e, option), url: salla.cart.api.getUploadImageEndpoint(), "form-data": { cart_item_id: this.productId, product_id: this.productId }, onInvalidInput: (e) => this.invalidHandler(e, option), class: { "s-product-options-image-input": true, required: option.required } }, index.h("div", { class: "s-product-options-filepond-placeholder" }, index.h("span", { class: "s-product-options-filepond-placeholder-icon", innerHTML: additions.accept?.split(',').every(type => type.includes('image'))
|
|
1034
|
+
? camera.CameraIcon
|
|
1035
|
+
: FileIcon }), index.h("p", { class: "s-product-options-filepond-placeholder-text" }, salla.lang.get('common.uploader.drag_and_drop')), index.h("span", { class: "filepond--label-action" }, salla.lang.get('common.uploader.browse'))));
|
|
1036
|
+
}
|
|
1037
|
+
//@ts-ignore
|
|
1038
|
+
imageOption(option) {
|
|
1039
|
+
return this.fileUploader(option, { accept: 'image/png,image/jpeg,image/jpg,image/gif' });
|
|
1040
|
+
}
|
|
1041
|
+
//@ts-ignore
|
|
1042
|
+
fileOption(option) {
|
|
1043
|
+
const types = option.details.map(detail => this.fileTypes[detail.name]).filter(Boolean);
|
|
1044
|
+
return types?.length
|
|
1045
|
+
? this.fileUploader(option, { accept: types.join(',') })
|
|
1046
|
+
: 'File types not selected.';
|
|
1047
|
+
}
|
|
1048
|
+
// TODO: (ONLY FOR TESTING!) find a better way to make it testable, e.g. wrap it with a unique class like textOption
|
|
1049
|
+
//@ts-ignore
|
|
1050
|
+
numberOption(option) {
|
|
1051
|
+
return index.h("input", { type: "text", value: option.value, class: "s-form-control", required: !option.visibility_condition && option.required, id: this.generateInputId(option.id), name: this.generateInputName(option.id), placeholder: option.placeholder, onBlur: e => this.changedHandler(e, option), onInvalid: (e) => this.invalidHandler(e, option), onInput: e => salla.helpers.inputDigitsOnly(e.target) });
|
|
1052
|
+
}
|
|
1053
|
+
//@ts-ignore
|
|
1054
|
+
splitterOption() {
|
|
1055
|
+
return index.h("div", { class: "s-product-options-splitter" });
|
|
1056
|
+
}
|
|
1057
|
+
//@ts-ignore
|
|
1058
|
+
textOption(option) {
|
|
1059
|
+
return index.h("div", { class: "s-product-options-text" }, index.h("input", { type: "text", value: option.value, maxLength: option?.length, class: 's-form-control', required: !option.visibility_condition && option.required, id: this.generateInputId(option.id), name: this.generateInputName(option.id), placeholder: option.placeholder, onInvalid: (e) => this.invalidHandler(e, option), onChange: e => this.changedHandler(e, option) }));
|
|
1060
|
+
}
|
|
1061
|
+
//@ts-ignore
|
|
1062
|
+
textareaOption(option) {
|
|
1063
|
+
//todo::remove mt-1 class, and if it's okay to remove the tag itself will be great
|
|
1064
|
+
return index.h("div", { class: "s-product-options-textarea" }, index.h("div", { class: "mt-1" }, index.h("textarea", { rows: 4, value: option.value, maxLength: option?.length, class: "s-form-control", required: !option.visibility_condition && option.required, id: this.generateInputId(option.id), name: this.generateInputName(option.id), placeholder: option.placeholder, onInvalid: (e) => this.invalidHandler(e, option), onChange: (e) => this.changedHandler(e, option) })));
|
|
1065
|
+
}
|
|
1066
|
+
//@ts-ignore
|
|
1067
|
+
mapOption(option) {
|
|
1068
|
+
return index.h("salla-map", { zoom: 15, lat: this.getLatLng(option.value, 'lat'), lng: this.getLatLng(option.value, 'lng'), name: this.generateInputName(option.id), searchable: true, required: option.required, onInvalidInput: (e) => this.invalidHandler(e, option), onSelected: e => this.changedHandler(e, option) });
|
|
1069
|
+
}
|
|
1070
|
+
colorPickerOption(option) {
|
|
1071
|
+
return index.h("salla-color-picker", { onSubmitted: e => this.changedHandler(e, option), name: this.generateInputName(option.id), required: !option.visibility_condition && option.required, onInvalidInput: (e) => this.invalidHandler(e, option), color: option.value });
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* ============= Date Time options =============
|
|
1075
|
+
*/
|
|
1076
|
+
//@ts-ignore
|
|
1077
|
+
timeOption(option) {
|
|
1078
|
+
return index.h("salla-datetime-picker", { noCalendar: true, enableTime: true, dateFormat: "h:i K", value: option.value, placeholder: option.name, required: !option.visibility_condition && option.required, name: this.generateInputName(option.id), class: "s-product-options-time-element", onInvalidInput: (e) => this.invalidHandler(e, option), onPicked: e => this.changedHandler(e, option) });
|
|
1079
|
+
}
|
|
1080
|
+
//@ts-ignore
|
|
1081
|
+
dateOption(option) {
|
|
1082
|
+
//todo:: consider date-range @see https://github.com/SallaApp/theme-raed/blob/master/src/assets/js/partials/product-options.js#L8-L23
|
|
1083
|
+
return index.h("div", { class: "s-product-options-date-element" }, index.h("salla-datetime-picker", { value: option.value, placeholder: option.name, required: !option.visibility_condition && option.required, minDate: new Date(), name: this.generateInputName(option.id), onInvalidInput: (e) => this.invalidHandler(e, option), onPicked: e => this.changedHandler(e, option) }));
|
|
1084
|
+
}
|
|
1085
|
+
//@ts-ignore
|
|
1086
|
+
datetimeOption(option) {
|
|
1087
|
+
//todo:: consider date-range @see https://github.com/SallaApp/theme-raed/blob/master/src/assets/js/partials/product-options.js#L8-L23
|
|
1088
|
+
return index.h("div", { class: "s-product-options-datetime-element" }, index.h("salla-datetime-picker", { enableTime: true, value: option.value, dateFormat: "Y-m-d G:i:K", placeholder: option.name, required: !option.visibility_condition && option.required, name: this.generateInputName(option.id), maxDate: option.to_date_time, minDate: option.from_date_time, onInvalidInput: (e) => this.invalidHandler(e, option), onPicked: e => this.changedHandler(e, option) }));
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* ============= Advanced options =============
|
|
1092
|
+
*/
|
|
1093
|
+
getOptionDetailName(detail, outOfStock = true, optionType) {
|
|
1094
|
+
let detailName;
|
|
1095
|
+
if (optionType && optionType === DisplayType.COLOR) {
|
|
1096
|
+
detailName = detail.name
|
|
1097
|
+
+ ((outOfStock && this.isOptionDetailOut(detail) && !salla.url.is_page("cart")) && !this.hideOutLabel ? ` <br/> <p> ${this.outOfStockText} </p>` : '')
|
|
1098
|
+
+ (detail.additional_price ? ` <p> (${salla.money(detail.additional_price, false)}) </p>` : '');
|
|
1099
|
+
}
|
|
1100
|
+
if (!detailName) {
|
|
1101
|
+
detailName = detail.name
|
|
1102
|
+
+ ((outOfStock && this.isOptionDetailOut(detail) && !salla.url.is_page("cart")) && !this.hideOutLabel ? ` - ${this.outOfStockText}` : '')
|
|
1103
|
+
+ (detail.additional_price ? ` (${salla.money(detail.additional_price, false)})` : '');
|
|
1104
|
+
}
|
|
1105
|
+
//Some merchants adding price to the names of the options,
|
|
1106
|
+
//and because we are using this inside select option, we need to replace the html currency symbol with the store currency symbol
|
|
1107
|
+
return detailName.replace('<i class=sicon-sar></i>', salla.config.currency()?.symbol || 'ر.س');
|
|
1108
|
+
}
|
|
1109
|
+
isOptionDetailOut(detail) {
|
|
1110
|
+
if (detail.is_out || !detail.skus_availability || !this.selectedSkus?.length) {
|
|
1111
|
+
return detail.is_out;
|
|
1112
|
+
}
|
|
1113
|
+
const isDetailSelected = this.selectedOptions.filter(option => option.id === detail.id).length;
|
|
1114
|
+
//if the current options is the only selected option, so we are sure that it's not out, because there is no other options selected yet
|
|
1115
|
+
if (isDetailSelected && this.selectedOptions.length === 1) {
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
//if current details has sku in the possible outSkus it's out for sure
|
|
1119
|
+
if (isDetailSelected) {
|
|
1120
|
+
//here we will get the possible outSkus for current selected options
|
|
1121
|
+
const outSelectableSkus = this.selectedSkus.filter(sku => this.outSkus.includes(sku));
|
|
1122
|
+
return Object.keys(detail.skus_availability).some(sku => outSelectableSkus.includes(Number(sku)));
|
|
1123
|
+
}
|
|
1124
|
+
return this.selectedOptions.some(option => option.is_out && option.option_id !== detail.option_id);
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Renders a single input element (radio or checkbox) for an option detail.
|
|
1128
|
+
* @param type - The type of input element ('radio' or 'checkbox').
|
|
1129
|
+
* @param detail - The detail object representing an option detail.
|
|
1130
|
+
* @param option - The parent option object containing the details.
|
|
1131
|
+
* @param isRequired - Indicates if the input is required based on the option's rules.
|
|
1132
|
+
* @param name - The name attribute for the input element.
|
|
1133
|
+
* @returns HTMLElement - A labeled input element.
|
|
1134
|
+
*/
|
|
1135
|
+
renderInput(type, detail, option, isRequired, name, buttonStyle) {
|
|
1136
|
+
const id = this.generateUniqueKey(`${type}-${option.id}-${detail.id}`);
|
|
1137
|
+
const isDisabled = this.isOptionDetailOut(detail);
|
|
1138
|
+
return (index.h("label", { class: {
|
|
1139
|
+
"s-product-options-disabled": isDisabled,
|
|
1140
|
+
} }, index.h("input", { id: id, type: type, name: name, value: detail.id, disabled: isDisabled, required: isRequired, checked: detail.is_selected, onInvalid: (e) => this.invalidHandler(e, option), onChange: (e) => this.changedHandler(e, option) }), index.h("div", { class: { "s-product-options-grid-mode-span": buttonStyle, "s-product-options-disabled": isDisabled } }, this.getOptionDetailName(detail))));
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Renders a collection of input elements for all details of an option.
|
|
1144
|
+
* @param type - The type of input elements ('radio' or 'checkbox').
|
|
1145
|
+
* @param option - The parent option object containing the details.
|
|
1146
|
+
* @param isRequired - Indicates if the inputs are required based on the option's rules.
|
|
1147
|
+
* @returns HTMLElement[] - An array of labeled input elements.
|
|
1148
|
+
*/
|
|
1149
|
+
renderOptionDetails(type, option, isRequired, buttonStyle = false) {
|
|
1150
|
+
const baseName = this.generateInputName(option.id);
|
|
1151
|
+
const name = type === 'radio' ? baseName : `${baseName}[]`;
|
|
1152
|
+
return option?.details.map((detail) => this.renderInput(type, detail, option, isRequired, name, buttonStyle));
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Renders a dropdown (select) element for a single-option selection.
|
|
1156
|
+
* @param option - The parent option object.
|
|
1157
|
+
* @returns HTMLElement - A select dropdown element with all option details.
|
|
1158
|
+
*/
|
|
1159
|
+
renderSelect(option) {
|
|
1160
|
+
return (index.h("div", null, index.h("select", { id: this.generateInputId(option.id), name: this.generateInputName(option.id), required: !option.visibility_condition && option.required, class: "s-form-control", onInvalid: (e) => this.invalidHandler(e, option), onChange: (e) => this.changedHandler(e, option) }, index.h("option", { value: "" }, option.placeholder), option?.details.map((detail) => (index.h("option", { key: detail.id, value: detail.id, disabled: this.canDisabled && this.isOptionDetailOut(detail), selected: detail.is_selected }, this.getOptionDetailName(detail)))))));
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Renders a grid-based layout for option inputs (radio or checkbox).
|
|
1164
|
+
* @param type - The type of input elements ('radio' or 'checkbox').
|
|
1165
|
+
* @param option - The parent option object containing the details.
|
|
1166
|
+
* @param isRequired - Indicates if the inputs are required based on the option's rules.
|
|
1167
|
+
* @returns HTMLElement - A grid-based container with input elements.
|
|
1168
|
+
*/
|
|
1169
|
+
renderButtonStyle(type, option, isRequired) {
|
|
1170
|
+
return (index.h("div", { class: "s-product-options-grid-mode" }, this.renderOptionDetails(type, option, isRequired, true)));
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Renders a single-option selection, either as a grid or dropdown, based on configuration.
|
|
1174
|
+
* @param option - The parent option object.
|
|
1175
|
+
* @returns HTMLElement - The rendered single-option element.
|
|
1176
|
+
*/
|
|
1177
|
+
singleOption(option) {
|
|
1178
|
+
const buttonStyle = this.optionConfig?.['single-option']?.type === 'button';
|
|
1179
|
+
const isRequired = !option.visibility_condition && option.required;
|
|
1180
|
+
return buttonStyle
|
|
1181
|
+
? this.renderButtonStyle('radio', option, isRequired)
|
|
1182
|
+
: this.renderSelect(option);
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Renders a multiple-option selection, either as a grid or list, based on configuration.
|
|
1186
|
+
* @param option - The parent option object.
|
|
1187
|
+
* @returns HTMLElement - The rendered multiple-option element.
|
|
1188
|
+
*/
|
|
1189
|
+
multipleOptions(option) {
|
|
1190
|
+
const buttonStyle = this.optionConfig?.['multiple-option']?.type === 'button';
|
|
1191
|
+
const isRequired = option.required &&
|
|
1192
|
+
!option.details.some((detail) => detail.is_selected) &&
|
|
1193
|
+
!option.visibility_condition;
|
|
1194
|
+
return buttonStyle
|
|
1195
|
+
? this.renderButtonStyle('checkbox', option, isRequired)
|
|
1196
|
+
: (index.h("div", { class: {
|
|
1197
|
+
's-product-options-multiple-options-wrapper': true,
|
|
1198
|
+
required: option.required,
|
|
1199
|
+
} }, this.renderOptionDetails('checkbox', option, isRequired)));
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Renders a radio option selection (used for bundle products).
|
|
1203
|
+
* This is essentially the same as single option but with explicit radio handling.
|
|
1204
|
+
* @param option - The parent option object.
|
|
1205
|
+
* @returns HTMLElement - The rendered radio option element.
|
|
1206
|
+
*/
|
|
1207
|
+
radioOption(option) {
|
|
1208
|
+
// Radio options behave the same as single options
|
|
1209
|
+
return this.singleOption(option);
|
|
1210
|
+
}
|
|
1211
|
+
//@ts-ignore
|
|
1212
|
+
colorOption(option) {
|
|
1213
|
+
return (index.h("fieldset", { class: "s-product-options-colors-wrapper" }, option?.details.map((detail) => (index.h("div", { class: "s-product-options-colors-item", key: detail.id }, index.h("input", { type: "radio", value: detail.id, required: !option.visibility_condition && option.required, checked: detail.is_selected, name: this.generateInputName(option.id), disabled: this.canDisabled && this.isOptionDetailOut(detail), id: this.generateUniqueKey(`color-${this.productId}-${option.id}-${detail.id}`), onInvalid: (e) => this.invalidHandler(e, option), onChange: (e) => this.changedHandler(e, option) }), index.h("label", { htmlFor: this.generateUniqueKey(`color-${this.productId}-${option.id}-${detail.id}`) }, index.h("span", { style: { backgroundColor: detail.color } }), index.h("div", { innerHTML: this.getOptionDetailName(detail, true, option.type) })))))));
|
|
1214
|
+
}
|
|
1215
|
+
//@ts-ignore
|
|
1216
|
+
thumbnailOption(option) {
|
|
1217
|
+
return index.h("div", { class: "s-product-options-thumbnails-wrapper" }, option.details.map((detail) => {
|
|
1218
|
+
return index.h("div", { key: detail.id }, index.h("input", { type: "radio", value: detail.id, "data-itemid": detail.id, required: !option.visibility_condition && option.required, checked: detail.is_selected, name: this.generateInputName(option.id), "data-img-id": detail.option_value, disabled: this.canDisabled && this.isOptionDetailOut(detail), id: this.generateUniqueKey(`option_${this.productId}-${option.id}_${detail.id}`), onInvalid: (e) => this.invalidHandler(e, option), onChange: (e) => this.changedHandler(e, option) }), index.h("label", { htmlFor: this.generateUniqueKey(`option_${this.productId}-${option.id}_${detail.id}`), "data-img-id": detail.option_value, class: "go-to-slide" }, index.h("img", { "data-src": detail.image, src: detail.image, title: detail.name, alt: detail.name }), index.h("span", { innerHTML: check.iconCheck, class: "s-product-options-thumbnails-icon" }), this.isOptionDetailOut(detail) ?
|
|
1219
|
+
[
|
|
1220
|
+
index.h("small", { key: detail.id, class: "s-product-options-thumbnails-stock-badge" }, this.outOfStockText),
|
|
1221
|
+
this.canDisabled ? index.h("div", { key: detail.id, class: "s-product-options-thumbnails-badge-overlay" }) : '',
|
|
1222
|
+
]
|
|
1223
|
+
: ''), index.h("p", null, this.getOptionDetailName(detail, false), " "));
|
|
1224
|
+
}));
|
|
1225
|
+
}
|
|
1226
|
+
// Digital card options
|
|
1227
|
+
digitalCardValuesOption(option) {
|
|
1228
|
+
return index.h("div", { class: "s-product-options-digital-card-wrapper" }, this.availableDigitalCardValues.length > 0 ? this.availableDigitalCardValues.map((detail) => {
|
|
1229
|
+
const id = String(detail.id);
|
|
1230
|
+
return index.h("label", { htmlFor: this.generateUniqueKey(id.toString()), key: id, class: "s-product-options-digital-card-option" }, index.h("input", { type: "radio", "data-code-value": true, class: "s-form-control s-product-options-digital-card-input", value: detail.id, name: this.generateInputName(option.id), id: this.generateUniqueKey(id.toString()), required: !option.visibility_condition && option.required, onInvalid: (e) => this.invalidHandler(e, option), ...(!this.ignoreDefaultCardValue ? { defaultChecked: this.getSelectedDigitalCardOptions(option)?.id === detail.id } : {}) }), index.h("span", null, detail.name, " ", salla.config?.currency()?.symbol));
|
|
1231
|
+
})
|
|
1232
|
+
: index.h("div", { class: "s-product-options-digital-card-out-of-stock" }));
|
|
1233
|
+
}
|
|
1234
|
+
countryOption(option) {
|
|
1235
|
+
return index.h("div", { class: "s-product-options-digital-card-wrapper" }, option.details.map((detail) => {
|
|
1236
|
+
return index.h("label", { htmlFor: this.generateUniqueKey(detail.id.toString()), key: detail.id, class: { "s-product-options-digital-card-option": true, "s-product-options-digital-card-option-stock-out": detail.is_out } }, index.h("input", { id: this.generateUniqueKey(detail.id.toString()), type: "radio", class: "s-form-control s-product-options-digital-card-input", value: detail.id, name: this.generateInputName(option.id), disabled: detail.is_out, required: !option.visibility_condition && option.required, onInvalid: (e) => this.invalidHandler(e, option), onChange: e => this.changedHandler(e, option), onClick: () => { this.disableCardValue = false; }, ...(salla.url.is_page("cart") ? { defaultChecked: this.getSelectedDigitalCardOptions(option)?.id === detail.id } : {}) }), index.h("img", { loading: "lazy", alt: detail.code, height: 24, width: 24, class: "s-product-options-country-flag", src: `https://cdn.assets.salla.network/prod/admin/cp/assets/flags/1x1/${String(detail.code).toLocaleLowerCase()}.svg`, onError: e => {
|
|
1237
|
+
e.currentTarget.onerror = null;
|
|
1238
|
+
e.currentTarget.src =
|
|
1239
|
+
salla.url.cdn('images/globe.svg');
|
|
1240
|
+
} }), index.h("span", null, detail.name));
|
|
1241
|
+
}));
|
|
1242
|
+
}
|
|
1243
|
+
get host() { return index.getElement(this); }
|
|
1244
|
+
};
|
|
1245
|
+
SallaProductOptions.style = sallaProductOptionsCss;
|
|
1246
|
+
|
|
1247
|
+
var http = {
|
|
1248
|
+
request(method, url, data, successCb = null, errorCb = null) {
|
|
1249
|
+
return index.axios
|
|
1250
|
+
.request({url, data, method: method.toLowerCase(), responseType: 'json'})
|
|
1251
|
+
.then(successCb)
|
|
1252
|
+
.catch(errorCb);
|
|
1253
|
+
},
|
|
1254
|
+
|
|
1255
|
+
get(url, successCb = null, errorCb = null, data) {
|
|
1256
|
+
// return this.request('get', url, data, successCb, errorCb);
|
|
1257
|
+
return index.axios
|
|
1258
|
+
.get(url, {params: data})
|
|
1259
|
+
.then(successCb)
|
|
1260
|
+
.catch(errorCb);
|
|
1261
|
+
},
|
|
1262
|
+
|
|
1263
|
+
post(url, data, successCb = null, errorCb = null) {
|
|
1264
|
+
return this.request('post', url, data, successCb, errorCb);
|
|
1265
|
+
},
|
|
1266
|
+
|
|
1267
|
+
put(url, data, successCb = null, errorCb = null) {
|
|
1268
|
+
return this.request('put', url, data, successCb, errorCb);
|
|
1269
|
+
},
|
|
1270
|
+
|
|
1271
|
+
delete(url, data, successCb = null, errorCb = null) {
|
|
1272
|
+
return this.request('delete', url, data, successCb, errorCb);
|
|
1273
|
+
},
|
|
1274
|
+
|
|
1275
|
+
requestWithSupportAjax(url, payload, method = 'post') {
|
|
1276
|
+
return new Promise((resolve, reject) => {
|
|
1277
|
+
if (!window?.isLegacyTheme) {
|
|
1278
|
+
return this.request(method, url, payload, ({data}) => {
|
|
1279
|
+
return resolve(data);
|
|
1280
|
+
}, ({response}) => {
|
|
1281
|
+
return reject(response);
|
|
1282
|
+
})
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* @deprecated to support legacy themes
|
|
1287
|
+
*/
|
|
1288
|
+
$.ajax({
|
|
1289
|
+
url: url,
|
|
1290
|
+
method: method.toUpperCase(),
|
|
1291
|
+
data: payload,
|
|
1292
|
+
async: false,
|
|
1293
|
+
success: function ({data}) {
|
|
1294
|
+
return resolve(data);
|
|
1295
|
+
},
|
|
1296
|
+
error: function ({response}) {
|
|
1297
|
+
return reject(response);
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
})
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1304
|
+
var DetectOS = {
|
|
1305
|
+
options: [],
|
|
1306
|
+
header: [navigator.platform, navigator.userAgent, navigator.appVersion, navigator.vendor, window.opera],
|
|
1307
|
+
dataos: [
|
|
1308
|
+
{name: 'Windows Phone', value: 'Windows Phone', version: 'OS'},
|
|
1309
|
+
{name: 'Windows', value: 'Win', version: 'NT'},
|
|
1310
|
+
{name: 'iPhone', value: 'iPhone', version: 'OS'},
|
|
1311
|
+
{name: 'iPad', value: 'iPad', version: 'OS'},
|
|
1312
|
+
{name: 'Kindle', value: 'Silk', version: 'Silk'},
|
|
1313
|
+
{name: 'Android', value: 'Android', version: 'Android'},
|
|
1314
|
+
{name: 'PlayBook', value: 'PlayBook', version: 'OS'},
|
|
1315
|
+
{name: 'BlackBerry', value: 'BlackBerry', version: '/'},
|
|
1316
|
+
{name: 'Macintosh', value: 'Mac', version: 'OS X'},
|
|
1317
|
+
{name: 'Linux', value: 'Linux', version: 'rv'},
|
|
1318
|
+
{name: 'Palm', value: 'Palm', version: 'PalmOS'}
|
|
1319
|
+
],
|
|
1320
|
+
databrowser: [
|
|
1321
|
+
{name: 'Chrome', value: 'Chrome', version: 'Chrome'},
|
|
1322
|
+
{name: 'Firefox', value: 'Firefox', version: 'Firefox'},
|
|
1323
|
+
{name: 'Safari', value: 'Safari', version: 'Version'},
|
|
1324
|
+
{name: 'Internet Explorer', value: 'MSIE', version: 'MSIE'},
|
|
1325
|
+
{name: 'Opera', value: 'Opera', version: 'Opera'},
|
|
1326
|
+
{name: 'BlackBerry', value: 'CLDC', version: 'CLDC'},
|
|
1327
|
+
{name: 'Mozilla', value: 'Mozilla', version: 'Mozilla'}
|
|
1328
|
+
],
|
|
1329
|
+
init: function () {
|
|
1330
|
+
var agent = this.header.join(' '),
|
|
1331
|
+
os = this.matchItem(agent, this.dataos),
|
|
1332
|
+
browser = this.matchItem(agent, this.databrowser);
|
|
1333
|
+
|
|
1334
|
+
return {os: os, browser: browser};
|
|
1335
|
+
},
|
|
1336
|
+
matchItem: function (string, data) {
|
|
1337
|
+
var i = 0,
|
|
1338
|
+
j = 0,
|
|
1339
|
+
regex,
|
|
1340
|
+
regexv,
|
|
1341
|
+
match,
|
|
1342
|
+
matches,
|
|
1343
|
+
version;
|
|
1344
|
+
|
|
1345
|
+
for (i = 0; i < data.length; i += 1) {
|
|
1346
|
+
regex = new RegExp(data[i].value, 'i');
|
|
1347
|
+
match = regex.test(string);
|
|
1348
|
+
if (match) {
|
|
1349
|
+
regexv = new RegExp(data[i].version + '[- /:;]([\\d._]+)', 'i');
|
|
1350
|
+
matches = string.match(regexv);
|
|
1351
|
+
version = '';
|
|
1352
|
+
if (matches) {
|
|
1353
|
+
if (matches[1]) {
|
|
1354
|
+
matches = matches[1];
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (matches) {
|
|
1358
|
+
matches = matches.split(/[._]+/);
|
|
1359
|
+
for (j = 0; j < matches.length; j += 1) {
|
|
1360
|
+
if (j === 0) {
|
|
1361
|
+
version += matches[j] + '.';
|
|
1362
|
+
} else {
|
|
1363
|
+
version += matches[j];
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
} else {
|
|
1367
|
+
version = '0';
|
|
1368
|
+
}
|
|
1369
|
+
return {
|
|
1370
|
+
name: data[i].name,
|
|
1371
|
+
version: parseFloat(version)
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return {name: 'unknown', version: 0};
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* @typedef {Object} ApplePayPaymentContact
|
|
1381
|
+
* @property {string} phoneNumber
|
|
1382
|
+
* @property {string} emailAddress
|
|
1383
|
+
* @property {string} givenName
|
|
1384
|
+
* @property {string} familyName
|
|
1385
|
+
* @property {string} [phoneticGivenName]
|
|
1386
|
+
* @property {string} [phoneticFamilyName]
|
|
1387
|
+
* @property {string[]} addressLines
|
|
1388
|
+
* @property {string} [subLocality]
|
|
1389
|
+
* @property {string} locality
|
|
1390
|
+
* @property {string} postalCode
|
|
1391
|
+
* @property {string} [subAdministrativeArea]
|
|
1392
|
+
* @property {string} administrativeArea
|
|
1393
|
+
* @property {string} country
|
|
1394
|
+
* @property {string} countryCode
|
|
1395
|
+
*/
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
*
|
|
1400
|
+
* @param {SallaApplePay} SallaApplePay
|
|
1401
|
+
* @param {boolean} isAuthorized
|
|
1402
|
+
* @param {ApplePayPaymentContact} shippingContact
|
|
1403
|
+
*
|
|
1404
|
+
*/
|
|
1405
|
+
async function mutateShippingContact(SallaApplePay, shippingContact, isAuthorized = false) {
|
|
1406
|
+
salla.logger.log('🍏 Pay: mutateShippingContact called', shippingContact, isAuthorized);
|
|
1407
|
+
|
|
1408
|
+
if (!SallaApplePay.detail.requiredShippingContactFields || SallaApplePay.detail.requiredShippingContactFields.length == 0) {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
if (isAuthorized) {
|
|
1413
|
+
if (isGuestCheckout()) {
|
|
1414
|
+
|
|
1415
|
+
if (
|
|
1416
|
+
!shippingContact.emailAddress ||
|
|
1417
|
+
!shippingContact.givenName ||
|
|
1418
|
+
!shippingContact.familyName ||
|
|
1419
|
+
!shippingContact.phoneNumber
|
|
1420
|
+
) {
|
|
1421
|
+
|
|
1422
|
+
salla.logger.warn('🍏 Pay: Guest contact fields are required', shippingContact);
|
|
1423
|
+
|
|
1424
|
+
const errors = [];
|
|
1425
|
+
if (!shippingContact.emailAddress) {
|
|
1426
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'emailAddress', 'Email address is required'));
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
if (!shippingContact.phoneNumber) {
|
|
1430
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'phoneNumber', 'Phone number is required'));
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
if (!shippingContact.givenName || !shippingContact.familyName) {
|
|
1434
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'name', 'Name is required'));
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
SallaApplePay.session.completePayment({
|
|
1438
|
+
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_CONTACT,
|
|
1439
|
+
errors: errors
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
await updateGuestContact(SallaApplePay, shippingContact);
|
|
1446
|
+
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// if authorized and not guest checkout, do nothing address already added
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (!SallaApplePay.detail.requiredShippingContactFields.includes('postalAddress')) {
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
return http.post(
|
|
1458
|
+
SallaApplePay.detail.shippingContactSelected.url.replace('{id}', SallaApplePay.id),
|
|
1459
|
+
{
|
|
1460
|
+
'country': shippingContact.country,
|
|
1461
|
+
'city': shippingContact.locality,
|
|
1462
|
+
'local': shippingContact.subLocality || shippingContact.administrativeArea || shippingContact.locality,
|
|
1463
|
+
'description': shippingContact.subAdministrativeArea,
|
|
1464
|
+
'street': shippingContact.addressLines?.join(", ") || shippingContact.administrativeArea,
|
|
1465
|
+
'country_code': shippingContact.countryCode,
|
|
1466
|
+
'postal_code': shippingContact.postalCode,
|
|
1467
|
+
},
|
|
1468
|
+
async ({ data }) => {
|
|
1469
|
+
if (typeof SallaApplePay.detail.shippingContactSelected.onSuccess === 'function') {
|
|
1470
|
+
SallaApplePay.detail.shippingContactSelected.onSuccess(data);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
SallaApplePay.address_id = data.data.address_id;
|
|
1474
|
+
SallaApplePay.shipping_methods = data.data.shipping_methods;
|
|
1475
|
+
|
|
1476
|
+
if (!SallaApplePay.shipping_methods || (SallaApplePay.shipping_methods && !SallaApplePay.shipping_methods.length)) {
|
|
1477
|
+
salla.logger.warn('🍏 Pay: We dont found any supported methods', data);
|
|
1478
|
+
|
|
1479
|
+
SallaApplePay.session.completeShippingContactSelection({
|
|
1480
|
+
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS,
|
|
1481
|
+
errors: [
|
|
1482
|
+
new window.ApplePayError('addressUnserviceable')
|
|
1483
|
+
]
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
return
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
try {
|
|
1490
|
+
await SallaApplePay.selectApplePayShippingMethod(SallaApplePay.shipping_methods[0]);
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
salla.logger.warn('Failed set the shipping details to api', error);
|
|
1493
|
+
|
|
1494
|
+
SallaApplePay.session.completeShippingContactSelection({
|
|
1495
|
+
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS,
|
|
1496
|
+
errors: [
|
|
1497
|
+
new window.ApplePayError('addressUnserviceable')
|
|
1498
|
+
]
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
try {
|
|
1505
|
+
await SallaApplePay.recalculateTotal();
|
|
1506
|
+
} catch (error) {
|
|
1507
|
+
salla.logger.warn('🍏 Pay: Failed recalculate total', error);
|
|
1508
|
+
|
|
1509
|
+
SallaApplePay.session.completeShippingContactSelection({
|
|
1510
|
+
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS,
|
|
1511
|
+
errors: [
|
|
1512
|
+
new window.ApplePayError('addressUnserviceable')
|
|
1513
|
+
]
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const updatedShippingContactSelection = {
|
|
1520
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
1521
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
1522
|
+
newShippingMethods: SallaApplePay.mappingShippingMethods(SallaApplePay.shipping_methods)
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1525
|
+
salla.logger.log('🍏 Pay: completeShippingContactSelection', updatedShippingContactSelection);
|
|
1526
|
+
|
|
1527
|
+
SallaApplePay.session.completeShippingContactSelection(updatedShippingContactSelection);
|
|
1528
|
+
},
|
|
1529
|
+
({ response }) => {
|
|
1530
|
+
salla.logger.warn('🍏 Pay: Failed add address via api', response);
|
|
1531
|
+
|
|
1532
|
+
if (typeof SallaApplePay.detail.shippingContactSelected.onFailed === 'function') {
|
|
1533
|
+
SallaApplePay.detail.shippingContactSelected.onFailed(response);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// parse 422 errors
|
|
1537
|
+
let fields = response?.data?.error?.fields;
|
|
1538
|
+
|
|
1539
|
+
let errors = getApplePayErrors({
|
|
1540
|
+
countryCode: fields?.country_code && fields.country_code.length > 0 ? fields.country_code[0] : null,
|
|
1541
|
+
locality: fields?.city && fields.city.length > 0 ? fields.city[0] : null,
|
|
1542
|
+
country: fields?.country && fields.country.length > 0 ? fields.country[0] : null,
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
if (errors.length === 0 && response?.data?.error?.message) {
|
|
1546
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'locality', response?.data?.error?.message));
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
SallaApplePay.session.completeShippingContactSelection({
|
|
1550
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
1551
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
1552
|
+
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS,
|
|
1553
|
+
errors: errors
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function isGuestCheckout() {
|
|
1560
|
+
return salla.config.isGuest() && salla.config.get('store.features').includes('guest-checkout');
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/**
|
|
1564
|
+
* Update guest contact
|
|
1565
|
+
*
|
|
1566
|
+
* @param {SallaApplePay} SallaApplePay
|
|
1567
|
+
* @param {ApplePayPaymentContact} shippingContact
|
|
1568
|
+
*
|
|
1569
|
+
*/
|
|
1570
|
+
async function updateGuestContact(SallaApplePay, shippingContact) {
|
|
1571
|
+
salla.logger.log('🍏 Pay: Updating guest contact', shippingContact);
|
|
1572
|
+
|
|
1573
|
+
return new Promise((resolve, reject) => {
|
|
1574
|
+
http.post(
|
|
1575
|
+
SallaApplePay.detail.guestContactSelected.url.replace('{id}', SallaApplePay.id),
|
|
1576
|
+
{
|
|
1577
|
+
'email': shippingContact.emailAddress || null,
|
|
1578
|
+
'first_name': shippingContact.givenName || null,
|
|
1579
|
+
'last_name': shippingContact.familyName || null,
|
|
1580
|
+
'phone_number': shippingContact.phoneNumber || null,
|
|
1581
|
+
'country_code': shippingContact.countryCode || null,
|
|
1582
|
+
},
|
|
1583
|
+
async ({ data }) => {
|
|
1584
|
+
if (typeof SallaApplePay.detail.guestContactSelected?.onSuccess === 'function') {
|
|
1585
|
+
SallaApplePay.detail.guestContactSelected.onSuccess(data);
|
|
1586
|
+
}
|
|
1587
|
+
resolve(data);
|
|
1588
|
+
},
|
|
1589
|
+
({ response }) => {
|
|
1590
|
+
salla.logger.warn('🍏 Pay: Failed to update guest contact via api', response);
|
|
1591
|
+
|
|
1592
|
+
if (typeof SallaApplePay.detail.guestContactSelected?.onFailed === 'function') {
|
|
1593
|
+
SallaApplePay.detail.guestContactSelected.onFailed(response);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// Reject the promise so it can be caught in onPaymentAuthorized
|
|
1597
|
+
reject({ response });
|
|
1598
|
+
}
|
|
1599
|
+
);
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function getApplePayErrors(fields) {
|
|
1604
|
+
return Object.entries(fields)
|
|
1605
|
+
.filter(([field, messages]) => messages && messages.length > 0)
|
|
1606
|
+
.map(([field, messages]) => new window.ApplePayError('shippingContactInvalid', field, messages[0]));
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
window.Salla = window.Salla || {};
|
|
1610
|
+
window.Salla.Payments = window.Salla.Payments || {};
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* Full Example
|
|
1614
|
+
*
|
|
1615
|
+
* Salla.event.createAndDispatch('payments::apple-pay.start-transaction', {
|
|
1616
|
+
* amount: 1000,
|
|
1617
|
+
* validateMerchant: {
|
|
1618
|
+
* url: '{{ route('cp.marketplace.cart.pay', ['cart' => $cart]) }}',
|
|
1619
|
+
* // onFailed: (response) => {
|
|
1620
|
+
* // laravel.ajax.errorHandler(response);
|
|
1621
|
+
* // this.onCancel({}, response.data.error.message);
|
|
1622
|
+
* // },
|
|
1623
|
+
* // onSuccess: (response) => {
|
|
1624
|
+
* // laravel.ajax.successHandler(response);
|
|
1625
|
+
* // }
|
|
1626
|
+
* },
|
|
1627
|
+
* authorized: {
|
|
1628
|
+
* url: '{{ route('cp.marketplace.cart.confirm', ['cart' => $cart]) }}',
|
|
1629
|
+
* // onFailed: (response) => {
|
|
1630
|
+
* // laravel.ajax.errorHandler(response);
|
|
1631
|
+
* // this.onCancel({}, response.data.error.message);
|
|
1632
|
+
* // },
|
|
1633
|
+
* // onSuccess: (response) => {
|
|
1634
|
+
* // // nothing
|
|
1635
|
+
* // }
|
|
1636
|
+
* },
|
|
1637
|
+
* // onError: function (message) {
|
|
1638
|
+
* // laravel.alert(message);
|
|
1639
|
+
* // }
|
|
1640
|
+
* });
|
|
1641
|
+
*/
|
|
1642
|
+
window.SallaApplePay = {
|
|
1643
|
+
session: null,
|
|
1644
|
+
detail: null,
|
|
1645
|
+
address_id: null,
|
|
1646
|
+
shipping_methods: [],
|
|
1647
|
+
total: undefined,
|
|
1648
|
+
request: undefined,
|
|
1649
|
+
id: undefined,
|
|
1650
|
+
countryCode: null,
|
|
1651
|
+
totals: [],
|
|
1652
|
+
shippingCompany: null,
|
|
1653
|
+
init: function () {
|
|
1654
|
+
document.removeEventListener('payments::apple-pay.start-transaction', SallaApplePay.startSession);
|
|
1655
|
+
Salla.event.addEventListener('payments::apple-pay.start-transaction', SallaApplePay.startSession);
|
|
1656
|
+
},
|
|
1657
|
+
|
|
1658
|
+
initDefault: function () {
|
|
1659
|
+
if (!SallaApplePay.detail.onError) {
|
|
1660
|
+
SallaApplePay.detail.onError = function (message) {
|
|
1661
|
+
salla.notify.error(message);
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if (!SallaApplePay.detail.authorized.onFailed) {
|
|
1666
|
+
SallaApplePay.detail.authorized.onFailed = (response) => {
|
|
1667
|
+
salla.logger.log(JSON.stringify(response));
|
|
1668
|
+
salla.api.handleErrorResponse(response);
|
|
1669
|
+
SallaApplePay.onCancel({}, response.data.error.message);
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
if (!SallaApplePay.detail.validateMerchant.onFailed) {
|
|
1674
|
+
SallaApplePay.detail.validateMerchant.onFailed = (response) => {
|
|
1675
|
+
salla.logger.log(JSON.stringify(response));
|
|
1676
|
+
salla.api.handleErrorResponse(response);
|
|
1677
|
+
SallaApplePay.onCancel({}, response.data.error.message);
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
if (!SallaApplePay.detail.authorized.onSuccess) {
|
|
1682
|
+
SallaApplePay.detail.authorized.onSuccess = (response) => {
|
|
1683
|
+
salla.logger.log(JSON.stringify(response));
|
|
1684
|
+
salla.api.handleAfterResponseActions(response);
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
},
|
|
1688
|
+
|
|
1689
|
+
prepareLineItems: function () {
|
|
1690
|
+
if (!SallaApplePay.detail?.items?.length) {
|
|
1691
|
+
SallaApplePay.detail.items = [
|
|
1692
|
+
{
|
|
1693
|
+
label: salla.lang.get('pages.cart.items_total'),
|
|
1694
|
+
amount: parseFloat(SallaApplePay.detail.amount).toString()
|
|
1695
|
+
}
|
|
1696
|
+
];
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
return SallaApplePay.detail.items;
|
|
1700
|
+
},
|
|
1701
|
+
|
|
1702
|
+
prepareTotal: function () {
|
|
1703
|
+
return {
|
|
1704
|
+
// apple ask to use business name
|
|
1705
|
+
label: window.location.hostname || 'Salla',
|
|
1706
|
+
//label: salla.lang.get('pages.cart.final_total'),
|
|
1707
|
+
amount: parseFloat(SallaApplePay.detail.amount).toString()
|
|
1708
|
+
}
|
|
1709
|
+
},
|
|
1710
|
+
|
|
1711
|
+
isPhysical() {
|
|
1712
|
+
return SallaApplePay.detail?.requiredShippingContactFields?.includes('postalAddress');
|
|
1713
|
+
},
|
|
1714
|
+
|
|
1715
|
+
startSession: async function (event) {
|
|
1716
|
+
|
|
1717
|
+
SallaApplePay.detail = event.detail || event;
|
|
1718
|
+
|
|
1719
|
+
salla.log('🍏 Pay: payments::apple-pay.start-transaction', SallaApplePay.detail);
|
|
1720
|
+
|
|
1721
|
+
SallaApplePay.initDefault();
|
|
1722
|
+
|
|
1723
|
+
let version = SallaApplePay.getApplePaySessionVersion();
|
|
1724
|
+
let supportedNetworks = SallaApplePay.detail.supportedNetworks || ['masterCard', 'visa'];
|
|
1725
|
+
|
|
1726
|
+
if (version === 5) {
|
|
1727
|
+
supportedNetworks.push('mada');
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
SallaApplePay.request = {
|
|
1731
|
+
countryCode: SallaApplePay.detail.countryCode || 'SA',
|
|
1732
|
+
supportsCouponCode: true,
|
|
1733
|
+
couponCode: '',
|
|
1734
|
+
currencyCode: SallaApplePay.detail.currency || 'SAR',
|
|
1735
|
+
requiredShippingContactFields: SallaApplePay.detail.requiredShippingContactFields ? SallaApplePay.detail.requiredShippingContactFields : [],
|
|
1736
|
+
merchantCapabilities: ['supports3DS'],
|
|
1737
|
+
supportedNetworks: supportedNetworks,
|
|
1738
|
+
supportedCountries: SallaApplePay.detail.supportedCountries || ['SA'],
|
|
1739
|
+
total: SallaApplePay.prepareTotal(),
|
|
1740
|
+
shippingContact: SallaApplePay.detail.shippingContact ? SallaApplePay.detail.shippingContact : {},
|
|
1741
|
+
shippingMethods: SallaApplePay.detail.shippingMethods && SallaApplePay.detail.shippingMethods.length ? SallaApplePay.mappingShippingMethods(event.detail.shippingMethods) : [],
|
|
1742
|
+
lineItems: SallaApplePay.prepareLineItems()
|
|
1743
|
+
};
|
|
1744
|
+
|
|
1745
|
+
salla.log('🍏 Pay: init ', SallaApplePay.request);
|
|
1746
|
+
|
|
1747
|
+
// https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest
|
|
1748
|
+
SallaApplePay.session = new ApplePaySession(version, SallaApplePay.request);
|
|
1749
|
+
|
|
1750
|
+
SallaApplePay.session.onshippingcontactselected = SallaApplePay.onShippingContactSelected;
|
|
1751
|
+
SallaApplePay.session.onshippingmethodselected = SallaApplePay.onShippingMethodSelected;
|
|
1752
|
+
SallaApplePay.session.onvalidatemerchant = SallaApplePay.onValidateMerchant;
|
|
1753
|
+
SallaApplePay.session.onpaymentauthorized = SallaApplePay.onPaymentAuthorized;
|
|
1754
|
+
SallaApplePay.session.oncancel = SallaApplePay.onCancel;
|
|
1755
|
+
SallaApplePay.session.oncouponcodechanged = SallaApplePay.onCouponCodeChanged;
|
|
1756
|
+
SallaApplePay.session.onpaymentmethodselected = SallaApplePay.onPaymentMethodSelected;
|
|
1757
|
+
|
|
1758
|
+
SallaApplePay.session.begin();
|
|
1759
|
+
},
|
|
1760
|
+
onPaymentMethodSelected: async (event) => {
|
|
1761
|
+
salla.logger.log('🍏 Pay: onPaymentMethodSelected', event);
|
|
1762
|
+
|
|
1763
|
+
// perform recalculate here only if digital product
|
|
1764
|
+
// if physical recalculate performed with shipping company
|
|
1765
|
+
if (!SallaApplePay.isPhysical()) {
|
|
1766
|
+
try {
|
|
1767
|
+
await SallaApplePay.recalculateTotal();
|
|
1768
|
+
|
|
1769
|
+
const updatedPaymentDetails = {
|
|
1770
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
1771
|
+
newLineItems: SallaApplePay.prepareLineItems()
|
|
1772
|
+
};
|
|
1773
|
+
|
|
1774
|
+
salla.logger.log('🍏 Pay: completePaymentMethodSelection', updatedPaymentDetails);
|
|
1775
|
+
|
|
1776
|
+
SallaApplePay.session.completePaymentMethodSelection(updatedPaymentDetails);
|
|
1777
|
+
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
salla.logger.warn('🍏 Pay: Failed recalculate total', error);
|
|
1780
|
+
|
|
1781
|
+
SallaApplePay.session.completePaymentMethodSelection({
|
|
1782
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
1783
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
1784
|
+
status: SallaApplePay.session.STATUS_FAILURE,
|
|
1785
|
+
errors: [new window.ApplePayError("unknown", undefined, error?.response?.data?.error?.message || error?.response?.data?.error?.code || 'Failed to recalculate total')]
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
salla.logger.log('🍏 Pay: completePaymentMethodSelection', {
|
|
1792
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
1793
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1796
|
+
SallaApplePay.session.completePaymentMethodSelection({
|
|
1797
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
1798
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
1799
|
+
});
|
|
1800
|
+
},
|
|
1801
|
+
|
|
1802
|
+
onCouponCodeChanged(event) {
|
|
1803
|
+
Salla.event.dispatch('payments::apple-pay.coupon.change', event);
|
|
1804
|
+
|
|
1805
|
+
return http.post(SallaApplePay.detail.onCouponCodeChanged.url.replace('{id}', SallaApplePay.id), {
|
|
1806
|
+
'coupon': event.couponCode,
|
|
1807
|
+
'payment_method': 'apple_pay',
|
|
1808
|
+
}, async ({ data }) => {
|
|
1809
|
+
if (typeof SallaApplePay.detail.onCouponCodeChanged.onSuccess === 'function') {
|
|
1810
|
+
SallaApplePay.detail.onCouponCodeChanged.onSuccess(data);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
salla.log('🍏 Pay: Coupon applied success');
|
|
1814
|
+
|
|
1815
|
+
await SallaApplePay.recalculateTotal();
|
|
1816
|
+
|
|
1817
|
+
SallaApplePay.session.completeCouponCodeChange({
|
|
1818
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
1819
|
+
newLineItems: SallaApplePay.prepareLineItems()
|
|
1820
|
+
});
|
|
1821
|
+
}, async (error) => {
|
|
1822
|
+
let response = error?.response;
|
|
1823
|
+
|
|
1824
|
+
Salla.event.dispatch('payments::apple-pay.coupon.failed', response);
|
|
1825
|
+
|
|
1826
|
+
// SallaApplePay.abortSession();
|
|
1827
|
+
if (typeof SallaApplePay.detail.onCouponCodeChanged.onFailed === 'function') {
|
|
1828
|
+
SallaApplePay.detail.onCouponCodeChanged.onFailed(response);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
await SallaApplePay.recalculateTotal();
|
|
1832
|
+
|
|
1833
|
+
SallaApplePay.session.completeCouponCodeChange({
|
|
1834
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
1835
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
1836
|
+
errors: [new window.ApplePayError('couponCodeInvalid')]
|
|
1837
|
+
});
|
|
1838
|
+
});
|
|
1839
|
+
},
|
|
1840
|
+
|
|
1841
|
+
onCancel: (event = {}, message = null) => {
|
|
1842
|
+
SallaApplePay.detail.onError(message || salla.lang.get('pages.checkout.payment_failed'));
|
|
1843
|
+
Salla.event.createAndDispatch('payments::apple-pay.canceled', event);
|
|
1844
|
+
},
|
|
1845
|
+
|
|
1846
|
+
/**
|
|
1847
|
+
* Confirm payment after authorization.
|
|
1848
|
+
*
|
|
1849
|
+
* @param event
|
|
1850
|
+
*/
|
|
1851
|
+
onPaymentAuthorized: async (event) => {
|
|
1852
|
+
salla.logger.log('🍏 Pay: onPaymentAuthorized', event.payment);
|
|
1853
|
+
|
|
1854
|
+
// update guest details
|
|
1855
|
+
try {
|
|
1856
|
+
await mutateShippingContact(SallaApplePay, event.payment.shippingContact, true);
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
salla.logger.error('🍏 Pay: Failed to update guest contact details', error);
|
|
1859
|
+
|
|
1860
|
+
const response = error?.response || error;
|
|
1861
|
+
|
|
1862
|
+
// Parse backend errors for shipping contact fields
|
|
1863
|
+
const fields = response?.data?.error?.fields || {};
|
|
1864
|
+
const errors = [];
|
|
1865
|
+
|
|
1866
|
+
// Map backend field errors to Apple Pay contact field errors
|
|
1867
|
+
if (fields?.email && fields.email.length > 0) {
|
|
1868
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'emailAddress', fields.email[0]));
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (fields?.phone && fields.phone.length > 0) {
|
|
1872
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'phoneNumber', fields.phone[0]));
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
if (fields?.first_name && fields.first_name.length > 0) {
|
|
1876
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'name', fields.first_name[0]));
|
|
1877
|
+
} else if (fields?.last_name && fields.last_name.length > 0) {
|
|
1878
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'name', fields.last_name[0]));
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// If no specific field errors, use general error message
|
|
1882
|
+
if (errors.length === 0) {
|
|
1883
|
+
const errorMessage = response?.data?.error?.message || response?.data?.error?.code || 'Invalid shipping contact details';
|
|
1884
|
+
errors.push(new window.ApplePayError('shippingContactInvalid', 'name', errorMessage));
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
Salla.event.dispatch('payments::apple-pay.authorized.failed', response);
|
|
1888
|
+
|
|
1889
|
+
if (typeof SallaApplePay.detail.authorized.onFailed === 'function') {
|
|
1890
|
+
SallaApplePay.detail.authorized.onFailed(response);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// Complete shipping contact selection with invalid contact status
|
|
1894
|
+
SallaApplePay.session.completePayment({
|
|
1895
|
+
status: ApplePaySession.STATUS_INVALID_SHIPPING_CONTACT,
|
|
1896
|
+
errors: errors
|
|
1897
|
+
});
|
|
1898
|
+
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
Salla.event.dispatch('payments::apple-pay.authorized.init', event);
|
|
1903
|
+
http.post(SallaApplePay.detail.authorized.url.replace('{id}', SallaApplePay.id), {
|
|
1904
|
+
payment_method: 'apple_pay',
|
|
1905
|
+
applepay_token: JSON.stringify(event.payment)
|
|
1906
|
+
}, ({ data }) => {
|
|
1907
|
+
Salla.event.dispatch('payments::apple-pay.authorized.success', data);
|
|
1908
|
+
|
|
1909
|
+
if (typeof SallaApplePay.detail.authorized.onSuccess === 'function') {
|
|
1910
|
+
SallaApplePay.detail.authorized.onSuccess(data);
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
SallaApplePay.session.completePayment(ApplePaySession.STATUS_SUCCESS);
|
|
1914
|
+
}, (error) => {
|
|
1915
|
+
|
|
1916
|
+
let response = error?.response;
|
|
1917
|
+
|
|
1918
|
+
Salla.event.dispatch('payments::apple-pay.authorized.failed', response);
|
|
1919
|
+
|
|
1920
|
+
if (typeof SallaApplePay.detail.authorized.onFailed === 'function') {
|
|
1921
|
+
SallaApplePay.detail.authorized.onFailed(response);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
SallaApplePay.session.completePayment({
|
|
1925
|
+
status: ApplePaySession.STATUS_FAILURE,
|
|
1926
|
+
errors: [new window.ApplePayError("unknown", undefined, response?.data?.error?.message || response?.data?.error?.code || 'Failed to parse authorized response')]
|
|
1927
|
+
});
|
|
1928
|
+
});
|
|
1929
|
+
},
|
|
1930
|
+
|
|
1931
|
+
/**
|
|
1932
|
+
* Validate Submit.
|
|
1933
|
+
*
|
|
1934
|
+
* @param event
|
|
1935
|
+
*/
|
|
1936
|
+
onValidateMerchant: async (event) => {
|
|
1937
|
+
try {
|
|
1938
|
+
// Dispatch event to initialize Apple Pay merchant validation
|
|
1939
|
+
Salla.event.dispatch('payments::apple-pay.validate-merchant.init', event);
|
|
1940
|
+
|
|
1941
|
+
// Post request to validate merchant
|
|
1942
|
+
const { data } = await http.post(SallaApplePay.detail.validateMerchant.url.replace('{id}', SallaApplePay.id), {
|
|
1943
|
+
validation_url: event.validationURL
|
|
1944
|
+
});
|
|
1945
|
+
|
|
1946
|
+
// Dispatch event on successful merchant validation
|
|
1947
|
+
Salla.event.dispatch('payments::apple-pay.validate-merchant.success', data);
|
|
1948
|
+
|
|
1949
|
+
// Define a function to handle the completion of merchant validation
|
|
1950
|
+
const completeMerchantValidation = (responseData) => {
|
|
1951
|
+
SallaApplePay.session.completeMerchantValidation(responseData);
|
|
1952
|
+
};
|
|
1953
|
+
|
|
1954
|
+
// Check if onSuccess function is defined in SallaApplePay.detail.validateMerchant
|
|
1955
|
+
if (typeof SallaApplePay.detail.validateMerchant.onSuccess === 'function') {
|
|
1956
|
+
// Call onSuccess function and handle response
|
|
1957
|
+
const response = await SallaApplePay.detail.validateMerchant.onSuccess(data);
|
|
1958
|
+
if (response?.redirect) {
|
|
1959
|
+
// Handle redirect if present
|
|
1960
|
+
window.location = response.redirect;
|
|
1961
|
+
return SallaApplePay.abortValidateMerchant(response);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
completeMerchantValidation(data.data);
|
|
1965
|
+
} catch (error) {
|
|
1966
|
+
// Handle errors
|
|
1967
|
+
console.error(error);
|
|
1968
|
+
SallaApplePay.abortValidateMerchant(error?.response);
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
|
|
1972
|
+
abortValidateMerchant: (response = null) => {
|
|
1973
|
+
|
|
1974
|
+
SallaApplePay.abortSession();
|
|
1975
|
+
Salla.event.dispatch('payments::apple-pay.validate-merchant.failed', response);
|
|
1976
|
+
|
|
1977
|
+
if (typeof SallaApplePay.detail.validateMerchant.onFailed === 'function') {
|
|
1978
|
+
SallaApplePay.detail.validateMerchant.onFailed(response);
|
|
1979
|
+
}
|
|
1980
|
+
},
|
|
1981
|
+
|
|
1982
|
+
/**
|
|
1983
|
+
* Select Shipping Contact
|
|
1984
|
+
*
|
|
1985
|
+
* @param event
|
|
1986
|
+
*/
|
|
1987
|
+
onShippingContactSelected: async (event) => {
|
|
1988
|
+
salla.logger.log('🍏 Pay: onShippingContactSelected', event.shippingContact);
|
|
1989
|
+
|
|
1990
|
+
// create address for shipping calculation
|
|
1991
|
+
await mutateShippingContact(SallaApplePay, event.shippingContact);
|
|
1992
|
+
},
|
|
1993
|
+
|
|
1994
|
+
/**
|
|
1995
|
+
* Select Shipping Method
|
|
1996
|
+
*
|
|
1997
|
+
* @param event
|
|
1998
|
+
*
|
|
1999
|
+
*/
|
|
2000
|
+
onShippingMethodSelected: async (event) => {
|
|
2001
|
+
|
|
2002
|
+
let shipping_ids = event.shippingMethod.identifier.split(',');
|
|
2003
|
+
let shippingMethod = {
|
|
2004
|
+
ship_id: shipping_ids[0],
|
|
2005
|
+
private_ship_id: typeof shipping_ids[1] === 'undefined' ? null : shipping_ids[1],
|
|
2006
|
+
type: typeof shipping_ids[2] === 'undefined' ? null : shipping_ids[2],
|
|
2007
|
+
route_id: typeof shipping_ids[3] === 'undefined' ? null : shipping_ids[3],
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
salla.logger.log('🍏 Pay: onShippingMethodSelected', {
|
|
2011
|
+
event,
|
|
2012
|
+
previous: SallaApplePay.shippingCompany,
|
|
2013
|
+
current: shippingMethod,
|
|
2014
|
+
});
|
|
2015
|
+
|
|
2016
|
+
if (SallaApplePay.shouldUpdateShippingCompany(shippingMethod)) {
|
|
2017
|
+
try {
|
|
2018
|
+
|
|
2019
|
+
await SallaApplePay.selectApplePayShippingMethod(shippingMethod);
|
|
2020
|
+
|
|
2021
|
+
await SallaApplePay.recalculateTotal();
|
|
2022
|
+
|
|
2023
|
+
} catch (error) {
|
|
2024
|
+
salla.logger.warn('🍏 Pay: Failed set the shipping details to api', error);
|
|
2025
|
+
|
|
2026
|
+
// todo :: find a better handling for error without abort session
|
|
2027
|
+
SallaApplePay.session.completeShippingMethodSelection({
|
|
2028
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
2029
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
2030
|
+
status: SallaApplePay.session.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS,
|
|
2031
|
+
errors: [
|
|
2032
|
+
new window.ApplePayError('addressUnserviceable')
|
|
2033
|
+
]
|
|
2034
|
+
});
|
|
2035
|
+
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
salla.logger.log('🍏 Pay: completeShippingMethodSelection', {
|
|
2041
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
2042
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
SallaApplePay.session.completeShippingMethodSelection({
|
|
2046
|
+
newTotal: SallaApplePay.prepareTotal(),
|
|
2047
|
+
newLineItems: SallaApplePay.prepareLineItems(),
|
|
2048
|
+
});
|
|
2049
|
+
},
|
|
2050
|
+
|
|
2051
|
+
|
|
2052
|
+
abortSession: () => {
|
|
2053
|
+
if (SallaApplePay.session) {
|
|
2054
|
+
SallaApplePay.session.abort();
|
|
2055
|
+
}
|
|
2056
|
+
},
|
|
2057
|
+
|
|
2058
|
+
shouldUpdateShippingCompany(shippingCompany) {
|
|
2059
|
+
return SallaApplePay.shippingCompany?.ship_id != shippingCompany?.ship_id || SallaApplePay.shippingCompany?.private_ship_id != shippingCompany?.private_ship_id
|
|
2060
|
+
},
|
|
2061
|
+
|
|
2062
|
+
getApplePaySessionVersion: () => {
|
|
2063
|
+
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
2064
|
+
|
|
2065
|
+
if (userAgent === 'sallapp') {
|
|
2066
|
+
return 5;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
// can't handle custom user agent like sallapp
|
|
2070
|
+
let detection = DetectOS.init();
|
|
2071
|
+
let v = parseFloat(detection.os.version);
|
|
2072
|
+
|
|
2073
|
+
if (detection.os.name === 'Macintosh') {
|
|
2074
|
+
if (v < 10.142) {
|
|
2075
|
+
return 1;
|
|
2076
|
+
}
|
|
2077
|
+
} else {
|
|
2078
|
+
if (v < 12.11) {
|
|
2079
|
+
return 1;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
return 5;
|
|
2084
|
+
},
|
|
2085
|
+
|
|
2086
|
+
recalculateTotal: () => {
|
|
2087
|
+
salla.logger.log('🍏 Pay: recalculate total');
|
|
2088
|
+
|
|
2089
|
+
return http.get(SallaApplePay.detail.recalculateTotal.url.replace('{id}', SallaApplePay.id), ({ data }) => {
|
|
2090
|
+
salla.logger.log('🍏 Pay: recalculate total success', data);
|
|
2091
|
+
let cart = data.data.initial_data?.cart || data.data.cart;
|
|
2092
|
+
let payments = data.data.initial_data?.payments || data.data.payments;
|
|
2093
|
+
|
|
2094
|
+
salla.logger.log('🍏 Pay: recalculate total success', {
|
|
2095
|
+
cart,
|
|
2096
|
+
payments
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
// todo :: enhance response from backend
|
|
2100
|
+
SallaApplePay.detail.amount = payments?.gateways?.applePay?.supportedMultiCurrency ? cart.total_in_customer_currency : cart.total;
|
|
2101
|
+
SallaApplePay.detail.items = (cart.totals || cart.items).map((item) => {
|
|
2102
|
+
return {
|
|
2103
|
+
label: item.title,
|
|
2104
|
+
amount: (item.amount === 'مجاني' || item.amount === 'Free') ? 0 : item.amount.toString().replace('+', ''),
|
|
2105
|
+
};
|
|
2106
|
+
});
|
|
2107
|
+
|
|
2108
|
+
// lets remove last element (final total)
|
|
2109
|
+
SallaApplePay.detail.items.pop();
|
|
2110
|
+
SallaApplePay.totals = SallaApplePay.detail.items;
|
|
2111
|
+
|
|
2112
|
+
}, (error) => {
|
|
2113
|
+
salla.logger.warn('🍏 Pay: recalculate total failed', error);
|
|
2114
|
+
|
|
2115
|
+
// general error
|
|
2116
|
+
return error?.response?.data?.message;
|
|
2117
|
+
});
|
|
2118
|
+
},
|
|
2119
|
+
|
|
2120
|
+
|
|
2121
|
+
selectApplePayShippingMethod: (shippingMethod) => {
|
|
2122
|
+
salla.logger.log('🍏 Pay: select shipping method ', shippingMethod);
|
|
2123
|
+
SallaApplePay.shippingCompany = shippingMethod;
|
|
2124
|
+
|
|
2125
|
+
const payload = {
|
|
2126
|
+
address_id: SallaApplePay.address_id,
|
|
2127
|
+
company_id: shippingMethod.ship_id,
|
|
2128
|
+
private_company_id: shippingMethod.private_ship_id,
|
|
2129
|
+
type: shippingMethod.type || undefined,
|
|
2130
|
+
route_id: shippingMethod.route_id || undefined,
|
|
2131
|
+
payment_method: 'apple_pay'
|
|
2132
|
+
};
|
|
2133
|
+
return http.post(SallaApplePay.detail.shippingMethodSelected.url.replace('{id}', SallaApplePay.id), payload, ({ data }) => {
|
|
2134
|
+
if (typeof SallaApplePay.detail.shippingMethodSelected.onSuccess === 'function') {
|
|
2135
|
+
SallaApplePay.detail.shippingMethodSelected.onSuccess(data);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// we don't have any data in this request, lets resolve the promise
|
|
2139
|
+
return true;
|
|
2140
|
+
}, (error) => {
|
|
2141
|
+
salla.logger.warn('🍏 Pay: Set shipping method failed', error);
|
|
2142
|
+
|
|
2143
|
+
if (typeof SallaApplePay.detail.shippingMethodSelected.onFailed === 'function') {
|
|
2144
|
+
SallaApplePay.detail.shippingMethodSelected.onFailed(error);
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
// parse 422 errors
|
|
2148
|
+
let response = error?.response?.data?.error;
|
|
2149
|
+
|
|
2150
|
+
// address id is not valid
|
|
2151
|
+
if (response?.data?.fields?.address_id) {
|
|
2152
|
+
return response?.data?.fields?.address_id[0];
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// general error
|
|
2156
|
+
return response?.data?.message;
|
|
2157
|
+
});
|
|
2158
|
+
},
|
|
2159
|
+
mappingShippingMethods: methods => methods.map(method => ({
|
|
2160
|
+
'label': method.shipping_title,
|
|
2161
|
+
'amount': method.enable_free_shipping ? 0 : method.ship_cost,
|
|
2162
|
+
'detail': '',
|
|
2163
|
+
'identifier': method.ship_id.toString() + (method.private_ship_id ? ',' + method.private_ship_id.toString() : '') + (method.type ? ',' + method.type : '') + (method.route_id ? ',' + method.route_id : '')
|
|
2164
|
+
}))
|
|
2165
|
+
};
|
|
2166
|
+
|
|
2167
|
+
//applePay doesn't allow iframes
|
|
2168
|
+
// if (window.ApplePaySession && window.self === window.top && ApplePaySession.canMakePayments()) {
|
|
2169
|
+
if (window.ApplePaySession?.canMakePayments()) {
|
|
2170
|
+
SallaApplePay.init();
|
|
2171
|
+
} else {
|
|
2172
|
+
// You can hide the Apple Pay button easy with add data-show-if-apple-pay-supported to element like <div data-show-if-apple-pay-supported>
|
|
2173
|
+
document.querySelectorAll('data-show-if-apple-pay-supported').forEach(element => element.style.display = 'none');
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
const sallaQuickBuyCss = ".s-quick-buy-button .s-button-text{display:flex}apple-pay-button{--apple-pay-button-width:100%;--apple-pay-button-height:40px;--apple-pay-button-border-radius:.75rem;--apple-pay-button-box-sizing:content-box}";
|
|
2177
|
+
|
|
2178
|
+
const SallaQuickBuy = class {
|
|
2179
|
+
constructor(hostRef) {
|
|
2180
|
+
index.registerInstance(this, hostRef);
|
|
2181
|
+
this.validationFailed = index.createEvent(this, "validationFailed");
|
|
2182
|
+
this.requireLogin = index.createEvent(this, "requireLogin");
|
|
2183
|
+
/**
|
|
2184
|
+
* Button type.
|
|
2185
|
+
*
|
|
2186
|
+
* @type {string}
|
|
2187
|
+
* @default buy
|
|
2188
|
+
**/
|
|
2189
|
+
this.type = 'buy';
|
|
2190
|
+
/**
|
|
2191
|
+
* Product options, if is empty will get the data from the document.querySelector('salla-product-options[product-id="X"]')
|
|
2192
|
+
*
|
|
2193
|
+
* @type {object}
|
|
2194
|
+
* @default {}
|
|
2195
|
+
*/
|
|
2196
|
+
this.options = {};
|
|
2197
|
+
this.quickBuy = salla.lang.get('pages.products.buy_now');
|
|
2198
|
+
salla.lang.onLoaded(() => {
|
|
2199
|
+
this.quickBuy = salla.lang.get('pages.products.buy_now');
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
async quickBuyHandler() {
|
|
2203
|
+
// user guest and guest-checkout not enabled
|
|
2204
|
+
if (salla.config.isGuest() && !this.isGuestCheckout()) {
|
|
2205
|
+
this.requireLogin.emit({ productId: this.productId });
|
|
2206
|
+
// todo (low) :: find a way to re-fire the method after success
|
|
2207
|
+
let afterLoginEvent = "salla-quick-buy::user.logged-in";
|
|
2208
|
+
salla.event.on(afterLoginEvent, () => this.settlePayment());
|
|
2209
|
+
salla.api.auth.setAfterLoginEvent(afterLoginEvent);
|
|
2210
|
+
return salla.auth.event.dispatch('login::open', { withoutReload: true });
|
|
2211
|
+
}
|
|
2212
|
+
await this.settlePayment();
|
|
2213
|
+
}
|
|
2214
|
+
async settlePayment() {
|
|
2215
|
+
let optionsElement = document.querySelector(`salla-product-options[product-id="${this.productId}"]`);
|
|
2216
|
+
//make sure all the required options are selected
|
|
2217
|
+
if (optionsElement && !await optionsElement.reportValidity()) {
|
|
2218
|
+
this.validationFailed.emit({ productId: this.productId });
|
|
2219
|
+
return salla.error(salla.lang.get('common.messages.required_fields'));
|
|
2220
|
+
}
|
|
2221
|
+
//use this way to get quantity too
|
|
2222
|
+
let data = this.host.getElementSallaData();
|
|
2223
|
+
// if the store doesn't have Apple Pay , just create a cart and then redirect to check out page
|
|
2224
|
+
if (!this.isApplePayActive) {
|
|
2225
|
+
// return salla.product.buyNow(this.productId, data);
|
|
2226
|
+
return salla.api.request('checkout/quick-purchase/' + this.productId, data, 'post')
|
|
2227
|
+
.then(resp => {
|
|
2228
|
+
if (resp.data.redirect) {
|
|
2229
|
+
window.location.href = resp.data.redirect;
|
|
2230
|
+
}
|
|
2231
|
+
return resp;
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
data.is_applepay = true;
|
|
2235
|
+
if ('append' in data) {
|
|
2236
|
+
data.append('is_applepay', true);
|
|
2237
|
+
}
|
|
2238
|
+
// noinspection TypeScriptValidateJSTypes
|
|
2239
|
+
salla.event.dispatch('payments::apple-pay.start-transaction', {
|
|
2240
|
+
amount: this.amount, // 1000
|
|
2241
|
+
currency: this.currency || 'SAR', // SAR
|
|
2242
|
+
requiredShippingContactFields: this.getRequiredShippingContactFields(),
|
|
2243
|
+
shippingMethods: this.isRequireShipping ? [] : undefined,
|
|
2244
|
+
supportedNetworks: salla.config.get('store.settings.buy_now.networks'),
|
|
2245
|
+
supportedCountries: salla.config.get('store.settings.buy_now.countries'),
|
|
2246
|
+
countryCode: salla.config.get('store.store_country') || 'SA',
|
|
2247
|
+
validateMerchant: {
|
|
2248
|
+
url: salla.url.get('checkout/applepay/validate'),
|
|
2249
|
+
onSuccess: (response) => {
|
|
2250
|
+
if (this.applePayOnly && !this.productId) { // the cart is not passes
|
|
2251
|
+
if (!this.cartId) {
|
|
2252
|
+
salla.logger.warn('🍏 Pay: trying to create applePay transaction without cartId/ProductId !');
|
|
2253
|
+
return Promise.resolve(response);
|
|
2254
|
+
}
|
|
2255
|
+
window.SallaApplePay.id = this.cartId;
|
|
2256
|
+
salla.log('🍏 Pay: create checkout success: with id #' + this.cartId);
|
|
2257
|
+
return Promise.resolve(response);
|
|
2258
|
+
}
|
|
2259
|
+
return salla.api.request('checkout/quick-purchase/' + this.productId, typeof data == 'object' ? data : undefined, 'post', {}).then(response => {
|
|
2260
|
+
// if is redirect url returned for any reason, lets redirect the user to check out
|
|
2261
|
+
if (response?.data?.redirect) {
|
|
2262
|
+
salla.log('🍏 Pay: create checkout success: redirect exits, go to checkout page');
|
|
2263
|
+
window.location.href = response.data.redirect.url;
|
|
2264
|
+
return response;
|
|
2265
|
+
}
|
|
2266
|
+
// the cart is not ready to complete apply pay session
|
|
2267
|
+
if (!response?.data?.id) {
|
|
2268
|
+
salla.logger.warn('🍏 Pay: create checkout success: No id, or redirect');
|
|
2269
|
+
return response;
|
|
2270
|
+
}
|
|
2271
|
+
window.SallaApplePay.id = response.data.id;
|
|
2272
|
+
salla.log('🍏 Pay: create checkout success: with id #' + window.SallaApplePay.id);
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
},
|
|
2276
|
+
authorized: {
|
|
2277
|
+
// submit checkout route
|
|
2278
|
+
url: salla.url.get('checkout/{id}/payments/submit'),
|
|
2279
|
+
onFailed: (response) => {
|
|
2280
|
+
window.SallaApplePay.onCancel({}, response?.data?.error?.message || response?.data?.error?.code || salla.lang.get('pages.checkout.payment_failed'));
|
|
2281
|
+
},
|
|
2282
|
+
onSuccess: (response) => {
|
|
2283
|
+
window.location.href = response.redirect.url;
|
|
2284
|
+
salla.log('🍏 Pay: authorized Success:: redirect to thank you page, order placed');
|
|
2285
|
+
}
|
|
2286
|
+
},
|
|
2287
|
+
shippingMethodSelected: this.isRequireShipping ? {
|
|
2288
|
+
url: salla.url.get('checkout/{id}/shipping/details'),
|
|
2289
|
+
} : undefined,
|
|
2290
|
+
shippingContactSelected: this.isRequireShipping ? {
|
|
2291
|
+
url: salla.url.get('checkout/{id}/address/add'),
|
|
2292
|
+
} : undefined,
|
|
2293
|
+
guestContactSelected: this.isGuestCheckout() ? {
|
|
2294
|
+
url: salla.url.get('checkout/{id}/customer'),
|
|
2295
|
+
} : undefined,
|
|
2296
|
+
onCouponCodeChanged: {
|
|
2297
|
+
url: salla.url.get('checkout/{id}/coupons')
|
|
2298
|
+
},
|
|
2299
|
+
recalculateTotal: {
|
|
2300
|
+
url: salla.url.get('checkout/{id}/payments/recalculate?payment_method=apple_pay')
|
|
2301
|
+
},
|
|
2302
|
+
onError: function (message) {
|
|
2303
|
+
salla.log(message);
|
|
2304
|
+
salla.notify.error(message);
|
|
2305
|
+
}
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
isGuestCheckout() {
|
|
2309
|
+
return salla.config.isGuest() && salla.config.get('store.features').includes('guest-checkout');
|
|
2310
|
+
}
|
|
2311
|
+
getRequiredShippingContactFields() {
|
|
2312
|
+
let fields = [];
|
|
2313
|
+
if (this.isRequireShipping) {
|
|
2314
|
+
fields.push('postalAddress');
|
|
2315
|
+
}
|
|
2316
|
+
if (this.isGuestCheckout()) {
|
|
2317
|
+
fields.push('email', 'phone', 'name');
|
|
2318
|
+
}
|
|
2319
|
+
return fields;
|
|
2320
|
+
}
|
|
2321
|
+
componentWillLoad() {
|
|
2322
|
+
console.log('🍏 Pay: Quick Buy Component Loaded');
|
|
2323
|
+
const canMakePayments = typeof window !== 'undefined' && !!window.ApplePaySession?.canMakePayments?.();
|
|
2324
|
+
const storeHasApplePay = salla?.config?.get?.('store.settings.payments')?.includes?.('apple_pay') ?? true;
|
|
2325
|
+
const isSallaGateway = salla?.config?.get?.('store.settings.is_salla_gateway', false) ?? true;
|
|
2326
|
+
this.isApplePayActive = canMakePayments && storeHasApplePay && isSallaGateway;
|
|
2327
|
+
const runInit = async () => {
|
|
2328
|
+
if (!this.currency && salla?.config?.get) {
|
|
2329
|
+
this.currency = salla.config.get('store.settings.buy_now.multi_currency') ? salla.config.get('user.currency_code') : 'SAR';
|
|
2330
|
+
}
|
|
2331
|
+
if (!this.productId && salla?.config?.get && salla.url?.is_page) {
|
|
2332
|
+
this.productId = salla.config.get('page.id');
|
|
2333
|
+
}
|
|
2334
|
+
if (!this.applePayOnly && !this.productId) {
|
|
2335
|
+
salla?.logger?.warn?.('🍏 Pay: Failed load the quick buy, the product id is missing');
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
if ((!this.amount || !this.isRequireShipping) && this.productId && salla?.product?.getDetails) {
|
|
2339
|
+
await salla.product.getDetails(this.productId, []).then((response) => {
|
|
2340
|
+
this.amount = response.data.price;
|
|
2341
|
+
this.isRequireShipping = response?.data?.is_require_shipping || false;
|
|
2342
|
+
}).catch((error) => {
|
|
2343
|
+
salla?.logger?.warn?.('🍏 Pay: Failed load the quick buy, get the product details failed: ', error);
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
if (this.type === 'donate' && salla?.event?.on) {
|
|
2347
|
+
salla.event.on('product-options::donation-changed', (data) => {
|
|
2348
|
+
if (String(data.id) !== String(this.productId))
|
|
2349
|
+
return;
|
|
2350
|
+
this.amount = data.price;
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
else if (salla?.url?.is_page?.('product.single') && salla?.product?.event?.onPriceUpdated) {
|
|
2354
|
+
salla.product.event.onPriceUpdated(response => { this.amount = response.data.price; });
|
|
2355
|
+
}
|
|
2356
|
+
this.isApplePayActive = (salla?.helpers?.hasApplePay?.() ?? canMakePayments)
|
|
2357
|
+
&& (salla?.config?.get?.('store.settings.payments')?.includes?.('apple_pay') ?? storeHasApplePay)
|
|
2358
|
+
&& (salla?.config?.get?.('store.settings.is_salla_gateway', false) ?? isSallaGateway);
|
|
2359
|
+
if (!document.getElementById('apple-pay-sdk') && this.isApplePayActive) {
|
|
2360
|
+
const script = document.createElement('script');
|
|
2361
|
+
script.src = 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js';
|
|
2362
|
+
script.setAttribute('id', 'apple-pay-sdk');
|
|
2363
|
+
script.async = true;
|
|
2364
|
+
document.body.appendChild(script);
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
return new Promise((resolve) => {
|
|
2368
|
+
if (typeof salla?.onReady === 'function') {
|
|
2369
|
+
salla.onReady(() => runInit().then(() => resolve(true)));
|
|
2370
|
+
}
|
|
2371
|
+
else {
|
|
2372
|
+
runInit().then(() => resolve(true));
|
|
2373
|
+
}
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
render() {
|
|
2377
|
+
return index.h(index.Host, { key: '9a36fa6b7163dae6feed474712e3d0c3003d012c' }, this.quickBuyButton());
|
|
2378
|
+
}
|
|
2379
|
+
quickBuyButton() {
|
|
2380
|
+
return index.h("apple-pay-button", { locale: salla?.config?.get?.('user.language_code') || 'ar', onClick: () => this.quickBuyHandler(), "data-quick-purchase": "applepay", class: "s-quick-buy-apple-pay", "data-is-applepay": "1", buttonstyle: "black", type: this.type });
|
|
2381
|
+
}
|
|
2382
|
+
get host() { return index.getElement(this); }
|
|
2383
|
+
};
|
|
2384
|
+
SallaQuickBuy.style = sallaQuickBuyCss;
|
|
2385
|
+
|
|
2386
|
+
exports.salla_add_product_button = SallaAddProductButton;
|
|
2387
|
+
exports.salla_product_availability = SallaProductAvailability;
|
|
2388
|
+
exports.salla_product_options = SallaProductOptions;
|
|
2389
|
+
exports.salla_quick_buy = SallaQuickBuy;
|