@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,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 &quot;{searchTerm}&quot;
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
+ }