@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,31 @@
|
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
import { getStoreName } from "@/app/utils/branding"
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: `Blog - ${getStoreName()}`,
|
|
6
|
+
description: `Read tips, product reviews, guides, and industry news from ${getStoreName()} experts.`,
|
|
7
|
+
alternates: {
|
|
8
|
+
canonical: "/blog",
|
|
9
|
+
},
|
|
10
|
+
openGraph: {
|
|
11
|
+
title: `Blog - ${getStoreName()}`,
|
|
12
|
+
description: `Read tips, product reviews, guides, and industry news from ${getStoreName()} experts.`,
|
|
13
|
+
type: "website",
|
|
14
|
+
url: "/blog",
|
|
15
|
+
images: [{ url: "/Logo.png" }],
|
|
16
|
+
},
|
|
17
|
+
twitter: {
|
|
18
|
+
card: "summary_large_image",
|
|
19
|
+
title: `Blog - ${getStoreName()}`,
|
|
20
|
+
description: `Read tips, product reviews, guides, and industry news from ${getStoreName()} experts.`,
|
|
21
|
+
images: ["/Logo.png"],
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function BlogLayout({
|
|
26
|
+
children,
|
|
27
|
+
}: {
|
|
28
|
+
children: React.ReactNode
|
|
29
|
+
}) {
|
|
30
|
+
return children
|
|
31
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
|
|
2
|
+
import React, { Suspense } from "react"
|
|
3
|
+
import type { Metadata } from "next"
|
|
4
|
+
import Heading from "../components/reuseableUI/heading"
|
|
5
|
+
import Breadcrumb from "../components/reuseableUI/breadcrumb"
|
|
6
|
+
import BlogList from "../components/blog/BlogList"
|
|
7
|
+
import { fetchBlogPages } from "@/graphql/queries/getBlogs"
|
|
8
|
+
import { getStoreName } from "@/app/utils/branding"
|
|
9
|
+
import {
|
|
10
|
+
generateCollectionPageSchema,
|
|
11
|
+
generateBreadcrumbSchema,
|
|
12
|
+
} from "@/lib/schema"
|
|
13
|
+
|
|
14
|
+
export const metadata: Metadata = {
|
|
15
|
+
title: `Blog - ${getStoreName()}`,
|
|
16
|
+
description: "Read our latest articles, news, and insights about our products and services.",
|
|
17
|
+
alternates: {
|
|
18
|
+
canonical: "/blog",
|
|
19
|
+
},
|
|
20
|
+
openGraph: {
|
|
21
|
+
title: `Blog - ${getStoreName()}`,
|
|
22
|
+
description: "Read our latest articles, news, and insights about our products and services.",
|
|
23
|
+
type: "website",
|
|
24
|
+
url: "/blog",
|
|
25
|
+
images: [{ url: "/Logo.png" }],
|
|
26
|
+
},
|
|
27
|
+
twitter: {
|
|
28
|
+
card: "summary_large_image",
|
|
29
|
+
title: `Blog - ${getStoreName()}`,
|
|
30
|
+
description: "Read our latest articles, news, and insights about our products and services.",
|
|
31
|
+
images: ["/Logo.png"],
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default async function BlogPage() {
|
|
36
|
+
// Fetch blog posts from CMS
|
|
37
|
+
const blogs = await fetchBlogPages()
|
|
38
|
+
|
|
39
|
+
// Generate schema.org structured data
|
|
40
|
+
const collectionSchema = generateCollectionPageSchema(
|
|
41
|
+
'Blog',
|
|
42
|
+
'Read our latest articles, news, and insights about our products and services.',
|
|
43
|
+
'/blog'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const breadcrumbSchema = generateBreadcrumbSchema([
|
|
47
|
+
{ name: "Home", url: "/" },
|
|
48
|
+
{ name: "Blog", url: "/blog" },
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<main className="h-full w-full">
|
|
53
|
+
{/* Schema.org structured data */}
|
|
54
|
+
<script
|
|
55
|
+
type="application/ld+json"
|
|
56
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionSchema) }}
|
|
57
|
+
/>
|
|
58
|
+
<script
|
|
59
|
+
type="application/ld+json"
|
|
60
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
|
|
61
|
+
/>
|
|
62
|
+
<div className="container mx-auto max-w-[1276px]">
|
|
63
|
+
<div className="flex flex-col items-start w-full gap-16 px-4 md:px-6 py-12 md:py-16 lg:py-24">
|
|
64
|
+
<div className="flex flex-col items-start gap-5 w-full">
|
|
65
|
+
<Breadcrumb
|
|
66
|
+
items={[
|
|
67
|
+
{ text: "Home", link: "/" },
|
|
68
|
+
{ text: "BLOG", link: "/blog" },
|
|
69
|
+
]}
|
|
70
|
+
/>
|
|
71
|
+
<Heading as="h1" content="Blog" />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<Suspense fallback={<div>Loading blogs...</div>}>
|
|
75
|
+
<BlogList blogs={blogs} pageSize={9} />
|
|
76
|
+
</Suspense>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</main>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Breadcrumb from "@/app/components/reuseableUI/breadcrumb";
|
|
4
|
+
import EmptyState from "@/app/components/reuseableUI/emptyState";
|
|
5
|
+
import { ProductCard } from "@/app/components/reuseableUI/productCard";
|
|
6
|
+
import ItemsPerPageSelectClient from "@/app/components/shop/ItemsPerPageSelectClient";
|
|
7
|
+
import SearchFilterClient from "@/app/components/shop/SearchFilterClient";
|
|
8
|
+
import { PLSearchProduct, shopApi } from "@/lib/api/shop";
|
|
9
|
+
import { usePathname } from "next/navigation";
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
|
|
12
|
+
type ItemsPerPage = 10 | 20 | 50 | 100;
|
|
13
|
+
|
|
14
|
+
export default function BrandPageClient() {
|
|
15
|
+
const pathName = usePathname();
|
|
16
|
+
const route = pathName.split("/").slice(2).join("/");
|
|
17
|
+
const [itemsPerPage, setItemsPerPage] = useState<ItemsPerPage>(10);
|
|
18
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
19
|
+
const [products, setProducts] = useState<PLSearchProduct[]>([]);
|
|
20
|
+
const [loading, setLoading] = useState(false);
|
|
21
|
+
const [loadingMore, setLoadingMore] = useState(false);
|
|
22
|
+
const [categoryName, setCategoryName] = useState<string>("");
|
|
23
|
+
const [pagination, setPagination] = useState({
|
|
24
|
+
total: 0,
|
|
25
|
+
page: 1,
|
|
26
|
+
per_page: 10,
|
|
27
|
+
total_pages: 0,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const fetchProducts = async () => {
|
|
32
|
+
setLoading(true);
|
|
33
|
+
try {
|
|
34
|
+
const response = await shopApi.getProductsBySlug({
|
|
35
|
+
slug: route,
|
|
36
|
+
page: 1,
|
|
37
|
+
per_page: itemsPerPage,
|
|
38
|
+
search: searchQuery || undefined,
|
|
39
|
+
filterType: "brand_slug",
|
|
40
|
+
});
|
|
41
|
+
setProducts(response.products || []);
|
|
42
|
+
setPagination(response.pagination);
|
|
43
|
+
if (response.products && response.products.length > 0) {
|
|
44
|
+
setCategoryName(response.products[0].category_name || route);
|
|
45
|
+
} else {
|
|
46
|
+
setCategoryName(route);
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("Error fetching products:", error);
|
|
50
|
+
setProducts([]);
|
|
51
|
+
} finally {
|
|
52
|
+
setLoading(false);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
fetchProducts();
|
|
57
|
+
}, [itemsPerPage, searchQuery, route]);
|
|
58
|
+
|
|
59
|
+
const loadMore = async () => {
|
|
60
|
+
if (loadingMore || pagination.page >= pagination.total_pages) return;
|
|
61
|
+
|
|
62
|
+
setLoadingMore(true);
|
|
63
|
+
const nextPage = pagination.page + 1;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const response = await shopApi.getProductsBySlug({
|
|
67
|
+
slug: route,
|
|
68
|
+
page: nextPage,
|
|
69
|
+
per_page: itemsPerPage,
|
|
70
|
+
search: searchQuery || undefined,
|
|
71
|
+
filterType: "brand_slug",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const newProducts = response.products || [];
|
|
75
|
+
setProducts((prev) => [...prev, ...newProducts]);
|
|
76
|
+
setPagination(response.pagination);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error("Error loading more products:", error);
|
|
79
|
+
} finally {
|
|
80
|
+
setLoadingMore(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const Title = route.replace(/-/g, " ").toUpperCase();
|
|
85
|
+
|
|
86
|
+
const breadcrumbItems = [
|
|
87
|
+
{ text: "HOME", link: "/" },
|
|
88
|
+
{ text: "SHOP", link: "/products/all" },
|
|
89
|
+
{ text: Title },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="container mx-auto min-h-[100dvh] py-12 px-4 md:px-6 md:py-16 lg:py-24 lg:px-0 relative">
|
|
94
|
+
<div className="space-y-5">
|
|
95
|
+
<Breadcrumb items={breadcrumbItems} />
|
|
96
|
+
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between w-full gap-4">
|
|
97
|
+
<div className="space-y-2">
|
|
98
|
+
<p className="font-normal uppercase text-[var(--color-secondary-800)] text-xl md:text-3xl lg:text-5xl font-primary">
|
|
99
|
+
{Title}
|
|
100
|
+
</p>
|
|
101
|
+
{searchQuery && (
|
|
102
|
+
<div className="flex items-center gap-2 text-sm text-[var(--color-secondary-600)]">
|
|
103
|
+
<div className="flex items-center gap-2">
|
|
104
|
+
{pagination.total > 0
|
|
105
|
+
? `Found ${pagination.total} result${
|
|
106
|
+
pagination.total === 1 ? "" : "s"
|
|
107
|
+
} for "${searchQuery}" in ${categoryName || Title}`
|
|
108
|
+
: `No results found for "${searchQuery}" in ${
|
|
109
|
+
categoryName || Title
|
|
110
|
+
}`}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
{!searchQuery && (
|
|
115
|
+
<div className="flex items-center gap-2 text-sm text-[var(--color-secondary-600)]">
|
|
116
|
+
<div className="flex items-center gap-2">
|
|
117
|
+
{pagination.total} product{pagination.total === 1 ? "" : "s"}{" "}
|
|
118
|
+
in {Title}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
<div className="ml-auto">
|
|
124
|
+
<ItemsPerPageSelectClient
|
|
125
|
+
value={itemsPerPage}
|
|
126
|
+
onChange={setItemsPerPage}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
<section className="mt-10 relative min-h-[400px]">
|
|
132
|
+
{loading && (
|
|
133
|
+
<div className="h-[60vh] bg-white/70 z-10 flex items-center justify-center rounded-lg">
|
|
134
|
+
<div className="size-12 border-t-2 border-black rounded-full animate-spin" />
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
139
|
+
{loading ? null : products && products.length > 0 ? (
|
|
140
|
+
products.map((item) => (
|
|
141
|
+
<ProductCard
|
|
142
|
+
key={item.id}
|
|
143
|
+
id={item.id}
|
|
144
|
+
name={item.name}
|
|
145
|
+
image={item.primary_image || "/no-image-avail-large.png"}
|
|
146
|
+
href={`/product/${item.slug}`}
|
|
147
|
+
price={item.price_min || 0}
|
|
148
|
+
category_id={item.category_id || ""}
|
|
149
|
+
category={item.category_name || ""}
|
|
150
|
+
discount={
|
|
151
|
+
item.price_max &&
|
|
152
|
+
item.price_min &&
|
|
153
|
+
item.price_max > item.price_min
|
|
154
|
+
? item.price_max - item.price_min
|
|
155
|
+
: null
|
|
156
|
+
}
|
|
157
|
+
isFeatured={
|
|
158
|
+
item.collection_names?.includes("Best Sellers") || false
|
|
159
|
+
}
|
|
160
|
+
onSale={(item.price_max || 0) > (item.price_min || 0)}
|
|
161
|
+
/>
|
|
162
|
+
))
|
|
163
|
+
) : (
|
|
164
|
+
<EmptyState
|
|
165
|
+
text="No products found"
|
|
166
|
+
textParagraph="Try adjusting your search or filter to find what you're looking for."
|
|
167
|
+
className="col-span-full my-12"
|
|
168
|
+
/>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{!loading &&
|
|
173
|
+
pagination.page < pagination.total_pages &&
|
|
174
|
+
products.length > 0 && (
|
|
175
|
+
<div className="mt-10 flex justify-center">
|
|
176
|
+
<button
|
|
177
|
+
onClick={loadMore}
|
|
178
|
+
disabled={loadingMore}
|
|
179
|
+
className="px-4 py-2 bg-[var(--color-secondary-200)] text-gray-800 hover:opacity-80 disabled:opacity-60 transition-opacity"
|
|
180
|
+
>
|
|
181
|
+
{loadingMore ? "Loading..." : "Load More"}
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
</section>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
import { getStoreName } from "@/app/utils/branding"
|
|
3
|
+
|
|
4
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
5
|
+
return {
|
|
6
|
+
title: `Brand Products - ${getStoreName()}`,
|
|
7
|
+
description: `Browse products from this brand. Find genuine parts and accessories with manufacturer warranty.`,
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function BrandDetailLayout({
|
|
12
|
+
children,
|
|
13
|
+
}: {
|
|
14
|
+
children: React.ReactNode
|
|
15
|
+
}) {
|
|
16
|
+
return children
|
|
17
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import BrandPageClient from "./BrandPageClient";
|
|
2
|
+
import {
|
|
3
|
+
generateCollectionPageSchema,
|
|
4
|
+
generateBreadcrumbSchema,
|
|
5
|
+
} from "@/lib/schema";
|
|
6
|
+
import { Metadata } from "next";
|
|
7
|
+
import { Suspense } from "react";
|
|
8
|
+
import ServerProductGrid, {
|
|
9
|
+
type ServerGridProduct,
|
|
10
|
+
} from "@/app/components/seo/ServerProductGrid";
|
|
11
|
+
|
|
12
|
+
export const revalidate = 300;
|
|
13
|
+
|
|
14
|
+
type SearchParams = Record<string, string | string[] | undefined>;
|
|
15
|
+
|
|
16
|
+
function toTitleCaseFromSlug(input: string): string {
|
|
17
|
+
return input
|
|
18
|
+
.replace(/-/g, " ")
|
|
19
|
+
.replace(/\b\w/g, (l) => l.toUpperCase());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getFirstParam(v: string | string[] | undefined): string | undefined {
|
|
23
|
+
if (!v) return undefined;
|
|
24
|
+
return Array.isArray(v) ? v[0] : v;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toInt(v: string | undefined): number | undefined {
|
|
28
|
+
if (!v) return undefined;
|
|
29
|
+
const n = Number.parseInt(v, 10);
|
|
30
|
+
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function fetchBrandListing(input: {
|
|
34
|
+
brandSlug: string;
|
|
35
|
+
page: number;
|
|
36
|
+
perPage: number;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
products: ServerGridProduct[];
|
|
39
|
+
pagination?: { total?: number; page?: number; per_page?: number; total_pages?: number };
|
|
40
|
+
}> {
|
|
41
|
+
const base = process.env.NEXT_PUBLIC_PARTSLOGIC_URL;
|
|
42
|
+
if (!base) return { products: [] };
|
|
43
|
+
|
|
44
|
+
const url = new URL("/api/search/products", base);
|
|
45
|
+
url.searchParams.set("brand_slug", input.brandSlug);
|
|
46
|
+
url.searchParams.set("page", String(input.page));
|
|
47
|
+
url.searchParams.set("per_page", String(input.perPage));
|
|
48
|
+
|
|
49
|
+
const res = await fetch(url.toString(), {
|
|
50
|
+
headers: { Accept: "application/json" },
|
|
51
|
+
next: { revalidate },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!res.ok) return { products: [] };
|
|
55
|
+
const json = (await res.json()) as {
|
|
56
|
+
products?: ServerGridProduct[];
|
|
57
|
+
pagination?: { total?: number; page?: number; per_page?: number; total_pages?: number };
|
|
58
|
+
};
|
|
59
|
+
return { products: Array.isArray(json.products) ? json.products : [], pagination: json.pagination };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function generateMetadata({
|
|
63
|
+
params,
|
|
64
|
+
searchParams,
|
|
65
|
+
}: {
|
|
66
|
+
params: Promise<{ id: string }>;
|
|
67
|
+
searchParams?: Promise<SearchParams>;
|
|
68
|
+
}): Promise<Metadata> {
|
|
69
|
+
const { id } = await params;
|
|
70
|
+
const decodedId = decodeURIComponent(id);
|
|
71
|
+
const sp = searchParams ? await searchParams : undefined;
|
|
72
|
+
const page = toInt(getFirstParam(sp?.page));
|
|
73
|
+
|
|
74
|
+
const brandName = toTitleCaseFromSlug(decodedId);
|
|
75
|
+
const basePath = `/brand/${id}`;
|
|
76
|
+
const canonical = page && page > 1 ? `${basePath}?page=${page}` : basePath;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
title: `${brandName} | Shop`,
|
|
80
|
+
description: `Browse our ${brandName} collection. Find the best products from ${brandName}.`,
|
|
81
|
+
keywords: `${brandName}, brand, products, buy online`,
|
|
82
|
+
alternates: {
|
|
83
|
+
canonical,
|
|
84
|
+
},
|
|
85
|
+
openGraph: {
|
|
86
|
+
title: `${brandName} | Shop`,
|
|
87
|
+
description: `Browse our ${brandName} collection`,
|
|
88
|
+
type: "website",
|
|
89
|
+
url: canonical,
|
|
90
|
+
images: [{ url: "/Logo.png" }],
|
|
91
|
+
},
|
|
92
|
+
twitter: {
|
|
93
|
+
card: "summary_large_image",
|
|
94
|
+
title: `${brandName} | Shop`,
|
|
95
|
+
description: `Browse our ${brandName} collection`,
|
|
96
|
+
images: ["/Logo.png"],
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default async function BrandPage({
|
|
102
|
+
params,
|
|
103
|
+
searchParams,
|
|
104
|
+
}: {
|
|
105
|
+
params: Promise<{ id: string }>;
|
|
106
|
+
searchParams?: Promise<SearchParams>;
|
|
107
|
+
}) {
|
|
108
|
+
const { id } = await params;
|
|
109
|
+
const decodedId = decodeURIComponent(id);
|
|
110
|
+
const sp = searchParams ? await searchParams : undefined;
|
|
111
|
+
const page = toInt(getFirstParam(sp?.page)) ?? 1;
|
|
112
|
+
|
|
113
|
+
const brandName = toTitleCaseFromSlug(decodedId);
|
|
114
|
+
const listing = await fetchBrandListing({
|
|
115
|
+
brandSlug: decodedId,
|
|
116
|
+
page,
|
|
117
|
+
perPage: 12,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Generate schema.org structured data
|
|
121
|
+
const collectionSchema = generateCollectionPageSchema(
|
|
122
|
+
brandName,
|
|
123
|
+
`Browse our ${brandName} collection. Find the best products from ${brandName}.`,
|
|
124
|
+
`/brand/${id}`
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const breadcrumbSchema = generateBreadcrumbSchema([
|
|
128
|
+
{ name: "Home", url: "/" },
|
|
129
|
+
{ name: "Brands", url: "/brands" },
|
|
130
|
+
{ name: brandName, url: `/brand/${id}` },
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<>
|
|
135
|
+
{/* Schema.org structured data */}
|
|
136
|
+
<script
|
|
137
|
+
type="application/ld+json"
|
|
138
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionSchema) }}
|
|
139
|
+
/>
|
|
140
|
+
<script
|
|
141
|
+
type="application/ld+json"
|
|
142
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
|
|
143
|
+
/>
|
|
144
|
+
|
|
145
|
+
<main className="h-full w-full">
|
|
146
|
+
<div className="container mx-auto min-h-[100dvh] py-12 px-4 md:px-6 md:py-16 lg:py-24 lg:px-0">
|
|
147
|
+
<div className="space-y-5">
|
|
148
|
+
<div className="text-xs font-semibold text-[var(--color-secondary-600)]">
|
|
149
|
+
Home / Brands / {brandName}
|
|
150
|
+
</div>
|
|
151
|
+
<div className="space-y-2">
|
|
152
|
+
<h1 className="font-normal uppercase text-[var(--color-secondary-800)] text-xl md:text-3xl lg:text-5xl font-primary">
|
|
153
|
+
{brandName}
|
|
154
|
+
</h1>
|
|
155
|
+
<p className="text-sm text-[var(--color-secondary-600)]">
|
|
156
|
+
Browse products from {brandName}.
|
|
157
|
+
</p>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
{/* SSR fallback (visible with JS disabled) */}
|
|
161
|
+
<div className="ssr-only">
|
|
162
|
+
<ServerProductGrid products={listing.products || []} />
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
{/* Enhanced client experience (visible with JS enabled) */}
|
|
166
|
+
<div className="js-only">
|
|
167
|
+
<Suspense>
|
|
168
|
+
<BrandPageClient />
|
|
169
|
+
</Suspense>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</main>
|
|
174
|
+
</>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { brandSearchIcon } from "@/app/utils/svgs/brandsSearchIcon";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
|
|
6
|
+
interface Brand {
|
|
7
|
+
id: string | number;
|
|
8
|
+
name: string;
|
|
9
|
+
logo?: string;
|
|
10
|
+
slug?: string;
|
|
11
|
+
image?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface BrandsListingClientProps {
|
|
15
|
+
brands: Brand[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const BrandsListingClient = ({ brands }: BrandsListingClientProps) => {
|
|
19
|
+
const [filteredBrands, setFilteredBrands] = useState<Brand[]>(brands);
|
|
20
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (searchTerm.trim() === "") {
|
|
24
|
+
setFilteredBrands(brands);
|
|
25
|
+
} else {
|
|
26
|
+
const filtered = brands.filter((brand) =>
|
|
27
|
+
brand.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
28
|
+
);
|
|
29
|
+
setFilteredBrands(filtered);
|
|
30
|
+
}
|
|
31
|
+
}, [searchTerm, brands]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="w-full">
|
|
35
|
+
<div className="mb-8">
|
|
36
|
+
<div className="relative max-w-2xl">
|
|
37
|
+
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
38
|
+
{brandSearchIcon}
|
|
39
|
+
</div>
|
|
40
|
+
<input
|
|
41
|
+
type="text"
|
|
42
|
+
placeholder="Search brands..."
|
|
43
|
+
value={searchTerm}
|
|
44
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
45
|
+
className="block w-full pl-10 pr-3 py-3 border border-gray-300 leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-2 focus:ring-orange-500 focus:border-orange-500"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div className="mb-6">
|
|
51
|
+
<p className="text-gray-600 italic">
|
|
52
|
+
We carry over{" "}
|
|
53
|
+
<span className="font-semibold text-gray-900">{brands.length}</span>{" "}
|
|
54
|
+
aftermarket manufacturers!
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
{filteredBrands.length === 0 ? (
|
|
59
|
+
<div className="text-center py-12">
|
|
60
|
+
<p className="text-gray-500 text-lg">
|
|
61
|
+
No brands found matching "{searchTerm}"
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
) : (
|
|
65
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4">
|
|
66
|
+
{filteredBrands.map((brand) => (
|
|
67
|
+
<Link
|
|
68
|
+
href={`/brand/${brand.slug}`}
|
|
69
|
+
key={brand.id}
|
|
70
|
+
className="border border-gray-200 py-2 px-4 hover:shadow-lg transition-shadow duration-200 cursor-pointer bg-white flex flex-col items-center justify-center min-h-[140px]"
|
|
71
|
+
>
|
|
72
|
+
<div className="w-full h-24 mb-4 flex items-center justify-center">
|
|
73
|
+
{brand.logo || brand.image ? (
|
|
74
|
+
<img
|
|
75
|
+
src={brand.logo || brand.image}
|
|
76
|
+
alt={brand.name}
|
|
77
|
+
className="max-w-full max-h-full object-contain p-2"
|
|
78
|
+
/>
|
|
79
|
+
) : (
|
|
80
|
+
<div className="text-gray-300 text-4xl font-bold">
|
|
81
|
+
{brand.name.charAt(0)}
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<h3 className="text-center text-sm font-normal text-gray-900 line-clamp-2 font-secondary">
|
|
87
|
+
{brand.name}
|
|
88
|
+
</h3>
|
|
89
|
+
</Link>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default BrandsListingClient;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getStoreName } from "@/app/utils/branding";
|
|
2
|
+
import type { Metadata } from "next";
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: `Brands - ${getStoreName()}`,
|
|
6
|
+
description: `Explore the wide range of automotive brands available at ${getStoreName()}. Find parts and accessories from top manufacturers to enhance your vehicle's performance and style.`,
|
|
7
|
+
alternates: {
|
|
8
|
+
canonical: "/brands",
|
|
9
|
+
},
|
|
10
|
+
openGraph: {
|
|
11
|
+
title: `Brands - ${getStoreName()}`,
|
|
12
|
+
description: `Explore the wide range of automotive brands available at ${getStoreName()}. Find parts and accessories from top manufacturers to enhance your vehicle's performance and style.`,
|
|
13
|
+
type: "website",
|
|
14
|
+
url: "/brands",
|
|
15
|
+
images: [{ url: "/Logo.png" }],
|
|
16
|
+
},
|
|
17
|
+
twitter: {
|
|
18
|
+
card: "summary_large_image",
|
|
19
|
+
title: `Brands - ${getStoreName()}`,
|
|
20
|
+
description: `Explore the wide range of automotive brands available at ${getStoreName()}. Find parts and accessories from top manufacturers to enhance your vehicle's performance and style.`,
|
|
21
|
+
images: ["/Logo.png"],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default function BrandsLayout({
|
|
26
|
+
children,
|
|
27
|
+
}: {
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
}) {
|
|
30
|
+
return children;
|
|
31
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CategoryAPIType } from "@/lib/api/shop";
|
|
2
|
+
import Heading from "../components/reuseableUI/heading";
|
|
3
|
+
import BrandsListingClient from "./components/brandsListingClient";
|
|
4
|
+
|
|
5
|
+
async function fetchBrands(): Promise<CategoryAPIType[]> {
|
|
6
|
+
try {
|
|
7
|
+
const partsLogicUrl = process.env.NEXT_PUBLIC_PARTSLOGIC_URL || "";
|
|
8
|
+
const url = `${partsLogicUrl}/api/brands?page=1&per_page=100`;
|
|
9
|
+
|
|
10
|
+
const res = await fetch(url, {
|
|
11
|
+
headers: {
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
Accept: "application/json",
|
|
14
|
+
},
|
|
15
|
+
next: { revalidate: 3600 }, // Cache for 1 hour
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
throw new Error(`Failed to fetch brands: ${res.status}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const data = await res.json();
|
|
23
|
+
return data.brands || [];
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error("Error fetching brands:", error);
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default async function BrandsPage() {
|
|
31
|
+
const brands = await fetchBrands();
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="min-h-screen container mx-auto px-4 py-8 lg:py-14 space-y-6">
|
|
35
|
+
<Heading as="h1" content="Brands" />
|
|
36
|
+
|
|
37
|
+
<BrandsListingClient brands={brands} />
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|