@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,628 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { CHECKOUT_CREATE } from '@/graphql/mutations/checkoutCreate';
|
|
4
|
+
import { ME_ADDRESSES_QUERY, type MeAddressesData } from '@/graphql/queries/meAddresses';
|
|
5
|
+
import { useGlobalStore } from '@/store/useGlobalStore';
|
|
6
|
+
import { useQuery } from '@apollo/client';
|
|
7
|
+
import Image from 'next/image';
|
|
8
|
+
import { useRouter } from 'next/navigation';
|
|
9
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
10
|
+
import Breadcrumb from '@/app/components/reuseableUI/breadcrumb';
|
|
11
|
+
import CommonButton from '@/app/components/reuseableUI/commonButton';
|
|
12
|
+
import EmptyState from '@/app/components/reuseableUI/emptyState';
|
|
13
|
+
import { ArrowIcon } from '@/app/utils/svgs/arrowIcon';
|
|
14
|
+
import { CartIcon } from '@/app/utils/svgs/cart/cartIcon';
|
|
15
|
+
import { PlusIcon } from '@/app/utils/svgs/cart/plusIcon';
|
|
16
|
+
import { SubtractIcon } from '@/app/utils/svgs/cart/subtractIcon';
|
|
17
|
+
import { gtmBeginCheckout, gtmViewCart, gtmRemoveFromCart, Product } from '@/app/utils/googleTagManager';
|
|
18
|
+
import { useAppConfiguration } from '../providers/ServerAppConfigurationProvider';
|
|
19
|
+
import { getSaleorApiUrl } from '@/lib/saleor/getSaleorApiUrl';
|
|
20
|
+
|
|
21
|
+
type CartItemOption = {
|
|
22
|
+
variantId: string;
|
|
23
|
+
name: string;
|
|
24
|
+
price: number;
|
|
25
|
+
optionSetName: string;
|
|
26
|
+
optionSetLabel: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type CartItem = {
|
|
30
|
+
key: string;
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
price: number;
|
|
34
|
+
image: string;
|
|
35
|
+
quantity: number;
|
|
36
|
+
category?: string;
|
|
37
|
+
sku?: string;
|
|
38
|
+
selectedOptions?: CartItemOption[];
|
|
39
|
+
customInputs?: Record<string, string>;
|
|
40
|
+
skipBaseProduct?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Helper to calculate item total including options
|
|
44
|
+
const getItemTotal = (item: CartItem): number => {
|
|
45
|
+
let total = item.price;
|
|
46
|
+
if (item.selectedOptions?.length) {
|
|
47
|
+
total += item.selectedOptions.reduce((sum, opt) => sum + opt.price, 0);
|
|
48
|
+
}
|
|
49
|
+
return total;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default function CartDropDown() {
|
|
53
|
+
const { cartItems: items, totalItems, totalAmount, removeFromCart, updateQuantity, addToCart, checkoutId, setCheckoutId, setCheckoutToken, isLoggedIn, user, guestEmail, guestShippingInfo, setGuestShippingInfo } = useGlobalStore();
|
|
54
|
+
const { getGoogleTagManagerConfig } = useAppConfiguration();
|
|
55
|
+
const router = useRouter();
|
|
56
|
+
const [creating, setCreating] = useState(false);
|
|
57
|
+
const [error, setError] = useState<string | null>(null);
|
|
58
|
+
const [refreshedItems, setRefreshedItems] = useState<CartItem[]>([]);
|
|
59
|
+
const [refreshedTotals, setRefreshedTotals] = useState({ totalItems: 0, totalAmount: 0 });
|
|
60
|
+
const [pricesRefreshed, setPricesRefreshed] = useState(false);
|
|
61
|
+
const [loadingItems, setLoadingItems] = useState<{ [key: string]: { plus: boolean; minus: boolean; remove: boolean } }>({});
|
|
62
|
+
|
|
63
|
+
// When logged in, fetch account addresses
|
|
64
|
+
const { data: meData, loading: meLoading } = useQuery<MeAddressesData>(ME_ADDRESSES_QUERY, { skip: !isLoggedIn });
|
|
65
|
+
const accountShipping = useMemo(() => {
|
|
66
|
+
const me = meData?.me;
|
|
67
|
+
if (!me || !me.addresses?.length) return null;
|
|
68
|
+
const defId = me.defaultShippingAddress?.id;
|
|
69
|
+
return (defId ? me.addresses.find(a => a.id === defId) : me.addresses[0]) || null;
|
|
70
|
+
}, [meData]);
|
|
71
|
+
const accountBilling = useMemo(() => {
|
|
72
|
+
const me = meData?.me;
|
|
73
|
+
if (!me || !me.addresses?.length) return null;
|
|
74
|
+
const defId = me.defaultBillingAddress?.id;
|
|
75
|
+
return (defId ? me.addresses.find(a => a.id === defId) : accountShipping || me.addresses[0]) || null;
|
|
76
|
+
}, [meData, accountShipping]);
|
|
77
|
+
|
|
78
|
+
const endpoint = getSaleorApiUrl();
|
|
79
|
+
|
|
80
|
+
const handleCountryChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
81
|
+
const value = e.target.value;
|
|
82
|
+
setGuestShippingInfo({ country: value });
|
|
83
|
+
}, [setGuestShippingInfo]);
|
|
84
|
+
|
|
85
|
+
// Refresh prices from checkout if available to ensure we show discounted prices
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const refreshPricesFromCheckout = async () => {
|
|
88
|
+
if (!checkoutId || pricesRefreshed || items.length === 0) return;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const response = await fetch(endpoint, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: { 'Content-Type': 'application/json' },
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
query: `
|
|
96
|
+
query GetCheckoutDetails($id: ID!) {
|
|
97
|
+
checkout(id: $id) {
|
|
98
|
+
id
|
|
99
|
+
totalPrice { gross { amount currency } }
|
|
100
|
+
subtotalPrice { gross { amount currency } }
|
|
101
|
+
lines {
|
|
102
|
+
id
|
|
103
|
+
quantity
|
|
104
|
+
totalPrice { gross { amount currency } }
|
|
105
|
+
variant {
|
|
106
|
+
id
|
|
107
|
+
name
|
|
108
|
+
product {
|
|
109
|
+
name
|
|
110
|
+
thumbnail { url }
|
|
111
|
+
pricing {
|
|
112
|
+
discount { gross { amount currency } }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
pricing {
|
|
116
|
+
price { gross { amount currency } }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
`,
|
|
123
|
+
variables: { id: checkoutId }
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (response.ok) {
|
|
128
|
+
const data = await response.json();
|
|
129
|
+
const checkout = data?.data?.checkout;
|
|
130
|
+
|
|
131
|
+
if (checkout?.lines?.length > 0) {
|
|
132
|
+
|
|
133
|
+
type CheckoutLine = {
|
|
134
|
+
id: string;
|
|
135
|
+
quantity: number;
|
|
136
|
+
variant: {
|
|
137
|
+
id: string;
|
|
138
|
+
product: {
|
|
139
|
+
name: string;
|
|
140
|
+
pricing?: {
|
|
141
|
+
discount?: {
|
|
142
|
+
gross?: {
|
|
143
|
+
amount: number;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
pricing?: {
|
|
149
|
+
price?: {
|
|
150
|
+
gross?: {
|
|
151
|
+
amount: number;
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
name: string;
|
|
156
|
+
};
|
|
157
|
+
totalPrice: {
|
|
158
|
+
gross: {
|
|
159
|
+
amount: number;
|
|
160
|
+
currency: string;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
name: string;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Build a map of checkout lines by variant ID for quick lookup
|
|
167
|
+
const checkoutLinesByVariantId = new Map<string, CheckoutLine>();
|
|
168
|
+
for (const line of checkout.lines) {
|
|
169
|
+
checkoutLinesByVariantId.set(line.variant.id, line);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const updatedItems: CartItem[] = items
|
|
173
|
+
.map((item: CartItem) => {
|
|
174
|
+
if (item.skipBaseProduct && item.selectedOptions?.length) {
|
|
175
|
+
// For items with skipBaseProduct, match using the first selectedOption's variant
|
|
176
|
+
const firstOptionLine = checkoutLinesByVariantId.get(item.selectedOptions[0].variantId);
|
|
177
|
+
if (!firstOptionLine) return null;
|
|
178
|
+
|
|
179
|
+
// Calculate total price from all option lines
|
|
180
|
+
let optionsTotalPrice = 0;
|
|
181
|
+
const updatedOptions = item.selectedOptions.map(opt => {
|
|
182
|
+
const optLine = checkoutLinesByVariantId.get(opt.variantId);
|
|
183
|
+
if (optLine) {
|
|
184
|
+
const lineTotal = optLine.totalPrice?.gross?.amount ?? 0;
|
|
185
|
+
const qty = Math.max(1, optLine.quantity);
|
|
186
|
+
const unitPrice = lineTotal / qty;
|
|
187
|
+
optionsTotalPrice += unitPrice;
|
|
188
|
+
return { ...opt, price: unitPrice };
|
|
189
|
+
}
|
|
190
|
+
return opt;
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
...item,
|
|
195
|
+
price: 0, // Base product not included
|
|
196
|
+
// Don't update quantity from Saleor - use the grouped quantity from store
|
|
197
|
+
// Saleor combines lines, so line.quantity would be wrong for grouped items
|
|
198
|
+
selectedOptions: updatedOptions,
|
|
199
|
+
};
|
|
200
|
+
} else {
|
|
201
|
+
// Normal item - match by item id (base product variant)
|
|
202
|
+
const line = checkoutLinesByVariantId.get(item.id);
|
|
203
|
+
if (!line) return null;
|
|
204
|
+
|
|
205
|
+
// Use line totalPrice divided by quantity to get the actual unit price
|
|
206
|
+
const lineTotal = line?.totalPrice?.gross?.amount ?? 0;
|
|
207
|
+
const qty = Math.max(1, line.quantity);
|
|
208
|
+
const unitPrice = lineTotal / qty;
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
...item,
|
|
212
|
+
price: unitPrice,
|
|
213
|
+
// Don't update quantity from Saleor - use the grouped quantity from store
|
|
214
|
+
// Saleor combines lines, so line.quantity would be wrong for grouped items
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
.filter(Boolean) as CartItem[];
|
|
219
|
+
|
|
220
|
+
const totalItems = updatedItems.reduce((sum, item) => sum + item.quantity, 0);
|
|
221
|
+
const totalAmount = updatedItems.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
|
|
222
|
+
|
|
223
|
+
setRefreshedItems(updatedItems);
|
|
224
|
+
setRefreshedTotals({ totalItems, totalAmount });
|
|
225
|
+
setPricesRefreshed(true);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error('[CartDropdown] Failed to refresh prices from checkout:', error);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
refreshPricesFromCheckout();
|
|
234
|
+
}, [checkoutId, endpoint, items, pricesRefreshed]);
|
|
235
|
+
|
|
236
|
+
// Reset prices refreshed state when global cart items change (e.g., when items are removed from cart page)
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (pricesRefreshed && refreshedItems.length > 0) {
|
|
239
|
+
// Check if any items in refreshedItems no longer exist in the global store (by key)
|
|
240
|
+
const hasRemovedItems = refreshedItems.some(refreshedItem =>
|
|
241
|
+
!items.find(globalItem => globalItem.key === refreshedItem.key)
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// Check if global items count differs from refreshed items count
|
|
245
|
+
const itemCountDiffers = items.length !== refreshedItems.length;
|
|
246
|
+
|
|
247
|
+
if (hasRemovedItems || itemCountDiffers) {
|
|
248
|
+
setPricesRefreshed(false);
|
|
249
|
+
setRefreshedItems([]);
|
|
250
|
+
setRefreshedTotals({ totalItems: 0, totalAmount: 0 });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}, [items, refreshedItems, pricesRefreshed]);
|
|
254
|
+
|
|
255
|
+
// Force refresh when cart becomes empty but dropdown still has cached items
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (items.length === 0 && (refreshedItems.length > 0 || pricesRefreshed)) {
|
|
258
|
+
setPricesRefreshed(false);
|
|
259
|
+
setRefreshedItems([]);
|
|
260
|
+
setRefreshedTotals({ totalItems: 0, totalAmount: 0 });
|
|
261
|
+
}
|
|
262
|
+
}, [items.length, refreshedItems.length, pricesRefreshed]);
|
|
263
|
+
|
|
264
|
+
// Use refreshed items and totals if available, otherwise fall back to store values
|
|
265
|
+
const displayItems = pricesRefreshed ? refreshedItems : items;
|
|
266
|
+
const displayTotalItems = pricesRefreshed ? refreshedTotals.totalItems : totalItems;
|
|
267
|
+
const displayTotalAmount = pricesRefreshed ? refreshedTotals.totalAmount : totalAmount;
|
|
268
|
+
|
|
269
|
+
// Track view_cart GTM event when cart dropdown is opened/items change
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
const gtmConfig = getGoogleTagManagerConfig();
|
|
272
|
+
const currentItems = pricesRefreshed ? refreshedItems : items;
|
|
273
|
+
if (currentItems.length > 0) {
|
|
274
|
+
const products: Product[] = currentItems.map((item, index) => ({
|
|
275
|
+
item_id: item.id,
|
|
276
|
+
item_name: item.name,
|
|
277
|
+
item_category: item.category || 'Products',
|
|
278
|
+
price: item.price,
|
|
279
|
+
quantity: item.quantity,
|
|
280
|
+
currency: 'USD',
|
|
281
|
+
index: index
|
|
282
|
+
}));
|
|
283
|
+
const totalValue = currentItems.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
|
|
284
|
+
gtmViewCart(products, 'USD', totalValue, gtmConfig?.container_id);
|
|
285
|
+
}
|
|
286
|
+
}, [items, refreshedItems, pricesRefreshed, getGoogleTagManagerConfig]);
|
|
287
|
+
|
|
288
|
+
// Enhanced cart functions that work with refreshed prices and use add to cart endpoint
|
|
289
|
+
// Now uses item.key for all identity operations to support different option sets
|
|
290
|
+
const handleRemoveFromCart = useCallback(async (itemKey: string) => {
|
|
291
|
+
setLoadingItems(prev => ({ ...prev, [itemKey]: { ...prev[itemKey], remove: true } }));
|
|
292
|
+
|
|
293
|
+
// Track remove_from_cart GTM event BEFORE removing the item
|
|
294
|
+
const gtmConfig = getGoogleTagManagerConfig();
|
|
295
|
+
const currentItems = pricesRefreshed ? refreshedItems : items;
|
|
296
|
+
const itemToRemove = currentItems.find(item => item.key === itemKey);
|
|
297
|
+
|
|
298
|
+
if (itemToRemove) {
|
|
299
|
+
const product: Product = {
|
|
300
|
+
item_id: itemToRemove.id,
|
|
301
|
+
item_name: itemToRemove.name,
|
|
302
|
+
item_category: itemToRemove.category || 'Products',
|
|
303
|
+
price: itemToRemove.price,
|
|
304
|
+
quantity: itemToRemove.quantity,
|
|
305
|
+
currency: 'USD',
|
|
306
|
+
index: 0
|
|
307
|
+
};
|
|
308
|
+
const itemValue = getItemTotal(itemToRemove) * itemToRemove.quantity;
|
|
309
|
+
gtmRemoveFromCart([product], 'USD', itemValue, gtmConfig?.container_id);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
if (pricesRefreshed) {
|
|
314
|
+
// Update both store and local refreshed state
|
|
315
|
+
setRefreshedItems(prev => prev.filter(item => item.key !== itemKey));
|
|
316
|
+
setRefreshedTotals(() => {
|
|
317
|
+
const filteredItems = refreshedItems.filter(item => item.key !== itemKey);
|
|
318
|
+
const totalItems = filteredItems.reduce((sum, item) => sum + item.quantity, 0);
|
|
319
|
+
const totalAmount = filteredItems.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
|
|
320
|
+
return { totalItems, totalAmount };
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
removeFromCart(itemKey);
|
|
324
|
+
|
|
325
|
+
// Small delay to ensure backend sync completes
|
|
326
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
327
|
+
} finally {
|
|
328
|
+
setLoadingItems(prev => ({ ...prev, [itemKey]: { ...prev[itemKey], remove: false } }));
|
|
329
|
+
}
|
|
330
|
+
}, [pricesRefreshed, refreshedItems, removeFromCart]);
|
|
331
|
+
|
|
332
|
+
const handleUpdateQuantity = useCallback(async (itemKey: string, newQuantity: number) => {
|
|
333
|
+
setLoadingItems(prev => ({ ...prev, [itemKey]: { ...prev[itemKey], minus: true } }));
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
if (pricesRefreshed) {
|
|
337
|
+
// Update both store and local refreshed state
|
|
338
|
+
setRefreshedItems(prev => {
|
|
339
|
+
const updated = prev.map(item =>
|
|
340
|
+
item.key === itemKey
|
|
341
|
+
? { ...item, quantity: Math.max(0, newQuantity) }
|
|
342
|
+
: item
|
|
343
|
+
).filter(item => item.quantity > 0);
|
|
344
|
+
|
|
345
|
+
// Update totals
|
|
346
|
+
const totalItems = updated.reduce((sum, item) => sum + item.quantity, 0);
|
|
347
|
+
const totalAmount = updated.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
|
|
348
|
+
setRefreshedTotals({ totalItems, totalAmount });
|
|
349
|
+
|
|
350
|
+
return updated;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
updateQuantity(itemKey, newQuantity);
|
|
354
|
+
|
|
355
|
+
// Small delay to ensure backend sync completes
|
|
356
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
357
|
+
} finally {
|
|
358
|
+
setLoadingItems(prev => ({ ...prev, [itemKey]: { ...prev[itemKey], minus: false } }));
|
|
359
|
+
}
|
|
360
|
+
}, [pricesRefreshed, updateQuantity]);
|
|
361
|
+
|
|
362
|
+
const handleAddToCart = useCallback(async (item: CartItem) => {
|
|
363
|
+
setLoadingItems(prev => ({ ...prev, [item.key]: { ...prev[item.key], plus: true } }));
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
await addToCart({
|
|
367
|
+
key: item.key,
|
|
368
|
+
id: item.id,
|
|
369
|
+
name: item.name,
|
|
370
|
+
price: item.price,
|
|
371
|
+
image: item.image,
|
|
372
|
+
quantity: 1,
|
|
373
|
+
sku: item.sku,
|
|
374
|
+
category: item.category,
|
|
375
|
+
selectedOptions: item.selectedOptions,
|
|
376
|
+
customInputs: item.customInputs,
|
|
377
|
+
skipBaseProduct: item.skipBaseProduct
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Reset price refresh state to force refresh with new data
|
|
381
|
+
setPricesRefreshed(false);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error('[CartDropdown] Failed to add item to cart:', error);
|
|
384
|
+
} finally {
|
|
385
|
+
setLoadingItems(prev => ({ ...prev, [item.key]: { ...prev[item.key], plus: false } }));
|
|
386
|
+
}
|
|
387
|
+
}, [addToCart]);
|
|
388
|
+
|
|
389
|
+
const handleProceed = useCallback(async () => {
|
|
390
|
+
setError(null);
|
|
391
|
+
if (isLoggedIn && meLoading) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Track begin_checkout GTM event
|
|
396
|
+
const gtmConfig = getGoogleTagManagerConfig();
|
|
397
|
+
if (displayItems.length > 0) {
|
|
398
|
+
const products: Product[] = displayItems.map((item, index) => ({
|
|
399
|
+
item_id: item.id,
|
|
400
|
+
item_name: item.name,
|
|
401
|
+
item_category: item.category || 'Products',
|
|
402
|
+
price: item.price,
|
|
403
|
+
quantity: item.quantity,
|
|
404
|
+
currency: 'USD',
|
|
405
|
+
index: index
|
|
406
|
+
}));
|
|
407
|
+
const totalValue = displayItems.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
|
|
408
|
+
gtmBeginCheckout(products, 'USD', totalValue, undefined, gtmConfig?.container_id);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// If we already have a checkout, use it instead of creating a new one
|
|
412
|
+
if (checkoutId) {
|
|
413
|
+
router.push(`/checkout?checkoutId=${encodeURIComponent(checkoutId)}`);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Otherwise, create a new checkout
|
|
418
|
+
setCreating(true);
|
|
419
|
+
try {
|
|
420
|
+
// Build lines from cart using the item id as variantId directly
|
|
421
|
+
const lines = displayItems.map((it) => ({ quantity: it.quantity, variantId: it.id }));
|
|
422
|
+
if (lines.length === 0) {
|
|
423
|
+
setCreating(false);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const email = (isLoggedIn ? (user?.email || meData?.me?.email || '') : guestEmail) || 'guest@example.com';
|
|
427
|
+
|
|
428
|
+
const mutation = CHECKOUT_CREATE;
|
|
429
|
+
type CheckoutLineInputTS = { variantId: string; quantity: number };
|
|
430
|
+
type CheckoutCreateInputTS = {
|
|
431
|
+
channel: string;
|
|
432
|
+
email: string;
|
|
433
|
+
lines: CheckoutLineInputTS[];
|
|
434
|
+
};
|
|
435
|
+
const input: CheckoutCreateInputTS = {
|
|
436
|
+
channel: process.env.NEXT_PUBLIC_SALEOR_CHANNEL || 'default-channel',
|
|
437
|
+
email,
|
|
438
|
+
lines,
|
|
439
|
+
};
|
|
440
|
+
const variables = { input };
|
|
441
|
+
const res = await fetch(endpoint, {
|
|
442
|
+
method: 'POST',
|
|
443
|
+
headers: { 'Content-Type': 'application/json' },
|
|
444
|
+
body: JSON.stringify({ query: mutation, variables }),
|
|
445
|
+
});
|
|
446
|
+
if (!res.ok) throw new Error('Failed to create checkout');
|
|
447
|
+
const json = await res.json();
|
|
448
|
+
const errs = json.data?.checkoutCreate?.errors;
|
|
449
|
+
if (errs && errs.length) throw new Error(errs[0]?.message || 'Checkout creation error');
|
|
450
|
+
const createdId = json.data?.checkoutCreate?.checkout?.id as string | undefined;
|
|
451
|
+
const createdToken = json.data?.checkoutCreate?.checkout?.token as string | undefined;
|
|
452
|
+
if (!createdId) throw new Error('No checkout id returned');
|
|
453
|
+
setCheckoutId(createdId);
|
|
454
|
+
if (createdToken) {
|
|
455
|
+
setCheckoutToken(createdToken);
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
localStorage.setItem('checkoutId', createdId);
|
|
459
|
+
if (createdToken) localStorage.setItem('checkoutToken', createdToken);
|
|
460
|
+
} catch { }
|
|
461
|
+
router.push(`/checkout?checkoutId=${encodeURIComponent(createdId)}`);
|
|
462
|
+
} catch (e) {
|
|
463
|
+
const msg = e instanceof Error ? e.message : 'Unable to proceed to checkout';
|
|
464
|
+
console.error('[Cart] handleProceed error:', e);
|
|
465
|
+
setError(msg);
|
|
466
|
+
} finally {
|
|
467
|
+
setCreating(false);
|
|
468
|
+
}
|
|
469
|
+
}, [checkoutId, endpoint, guestEmail, isLoggedIn, displayItems, meData, meLoading, router, setCheckoutId, setCheckoutToken, user?.email]);
|
|
470
|
+
|
|
471
|
+
if (displayItems.length === 0) {
|
|
472
|
+
return (
|
|
473
|
+
<div className="max-w-md w-full absolute right-20 top-20 pt-7 z-[100]">
|
|
474
|
+
<div className='bg-white shadow-[0_10px_20px_0_#0000001A] p-4'>
|
|
475
|
+
<p className='border-b border-[var(--color-secondary-200)] pb-2 font-secondary font-semibold text-xl text-[var(--color-secondary-800)]'>
|
|
476
|
+
MY CART
|
|
477
|
+
</p>
|
|
478
|
+
<EmptyState
|
|
479
|
+
icon={CartIcon}
|
|
480
|
+
iconContainer="p-5"
|
|
481
|
+
text="YOUR CART IS EMPTY"
|
|
482
|
+
textParagraph="Browse parts and accessories to get started."
|
|
483
|
+
className='min-h-96'
|
|
484
|
+
/>
|
|
485
|
+
</div>
|
|
486
|
+
</div >
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<div className="max-w-md w-full absolute right-20 top-20 pt-7 z-[100]">
|
|
492
|
+
<div className='bg-white shadow-[0_10px_20px_0_#0000001A] p-4'>
|
|
493
|
+
<div className="space-y-5">
|
|
494
|
+
<p className='border-b border-[var(--color-secondary-200)] pb-2 font-secondary font-semibold text-xl text-[var(--color-secondary-800)]'>
|
|
495
|
+
MY CART
|
|
496
|
+
</p>
|
|
497
|
+
<div className='space-y-4 max-h-[250px] overflow-y-auto pr-2'>
|
|
498
|
+
{displayItems.map((item: CartItem) => (
|
|
499
|
+
<div key={item.key} className="flex items-center gap-4 border-b border-[var(--color-secondary-200)] last:border-b-0 pb-4 last:pb-0">
|
|
500
|
+
<div className='w-full items-center gap-2 flex'>
|
|
501
|
+
<div className="relative size-[100px] flex-shrink-0">
|
|
502
|
+
<Image
|
|
503
|
+
src={item?.image || '/no-image-avail-large.png'}
|
|
504
|
+
alt={item?.name || 'Product Image'}
|
|
505
|
+
fill
|
|
506
|
+
className="object-contain"
|
|
507
|
+
/>
|
|
508
|
+
</div>
|
|
509
|
+
<div className="space-y-4">
|
|
510
|
+
<div className='space-y-1'>
|
|
511
|
+
<p className="text-xs font-normal text-[var(--color-secondary-800)]">{item.category ?? "N/A"}</p>
|
|
512
|
+
<h3 className="font-medium font-secondary text-sm text-[var(--color-secondary-800)] line-clamp-2">{item.name}</h3>
|
|
513
|
+
{/* Display selected options */}
|
|
514
|
+
{item.selectedOptions && item.selectedOptions.length > 0 && (
|
|
515
|
+
<div className="mt-1 space-y-0.5">
|
|
516
|
+
{item.selectedOptions.map((option, idx) => (
|
|
517
|
+
<p key={idx} className="text-xs text-[var(--color-secondary-600)]">
|
|
518
|
+
+ {option.name}
|
|
519
|
+
</p>
|
|
520
|
+
))}
|
|
521
|
+
</div>
|
|
522
|
+
)}
|
|
523
|
+
{/* Display custom inputs */}
|
|
524
|
+
{item.customInputs && Object.keys(item.customInputs).length > 0 && (
|
|
525
|
+
<div className="mt-1 space-y-0.5">
|
|
526
|
+
{Object.entries(item.customInputs).map(([key, value]) => (
|
|
527
|
+
<p key={key} className="text-xs text-[var(--color-secondary-600)]">
|
|
528
|
+
+ {key}: {value}
|
|
529
|
+
</p>
|
|
530
|
+
))}
|
|
531
|
+
</div>
|
|
532
|
+
)}
|
|
533
|
+
</div>
|
|
534
|
+
<p className="font-semibold text-base text-[var(--color-primary-600)] font-secondary">
|
|
535
|
+
${(getItemTotal(item) * item.quantity).toFixed(2)}
|
|
536
|
+
</p>
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
<div className='flex flex-col items-end gap-8'>
|
|
540
|
+
<CommonButton
|
|
541
|
+
onClick={() => handleRemoveFromCart(item.key)}
|
|
542
|
+
disabled={loadingItems[item.key]?.remove || loadingItems[item.key]?.plus || loadingItems[item.key]?.minus}
|
|
543
|
+
variant="tertiary"
|
|
544
|
+
className="p-0"
|
|
545
|
+
>
|
|
546
|
+
{loadingItems[item.key]?.remove ? 'Removing...' : 'Remove'}
|
|
547
|
+
</CommonButton>
|
|
548
|
+
<div className="flex items-center border border-[var(--color-secondary-200)] bg-[var(--color-secondary-50)] px-2 py-3 gap-2 min-w-32 justify-between">
|
|
549
|
+
<button
|
|
550
|
+
onClick={() => handleUpdateQuantity(item.key, item.quantity - 1)}
|
|
551
|
+
disabled={loadingItems[item.key]?.minus || loadingItems[item.key]?.plus || loadingItems[item.key]?.remove}
|
|
552
|
+
className={`border-r border-[var(--color-secondary-200)] [&>svg]:size-6 pr-2 transition-opacity ${
|
|
553
|
+
loadingItems[item.key]?.minus || loadingItems[item.key]?.plus || loadingItems[item.key]?.remove
|
|
554
|
+
? 'cursor-not-allowed opacity-50'
|
|
555
|
+
: 'cursor-pointer hover:opacity-75'
|
|
556
|
+
}`}
|
|
557
|
+
>
|
|
558
|
+
{loadingItems[item.key]?.minus ? (
|
|
559
|
+
<svg className="animate-spin size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
560
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
561
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
562
|
+
</svg>
|
|
563
|
+
) : (
|
|
564
|
+
SubtractIcon
|
|
565
|
+
)}
|
|
566
|
+
</button>
|
|
567
|
+
<span className='font-normal text-base font-secondary text-[var(--color-secondary-800)]'>{item.quantity}</span>
|
|
568
|
+
<button
|
|
569
|
+
onClick={() => handleAddToCart(item)}
|
|
570
|
+
disabled={loadingItems[item.key]?.plus || loadingItems[item.key]?.minus || loadingItems[item.key]?.remove}
|
|
571
|
+
className={`border-l border-[var(--color-secondary-200)] [&>svg]:size-6 pl-2 transition-opacity ${
|
|
572
|
+
loadingItems[item.key]?.plus || loadingItems[item.key]?.minus || loadingItems[item.key]?.remove
|
|
573
|
+
? 'cursor-not-allowed opacity-50'
|
|
574
|
+
: 'cursor-pointer hover:opacity-75'
|
|
575
|
+
}`}
|
|
576
|
+
>
|
|
577
|
+
{loadingItems[item.key]?.plus ? (
|
|
578
|
+
<svg className="animate-spin size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
579
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
580
|
+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
581
|
+
</svg>
|
|
582
|
+
) : (
|
|
583
|
+
PlusIcon
|
|
584
|
+
)}
|
|
585
|
+
</button>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
))}
|
|
591
|
+
</div>
|
|
592
|
+
<div className="gap-2 flex flex-col mb-4 font-normal text-base font-secondary text-[var(--color-secondary-600)] border-t border-[var(--color-secondary-200)] pt-4">
|
|
593
|
+
<div className="flex justify-between">
|
|
594
|
+
<span>Subtotal</span>
|
|
595
|
+
<span className='font-medium'>${displayTotalAmount.toFixed(2)}</span>
|
|
596
|
+
</div>
|
|
597
|
+
{/* <div className="flex justify-between pb-2">
|
|
598
|
+
<span>Tax</span>
|
|
599
|
+
<span className='font-medium'>N/A</span>
|
|
600
|
+
</div> */}
|
|
601
|
+
<div className="border-t border-[var(--color-secondary-200)] pt-4 flex justify-between text-xl text-[var(--color-secondary-600)] font-medium ">
|
|
602
|
+
<span>TOTAL</span>
|
|
603
|
+
<span className='font-semibold text-[var(--color-secondary-800)]'>${displayTotalAmount.toFixed(2)}</span>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
{error && (
|
|
607
|
+
<div className="mb-3 text-sm text-red-600">{error}</div>
|
|
608
|
+
)}
|
|
609
|
+
<div className='grid grid-cols-2 gap-4'>
|
|
610
|
+
<CommonButton
|
|
611
|
+
onClick={() => router.push("/cart")}
|
|
612
|
+
variant="secondary"
|
|
613
|
+
>
|
|
614
|
+
VIEW CART
|
|
615
|
+
</CommonButton>
|
|
616
|
+
<CommonButton
|
|
617
|
+
onClick={handleProceed}
|
|
618
|
+
disabled={creating || (isLoggedIn && meLoading)}
|
|
619
|
+
variant="primary"
|
|
620
|
+
>
|
|
621
|
+
{creating ? 'Loading..' : (isLoggedIn && meLoading ? 'Loading your address...' : 'CHECKOUT')}
|
|
622
|
+
</CommonButton>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
);
|
|
628
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { NewsLetterClient } from "../../reuseableUI/newsletter/newsletterClient";
|
|
5
|
+
|
|
6
|
+
export const FooterNewsletter = () => {
|
|
7
|
+
return (
|
|
8
|
+
<div className="flex flex-col gap-4 w-full max-w-md">
|
|
9
|
+
<h3 className="text-xl md:text-2xl font-primary italic font-black text-[var(--color-primary-600)] uppercase leading-tight">
|
|
10
|
+
JOIN THE LIST & UNLOCK 5% OFF.
|
|
11
|
+
</h3>
|
|
12
|
+
<p className="text-sm text-white font-secondary">
|
|
13
|
+
Sign up for insider deals, product releases, and expert advice for all your performance needs.
|
|
14
|
+
</p>
|
|
15
|
+
<div className="[&_form]:flex [&_form]:flex-row [&_form]:gap-0 [&_form>div:first-child]:flex-1 [&_input]:bg-[#262626] [&_input]:border-[#404040] [&_input]:text-white [&_input]:rounded-l-md [&_input]:rounded-r-none [&_input]:h-12 [&_button]:rounded-r-md [&_button]:rounded-l-none [&_button]:bg-[var(--color-primary-600)] [&_button]:text-white [&_button]:font-bold [&_button]:px-6 [&_button]:h-12 [&_button]:hover:bg-[var(--color-primary-700)] [&_main]:p-0 [&_main>div]:p-0 [&_main>div>div]:p-0 [&_main>div>div>div]:hidden [&_form>div:last-child]:w-auto [&_form>div:last-child]:flex-none">
|
|
16
|
+
<NewsLetterClient isModalNewsletter={true} buttonText="JOIN" />
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|