@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,25 @@
|
|
|
1
|
+
const GRAPHQL_SUFFIX_RE = /\/graphql\/?$/i;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the Saleor GraphQL endpoint URL.
|
|
5
|
+
*
|
|
6
|
+
* This template requires `NEXT_PUBLIC_API_URL` to be set (single-tenant per deployment).
|
|
7
|
+
* We also normalize it so callers can safely pass either:
|
|
8
|
+
* - https://example.com/graphql
|
|
9
|
+
* - https://example.com/graphql/
|
|
10
|
+
* - https://example.com (we'll append /graphql/)
|
|
11
|
+
*/
|
|
12
|
+
export function getSaleorApiUrl(): string {
|
|
13
|
+
const raw = process.env.NEXT_PUBLIC_API_URL?.trim();
|
|
14
|
+
if (!raw) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Missing NEXT_PUBLIC_API_URL. Set it to your Saleor GraphQL endpoint (e.g. https://.../graphql/)."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (GRAPHQL_SUFFIX_RE.test(raw)) {
|
|
21
|
+
return raw.endsWith("/") ? raw : `${raw}/`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return `${raw.replace(/\/+$/, "")}/graphql/`;
|
|
25
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema.org structured data utilities
|
|
3
|
+
* Generate JSON-LD markup for SEO
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
interface Product {
|
|
7
|
+
id: string;
|
|
8
|
+
slug?: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
image?: string | string[];
|
|
12
|
+
price?: number;
|
|
13
|
+
currency?: string;
|
|
14
|
+
availability?: string;
|
|
15
|
+
sku?: string;
|
|
16
|
+
brand?: string;
|
|
17
|
+
rating?: number;
|
|
18
|
+
reviewCount?: number;
|
|
19
|
+
category?: {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface BreadcrumbItem {
|
|
26
|
+
name: string;
|
|
27
|
+
url: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generate Organization schema for homepage
|
|
32
|
+
*/
|
|
33
|
+
export function generateOrganizationSchema(
|
|
34
|
+
siteName: string,
|
|
35
|
+
siteUrl: string,
|
|
36
|
+
logoUrl?: string,
|
|
37
|
+
socialLinks?: string[]
|
|
38
|
+
) {
|
|
39
|
+
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || siteUrl).replace(
|
|
40
|
+
/\/$/,
|
|
41
|
+
""
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"@context": "https://schema.org",
|
|
46
|
+
"@type": "Organization",
|
|
47
|
+
name: siteName,
|
|
48
|
+
url: baseUrl,
|
|
49
|
+
logo: logoUrl
|
|
50
|
+
? {
|
|
51
|
+
"@type": "ImageObject",
|
|
52
|
+
url: `${baseUrl}${logoUrl}`,
|
|
53
|
+
}
|
|
54
|
+
: undefined,
|
|
55
|
+
sameAs: socialLinks || [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate WebSite schema with search action
|
|
61
|
+
*/
|
|
62
|
+
export function generateWebsiteSchema(
|
|
63
|
+
siteName: string,
|
|
64
|
+
siteUrl: string,
|
|
65
|
+
searchUrl?: string
|
|
66
|
+
) {
|
|
67
|
+
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || siteUrl).replace(
|
|
68
|
+
/\/$/,
|
|
69
|
+
""
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"@context": "https://schema.org",
|
|
74
|
+
"@type": "WebSite",
|
|
75
|
+
name: siteName,
|
|
76
|
+
url: baseUrl,
|
|
77
|
+
potentialAction: searchUrl
|
|
78
|
+
? {
|
|
79
|
+
"@type": "SearchAction",
|
|
80
|
+
target: {
|
|
81
|
+
"@type": "EntryPoint",
|
|
82
|
+
urlTemplate: `${baseUrl}${searchUrl}?q={search_term_string}`,
|
|
83
|
+
},
|
|
84
|
+
"query-input": "required name=search_term_string",
|
|
85
|
+
}
|
|
86
|
+
: undefined,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate Product schema
|
|
92
|
+
*/
|
|
93
|
+
export function generateProductSchema(product: Product) {
|
|
94
|
+
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000").replace(
|
|
95
|
+
/\/$/,
|
|
96
|
+
""
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const images = Array.isArray(product.image)
|
|
100
|
+
? product.image
|
|
101
|
+
: product.image
|
|
102
|
+
? [product.image]
|
|
103
|
+
: [];
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
"@context": "https://schema.org",
|
|
107
|
+
"@type": "Product",
|
|
108
|
+
name: product.name,
|
|
109
|
+
description: product.description || "",
|
|
110
|
+
image: images.map((img) =>
|
|
111
|
+
img.startsWith("http") ? img : `${baseUrl}${img}`
|
|
112
|
+
),
|
|
113
|
+
sku: product.sku || product.id,
|
|
114
|
+
category: product.category?.name,
|
|
115
|
+
offers: {
|
|
116
|
+
"@type": "Offer",
|
|
117
|
+
price: product.price?.toString() || "0",
|
|
118
|
+
priceCurrency: product.currency || "USD",
|
|
119
|
+
availability: product.availability
|
|
120
|
+
? `https://schema.org/${product.availability}`
|
|
121
|
+
: "https://schema.org/InStock",
|
|
122
|
+
url: `${baseUrl}/product/${product.slug}`,
|
|
123
|
+
},
|
|
124
|
+
brand: product.brand
|
|
125
|
+
? {
|
|
126
|
+
"@type": "Brand",
|
|
127
|
+
name: product.brand,
|
|
128
|
+
}
|
|
129
|
+
: undefined,
|
|
130
|
+
aggregateRating:
|
|
131
|
+
product.rating && product.reviewCount
|
|
132
|
+
? {
|
|
133
|
+
"@type": "AggregateRating",
|
|
134
|
+
ratingValue: product.rating.toString(),
|
|
135
|
+
reviewCount: product.reviewCount.toString(),
|
|
136
|
+
}
|
|
137
|
+
: undefined,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generate ItemList schema for product listings
|
|
143
|
+
*/
|
|
144
|
+
export function generateItemListSchema(products: Product[], listName: string) {
|
|
145
|
+
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000").replace(
|
|
146
|
+
/\/$/,
|
|
147
|
+
""
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
"@context": "https://schema.org",
|
|
152
|
+
"@type": "ItemList",
|
|
153
|
+
name: listName,
|
|
154
|
+
numberOfItems: products.length,
|
|
155
|
+
itemListElement: products.map((product, index) => ({
|
|
156
|
+
"@type": "ListItem",
|
|
157
|
+
position: index + 1,
|
|
158
|
+
item: {
|
|
159
|
+
"@type": "Product",
|
|
160
|
+
name: product.name,
|
|
161
|
+
url: `${baseUrl}/product/${product.id}`,
|
|
162
|
+
image: Array.isArray(product.image)
|
|
163
|
+
? product.image[0]
|
|
164
|
+
: product.image || "",
|
|
165
|
+
category: product.category?.name,
|
|
166
|
+
offers: {
|
|
167
|
+
"@type": "Offer",
|
|
168
|
+
price: product.price?.toString() || "0",
|
|
169
|
+
priceCurrency: product.currency || "USD",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
})),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Generate BreadcrumbList schema
|
|
178
|
+
*/
|
|
179
|
+
export function generateBreadcrumbSchema(items: BreadcrumbItem[]) {
|
|
180
|
+
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000").replace(
|
|
181
|
+
/\/$/,
|
|
182
|
+
""
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
"@context": "https://schema.org",
|
|
187
|
+
"@type": "BreadcrumbList",
|
|
188
|
+
itemListElement: items.map((item, index) => ({
|
|
189
|
+
"@type": "ListItem",
|
|
190
|
+
position: index + 1,
|
|
191
|
+
name: item.name,
|
|
192
|
+
item: item.url.startsWith("http") ? item.url : `${baseUrl}${item.url}`,
|
|
193
|
+
})),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Generate CollectionPage schema for category/collection pages
|
|
199
|
+
*/
|
|
200
|
+
export function generateCollectionPageSchema(
|
|
201
|
+
name: string,
|
|
202
|
+
description: string,
|
|
203
|
+
url: string
|
|
204
|
+
) {
|
|
205
|
+
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000").replace(
|
|
206
|
+
/\/$/,
|
|
207
|
+
""
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
"@context": "https://schema.org",
|
|
212
|
+
"@type": "CollectionPage",
|
|
213
|
+
name,
|
|
214
|
+
description,
|
|
215
|
+
url: url.startsWith("http") ? url : `${baseUrl}${url}`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Generate WebPage schema for product category pages with ItemList
|
|
221
|
+
*/
|
|
222
|
+
export function generateProductCategoryPageSchema(
|
|
223
|
+
name: string,
|
|
224
|
+
description: string,
|
|
225
|
+
url: string
|
|
226
|
+
) {
|
|
227
|
+
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000").replace(
|
|
228
|
+
/\/$/,
|
|
229
|
+
""
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
"@context": "https://schema.org",
|
|
234
|
+
"@type": "WebPage",
|
|
235
|
+
name: `${name} - Product Category`,
|
|
236
|
+
description,
|
|
237
|
+
url: url.startsWith("http") ? url : `${baseUrl}${url}`,
|
|
238
|
+
breadcrumb: {
|
|
239
|
+
"@type": "BreadcrumbList",
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Generate Blog schema for blog listing pages
|
|
246
|
+
*/
|
|
247
|
+
export function generateBlogSchema(
|
|
248
|
+
name: string,
|
|
249
|
+
description: string,
|
|
250
|
+
url: string
|
|
251
|
+
) {
|
|
252
|
+
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000").replace(
|
|
253
|
+
/\/$/,
|
|
254
|
+
""
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
"@context": "https://schema.org",
|
|
259
|
+
"@type": "Blog",
|
|
260
|
+
name,
|
|
261
|
+
description,
|
|
262
|
+
url: url.startsWith("http") ? url : `${baseUrl}${url}`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Generate BlogPosting schema for blog posts
|
|
268
|
+
*/
|
|
269
|
+
export function generateBlogPostingSchema(
|
|
270
|
+
title: string,
|
|
271
|
+
description: string,
|
|
272
|
+
url: string,
|
|
273
|
+
datePublished: string,
|
|
274
|
+
dateModified?: string,
|
|
275
|
+
authorName?: string,
|
|
276
|
+
image?: string
|
|
277
|
+
) {
|
|
278
|
+
const baseUrl = (process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000").replace(
|
|
279
|
+
/\/$/,
|
|
280
|
+
""
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
"@context": "https://schema.org",
|
|
285
|
+
"@type": "BlogPosting",
|
|
286
|
+
headline: title,
|
|
287
|
+
description,
|
|
288
|
+
url: url.startsWith("http") ? url : `${baseUrl}${url}`,
|
|
289
|
+
datePublished,
|
|
290
|
+
dateModified: dateModified || datePublished,
|
|
291
|
+
author: authorName
|
|
292
|
+
? {
|
|
293
|
+
"@type": "Person",
|
|
294
|
+
name: authorName,
|
|
295
|
+
}
|
|
296
|
+
: undefined,
|
|
297
|
+
image: image
|
|
298
|
+
? image.startsWith("http")
|
|
299
|
+
? image
|
|
300
|
+
: `${baseUrl}${image}`
|
|
301
|
+
: undefined,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function stripHtml(input: string): string {
|
|
2
|
+
return input.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
type EditorJsBlock =
|
|
6
|
+
| { type: "paragraph"; data?: { text?: string } }
|
|
7
|
+
| { type: "header"; data?: { text?: string } }
|
|
8
|
+
| { type: "quote"; data?: { text?: string } }
|
|
9
|
+
| { type: "list"; data?: { items?: string[] } }
|
|
10
|
+
| { type: string; data?: Record<string, unknown> };
|
|
11
|
+
|
|
12
|
+
type EditorJsDoc = { blocks?: EditorJsBlock[] };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Saleor pages/descriptions are often stored as Editor.js JSON (string).
|
|
16
|
+
* This extracts readable plaintext for meta descriptions / schema.
|
|
17
|
+
*/
|
|
18
|
+
export function extractTextFromEditorJs(input?: string | null): string {
|
|
19
|
+
if (!input) return "";
|
|
20
|
+
|
|
21
|
+
const trimmed = input.trim();
|
|
22
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
23
|
+
return stripHtml(trimmed);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(trimmed) as EditorJsDoc;
|
|
28
|
+
const blocks = Array.isArray(parsed?.blocks) ? parsed.blocks : [];
|
|
29
|
+
|
|
30
|
+
const parts: string[] = [];
|
|
31
|
+
for (const block of blocks) {
|
|
32
|
+
if (!block || typeof block !== "object") continue;
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
block.type === "paragraph" ||
|
|
36
|
+
block.type === "header" ||
|
|
37
|
+
block.type === "quote"
|
|
38
|
+
) {
|
|
39
|
+
const txt = stripHtml(String(block.data?.text || ""));
|
|
40
|
+
if (txt) parts.push(txt);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (block.type === "list") {
|
|
45
|
+
const items = Array.isArray(block.data?.items) ? block.data?.items : [];
|
|
46
|
+
for (const item of items) {
|
|
47
|
+
const txt = stripHtml(String(item || ""));
|
|
48
|
+
if (txt) parts.push(txt);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return stripHtml(parts.join(" "));
|
|
54
|
+
} catch {
|
|
55
|
+
return stripHtml(trimmed);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function getSiteUrl(): URL {
|
|
2
|
+
const raw = process.env.NEXT_PUBLIC_SITE_URL?.trim() || "http://localhost:3000";
|
|
3
|
+
return new URL(raw.endsWith("/") ? raw : `${raw}/`);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function canonicalPath(pathname: string): string {
|
|
7
|
+
if (!pathname.startsWith("/")) return `/${pathname}`;
|
|
8
|
+
return pathname;
|
|
9
|
+
}
|
|
10
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function normalizeInternalUrl(input: string): string {
|
|
2
|
+
const raw = input.trim();
|
|
3
|
+
if (!raw) return raw;
|
|
4
|
+
|
|
5
|
+
// Already relative.
|
|
6
|
+
if (raw.startsWith("/") && !raw.startsWith("//")) return raw;
|
|
7
|
+
|
|
8
|
+
// Try to interpret absolute URLs and strip the host when it matches our site.
|
|
9
|
+
try {
|
|
10
|
+
const siteRaw = process.env.NEXT_PUBLIC_SITE_URL?.trim();
|
|
11
|
+
const site = siteRaw ? new URL(siteRaw.endsWith("/") ? siteRaw : `${siteRaw}/`) : null;
|
|
12
|
+
|
|
13
|
+
const url = new URL(raw);
|
|
14
|
+
|
|
15
|
+
if (site && url.hostname === site.hostname) {
|
|
16
|
+
return `${url.pathname}${url.search}${url.hash}` || "/";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Saleor menus often include absolute storefront URLs; in templates the domain may differ
|
|
20
|
+
// between environments (localhost vs deployed). Only strip the host for *known* internal routes.
|
|
21
|
+
const internalPrefixes = [
|
|
22
|
+
"/",
|
|
23
|
+
"/about",
|
|
24
|
+
"/contact",
|
|
25
|
+
"/products",
|
|
26
|
+
"/product",
|
|
27
|
+
"/category",
|
|
28
|
+
"/blog",
|
|
29
|
+
"/content",
|
|
30
|
+
"/request-return",
|
|
31
|
+
"/account",
|
|
32
|
+
"/privacy-policy",
|
|
33
|
+
"/shipping-returns",
|
|
34
|
+
"/terms-and-conditions",
|
|
35
|
+
"/warranty",
|
|
36
|
+
"/frequently-asked-questions",
|
|
37
|
+
"/site-map",
|
|
38
|
+
"/discounts",
|
|
39
|
+
"/core-policies",
|
|
40
|
+
"/cancellation-policy",
|
|
41
|
+
"/brands",
|
|
42
|
+
"/brand",
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
if (internalPrefixes.some((p) => url.pathname === p || url.pathname.startsWith(`${p}/`))) {
|
|
46
|
+
return `${url.pathname}${url.search}${url.hash}` || "/";
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// fall through
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return raw;
|
|
53
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { NextRequest } from 'next/server';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import { jwtDecode } from 'jwt-decode';
|
|
4
|
+
import { fetchConfigurationDirect, isFeatureActive } from '@/app/utils/configurationService';
|
|
5
|
+
|
|
6
|
+
type JwtPayload = { exp?: number };
|
|
7
|
+
|
|
8
|
+
const AUTH_ROUTES = [
|
|
9
|
+
'/account/login',
|
|
10
|
+
'/account/register',
|
|
11
|
+
'/account/forgot-password',
|
|
12
|
+
'/account/reset-password',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const PROTECTED_PREFIXES = ['/account', '/orders', '/settings'];
|
|
16
|
+
|
|
17
|
+
// Feature route mappings
|
|
18
|
+
const FEATURE_ROUTES = {
|
|
19
|
+
'/locator': 'dealer_locator',
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export async function middleware(req: NextRequest) {
|
|
23
|
+
const { pathname } = req.nextUrl;
|
|
24
|
+
|
|
25
|
+
const normalizedPath =
|
|
26
|
+
pathname.endsWith('/') && pathname.length > 1
|
|
27
|
+
? pathname.slice(0, -1)
|
|
28
|
+
: pathname;
|
|
29
|
+
|
|
30
|
+
// Check feature route protection first
|
|
31
|
+
const featureName = FEATURE_ROUTES[normalizedPath as keyof typeof FEATURE_ROUTES];
|
|
32
|
+
if (featureName) {
|
|
33
|
+
try {
|
|
34
|
+
const configuration = await fetchConfigurationDirect();
|
|
35
|
+
const isActive = isFeatureActive(configuration, featureName);
|
|
36
|
+
|
|
37
|
+
if (!isActive) {
|
|
38
|
+
// Redirect to home page if feature is not active
|
|
39
|
+
const homeUrl = new URL('/', req.url);
|
|
40
|
+
const response = NextResponse.redirect(homeUrl);
|
|
41
|
+
|
|
42
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
43
|
+
if (!isProd) {
|
|
44
|
+
response.headers.set('x-middleware-redirect', `home:feature-disabled:${featureName}`);
|
|
45
|
+
}
|
|
46
|
+
return response;
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('Error checking feature configuration:', error);
|
|
50
|
+
// Allow access if there's an error (fail open)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const tokenCookie = req.cookies.get('token');
|
|
55
|
+
const refreshCookie = req.cookies.get('refreshToken');
|
|
56
|
+
|
|
57
|
+
let isTokenValid = false;
|
|
58
|
+
if (tokenCookie?.value) {
|
|
59
|
+
try {
|
|
60
|
+
const { exp } = jwtDecode<JwtPayload>(tokenCookie.value);
|
|
61
|
+
isTokenValid = !!exp && exp * 1000 > Date.now();
|
|
62
|
+
} catch {
|
|
63
|
+
isTokenValid = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const isLoggedIn = !!tokenCookie && isTokenValid;
|
|
68
|
+
|
|
69
|
+
const isAuthRoute = AUTH_ROUTES.some(
|
|
70
|
+
route => normalizedPath === route || normalizedPath.startsWith(route + '/')
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const isProtectedRoute =
|
|
74
|
+
PROTECTED_PREFIXES.some(
|
|
75
|
+
prefix => normalizedPath === prefix || normalizedPath.startsWith(prefix + '/')
|
|
76
|
+
) && !isAuthRoute;
|
|
77
|
+
|
|
78
|
+
// Debug headers only in non-prod and NEVER include token value
|
|
79
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
80
|
+
const debugHeaders: Record<string, string> = {
|
|
81
|
+
'x-pathname': normalizedPath,
|
|
82
|
+
'x-has-token': tokenCookie ? '1' : '0',
|
|
83
|
+
'x-has-refresh': refreshCookie ? '1' : '0',
|
|
84
|
+
'x-is-logged-in': isLoggedIn ? '1' : '0',
|
|
85
|
+
'x-is-auth-route': isAuthRoute ? '1' : '0',
|
|
86
|
+
'x-is-protected-route': isProtectedRoute ? '1' : '0',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// If token exists but is expired, redirect to clear-cookies API which will then redirect to login
|
|
90
|
+
if (tokenCookie && !isTokenValid) {
|
|
91
|
+
const loginUrl = new URL('/api/auth/clear-cookies', req.url);
|
|
92
|
+
loginUrl.searchParams.set('redirect', '/account/login');
|
|
93
|
+
loginUrl.searchParams.set('reason', 'token-expired');
|
|
94
|
+
const response = NextResponse.redirect(loginUrl);
|
|
95
|
+
if (!isProd) {
|
|
96
|
+
response.headers.set('x-middleware-redirect', 'login:token-expired');
|
|
97
|
+
Object.entries(debugHeaders).forEach(([k, v]) => response.headers.set(k, v));
|
|
98
|
+
}
|
|
99
|
+
return response;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isLoggedIn && isAuthRoute) {
|
|
103
|
+
const res = NextResponse.redirect(new URL('/', req.url));
|
|
104
|
+
if (!isProd) {
|
|
105
|
+
res.headers.set('x-middleware-redirect', 'home:auth-while-logged-in');
|
|
106
|
+
Object.entries(debugHeaders).forEach(([k, v]) => res.headers.set(k, v));
|
|
107
|
+
}
|
|
108
|
+
return res;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!isLoggedIn && isProtectedRoute) {
|
|
112
|
+
const loginUrl = new URL('/account/login', req.url);
|
|
113
|
+
loginUrl.searchParams.set('next', normalizedPath);
|
|
114
|
+
const res = NextResponse.redirect(loginUrl);
|
|
115
|
+
if (!isProd) {
|
|
116
|
+
res.headers.set('x-middleware-redirect', 'login:protected-while-logged-out');
|
|
117
|
+
Object.entries(debugHeaders).forEach(([k, v]) => res.headers.set(k, v));
|
|
118
|
+
}
|
|
119
|
+
return res;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const res = NextResponse.next();
|
|
123
|
+
if (!isProd) {
|
|
124
|
+
res.headers.set('x-middleware-hit', '1');
|
|
125
|
+
Object.entries(debugHeaders).forEach(([k, v]) => res.headers.set(k, v));
|
|
126
|
+
}
|
|
127
|
+
return res;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const config = {
|
|
131
|
+
matcher: [
|
|
132
|
+
'/((?!api|_next/static|_next/image|favicon.ico|images|manifest.webmanifest|sitemap.xml|robots.txt).*)',
|
|
133
|
+
],
|
|
134
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Sitemap Integration
|
|
2
|
+
|
|
3
|
+
Complete sitemap setup for all pages including static and dynamic content for Google, Yahoo, and Bing.
|
|
4
|
+
|
|
5
|
+
## Files Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/sitemaps/
|
|
9
|
+
├── static-pages-sitemap.ts # All static pages sitemap
|
|
10
|
+
├── dynamic-pages-sitemap.ts # Dynamic content (products, categories, etc.)
|
|
11
|
+
├── sitemap-index.ts # Configuration and search engine URLs
|
|
12
|
+
└── README.md # This documentation
|
|
13
|
+
|
|
14
|
+
src/app/
|
|
15
|
+
├── sitemap.ts # Main sitemap endpoint (combines all)
|
|
16
|
+
└── sitemap-index.xml/ # Sitemap index route
|
|
17
|
+
└── route.ts
|
|
18
|
+
|
|
19
|
+
public/robots.txt # Search engine crawler instructions
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Available URLs
|
|
23
|
+
|
|
24
|
+
- **Main Sitemap**: `http://localhost:3000/sitemap.xml`
|
|
25
|
+
- **Sitemap Index**: `http://localhost:3000/sitemap-index.xml`
|
|
26
|
+
- **Robots.txt**: `http://localhost:3000/robots.txt`
|
|
27
|
+
|
|
28
|
+
## Current Pages in Sitemap
|
|
29
|
+
|
|
30
|
+
### Static Pages (22 pages):
|
|
31
|
+
- **Main Pages**: Homepage, About, Contact, Shop, Categories, Products, Blog
|
|
32
|
+
- **Account Pages**: Login, Register, Password Reset, Account Dashboard, Orders, Settings, Addresses
|
|
33
|
+
- **Legal Pages**: Privacy Policy, Terms & Conditions, Warranty, Shipping & Returns
|
|
34
|
+
- **Support Pages**: FAQ, Store Locator
|
|
35
|
+
- **E-commerce**: Cart, Checkout, Order Confirmation
|
|
36
|
+
|
|
37
|
+
### Dynamic Pages (Ready for implementation):
|
|
38
|
+
- **Products**: `/product/[id]` - Product detail pages
|
|
39
|
+
- **Categories**: `/category/[slug]` - Category listing pages
|
|
40
|
+
- **Brands**: `/brand/[id]` - Brand pages
|
|
41
|
+
- **Blog Posts**: `/blog/[slug]` - Individual blog posts
|
|
42
|
+
- **Dynamic Pages**: `/[slug]` - CMS-managed pages
|
|
43
|
+
|
|
44
|
+
## Google Search Console Setup
|
|
45
|
+
|
|
46
|
+
1. **Add Property**: Go to [Google Search Console](https://search.google.com/search-console) and add your domain
|
|
47
|
+
2. **Verify Ownership**: Follow verification steps (HTML file upload, DNS, or meta tag)
|
|
48
|
+
3. **Submit Sitemaps**:
|
|
49
|
+
- Go to "Sitemaps" in the left menu
|
|
50
|
+
- Add sitemaps:
|
|
51
|
+
- `https://yourdomain.com/sitemap.xml` (main sitemap)
|
|
52
|
+
- `https://yourdomain.com/sitemap-index.xml` (sitemap index)
|
|
53
|
+
- Click "Submit"
|
|
54
|
+
|
|
55
|
+
## Other Search Engines
|
|
56
|
+
|
|
57
|
+
### Bing Webmaster Tools
|
|
58
|
+
1. Visit [Bing Webmaster Tools](https://www.bing.com/webmasters)
|
|
59
|
+
2. Add your site and verify ownership
|
|
60
|
+
3. Submit both sitemaps in "Sitemaps" section
|
|
61
|
+
|
|
62
|
+
### Yahoo Search
|
|
63
|
+
Yahoo uses Bing's index, so submitting to Bing covers Yahoo as well.
|
|
64
|
+
|
|
65
|
+
### Manual Ping (Optional)
|
|
66
|
+
Search engines can be notified directly:
|
|
67
|
+
- Google: `https://www.google.com/ping?sitemap=https://yourdomain.com/sitemap.xml`
|
|
68
|
+
- Bing: `https://www.bing.com/ping?sitemap=https://yourdomain.com/sitemap.xml`
|
|
69
|
+
|
|
70
|
+
## Production Setup
|
|
71
|
+
|
|
72
|
+
1. Update `NEXT_PUBLIC_SITE_URL` in your environment variables
|
|
73
|
+
2. Update robots.txt with your actual domain
|
|
74
|
+
3. Implement dynamic content fetching in `dynamic-pages-sitemap.ts`
|
|
75
|
+
4. Submit sitemaps to search engines using your production URL
|
|
76
|
+
|
|
77
|
+
## Adding Dynamic Content
|
|
78
|
+
|
|
79
|
+
To populate dynamic pages, implement the helper functions in `dynamic-pages-sitemap.ts`:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Example: Connect to your GraphQL API
|
|
83
|
+
export async function getProductSitemapEntries(): Promise<MetadataRoute.Sitemap> {
|
|
84
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'
|
|
85
|
+
const { data } = await client.query({ query: GET_ALL_PRODUCTS })
|
|
86
|
+
|
|
87
|
+
return data.products.map(product => ({
|
|
88
|
+
url: `${baseUrl}/product/${product.id}`,
|
|
89
|
+
lastModified: new Date(product.updatedAt),
|
|
90
|
+
changeFrequency: 'weekly' as const,
|
|
91
|
+
priority: 0.8,
|
|
92
|
+
}))
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## SEO Priority Guidelines
|
|
97
|
+
|
|
98
|
+
- **Priority 1.0**: Homepage
|
|
99
|
+
- **Priority 0.9**: Shop, Main category pages
|
|
100
|
+
- **Priority 0.8**: About, Product pages, Category pages, Blog
|
|
101
|
+
- **Priority 0.7**: Contact, Brand pages
|
|
102
|
+
- **Priority 0.6**: FAQ, Blog posts, Store locator
|
|
103
|
+
- **Priority 0.5**: Legal pages, Dynamic CMS pages
|
|
104
|
+
- **Priority 0.4**: Account pages (login, register)
|
|
105
|
+
- **Priority 0.3**: Password reset, OTP pages
|