@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.
Files changed (441) hide show
  1. package/.env.example +57 -0
  2. package/APPLE_PAY_QUICK_START.md +165 -0
  3. package/APPLE_PAY_SETUP.md +331 -0
  4. package/README.md +46 -0
  5. package/SEO_AUDIT_CHECKLIST_STATUS.md +244 -0
  6. package/SEO_AUDIT_REPORT.md +66 -0
  7. package/eslint.config.mjs +16 -0
  8. package/next-env.d.ts +5 -0
  9. package/next.config.ts +109 -0
  10. package/package.json +47 -0
  11. package/postcss.config.mjs +5 -0
  12. package/public/.well-known/apple-developer-merchantid-domain-association +1 -0
  13. package/public/Logo.png +0 -0
  14. package/public/brand-video.mp4 +0 -0
  15. package/public/favicon.ico +0 -0
  16. package/public/file.svg +1 -0
  17. package/public/footer/facebook.tsx +34 -0
  18. package/public/footer/instagram.tsx +27 -0
  19. package/public/footer/mail.tsx +5 -0
  20. package/public/footer/x.tsx +35 -0
  21. package/public/globe.svg +1 -0
  22. package/public/icons/Authorize.net.webp +0 -0
  23. package/public/icons/amex.gif +0 -0
  24. package/public/icons/appIcon.png +0 -0
  25. package/public/icons/discover.gif +0 -0
  26. package/public/icons/master.gif +0 -0
  27. package/public/icons/paypal.png +0 -0
  28. package/public/icons/stripe.png +0 -0
  29. package/public/icons/visa.gif +0 -0
  30. package/public/images/BackgroundNoise.png +0 -0
  31. package/public/images/footer-background.png +0 -0
  32. package/public/next.svg +1 -0
  33. package/public/no-image-avail-large.png +0 -0
  34. package/public/random-car-1.jpeg +0 -0
  35. package/public/random-car-2.png +0 -0
  36. package/public/random-car-3.jpg +0 -0
  37. package/public/random-car-4.jpg +0 -0
  38. package/public/random-car-5.jpg +0 -0
  39. package/public/star.svg +3 -0
  40. package/public/vercel.svg +1 -0
  41. package/public/window.svg +1 -0
  42. package/scripts/seo-audit/generate-checklist.mjs +156 -0
  43. package/src/app/(auth)/account/forgot-password/layout.tsx +16 -0
  44. package/src/app/(auth)/account/forgot-password/page.tsx +135 -0
  45. package/src/app/(auth)/account/login/layout.tsx +16 -0
  46. package/src/app/(auth)/account/login/page.tsx +288 -0
  47. package/src/app/(auth)/account/otp/layout.tsx +16 -0
  48. package/src/app/(auth)/account/otp/page.tsx +108 -0
  49. package/src/app/(auth)/account/register/layout.tsx +16 -0
  50. package/src/app/(auth)/account/register/page.tsx +431 -0
  51. package/src/app/(auth)/account/reset-password/layout.tsx +16 -0
  52. package/src/app/(auth)/account/reset-password/page.tsx +222 -0
  53. package/src/app/[slug]/page.tsx +43 -0
  54. package/src/app/about/loading.tsx +17 -0
  55. package/src/app/about/page.tsx +61 -0
  56. package/src/app/account/address/layout.tsx +15 -0
  57. package/src/app/account/address/page.tsx +166 -0
  58. package/src/app/account/head.tsx +4 -0
  59. package/src/app/account/layout.tsx +62 -0
  60. package/src/app/account/orders/[id]/layout.tsx +17 -0
  61. package/src/app/account/orders/[id]/page.tsx +115 -0
  62. package/src/app/account/orders/components/orderDetailsModal.tsx +410 -0
  63. package/src/app/account/orders/layout.tsx +15 -0
  64. package/src/app/account/orders/page.tsx +146 -0
  65. package/src/app/account/page.tsx +39 -0
  66. package/src/app/account/settings/components/editProfileSuccessModal.tsx +28 -0
  67. package/src/app/account/settings/layout.tsx +15 -0
  68. package/src/app/account/settings/page.tsx +260 -0
  69. package/src/app/api/affirm/check-status/route.ts +94 -0
  70. package/src/app/api/affirm/create-checkout/route.ts +109 -0
  71. package/src/app/api/affirm/get-config/route.ts +108 -0
  72. package/src/app/api/affirm/process-payment/route.ts +244 -0
  73. package/src/app/api/affirm/test-connection/route.ts +45 -0
  74. package/src/app/api/auth/clear/route.ts +16 -0
  75. package/src/app/api/auth/clear-cookies/route.ts +42 -0
  76. package/src/app/api/auth/set/route.ts +47 -0
  77. package/src/app/api/configuration/route.ts +18 -0
  78. package/src/app/api/dynamic-page/[slug]/route.ts +24 -0
  79. package/src/app/api/form-submission/route.ts +237 -0
  80. package/src/app/api/paypal/capture-order/route.ts +303 -0
  81. package/src/app/api/paypal/create-order/route.ts +211 -0
  82. package/src/app/api/paypal/get-config/route.ts +240 -0
  83. package/src/app/api/search-proxy/route.ts +52 -0
  84. package/src/app/authorize-net-success/layout.tsx +19 -0
  85. package/src/app/authorize-net-success/page.tsx +12 -0
  86. package/src/app/authorize-net-success/summary.tsx +486 -0
  87. package/src/app/blog/[slug]/blogContentRenderer.tsx +369 -0
  88. package/src/app/blog/[slug]/layout.tsx +17 -0
  89. package/src/app/blog/[slug]/page.tsx +151 -0
  90. package/src/app/blog/constant.tsx +147 -0
  91. package/src/app/blog/layout.tsx +31 -0
  92. package/src/app/blog/page.tsx +81 -0
  93. package/src/app/brand/[id]/BrandPageClient.tsx +188 -0
  94. package/src/app/brand/[id]/layout.tsx +17 -0
  95. package/src/app/brand/[id]/page.tsx +176 -0
  96. package/src/app/brands/components/brandsListingClient.tsx +97 -0
  97. package/src/app/brands/layout.tsx +31 -0
  98. package/src/app/brands/page.tsx +40 -0
  99. package/src/app/cancellation-policy/page.tsx +53 -0
  100. package/src/app/cart/layout.tsx +19 -0
  101. package/src/app/cart/page.tsx +752 -0
  102. package/src/app/category/[slug]/CategoryPageClient.tsx +377 -0
  103. package/src/app/category/[slug]/layout.tsx +17 -0
  104. package/src/app/category/[slug]/page.tsx +224 -0
  105. package/src/app/category/page.tsx +114 -0
  106. package/src/app/checkout/components/addNewAddressModal.tsx +474 -0
  107. package/src/app/checkout/layout.tsx +19 -0
  108. package/src/app/checkout/page.tsx +3312 -0
  109. package/src/app/components/account/AccountTabs.tsx +40 -0
  110. package/src/app/components/ads/GoogleAdSense.tsx +74 -0
  111. package/src/app/components/analytics/AnalyticsScripts.tsx +78 -0
  112. package/src/app/components/analytics/ConditionalGTMNoscript.tsx +24 -0
  113. package/src/app/components/analytics/ConditionalGoogleAnalytics.tsx +16 -0
  114. package/src/app/components/ancillary/AncillaryContent.tsx +7 -0
  115. package/src/app/components/auth/TokenExpirationHandler.tsx +8 -0
  116. package/src/app/components/blog/BlogList.tsx +112 -0
  117. package/src/app/components/checkout/AddressInformationSection.tsx +34 -0
  118. package/src/app/components/checkout/AddressManagement.tsx +571 -0
  119. package/src/app/components/checkout/CheckoutHeader.tsx +51 -0
  120. package/src/app/components/checkout/CheckoutQuestions.tsx +454 -0
  121. package/src/app/components/checkout/CheckoutTermsModal.tsx +81 -0
  122. package/src/app/components/checkout/ContactDetailsSection.tsx +52 -0
  123. package/src/app/components/checkout/DealerShippingSection.tsx +359 -0
  124. package/src/app/components/checkout/DeliveryMethodSection.tsx +249 -0
  125. package/src/app/components/checkout/OrderSummary.tsx +386 -0
  126. package/src/app/components/checkout/TermsContentRenderer.tsx +147 -0
  127. package/src/app/components/checkout/WillCallSection.tsx +133 -0
  128. package/src/app/components/checkout/affirmPayment.tsx +383 -0
  129. package/src/app/components/checkout/checkoutProcessingModal.tsx +96 -0
  130. package/src/app/components/checkout/googlePayButton.tsx +334 -0
  131. package/src/app/components/checkout/paymentStep.tsx +180 -0
  132. package/src/app/components/checkout/paypalPayment.tsx +1083 -0
  133. package/src/app/components/checkout/saleorNativePayment.tsx +1758 -0
  134. package/src/app/components/dynamicPage/DynamicPageRenderer.tsx +13 -0
  135. package/src/app/components/dynamicPage/HtmlWidgetRenderer.tsx +144 -0
  136. package/src/app/components/filtersCollapsible/index.tsx +365 -0
  137. package/src/app/components/globalSearch/index.tsx +423 -0
  138. package/src/app/components/layout/cartDropDown.tsx +628 -0
  139. package/src/app/components/layout/components/FooterNewsletter.tsx +21 -0
  140. package/src/app/components/layout/footer.tsx +283 -0
  141. package/src/app/components/layout/header/accountMenuDropdown.tsx +53 -0
  142. package/src/app/components/layout/header/components/CartBadge.tsx +18 -0
  143. package/src/app/components/layout/header/components/LoadingState.tsx +17 -0
  144. package/src/app/components/layout/header/components/MenuItemDropdown.tsx +124 -0
  145. package/src/app/components/layout/header/components/MobileNavbar.tsx +123 -0
  146. package/src/app/components/layout/header/components/NavbarActions.tsx +125 -0
  147. package/src/app/components/layout/header/components/NavbarBrand.tsx +29 -0
  148. package/src/app/components/layout/header/components/NavigationLinks.tsx +131 -0
  149. package/src/app/components/layout/header/hamMenuSlide.tsx +318 -0
  150. package/src/app/components/layout/header/header.tsx +44 -0
  151. package/src/app/components/layout/header/hooks/useDropdown.ts +45 -0
  152. package/src/app/components/layout/header/hooks/useNavbarData.ts +138 -0
  153. package/src/app/components/layout/header/hooks/useNavbarState.ts +66 -0
  154. package/src/app/components/layout/header/megaMenuDropdown.tsx +116 -0
  155. package/src/app/components/layout/header/navBar.tsx +121 -0
  156. package/src/app/components/layout/header/search.tsx +418 -0
  157. package/src/app/components/layout/header/styles/navbarStyles.ts +27 -0
  158. package/src/app/components/layout/header/topBar.tsx +214 -0
  159. package/src/app/components/layout/joinNewsletterForm/index.tsx +72 -0
  160. package/src/app/components/layout/mobileAccordian/index.tsx +92 -0
  161. package/src/app/components/layout/paymentMethods.tsx +75 -0
  162. package/src/app/components/layout/rootLayout.tsx +23 -0
  163. package/src/app/components/layout/siteInfo.tsx +103 -0
  164. package/src/app/components/layout/socialLinks.tsx +65 -0
  165. package/src/app/components/newsletterSection/emailListSection.tsx +224 -0
  166. package/src/app/components/newsletterSection/emailSectionServer.tsx +8 -0
  167. package/src/app/components/providers/ApolloWrapper.tsx +12 -0
  168. package/src/app/components/providers/AppConfigurationProvider.tsx +108 -0
  169. package/src/app/components/providers/GoogleAnalyticsProvider.tsx +149 -0
  170. package/src/app/components/providers/GoogleTagManagerProvider.tsx +31 -0
  171. package/src/app/components/providers/RecaptchaProvider.tsx +18 -0
  172. package/src/app/components/providers/ServerAppConfigurationProvider.tsx +133 -0
  173. package/src/app/components/providers/YMMStatusProvider.tsx +15 -0
  174. package/src/app/components/reuseableUI/AboutUs.tsx +115 -0
  175. package/src/app/components/reuseableUI/AddToCartClient.tsx +125 -0
  176. package/src/app/components/reuseableUI/EditorJsRenderer.tsx +219 -0
  177. package/src/app/components/reuseableUI/HeroSectionsearchByVehicle.tsx +188 -0
  178. package/src/app/components/reuseableUI/ImageWithFallback.tsx +41 -0
  179. package/src/app/components/reuseableUI/Toast.tsx +101 -0
  180. package/src/app/components/reuseableUI/blogCard.tsx +52 -0
  181. package/src/app/components/reuseableUI/brandCard.tsx +68 -0
  182. package/src/app/components/reuseableUI/breadcrumb.tsx +38 -0
  183. package/src/app/components/reuseableUI/categoryCard.tsx +37 -0
  184. package/src/app/components/reuseableUI/categorySkeleton.tsx +31 -0
  185. package/src/app/components/reuseableUI/commonButton.tsx +48 -0
  186. package/src/app/components/reuseableUI/defaultInputField/index.tsx +84 -0
  187. package/src/app/components/reuseableUI/emptyState.tsx +29 -0
  188. package/src/app/components/reuseableUI/errorTag.tsx +15 -0
  189. package/src/app/components/reuseableUI/heading/index.tsx +20 -0
  190. package/src/app/components/reuseableUI/input.tsx +117 -0
  191. package/src/app/components/reuseableUI/listCard.tsx +137 -0
  192. package/src/app/components/reuseableUI/loadingUI.tsx +12 -0
  193. package/src/app/components/reuseableUI/modalLayout.tsx +76 -0
  194. package/src/app/components/reuseableUI/newsletter/newsletterClient.tsx +622 -0
  195. package/src/app/components/reuseableUI/newsletter/newslettersHomeModal.tsx +68 -0
  196. package/src/app/components/reuseableUI/offerCard.tsx +42 -0
  197. package/src/app/components/reuseableUI/passwordRules/passwordRules.tsx +56 -0
  198. package/src/app/components/reuseableUI/primaryButton/index.tsx +34 -0
  199. package/src/app/components/reuseableUI/productCard.tsx +118 -0
  200. package/src/app/components/reuseableUI/productSkeleton.tsx +34 -0
  201. package/src/app/components/reuseableUI/searchByVehicle.tsx +187 -0
  202. package/src/app/components/reuseableUI/secondaryButton/index.tsx +34 -0
  203. package/src/app/components/reuseableUI/section.tsx +20 -0
  204. package/src/app/components/reuseableUI/select/index.tsx +98 -0
  205. package/src/app/components/reuseableUI/skeletonLoader.tsx +117 -0
  206. package/src/app/components/reuseableUI/statusTag.tsx +24 -0
  207. package/src/app/components/reuseableUI/tags/saleTag.tsx +19 -0
  208. package/src/app/components/reuseableUI/testimonialCard.tsx +93 -0
  209. package/src/app/components/richText/EditorRenderer.tsx +318 -0
  210. package/src/app/components/search/HierarchicalCategoryFilter.tsx +155 -0
  211. package/src/app/components/search/SearchFilters.tsx +155 -0
  212. package/src/app/components/search/YMMSearchSidebar.tsx +187 -0
  213. package/src/app/components/seo/ServerProductCard.tsx +91 -0
  214. package/src/app/components/seo/ServerProductGrid.tsx +45 -0
  215. package/src/app/components/shop/CategoryFilter.tsx +184 -0
  216. package/src/app/components/shop/ItemsPerPageSelect.tsx +69 -0
  217. package/src/app/components/shop/ItemsPerPageSelectClient.tsx +58 -0
  218. package/src/app/components/shop/MobileFilters.tsx +103 -0
  219. package/src/app/components/shop/ProductGridSkeleton.tsx +16 -0
  220. package/src/app/components/shop/ProductsGrid.tsx +230 -0
  221. package/src/app/components/shop/SearchFilter.tsx +218 -0
  222. package/src/app/components/shop/SearchFilterClient.tsx +122 -0
  223. package/src/app/components/shop/SearchLoadingOverlay.tsx +32 -0
  224. package/src/app/components/shop/ShopMobileFilters.tsx +205 -0
  225. package/src/app/components/showroom/VehicleSearchDropdowns.tsx +187 -0
  226. package/src/app/components/showroom/brandsSwiper.tsx +49 -0
  227. package/src/app/components/showroom/brandsSwiperClient copy.tsx +93 -0
  228. package/src/app/components/showroom/brandsSwiperClient.tsx +122 -0
  229. package/src/app/components/showroom/brandsSwiperServer.tsx +42 -0
  230. package/src/app/components/showroom/bundleProducts.tsx +120 -0
  231. package/src/app/components/showroom/categoryGrid.tsx +51 -0
  232. package/src/app/components/showroom/categoryGridServer.tsx +45 -0
  233. package/src/app/components/showroom/categorySwiper.tsx +115 -0
  234. package/src/app/components/showroom/featureStrip.tsx +139 -0
  235. package/src/app/components/showroom/offersSwiper.tsx +181 -0
  236. package/src/app/components/showroom/productGrid.tsx +56 -0
  237. package/src/app/components/showroom/productSwiper.tsx +119 -0
  238. package/src/app/components/showroom/promotion-slider.tsx +138 -0
  239. package/src/app/components/showroom/promotion.tsx +207 -0
  240. package/src/app/components/showroom/promotionsSwiper.tsx +174 -0
  241. package/src/app/components/showroom/showroomHeroCarousel.tsx +141 -0
  242. package/src/app/components/showroom/testimonialsGrid.tsx +106 -0
  243. package/src/app/components/skeletons/ContentSkeleton.tsx +14 -0
  244. package/src/app/components/sortDropdown/index.tsx +116 -0
  245. package/src/app/components/tertiaryButton/index.tsx +25 -0
  246. package/src/app/components/theme/theme-provider.tsx +82 -0
  247. package/src/app/contact/layout.tsx +32 -0
  248. package/src/app/contact/page.tsx +591 -0
  249. package/src/app/content/[slug]/layout.tsx +17 -0
  250. package/src/app/content/[slug]/page.tsx +159 -0
  251. package/src/app/content/layout.tsx +31 -0
  252. package/src/app/content/page.tsx +88 -0
  253. package/src/app/core-policies/page.tsx +55 -0
  254. package/src/app/discounts/page.tsx +54 -0
  255. package/src/app/frequently-asked-questions/page.tsx +57 -0
  256. package/src/app/globals.css +440 -0
  257. package/src/app/hooks/useDealerLocations.ts +259 -0
  258. package/src/app/hooks/useGTMEngagement.ts +71 -0
  259. package/src/app/hooks/useGoogleAnalytics.ts +145 -0
  260. package/src/app/layout.tsx +149 -0
  261. package/src/app/not-found.tsx +31 -0
  262. package/src/app/order-confirmation/layout.tsx +19 -0
  263. package/src/app/order-confirmation/page.tsx +12 -0
  264. package/src/app/order-confirmation/summary.tsx +1775 -0
  265. package/src/app/page.tsx +194 -0
  266. package/src/app/privacy-policy/loading.tsx +17 -0
  267. package/src/app/privacy-policy/page.tsx +56 -0
  268. package/src/app/product/[id]/ProductDetailClient.tsx +2448 -0
  269. package/src/app/product/[id]/components/itemInquiryModal.tsx +461 -0
  270. package/src/app/product/[id]/layout.tsx +116 -0
  271. package/src/app/product/[id]/page.tsx +200 -0
  272. package/src/app/product/layout.tsx +15 -0
  273. package/src/app/products/all/AllProductsClient.tsx +743 -0
  274. package/src/app/products/all/page.tsx +176 -0
  275. package/src/app/products/components/shopEmptyState.tsx +29 -0
  276. package/src/app/request-return/layout.tsx +36 -0
  277. package/src/app/request-return/page.tsx +597 -0
  278. package/src/app/robots.txt/route.ts +27 -0
  279. package/src/app/search/layout.tsx +16 -0
  280. package/src/app/search/page.tsx +736 -0
  281. package/src/app/shipping-returns/page.tsx +60 -0
  282. package/src/app/site-map/layout.tsx +33 -0
  283. package/src/app/site-map/page.tsx +113 -0
  284. package/src/app/sitemap-index.xml/route.ts +20 -0
  285. package/src/app/sitemap.ts +10 -0
  286. package/src/app/terms-and-conditions/loading.tsx +17 -0
  287. package/src/app/terms-and-conditions/page.tsx +56 -0
  288. package/src/app/utils/appConfiguration.ts +327 -0
  289. package/src/app/utils/branding.ts +52 -0
  290. package/src/app/utils/configurationService.ts +202 -0
  291. package/src/app/utils/constant.tsx +242 -0
  292. package/src/app/utils/editorJsUtils.tsx +249 -0
  293. package/src/app/utils/functions.ts +146 -0
  294. package/src/app/utils/googleAnalytics.ts +168 -0
  295. package/src/app/utils/googleTagManager.ts +475 -0
  296. package/src/app/utils/ipDetection.ts +270 -0
  297. package/src/app/utils/serverConfigurationService.ts +209 -0
  298. package/src/app/utils/svgs/GridIcon.tsx +45 -0
  299. package/src/app/utils/svgs/account/myAccount/listDotIcon.tsx +3 -0
  300. package/src/app/utils/svgs/account/myAccount/tickIcon.tsx +10 -0
  301. package/src/app/utils/svgs/account/orderHistory/InfoIcon.tsx +49 -0
  302. package/src/app/utils/svgs/arrowDownIcon.tsx +17 -0
  303. package/src/app/utils/svgs/arrowIcon.tsx +25 -0
  304. package/src/app/utils/svgs/arrowUpIcon.tsx +16 -0
  305. package/src/app/utils/svgs/brandsSearchIcon.tsx +25 -0
  306. package/src/app/utils/svgs/cart/cartIcon.tsx +31 -0
  307. package/src/app/utils/svgs/cart/plusIcon.tsx +13 -0
  308. package/src/app/utils/svgs/cart/subtractIcon.tsx +13 -0
  309. package/src/app/utils/svgs/cart/successTickIcon.tsx +14 -0
  310. package/src/app/utils/svgs/chevronDownIcon.tsx +21 -0
  311. package/src/app/utils/svgs/closeEyeIcon.tsx +47 -0
  312. package/src/app/utils/svgs/crossIcon.tsx +25 -0
  313. package/src/app/utils/svgs/eyeIcon.tsx +29 -0
  314. package/src/app/utils/svgs/featureTag.tsx +20 -0
  315. package/src/app/utils/svgs/filterIcon.tsx +3 -0
  316. package/src/app/utils/svgs/globleIcon.tsx +41 -0
  317. package/src/app/utils/svgs/infoIcon.tsx +34 -0
  318. package/src/app/utils/svgs/listIcon.tsx +50 -0
  319. package/src/app/utils/svgs/logOutIcon.tsx +35 -0
  320. package/src/app/utils/svgs/menuIcon.tsx +8 -0
  321. package/src/app/utils/svgs/minusIcon.tsx +18 -0
  322. package/src/app/utils/svgs/newsletterIcon.tsx +19 -0
  323. package/src/app/utils/svgs/noDataFoundIcon-.tsx +26 -0
  324. package/src/app/utils/svgs/noProductFoundIcon.tsx +43 -0
  325. package/src/app/utils/svgs/passwordIcons/errorIcon.tsx +31 -0
  326. package/src/app/utils/svgs/passwordIcons/successIcon.tsx +24 -0
  327. package/src/app/utils/svgs/paymentProcessingIcons/hourglassIcon.tsx +43 -0
  328. package/src/app/utils/svgs/paymentProcessingIcons/modalCrossIcon.tsx +23 -0
  329. package/src/app/utils/svgs/paymentProcessingIcons/paymentFailedIcon.tsx +47 -0
  330. package/src/app/utils/svgs/pencilIcon.tsx +11 -0
  331. package/src/app/utils/svgs/plusIcon.tsx +25 -0
  332. package/src/app/utils/svgs/productInquiryIcon.tsx +40 -0
  333. package/src/app/utils/svgs/searchIcon.tsx +31 -0
  334. package/src/app/utils/svgs/shoppingCart.tsx +32 -0
  335. package/src/app/utils/svgs/spinnerIcon.tsx +22 -0
  336. package/src/app/utils/svgs/spinnerLoadingIcon.tsx +26 -0
  337. package/src/app/utils/svgs/successTickIcon.tsx +40 -0
  338. package/src/app/utils/svgs/swiperArrowIconLeft.tsx +18 -0
  339. package/src/app/utils/svgs/swiperArrowIconRight.tsx +18 -0
  340. package/src/app/utils/svgs/userProfileIcon.tsx +31 -0
  341. package/src/app/utils/svgs/warningCircleIcon.tsx +15 -0
  342. package/src/app/warranty/constant.tsx +63 -0
  343. package/src/app/warranty/loading.tsx +17 -0
  344. package/src/app/warranty/page.tsx +56 -0
  345. package/src/graphql/client.ts +288 -0
  346. package/src/graphql/mutations/accountAddressCreate.ts +56 -0
  347. package/src/graphql/mutations/accountAddressDelete.ts +23 -0
  348. package/src/graphql/mutations/accountAddressUpdate.ts +55 -0
  349. package/src/graphql/mutations/accountSetDefaultAddress.ts +32 -0
  350. package/src/graphql/mutations/accountUpdate.ts +34 -0
  351. package/src/graphql/mutations/changePassword.ts +25 -0
  352. package/src/graphql/mutations/checkout.ts +117 -0
  353. package/src/graphql/mutations/checkoutAddVoucher.ts +63 -0
  354. package/src/graphql/mutations/checkoutComplete.ts +79 -0
  355. package/src/graphql/mutations/checkoutCreate.ts +131 -0
  356. package/src/graphql/mutations/checkoutCustomerAttach.ts +50 -0
  357. package/src/graphql/mutations/checkoutEmailUpdate.ts +15 -0
  358. package/src/graphql/mutations/checkoutLineMetadataUpdate.ts +52 -0
  359. package/src/graphql/mutations/checkoutPaymentCreate.ts +82 -0
  360. package/src/graphql/mutations/paymentGatewayInitialize.ts +58 -0
  361. package/src/graphql/mutations/registerAccount.ts +65 -0
  362. package/src/graphql/mutations/requestPasswordReset.ts +32 -0
  363. package/src/graphql/mutations/setPassword.ts +49 -0
  364. package/src/graphql/mutations/signIn.ts +50 -0
  365. package/src/graphql/mutations/tokenRefresh.ts +19 -0
  366. package/src/graphql/mutations/updateCheckoutMetadata.ts +49 -0
  367. package/src/graphql/mutations/updateProfile.ts +18 -0
  368. package/src/graphql/mutations/willCallDeliveryMethod.ts +81 -0
  369. package/src/graphql/queries/checkout.ts +168 -0
  370. package/src/graphql/queries/findProductByOldSlug.ts +58 -0
  371. package/src/graphql/queries/getAboutPage.ts +24 -0
  372. package/src/graphql/queries/getAboutPageId.ts +9 -0
  373. package/src/graphql/queries/getAboutUs.ts +38 -0
  374. package/src/graphql/queries/getAddressInformation.ts +38 -0
  375. package/src/graphql/queries/getAllCategories.ts +41 -0
  376. package/src/graphql/queries/getAllCategoriesTree.ts +67 -0
  377. package/src/graphql/queries/getAllCategoriesWithProducts.ts +29 -0
  378. package/src/graphql/queries/getAllCollectionsWithProducts.ts +16 -0
  379. package/src/graphql/queries/getBlogs.ts +222 -0
  380. package/src/graphql/queries/getBrands.ts +17 -0
  381. package/src/graphql/queries/getBundles.ts +43 -0
  382. package/src/graphql/queries/getCategories.ts +20 -0
  383. package/src/graphql/queries/getChannels.ts +77 -0
  384. package/src/graphql/queries/getCheckoutQuestions.ts +115 -0
  385. package/src/graphql/queries/getCheckoutTermsAndConditions.ts +37 -0
  386. package/src/graphql/queries/getContactPage.ts +117 -0
  387. package/src/graphql/queries/getContentPage.ts +191 -0
  388. package/src/graphql/queries/getDiscountOffers.ts +18 -0
  389. package/src/graphql/queries/getDynamicPageBySlug.ts +251 -0
  390. package/src/graphql/queries/getFeaturedProducts.ts +48 -0
  391. package/src/graphql/queries/getHeroMetadata.ts +23 -0
  392. package/src/graphql/queries/getMenuBySlug.ts +84 -0
  393. package/src/graphql/queries/getMyProfile.ts +23 -0
  394. package/src/graphql/queries/getNewsletter.ts +122 -0
  395. package/src/graphql/queries/getNewsletterPage.ts +111 -0
  396. package/src/graphql/queries/getPageBySlug.ts +52 -0
  397. package/src/graphql/queries/getPageTypeId.ts +27 -0
  398. package/src/graphql/queries/getPaymentMethods.ts +61 -0
  399. package/src/graphql/queries/getProducts.ts +78 -0
  400. package/src/graphql/queries/getPromotions.ts +24 -0
  401. package/src/graphql/queries/getRequestReturnPage.ts +121 -0
  402. package/src/graphql/queries/getSiteInfo.ts +54 -0
  403. package/src/graphql/queries/getSocialLinks.ts +52 -0
  404. package/src/graphql/queries/getTestimonials.ts +25 -0
  405. package/src/graphql/queries/getUserWithCheckout.ts +27 -0
  406. package/src/graphql/queries/getVehicleMakes.ts +21 -0
  407. package/src/graphql/queries/getVehicleModels.ts +21 -0
  408. package/src/graphql/queries/getVehicleYears.ts +21 -0
  409. package/src/graphql/queries/meAddresses.ts +56 -0
  410. package/src/graphql/queries/myOrders.ts +37 -0
  411. package/src/graphql/queries/orderDetail.ts +231 -0
  412. package/src/graphql/queries/productDetailsById.ts +197 -0
  413. package/src/graphql/queries/productInquiry.ts +115 -0
  414. package/src/graphql/queries/productsByCategoriesAndCollections.ts +39 -0
  415. package/src/graphql/queries/willCallCollectionPoints.ts +55 -0
  416. package/src/graphql/server-client.ts +54 -0
  417. package/src/graphql/types/categories.ts +9 -0
  418. package/src/graphql/types/checkout.ts +168 -0
  419. package/src/graphql/types/offer.ts +12 -0
  420. package/src/graphql/types/product.ts +44 -0
  421. package/src/hooks/scrollPageTop.ts +9 -0
  422. package/src/hooks/serverNavbarData.ts +79 -0
  423. package/src/hooks/useCartSync.ts +24 -0
  424. package/src/hooks/useRecaptcha.ts +33 -0
  425. package/src/hooks/useTokenExpiration.ts +81 -0
  426. package/src/hooks/useVehicleData.ts +346 -0
  427. package/src/lib/api/kount.ts +165 -0
  428. package/src/lib/api/shop.ts +1445 -0
  429. package/src/lib/saleor/getSaleorApiUrl.ts +25 -0
  430. package/src/lib/schema.ts +303 -0
  431. package/src/lib/seo/extractTextFromEditorJs.ts +58 -0
  432. package/src/lib/seo/site.ts +10 -0
  433. package/src/lib/urls/normalizeInternalUrl.ts +53 -0
  434. package/src/middleware.ts +134 -0
  435. package/src/sitemaps/README.md +105 -0
  436. package/src/sitemaps/dynamic-pages-sitemap.ts +247 -0
  437. package/src/sitemaps/sitemap-index.ts +21 -0
  438. package/src/sitemaps/static-pages-sitemap.ts +36 -0
  439. package/src/store/useGlobalStore.tsx +1656 -0
  440. package/src/types/global.d.ts +148 -0
  441. package/tsconfig.json +27 -0
@@ -0,0 +1,1445 @@
1
+ const BASE_URL = process.env.NEXT_PUBLIC_SEARCH_URL || "";
2
+ const PARTSLOGIC_URL = process.env.NEXT_PUBLIC_PARTSLOGIC_URL || "";
3
+ const TENANT = process.env.NEXT_PUBLIC_TENANT_NAME;
4
+
5
+ /* ----------------- Search + Product Types ----------------- */
6
+ export type Category = {
7
+ id: number | string;
8
+ name: string;
9
+ description?: string | null;
10
+ image?: number | null;
11
+ image_id?: number | null;
12
+ image_url?: string | null;
13
+ product_count?: number;
14
+ active_product_count?: number;
15
+ children?: Category[];
16
+ priority?: number;
17
+ };
18
+ export type Brand = { name: string; product_count: number; slug: string };
19
+ export type YMMCombinations = {
20
+ years: Array<{ year: number; product_count: number }>;
21
+ makes: Array<{ name: string; product_count: number; slug: string }>;
22
+ models: Array<{ name: string; product_count: number; slug: string }>;
23
+ };
24
+
25
+ // YMM API specific types
26
+ export type YMMYear = { year: number; product_count: number };
27
+ export type YMMMake = { id: number; value: string; product_count: number };
28
+ export type YMMModel = { id: number; value: string; product_count: number };
29
+ export type Product = {
30
+ id: string;
31
+ name: string;
32
+ description: string;
33
+ price: number;
34
+ price_max: number;
35
+ currency: string;
36
+ brand: string;
37
+ categories: string[];
38
+ sku: string[];
39
+ in_stock: boolean;
40
+ stock_quantity: number;
41
+ rating: number;
42
+ image_url: string;
43
+ images: string[];
44
+ tenant_id: string;
45
+ tenant_name: string;
46
+ years: string[];
47
+ makes: string[];
48
+ models: string[];
49
+ slug?: string;
50
+ };
51
+ export type ProductDetail = Product & {
52
+ all_images: string[];
53
+ availability_status: string;
54
+ available_in_tenants: string[];
55
+ category_descriptions: Record<string, string>;
56
+ category_hierarchy: Array<{
57
+ id: number | string;
58
+ title: string;
59
+ description: string | null;
60
+ image_id: number | null;
61
+ parent_title: string | null;
62
+ }>;
63
+ category_images: Record<string, string>;
64
+ category_metadata: Array<{
65
+ id: number | string;
66
+ name: string;
67
+ description: string | null;
68
+ image_id: number | null;
69
+ image_url: string | null;
70
+ parent_name: string | null;
71
+ }>;
72
+ collections: Array<{ id: string; name: string }>;
73
+ created_at: string;
74
+ created_at_timestamp: number;
75
+ cross_tenant_availability: boolean;
76
+ description_plaintext: string;
77
+ is_primary_tenant: boolean;
78
+ media_url: string;
79
+ parent_categories: string[];
80
+ price_min: number;
81
+ product_type: string;
82
+ raw_availability: string;
83
+ search_document: string;
84
+ thumbnail_url: string;
85
+ updated_at: string;
86
+ variant_count: number;
87
+ variant_names: string[];
88
+ };
89
+ export type ProductsResponse = {
90
+ products: Product[];
91
+ total_products: number;
92
+ search_time_ms: number;
93
+ filters: {
94
+ categories: Array<{ name: string; count: number }>;
95
+ brand: Array<{ name: string; count: number }>;
96
+ years: Array<{ name: string; count: number }>;
97
+ makes: Array<{ name: string; count: number }>;
98
+ models: Array<{ name: string; count: number }>;
99
+ price_min: Array<{ name: string; count: number }>;
100
+ in_stock: Array<{ name: string; count: number }>;
101
+ };
102
+ pagination: { current_page: number; per_page: number; total_pages: number };
103
+ tenant: { id: string; name: string };
104
+ };
105
+
106
+ /* ----------------- GraphQL Types ----------------- */
107
+ export type GraphQLProduct = {
108
+ id: string;
109
+ name: string;
110
+ slug: string;
111
+ description: string;
112
+ category: {
113
+ id: string;
114
+ name: string;
115
+ } | null;
116
+ productType: {
117
+ id: string;
118
+ name: string;
119
+ } | null;
120
+ media: Array<{
121
+ id: string;
122
+ url: string;
123
+ alt: string;
124
+ }>;
125
+ pricing: {
126
+ priceRange: {
127
+ start: {
128
+ gross: {
129
+ amount: number;
130
+ currency: string;
131
+ };
132
+ };
133
+ stop: {
134
+ gross: {
135
+ amount: number;
136
+ currency: string;
137
+ };
138
+ };
139
+ };
140
+ } | null;
141
+ };
142
+
143
+ export type GraphQLProductsResponse = {
144
+ products: {
145
+ totalCount: number;
146
+ pageInfo: {
147
+ hasNextPage: boolean;
148
+ hasPreviousPage: boolean;
149
+ endCursor: string | null;
150
+ startCursor: string | null;
151
+ };
152
+ edges: Array<{
153
+ cursor: string;
154
+ node: GraphQLProduct;
155
+ }>;
156
+ };
157
+ };
158
+
159
+ export type GraphQLCategory = {
160
+ id: string;
161
+ name: string;
162
+ slug: string;
163
+ level: number;
164
+ parent: {
165
+ id: string;
166
+ name: string;
167
+ } | null;
168
+ backgroundImage: {
169
+ url: string;
170
+ alt: string | null;
171
+ } | null;
172
+ products: {
173
+ totalCount: number;
174
+ };
175
+ };
176
+
177
+ export type GraphQLCategoriesResponse = {
178
+ categories: {
179
+ totalCount: number;
180
+ pageInfo: {
181
+ hasNextPage: boolean;
182
+ hasPreviousPage: boolean;
183
+ startCursor: string | null;
184
+ endCursor: string | null;
185
+ };
186
+ edges: Array<{
187
+ node: GraphQLCategory;
188
+ }>;
189
+ };
190
+ };
191
+
192
+ export type GraphQLProductType = {
193
+ id: string;
194
+ name: string;
195
+ slug: string;
196
+ hasVariants: boolean;
197
+ isShippingRequired: boolean;
198
+ kind: string;
199
+ metadata: Array<{
200
+ key: string;
201
+ value: string;
202
+ }>;
203
+ products: {
204
+ totalCount: number;
205
+ };
206
+ };
207
+
208
+ export type GraphQLProductTypesResponse = {
209
+ productTypes: {
210
+ totalCount: number;
211
+ edges: Array<{
212
+ node: GraphQLProductType;
213
+ }>;
214
+ };
215
+ };
216
+
217
+ export type GlobalSearchProduct = {
218
+ id: string;
219
+ name: string;
220
+ slug: string;
221
+ updatedAt: string;
222
+ category: {
223
+ id: string;
224
+ name: string;
225
+ } | null;
226
+ thumbnail: {
227
+ url: string;
228
+ alt: string | null;
229
+ } | null;
230
+ };
231
+
232
+ export type GlobalSearchCategory = {
233
+ id: string;
234
+ name: string;
235
+ slug: string;
236
+ level: number;
237
+ parent: {
238
+ id: string;
239
+ name: string;
240
+ } | null;
241
+ backgroundImage: {
242
+ url: string;
243
+ alt: string | null;
244
+ } | null;
245
+ products: {
246
+ totalCount: number;
247
+ };
248
+ };
249
+
250
+ export type GlobalSearchCollection = {
251
+ id: string;
252
+ name: string;
253
+ slug: string;
254
+ backgroundImage: {
255
+ url: string;
256
+ alt: string | null;
257
+ } | null;
258
+ products: {
259
+ totalCount: number;
260
+ };
261
+ };
262
+
263
+ export type GlobalSearchProductType = {
264
+ id: string;
265
+ name: string;
266
+ slug: string;
267
+ hasVariants: boolean;
268
+ };
269
+
270
+ export type GlobalSearchResponse = {
271
+ products?: {
272
+ edges: Array<{
273
+ node: GlobalSearchProduct;
274
+ }>;
275
+ };
276
+ categories?: {
277
+ edges: Array<{
278
+ node: GlobalSearchCategory;
279
+ }>;
280
+ };
281
+ collections?: {
282
+ edges: Array<{
283
+ node: GlobalSearchCollection;
284
+ }>;
285
+ };
286
+ productTypes?: {
287
+ edges: Array<{
288
+ node: GlobalSearchProductType;
289
+ }>;
290
+ };
291
+ };
292
+
293
+ // PartsLogic Search Products API Types
294
+
295
+ export type PLSearchProduct = {
296
+ id: string;
297
+ name: string;
298
+ slug: string;
299
+ description?: string;
300
+ primary_image?: string;
301
+ category_id?: string;
302
+ category_name?: string;
303
+ category_slug?: string;
304
+ brand_id?: string;
305
+ brand_name?: string;
306
+ brand_slug?: string;
307
+ category: {
308
+ id: string;
309
+ name: string;
310
+ } | null;
311
+ productType?: {
312
+ id: string;
313
+ name: string;
314
+ slug: string;
315
+ } | null;
316
+ thumbnail?: {
317
+ url: string;
318
+ alt: string;
319
+ } | null;
320
+ pricing?: {
321
+ priceRange: {
322
+ start: {
323
+ gross: {
324
+ amount: number;
325
+ currency: string;
326
+ };
327
+ };
328
+ stop: {
329
+ gross: {
330
+ amount: number;
331
+ currency: string;
332
+ };
333
+ };
334
+ };
335
+ } | null;
336
+ price_min?: number;
337
+ price_max?: number;
338
+ currency?: string;
339
+ media?: Array<{
340
+ id: number;
341
+ url: string;
342
+ alt: string;
343
+ }>;
344
+ collection_ids?: number[];
345
+ collection_names?: string[];
346
+ skus?: string[];
347
+ in_stock?: boolean;
348
+ total_quantity?: number;
349
+ wsm_id?: string;
350
+ rating?: number;
351
+ created_at?: number;
352
+ updated_at?: number;
353
+ category_path?: string[];
354
+ category_slugs?: string[] | null;
355
+ category_ids?: number[];
356
+ category_images?: string[] | null;
357
+ image_count?: number;
358
+ };
359
+
360
+ export type CategoryAPIType = {
361
+ id: string;
362
+ image: string;
363
+ name: string;
364
+ children?: CategoryAPIType[];
365
+ slug: string;
366
+ };
367
+
368
+ export type PLSearchCategory = {
369
+ id: string;
370
+ value: string;
371
+ slug: string;
372
+ count: number;
373
+ media?: string;
374
+ };
375
+
376
+ export type PLSearchBrand = {
377
+ id: string;
378
+ value: string;
379
+ count: number;
380
+ slug: string;
381
+ media?: string;
382
+ };
383
+
384
+ export type PLSearchProductsResponse = {
385
+ products: PLSearchProduct[];
386
+ facets: {
387
+ brands: PLSearchBrand[];
388
+ categories: PLSearchCategory[];
389
+ price_ranges: Array<{ min: number; max: number; count: number }> | null;
390
+ years: Array<{ value: string; count: number }>;
391
+ makes: Array<{ value: string; count: number }>;
392
+ models: Array<{ value: string; count: number }>;
393
+ };
394
+ pagination: {
395
+ total: number;
396
+ page: number;
397
+ per_page: number;
398
+ total_pages: number;
399
+ };
400
+ };
401
+
402
+ /* ----------------- REST helper ----------------- */
403
+ async function httpGet<T>(pathWithQuery: string): Promise<T> {
404
+ if (!BASE_URL) throw new Error("Missing NEXT_PUBLIC_SEARCH_URL");
405
+ const path = pathWithQuery.startsWith("/")
406
+ ? pathWithQuery
407
+ : `/${pathWithQuery}`;
408
+ const url = `${BASE_URL}${path}`;
409
+
410
+ let res: Response;
411
+ try {
412
+ res = await fetch(url, {
413
+ cache: "no-store",
414
+ headers: {
415
+ "Content-Type": "application/json",
416
+ },
417
+ });
418
+ } catch (e) {
419
+ throw new Error(
420
+ `GET ${url} network error: ${e instanceof Error ? e.message : String(e)}`
421
+ );
422
+ }
423
+
424
+ if (!res.ok) {
425
+ let msg = res.statusText;
426
+ try {
427
+ const j = (await res.json()) as { message?: string };
428
+ msg = j?.message || msg;
429
+ } catch {
430
+ /* ignore */
431
+ }
432
+ throw new Error(`GET ${url} failed: ${res.status} ${msg}`);
433
+ }
434
+ return res.json() as Promise<T>;
435
+ }
436
+
437
+ /* ----------------- PartsLogic API helper ----------------- */
438
+ async function partsLogicGet<T>(pathWithQuery: string): Promise<T> {
439
+ if (!PARTSLOGIC_URL) throw new Error("Missing NEXT_PUBLIC_PARTSLOGIC_URL");
440
+ const path = pathWithQuery.startsWith("/")
441
+ ? pathWithQuery
442
+ : `/${pathWithQuery}`;
443
+ const url = `${PARTSLOGIC_URL}${path}`;
444
+
445
+ let res: Response;
446
+ try {
447
+ res = await fetch(url, {
448
+ cache: "no-store",
449
+ headers: {
450
+ "Content-Type": "application/json",
451
+ Accept: "application/json",
452
+ },
453
+ });
454
+ } catch (e) {
455
+ throw new Error(
456
+ `GET ${url} network error: ${e instanceof Error ? e.message : String(e)}`
457
+ );
458
+ }
459
+
460
+ if (!res.ok) {
461
+ let msg = res.statusText;
462
+ try {
463
+ const j = (await res.json()) as { message?: string };
464
+ msg = j?.message || msg;
465
+ } catch {
466
+ /* ignore */
467
+ }
468
+ throw new Error(`GET ${url} failed: ${res.status} ${msg}`);
469
+ }
470
+ return res.json() as Promise<T>;
471
+ }
472
+ function qp(params: Record<string, string | number | boolean | undefined>) {
473
+ const sp = new URLSearchParams();
474
+ Object.entries(params).forEach(([k, v]) => {
475
+ if (v === undefined || v === "") return;
476
+ sp.set(k, String(v));
477
+ });
478
+ return sp.toString();
479
+ }
480
+
481
+ /* ----------------- GraphQL helper ----------------- */
482
+ function normalizeGraphqlUrl(raw: string | undefined) {
483
+ if (!raw) throw new Error("NEXT_PUBLIC_API_URL is not configured");
484
+ const trimmed = raw.trim();
485
+ return /\/graphql\/?$/.test(trimmed.toLowerCase())
486
+ ? trimmed
487
+ : trimmed.replace(/\/+$/, "") + "/graphql";
488
+ }
489
+
490
+ async function graphqlFetch<T>(
491
+ query: string,
492
+ variables?: Record<string, unknown>,
493
+ scope = "graphql"
494
+ ): Promise<T> {
495
+ const url = normalizeGraphqlUrl(process.env.NEXT_PUBLIC_API_URL);
496
+ let res: Response;
497
+ try {
498
+ res = await fetch(url, {
499
+ method: "POST",
500
+ headers: { "Content-Type": "application/json" },
501
+ body: JSON.stringify({ query, variables }),
502
+ });
503
+ } catch (e) {
504
+ const msg = e instanceof Error ? e.message : String(e);
505
+ console.error(`[${scope}] network error`, msg, { url, variables });
506
+ throw new Error(`${scope}: network error: ${msg}`);
507
+ }
508
+
509
+ const text = await res.text();
510
+ if (!res.ok) {
511
+ console.error(`[${scope}] HTTP ${res.status}`, text.slice(0, 500));
512
+ throw new Error(`${scope}: HTTP ${res.status}`);
513
+ }
514
+
515
+ let parsed: unknown;
516
+ try {
517
+ parsed = JSON.parse(text);
518
+ } catch {
519
+ console.error(`[${scope}] invalid JSON`, text.slice(0, 500));
520
+ throw new Error(`${scope}: invalid JSON`);
521
+ }
522
+
523
+ const p = parsed as { data?: T; errors?: Array<{ message?: string }> };
524
+ if (p.errors?.length) {
525
+ console.error(`[${scope}] GraphQL errors`, p.errors);
526
+ throw new Error(`${scope}: ${p.errors[0]?.message || "GraphQL error"}`);
527
+ }
528
+ if (!p.data) {
529
+ console.error(`[${scope}] missing data`, parsed);
530
+ throw new Error(`${scope}: invalid response (no data)`);
531
+ }
532
+ return p.data;
533
+ }
534
+
535
+ const ORDER_QUERY = `
536
+ query Order($id: ID!) {
537
+ order(id: $id) {
538
+ id
539
+ number
540
+ created
541
+ status
542
+ paymentStatus
543
+ total {
544
+ gross { amount currency }
545
+ }
546
+ lines {
547
+ id
548
+ productName
549
+ variantName
550
+ quantity
551
+ thumbnail { url }
552
+ totalPrice {
553
+ gross { amount currency }
554
+ }
555
+ }
556
+ }
557
+ }
558
+ `;
559
+
560
+ const PRODUCTS_BY_CATEGORIES_AND_PRODUCT_TYPES_QUERY = `
561
+ query ProductsByCategoriesAndProductTypes(
562
+ $categoryIds: [ID!],
563
+ $productTypeIds: [ID!],
564
+ $channel: String!,
565
+ $first: Int!,
566
+ $after: String,
567
+ $sortField: ProductOrderField!,
568
+ $sortDirection: OrderDirection!
569
+ ) {
570
+ products(
571
+ filter: {
572
+ categories: $categoryIds
573
+ productTypes: $productTypeIds
574
+ },
575
+ channel: $channel,
576
+ first: $first,
577
+ after: $after,
578
+ sortBy: {
579
+ field: $sortField,
580
+ direction: $sortDirection
581
+ }
582
+ ) {
583
+ totalCount
584
+ pageInfo {
585
+ hasNextPage
586
+ hasPreviousPage
587
+ startCursor
588
+ endCursor
589
+ }
590
+ edges {
591
+ node {
592
+ id
593
+ name
594
+ slug
595
+ description
596
+ category {
597
+ id
598
+ name
599
+ }
600
+ productType {
601
+ id
602
+ name
603
+ }
604
+ media {
605
+ id
606
+ url
607
+ alt
608
+ }
609
+ pricing {
610
+ priceRange {
611
+ start {
612
+ gross {
613
+ amount
614
+ currency
615
+ }
616
+ }
617
+ stop {
618
+ gross {
619
+ amount
620
+ currency
621
+ }
622
+ }
623
+ }
624
+ }
625
+ }
626
+ }
627
+ }
628
+ }
629
+ `;
630
+
631
+ const GET_ALL_CATEGORIES_QUERY = `
632
+ query GetAllCategories(
633
+ $channel: String!
634
+ $first: Int!
635
+ $after: String
636
+ $sortBy: CategorySortingInput
637
+ ) {
638
+ categories(
639
+ first: $first
640
+ after: $after
641
+ sortBy: $sortBy
642
+ ) {
643
+ totalCount
644
+ pageInfo {
645
+ hasNextPage
646
+ endCursor
647
+ }
648
+ edges {
649
+ node {
650
+ id
651
+ name
652
+ slug
653
+ level
654
+ parent {
655
+ id
656
+ name
657
+ }
658
+ backgroundImage {
659
+ url
660
+ alt
661
+ }
662
+ products(channel: $channel) {
663
+ totalCount
664
+ }
665
+ }
666
+ }
667
+ }
668
+ }
669
+ `;
670
+
671
+ const GET_CATEGORY_BY_SLUG_QUERY = `
672
+ query GetCategoryBySlug($slug: String!, $channel: String!) {
673
+ categories(first: 1, filter: { search: $slug }) {
674
+ edges {
675
+ node {
676
+ id
677
+ name
678
+ slug
679
+ level
680
+ parent {
681
+ id
682
+ name
683
+ }
684
+ backgroundImage {
685
+ url
686
+ alt
687
+ }
688
+ products(channel: $channel) {
689
+ totalCount
690
+ }
691
+ }
692
+ }
693
+ }
694
+ }
695
+ `;
696
+
697
+ const GET_ALL_PRODUCT_TYPES_QUERY = `
698
+ query GetAllProductTypesWithCounts($first: Int!, $channel: String!) {
699
+ productTypes(first: $first) {
700
+ totalCount
701
+ edges {
702
+ node {
703
+ id
704
+ name
705
+ slug
706
+ hasVariants
707
+ isShippingRequired
708
+ kind
709
+ metadata {
710
+ key
711
+ value
712
+ }
713
+ products(channel: $channel) {
714
+ totalCount
715
+ }
716
+ }
717
+ }
718
+ }
719
+ }
720
+ `;
721
+
722
+ const GET_PRODUCTS_BY_CATEGORY_QUERY = `
723
+ query GetProductsByCategory(
724
+ $categoryIds: [ID!]
725
+ $channel: String!
726
+ $first: Int!
727
+ $after: String
728
+ $search: String
729
+ ) {
730
+ products(
731
+ filter: {
732
+ categories: $categoryIds
733
+ }
734
+ search: $search
735
+ channel: $channel
736
+ first: $first
737
+ after: $after
738
+ ) {
739
+ totalCount
740
+ pageInfo {
741
+ hasNextPage
742
+ hasPreviousPage
743
+ endCursor
744
+ startCursor
745
+ }
746
+ edges {
747
+ cursor
748
+ node {
749
+ id
750
+ name
751
+ slug
752
+ description
753
+ category {
754
+ id
755
+ name
756
+ }
757
+ productType {
758
+ id
759
+ name
760
+ }
761
+ media {
762
+ id
763
+ url
764
+ alt
765
+ }
766
+ pricing {
767
+ priceRange {
768
+ start {
769
+ gross {
770
+ amount
771
+ currency
772
+ }
773
+ }
774
+ stop {
775
+ gross {
776
+ amount
777
+ currency
778
+ }
779
+ }
780
+ }
781
+ }
782
+ }
783
+ }
784
+ }
785
+ }
786
+ `;
787
+
788
+ const GET_PRODUCTS_BY_PRODUCT_TYPE_QUERY = `
789
+ query GetProductsByProductType(
790
+ $productTypeIds: [ID!]
791
+ $channel: String!
792
+ $first: Int!
793
+ $after: String
794
+ $search: String
795
+ ) {
796
+ products(
797
+ filter: {
798
+ productTypes: $productTypeIds
799
+ }
800
+ search: $search
801
+ channel: $channel
802
+ first: $first
803
+ after: $after
804
+ ) {
805
+ totalCount
806
+ pageInfo {
807
+ hasNextPage
808
+ hasPreviousPage
809
+ endCursor
810
+ startCursor
811
+ }
812
+ edges {
813
+ cursor
814
+ node {
815
+ id
816
+ name
817
+ slug
818
+ description
819
+ category {
820
+ id
821
+ name
822
+ }
823
+ productType {
824
+ id
825
+ name
826
+ }
827
+ media {
828
+ id
829
+ url
830
+ alt
831
+ }
832
+ pricing {
833
+ priceRange {
834
+ start {
835
+ gross {
836
+ amount
837
+ currency
838
+ }
839
+ }
840
+ stop {
841
+ gross {
842
+ amount
843
+ currency
844
+ }
845
+ }
846
+ }
847
+ }
848
+ }
849
+ }
850
+ }
851
+ }
852
+ `;
853
+
854
+ const GLOBAL_SEARCH_STOREFRONT_QUERY = `
855
+ query GlobalSearchStorefront(
856
+ $query: String!
857
+ $channel: String!
858
+ $includeProducts: Boolean!
859
+ $includeCategories: Boolean!
860
+ $includeCollections: Boolean!
861
+ $includeProductTypes: Boolean!
862
+ ) {
863
+ products(first: 10, channel: $channel, filter: { search: $query }) @include(if: $includeProducts) {
864
+ edges {
865
+ node {
866
+ id
867
+ name
868
+ slug
869
+ updatedAt
870
+ category {
871
+ id
872
+ name
873
+ }
874
+ thumbnail(size: 64) {
875
+ url
876
+ alt
877
+ }
878
+ }
879
+ }
880
+ }
881
+
882
+ categories(first: 10, filter: { search: $query }) @include(if: $includeCategories) {
883
+ edges {
884
+ node {
885
+ id
886
+ name
887
+ slug
888
+ level
889
+ parent {
890
+ id
891
+ name
892
+ }
893
+ backgroundImage(size: 64) {
894
+ url
895
+ alt
896
+ }
897
+ products(first: 1, channel: $channel) {
898
+ totalCount
899
+ }
900
+ }
901
+ }
902
+ }
903
+
904
+ collections(first: 10, channel: $channel, filter: { search: $query }) @include(if: $includeCollections) {
905
+ edges {
906
+ node {
907
+ id
908
+ name
909
+ slug
910
+ backgroundImage(size: 64) {
911
+ url
912
+ alt
913
+ }
914
+ products(first: 1) {
915
+ totalCount
916
+ }
917
+ }
918
+ }
919
+ }
920
+
921
+ productTypes(first: 10, filter: { search: $query }) @include(if: $includeProductTypes) {
922
+ edges {
923
+ node {
924
+ id
925
+ name
926
+ slug
927
+ hasVariants
928
+ }
929
+ }
930
+ }
931
+ }
932
+ `;
933
+
934
+ type OrderLine = {
935
+ id: string;
936
+ productName: string;
937
+ variantName: string;
938
+ quantity: number;
939
+ thumbnail: { url: string };
940
+ totalPrice: { gross: { amount: number; currency: string } };
941
+ };
942
+ type Order = {
943
+ id: string;
944
+ number: string;
945
+ created: string;
946
+ status: string;
947
+ paymentStatus: string;
948
+ total: { gross: { amount: number; currency: string } };
949
+ lines: OrderLine[];
950
+ };
951
+
952
+ async function getOrderById({ orderId }: { orderId: string }) {
953
+ const data = await graphqlFetch<{ order: Order | null }>(
954
+ ORDER_QUERY,
955
+ { id: orderId },
956
+ "order"
957
+ );
958
+ if (!data.order) throw new Error("Order not found");
959
+ return data.order;
960
+ }
961
+
962
+ async function getProductsByCategoriesAndProductTypes({
963
+ categoryIds,
964
+ productTypeIds,
965
+ channel,
966
+ first,
967
+ after,
968
+ sortField = "DATE",
969
+ sortDirection = "ASC",
970
+ }: {
971
+ categoryIds?: string[];
972
+ productTypeIds?: string[];
973
+ channel: string;
974
+ first: number;
975
+ after?: string;
976
+ sortField?: "PRICE" | "DATE" | "NAME";
977
+ sortDirection?: "ASC" | "DESC";
978
+ }) {
979
+ // Pass null for empty arrays so GraphQL doesn't apply those filters
980
+ const variables: Record<string, unknown> = {
981
+ categoryIds: categoryIds?.length ? categoryIds : null,
982
+ productTypeIds: productTypeIds?.length ? productTypeIds : null,
983
+ channel,
984
+ first,
985
+ sortField,
986
+ sortDirection,
987
+ };
988
+
989
+ if (after) {
990
+ variables.after = after;
991
+ }
992
+
993
+ const data = await graphqlFetch<GraphQLProductsResponse>(
994
+ PRODUCTS_BY_CATEGORIES_AND_PRODUCT_TYPES_QUERY,
995
+ variables,
996
+ "products"
997
+ );
998
+ return data;
999
+ }
1000
+
1001
+ async function getGraphQLCategories({
1002
+ channel,
1003
+ first = 100,
1004
+ after,
1005
+ }: {
1006
+ channel: string;
1007
+ first?: number;
1008
+ after?: string;
1009
+ }) {
1010
+ const variables: Record<string, unknown> = {
1011
+ channel,
1012
+ first,
1013
+ sortBy: {
1014
+ field: "PRODUCT_COUNT",
1015
+ direction: "DESC",
1016
+ },
1017
+ };
1018
+
1019
+ if (after) {
1020
+ variables.after = after;
1021
+ }
1022
+
1023
+ const data = await graphqlFetch<GraphQLCategoriesResponse>(
1024
+ GET_ALL_CATEGORIES_QUERY,
1025
+ variables,
1026
+ "categories"
1027
+ );
1028
+ return data;
1029
+ }
1030
+
1031
+ async function getCategoryBySlug({
1032
+ slug,
1033
+ channel,
1034
+ }: {
1035
+ slug: string;
1036
+ channel: string;
1037
+ }) {
1038
+ // First try the search query for quick lookup
1039
+ const searchData = await graphqlFetch<{
1040
+ categories: {
1041
+ edges: Array<{
1042
+ node: GraphQLCategory;
1043
+ }>;
1044
+ };
1045
+ }>(GET_CATEGORY_BY_SLUG_QUERY, { slug, channel }, "category");
1046
+
1047
+ // If found via search and slug matches exactly, return it
1048
+ const searchResult = searchData.categories.edges[0]?.node;
1049
+ if (searchResult && searchResult.slug === slug) {
1050
+ return searchResult;
1051
+ }
1052
+
1053
+ // If not found or slug doesn't match exactly, paginate through all categories
1054
+ let hasNextPage = true;
1055
+ let after: string | null = null;
1056
+ const batchSize = 100;
1057
+
1058
+ while (hasNextPage) {
1059
+ const variables: Record<string, unknown> = {
1060
+ channel,
1061
+ first: batchSize,
1062
+ };
1063
+
1064
+ if (after) {
1065
+ variables.after = after;
1066
+ }
1067
+
1068
+ const batchData = await graphqlFetch<GraphQLCategoriesResponse>(
1069
+ GET_ALL_CATEGORIES_QUERY,
1070
+ variables,
1071
+ "categories"
1072
+ );
1073
+
1074
+ // Search for exact slug match in this batch
1075
+ const foundCategory = batchData.categories.edges.find(
1076
+ (edge) => edge.node.slug === slug
1077
+ );
1078
+
1079
+ if (foundCategory) {
1080
+ return foundCategory.node;
1081
+ }
1082
+
1083
+ // Check if there are more pages
1084
+ hasNextPage = batchData.categories.pageInfo.hasNextPage;
1085
+ after = batchData.categories.pageInfo.endCursor;
1086
+ }
1087
+
1088
+ return null;
1089
+ }
1090
+
1091
+ async function getGraphQLProductTypes({ first = 100 }: { first?: number }) {
1092
+ const channel = process.env.NEXT_PUBLIC_SALEOR_CHANNEL || "default-channel";
1093
+ const data = await graphqlFetch<GraphQLProductTypesResponse>(
1094
+ GET_ALL_PRODUCT_TYPES_QUERY,
1095
+ {
1096
+ first,
1097
+ channel,
1098
+ },
1099
+ "productTypes"
1100
+ );
1101
+ return data;
1102
+ }
1103
+
1104
+ async function getProductsByCategory({
1105
+ categoryIds,
1106
+ channel,
1107
+ first = 100,
1108
+ after,
1109
+ search,
1110
+ }: {
1111
+ categoryIds: string[];
1112
+ channel: string;
1113
+ first?: number;
1114
+ after?: string;
1115
+ search?: string;
1116
+ }) {
1117
+ // Prepare variables, excluding null/undefined values
1118
+ const variables: Record<string, unknown> = {
1119
+ categoryIds,
1120
+ channel,
1121
+ first,
1122
+ };
1123
+
1124
+ if (after) {
1125
+ variables.after = after;
1126
+ }
1127
+
1128
+ if (search && search.trim()) {
1129
+ variables.search = search;
1130
+ }
1131
+
1132
+ const data = await graphqlFetch<GraphQLProductsResponse>(
1133
+ GET_PRODUCTS_BY_CATEGORY_QUERY,
1134
+ variables,
1135
+ "products"
1136
+ );
1137
+ return data;
1138
+ }
1139
+
1140
+ async function getProductsByProductType({
1141
+ productTypeIds,
1142
+ channel,
1143
+ first = 100,
1144
+ after,
1145
+ search,
1146
+ }: {
1147
+ productTypeIds: string[];
1148
+ channel: string;
1149
+ first?: number;
1150
+ after?: string;
1151
+ search?: string;
1152
+ }) {
1153
+ // Prepare variables, excluding null/undefined values
1154
+ const variables: Record<string, unknown> = {
1155
+ productTypeIds,
1156
+ channel,
1157
+ first,
1158
+ };
1159
+
1160
+ if (after) {
1161
+ variables.after = after;
1162
+ }
1163
+
1164
+ if (search && search.trim()) {
1165
+ variables.search = search;
1166
+ }
1167
+
1168
+ const data = await graphqlFetch<GraphQLProductsResponse>(
1169
+ GET_PRODUCTS_BY_PRODUCT_TYPE_QUERY,
1170
+ variables,
1171
+ "products"
1172
+ );
1173
+ return data;
1174
+ }
1175
+
1176
+ async function globalSearchStorefront({
1177
+ query,
1178
+ channel = process.env.NEXT_PUBLIC_SALEOR_CHANNEL?.trim() || "default-channel",
1179
+ includeProducts = true,
1180
+ includeCategories = true,
1181
+ includeCollections = false,
1182
+ includeProductTypes = true,
1183
+ }: {
1184
+ query: string;
1185
+ channel?: string;
1186
+ includeProducts?: boolean;
1187
+ includeCategories?: boolean;
1188
+ includeCollections?: boolean;
1189
+ includeProductTypes?: boolean;
1190
+ }) {
1191
+ const variables = {
1192
+ query,
1193
+ channel,
1194
+ includeProducts,
1195
+ includeCategories,
1196
+ includeCollections,
1197
+ includeProductTypes,
1198
+ };
1199
+
1200
+ const data = await graphqlFetch<GlobalSearchResponse>(
1201
+ GLOBAL_SEARCH_STOREFRONT_QUERY,
1202
+ variables,
1203
+ "globalSearch"
1204
+ );
1205
+ return data;
1206
+ }
1207
+
1208
+ function transformGraphQLProductToProduct(
1209
+ graphqlProduct: GraphQLProduct
1210
+ ): Product {
1211
+ const media = graphqlProduct.media?.[0];
1212
+ const pricing = graphqlProduct.pricing?.priceRange;
1213
+ const startPrice = pricing?.start?.gross?.amount || 0;
1214
+ const stopPrice = pricing?.stop?.gross?.amount || 0;
1215
+
1216
+ return {
1217
+ id: graphqlProduct.id,
1218
+ name: graphqlProduct.name,
1219
+ description: graphqlProduct.description || "",
1220
+ price: startPrice,
1221
+ price_max: stopPrice,
1222
+ currency: pricing?.start?.gross?.currency || "USD",
1223
+ brand: "",
1224
+ categories: graphqlProduct.category ? [graphqlProduct.category.name] : [],
1225
+ sku: [],
1226
+ in_stock: true,
1227
+ stock_quantity: 0,
1228
+ rating: 0,
1229
+ image_url: media?.url || "",
1230
+ images: graphqlProduct.media?.map((m) => m.url) || [],
1231
+ tenant_id: "",
1232
+ tenant_name: "",
1233
+ years: [],
1234
+ makes: [],
1235
+ models: [],
1236
+ slug: graphqlProduct.slug,
1237
+ };
1238
+ }
1239
+
1240
+ function getImageUrlFromProductTypeMetadata(
1241
+ productType: GraphQLProductType
1242
+ ): string | null {
1243
+ const imageUrlMetadata = productType.metadata.find(
1244
+ (meta) => meta.key === "image-url"
1245
+ );
1246
+ if (
1247
+ imageUrlMetadata &&
1248
+ imageUrlMetadata.value &&
1249
+ imageUrlMetadata.value.trim() !== ""
1250
+ ) {
1251
+ return imageUrlMetadata.value;
1252
+ }
1253
+ return null;
1254
+ }
1255
+
1256
+ /* ----------------- Public API ----------------- */
1257
+ export { transformGraphQLProductToProduct, getImageUrlFromProductTypeMetadata };
1258
+
1259
+ export const shopApi = {
1260
+ getOrderById,
1261
+ getProductsByCategoriesAndProductTypes,
1262
+ getGraphQLCategories,
1263
+ getCategoryBySlug,
1264
+ getGraphQLProductTypes,
1265
+ getProductsByCategory,
1266
+ getProductsByProductType,
1267
+ globalSearchStorefront,
1268
+
1269
+ async getCategories(): Promise<{ tenant: string; categories: Category[] }> {
1270
+ const search = qp({ tenant: TENANT });
1271
+ return httpGet(`/search/api/categories?${search}`);
1272
+ },
1273
+
1274
+ async getBrands(): Promise<{
1275
+ brands: Brand[];
1276
+ total_brands: number;
1277
+ tenant: string;
1278
+ }> {
1279
+ const search = qp({ tenant: TENANT });
1280
+ return httpGet(`/search/api/brands?${search}`);
1281
+ },
1282
+
1283
+ async getYMMCombinations(): Promise<{
1284
+ combinations: YMMCombinations;
1285
+ tenant: string;
1286
+ total_years: number;
1287
+ total_makes: number;
1288
+ total_models: number;
1289
+ }> {
1290
+ const search = qp({ tenant: TENANT });
1291
+ return httpGet(`/search/api/ymm/combinations?${search}`);
1292
+ },
1293
+
1294
+ async getProductById(id: string): Promise<ProductDetail> {
1295
+ const query = qp({ tenant: TENANT });
1296
+ return httpGet(`/search/api/products/${id}?${query}`);
1297
+ },
1298
+
1299
+ async searchProducts(
1300
+ params: {
1301
+ q?: string;
1302
+ category?: string | string[];
1303
+ brand?: string | string[];
1304
+ years?: string | string[];
1305
+ makes?: string | string[];
1306
+ models?: string | string[];
1307
+ per_page?: number;
1308
+ page?: number;
1309
+ sort?: string;
1310
+ in_stock?: boolean;
1311
+ min_price?: number;
1312
+ max_price?: number;
1313
+ } = {}
1314
+ ): Promise<ProductsResponse> {
1315
+ const normalize = (v?: string | string[]) =>
1316
+ Array.isArray(v) ? v.join(",") : v;
1317
+ const query = qp({
1318
+ q: params.q ?? "*",
1319
+ tenant: TENANT,
1320
+ category: normalize(params.category),
1321
+ brand: normalize(params.brand),
1322
+ years: normalize(params.years),
1323
+ makes: normalize(params.makes),
1324
+ models: normalize(params.models),
1325
+ per_page: params.per_page ?? 10,
1326
+ page: params.page ?? 1,
1327
+ sort: params.sort,
1328
+ in_stock: params.in_stock,
1329
+ min_price: params.min_price,
1330
+
1331
+ max_price: params.max_price,
1332
+ });
1333
+ return httpGet(`/search/api/search/multi-tenant?${query}`);
1334
+ },
1335
+
1336
+ // YMM (Year/Make/Model) API functions
1337
+ async pingYMM(): Promise<{
1338
+ message: string;
1339
+ status: string;
1340
+ }> {
1341
+ return partsLogicGet(`/ping`);
1342
+ },
1343
+ async getYears(): Promise<{
1344
+ success: boolean;
1345
+ data: YMMYear[];
1346
+ message: string;
1347
+ }> {
1348
+ return partsLogicGet(`/api/search/fitments/years`);
1349
+ },
1350
+ async getRootTypes(): Promise<{
1351
+ success: boolean;
1352
+ data: Array<{
1353
+ id: number;
1354
+ name: string;
1355
+ }>;
1356
+ message: string;
1357
+ }> {
1358
+ return partsLogicGet("/api/fitment-search/root-types");
1359
+ },
1360
+ async getFitmentValuesApi(query: number | string): Promise<{
1361
+ success: boolean;
1362
+ data: {
1363
+ id: number;
1364
+ value: string;
1365
+ };
1366
+ message: string;
1367
+ }> {
1368
+ return partsLogicGet(`/api/fitment-search/values/${query}`);
1369
+ },
1370
+ async getFitmentChildAPI(query: string): Promise<{
1371
+ success: boolean;
1372
+ data: Array<{
1373
+ id: number;
1374
+ value: string;
1375
+ }>;
1376
+ message: string;
1377
+ }> {
1378
+ return partsLogicGet(`/api/fitment-search/child-types/${query}`);
1379
+ },
1380
+
1381
+ async getMakes(yearId: number): Promise<{
1382
+ success: boolean;
1383
+ data: YMMMake[];
1384
+ message: string;
1385
+ }> {
1386
+ return partsLogicGet(`/api/search/fitments/makes?year_id=${yearId}`);
1387
+ },
1388
+
1389
+ async getModels(
1390
+ yearId: number,
1391
+ makeId: number
1392
+ ): Promise<{
1393
+ success: boolean;
1394
+ data: YMMModel[];
1395
+ message: string;
1396
+ }> {
1397
+ return partsLogicGet(
1398
+ `/api/search/fitments/models?year_id=${yearId}&make_id=${makeId}`
1399
+ );
1400
+ },
1401
+
1402
+ async searchProductsPL(params: {
1403
+ q?: string;
1404
+ page?: number;
1405
+ per_page?: number;
1406
+ fitment_pairs?: string;
1407
+ }): Promise<PLSearchProductsResponse> {
1408
+ const query = qp({
1409
+ q: params.q,
1410
+ fitment_pairs: params.fitment_pairs,
1411
+ page: params.page ?? 1,
1412
+ per_page: params.per_page ?? 20,
1413
+ });
1414
+ return partsLogicGet(`/api/search/products?${query}`);
1415
+ },
1416
+
1417
+ async categoryProductPL(): Promise<{ categories: CategoryAPIType[] }> {
1418
+ return partsLogicGet("/api/categories?page=1&per_page=100");
1419
+ },
1420
+
1421
+ async brandsProductPL(): Promise<{ brands: CategoryAPIType[] }> {
1422
+ return partsLogicGet("/api/brands?page=1&per_page=100");
1423
+ },
1424
+ async getProductsBySlug({
1425
+ slug,
1426
+ page = 1,
1427
+ per_page = 20,
1428
+ search,
1429
+ filterType = "category_slug",
1430
+ }: {
1431
+ slug: string;
1432
+ page?: number;
1433
+ per_page?: number;
1434
+ search?: string;
1435
+ filterType?: "category_slug" | "brand_slug";
1436
+ }): Promise<PLSearchProductsResponse> {
1437
+ const query = qp({
1438
+ q: search || undefined,
1439
+ [filterType]: slug,
1440
+ page,
1441
+ per_page,
1442
+ });
1443
+ return partsLogicGet(`/api/search/products?${query}`);
1444
+ },
1445
+ };