@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,218 @@
1
+ "use client";
2
+
3
+ import { SearchIcon } from "@/app/utils/svgs/searchIcon";
4
+ import { useRouter, useSearchParams, usePathname } from "next/navigation";
5
+ import { useState, useEffect, useCallback, useRef } from "react";
6
+ import { gtmSearch } from "../../utils/googleTagManager";
7
+ import { useAppConfiguration } from "../providers/ServerAppConfigurationProvider";
8
+
9
+ export default function SearchFilter() {
10
+ const router = useRouter();
11
+ const searchParams = useSearchParams();
12
+ const pathname = usePathname();
13
+ const { getGoogleTagManagerConfig } = useAppConfiguration();
14
+ const gtmConfig = getGoogleTagManagerConfig();
15
+ const [searchTerm, setSearchTerm] = useState("");
16
+ const [isSearching, setIsSearching] = useState(false);
17
+ const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
18
+ const navigationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
19
+ const currentSearchIdRef = useRef<number>(0);
20
+
21
+ // Track if user is actively typing to avoid URL interference
22
+ const isTypingRef = useRef(false);
23
+
24
+ useEffect(() => {
25
+ const q = searchParams?.get("q") || "";
26
+
27
+ // Only update search term from URL if user is not actively typing
28
+ if (!isTypingRef.current) {
29
+ setSearchTerm(q);
30
+ }
31
+
32
+ // Reset loading state when search params change (page loaded with new results)
33
+ setIsSearching(false);
34
+
35
+ // Clear any pending navigation timeout
36
+ if (navigationTimeoutRef.current) {
37
+ clearTimeout(navigationTimeoutRef.current);
38
+ }
39
+ }, [searchParams]);
40
+
41
+ const performSearch = useCallback((term: string, searchId: number) => {
42
+ // Check if this search is still the current one
43
+ if (searchId !== currentSearchIdRef.current) {
44
+ return; // This search has been cancelled by a newer one
45
+ }
46
+
47
+ const params = new URLSearchParams(searchParams?.toString() || "");
48
+
49
+ if (term.trim()) {
50
+ params.set("q", term.trim());
51
+ } else {
52
+ params.delete("q");
53
+ }
54
+
55
+ // Reset pagination when searching
56
+ params.delete("after");
57
+
58
+ const href = `${pathname}?${params.toString()}`;
59
+
60
+ // Show loading state only if this is still the current search
61
+ if (searchId === currentSearchIdRef.current) {
62
+ setIsSearching(true);
63
+
64
+ // Track search in GTM
65
+ if (term.trim()) {
66
+ gtmSearch(term.trim(), undefined, gtmConfig?.container_id);
67
+ }
68
+
69
+ // Navigate
70
+ router.push(href, { scroll: false });
71
+
72
+ // Clear any existing timeout
73
+ if (navigationTimeoutRef.current) {
74
+ clearTimeout(navigationTimeoutRef.current);
75
+ }
76
+
77
+ // Reset loading state after a reasonable time
78
+ navigationTimeoutRef.current = setTimeout(() => {
79
+ // Only reset if this is still the current search
80
+ if (searchId === currentSearchIdRef.current) {
81
+ setIsSearching(false);
82
+ }
83
+ }, 3000);
84
+ }
85
+ }, [router, searchParams, pathname, gtmConfig?.container_id]);
86
+
87
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
88
+ const value = e.target.value;
89
+
90
+ // Mark that user is actively typing
91
+ isTypingRef.current = true;
92
+
93
+ // Immediately update the search term for responsive UI
94
+ setSearchTerm(value);
95
+
96
+ // Immediately reset loading state when user starts typing again
97
+ setIsSearching(false);
98
+
99
+ // Cancel any previous search by incrementing the search ID
100
+ currentSearchIdRef.current += 1;
101
+ const currentSearchId = currentSearchIdRef.current;
102
+
103
+ // Clear existing timeouts (cancels previous debounced search and navigation timeout)
104
+ if (debounceTimeoutRef.current) {
105
+ clearTimeout(debounceTimeoutRef.current);
106
+ }
107
+ if (navigationTimeoutRef.current) {
108
+ clearTimeout(navigationTimeoutRef.current);
109
+ }
110
+
111
+ // Auto-search with debounce for any meaningful input
112
+ if (value.trim().length >= 2 || value.trim().length === 0) {
113
+ debounceTimeoutRef.current = setTimeout(() => {
114
+ // Mark that user is no longer actively typing
115
+ isTypingRef.current = false;
116
+
117
+ // Only show loading if this is still the current search
118
+ if (currentSearchId === currentSearchIdRef.current) {
119
+ setIsSearching(true);
120
+ }
121
+ performSearch(value, currentSearchId);
122
+ }, 800); // 800ms debounce - gives user time to finish typing
123
+ } else {
124
+ // For short inputs, still mark as not typing after a brief delay
125
+ setTimeout(() => {
126
+ isTypingRef.current = false;
127
+ }, 100);
128
+ }
129
+ };
130
+
131
+
132
+ const clearSearch = () => {
133
+ // Mark as not typing since this is a programmatic action
134
+ isTypingRef.current = false;
135
+
136
+ setSearchTerm("");
137
+
138
+ // Cancel any previous search
139
+ currentSearchIdRef.current += 1;
140
+ const currentSearchId = currentSearchIdRef.current;
141
+
142
+ // Clear debounce timeout
143
+ if (debounceTimeoutRef.current) {
144
+ clearTimeout(debounceTimeoutRef.current);
145
+ }
146
+
147
+ setIsSearching(true);
148
+ performSearch("", currentSearchId);
149
+ };
150
+
151
+ // Cleanup timeouts on unmount
152
+ useEffect(() => {
153
+ return () => {
154
+ if (debounceTimeoutRef.current) {
155
+ clearTimeout(debounceTimeoutRef.current);
156
+ }
157
+ if (navigationTimeoutRef.current) {
158
+ clearTimeout(navigationTimeoutRef.current);
159
+ }
160
+ };
161
+ }, []);
162
+
163
+ return (
164
+ <div className="w-full md:max-w-md relative">
165
+ <div className="relative">
166
+ <input
167
+ type="text"
168
+ placeholder="Search products..."
169
+ value={searchTerm}
170
+ onChange={handleInputChange}
171
+ className="w-full h-10 pl-10 pr-10 text-sm border border-[var(--color-secondary-300)] bg-white rounded-md focus:outline-none focus:ring-2 focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-600)] text-[var(--color-secondary-900)] placeholder-[var(--color-secondary-500)]"
172
+ />
173
+
174
+ {/* Search icon or loading spinner */}
175
+ <div className="absolute left-3 top-1/2 -translate-y-1/2">
176
+ {isSearching ? (
177
+ <div className="w-4 h-4 border-2 border-[var(--color-secondary-300)] border-t-[var(--color-primary-500)] rounded-full animate-spin" />
178
+ ) : (
179
+ <span className="w-4 h-4 block text-[var(--color-secondary-600)]">
180
+ {SearchIcon}
181
+ </span>
182
+ )}
183
+ </div>
184
+
185
+ {/* Clear button or loading text */}
186
+ <div className="absolute right-3 top-1/2 -translate-y-1/2">
187
+ {isSearching ? (
188
+ <span className="text-xs text-[var(--color-secondary-500)] font-medium">
189
+ Searching...
190
+ </span>
191
+ ) : searchTerm ? (
192
+ <button
193
+ type="button"
194
+ onClick={clearSearch}
195
+ className="text-[var(--color-secondary-400)] hover:text-[var(--color-secondary-600)] transition-colors"
196
+ >
197
+ <svg
198
+ xmlns="http://www.w3.org/2000/svg"
199
+ viewBox="0 0 16 16"
200
+ width="16"
201
+ height="16"
202
+ fill="none"
203
+ >
204
+ <path
205
+ d="M12 4L4 12M4 4L12 12"
206
+ stroke="currentColor"
207
+ strokeLinecap="round"
208
+ strokeLinejoin="round"
209
+ strokeWidth="1.5"
210
+ />
211
+ </svg>
212
+ </button>
213
+ ) : null}
214
+ </div>
215
+ </div>
216
+ </div>
217
+ );
218
+ }
@@ -0,0 +1,122 @@
1
+ "use client";
2
+
3
+ import { SearchIcon } from "@/app/utils/svgs/searchIcon";
4
+ import { useState, useEffect, useRef } from "react";
5
+
6
+ interface SearchFilterClientProps {
7
+ value: string;
8
+ onChange: (value: string) => void;
9
+ onSearch?: (value: string) => void;
10
+ }
11
+
12
+ export default function SearchFilterClient({ value, onChange, onSearch }: SearchFilterClientProps) {
13
+ const [searchTerm, setSearchTerm] = useState(value);
14
+ const [isSearching, setIsSearching] = useState(false);
15
+ const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
16
+
17
+ // Sync with external value changes
18
+ useEffect(() => {
19
+ setSearchTerm(value);
20
+ }, [value]);
21
+
22
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
23
+ const newValue = e.target.value;
24
+ setSearchTerm(newValue);
25
+ setIsSearching(true);
26
+
27
+ // Clear existing timeout
28
+ if (debounceTimeoutRef.current) {
29
+ clearTimeout(debounceTimeoutRef.current);
30
+ }
31
+
32
+ // Debounce the search
33
+ debounceTimeoutRef.current = setTimeout(() => {
34
+ setIsSearching(false);
35
+ if (onSearch) {
36
+ onSearch(newValue);
37
+ } else {
38
+ onChange(newValue);
39
+ }
40
+ }, 800);
41
+ };
42
+
43
+ const clearSearch = () => {
44
+ setSearchTerm("");
45
+ setIsSearching(false);
46
+
47
+ if (debounceTimeoutRef.current) {
48
+ clearTimeout(debounceTimeoutRef.current);
49
+ }
50
+
51
+ if (onSearch) {
52
+ onSearch("");
53
+ } else {
54
+ onChange("");
55
+ }
56
+ };
57
+
58
+ // Cleanup on unmount
59
+ useEffect(() => {
60
+ return () => {
61
+ if (debounceTimeoutRef.current) {
62
+ clearTimeout(debounceTimeoutRef.current);
63
+ }
64
+ };
65
+ }, []);
66
+
67
+ return (
68
+ <div className="w-full md:max-w-md relative">
69
+ <div className="relative">
70
+ <input
71
+ type="text"
72
+ placeholder="Search products..."
73
+ value={searchTerm}
74
+ onChange={handleInputChange}
75
+ className="w-full h-10 pl-10 pr-10 text-sm border border-[var(--color-secondary-300)] bg-white rounded-md focus:outline-none focus:ring-2 focus:ring-[var(--color-primary-500)] focus:border-[var(--color-primary-600)] text-[var(--color-secondary-900)] placeholder-[var(--color-secondary-500)]"
76
+ />
77
+
78
+ {/* Search icon or loading spinner */}
79
+ <div className="absolute left-3 top-1/2 -translate-y-1/2">
80
+ {isSearching ? (
81
+ <div className="w-4 h-4 border-2 border-[var(--color-secondary-300)] border-t-[var(--color-primary-500)] rounded-full animate-spin" />
82
+ ) : (
83
+ <span className="w-4 h-4 block text-[var(--color-secondary-600)]">
84
+ {SearchIcon}
85
+ </span>
86
+ )}
87
+ </div>
88
+
89
+ {/* Clear button or loading text */}
90
+ <div className="absolute right-3 top-1/2 -translate-y-1/2">
91
+ {isSearching ? (
92
+ <span className="text-xs text-[var(--color-secondary-500)] font-medium">
93
+ Searching...
94
+ </span>
95
+ ) : searchTerm ? (
96
+ <button
97
+ type="button"
98
+ onClick={clearSearch}
99
+ className="text-[var(--color-secondary-400)] hover:text-[var(--color-secondary-600)] transition-colors"
100
+ >
101
+ <svg
102
+ xmlns="http://www.w3.org/2000/svg"
103
+ viewBox="0 0 16 16"
104
+ width="16"
105
+ height="16"
106
+ fill="none"
107
+ >
108
+ <path
109
+ d="M12 4L4 12M4 4L12 12"
110
+ stroke="currentColor"
111
+ strokeLinecap="round"
112
+ strokeLinejoin="round"
113
+ strokeWidth="1.5"
114
+ />
115
+ </svg>
116
+ </button>
117
+ ) : null}
118
+ </div>
119
+ </div>
120
+ </div>
121
+ );
122
+ }
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ interface SearchLoadingOverlayProps {
4
+ isVisible: boolean;
5
+ searchTerm: string;
6
+ }
7
+
8
+ export default function SearchLoadingOverlay({ isVisible, searchTerm }: SearchLoadingOverlayProps) {
9
+ if (!isVisible) return null;
10
+
11
+ return (
12
+ <div className="absolute inset-0 bg-white/90 backdrop-blur-sm flex items-center justify-center z-20">
13
+ <div className="flex flex-col items-center gap-4 p-8 bg-white rounded-xl shadow-lg border border-[var(--color-secondary-200)] max-w-sm text-center">
14
+ <div className="relative">
15
+ <div className="w-12 h-12 border-4 border-[var(--color-primary-200)] border-t-[var(--color-primary-600)] rounded-full animate-spin"></div>
16
+ <div className="absolute inset-0 w-12 h-12 border-4 border-transparent border-b-[var(--color-primary-400)] rounded-full animate-spin" style={{ animationDirection: 'reverse', animationDuration: '1.5s' }}></div>
17
+ </div>
18
+ <div className="space-y-2">
19
+ <h3 className="text-lg font-semibold text-[var(--color-secondary-800)]">
20
+ Searching Products
21
+ </h3>
22
+ <p className="text-sm text-[var(--color-secondary-600)]">
23
+ Looking for &ldquo;{searchTerm}&rdquo;...
24
+ </p>
25
+ <p className="text-xs text-[var(--color-secondary-500)]">
26
+ This may take a few moments
27
+ </p>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,205 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import ModalLayout from "@/app/components/reuseableUI/modalLayout";
5
+ import { FiltersCollapsible } from "@/app/components/filtersCollapsible";
6
+ import { FilterIcon } from "@/app/utils/svgs/filterIcon";
7
+ import type { GraphQLCategory, GraphQLProductType } from "@/lib/api/shop";
8
+
9
+ type HierarchicalCategory = {
10
+ id: string;
11
+ name: string;
12
+ level: number;
13
+ count: number;
14
+ children: HierarchicalCategory[];
15
+ };
16
+
17
+ type CategoryNode = {
18
+ id: string;
19
+ name: string;
20
+ product_count: number;
21
+ children?: CategoryNode[];
22
+ };
23
+
24
+ // Helper function to build hierarchical category structure
25
+ function buildCategoryHierarchy(categories: GraphQLCategory[]) {
26
+ const categoryMap = new Map();
27
+ const rootCategories: HierarchicalCategory[] = [];
28
+ // First pass: create all categories
29
+ categories.forEach(cat => {
30
+ categoryMap.set(cat.id, {
31
+ id: cat.id,
32
+ name: cat.name,
33
+ level: cat.level,
34
+ count: cat.products.totalCount,
35
+ children: []
36
+ });
37
+ });
38
+
39
+ // Second pass: build hierarchy
40
+ categories.forEach(cat => {
41
+ const hierarchicalCat = categoryMap.get(cat.id)!;
42
+
43
+ if (cat.parent) {
44
+ const parentCat = categoryMap.get(cat.parent.id);
45
+ if (parentCat) {
46
+ parentCat.children.push(hierarchicalCat);
47
+ } else {
48
+ // If parent not found, treat as root
49
+ rootCategories.push(hierarchicalCat);
50
+ }
51
+ } else {
52
+ // No parent, it's a root category
53
+ rootCategories.push(hierarchicalCat);
54
+ }
55
+ });
56
+
57
+ // Sort categories by name at each level
58
+ // const sortCategories = (cats: HierarchicalCategory[]) => {
59
+ // cats.sort((a, b) => a.name.localeCompare(b.name));
60
+ // cats.forEach(cat => sortCategories(cat.children));
61
+ // };
62
+
63
+ // sortCategories(rootCategories);
64
+ return rootCategories;
65
+ }
66
+
67
+ // Helper function to convert to CategoryNode format for FiltersCollapsible
68
+ function convertToCategoryNodes(categories: HierarchicalCategory[]): CategoryNode[] {
69
+ return categories.map(cat => ({
70
+ id: cat.id,
71
+ name: cat.name,
72
+ product_count: cat.count,
73
+ children: cat.children.length > 0 ? convertToCategoryNodes(cat.children) : undefined
74
+ }));
75
+ }
76
+
77
+ interface ShopMobileFiltersProps {
78
+ categories: GraphQLCategory[];
79
+ productTypes: GraphQLProductType[];
80
+ selectedCategoryIds: string[];
81
+ selectedProductTypeIds: string[];
82
+ onCategoryChange: (ids: string[]) => void;
83
+ onProductTypeChange: (ids: string[]) => void;
84
+ filtersLoading: boolean;
85
+ }
86
+
87
+ export default function ShopMobileFilters({
88
+ categories,
89
+ productTypes,
90
+ selectedCategoryIds,
91
+ selectedProductTypeIds,
92
+ onCategoryChange,
93
+ onProductTypeChange,
94
+ filtersLoading
95
+ }: ShopMobileFiltersProps) {
96
+ const [open, setOpen] = useState(false);
97
+ const [tempSelectedCategories, setTempSelectedCategories] = useState<string[]>(selectedCategoryIds);
98
+ const [tempSelectedProductTypes, setTempSelectedProductTypes] = useState<string[]>(selectedProductTypeIds);
99
+
100
+ const activeCount = selectedCategoryIds.length + selectedProductTypeIds.length;
101
+
102
+ const hierarchicalCategories = buildCategoryHierarchy(categories);
103
+
104
+ const openModal = () => {
105
+ setTempSelectedCategories(selectedCategoryIds);
106
+ setTempSelectedProductTypes(selectedProductTypeIds);
107
+ setOpen(true);
108
+ };
109
+
110
+ const applyFilters = () => {
111
+ onCategoryChange(tempSelectedCategories);
112
+ onProductTypeChange(tempSelectedProductTypes);
113
+ setOpen(false);
114
+ };
115
+
116
+ const clearFilters = () => {
117
+ setTempSelectedCategories([]);
118
+ setTempSelectedProductTypes([]);
119
+ };
120
+
121
+ return (
122
+ <>
123
+ {/* Filter Button */}
124
+ <button
125
+ type="button"
126
+ aria-label={
127
+ activeCount > 0
128
+ ? `Open filters, ${activeCount} active`
129
+ : "Open filters"
130
+ }
131
+ className="lg:hidden px-4 h-12 cursor-pointer relative border border-[var(--color-secondary-300)] bg-[var(--color-secondary-110)] transition-colors hover:opacity-80"
132
+ onClick={openModal}
133
+ >
134
+ {FilterIcon}
135
+ {activeCount > 0 && (
136
+ <span
137
+ aria-hidden
138
+ className="absolute -top-1 -right-1 min-w-[18px] h-[18px] px-1 rounded-full bg-[var(--color-primary-600)] text-white text-[10px] leading-[18px] text-center font-semibold"
139
+ >
140
+ {activeCount}
141
+ </span>
142
+ )}
143
+ </button>
144
+
145
+ {/* Modal */}
146
+ <ModalLayout
147
+ isModalOpen={open}
148
+ onClose={() => setOpen(false)}
149
+ heading="Filters"
150
+ className="lg:max-w-lg overflow-hidden"
151
+ >
152
+ <div className="flex h-full flex-col">
153
+ {/* Scrollable content */}
154
+ <div className="flex-1 overflow-y-auto mt-4 md:mt-6 pt-1 md:pt-2 space-y-6 px-1">
155
+ <FiltersCollapsible
156
+ title="Categories"
157
+ categoryTree={{
158
+ data: convertToCategoryNodes(hierarchicalCategories),
159
+ selected: tempSelectedCategories,
160
+ onChange: setTempSelectedCategories,
161
+ loading: filtersLoading,
162
+ }}
163
+ />
164
+
165
+ <FiltersCollapsible
166
+ title="Product Types"
167
+ list={{
168
+ items: productTypes.map(productType => ({
169
+ id: productType.id,
170
+ label: productType.name,
171
+ count: productType.products.totalCount,
172
+ })),
173
+ selected: tempSelectedProductTypes,
174
+ onChange: setTempSelectedProductTypes,
175
+ loading: filtersLoading,
176
+ }}
177
+ defaultOpen={true}
178
+ />
179
+ </div>
180
+
181
+ {/* Fixed footer inside modal panel (non-scrolling) */}
182
+ <div className="shrink-0 border-t border-[var(--color-secondary-200)] bg-[var(--color-secondary-50)] px-4 py-3">
183
+ <div className="space-y-2">
184
+ <button
185
+ type="button"
186
+ onClick={applyFilters}
187
+ className="w-full h-11 rounded bg-[var(--color-primary-600)] text-white font-secondary font-semibold transition-colors hover:opacity-90"
188
+ >
189
+ Apply Filters
190
+ </button>
191
+ <button
192
+ type="button"
193
+ onClick={clearFilters}
194
+ className="w-full h-11 rounded border border-[var(--color-secondary-300)] text-[var(--color-secondary-800)] bg-white font-secondary transition-colors hover:opacity-80"
195
+ >
196
+ Clear All
197
+ </button>
198
+ </div>
199
+ <div className="pt-[env(safe-area-inset-bottom)]" />
200
+ </div>
201
+ </div>
202
+ </ModalLayout>
203
+ </>
204
+ );
205
+ }