@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,628 @@
1
+ 'use client';
2
+
3
+ import { CHECKOUT_CREATE } from '@/graphql/mutations/checkoutCreate';
4
+ import { ME_ADDRESSES_QUERY, type MeAddressesData } from '@/graphql/queries/meAddresses';
5
+ import { useGlobalStore } from '@/store/useGlobalStore';
6
+ import { useQuery } from '@apollo/client';
7
+ import Image from 'next/image';
8
+ import { useRouter } from 'next/navigation';
9
+ import { useCallback, useEffect, useMemo, useState } from 'react';
10
+ import Breadcrumb from '@/app/components/reuseableUI/breadcrumb';
11
+ import CommonButton from '@/app/components/reuseableUI/commonButton';
12
+ import EmptyState from '@/app/components/reuseableUI/emptyState';
13
+ import { ArrowIcon } from '@/app/utils/svgs/arrowIcon';
14
+ import { CartIcon } from '@/app/utils/svgs/cart/cartIcon';
15
+ import { PlusIcon } from '@/app/utils/svgs/cart/plusIcon';
16
+ import { SubtractIcon } from '@/app/utils/svgs/cart/subtractIcon';
17
+ import { gtmBeginCheckout, gtmViewCart, gtmRemoveFromCart, Product } from '@/app/utils/googleTagManager';
18
+ import { useAppConfiguration } from '../providers/ServerAppConfigurationProvider';
19
+ import { getSaleorApiUrl } from '@/lib/saleor/getSaleorApiUrl';
20
+
21
+ type CartItemOption = {
22
+ variantId: string;
23
+ name: string;
24
+ price: number;
25
+ optionSetName: string;
26
+ optionSetLabel: string;
27
+ };
28
+
29
+ type CartItem = {
30
+ key: string;
31
+ id: string;
32
+ name: string;
33
+ price: number;
34
+ image: string;
35
+ quantity: number;
36
+ category?: string;
37
+ sku?: string;
38
+ selectedOptions?: CartItemOption[];
39
+ customInputs?: Record<string, string>;
40
+ skipBaseProduct?: boolean;
41
+ };
42
+
43
+ // Helper to calculate item total including options
44
+ const getItemTotal = (item: CartItem): number => {
45
+ let total = item.price;
46
+ if (item.selectedOptions?.length) {
47
+ total += item.selectedOptions.reduce((sum, opt) => sum + opt.price, 0);
48
+ }
49
+ return total;
50
+ };
51
+
52
+ export default function CartDropDown() {
53
+ const { cartItems: items, totalItems, totalAmount, removeFromCart, updateQuantity, addToCart, checkoutId, setCheckoutId, setCheckoutToken, isLoggedIn, user, guestEmail, guestShippingInfo, setGuestShippingInfo } = useGlobalStore();
54
+ const { getGoogleTagManagerConfig } = useAppConfiguration();
55
+ const router = useRouter();
56
+ const [creating, setCreating] = useState(false);
57
+ const [error, setError] = useState<string | null>(null);
58
+ const [refreshedItems, setRefreshedItems] = useState<CartItem[]>([]);
59
+ const [refreshedTotals, setRefreshedTotals] = useState({ totalItems: 0, totalAmount: 0 });
60
+ const [pricesRefreshed, setPricesRefreshed] = useState(false);
61
+ const [loadingItems, setLoadingItems] = useState<{ [key: string]: { plus: boolean; minus: boolean; remove: boolean } }>({});
62
+
63
+ // When logged in, fetch account addresses
64
+ const { data: meData, loading: meLoading } = useQuery<MeAddressesData>(ME_ADDRESSES_QUERY, { skip: !isLoggedIn });
65
+ const accountShipping = useMemo(() => {
66
+ const me = meData?.me;
67
+ if (!me || !me.addresses?.length) return null;
68
+ const defId = me.defaultShippingAddress?.id;
69
+ return (defId ? me.addresses.find(a => a.id === defId) : me.addresses[0]) || null;
70
+ }, [meData]);
71
+ const accountBilling = useMemo(() => {
72
+ const me = meData?.me;
73
+ if (!me || !me.addresses?.length) return null;
74
+ const defId = me.defaultBillingAddress?.id;
75
+ return (defId ? me.addresses.find(a => a.id === defId) : accountShipping || me.addresses[0]) || null;
76
+ }, [meData, accountShipping]);
77
+
78
+ const endpoint = getSaleorApiUrl();
79
+
80
+ const handleCountryChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
81
+ const value = e.target.value;
82
+ setGuestShippingInfo({ country: value });
83
+ }, [setGuestShippingInfo]);
84
+
85
+ // Refresh prices from checkout if available to ensure we show discounted prices
86
+ useEffect(() => {
87
+ const refreshPricesFromCheckout = async () => {
88
+ if (!checkoutId || pricesRefreshed || items.length === 0) return;
89
+
90
+ try {
91
+ const response = await fetch(endpoint, {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/json' },
94
+ body: JSON.stringify({
95
+ query: `
96
+ query GetCheckoutDetails($id: ID!) {
97
+ checkout(id: $id) {
98
+ id
99
+ totalPrice { gross { amount currency } }
100
+ subtotalPrice { gross { amount currency } }
101
+ lines {
102
+ id
103
+ quantity
104
+ totalPrice { gross { amount currency } }
105
+ variant {
106
+ id
107
+ name
108
+ product {
109
+ name
110
+ thumbnail { url }
111
+ pricing {
112
+ discount { gross { amount currency } }
113
+ }
114
+ }
115
+ pricing {
116
+ price { gross { amount currency } }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ `,
123
+ variables: { id: checkoutId }
124
+ }),
125
+ });
126
+
127
+ if (response.ok) {
128
+ const data = await response.json();
129
+ const checkout = data?.data?.checkout;
130
+
131
+ if (checkout?.lines?.length > 0) {
132
+
133
+ type CheckoutLine = {
134
+ id: string;
135
+ quantity: number;
136
+ variant: {
137
+ id: string;
138
+ product: {
139
+ name: string;
140
+ pricing?: {
141
+ discount?: {
142
+ gross?: {
143
+ amount: number;
144
+ };
145
+ };
146
+ };
147
+ };
148
+ pricing?: {
149
+ price?: {
150
+ gross?: {
151
+ amount: number;
152
+ };
153
+ };
154
+ };
155
+ name: string;
156
+ };
157
+ totalPrice: {
158
+ gross: {
159
+ amount: number;
160
+ currency: string;
161
+ };
162
+ };
163
+ name: string;
164
+ };
165
+
166
+ // Build a map of checkout lines by variant ID for quick lookup
167
+ const checkoutLinesByVariantId = new Map<string, CheckoutLine>();
168
+ for (const line of checkout.lines) {
169
+ checkoutLinesByVariantId.set(line.variant.id, line);
170
+ }
171
+
172
+ const updatedItems: CartItem[] = items
173
+ .map((item: CartItem) => {
174
+ if (item.skipBaseProduct && item.selectedOptions?.length) {
175
+ // For items with skipBaseProduct, match using the first selectedOption's variant
176
+ const firstOptionLine = checkoutLinesByVariantId.get(item.selectedOptions[0].variantId);
177
+ if (!firstOptionLine) return null;
178
+
179
+ // Calculate total price from all option lines
180
+ let optionsTotalPrice = 0;
181
+ const updatedOptions = item.selectedOptions.map(opt => {
182
+ const optLine = checkoutLinesByVariantId.get(opt.variantId);
183
+ if (optLine) {
184
+ const lineTotal = optLine.totalPrice?.gross?.amount ?? 0;
185
+ const qty = Math.max(1, optLine.quantity);
186
+ const unitPrice = lineTotal / qty;
187
+ optionsTotalPrice += unitPrice;
188
+ return { ...opt, price: unitPrice };
189
+ }
190
+ return opt;
191
+ });
192
+
193
+ return {
194
+ ...item,
195
+ price: 0, // Base product not included
196
+ // Don't update quantity from Saleor - use the grouped quantity from store
197
+ // Saleor combines lines, so line.quantity would be wrong for grouped items
198
+ selectedOptions: updatedOptions,
199
+ };
200
+ } else {
201
+ // Normal item - match by item id (base product variant)
202
+ const line = checkoutLinesByVariantId.get(item.id);
203
+ if (!line) return null;
204
+
205
+ // Use line totalPrice divided by quantity to get the actual unit price
206
+ const lineTotal = line?.totalPrice?.gross?.amount ?? 0;
207
+ const qty = Math.max(1, line.quantity);
208
+ const unitPrice = lineTotal / qty;
209
+
210
+ return {
211
+ ...item,
212
+ price: unitPrice,
213
+ // Don't update quantity from Saleor - use the grouped quantity from store
214
+ // Saleor combines lines, so line.quantity would be wrong for grouped items
215
+ };
216
+ }
217
+ })
218
+ .filter(Boolean) as CartItem[];
219
+
220
+ const totalItems = updatedItems.reduce((sum, item) => sum + item.quantity, 0);
221
+ const totalAmount = updatedItems.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
222
+
223
+ setRefreshedItems(updatedItems);
224
+ setRefreshedTotals({ totalItems, totalAmount });
225
+ setPricesRefreshed(true);
226
+ }
227
+ }
228
+ } catch (error) {
229
+ console.error('[CartDropdown] Failed to refresh prices from checkout:', error);
230
+ }
231
+ };
232
+
233
+ refreshPricesFromCheckout();
234
+ }, [checkoutId, endpoint, items, pricesRefreshed]);
235
+
236
+ // Reset prices refreshed state when global cart items change (e.g., when items are removed from cart page)
237
+ useEffect(() => {
238
+ if (pricesRefreshed && refreshedItems.length > 0) {
239
+ // Check if any items in refreshedItems no longer exist in the global store (by key)
240
+ const hasRemovedItems = refreshedItems.some(refreshedItem =>
241
+ !items.find(globalItem => globalItem.key === refreshedItem.key)
242
+ );
243
+
244
+ // Check if global items count differs from refreshed items count
245
+ const itemCountDiffers = items.length !== refreshedItems.length;
246
+
247
+ if (hasRemovedItems || itemCountDiffers) {
248
+ setPricesRefreshed(false);
249
+ setRefreshedItems([]);
250
+ setRefreshedTotals({ totalItems: 0, totalAmount: 0 });
251
+ }
252
+ }
253
+ }, [items, refreshedItems, pricesRefreshed]);
254
+
255
+ // Force refresh when cart becomes empty but dropdown still has cached items
256
+ useEffect(() => {
257
+ if (items.length === 0 && (refreshedItems.length > 0 || pricesRefreshed)) {
258
+ setPricesRefreshed(false);
259
+ setRefreshedItems([]);
260
+ setRefreshedTotals({ totalItems: 0, totalAmount: 0 });
261
+ }
262
+ }, [items.length, refreshedItems.length, pricesRefreshed]);
263
+
264
+ // Use refreshed items and totals if available, otherwise fall back to store values
265
+ const displayItems = pricesRefreshed ? refreshedItems : items;
266
+ const displayTotalItems = pricesRefreshed ? refreshedTotals.totalItems : totalItems;
267
+ const displayTotalAmount = pricesRefreshed ? refreshedTotals.totalAmount : totalAmount;
268
+
269
+ // Track view_cart GTM event when cart dropdown is opened/items change
270
+ useEffect(() => {
271
+ const gtmConfig = getGoogleTagManagerConfig();
272
+ const currentItems = pricesRefreshed ? refreshedItems : items;
273
+ if (currentItems.length > 0) {
274
+ const products: Product[] = currentItems.map((item, index) => ({
275
+ item_id: item.id,
276
+ item_name: item.name,
277
+ item_category: item.category || 'Products',
278
+ price: item.price,
279
+ quantity: item.quantity,
280
+ currency: 'USD',
281
+ index: index
282
+ }));
283
+ const totalValue = currentItems.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
284
+ gtmViewCart(products, 'USD', totalValue, gtmConfig?.container_id);
285
+ }
286
+ }, [items, refreshedItems, pricesRefreshed, getGoogleTagManagerConfig]);
287
+
288
+ // Enhanced cart functions that work with refreshed prices and use add to cart endpoint
289
+ // Now uses item.key for all identity operations to support different option sets
290
+ const handleRemoveFromCart = useCallback(async (itemKey: string) => {
291
+ setLoadingItems(prev => ({ ...prev, [itemKey]: { ...prev[itemKey], remove: true } }));
292
+
293
+ // Track remove_from_cart GTM event BEFORE removing the item
294
+ const gtmConfig = getGoogleTagManagerConfig();
295
+ const currentItems = pricesRefreshed ? refreshedItems : items;
296
+ const itemToRemove = currentItems.find(item => item.key === itemKey);
297
+
298
+ if (itemToRemove) {
299
+ const product: Product = {
300
+ item_id: itemToRemove.id,
301
+ item_name: itemToRemove.name,
302
+ item_category: itemToRemove.category || 'Products',
303
+ price: itemToRemove.price,
304
+ quantity: itemToRemove.quantity,
305
+ currency: 'USD',
306
+ index: 0
307
+ };
308
+ const itemValue = getItemTotal(itemToRemove) * itemToRemove.quantity;
309
+ gtmRemoveFromCart([product], 'USD', itemValue, gtmConfig?.container_id);
310
+ }
311
+
312
+ try {
313
+ if (pricesRefreshed) {
314
+ // Update both store and local refreshed state
315
+ setRefreshedItems(prev => prev.filter(item => item.key !== itemKey));
316
+ setRefreshedTotals(() => {
317
+ const filteredItems = refreshedItems.filter(item => item.key !== itemKey);
318
+ const totalItems = filteredItems.reduce((sum, item) => sum + item.quantity, 0);
319
+ const totalAmount = filteredItems.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
320
+ return { totalItems, totalAmount };
321
+ });
322
+ }
323
+ removeFromCart(itemKey);
324
+
325
+ // Small delay to ensure backend sync completes
326
+ await new Promise(resolve => setTimeout(resolve, 500));
327
+ } finally {
328
+ setLoadingItems(prev => ({ ...prev, [itemKey]: { ...prev[itemKey], remove: false } }));
329
+ }
330
+ }, [pricesRefreshed, refreshedItems, removeFromCart]);
331
+
332
+ const handleUpdateQuantity = useCallback(async (itemKey: string, newQuantity: number) => {
333
+ setLoadingItems(prev => ({ ...prev, [itemKey]: { ...prev[itemKey], minus: true } }));
334
+
335
+ try {
336
+ if (pricesRefreshed) {
337
+ // Update both store and local refreshed state
338
+ setRefreshedItems(prev => {
339
+ const updated = prev.map(item =>
340
+ item.key === itemKey
341
+ ? { ...item, quantity: Math.max(0, newQuantity) }
342
+ : item
343
+ ).filter(item => item.quantity > 0);
344
+
345
+ // Update totals
346
+ const totalItems = updated.reduce((sum, item) => sum + item.quantity, 0);
347
+ const totalAmount = updated.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
348
+ setRefreshedTotals({ totalItems, totalAmount });
349
+
350
+ return updated;
351
+ });
352
+ }
353
+ updateQuantity(itemKey, newQuantity);
354
+
355
+ // Small delay to ensure backend sync completes
356
+ await new Promise(resolve => setTimeout(resolve, 500));
357
+ } finally {
358
+ setLoadingItems(prev => ({ ...prev, [itemKey]: { ...prev[itemKey], minus: false } }));
359
+ }
360
+ }, [pricesRefreshed, updateQuantity]);
361
+
362
+ const handleAddToCart = useCallback(async (item: CartItem) => {
363
+ setLoadingItems(prev => ({ ...prev, [item.key]: { ...prev[item.key], plus: true } }));
364
+
365
+ try {
366
+ await addToCart({
367
+ key: item.key,
368
+ id: item.id,
369
+ name: item.name,
370
+ price: item.price,
371
+ image: item.image,
372
+ quantity: 1,
373
+ sku: item.sku,
374
+ category: item.category,
375
+ selectedOptions: item.selectedOptions,
376
+ customInputs: item.customInputs,
377
+ skipBaseProduct: item.skipBaseProduct
378
+ });
379
+
380
+ // Reset price refresh state to force refresh with new data
381
+ setPricesRefreshed(false);
382
+ } catch (error) {
383
+ console.error('[CartDropdown] Failed to add item to cart:', error);
384
+ } finally {
385
+ setLoadingItems(prev => ({ ...prev, [item.key]: { ...prev[item.key], plus: false } }));
386
+ }
387
+ }, [addToCart]);
388
+
389
+ const handleProceed = useCallback(async () => {
390
+ setError(null);
391
+ if (isLoggedIn && meLoading) {
392
+ return;
393
+ }
394
+
395
+ // Track begin_checkout GTM event
396
+ const gtmConfig = getGoogleTagManagerConfig();
397
+ if (displayItems.length > 0) {
398
+ const products: Product[] = displayItems.map((item, index) => ({
399
+ item_id: item.id,
400
+ item_name: item.name,
401
+ item_category: item.category || 'Products',
402
+ price: item.price,
403
+ quantity: item.quantity,
404
+ currency: 'USD',
405
+ index: index
406
+ }));
407
+ const totalValue = displayItems.reduce((sum, item) => sum + (getItemTotal(item) * item.quantity), 0);
408
+ gtmBeginCheckout(products, 'USD', totalValue, undefined, gtmConfig?.container_id);
409
+ }
410
+
411
+ // If we already have a checkout, use it instead of creating a new one
412
+ if (checkoutId) {
413
+ router.push(`/checkout?checkoutId=${encodeURIComponent(checkoutId)}`);
414
+ return;
415
+ }
416
+
417
+ // Otherwise, create a new checkout
418
+ setCreating(true);
419
+ try {
420
+ // Build lines from cart using the item id as variantId directly
421
+ const lines = displayItems.map((it) => ({ quantity: it.quantity, variantId: it.id }));
422
+ if (lines.length === 0) {
423
+ setCreating(false);
424
+ return;
425
+ }
426
+ const email = (isLoggedIn ? (user?.email || meData?.me?.email || '') : guestEmail) || 'guest@example.com';
427
+
428
+ const mutation = CHECKOUT_CREATE;
429
+ type CheckoutLineInputTS = { variantId: string; quantity: number };
430
+ type CheckoutCreateInputTS = {
431
+ channel: string;
432
+ email: string;
433
+ lines: CheckoutLineInputTS[];
434
+ };
435
+ const input: CheckoutCreateInputTS = {
436
+ channel: process.env.NEXT_PUBLIC_SALEOR_CHANNEL || 'default-channel',
437
+ email,
438
+ lines,
439
+ };
440
+ const variables = { input };
441
+ const res = await fetch(endpoint, {
442
+ method: 'POST',
443
+ headers: { 'Content-Type': 'application/json' },
444
+ body: JSON.stringify({ query: mutation, variables }),
445
+ });
446
+ if (!res.ok) throw new Error('Failed to create checkout');
447
+ const json = await res.json();
448
+ const errs = json.data?.checkoutCreate?.errors;
449
+ if (errs && errs.length) throw new Error(errs[0]?.message || 'Checkout creation error');
450
+ const createdId = json.data?.checkoutCreate?.checkout?.id as string | undefined;
451
+ const createdToken = json.data?.checkoutCreate?.checkout?.token as string | undefined;
452
+ if (!createdId) throw new Error('No checkout id returned');
453
+ setCheckoutId(createdId);
454
+ if (createdToken) {
455
+ setCheckoutToken(createdToken);
456
+ }
457
+ try {
458
+ localStorage.setItem('checkoutId', createdId);
459
+ if (createdToken) localStorage.setItem('checkoutToken', createdToken);
460
+ } catch { }
461
+ router.push(`/checkout?checkoutId=${encodeURIComponent(createdId)}`);
462
+ } catch (e) {
463
+ const msg = e instanceof Error ? e.message : 'Unable to proceed to checkout';
464
+ console.error('[Cart] handleProceed error:', e);
465
+ setError(msg);
466
+ } finally {
467
+ setCreating(false);
468
+ }
469
+ }, [checkoutId, endpoint, guestEmail, isLoggedIn, displayItems, meData, meLoading, router, setCheckoutId, setCheckoutToken, user?.email]);
470
+
471
+ if (displayItems.length === 0) {
472
+ return (
473
+ <div className="max-w-md w-full absolute right-20 top-20 pt-7 z-[100]">
474
+ <div className='bg-white shadow-[0_10px_20px_0_#0000001A] p-4'>
475
+ <p className='border-b border-[var(--color-secondary-200)] pb-2 font-secondary font-semibold text-xl text-[var(--color-secondary-800)]'>
476
+ MY CART
477
+ </p>
478
+ <EmptyState
479
+ icon={CartIcon}
480
+ iconContainer="p-5"
481
+ text="YOUR CART IS EMPTY"
482
+ textParagraph="Browse parts and accessories to get started."
483
+ className='min-h-96'
484
+ />
485
+ </div>
486
+ </div >
487
+ );
488
+ }
489
+
490
+ return (
491
+ <div className="max-w-md w-full absolute right-20 top-20 pt-7 z-[100]">
492
+ <div className='bg-white shadow-[0_10px_20px_0_#0000001A] p-4'>
493
+ <div className="space-y-5">
494
+ <p className='border-b border-[var(--color-secondary-200)] pb-2 font-secondary font-semibold text-xl text-[var(--color-secondary-800)]'>
495
+ MY CART
496
+ </p>
497
+ <div className='space-y-4 max-h-[250px] overflow-y-auto pr-2'>
498
+ {displayItems.map((item: CartItem) => (
499
+ <div key={item.key} className="flex items-center gap-4 border-b border-[var(--color-secondary-200)] last:border-b-0 pb-4 last:pb-0">
500
+ <div className='w-full items-center gap-2 flex'>
501
+ <div className="relative size-[100px] flex-shrink-0">
502
+ <Image
503
+ src={item?.image || '/no-image-avail-large.png'}
504
+ alt={item?.name || 'Product Image'}
505
+ fill
506
+ className="object-contain"
507
+ />
508
+ </div>
509
+ <div className="space-y-4">
510
+ <div className='space-y-1'>
511
+ <p className="text-xs font-normal text-[var(--color-secondary-800)]">{item.category ?? "N/A"}</p>
512
+ <h3 className="font-medium font-secondary text-sm text-[var(--color-secondary-800)] line-clamp-2">{item.name}</h3>
513
+ {/* Display selected options */}
514
+ {item.selectedOptions && item.selectedOptions.length > 0 && (
515
+ <div className="mt-1 space-y-0.5">
516
+ {item.selectedOptions.map((option, idx) => (
517
+ <p key={idx} className="text-xs text-[var(--color-secondary-600)]">
518
+ + {option.name}
519
+ </p>
520
+ ))}
521
+ </div>
522
+ )}
523
+ {/* Display custom inputs */}
524
+ {item.customInputs && Object.keys(item.customInputs).length > 0 && (
525
+ <div className="mt-1 space-y-0.5">
526
+ {Object.entries(item.customInputs).map(([key, value]) => (
527
+ <p key={key} className="text-xs text-[var(--color-secondary-600)]">
528
+ + {key}: {value}
529
+ </p>
530
+ ))}
531
+ </div>
532
+ )}
533
+ </div>
534
+ <p className="font-semibold text-base text-[var(--color-primary-600)] font-secondary">
535
+ ${(getItemTotal(item) * item.quantity).toFixed(2)}
536
+ </p>
537
+ </div>
538
+ </div>
539
+ <div className='flex flex-col items-end gap-8'>
540
+ <CommonButton
541
+ onClick={() => handleRemoveFromCart(item.key)}
542
+ disabled={loadingItems[item.key]?.remove || loadingItems[item.key]?.plus || loadingItems[item.key]?.minus}
543
+ variant="tertiary"
544
+ className="p-0"
545
+ >
546
+ {loadingItems[item.key]?.remove ? 'Removing...' : 'Remove'}
547
+ </CommonButton>
548
+ <div className="flex items-center border border-[var(--color-secondary-200)] bg-[var(--color-secondary-50)] px-2 py-3 gap-2 min-w-32 justify-between">
549
+ <button
550
+ onClick={() => handleUpdateQuantity(item.key, item.quantity - 1)}
551
+ disabled={loadingItems[item.key]?.minus || loadingItems[item.key]?.plus || loadingItems[item.key]?.remove}
552
+ className={`border-r border-[var(--color-secondary-200)] [&>svg]:size-6 pr-2 transition-opacity ${
553
+ loadingItems[item.key]?.minus || loadingItems[item.key]?.plus || loadingItems[item.key]?.remove
554
+ ? 'cursor-not-allowed opacity-50'
555
+ : 'cursor-pointer hover:opacity-75'
556
+ }`}
557
+ >
558
+ {loadingItems[item.key]?.minus ? (
559
+ <svg className="animate-spin size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
560
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
561
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
562
+ </svg>
563
+ ) : (
564
+ SubtractIcon
565
+ )}
566
+ </button>
567
+ <span className='font-normal text-base font-secondary text-[var(--color-secondary-800)]'>{item.quantity}</span>
568
+ <button
569
+ onClick={() => handleAddToCart(item)}
570
+ disabled={loadingItems[item.key]?.plus || loadingItems[item.key]?.minus || loadingItems[item.key]?.remove}
571
+ className={`border-l border-[var(--color-secondary-200)] [&>svg]:size-6 pl-2 transition-opacity ${
572
+ loadingItems[item.key]?.plus || loadingItems[item.key]?.minus || loadingItems[item.key]?.remove
573
+ ? 'cursor-not-allowed opacity-50'
574
+ : 'cursor-pointer hover:opacity-75'
575
+ }`}
576
+ >
577
+ {loadingItems[item.key]?.plus ? (
578
+ <svg className="animate-spin size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
579
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
580
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
581
+ </svg>
582
+ ) : (
583
+ PlusIcon
584
+ )}
585
+ </button>
586
+ </div>
587
+
588
+ </div>
589
+ </div>
590
+ ))}
591
+ </div>
592
+ <div className="gap-2 flex flex-col mb-4 font-normal text-base font-secondary text-[var(--color-secondary-600)] border-t border-[var(--color-secondary-200)] pt-4">
593
+ <div className="flex justify-between">
594
+ <span>Subtotal</span>
595
+ <span className='font-medium'>${displayTotalAmount.toFixed(2)}</span>
596
+ </div>
597
+ {/* <div className="flex justify-between pb-2">
598
+ <span>Tax</span>
599
+ <span className='font-medium'>N/A</span>
600
+ </div> */}
601
+ <div className="border-t border-[var(--color-secondary-200)] pt-4 flex justify-between text-xl text-[var(--color-secondary-600)] font-medium ">
602
+ <span>TOTAL</span>
603
+ <span className='font-semibold text-[var(--color-secondary-800)]'>${displayTotalAmount.toFixed(2)}</span>
604
+ </div>
605
+ </div>
606
+ {error && (
607
+ <div className="mb-3 text-sm text-red-600">{error}</div>
608
+ )}
609
+ <div className='grid grid-cols-2 gap-4'>
610
+ <CommonButton
611
+ onClick={() => router.push("/cart")}
612
+ variant="secondary"
613
+ >
614
+ VIEW CART
615
+ </CommonButton>
616
+ <CommonButton
617
+ onClick={handleProceed}
618
+ disabled={creating || (isLoggedIn && meLoading)}
619
+ variant="primary"
620
+ >
621
+ {creating ? 'Loading..' : (isLoggedIn && meLoading ? 'Loading your address...' : 'CHECKOUT')}
622
+ </CommonButton>
623
+ </div>
624
+ </div>
625
+ </div>
626
+ </div>
627
+ );
628
+ }
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { NewsLetterClient } from "../../reuseableUI/newsletter/newsletterClient";
5
+
6
+ export const FooterNewsletter = () => {
7
+ return (
8
+ <div className="flex flex-col gap-4 w-full max-w-md">
9
+ <h3 className="text-xl md:text-2xl font-primary italic font-black text-[var(--color-primary-600)] uppercase leading-tight">
10
+ JOIN THE LIST & UNLOCK 5% OFF.
11
+ </h3>
12
+ <p className="text-sm text-white font-secondary">
13
+ Sign up for insider deals, product releases, and expert advice for all your performance needs.
14
+ </p>
15
+ <div className="[&_form]:flex [&_form]:flex-row [&_form]:gap-0 [&_form>div:first-child]:flex-1 [&_input]:bg-[#262626] [&_input]:border-[#404040] [&_input]:text-white [&_input]:rounded-l-md [&_input]:rounded-r-none [&_input]:h-12 [&_button]:rounded-r-md [&_button]:rounded-l-none [&_button]:bg-[var(--color-primary-600)] [&_button]:text-white [&_button]:font-bold [&_button]:px-6 [&_button]:h-12 [&_button]:hover:bg-[var(--color-primary-700)] [&_main]:p-0 [&_main>div]:p-0 [&_main>div>div]:p-0 [&_main>div>div>div]:hidden [&_form>div:last-child]:w-auto [&_form>div:last-child]:flex-none">
16
+ <NewsLetterClient isModalNewsletter={true} buttonText="JOIN" />
17
+ </div>
18
+ </div>
19
+ );
20
+ };
21
+