@alphasquad/saleor-template-advance 0.1.0
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/.env.example +57 -0
- package/APPLE_PAY_QUICK_START.md +165 -0
- package/APPLE_PAY_SETUP.md +331 -0
- package/README.md +46 -0
- package/SEO_AUDIT_CHECKLIST_STATUS.md +244 -0
- package/SEO_AUDIT_REPORT.md +66 -0
- package/eslint.config.mjs +16 -0
- package/next-env.d.ts +5 -0
- package/next.config.ts +109 -0
- package/package.json +47 -0
- package/postcss.config.mjs +5 -0
- package/public/.well-known/apple-developer-merchantid-domain-association +1 -0
- package/public/Logo.png +0 -0
- package/public/brand-video.mp4 +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/footer/facebook.tsx +34 -0
- package/public/footer/instagram.tsx +27 -0
- package/public/footer/mail.tsx +5 -0
- package/public/footer/x.tsx +35 -0
- package/public/globe.svg +1 -0
- package/public/icons/Authorize.net.webp +0 -0
- package/public/icons/amex.gif +0 -0
- package/public/icons/appIcon.png +0 -0
- package/public/icons/discover.gif +0 -0
- package/public/icons/master.gif +0 -0
- package/public/icons/paypal.png +0 -0
- package/public/icons/stripe.png +0 -0
- package/public/icons/visa.gif +0 -0
- package/public/images/BackgroundNoise.png +0 -0
- package/public/images/footer-background.png +0 -0
- package/public/next.svg +1 -0
- package/public/no-image-avail-large.png +0 -0
- package/public/random-car-1.jpeg +0 -0
- package/public/random-car-2.png +0 -0
- package/public/random-car-3.jpg +0 -0
- package/public/random-car-4.jpg +0 -0
- package/public/random-car-5.jpg +0 -0
- package/public/star.svg +3 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/seo-audit/generate-checklist.mjs +156 -0
- package/src/app/(auth)/account/forgot-password/layout.tsx +16 -0
- package/src/app/(auth)/account/forgot-password/page.tsx +135 -0
- package/src/app/(auth)/account/login/layout.tsx +16 -0
- package/src/app/(auth)/account/login/page.tsx +288 -0
- package/src/app/(auth)/account/otp/layout.tsx +16 -0
- package/src/app/(auth)/account/otp/page.tsx +108 -0
- package/src/app/(auth)/account/register/layout.tsx +16 -0
- package/src/app/(auth)/account/register/page.tsx +431 -0
- package/src/app/(auth)/account/reset-password/layout.tsx +16 -0
- package/src/app/(auth)/account/reset-password/page.tsx +222 -0
- package/src/app/[slug]/page.tsx +43 -0
- package/src/app/about/loading.tsx +17 -0
- package/src/app/about/page.tsx +61 -0
- package/src/app/account/address/layout.tsx +15 -0
- package/src/app/account/address/page.tsx +166 -0
- package/src/app/account/head.tsx +4 -0
- package/src/app/account/layout.tsx +62 -0
- package/src/app/account/orders/[id]/layout.tsx +17 -0
- package/src/app/account/orders/[id]/page.tsx +115 -0
- package/src/app/account/orders/components/orderDetailsModal.tsx +410 -0
- package/src/app/account/orders/layout.tsx +15 -0
- package/src/app/account/orders/page.tsx +146 -0
- package/src/app/account/page.tsx +39 -0
- package/src/app/account/settings/components/editProfileSuccessModal.tsx +28 -0
- package/src/app/account/settings/layout.tsx +15 -0
- package/src/app/account/settings/page.tsx +260 -0
- package/src/app/api/affirm/check-status/route.ts +94 -0
- package/src/app/api/affirm/create-checkout/route.ts +109 -0
- package/src/app/api/affirm/get-config/route.ts +108 -0
- package/src/app/api/affirm/process-payment/route.ts +244 -0
- package/src/app/api/affirm/test-connection/route.ts +45 -0
- package/src/app/api/auth/clear/route.ts +16 -0
- package/src/app/api/auth/clear-cookies/route.ts +42 -0
- package/src/app/api/auth/set/route.ts +47 -0
- package/src/app/api/configuration/route.ts +18 -0
- package/src/app/api/dynamic-page/[slug]/route.ts +24 -0
- package/src/app/api/form-submission/route.ts +237 -0
- package/src/app/api/paypal/capture-order/route.ts +303 -0
- package/src/app/api/paypal/create-order/route.ts +211 -0
- package/src/app/api/paypal/get-config/route.ts +240 -0
- package/src/app/api/search-proxy/route.ts +52 -0
- package/src/app/authorize-net-success/layout.tsx +19 -0
- package/src/app/authorize-net-success/page.tsx +12 -0
- package/src/app/authorize-net-success/summary.tsx +486 -0
- package/src/app/blog/[slug]/blogContentRenderer.tsx +369 -0
- package/src/app/blog/[slug]/layout.tsx +17 -0
- package/src/app/blog/[slug]/page.tsx +151 -0
- package/src/app/blog/constant.tsx +147 -0
- package/src/app/blog/layout.tsx +31 -0
- package/src/app/blog/page.tsx +81 -0
- package/src/app/brand/[id]/BrandPageClient.tsx +188 -0
- package/src/app/brand/[id]/layout.tsx +17 -0
- package/src/app/brand/[id]/page.tsx +176 -0
- package/src/app/brands/components/brandsListingClient.tsx +97 -0
- package/src/app/brands/layout.tsx +31 -0
- package/src/app/brands/page.tsx +40 -0
- package/src/app/cancellation-policy/page.tsx +53 -0
- package/src/app/cart/layout.tsx +19 -0
- package/src/app/cart/page.tsx +752 -0
- package/src/app/category/[slug]/CategoryPageClient.tsx +377 -0
- package/src/app/category/[slug]/layout.tsx +17 -0
- package/src/app/category/[slug]/page.tsx +224 -0
- package/src/app/category/page.tsx +114 -0
- package/src/app/checkout/components/addNewAddressModal.tsx +474 -0
- package/src/app/checkout/layout.tsx +19 -0
- package/src/app/checkout/page.tsx +3312 -0
- package/src/app/components/account/AccountTabs.tsx +40 -0
- package/src/app/components/ads/GoogleAdSense.tsx +74 -0
- package/src/app/components/analytics/AnalyticsScripts.tsx +78 -0
- package/src/app/components/analytics/ConditionalGTMNoscript.tsx +24 -0
- package/src/app/components/analytics/ConditionalGoogleAnalytics.tsx +16 -0
- package/src/app/components/ancillary/AncillaryContent.tsx +7 -0
- package/src/app/components/auth/TokenExpirationHandler.tsx +8 -0
- package/src/app/components/blog/BlogList.tsx +112 -0
- package/src/app/components/checkout/AddressInformationSection.tsx +34 -0
- package/src/app/components/checkout/AddressManagement.tsx +571 -0
- package/src/app/components/checkout/CheckoutHeader.tsx +51 -0
- package/src/app/components/checkout/CheckoutQuestions.tsx +454 -0
- package/src/app/components/checkout/CheckoutTermsModal.tsx +81 -0
- package/src/app/components/checkout/ContactDetailsSection.tsx +52 -0
- package/src/app/components/checkout/DealerShippingSection.tsx +359 -0
- package/src/app/components/checkout/DeliveryMethodSection.tsx +249 -0
- package/src/app/components/checkout/OrderSummary.tsx +386 -0
- package/src/app/components/checkout/TermsContentRenderer.tsx +147 -0
- package/src/app/components/checkout/WillCallSection.tsx +133 -0
- package/src/app/components/checkout/affirmPayment.tsx +383 -0
- package/src/app/components/checkout/checkoutProcessingModal.tsx +96 -0
- package/src/app/components/checkout/googlePayButton.tsx +334 -0
- package/src/app/components/checkout/paymentStep.tsx +180 -0
- package/src/app/components/checkout/paypalPayment.tsx +1083 -0
- package/src/app/components/checkout/saleorNativePayment.tsx +1758 -0
- package/src/app/components/dynamicPage/DynamicPageRenderer.tsx +13 -0
- package/src/app/components/dynamicPage/HtmlWidgetRenderer.tsx +144 -0
- package/src/app/components/filtersCollapsible/index.tsx +365 -0
- package/src/app/components/globalSearch/index.tsx +423 -0
- package/src/app/components/layout/cartDropDown.tsx +628 -0
- package/src/app/components/layout/components/FooterNewsletter.tsx +21 -0
- package/src/app/components/layout/footer.tsx +283 -0
- package/src/app/components/layout/header/accountMenuDropdown.tsx +53 -0
- package/src/app/components/layout/header/components/CartBadge.tsx +18 -0
- package/src/app/components/layout/header/components/LoadingState.tsx +17 -0
- package/src/app/components/layout/header/components/MenuItemDropdown.tsx +124 -0
- package/src/app/components/layout/header/components/MobileNavbar.tsx +123 -0
- package/src/app/components/layout/header/components/NavbarActions.tsx +125 -0
- package/src/app/components/layout/header/components/NavbarBrand.tsx +29 -0
- package/src/app/components/layout/header/components/NavigationLinks.tsx +131 -0
- package/src/app/components/layout/header/hamMenuSlide.tsx +318 -0
- package/src/app/components/layout/header/header.tsx +44 -0
- package/src/app/components/layout/header/hooks/useDropdown.ts +45 -0
- package/src/app/components/layout/header/hooks/useNavbarData.ts +138 -0
- package/src/app/components/layout/header/hooks/useNavbarState.ts +66 -0
- package/src/app/components/layout/header/megaMenuDropdown.tsx +116 -0
- package/src/app/components/layout/header/navBar.tsx +121 -0
- package/src/app/components/layout/header/search.tsx +418 -0
- package/src/app/components/layout/header/styles/navbarStyles.ts +27 -0
- package/src/app/components/layout/header/topBar.tsx +214 -0
- package/src/app/components/layout/joinNewsletterForm/index.tsx +72 -0
- package/src/app/components/layout/mobileAccordian/index.tsx +92 -0
- package/src/app/components/layout/paymentMethods.tsx +75 -0
- package/src/app/components/layout/rootLayout.tsx +23 -0
- package/src/app/components/layout/siteInfo.tsx +103 -0
- package/src/app/components/layout/socialLinks.tsx +65 -0
- package/src/app/components/newsletterSection/emailListSection.tsx +224 -0
- package/src/app/components/newsletterSection/emailSectionServer.tsx +8 -0
- package/src/app/components/providers/ApolloWrapper.tsx +12 -0
- package/src/app/components/providers/AppConfigurationProvider.tsx +108 -0
- package/src/app/components/providers/GoogleAnalyticsProvider.tsx +149 -0
- package/src/app/components/providers/GoogleTagManagerProvider.tsx +31 -0
- package/src/app/components/providers/RecaptchaProvider.tsx +18 -0
- package/src/app/components/providers/ServerAppConfigurationProvider.tsx +133 -0
- package/src/app/components/providers/YMMStatusProvider.tsx +15 -0
- package/src/app/components/reuseableUI/AboutUs.tsx +115 -0
- package/src/app/components/reuseableUI/AddToCartClient.tsx +125 -0
- package/src/app/components/reuseableUI/EditorJsRenderer.tsx +219 -0
- package/src/app/components/reuseableUI/HeroSectionsearchByVehicle.tsx +188 -0
- package/src/app/components/reuseableUI/ImageWithFallback.tsx +41 -0
- package/src/app/components/reuseableUI/Toast.tsx +101 -0
- package/src/app/components/reuseableUI/blogCard.tsx +52 -0
- package/src/app/components/reuseableUI/brandCard.tsx +68 -0
- package/src/app/components/reuseableUI/breadcrumb.tsx +38 -0
- package/src/app/components/reuseableUI/categoryCard.tsx +37 -0
- package/src/app/components/reuseableUI/categorySkeleton.tsx +31 -0
- package/src/app/components/reuseableUI/commonButton.tsx +48 -0
- package/src/app/components/reuseableUI/defaultInputField/index.tsx +84 -0
- package/src/app/components/reuseableUI/emptyState.tsx +29 -0
- package/src/app/components/reuseableUI/errorTag.tsx +15 -0
- package/src/app/components/reuseableUI/heading/index.tsx +20 -0
- package/src/app/components/reuseableUI/input.tsx +117 -0
- package/src/app/components/reuseableUI/listCard.tsx +137 -0
- package/src/app/components/reuseableUI/loadingUI.tsx +12 -0
- package/src/app/components/reuseableUI/modalLayout.tsx +76 -0
- package/src/app/components/reuseableUI/newsletter/newsletterClient.tsx +622 -0
- package/src/app/components/reuseableUI/newsletter/newslettersHomeModal.tsx +68 -0
- package/src/app/components/reuseableUI/offerCard.tsx +42 -0
- package/src/app/components/reuseableUI/passwordRules/passwordRules.tsx +56 -0
- package/src/app/components/reuseableUI/primaryButton/index.tsx +34 -0
- package/src/app/components/reuseableUI/productCard.tsx +118 -0
- package/src/app/components/reuseableUI/productSkeleton.tsx +34 -0
- package/src/app/components/reuseableUI/searchByVehicle.tsx +187 -0
- package/src/app/components/reuseableUI/secondaryButton/index.tsx +34 -0
- package/src/app/components/reuseableUI/section.tsx +20 -0
- package/src/app/components/reuseableUI/select/index.tsx +98 -0
- package/src/app/components/reuseableUI/skeletonLoader.tsx +117 -0
- package/src/app/components/reuseableUI/statusTag.tsx +24 -0
- package/src/app/components/reuseableUI/tags/saleTag.tsx +19 -0
- package/src/app/components/reuseableUI/testimonialCard.tsx +93 -0
- package/src/app/components/richText/EditorRenderer.tsx +318 -0
- package/src/app/components/search/HierarchicalCategoryFilter.tsx +155 -0
- package/src/app/components/search/SearchFilters.tsx +155 -0
- package/src/app/components/search/YMMSearchSidebar.tsx +187 -0
- package/src/app/components/seo/ServerProductCard.tsx +91 -0
- package/src/app/components/seo/ServerProductGrid.tsx +45 -0
- package/src/app/components/shop/CategoryFilter.tsx +184 -0
- package/src/app/components/shop/ItemsPerPageSelect.tsx +69 -0
- package/src/app/components/shop/ItemsPerPageSelectClient.tsx +58 -0
- package/src/app/components/shop/MobileFilters.tsx +103 -0
- package/src/app/components/shop/ProductGridSkeleton.tsx +16 -0
- package/src/app/components/shop/ProductsGrid.tsx +230 -0
- package/src/app/components/shop/SearchFilter.tsx +218 -0
- package/src/app/components/shop/SearchFilterClient.tsx +122 -0
- package/src/app/components/shop/SearchLoadingOverlay.tsx +32 -0
- package/src/app/components/shop/ShopMobileFilters.tsx +205 -0
- package/src/app/components/showroom/VehicleSearchDropdowns.tsx +187 -0
- package/src/app/components/showroom/brandsSwiper.tsx +49 -0
- package/src/app/components/showroom/brandsSwiperClient copy.tsx +93 -0
- package/src/app/components/showroom/brandsSwiperClient.tsx +122 -0
- package/src/app/components/showroom/brandsSwiperServer.tsx +42 -0
- package/src/app/components/showroom/bundleProducts.tsx +120 -0
- package/src/app/components/showroom/categoryGrid.tsx +51 -0
- package/src/app/components/showroom/categoryGridServer.tsx +45 -0
- package/src/app/components/showroom/categorySwiper.tsx +115 -0
- package/src/app/components/showroom/featureStrip.tsx +139 -0
- package/src/app/components/showroom/offersSwiper.tsx +181 -0
- package/src/app/components/showroom/productGrid.tsx +56 -0
- package/src/app/components/showroom/productSwiper.tsx +119 -0
- package/src/app/components/showroom/promotion-slider.tsx +138 -0
- package/src/app/components/showroom/promotion.tsx +207 -0
- package/src/app/components/showroom/promotionsSwiper.tsx +174 -0
- package/src/app/components/showroom/showroomHeroCarousel.tsx +141 -0
- package/src/app/components/showroom/testimonialsGrid.tsx +106 -0
- package/src/app/components/skeletons/ContentSkeleton.tsx +14 -0
- package/src/app/components/sortDropdown/index.tsx +116 -0
- package/src/app/components/tertiaryButton/index.tsx +25 -0
- package/src/app/components/theme/theme-provider.tsx +82 -0
- package/src/app/contact/layout.tsx +32 -0
- package/src/app/contact/page.tsx +591 -0
- package/src/app/content/[slug]/layout.tsx +17 -0
- package/src/app/content/[slug]/page.tsx +159 -0
- package/src/app/content/layout.tsx +31 -0
- package/src/app/content/page.tsx +88 -0
- package/src/app/core-policies/page.tsx +55 -0
- package/src/app/discounts/page.tsx +54 -0
- package/src/app/frequently-asked-questions/page.tsx +57 -0
- package/src/app/globals.css +440 -0
- package/src/app/hooks/useDealerLocations.ts +259 -0
- package/src/app/hooks/useGTMEngagement.ts +71 -0
- package/src/app/hooks/useGoogleAnalytics.ts +145 -0
- package/src/app/layout.tsx +149 -0
- package/src/app/not-found.tsx +31 -0
- package/src/app/order-confirmation/layout.tsx +19 -0
- package/src/app/order-confirmation/page.tsx +12 -0
- package/src/app/order-confirmation/summary.tsx +1775 -0
- package/src/app/page.tsx +194 -0
- package/src/app/privacy-policy/loading.tsx +17 -0
- package/src/app/privacy-policy/page.tsx +56 -0
- package/src/app/product/[id]/ProductDetailClient.tsx +2448 -0
- package/src/app/product/[id]/components/itemInquiryModal.tsx +461 -0
- package/src/app/product/[id]/layout.tsx +116 -0
- package/src/app/product/[id]/page.tsx +200 -0
- package/src/app/product/layout.tsx +15 -0
- package/src/app/products/all/AllProductsClient.tsx +743 -0
- package/src/app/products/all/page.tsx +176 -0
- package/src/app/products/components/shopEmptyState.tsx +29 -0
- package/src/app/request-return/layout.tsx +36 -0
- package/src/app/request-return/page.tsx +597 -0
- package/src/app/robots.txt/route.ts +27 -0
- package/src/app/search/layout.tsx +16 -0
- package/src/app/search/page.tsx +736 -0
- package/src/app/shipping-returns/page.tsx +60 -0
- package/src/app/site-map/layout.tsx +33 -0
- package/src/app/site-map/page.tsx +113 -0
- package/src/app/sitemap-index.xml/route.ts +20 -0
- package/src/app/sitemap.ts +10 -0
- package/src/app/terms-and-conditions/loading.tsx +17 -0
- package/src/app/terms-and-conditions/page.tsx +56 -0
- package/src/app/utils/appConfiguration.ts +327 -0
- package/src/app/utils/branding.ts +52 -0
- package/src/app/utils/configurationService.ts +202 -0
- package/src/app/utils/constant.tsx +242 -0
- package/src/app/utils/editorJsUtils.tsx +249 -0
- package/src/app/utils/functions.ts +146 -0
- package/src/app/utils/googleAnalytics.ts +168 -0
- package/src/app/utils/googleTagManager.ts +475 -0
- package/src/app/utils/ipDetection.ts +270 -0
- package/src/app/utils/serverConfigurationService.ts +209 -0
- package/src/app/utils/svgs/GridIcon.tsx +45 -0
- package/src/app/utils/svgs/account/myAccount/listDotIcon.tsx +3 -0
- package/src/app/utils/svgs/account/myAccount/tickIcon.tsx +10 -0
- package/src/app/utils/svgs/account/orderHistory/InfoIcon.tsx +49 -0
- package/src/app/utils/svgs/arrowDownIcon.tsx +17 -0
- package/src/app/utils/svgs/arrowIcon.tsx +25 -0
- package/src/app/utils/svgs/arrowUpIcon.tsx +16 -0
- package/src/app/utils/svgs/brandsSearchIcon.tsx +25 -0
- package/src/app/utils/svgs/cart/cartIcon.tsx +31 -0
- package/src/app/utils/svgs/cart/plusIcon.tsx +13 -0
- package/src/app/utils/svgs/cart/subtractIcon.tsx +13 -0
- package/src/app/utils/svgs/cart/successTickIcon.tsx +14 -0
- package/src/app/utils/svgs/chevronDownIcon.tsx +21 -0
- package/src/app/utils/svgs/closeEyeIcon.tsx +47 -0
- package/src/app/utils/svgs/crossIcon.tsx +25 -0
- package/src/app/utils/svgs/eyeIcon.tsx +29 -0
- package/src/app/utils/svgs/featureTag.tsx +20 -0
- package/src/app/utils/svgs/filterIcon.tsx +3 -0
- package/src/app/utils/svgs/globleIcon.tsx +41 -0
- package/src/app/utils/svgs/infoIcon.tsx +34 -0
- package/src/app/utils/svgs/listIcon.tsx +50 -0
- package/src/app/utils/svgs/logOutIcon.tsx +35 -0
- package/src/app/utils/svgs/menuIcon.tsx +8 -0
- package/src/app/utils/svgs/minusIcon.tsx +18 -0
- package/src/app/utils/svgs/newsletterIcon.tsx +19 -0
- package/src/app/utils/svgs/noDataFoundIcon-.tsx +26 -0
- package/src/app/utils/svgs/noProductFoundIcon.tsx +43 -0
- package/src/app/utils/svgs/passwordIcons/errorIcon.tsx +31 -0
- package/src/app/utils/svgs/passwordIcons/successIcon.tsx +24 -0
- package/src/app/utils/svgs/paymentProcessingIcons/hourglassIcon.tsx +43 -0
- package/src/app/utils/svgs/paymentProcessingIcons/modalCrossIcon.tsx +23 -0
- package/src/app/utils/svgs/paymentProcessingIcons/paymentFailedIcon.tsx +47 -0
- package/src/app/utils/svgs/pencilIcon.tsx +11 -0
- package/src/app/utils/svgs/plusIcon.tsx +25 -0
- package/src/app/utils/svgs/productInquiryIcon.tsx +40 -0
- package/src/app/utils/svgs/searchIcon.tsx +31 -0
- package/src/app/utils/svgs/shoppingCart.tsx +32 -0
- package/src/app/utils/svgs/spinnerIcon.tsx +22 -0
- package/src/app/utils/svgs/spinnerLoadingIcon.tsx +26 -0
- package/src/app/utils/svgs/successTickIcon.tsx +40 -0
- package/src/app/utils/svgs/swiperArrowIconLeft.tsx +18 -0
- package/src/app/utils/svgs/swiperArrowIconRight.tsx +18 -0
- package/src/app/utils/svgs/userProfileIcon.tsx +31 -0
- package/src/app/utils/svgs/warningCircleIcon.tsx +15 -0
- package/src/app/warranty/constant.tsx +63 -0
- package/src/app/warranty/loading.tsx +17 -0
- package/src/app/warranty/page.tsx +56 -0
- package/src/graphql/client.ts +288 -0
- package/src/graphql/mutations/accountAddressCreate.ts +56 -0
- package/src/graphql/mutations/accountAddressDelete.ts +23 -0
- package/src/graphql/mutations/accountAddressUpdate.ts +55 -0
- package/src/graphql/mutations/accountSetDefaultAddress.ts +32 -0
- package/src/graphql/mutations/accountUpdate.ts +34 -0
- package/src/graphql/mutations/changePassword.ts +25 -0
- package/src/graphql/mutations/checkout.ts +117 -0
- package/src/graphql/mutations/checkoutAddVoucher.ts +63 -0
- package/src/graphql/mutations/checkoutComplete.ts +79 -0
- package/src/graphql/mutations/checkoutCreate.ts +131 -0
- package/src/graphql/mutations/checkoutCustomerAttach.ts +50 -0
- package/src/graphql/mutations/checkoutEmailUpdate.ts +15 -0
- package/src/graphql/mutations/checkoutLineMetadataUpdate.ts +52 -0
- package/src/graphql/mutations/checkoutPaymentCreate.ts +82 -0
- package/src/graphql/mutations/paymentGatewayInitialize.ts +58 -0
- package/src/graphql/mutations/registerAccount.ts +65 -0
- package/src/graphql/mutations/requestPasswordReset.ts +32 -0
- package/src/graphql/mutations/setPassword.ts +49 -0
- package/src/graphql/mutations/signIn.ts +50 -0
- package/src/graphql/mutations/tokenRefresh.ts +19 -0
- package/src/graphql/mutations/updateCheckoutMetadata.ts +49 -0
- package/src/graphql/mutations/updateProfile.ts +18 -0
- package/src/graphql/mutations/willCallDeliveryMethod.ts +81 -0
- package/src/graphql/queries/checkout.ts +168 -0
- package/src/graphql/queries/findProductByOldSlug.ts +58 -0
- package/src/graphql/queries/getAboutPage.ts +24 -0
- package/src/graphql/queries/getAboutPageId.ts +9 -0
- package/src/graphql/queries/getAboutUs.ts +38 -0
- package/src/graphql/queries/getAddressInformation.ts +38 -0
- package/src/graphql/queries/getAllCategories.ts +41 -0
- package/src/graphql/queries/getAllCategoriesTree.ts +67 -0
- package/src/graphql/queries/getAllCategoriesWithProducts.ts +29 -0
- package/src/graphql/queries/getAllCollectionsWithProducts.ts +16 -0
- package/src/graphql/queries/getBlogs.ts +222 -0
- package/src/graphql/queries/getBrands.ts +17 -0
- package/src/graphql/queries/getBundles.ts +43 -0
- package/src/graphql/queries/getCategories.ts +20 -0
- package/src/graphql/queries/getChannels.ts +77 -0
- package/src/graphql/queries/getCheckoutQuestions.ts +115 -0
- package/src/graphql/queries/getCheckoutTermsAndConditions.ts +37 -0
- package/src/graphql/queries/getContactPage.ts +117 -0
- package/src/graphql/queries/getContentPage.ts +191 -0
- package/src/graphql/queries/getDiscountOffers.ts +18 -0
- package/src/graphql/queries/getDynamicPageBySlug.ts +251 -0
- package/src/graphql/queries/getFeaturedProducts.ts +48 -0
- package/src/graphql/queries/getHeroMetadata.ts +23 -0
- package/src/graphql/queries/getMenuBySlug.ts +84 -0
- package/src/graphql/queries/getMyProfile.ts +23 -0
- package/src/graphql/queries/getNewsletter.ts +122 -0
- package/src/graphql/queries/getNewsletterPage.ts +111 -0
- package/src/graphql/queries/getPageBySlug.ts +52 -0
- package/src/graphql/queries/getPageTypeId.ts +27 -0
- package/src/graphql/queries/getPaymentMethods.ts +61 -0
- package/src/graphql/queries/getProducts.ts +78 -0
- package/src/graphql/queries/getPromotions.ts +24 -0
- package/src/graphql/queries/getRequestReturnPage.ts +121 -0
- package/src/graphql/queries/getSiteInfo.ts +54 -0
- package/src/graphql/queries/getSocialLinks.ts +52 -0
- package/src/graphql/queries/getTestimonials.ts +25 -0
- package/src/graphql/queries/getUserWithCheckout.ts +27 -0
- package/src/graphql/queries/getVehicleMakes.ts +21 -0
- package/src/graphql/queries/getVehicleModels.ts +21 -0
- package/src/graphql/queries/getVehicleYears.ts +21 -0
- package/src/graphql/queries/meAddresses.ts +56 -0
- package/src/graphql/queries/myOrders.ts +37 -0
- package/src/graphql/queries/orderDetail.ts +231 -0
- package/src/graphql/queries/productDetailsById.ts +197 -0
- package/src/graphql/queries/productInquiry.ts +115 -0
- package/src/graphql/queries/productsByCategoriesAndCollections.ts +39 -0
- package/src/graphql/queries/willCallCollectionPoints.ts +55 -0
- package/src/graphql/server-client.ts +54 -0
- package/src/graphql/types/categories.ts +9 -0
- package/src/graphql/types/checkout.ts +168 -0
- package/src/graphql/types/offer.ts +12 -0
- package/src/graphql/types/product.ts +44 -0
- package/src/hooks/scrollPageTop.ts +9 -0
- package/src/hooks/serverNavbarData.ts +79 -0
- package/src/hooks/useCartSync.ts +24 -0
- package/src/hooks/useRecaptcha.ts +33 -0
- package/src/hooks/useTokenExpiration.ts +81 -0
- package/src/hooks/useVehicleData.ts +346 -0
- package/src/lib/api/kount.ts +165 -0
- package/src/lib/api/shop.ts +1445 -0
- package/src/lib/saleor/getSaleorApiUrl.ts +25 -0
- package/src/lib/schema.ts +303 -0
- package/src/lib/seo/extractTextFromEditorJs.ts +58 -0
- package/src/lib/seo/site.ts +10 -0
- package/src/lib/urls/normalizeInternalUrl.ts +53 -0
- package/src/middleware.ts +134 -0
- package/src/sitemaps/README.md +105 -0
- package/src/sitemaps/dynamic-pages-sitemap.ts +247 -0
- package/src/sitemaps/sitemap-index.ts +21 -0
- package/src/sitemaps/static-pages-sitemap.ts +36 -0
- package/src/store/useGlobalStore.tsx +1656 -0
- package/src/types/global.d.ts +148 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,1083 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { PaymentProcessingState } from "@/graphql/types/checkout";
|
|
6
|
+
import LoadingUI from "../reuseableUI/loadingUI";
|
|
7
|
+
|
|
8
|
+
// PayPal SDK Types
|
|
9
|
+
interface ApplePayConfig {
|
|
10
|
+
countryCode: string;
|
|
11
|
+
currencyCode: string;
|
|
12
|
+
merchantCapabilities: string[];
|
|
13
|
+
supportedNetworks: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface GooglePayConfigResponse {
|
|
17
|
+
allowedPaymentMethods: AllowedPaymentMethod[];
|
|
18
|
+
merchantInfo: {
|
|
19
|
+
merchantId?: string;
|
|
20
|
+
merchantName?: string;
|
|
21
|
+
};
|
|
22
|
+
isEligible: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface PayPalButtonsConfig {
|
|
26
|
+
createOrder: () => Promise<string>;
|
|
27
|
+
onApprove: (data: { orderID: string }) => Promise<void>;
|
|
28
|
+
onError?: (err: Error) => void;
|
|
29
|
+
onCancel?: () => void;
|
|
30
|
+
style?: {
|
|
31
|
+
layout?: "vertical" | "horizontal";
|
|
32
|
+
color?: "gold" | "blue" | "silver" | "white" | "black";
|
|
33
|
+
shape?: "rect" | "pill";
|
|
34
|
+
label?: "paypal" | "checkout" | "buynow" | "pay";
|
|
35
|
+
height?: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface PayPalPaymentProps {
|
|
40
|
+
checkoutId: string;
|
|
41
|
+
totalAmount: number;
|
|
42
|
+
currency?: string;
|
|
43
|
+
onSuccess: () => void;
|
|
44
|
+
onError: (message: string) => void;
|
|
45
|
+
setIsProcessingPayment: (state: PaymentProcessingState) => void;
|
|
46
|
+
paypalClientId?: string;
|
|
47
|
+
environment?: "sandbox" | "live";
|
|
48
|
+
userEmail?: string;
|
|
49
|
+
guestEmail?: string;
|
|
50
|
+
termsAccepted?: boolean;
|
|
51
|
+
termsData?: { page?: { isPublished: boolean } | null };
|
|
52
|
+
onTermsModalOpen?: () => void;
|
|
53
|
+
onTermsAcceptedChange?: (accepted: boolean) => void;
|
|
54
|
+
questionsValid?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function PayPalPayment({
|
|
58
|
+
checkoutId,
|
|
59
|
+
totalAmount,
|
|
60
|
+
currency = "USD",
|
|
61
|
+
onSuccess,
|
|
62
|
+
onError,
|
|
63
|
+
setIsProcessingPayment,
|
|
64
|
+
paypalClientId,
|
|
65
|
+
environment = "sandbox",
|
|
66
|
+
userEmail,
|
|
67
|
+
guestEmail,
|
|
68
|
+
termsAccepted = true,
|
|
69
|
+
termsData,
|
|
70
|
+
onTermsModalOpen,
|
|
71
|
+
onTermsAcceptedChange,
|
|
72
|
+
questionsValid = true,
|
|
73
|
+
}: PayPalPaymentProps) {
|
|
74
|
+
const router = useRouter();
|
|
75
|
+
const [sdkLoaded, setSdkLoaded] = useState(false);
|
|
76
|
+
const [sdkError, setSdkError] = useState<string | null>(null);
|
|
77
|
+
const [isCapturingPayment, setIsCapturingPayment] = useState(false);
|
|
78
|
+
const [paypalConfig, setPaypalConfig] = useState<{
|
|
79
|
+
clientId: string;
|
|
80
|
+
merchantId: string | null;
|
|
81
|
+
paymentMethodReadiness?: {
|
|
82
|
+
applePay: boolean;
|
|
83
|
+
googlePay: boolean;
|
|
84
|
+
paypalButtons: boolean;
|
|
85
|
+
advancedCardProcessing: boolean;
|
|
86
|
+
vaulting: boolean;
|
|
87
|
+
};
|
|
88
|
+
} | null>(null);
|
|
89
|
+
const [isLoadingConfig, setIsLoadingConfig] = useState(true);
|
|
90
|
+
const [googlePaySdkLoaded, setGooglePaySdkLoaded] = useState(false);
|
|
91
|
+
const paypalContainerRef = useRef<HTMLDivElement>(null);
|
|
92
|
+
const buttonsRendered = useRef(false);
|
|
93
|
+
const applePayRendered = useRef(false);
|
|
94
|
+
const googlePayRendered = useRef(false);
|
|
95
|
+
const configFetched = useRef(false);
|
|
96
|
+
|
|
97
|
+
// Fetch PayPal configuration dynamically from Saleor using GraphQL
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
// Prevent duplicate calls
|
|
100
|
+
if (configFetched.current) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const fetchPayPalConfig = async () => {
|
|
105
|
+
try {
|
|
106
|
+
setIsLoadingConfig(true);
|
|
107
|
+
|
|
108
|
+
// Call the API route to get PayPal configuration
|
|
109
|
+
const response = await fetch("/api/paypal/get-config", {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: {
|
|
112
|
+
"Content-Type": "application/json",
|
|
113
|
+
},
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
checkoutId,
|
|
116
|
+
amount: totalAmount,
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const errorData = await response.json();
|
|
122
|
+
throw new Error(errorData.error || `Failed to fetch PayPal configuration (HTTP ${response.status})`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const result = await response.json();
|
|
126
|
+
|
|
127
|
+
if (!result.clientId) {
|
|
128
|
+
throw new Error("PayPal client ID not configured in the payment app");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
setPaypalConfig({
|
|
132
|
+
clientId: result.clientId,
|
|
133
|
+
merchantId: result.merchantId || null,
|
|
134
|
+
paymentMethodReadiness: result.paymentMethodReadiness,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
// `paymentMethodReadiness` is optional and not required to render the PayPal flow.
|
|
139
|
+
|
|
140
|
+
// Mark config as fetched
|
|
141
|
+
configFetched.current = true;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
setSdkError(
|
|
144
|
+
error instanceof Error
|
|
145
|
+
? error.message
|
|
146
|
+
: "Failed to load PayPal configuration"
|
|
147
|
+
);
|
|
148
|
+
} finally {
|
|
149
|
+
setIsLoadingConfig(false);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
fetchPayPalConfig();
|
|
154
|
+
}, [checkoutId, totalAmount]);
|
|
155
|
+
|
|
156
|
+
// Load Google Pay SDK
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
// Check if already loaded
|
|
159
|
+
if (window.google?.payments?.api) {
|
|
160
|
+
setGooglePaySdkLoaded(true);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Load Google Pay SDK
|
|
165
|
+
const script = document.createElement("script");
|
|
166
|
+
script.src = "https://pay.google.com/gp/p/js/pay.js";
|
|
167
|
+
script.async = true;
|
|
168
|
+
script.onload = () => {
|
|
169
|
+
setGooglePaySdkLoaded(true);
|
|
170
|
+
};
|
|
171
|
+
script.onerror = () => {
|
|
172
|
+
console.error("Failed to load Google Pay SDK");
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
document.head.appendChild(script);
|
|
176
|
+
|
|
177
|
+
return () => {
|
|
178
|
+
if (document.head.contains(script)) {
|
|
179
|
+
document.head.removeChild(script);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
// Load PayPal SDK once config is available
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
// Wait for config to be loaded
|
|
187
|
+
if (!paypalConfig || isLoadingConfig) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if SDK already loaded
|
|
192
|
+
if (window.paypal) {
|
|
193
|
+
setSdkLoaded(true);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Create script element with dynamic configuration
|
|
198
|
+
const script = document.createElement("script");
|
|
199
|
+
|
|
200
|
+
// Build SDK URL with proper format (same as hardcoded version)
|
|
201
|
+
let sdkUrl = `https://www.paypal.com/sdk/js?client-id=${paypalConfig.clientId}`;
|
|
202
|
+
|
|
203
|
+
// Add merchant-id if available
|
|
204
|
+
if (paypalConfig.merchantId) {
|
|
205
|
+
sdkUrl += `&merchant-id=${paypalConfig.merchantId}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Add remaining parameters with Apple Pay and Google Pay components
|
|
209
|
+
sdkUrl += `¤cy=${currency}&intent=capture&components=buttons,applepay,googlepay`;
|
|
210
|
+
|
|
211
|
+
script.src = sdkUrl;
|
|
212
|
+
script.async = true;
|
|
213
|
+
script.setAttribute("data-partner-attribution-id", "bnCode");
|
|
214
|
+
|
|
215
|
+
script.onload = () => {
|
|
216
|
+
setSdkLoaded(true);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
script.onerror = () => {
|
|
220
|
+
setSdkError("Failed to load PayPal payment system. Please check the client ID configuration.");
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
document.body.appendChild(script);
|
|
224
|
+
|
|
225
|
+
return () => {
|
|
226
|
+
// Cleanup script on unmount
|
|
227
|
+
if (document.body.contains(script)) {
|
|
228
|
+
document.body.removeChild(script);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}, [paypalConfig, isLoadingConfig, currency]);
|
|
232
|
+
|
|
233
|
+
// Render PayPal buttons
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (!sdkLoaded || !window.paypal || buttonsRendered.current) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const container = paypalContainerRef.current;
|
|
240
|
+
if (!container) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check if buttons are already rendered in the container
|
|
245
|
+
if (container.children.length > 0) {
|
|
246
|
+
buttonsRendered.current = true;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
window.paypal
|
|
252
|
+
.Buttons({
|
|
253
|
+
createOrder: async () => {
|
|
254
|
+
setIsProcessingPayment({
|
|
255
|
+
isModalOpen: true,
|
|
256
|
+
paymentProcessingLoading: true,
|
|
257
|
+
error: false,
|
|
258
|
+
success: false,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
// Call transaction initialize endpoint
|
|
263
|
+
const response = await fetch("/api/paypal/create-order", {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: {
|
|
266
|
+
"Content-Type": "application/json",
|
|
267
|
+
},
|
|
268
|
+
body: JSON.stringify({
|
|
269
|
+
checkoutId,
|
|
270
|
+
amount: totalAmount,
|
|
271
|
+
currency,
|
|
272
|
+
}),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const data = await response.json();
|
|
276
|
+
|
|
277
|
+
if (!response.ok || !data.orderId) {
|
|
278
|
+
throw new Error(data.error || "Failed to create PayPal order");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Store transaction ID for later use
|
|
282
|
+
if (data.transactionId) {
|
|
283
|
+
sessionStorage.setItem(`paypal-txn-${checkoutId}`, data.transactionId);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return data.orderId;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
setIsProcessingPayment({
|
|
289
|
+
isModalOpen: false,
|
|
290
|
+
paymentProcessingLoading: false,
|
|
291
|
+
error: true,
|
|
292
|
+
success: false,
|
|
293
|
+
});
|
|
294
|
+
if (error instanceof Error) {
|
|
295
|
+
onError(`Failed to create PayPal order: ${error.message}`);
|
|
296
|
+
} else {
|
|
297
|
+
onError("Failed to create PayPal order");
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
onApprove: async (data: { orderID: string }) => {
|
|
304
|
+
// Set capturing state to disable buttons
|
|
305
|
+
setIsCapturingPayment(true);
|
|
306
|
+
|
|
307
|
+
setIsProcessingPayment({
|
|
308
|
+
isModalOpen: true,
|
|
309
|
+
paymentProcessingLoading: true,
|
|
310
|
+
error: false,
|
|
311
|
+
success: false,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
// Get stored transaction ID if available
|
|
316
|
+
const transactionId = sessionStorage.getItem(`paypal-txn-${checkoutId}`);
|
|
317
|
+
|
|
318
|
+
// Capture/complete the payment
|
|
319
|
+
const response = await fetch("/api/paypal/capture-order", {
|
|
320
|
+
method: "POST",
|
|
321
|
+
headers: {
|
|
322
|
+
"Content-Type": "application/json",
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify({
|
|
325
|
+
checkoutId,
|
|
326
|
+
orderId: data.orderID,
|
|
327
|
+
transactionId,
|
|
328
|
+
}),
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const result = await response.json();
|
|
332
|
+
|
|
333
|
+
if (!response.ok || result.error) {
|
|
334
|
+
throw new Error(result.error || "Failed to capture payment");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Extract order details from the response
|
|
338
|
+
const orderData = result.order;
|
|
339
|
+
|
|
340
|
+
if (orderData?.id && orderData?.number) {
|
|
341
|
+
setIsProcessingPayment({
|
|
342
|
+
isModalOpen: false,
|
|
343
|
+
paymentProcessingLoading: false,
|
|
344
|
+
error: false,
|
|
345
|
+
success: true,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Redirect to order confirmation page with order details
|
|
349
|
+
router.push(
|
|
350
|
+
`/order-confirmation?orderId=${orderData.id}&orderNumber=${orderData.number}&total=${orderData.total}`
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Call onSuccess callback
|
|
354
|
+
onSuccess();
|
|
355
|
+
} else {
|
|
356
|
+
throw new Error("Order data not found in response");
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
// Reset capturing state on error
|
|
360
|
+
setIsCapturingPayment(false);
|
|
361
|
+
|
|
362
|
+
setIsProcessingPayment({
|
|
363
|
+
isModalOpen: false,
|
|
364
|
+
paymentProcessingLoading: false,
|
|
365
|
+
error: true,
|
|
366
|
+
success: false,
|
|
367
|
+
});
|
|
368
|
+
if (error instanceof Error) {
|
|
369
|
+
onError(`Payment capture failed: ${error.message}`);
|
|
370
|
+
} else {
|
|
371
|
+
onError("Payment capture failed");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
onError: () => {
|
|
377
|
+
setIsProcessingPayment({
|
|
378
|
+
isModalOpen: false,
|
|
379
|
+
paymentProcessingLoading: false,
|
|
380
|
+
error: true,
|
|
381
|
+
success: false,
|
|
382
|
+
});
|
|
383
|
+
onError("PayPal payment error occurred");
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
onCancel: () => {
|
|
387
|
+
setIsProcessingPayment({
|
|
388
|
+
isModalOpen: false,
|
|
389
|
+
paymentProcessingLoading: false,
|
|
390
|
+
error: false,
|
|
391
|
+
success: false,
|
|
392
|
+
});
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
style: {
|
|
396
|
+
layout: "vertical",
|
|
397
|
+
color: "gold",
|
|
398
|
+
shape: "rect",
|
|
399
|
+
label: "paypal",
|
|
400
|
+
height: 45,
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
.render("#paypal-button-container")
|
|
404
|
+
.then(() => {
|
|
405
|
+
buttonsRendered.current = true;
|
|
406
|
+
})
|
|
407
|
+
.catch(() => {
|
|
408
|
+
setSdkError("Failed to render PayPal buttons");
|
|
409
|
+
});
|
|
410
|
+
} catch (error) {
|
|
411
|
+
setSdkError("Failed to initialize PayPal buttons");
|
|
412
|
+
}
|
|
413
|
+
}, [
|
|
414
|
+
sdkLoaded,
|
|
415
|
+
checkoutId,
|
|
416
|
+
totalAmount,
|
|
417
|
+
currency,
|
|
418
|
+
onSuccess,
|
|
419
|
+
onError,
|
|
420
|
+
setIsProcessingPayment,
|
|
421
|
+
router,
|
|
422
|
+
]);
|
|
423
|
+
|
|
424
|
+
// Render Apple Pay button
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
if (!sdkLoaded || !window.paypal || applePayRendered.current) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Check if Apple Pay is enabled
|
|
431
|
+
if (!paypalConfig?.paymentMethodReadiness?.applePay) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const applePayContainer = document.getElementById("applepay-container");
|
|
436
|
+
if (!applePayContainer) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Check if buttons are already rendered in the container
|
|
441
|
+
if (applePayContainer.children.length > 0) {
|
|
442
|
+
applePayRendered.current = true;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Mark as rendered immediately to prevent race conditions
|
|
447
|
+
applePayRendered.current = true;
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
// Check if ApplePaySession is available on this device/browser
|
|
451
|
+
if (!window.ApplePaySession || !window.ApplePaySession.canMakePayments()) {
|
|
452
|
+
// Apple Pay not available on this device/browser.
|
|
453
|
+
applePayContainer.style.display = "none";
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const applepay = window.paypal.Applepay();
|
|
458
|
+
|
|
459
|
+
// Configure Apple Pay
|
|
460
|
+
applepay
|
|
461
|
+
.config()
|
|
462
|
+
.then((applePayConfig) => {
|
|
463
|
+
// Apple Pay is available and configured
|
|
464
|
+
// Apple Pay configured.
|
|
465
|
+
|
|
466
|
+
// Create Apple Pay button
|
|
467
|
+
const button = document.createElement("button");
|
|
468
|
+
button.className = "apple-pay-button apple-pay-button-black";
|
|
469
|
+
button.style.cssText =
|
|
470
|
+
"width: 100%; height: 45px; display: block; cursor: pointer; -webkit-appearance: -apple-pay-button; -apple-pay-button-type: plain;";
|
|
471
|
+
|
|
472
|
+
button.addEventListener("click", () => {
|
|
473
|
+
try {
|
|
474
|
+
// Step 1: Create Apple Pay Payment Request (must be synchronous with click)
|
|
475
|
+
// Creating Apple Pay payment session.
|
|
476
|
+
|
|
477
|
+
const paymentRequest = {
|
|
478
|
+
countryCode: applePayConfig.countryCode || "US",
|
|
479
|
+
currencyCode: currency,
|
|
480
|
+
merchantCapabilities: applePayConfig.merchantCapabilities,
|
|
481
|
+
supportedNetworks: applePayConfig.supportedNetworks,
|
|
482
|
+
total: {
|
|
483
|
+
label: "Total",
|
|
484
|
+
type: "final",
|
|
485
|
+
amount: totalAmount.toFixed(2)
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// Step 2: Create Apple Pay Session (MUST be called synchronously in click handler)
|
|
490
|
+
if (!window.ApplePaySession) {
|
|
491
|
+
console.error("ApplePaySession not available");
|
|
492
|
+
onError("Apple Pay is not available on this device");
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const session = new window.ApplePaySession(4, paymentRequest);
|
|
496
|
+
|
|
497
|
+
// Handle merchant validation
|
|
498
|
+
session.onvalidatemerchant = (event: { validationURL: string }) => {
|
|
499
|
+
// Validating merchant.
|
|
500
|
+
applepay.validateMerchant({
|
|
501
|
+
validationUrl: event.validationURL,
|
|
502
|
+
displayName: "Web Shop Manager"
|
|
503
|
+
})
|
|
504
|
+
.then((validateResult: { merchantSession: unknown }) => {
|
|
505
|
+
// Merchant validated.
|
|
506
|
+
session.completeMerchantValidation(validateResult.merchantSession);
|
|
507
|
+
})
|
|
508
|
+
.catch((validateError: Error) => {
|
|
509
|
+
console.error("❌ Merchant validation failed:", validateError);
|
|
510
|
+
session.abort();
|
|
511
|
+
onError("Apple Pay validation failed");
|
|
512
|
+
});
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// Handle payment authorization
|
|
516
|
+
session.onpaymentauthorized = async (event: { payment: { token: unknown; billingContact?: unknown } }) => {
|
|
517
|
+
try {
|
|
518
|
+
// Payment authorized by user.
|
|
519
|
+
|
|
520
|
+
setIsProcessingPayment({
|
|
521
|
+
isModalOpen: true,
|
|
522
|
+
paymentProcessingLoading: true,
|
|
523
|
+
error: false,
|
|
524
|
+
success: false,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Step 3: Create PayPal order
|
|
528
|
+
// Creating PayPal order.
|
|
529
|
+
const response = await fetch("/api/paypal/create-order", {
|
|
530
|
+
method: "POST",
|
|
531
|
+
headers: {
|
|
532
|
+
"Content-Type": "application/json",
|
|
533
|
+
},
|
|
534
|
+
body: JSON.stringify({
|
|
535
|
+
checkoutId,
|
|
536
|
+
amount: totalAmount,
|
|
537
|
+
currency,
|
|
538
|
+
}),
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const data = await response.json();
|
|
542
|
+
|
|
543
|
+
if (!response.ok || !data.orderId) {
|
|
544
|
+
throw new Error(data.error || "Failed to create PayPal order");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// PayPal order created.
|
|
548
|
+
|
|
549
|
+
// Store transaction ID for later use
|
|
550
|
+
if (data.transactionId) {
|
|
551
|
+
sessionStorage.setItem(`paypal-txn-${checkoutId}`, data.transactionId);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Step 4: Confirm order with PayPal using Apple Pay token
|
|
555
|
+
// Confirming order with PayPal.
|
|
556
|
+
await applepay.confirmOrder({
|
|
557
|
+
orderId: data.orderId,
|
|
558
|
+
token: event.payment.token,
|
|
559
|
+
billingContact: event.payment.billingContact
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// Order confirmed with PayPal.
|
|
563
|
+
|
|
564
|
+
// Complete the Apple Pay session as successful
|
|
565
|
+
if (window.ApplePaySession) {
|
|
566
|
+
session.completePayment(window.ApplePaySession.STATUS_SUCCESS);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Step 5: Capture payment
|
|
570
|
+
setIsCapturingPayment(true);
|
|
571
|
+
// Capturing payment.
|
|
572
|
+
|
|
573
|
+
const transactionId = sessionStorage.getItem(`paypal-txn-${checkoutId}`);
|
|
574
|
+
|
|
575
|
+
const captureResponse = await fetch("/api/paypal/capture-order", {
|
|
576
|
+
method: "POST",
|
|
577
|
+
headers: {
|
|
578
|
+
"Content-Type": "application/json",
|
|
579
|
+
},
|
|
580
|
+
body: JSON.stringify({
|
|
581
|
+
checkoutId,
|
|
582
|
+
orderId: data.orderId,
|
|
583
|
+
transactionId,
|
|
584
|
+
}),
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const result = await captureResponse.json();
|
|
588
|
+
|
|
589
|
+
if (!captureResponse.ok || result.error) {
|
|
590
|
+
throw new Error(result.error || "Failed to capture payment");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const orderData = result.order;
|
|
594
|
+
|
|
595
|
+
if (orderData?.id && orderData?.number) {
|
|
596
|
+
// Apple Pay payment successful.
|
|
597
|
+
|
|
598
|
+
setIsProcessingPayment({
|
|
599
|
+
isModalOpen: false,
|
|
600
|
+
paymentProcessingLoading: false,
|
|
601
|
+
error: false,
|
|
602
|
+
success: true,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
router.push(
|
|
606
|
+
`/order-confirmation?orderId=${orderData.id}&orderNumber=${orderData.number}&total=${orderData.total}`
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
onSuccess();
|
|
610
|
+
} else {
|
|
611
|
+
throw new Error("Order data not found in response");
|
|
612
|
+
}
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.error("❌ Payment processing error:", error);
|
|
615
|
+
if (window.ApplePaySession) {
|
|
616
|
+
session.completePayment(window.ApplePaySession.STATUS_FAILURE);
|
|
617
|
+
}
|
|
618
|
+
setIsCapturingPayment(false);
|
|
619
|
+
setIsProcessingPayment({
|
|
620
|
+
isModalOpen: false,
|
|
621
|
+
paymentProcessingLoading: false,
|
|
622
|
+
error: true,
|
|
623
|
+
success: false,
|
|
624
|
+
});
|
|
625
|
+
if (error instanceof Error) {
|
|
626
|
+
onError(`Apple Pay payment failed: ${error.message}`);
|
|
627
|
+
} else {
|
|
628
|
+
onError("Apple Pay payment failed");
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
// Handle cancel event
|
|
634
|
+
session.oncancel = () => {
|
|
635
|
+
// Apple Pay session cancelled by user.
|
|
636
|
+
setIsProcessingPayment({
|
|
637
|
+
isModalOpen: false,
|
|
638
|
+
paymentProcessingLoading: false,
|
|
639
|
+
error: false,
|
|
640
|
+
success: false,
|
|
641
|
+
});
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
// Begin the session
|
|
645
|
+
session.begin();
|
|
646
|
+
// Apple Pay session started.
|
|
647
|
+
} catch (error) {
|
|
648
|
+
console.error("❌ Apple Pay session error:", error);
|
|
649
|
+
setIsProcessingPayment({
|
|
650
|
+
isModalOpen: false,
|
|
651
|
+
paymentProcessingLoading: false,
|
|
652
|
+
error: true,
|
|
653
|
+
success: false,
|
|
654
|
+
});
|
|
655
|
+
if (error instanceof Error) {
|
|
656
|
+
onError(`Apple Pay initialization failed: ${error.message}`);
|
|
657
|
+
} else {
|
|
658
|
+
onError("Apple Pay initialization failed");
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
applePayContainer.appendChild(button);
|
|
664
|
+
})
|
|
665
|
+
.catch((error) => {
|
|
666
|
+
console.error("Apple Pay not available:", error);
|
|
667
|
+
// Apple Pay is not available on this device/browser
|
|
668
|
+
// Hide the container
|
|
669
|
+
applePayContainer.style.display = "none";
|
|
670
|
+
// Reset flag if initialization failed
|
|
671
|
+
applePayRendered.current = false;
|
|
672
|
+
});
|
|
673
|
+
} catch (error) {
|
|
674
|
+
console.error("Failed to initialize Apple Pay:", error);
|
|
675
|
+
applePayContainer.style.display = "none";
|
|
676
|
+
// Reset flag if initialization failed
|
|
677
|
+
applePayRendered.current = false;
|
|
678
|
+
}
|
|
679
|
+
}, [
|
|
680
|
+
sdkLoaded,
|
|
681
|
+
paypalConfig,
|
|
682
|
+
checkoutId,
|
|
683
|
+
totalAmount,
|
|
684
|
+
currency,
|
|
685
|
+
onSuccess,
|
|
686
|
+
onError,
|
|
687
|
+
setIsProcessingPayment,
|
|
688
|
+
router,
|
|
689
|
+
]);
|
|
690
|
+
|
|
691
|
+
// Render Google Pay button
|
|
692
|
+
useEffect(() => {
|
|
693
|
+
if (!sdkLoaded || !window.paypal || googlePayRendered.current) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Check if Google Pay is enabled
|
|
698
|
+
if (!paypalConfig?.paymentMethodReadiness?.googlePay) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Check if Google Pay SDK is loaded
|
|
703
|
+
if (!googlePaySdkLoaded || !window.google?.payments?.api) {
|
|
704
|
+
// Google Pay SDK not loaded yet.
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const googlePayContainer = document.getElementById("googlepay-container");
|
|
709
|
+
if (!googlePayContainer) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Check if buttons are already rendered in the container
|
|
714
|
+
if (googlePayContainer.children.length > 0) {
|
|
715
|
+
googlePayRendered.current = true;
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Mark as rendered immediately to prevent race conditions
|
|
720
|
+
googlePayRendered.current = true;
|
|
721
|
+
|
|
722
|
+
try {
|
|
723
|
+
const googlepay = window.paypal.Googlepay();
|
|
724
|
+
|
|
725
|
+
// Configure Google Pay to check if it's available
|
|
726
|
+
googlepay
|
|
727
|
+
.config()
|
|
728
|
+
.then(async (googlePayConfig) => {
|
|
729
|
+
// Google Pay is available and configured
|
|
730
|
+
// Google Pay configured.
|
|
731
|
+
|
|
732
|
+
// Verify Google Pay SDK is still available (TypeScript safety check)
|
|
733
|
+
if (!window.google?.payments?.api) {
|
|
734
|
+
console.error("Google Pay SDK not available");
|
|
735
|
+
googlePayContainer.style.display = "none";
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Create Google Payments client
|
|
740
|
+
const paymentsClient = new window.google.payments.api.PaymentsClient({
|
|
741
|
+
environment: environment === "sandbox" ? "TEST" : "PRODUCTION",
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// Check if Google Pay is available on this device/browser
|
|
745
|
+
const isReadyToPayRequest = {
|
|
746
|
+
apiVersion: 2,
|
|
747
|
+
apiVersionMinor: 0,
|
|
748
|
+
allowedPaymentMethods: googlePayConfig.allowedPaymentMethods,
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
const { result: isReadyToPay } = await paymentsClient.isReadyToPay(isReadyToPayRequest);
|
|
752
|
+
if (!isReadyToPay) {
|
|
753
|
+
// Google Pay not available on this device.
|
|
754
|
+
googlePayContainer.style.display = "none";
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Create a regular button (not using paymentsClient.createButton)
|
|
759
|
+
const button = document.createElement("button");
|
|
760
|
+
button.className = "gpay-button";
|
|
761
|
+
button.style.cssText =
|
|
762
|
+
"width: 100%; height: 45px; background-color: #000; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: 500; display: flex; align-items: center; justify-content: center; font-size: 14px;";
|
|
763
|
+
button.textContent = "Google Pay";
|
|
764
|
+
|
|
765
|
+
button.addEventListener("click", async () => {
|
|
766
|
+
try {
|
|
767
|
+
// Step 1: Show Google Pay payment sheet IMMEDIATELY (must be synchronous with user click)
|
|
768
|
+
// This preserves the user activation context required by Payment Request API
|
|
769
|
+
// Showing Google Pay payment sheet.
|
|
770
|
+
|
|
771
|
+
const paymentDataRequest: PaymentDataRequest = {
|
|
772
|
+
apiVersion: 2,
|
|
773
|
+
apiVersionMinor: 0,
|
|
774
|
+
allowedPaymentMethods: googlePayConfig.allowedPaymentMethods,
|
|
775
|
+
merchantInfo: googlePayConfig.merchantInfo,
|
|
776
|
+
transactionInfo: {
|
|
777
|
+
totalPriceStatus: "FINAL",
|
|
778
|
+
totalPrice: totalAmount.toFixed(2),
|
|
779
|
+
currencyCode: currency,
|
|
780
|
+
},
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// Show Google Pay payment sheet - MUST be called synchronously after user click
|
|
784
|
+
const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest);
|
|
785
|
+
// Payment data received from Google Pay.
|
|
786
|
+
|
|
787
|
+
// Step 2: User has authorized payment, now show processing modal
|
|
788
|
+
setIsProcessingPayment({
|
|
789
|
+
isModalOpen: true,
|
|
790
|
+
paymentProcessingLoading: true,
|
|
791
|
+
error: false,
|
|
792
|
+
success: false,
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// Step 3: Create PayPal order (after user authorization)
|
|
796
|
+
// Creating PayPal order.
|
|
797
|
+
const response = await fetch("/api/paypal/create-order", {
|
|
798
|
+
method: "POST",
|
|
799
|
+
headers: {
|
|
800
|
+
"Content-Type": "application/json",
|
|
801
|
+
},
|
|
802
|
+
body: JSON.stringify({
|
|
803
|
+
checkoutId,
|
|
804
|
+
amount: totalAmount,
|
|
805
|
+
currency,
|
|
806
|
+
}),
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
const data = await response.json();
|
|
810
|
+
|
|
811
|
+
if (!response.ok || !data.orderId) {
|
|
812
|
+
throw new Error(data.error || "Failed to create PayPal order");
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// PayPal order created.
|
|
816
|
+
|
|
817
|
+
// Store transaction ID for later use
|
|
818
|
+
if (data.transactionId) {
|
|
819
|
+
sessionStorage.setItem(`paypal-txn-${checkoutId}`, data.transactionId);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Step 4: Confirm order with PayPal using the payment method data
|
|
823
|
+
// Confirming order with PayPal SDK.
|
|
824
|
+
const confirmResult = await googlepay.confirmOrder({
|
|
825
|
+
orderId: data.orderId,
|
|
826
|
+
paymentMethodData: paymentData.paymentMethodData,
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// PayPal confirm result received.
|
|
830
|
+
|
|
831
|
+
// Check if payment was approved
|
|
832
|
+
if (confirmResult.status !== "APPROVED" && confirmResult.status !== "COMPLETED") {
|
|
833
|
+
throw new Error(`Payment was not approved. Status: ${confirmResult.status}`);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Step 4: Capture the payment with retry logic
|
|
837
|
+
setIsCapturingPayment(true);
|
|
838
|
+
// Capturing payment.
|
|
839
|
+
|
|
840
|
+
// Get stored transaction ID
|
|
841
|
+
const transactionId = sessionStorage.getItem(`paypal-txn-${checkoutId}`);
|
|
842
|
+
|
|
843
|
+
// Retry logic for order creation (webhook processing can take time)
|
|
844
|
+
const maxRetries = 3;
|
|
845
|
+
const retryDelay = 2000; // 2 seconds
|
|
846
|
+
|
|
847
|
+
let result: { order?: { id: string; number: string; total: number }; error?: string; status?: string } | undefined;
|
|
848
|
+
let captureResponse: Response | undefined;
|
|
849
|
+
let retryCount = 0;
|
|
850
|
+
|
|
851
|
+
while (retryCount < maxRetries) {
|
|
852
|
+
// Capture payment
|
|
853
|
+
captureResponse = await fetch("/api/paypal/capture-order", {
|
|
854
|
+
method: "POST",
|
|
855
|
+
headers: {
|
|
856
|
+
"Content-Type": "application/json",
|
|
857
|
+
},
|
|
858
|
+
body: JSON.stringify({
|
|
859
|
+
checkoutId,
|
|
860
|
+
orderId: data.orderId,
|
|
861
|
+
transactionId,
|
|
862
|
+
}),
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
result = await captureResponse.json();
|
|
866
|
+
|
|
867
|
+
// If order is being processed (202), wait and retry
|
|
868
|
+
if (captureResponse.status === 202 && result?.status === "processing") {
|
|
869
|
+
// Retrying while order is processing.
|
|
870
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
871
|
+
retryCount++;
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// If successful or error, break out of loop
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (!captureResponse || !captureResponse.ok || result?.error) {
|
|
880
|
+
// If still processing after all retries, show a different message
|
|
881
|
+
if (captureResponse && captureResponse.status === 202) {
|
|
882
|
+
throw new Error("Your payment is being processed. Please check your email for order confirmation.");
|
|
883
|
+
}
|
|
884
|
+
throw new Error(result?.error || "Failed to capture payment");
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Extract order details
|
|
888
|
+
const orderData = result?.order;
|
|
889
|
+
|
|
890
|
+
if (orderData?.id && orderData?.number) {
|
|
891
|
+
// Order completed successfully.
|
|
892
|
+
|
|
893
|
+
setIsProcessingPayment({
|
|
894
|
+
isModalOpen: false,
|
|
895
|
+
paymentProcessingLoading: false,
|
|
896
|
+
error: false,
|
|
897
|
+
success: true,
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
// Redirect to order confirmation
|
|
901
|
+
router.push(
|
|
902
|
+
`/order-confirmation?orderId=${orderData.id}&orderNumber=${orderData.number}&total=${orderData.total}`
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
onSuccess();
|
|
906
|
+
} else {
|
|
907
|
+
throw new Error("Order data not found in response");
|
|
908
|
+
}
|
|
909
|
+
} catch (error) {
|
|
910
|
+
console.error("❌ Google Pay payment error:", error);
|
|
911
|
+
setIsCapturingPayment(false);
|
|
912
|
+
setIsProcessingPayment({
|
|
913
|
+
isModalOpen: false,
|
|
914
|
+
paymentProcessingLoading: false,
|
|
915
|
+
error: true,
|
|
916
|
+
success: false,
|
|
917
|
+
});
|
|
918
|
+
if (error instanceof Error) {
|
|
919
|
+
onError(`Google Pay payment failed: ${error.message}`);
|
|
920
|
+
} else {
|
|
921
|
+
onError("Google Pay payment failed");
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
// Append button to container
|
|
927
|
+
googlePayContainer.appendChild(button);
|
|
928
|
+
})
|
|
929
|
+
.catch((error) => {
|
|
930
|
+
console.error("Google Pay not available:", error);
|
|
931
|
+
// Google Pay is not available on this device/browser
|
|
932
|
+
// Hide the container
|
|
933
|
+
googlePayContainer.style.display = "none";
|
|
934
|
+
// Reset flag if initialization failed
|
|
935
|
+
googlePayRendered.current = false;
|
|
936
|
+
});
|
|
937
|
+
} catch (error) {
|
|
938
|
+
console.error("Failed to initialize Google Pay:", error);
|
|
939
|
+
googlePayContainer.style.display = "none";
|
|
940
|
+
// Reset flag if initialization failed
|
|
941
|
+
googlePayRendered.current = false;
|
|
942
|
+
}
|
|
943
|
+
}, [
|
|
944
|
+
sdkLoaded,
|
|
945
|
+
googlePaySdkLoaded,
|
|
946
|
+
paypalConfig,
|
|
947
|
+
checkoutId,
|
|
948
|
+
totalAmount,
|
|
949
|
+
currency,
|
|
950
|
+
environment,
|
|
951
|
+
onSuccess,
|
|
952
|
+
onError,
|
|
953
|
+
setIsProcessingPayment,
|
|
954
|
+
router,
|
|
955
|
+
]);
|
|
956
|
+
|
|
957
|
+
// Show error if SDK failed to load
|
|
958
|
+
if (sdkError) {
|
|
959
|
+
return (
|
|
960
|
+
<div className="bg-red-50 border border-red-200 rounded-md p-4">
|
|
961
|
+
<p className="text-red-700 text-sm">{sdkError}</p>
|
|
962
|
+
<p className="text-red-600 text-xs mt-2">
|
|
963
|
+
Please refresh the page or contact support.
|
|
964
|
+
</p>
|
|
965
|
+
</div>
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Show loading while fetching config or loading SDK
|
|
970
|
+
if (isLoadingConfig || !paypalConfig || !sdkLoaded) {
|
|
971
|
+
return (
|
|
972
|
+
<div className="space-y-4">
|
|
973
|
+
<LoadingUI className="h-32" />
|
|
974
|
+
<p className="text-center text-sm text-[var(--color-secondary-600)]">
|
|
975
|
+
{isLoadingConfig
|
|
976
|
+
? "Loading PayPal configuration..."
|
|
977
|
+
: "Loading PayPal payment system..."}
|
|
978
|
+
</p>
|
|
979
|
+
</div>
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Validation checks
|
|
984
|
+
const hasEmail = userEmail || guestEmail;
|
|
985
|
+
const needsTermsAcceptance = termsData?.page?.isPublished && !termsAccepted;
|
|
986
|
+
const isDisabled = !questionsValid || needsTermsAcceptance || !hasEmail || isCapturingPayment;
|
|
987
|
+
|
|
988
|
+
return (
|
|
989
|
+
<div className="space-y-6">
|
|
990
|
+
{/* Payment Capturing Loading Overlay */}
|
|
991
|
+
{isCapturingPayment && (
|
|
992
|
+
<div className="space-y-4">
|
|
993
|
+
<LoadingUI className="h-32" />
|
|
994
|
+
<p className="text-center text-sm text-[var(--color-secondary-600)]">
|
|
995
|
+
Processing payment...
|
|
996
|
+
</p>
|
|
997
|
+
</div>
|
|
998
|
+
)}
|
|
999
|
+
|
|
1000
|
+
{/* Main PayPal UI - hidden when capturing */}
|
|
1001
|
+
<div className={isCapturingPayment ? "hidden" : ""}>
|
|
1002
|
+
{/* Validation Messages - Only email and questions at top */}
|
|
1003
|
+
{!hasEmail && (
|
|
1004
|
+
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4">
|
|
1005
|
+
<p className="text-yellow-700 text-sm">
|
|
1006
|
+
Please provide an email address to continue with payment.
|
|
1007
|
+
</p>
|
|
1008
|
+
</div>
|
|
1009
|
+
)}
|
|
1010
|
+
|
|
1011
|
+
{!questionsValid && (
|
|
1012
|
+
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4">
|
|
1013
|
+
<p className="text-yellow-700 text-sm">
|
|
1014
|
+
Please complete all required questions below.
|
|
1015
|
+
</p>
|
|
1016
|
+
</div>
|
|
1017
|
+
)}
|
|
1018
|
+
|
|
1019
|
+
{/* Payment Buttons Container */}
|
|
1020
|
+
<div
|
|
1021
|
+
className={`transition-opacity space-y-3 mb-4 ${isDisabled ? "opacity-50 pointer-events-none" : ""}`}
|
|
1022
|
+
>
|
|
1023
|
+
{/* Apple Pay Button */}
|
|
1024
|
+
{paypalConfig?.paymentMethodReadiness?.applePay && (
|
|
1025
|
+
<div id="applepay-container" className="min-h-[50px]" />
|
|
1026
|
+
)}
|
|
1027
|
+
|
|
1028
|
+
{/* Google Pay Button */}
|
|
1029
|
+
{paypalConfig?.paymentMethodReadiness?.googlePay && (
|
|
1030
|
+
<div id="googlepay-container" className="min-h-[50px]" />
|
|
1031
|
+
)}
|
|
1032
|
+
|
|
1033
|
+
{/* PayPal Buttons */}
|
|
1034
|
+
{paypalConfig?.paymentMethodReadiness?.paypalButtons !== false && (
|
|
1035
|
+
<div
|
|
1036
|
+
id="paypal-button-container"
|
|
1037
|
+
ref={paypalContainerRef}
|
|
1038
|
+
className="min-h-[50px]"
|
|
1039
|
+
/>
|
|
1040
|
+
)}
|
|
1041
|
+
</div>
|
|
1042
|
+
|
|
1043
|
+
{/* Terms Warning - After PayPal Buttons */}
|
|
1044
|
+
{needsTermsAcceptance && (
|
|
1045
|
+
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4">
|
|
1046
|
+
<p className="text-yellow-700 text-sm">
|
|
1047
|
+
Please accept the Terms and Conditions to continue.
|
|
1048
|
+
</p>
|
|
1049
|
+
</div>
|
|
1050
|
+
)}
|
|
1051
|
+
|
|
1052
|
+
{/* Terms and Conditions Checkbox - At the end, before info message */}
|
|
1053
|
+
{termsData?.page?.isPublished && (
|
|
1054
|
+
<div className="flex items-start gap-2 w-full py-2 mb-4">
|
|
1055
|
+
<input
|
|
1056
|
+
style={{ accentColor: "var(--color-primary-600)" }}
|
|
1057
|
+
type="checkbox"
|
|
1058
|
+
id="termsAcceptedPayPal"
|
|
1059
|
+
className="w-5 h-5 cursor-pointer mt-0.5"
|
|
1060
|
+
checked={termsAccepted}
|
|
1061
|
+
onChange={(e) => onTermsAcceptedChange?.(e.target.checked)}
|
|
1062
|
+
/>
|
|
1063
|
+
<label
|
|
1064
|
+
htmlFor="termsAcceptedPayPal"
|
|
1065
|
+
style={{ color: "var(--color-secondary-600)" }}
|
|
1066
|
+
className="text-sm lg:text-base tracking-[-0.04px] cursor-pointer"
|
|
1067
|
+
>
|
|
1068
|
+
I agree to the{" "}
|
|
1069
|
+
<button
|
|
1070
|
+
type="button"
|
|
1071
|
+
onClick={onTermsModalOpen}
|
|
1072
|
+
className="font-semibold text-[var(--color-primary-600)] hover:underline focus:underline focus:outline-none"
|
|
1073
|
+
>
|
|
1074
|
+
Terms and Conditions
|
|
1075
|
+
</button>
|
|
1076
|
+
</label>
|
|
1077
|
+
</div>
|
|
1078
|
+
)}
|
|
1079
|
+
|
|
1080
|
+
</div>
|
|
1081
|
+
</div>
|
|
1082
|
+
);
|
|
1083
|
+
}
|