@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,1775 @@
1
+ "use client";
2
+
3
+ import { ApolloClient, HttpLink, InMemoryCache, gql } from "@apollo/client";
4
+
5
+ type GraphQLError = {
6
+ message: string;
7
+ code?: string;
8
+ field?: string;
9
+ };
10
+
11
+ // type GraphQLResponseError = {
12
+ // extensions?: {
13
+ // exception?: {
14
+ // code: string;
15
+ // };
16
+ // };
17
+ // message: string;
18
+ // code?: string;
19
+ // };
20
+ import { useRouter, useSearchParams } from "next/navigation";
21
+ import { useEffect, useMemo, useState } from "react";
22
+ import CommonButton from "../components/reuseableUI/commonButton";
23
+ import EmptyState from "../components/reuseableUI/emptyState";
24
+ import LoadingUI from "../components/reuseableUI/loadingUI";
25
+ import { ArrowIcon } from "../utils/svgs/arrowIcon";
26
+ import { SuccessTickIcon } from "../utils/svgs/cart/successTickIcon";
27
+ import { useGlobalStore } from "@/store/useGlobalStore";
28
+ import Image from "next/image";
29
+ import { getSaleorApiUrl } from "@/lib/saleor/getSaleorApiUrl";
30
+ import {
31
+ gtmPurchase,
32
+ gtmEnhancedConversion,
33
+ Product,
34
+ PurchaseData,
35
+ EnhancedConversionData,
36
+ } from "../utils/googleTagManager";
37
+ import { useAppConfiguration } from "../components/providers/ServerAppConfigurationProvider";
38
+
39
+ /** GraphQL ops */
40
+ const GET_CHECKOUT_SUMMARY = gql`
41
+ query getCheckoutSummary($checkoutId: ID!) {
42
+ checkout(id: $checkoutId) {
43
+ id
44
+ created
45
+ email
46
+ lines {
47
+ id
48
+ quantity
49
+ metadata {
50
+ key
51
+ value
52
+ }
53
+ variant {
54
+ id
55
+ sku
56
+ name
57
+ metadata {
58
+ key
59
+ value
60
+ }
61
+ product {
62
+ id
63
+ name
64
+ category {
65
+ name
66
+ }
67
+ thumbnail {
68
+ url
69
+ alt
70
+ }
71
+ }
72
+ }
73
+ totalPrice {
74
+ gross {
75
+ amount
76
+ currency
77
+ }
78
+ }
79
+ }
80
+ subtotalPrice {
81
+ gross {
82
+ amount
83
+ currency
84
+ }
85
+ net {
86
+ amount
87
+ currency
88
+ }
89
+ tax {
90
+ amount
91
+ currency
92
+ }
93
+ currency
94
+ }
95
+ shippingPrice {
96
+ gross {
97
+ amount
98
+ currency
99
+ }
100
+ net {
101
+ amount
102
+ currency
103
+ }
104
+ tax {
105
+ amount
106
+ currency
107
+ }
108
+ currency
109
+ }
110
+ totalPrice {
111
+ gross {
112
+ amount
113
+ currency
114
+ }
115
+ net {
116
+ amount
117
+ currency
118
+ }
119
+ tax {
120
+ amount
121
+ currency
122
+ }
123
+ currency
124
+ }
125
+ shippingMethod {
126
+ name
127
+ }
128
+ shippingAddress {
129
+ streetAddress1
130
+ city
131
+ countryArea
132
+ postalCode
133
+ country {
134
+ country
135
+ }
136
+ }
137
+ billingAddress {
138
+ firstName
139
+ lastName
140
+ phone
141
+ streetAddress1
142
+ city
143
+ countryArea
144
+ postalCode
145
+ country {
146
+ country
147
+ }
148
+ }
149
+ }
150
+ }
151
+ `;
152
+
153
+ const GET_ORDER_SUMMARY = gql`
154
+ query getOrderSummary($orderId: ID!) {
155
+ order(id: $orderId) {
156
+ id
157
+ number
158
+ created
159
+ userEmail
160
+ lines {
161
+ id
162
+ quantity
163
+ productName
164
+ metadata {
165
+ key
166
+ value
167
+ }
168
+ totalPrice {
169
+ gross {
170
+ amount
171
+ currency
172
+ }
173
+ }
174
+ thumbnail(size: 200) {
175
+ url
176
+ alt
177
+ }
178
+ variant {
179
+ id
180
+ sku
181
+ name
182
+ metadata {
183
+ key
184
+ value
185
+ }
186
+ product {
187
+ id
188
+ name
189
+ category {
190
+ name
191
+ }
192
+ }
193
+ }
194
+ }
195
+ subtotal {
196
+ gross {
197
+ amount
198
+ currency
199
+ }
200
+ net {
201
+ amount
202
+ currency
203
+ }
204
+ tax {
205
+ amount
206
+ currency
207
+ }
208
+ }
209
+ shippingPrice {
210
+ gross {
211
+ amount
212
+ currency
213
+ }
214
+ net {
215
+ amount
216
+ currency
217
+ }
218
+ tax {
219
+ amount
220
+ currency
221
+ }
222
+ }
223
+ total {
224
+ gross {
225
+ amount
226
+ currency
227
+ }
228
+ net {
229
+ amount
230
+ currency
231
+ }
232
+ tax {
233
+ amount
234
+ currency
235
+ }
236
+ }
237
+ shippingMethod {
238
+ id
239
+ name
240
+ }
241
+ shippingAddress {
242
+ streetAddress1
243
+ city
244
+ countryArea
245
+ postalCode
246
+ country {
247
+ country
248
+ }
249
+ }
250
+ billingAddress {
251
+ firstName
252
+ lastName
253
+ phone
254
+ streetAddress1
255
+ city
256
+ countryArea
257
+ postalCode
258
+ country {
259
+ country
260
+ }
261
+ }
262
+ }
263
+ }
264
+ `;
265
+
266
+ const COMPLETE_CHECKOUT = gql`
267
+ mutation CompleteCheckout($checkoutId: ID!) {
268
+ checkoutComplete(id: $checkoutId) {
269
+ order {
270
+ id
271
+ }
272
+ errors {
273
+ field
274
+ message
275
+ code
276
+ }
277
+ }
278
+ }
279
+ `;
280
+
281
+ const TRANSACTION_PROCESS = gql`
282
+ mutation TransactionProcess($transactionId: ID!, $data: JSON) {
283
+ transactionProcess(id: $transactionId, data: $data) {
284
+ transaction {
285
+ id
286
+ }
287
+ data
288
+ errors {
289
+ field
290
+ message
291
+ code
292
+ }
293
+ }
294
+ }
295
+ `;
296
+
297
+ /** minimal types used in UI */
298
+ type Money = { amount: number; currency: string };
299
+ type TaxedMoney = { gross: Money; net?: Money; tax?: Money };
300
+
301
+ type MetadataItem = {
302
+ key: string;
303
+ value: string;
304
+ };
305
+
306
+ type OptionSetMetadata = {
307
+ name: string;
308
+ label: string;
309
+ type?: string;
310
+ hidden?: boolean;
311
+ deselect?: string;
312
+ required?: boolean;
313
+ base_product_required?: boolean;
314
+ };
315
+
316
+ type CheckoutLine = {
317
+ id: string;
318
+ quantity: number;
319
+ metadata?: MetadataItem[] | null;
320
+ variant: {
321
+ id: string;
322
+ sku?: string | null;
323
+ name: string;
324
+ metadata?: MetadataItem[] | null;
325
+ product: {
326
+ id: string;
327
+ name: string;
328
+ category?: {
329
+ name: string;
330
+ } | null;
331
+ thumbnail?: {
332
+ url: string;
333
+ alt: string;
334
+ } | null;
335
+ };
336
+ };
337
+ totalPrice: TaxedMoney;
338
+ };
339
+
340
+ type OrderLine = {
341
+ id: string;
342
+ quantity: number;
343
+ productName: string;
344
+ metadata?: MetadataItem[] | null;
345
+ thumbnail?: {
346
+ url: string;
347
+ alt: string;
348
+ } | null;
349
+ variant?: {
350
+ id: string;
351
+ sku?: string | null;
352
+ name: string;
353
+ metadata?: MetadataItem[] | null;
354
+ product: {
355
+ id: string;
356
+ name: string;
357
+ category?: {
358
+ name: string;
359
+ } | null;
360
+ };
361
+ } | null;
362
+ totalPrice: TaxedMoney;
363
+ };
364
+
365
+ type OptionItem = {
366
+ lineId: string;
367
+ variantId: string;
368
+ variantName: string;
369
+ optionSetLabel: string;
370
+ price: number;
371
+ currency: string;
372
+ };
373
+
374
+ type CustomInput = {
375
+ key: string;
376
+ value: string;
377
+ };
378
+
379
+ type GroupedOrderItem = {
380
+ id: string;
381
+ name: string;
382
+ category: string;
383
+ thumbnail?: { url: string; alt: string } | null;
384
+ quantity: number;
385
+ basePrice: number;
386
+ totalPrice: TaxedMoney;
387
+ currency: string;
388
+ options: OptionItem[];
389
+ customInputs: CustomInput[];
390
+ };
391
+
392
+ type Address = {
393
+ streetAddress1?: string | null;
394
+ city?: string | null;
395
+ countryArea?: string | null;
396
+ postalCode?: string | null;
397
+ country: { country: string };
398
+ firstName?: string | null;
399
+ lastName?: string | null;
400
+ phone?: string | null;
401
+ };
402
+
403
+ type Checkout = {
404
+ id: string;
405
+ created: string;
406
+ email?: string | null;
407
+ lines: CheckoutLine[];
408
+ subtotalPrice: { gross: Money; net?: Money; tax?: Money; currency: string };
409
+ shippingPrice: { gross: Money; net?: Money; tax?: Money; currency: string };
410
+ totalPrice: { gross: Money; net?: Money; tax?: Money; currency: string };
411
+ shippingAddress?: Address | null;
412
+ billingAddress?: Address | null;
413
+ deliveryMethod?: {
414
+ id: string;
415
+ name: string;
416
+ price: { gross: Money; currency: string };
417
+ } | null;
418
+ shippingMethod?: {
419
+ name: string;
420
+ } | null;
421
+ };
422
+
423
+ type Order = {
424
+ id: string;
425
+ number?: string | null;
426
+ created: string;
427
+ userEmail?: string | null;
428
+ lines: OrderLine[];
429
+ subtotal: { gross: Money; net?: Money; tax?: Money; currency: string };
430
+ shippingPrice: { gross: Money; net?: Money; tax?: Money; currency: string };
431
+ total: { gross: Money; net?: Money; tax?: Money; currency: string };
432
+ shippingAddress?: Address | null;
433
+ billingAddress?: Address | null;
434
+ shippingMethod?: {
435
+ name: string;
436
+ } | null;
437
+ };
438
+
439
+ type OrderData = Checkout | Order;
440
+
441
+ function formatDateTime(ts: string | Date) {
442
+ const d = ts instanceof Date ? ts : new Date(ts);
443
+ const yyyy = d.getFullYear();
444
+ const MM = String(d.getMonth() + 1).padStart(2, "0");
445
+ const dd = String(d.getDate()).padStart(2, "0");
446
+ const HH = String(d.getHours()).padStart(2, "0");
447
+ const mm = String(d.getMinutes()).padStart(2, "0");
448
+ return `${yyyy}-${MM}-${dd} ${HH}:${mm}`;
449
+ }
450
+
451
+ function errMsg(e: unknown): string {
452
+ if (e instanceof Error) return e.message;
453
+ if (typeof e === "string") return e;
454
+ try {
455
+ return JSON.stringify(e);
456
+ } catch {
457
+ return "Unknown error";
458
+ }
459
+ }
460
+
461
+ // Helper to parse option_set metadata from a variant
462
+ function parseOptionSetMetadata(
463
+ metadata: MetadataItem[] | null | undefined
464
+ ): OptionSetMetadata | null {
465
+ if (!metadata) return null;
466
+ const optionMeta = metadata.find((m) => m.key === "option_set");
467
+ if (!optionMeta?.value) return null;
468
+ try {
469
+ return JSON.parse(optionMeta.value) as OptionSetMetadata;
470
+ } catch {
471
+ return null;
472
+ }
473
+ }
474
+
475
+ // Helper to extract custom inputs from line metadata (non-SKU option sets)
476
+ // These are user-provided values stored on the order/checkout line itself
477
+ function extractCustomInputs(
478
+ lineMetadata: MetadataItem[] | null | undefined
479
+ ): CustomInput[] {
480
+ if (!lineMetadata || lineMetadata.length === 0) return [];
481
+
482
+ // Filter out system metadata keys that aren't custom inputs
483
+ const systemKeys = new Set([
484
+ "__typename",
485
+ "option_set",
486
+ "wsm_availability",
487
+ "wsm_brand",
488
+ "wsm_condition",
489
+ "wsm_cost",
490
+ "wsm_dealer_id",
491
+ "wsm_height",
492
+ "wsm_id",
493
+ "wsm_inventory",
494
+ "wsm_length",
495
+ "wsm_price",
496
+ "wsm_sale_price",
497
+ "wsm_upscode",
498
+ "wsm_width",
499
+ ]);
500
+
501
+ return lineMetadata
502
+ .filter((m) => !systemKeys.has(m.key))
503
+ .map((m) => ({ key: m.key, value: m.value }));
504
+ }
505
+
506
+ // Group order/checkout lines by product, separating base products from options
507
+ function groupOrderLines(
508
+ lines: Array<{
509
+ id: string;
510
+ quantity: number;
511
+ productName?: string;
512
+ lineMetadata?: MetadataItem[] | null;
513
+ thumbnail?: { url: string; alt: string } | null;
514
+ variant?: {
515
+ id: string;
516
+ sku?: string | null;
517
+ name: string;
518
+ metadata?: MetadataItem[] | null;
519
+ product: {
520
+ id: string;
521
+ name: string;
522
+ category?: { name: string } | null;
523
+ thumbnail?: { url: string; alt: string } | null;
524
+ };
525
+ } | null;
526
+ totalPrice: TaxedMoney;
527
+ }>
528
+ ): GroupedOrderItem[] {
529
+ // Build a map of product ID -> lines for that product
530
+ const productMap = new Map<
531
+ string,
532
+ {
533
+ baseLine: (typeof lines)[0] | null;
534
+ optionLines: Array<{
535
+ line: (typeof lines)[0];
536
+ optionMeta: OptionSetMetadata;
537
+ }>;
538
+ }
539
+ >();
540
+
541
+ for (const line of lines) {
542
+ const variant = line.variant;
543
+ if (!variant) {
544
+ // Line has no variant info - treat as standalone item
545
+ const standaloneId = `standalone-${line.id}`;
546
+ productMap.set(standaloneId, { baseLine: line, optionLines: [] });
547
+ continue;
548
+ }
549
+
550
+ const productId = variant.product.id;
551
+ const optionMeta = parseOptionSetMetadata(variant.metadata);
552
+
553
+ if (!productMap.has(productId)) {
554
+ productMap.set(productId, { baseLine: null, optionLines: [] });
555
+ }
556
+
557
+ const entry = productMap.get(productId)!;
558
+
559
+ if (optionMeta) {
560
+ // This is an option variant (SKU-based option set)
561
+ entry.optionLines.push({ line, optionMeta });
562
+ } else {
563
+ // This is a base product variant
564
+ entry.baseLine = line;
565
+ }
566
+ }
567
+
568
+ // Convert the map into grouped items
569
+ const groupedItems: GroupedOrderItem[] = [];
570
+
571
+ for (const [, entry] of productMap) {
572
+ const { baseLine, optionLines } = entry;
573
+
574
+ // Determine the display line (prefer base, fall back to first option)
575
+ const displayLine = baseLine ?? optionLines[0]?.line;
576
+ if (!displayLine) continue;
577
+
578
+ const variant = displayLine.variant;
579
+ const productName =
580
+ displayLine.productName ??
581
+ variant?.product.name ??
582
+ "Unknown Product";
583
+ const category =
584
+ variant?.product.category?.name ?? "Uncategorized";
585
+ const thumbnail =
586
+ "thumbnail" in displayLine
587
+ ? displayLine.thumbnail
588
+ : variant?.product.thumbnail;
589
+
590
+ // Calculate base price (0 if no base line or if base_product_required is false for all options)
591
+ let basePrice = 0;
592
+ if (baseLine) {
593
+ basePrice = baseLine.totalPrice.gross.amount;
594
+ }
595
+
596
+ // Build SKU-based options array
597
+ const options: OptionItem[] = optionLines.map(({ line, optionMeta }) => ({
598
+ lineId: line.id,
599
+ variantId: line.variant?.id ?? "",
600
+ variantName: line.variant?.name ?? "",
601
+ optionSetLabel: optionMeta.label || optionMeta.name,
602
+ price: line.totalPrice.gross.amount,
603
+ currency: line.totalPrice.gross.currency,
604
+ }));
605
+
606
+ // Extract non-SKU custom inputs from line metadata
607
+ const customInputs = extractCustomInputs(displayLine.lineMetadata);
608
+
609
+ // Calculate combined total price
610
+ const combinedAmount =
611
+ basePrice + options.reduce((sum, opt) => sum + opt.price, 0);
612
+ const currency =
613
+ displayLine.totalPrice.gross.currency ||
614
+ options[0]?.currency ||
615
+ "USD";
616
+
617
+ groupedItems.push({
618
+ id: displayLine.id,
619
+ name: productName,
620
+ category,
621
+ thumbnail,
622
+ quantity: displayLine.quantity,
623
+ basePrice,
624
+ totalPrice: {
625
+ gross: { amount: combinedAmount, currency },
626
+ },
627
+ currency,
628
+ options,
629
+ customInputs,
630
+ });
631
+ }
632
+
633
+ return groupedItems;
634
+ }
635
+
636
+ export default function Summary() {
637
+ const [orderData, setOrderData] = useState<OrderData | null>(null);
638
+ const [orderId, setOrderId] = useState<string | null>(null);
639
+ const [loading, setLoading] = useState(true);
640
+ const [completing, setCompleting] = useState(false);
641
+ const [errorMsg, setErrorMsg] = useState<string | null>(null);
642
+ const route = useRouter();
643
+ const [flowRan, setFlowRan] = useState(false);
644
+ const [processingStatus, setProcessingStatus] = useState<
645
+ "idle" | "running" | "success" | "fail"
646
+ >("idle");
647
+
648
+ const { finalizeCheckoutCleanup } = useGlobalStore(); // ✅ use the cleanup
649
+ const { getGoogleTagManagerConfig } = useAppConfiguration();
650
+ const gtmConfig = getGoogleTagManagerConfig();
651
+
652
+ const searchParams = useSearchParams();
653
+ const saleorTransactionId = searchParams.get("saleorTransactionId");
654
+ const urlOrderId = searchParams.get("orderId");
655
+ const orderNumber = searchParams.get("orderNumber");
656
+
657
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL;
658
+ const client = useMemo(() => {
659
+ if (!apiUrl) throw new Error("NEXT_PUBLIC_API_URL is not defined");
660
+ return new ApolloClient({
661
+ link: new HttpLink({ uri: apiUrl, fetch }),
662
+ cache: new InMemoryCache(),
663
+ });
664
+ }, [apiUrl]);
665
+
666
+ // Single orchestrated flow: Load -> (Process) -> Complete
667
+ useEffect(() => {
668
+ if (flowRan) return;
669
+ let cancelled = false;
670
+
671
+ (async () => {
672
+ try {
673
+ setErrorMsg(null);
674
+ setLoading(true);
675
+
676
+ // 1) Try to load existing order OR checkout
677
+ let fetchedOrderData: OrderData | null = null;
678
+
679
+ // Prefer explicit orderId from URL (return-from-PSP or direct link)
680
+ if (urlOrderId) {
681
+ try {
682
+ const { data } = await client.query<{ order: Order | null }>({
683
+ query: GET_ORDER_SUMMARY,
684
+ variables: { orderId: urlOrderId },
685
+ fetchPolicy: "no-cache",
686
+ });
687
+ if (data.order) {
688
+ fetchedOrderData = data.order;
689
+ setOrderId(urlOrderId);
690
+ // Clean up after successful order confirmation
691
+ await finalizeCheckoutCleanup();
692
+ }
693
+ } catch (error) {
694
+ // Don't cleanup on error - might still need checkout data
695
+ console.error("Order fetch failed:", error);
696
+ }
697
+ }
698
+
699
+ // If we still have nothing, try checkoutId (pre-completion)
700
+ if (!fetchedOrderData) {
701
+ let checkoutId = localStorage.getItem("checkoutId");
702
+ if (!checkoutId) {
703
+ const urlCheckoutId = searchParams.get("checkoutId");
704
+ if (urlCheckoutId) {
705
+ checkoutId = urlCheckoutId;
706
+ localStorage.setItem("checkoutId", urlCheckoutId);
707
+ }
708
+ }
709
+
710
+ if (checkoutId) {
711
+ try {
712
+ const { data } = await client.query<{
713
+ checkout: Checkout | null;
714
+ }>({
715
+ query: GET_CHECKOUT_SUMMARY,
716
+ variables: { checkoutId },
717
+ fetchPolicy: "no-cache",
718
+ });
719
+ if (data.checkout) {
720
+ fetchedOrderData = data.checkout;
721
+ }
722
+ } catch {
723
+ // If checkout fetch fails, we’ll error below.
724
+ }
725
+ }
726
+ }
727
+
728
+ if (!fetchedOrderData) {
729
+ setErrorMsg("No order or checkout data found.");
730
+ return;
731
+ }
732
+
733
+ if (cancelled) return;
734
+ setOrderData(fetchedOrderData);
735
+
736
+ // Track purchase completion with GTM for all successful order confirmations
737
+ if (fetchedOrderData) {
738
+ const lines = getLines(fetchedOrderData);
739
+ const pricing = getPricing(fetchedOrderData);
740
+
741
+ const products: Product[] = lines.map((line, index) => ({
742
+ item_id: line.id,
743
+ item_name: line.name,
744
+ item_category: line.category || "Products",
745
+ price: line.totalPrice.gross.amount / line.quantity,
746
+ quantity: line.quantity,
747
+ currency: line.totalPrice.gross.currency || "USD",
748
+ index: index,
749
+ }));
750
+
751
+ // Use orderNumber from URL if available, fallback to order number from data, then to orderId
752
+ let transactionId = orderNumber;
753
+ if (
754
+ !transactionId &&
755
+ isOrder(fetchedOrderData) &&
756
+ fetchedOrderData.number
757
+ ) {
758
+ transactionId = fetchedOrderData.number;
759
+ }
760
+ if (!transactionId) {
761
+ transactionId = urlOrderId || fetchedOrderData.id;
762
+ }
763
+
764
+ const purchaseData: PurchaseData = {
765
+ transaction_id: transactionId,
766
+ value: pricing.total.gross.amount,
767
+ currency: pricing.total.gross.currency || "USD",
768
+ tax: 0,
769
+ shipping: pricing.shipping.gross.amount,
770
+ items: products,
771
+ };
772
+
773
+ gtmPurchase(purchaseData, gtmConfig?.container_id);
774
+
775
+ // Enhanced conversion tracking with customer data if available
776
+ const orderData = fetchedOrderData as Order;
777
+ const checkoutData = fetchedOrderData as Checkout;
778
+ const userEmail = orderData.userEmail || checkoutData.email;
779
+ const billingAddress =
780
+ orderData.billingAddress || checkoutData.billingAddress;
781
+
782
+ if (userEmail || billingAddress) {
783
+ const enhancedData: EnhancedConversionData = {};
784
+
785
+ if (userEmail) {
786
+ enhancedData.email = userEmail;
787
+ }
788
+
789
+ if (billingAddress) {
790
+ if (billingAddress.firstName)
791
+ enhancedData.first_name = billingAddress.firstName;
792
+ if (billingAddress.lastName)
793
+ enhancedData.last_name = billingAddress.lastName;
794
+ if (billingAddress.streetAddress1)
795
+ enhancedData.street = billingAddress.streetAddress1;
796
+ if (billingAddress.city) enhancedData.city = billingAddress.city;
797
+ if (billingAddress.countryArea)
798
+ enhancedData.region = billingAddress.countryArea;
799
+ if (billingAddress.postalCode)
800
+ enhancedData.postal_code = billingAddress.postalCode;
801
+ if (billingAddress.country?.country)
802
+ enhancedData.country = billingAddress.country.country;
803
+ if (billingAddress.phone)
804
+ enhancedData.phone_number = billingAddress.phone;
805
+ }
806
+
807
+ gtmEnhancedConversion(
808
+ enhancedData,
809
+ pricing.total.gross.amount,
810
+ pricing.total.gross.currency || "USD",
811
+ gtmConfig?.container_id,
812
+ );
813
+ }
814
+ }
815
+
816
+ // 2) If returning from a PSP with a transaction, process it first
817
+ if (saleorTransactionId) {
818
+ setProcessingStatus("running");
819
+ const tryProcess = async (attempt: number): Promise<void> => {
820
+ const { data: procData } = await client.mutate<{
821
+ transactionProcess: {
822
+ transaction: { id: string } | null;
823
+ errors: {
824
+ field?: string | null;
825
+ message: string;
826
+ code: string;
827
+ }[];
828
+ } | null;
829
+ }>({
830
+ mutation: TRANSACTION_PROCESS,
831
+ variables: { transactionId: saleorTransactionId },
832
+ fetchPolicy: "no-cache",
833
+ });
834
+ const resp = procData?.transactionProcess;
835
+ const errs = resp?.errors ?? [];
836
+ if (errs.length) {
837
+ if (attempt < 3) {
838
+ await new Promise((r) => setTimeout(r, 800));
839
+ return tryProcess(attempt + 1);
840
+ }
841
+ throw new Error(
842
+ "Payment processing failed: " +
843
+ errs.map((e) => e.message || e.code).join(", "),
844
+ );
845
+ }
846
+ };
847
+ await tryProcess(1);
848
+ if (cancelled) return;
849
+ setProcessingStatus("success");
850
+
851
+ // Track purchase completion with GTM
852
+ if (fetchedOrderData) {
853
+ const lines = getLines(fetchedOrderData);
854
+ const pricing = getPricing(fetchedOrderData);
855
+
856
+ const products: Product[] = lines.map((line, index) => ({
857
+ item_id: line.id,
858
+ item_name: line.name,
859
+ item_category: line.category || "Products",
860
+ price: line.totalPrice.gross.amount / line.quantity, // Calculate unit price
861
+ quantity: line.quantity,
862
+ currency: line.totalPrice.gross.currency || "USD",
863
+ index: index,
864
+ }));
865
+
866
+ // Use orderNumber from URL if available, fallback to order number from data, then to orderId
867
+ let transactionId = orderNumber;
868
+ if (
869
+ !transactionId &&
870
+ isOrder(fetchedOrderData) &&
871
+ fetchedOrderData.number
872
+ ) {
873
+ transactionId = fetchedOrderData.number;
874
+ }
875
+ if (!transactionId) {
876
+ transactionId =
877
+ urlOrderId || saleorTransactionId || fetchedOrderData.id;
878
+ }
879
+
880
+ const purchaseData: PurchaseData = {
881
+ transaction_id: transactionId,
882
+ value: pricing.total.gross.amount,
883
+ currency: pricing.total.gross.currency || "USD",
884
+ tax: 0, // You may want to extract tax if available
885
+ shipping: pricing.shipping.gross.amount,
886
+ items: products,
887
+ };
888
+
889
+ gtmPurchase(purchaseData, gtmConfig?.container_id);
890
+
891
+ // Enhanced conversion tracking with customer data if available
892
+ const checkoutData = fetchedOrderData as Checkout;
893
+ if (checkoutData.email || checkoutData.billingAddress) {
894
+ const enhancedData: EnhancedConversionData = {};
895
+
896
+ if (checkoutData.email) {
897
+ enhancedData.email = checkoutData.email;
898
+ }
899
+
900
+ if (checkoutData.billingAddress) {
901
+ const billing = checkoutData.billingAddress;
902
+ if (billing.firstName)
903
+ enhancedData.first_name = billing.firstName;
904
+ if (billing.lastName) enhancedData.last_name = billing.lastName;
905
+ if (billing.streetAddress1)
906
+ enhancedData.street = billing.streetAddress1;
907
+ if (billing.city) enhancedData.city = billing.city;
908
+ if (billing.countryArea)
909
+ enhancedData.region = billing.countryArea;
910
+ if (billing.postalCode)
911
+ enhancedData.postal_code = billing.postalCode;
912
+ if (billing.country?.country)
913
+ enhancedData.country = billing.country.country;
914
+ if (billing.phone) enhancedData.phone_number = billing.phone;
915
+ }
916
+
917
+ gtmEnhancedConversion(
918
+ enhancedData,
919
+ pricing.total.gross.amount,
920
+ pricing.total.gross.currency || "USD",
921
+ gtmConfig?.container_id,
922
+ );
923
+ }
924
+ }
925
+ }
926
+
927
+ // 3) If we’re still on a checkout (not an order yet), complete it
928
+ const isCheckoutData =
929
+ !("number" in fetchedOrderData) &&
930
+ Array.isArray((fetchedOrderData as Checkout).lines) &&
931
+ (fetchedOrderData as Checkout).lines.length > 0;
932
+
933
+ if (isCheckoutData) {
934
+ const checkoutData = fetchedOrderData as Checkout;
935
+
936
+ setCompleting(true);
937
+ const completeRes = await client.mutate<{
938
+ checkoutComplete: {
939
+ order: { id: string } | null;
940
+ errors: { message: string; code: string }[];
941
+ } | null;
942
+ }>({
943
+ mutation: COMPLETE_CHECKOUT,
944
+ variables: { checkoutId: checkoutData.id },
945
+ fetchPolicy: "no-cache",
946
+ });
947
+
948
+ if (cancelled) return;
949
+
950
+ const errs = completeRes.data?.checkoutComplete?.errors ?? [];
951
+ if (errs.length) {
952
+ // Special handling for shipping method not set error
953
+ const shippingError = errs.find(
954
+ (e) => e.code === "SHIPPING_METHOD_NOT_SET",
955
+ );
956
+ if (shippingError) {
957
+ console.error(
958
+ "Shipping method not set during checkout completion. This commonly happens with PayPal redirects.",
959
+ );
960
+ console.error("Checkout ID:", checkoutData.id, "Errors:", errs);
961
+ // For PayPal redirects or guest users, try auto-recovery - complete checkout without setting shipping method first
962
+ if (!saleorTransactionId) {
963
+ // Not a PayPal redirect; attempting auto-recovery for guest checkout.
964
+ // Don't immediately throw error for guest users, try auto-recovery first
965
+ }
966
+
967
+ // Try to complete checkout directly since user already selected shipping method during checkout
968
+ const flowType = saleorTransactionId
969
+ ? "PayPal redirect"
970
+ : "guest checkout";
971
+ try {
972
+ const directComplete = await client.mutate({
973
+ mutation: gql(`
974
+ mutation DirectCompleteCheckout($checkoutId: ID!) {
975
+ checkoutComplete(id: $checkoutId) {
976
+ order {
977
+ id
978
+ number
979
+ created
980
+ status
981
+ total { gross { amount currency } }
982
+ deliveryMethod {
983
+ ... on ShippingMethod {
984
+ id
985
+ name
986
+ price { amount currency }
987
+ }
988
+ }
989
+ }
990
+ errors { field message code }
991
+ }
992
+ }
993
+ `),
994
+ variables: { checkoutId: checkoutData.id },
995
+ });
996
+
997
+ const directErrors =
998
+ directComplete.data?.checkoutComplete?.errors || [];
999
+ if (
1000
+ directErrors.length === 0 &&
1001
+ directComplete.data?.checkoutComplete?.order
1002
+ ) {
1003
+ // Direct checkout completion successful.
1004
+ const newOrderId =
1005
+ directComplete.data.checkoutComplete.order.id;
1006
+ setOrderId(newOrderId);
1007
+ await finalizeCheckoutCleanup();
1008
+ // Completed successfully without shipping method auto-fix.
1009
+ return; // Success! Skip the auto-fix entirely
1010
+ } else {
1011
+ // Falling back to shipping method auto-fix.
1012
+ }
1013
+ } catch (directError) {
1014
+ // Falling back to shipping method auto-fix.
1015
+ }
1016
+
1017
+ // Try to auto-fix delivery method for PayPal redirects and guest checkouts
1018
+ console.warn(`Attempting auto-recovery for ${flowType}...`);
1019
+ try {
1020
+ const shippingMethodsQuery = `
1021
+ query GetShippingMethods($checkoutId: ID!) {
1022
+ checkout(id: $checkoutId) {
1023
+ id
1024
+ deliveryMethod {
1025
+ ... on ShippingMethod {
1026
+ id
1027
+ name
1028
+ }
1029
+ }
1030
+ availableShippingMethods {
1031
+ id
1032
+ name
1033
+ }
1034
+ shippingAddress {
1035
+ country {
1036
+ code
1037
+ }
1038
+ postalCode
1039
+ city
1040
+ }
1041
+ }
1042
+ }
1043
+ `;
1044
+
1045
+ const { data: methodsData } = await client.query({
1046
+ query: gql(shippingMethodsQuery),
1047
+ variables: { checkoutId: checkoutData.id },
1048
+ fetchPolicy: "no-cache",
1049
+ });
1050
+
1051
+ const checkout = methodsData?.checkout;
1052
+ const availableMethods =
1053
+ checkout?.availableShippingMethods || [];
1054
+
1055
+ // Avoid logging checkout state details in templates/production.
1056
+
1057
+ // If no methods available, don't show UI error - just fail gracefully
1058
+ if (availableMethods.length === 0) {
1059
+ console.error(
1060
+ "No shipping methods available during PayPal auto-recovery",
1061
+ );
1062
+ throw new Error(
1063
+ "No shipping methods are available for this order. " +
1064
+ "This may be due to address or product restrictions. " +
1065
+ "Please contact support to complete your order.",
1066
+ );
1067
+ }
1068
+
1069
+ if (availableMethods.length > 0) {
1070
+ // Try to use the stored shipping method first, then fall back to first available
1071
+ let methodToUse = availableMethods[0];
1072
+ let methodSource = "first available";
1073
+
1074
+ // Check if we have a stored shipping method from before PayPal redirect or guest checkout
1075
+ try {
1076
+ const pendingPaymentData =
1077
+ localStorage.getItem("pendingPaymentData");
1078
+ // Avoid logging raw stored data.
1079
+ if (pendingPaymentData) {
1080
+ const paymentData = JSON.parse(pendingPaymentData);
1081
+ if (paymentData.selectedShippingId) {
1082
+ // Try to find the previously selected shipping method
1083
+ const storedMethod = availableMethods.find(
1084
+ (m: { id: string }) =>
1085
+ m.id === paymentData.selectedShippingId,
1086
+ );
1087
+ if (storedMethod) {
1088
+ methodToUse = storedMethod;
1089
+ methodSource = saleorTransactionId
1090
+ ? "previously selected before PayPal redirect"
1091
+ : "previously selected during checkout";
1092
+ } else {
1093
+ console.warn(
1094
+ "Previously selected shipping method not available:",
1095
+ paymentData.selectedShippingId,
1096
+ );
1097
+ console.warn(
1098
+ "Available methods:",
1099
+ availableMethods.map(
1100
+ (m: { id: string; name: string }) => ({
1101
+ id: m.id,
1102
+ name: m.name,
1103
+ }),
1104
+ ),
1105
+ );
1106
+ }
1107
+ }
1108
+ } else {
1109
+ // For guest users without stored payment data, check if there are any other storage mechanisms
1110
+ // No stored payment data found.
1111
+
1112
+ // Check for any other stored checkout data
1113
+ const allStorageKeys = Object.keys(localStorage);
1114
+ const checkoutKeys = allStorageKeys.filter(
1115
+ (key) =>
1116
+ key.toLowerCase().includes("checkout") ||
1117
+ key.toLowerCase().includes("shipping"),
1118
+ );
1119
+
1120
+ checkoutKeys.forEach((key) => {
1121
+ // Avoid logging localStorage keys/values.
1122
+ void key;
1123
+ });
1124
+ }
1125
+ } catch (e) {
1126
+ console.warn("Failed to parse stored payment data:", e);
1127
+ }
1128
+
1129
+ // Validate that the delivery method ID is properly formatted
1130
+ if (!methodToUse.id || typeof methodToUse.id !== "string") {
1131
+ console.error("Invalid delivery method ID:", methodToUse);
1132
+ throw new Error(
1133
+ "Invalid delivery method data received from server",
1134
+ );
1135
+ }
1136
+
1137
+ console.warn(
1138
+ `Auto-selecting ${methodSource} shipping method for ${flowType} completion:`,
1139
+ {
1140
+ id: methodToUse.id,
1141
+ name: methodToUse.name,
1142
+ idLength: methodToUse.id.length,
1143
+ idIsBase64Like: /^[A-Za-z0-9+/]+=*$/.test(methodToUse.id),
1144
+ },
1145
+ );
1146
+
1147
+ // Use the same REST-style approach as the main checkout page to avoid GraphQL variable encoding issues
1148
+ const setShippingMethodMutation = `
1149
+ mutation CheckoutDeliveryMethodUpdate($id: ID!, $deliveryMethodId: ID!) {
1150
+ checkoutDeliveryMethodUpdate(id: $id, deliveryMethodId: $deliveryMethodId) {
1151
+ checkout {
1152
+ id
1153
+ deliveryMethod {
1154
+ ... on ShippingMethod {
1155
+ id
1156
+ name
1157
+ }
1158
+ }
1159
+ }
1160
+ errors { field message code }
1161
+ }
1162
+ }
1163
+ `;
1164
+
1165
+ try {
1166
+ // Setting delivery method for recovery.
1167
+ const endpoint = getSaleorApiUrl();
1168
+ const res = await fetch(endpoint, {
1169
+ method: "POST",
1170
+ headers: { "Content-Type": "application/json" },
1171
+ body: JSON.stringify({
1172
+ query: setShippingMethodMutation,
1173
+ variables: {
1174
+ id: checkoutData.id,
1175
+ deliveryMethodId: methodToUse.id,
1176
+ },
1177
+ }),
1178
+ });
1179
+
1180
+ if (!res.ok) {
1181
+ const errorText = await res.text();
1182
+ throw new Error(
1183
+ `Failed to set delivery method: ${res.status} ${res.statusText} ${errorText}`,
1184
+ );
1185
+ }
1186
+
1187
+ const setMethodResult = await res.json();
1188
+
1189
+ const setMethodErrors =
1190
+ setMethodResult.data?.checkoutDeliveryMethodUpdate
1191
+ ?.errors || [];
1192
+ if (setMethodErrors.length > 0) {
1193
+ console.error("Failed to set shipping method:", {
1194
+ checkoutId: checkoutData.id,
1195
+ deliveryMethodId: methodToUse.id,
1196
+ errors: setMethodErrors,
1197
+ fullResult: setMethodResult.data,
1198
+ availableMethodsCount: availableMethods.length,
1199
+ methodData: methodToUse,
1200
+ methodSource,
1201
+ requestBody: JSON.stringify({
1202
+ query: setShippingMethodMutation,
1203
+ variables: {
1204
+ id: checkoutData.id,
1205
+ deliveryMethodId: methodToUse.id,
1206
+ },
1207
+ }),
1208
+ });
1209
+
1210
+ // Check for specific error types
1211
+ const nodeError = setMethodErrors.find(
1212
+ (e: GraphQLError) =>
1213
+ e.message?.includes("Couldn't resolve to a node") ||
1214
+ e.code === "NOT_FOUND",
1215
+ );
1216
+
1217
+ const notApplicableError = setMethodErrors.find(
1218
+ (e: GraphQLError) =>
1219
+ e.message?.includes("not applicable") ||
1220
+ e.message?.includes(
1221
+ "shipping method is not applicable",
1222
+ ) ||
1223
+ e.code === "SHIPPING_METHOD_NOT_APPLICABLE",
1224
+ );
1225
+
1226
+ if (nodeError) {
1227
+ console.warn(
1228
+ "Delivery method ID is stale, will proceed without setting method",
1229
+ );
1230
+ return;
1231
+ }
1232
+
1233
+ if (notApplicableError) {
1234
+ console.warn(
1235
+ "First delivery method is not applicable, trying others...",
1236
+ );
1237
+
1238
+ // Try other available methods
1239
+ let methodSet = false;
1240
+ for (let i = 1; i < availableMethods.length; i++) {
1241
+ const alternateMethod = availableMethods[i];
1242
+ // Trying alternate method.
1243
+
1244
+ try {
1245
+ const altResult = await client.mutate({
1246
+ mutation: gql(setShippingMethodMutation),
1247
+ variables: {
1248
+ checkoutId: checkoutData.id,
1249
+ deliveryMethodId: alternateMethod.id,
1250
+ },
1251
+ });
1252
+
1253
+ const altErrors =
1254
+ altResult.data?.checkoutDeliveryMethodUpdate
1255
+ ?.errors || [];
1256
+ if (altErrors.length === 0) {
1257
+ // Successfully set alternate shipping method.
1258
+ methodSet = true;
1259
+ break;
1260
+ } else {
1261
+ console.warn(
1262
+ `Alternate method ${alternateMethod.name} also failed:`,
1263
+ altErrors,
1264
+ );
1265
+ }
1266
+ } catch (altError) {
1267
+ console.warn(
1268
+ `Failed to try alternate method ${alternateMethod.name}:`,
1269
+ altError,
1270
+ );
1271
+ }
1272
+ }
1273
+
1274
+ if (!methodSet) {
1275
+ console.error(
1276
+ "No applicable shipping methods found for this checkout",
1277
+ );
1278
+ return; // Let the main error flow handle this
1279
+ }
1280
+
1281
+ // If we successfully set an alternate method, continue
1282
+ return;
1283
+ }
1284
+
1285
+ throw new Error(
1286
+ setMethodErrors
1287
+ .map((e: GraphQLError) => e.message || e.code)
1288
+ .join(", "),
1289
+ );
1290
+ }
1291
+ } catch (mutationError) {
1292
+ console.error(
1293
+ "Mutation failed when setting delivery method:",
1294
+ mutationError,
1295
+ );
1296
+ // Don't throw here - let the main flow handle the error
1297
+ return;
1298
+ }
1299
+
1300
+ // Successfully set shipping method; retrying checkout completion.
1301
+
1302
+ // Retry checkout completion
1303
+ const retryRes = await client.mutate({
1304
+ mutation: COMPLETE_CHECKOUT,
1305
+ variables: { checkoutId: checkoutData.id },
1306
+ fetchPolicy: "no-cache",
1307
+ });
1308
+
1309
+ const retryErrs =
1310
+ retryRes.data?.checkoutComplete?.errors ?? [];
1311
+ if (retryErrs.length) {
1312
+ console.error("Retry completion failed:", retryErrs);
1313
+ throw new Error(
1314
+ "Checkout completion failed after setting shipping method: " +
1315
+ retryErrs
1316
+ .map((e: GraphQLError) => e.message)
1317
+ .join(", "),
1318
+ );
1319
+ }
1320
+
1321
+ const newOrderId =
1322
+ retryRes.data?.checkoutComplete?.order?.id ?? null;
1323
+ if (newOrderId) {
1324
+ setOrderId(newOrderId);
1325
+ await finalizeCheckoutCleanup();
1326
+ // Order completed successfully after auto-fixing shipping method.
1327
+ return; // Success, continue with normal flow
1328
+ } else {
1329
+ throw new Error(
1330
+ "No order ID returned after successful completion",
1331
+ );
1332
+ }
1333
+ } else {
1334
+ console.error(
1335
+ "No shipping methods available for this checkout",
1336
+ );
1337
+ // Don't throw here - let the natural error flow handle it
1338
+ }
1339
+ } catch (retryError) {
1340
+ console.error(
1341
+ "Failed to auto-fix shipping method:",
1342
+ retryError,
1343
+ );
1344
+ // Don't re-throw here, let the original error be thrown below
1345
+ }
1346
+
1347
+ // If we reach here, auto-fix failed, provide helpful error message
1348
+ const enhancedError = new Error(
1349
+ `Order completion failed: ${errs
1350
+ .map((e) => e.message)
1351
+ .join(", ")}. ` +
1352
+ `This commonly happens when delivery methods become unavailable after payment. ` +
1353
+ `Please contact support with your payment confirmation to complete your order.`,
1354
+ );
1355
+
1356
+ throw enhancedError;
1357
+ }
1358
+ throw new Error(errs.map((e) => e.message).join(", "));
1359
+ }
1360
+
1361
+ const newOrderId =
1362
+ completeRes.data?.checkoutComplete?.order?.id ?? null;
1363
+ if (newOrderId) {
1364
+ setOrderId(newOrderId);
1365
+
1366
+ // Clean up cart and checkout data after order completion
1367
+ await finalizeCheckoutCleanup();
1368
+ } else {
1369
+ setErrorMsg("Checkout completion returned no order id.");
1370
+ }
1371
+ }
1372
+ } catch (e) {
1373
+ if (!cancelled) {
1374
+ setErrorMsg(errMsg(e));
1375
+ setProcessingStatus((s) => (s === "running" ? "fail" : s));
1376
+ }
1377
+ } finally {
1378
+ if (!cancelled) {
1379
+ setLoading(false);
1380
+ setCompleting(false);
1381
+ setFlowRan(true);
1382
+ }
1383
+ }
1384
+ })();
1385
+
1386
+ return () => {
1387
+ cancelled = true;
1388
+ };
1389
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1390
+ }, [client, saleorTransactionId]);
1391
+
1392
+ const isOrder = (data: OrderData): data is Order =>
1393
+ "number" in data || "userEmail" in data;
1394
+
1395
+ const getBillingFirstName = (data: OrderData) =>
1396
+ data.billingAddress?.firstName;
1397
+
1398
+ const getLines = (data: OrderData): GroupedOrderItem[] => {
1399
+ if (isOrder(data)) {
1400
+ // Normalize order lines to common format for grouping
1401
+ const normalizedLines = data.lines.map((line) => ({
1402
+ id: line.id,
1403
+ quantity: line.quantity,
1404
+ productName: line.productName,
1405
+ lineMetadata: line.metadata,
1406
+ thumbnail: line.thumbnail,
1407
+ variant: line.variant
1408
+ ? {
1409
+ id: line.variant.id,
1410
+ sku: line.variant.sku,
1411
+ name: line.variant.name,
1412
+ metadata: line.variant.metadata,
1413
+ product: {
1414
+ id: line.variant.product.id,
1415
+ name: line.variant.product.name,
1416
+ category: line.variant.product.category,
1417
+ },
1418
+ }
1419
+ : null,
1420
+ totalPrice: line.totalPrice,
1421
+ }));
1422
+ return groupOrderLines(normalizedLines);
1423
+ } else {
1424
+ // Normalize checkout lines to common format for grouping
1425
+ const normalizedLines = data.lines.map((line) => ({
1426
+ id: line.id,
1427
+ quantity: line.quantity,
1428
+ productName: line.variant.product.name,
1429
+ lineMetadata: line.metadata,
1430
+ thumbnail: line.variant.product.thumbnail,
1431
+ variant: {
1432
+ id: line.variant.id,
1433
+ sku: line.variant.sku,
1434
+ name: line.variant.name,
1435
+ metadata: line.variant.metadata,
1436
+ product: {
1437
+ id: line.variant.product.id,
1438
+ name: line.variant.product.name,
1439
+ category: line.variant.product.category,
1440
+ },
1441
+ },
1442
+ totalPrice: line.totalPrice,
1443
+ }));
1444
+ return groupOrderLines(normalizedLines);
1445
+ }
1446
+ };
1447
+
1448
+ const getPricing = (data: OrderData) => {
1449
+ if (isOrder(data)) {
1450
+ return {
1451
+ subtotal: data.subtotal,
1452
+ shipping: data.shippingPrice,
1453
+ total: data.total,
1454
+ };
1455
+ } else {
1456
+ return {
1457
+ subtotal: data.subtotalPrice,
1458
+ shipping: data.shippingPrice,
1459
+ total: data.totalPrice,
1460
+ };
1461
+ }
1462
+ };
1463
+
1464
+ if (loading || completing) {
1465
+ return <LoadingUI className="h-[80vh]" />;
1466
+ }
1467
+
1468
+ if (!orderData && !loading) {
1469
+ return (
1470
+ <EmptyState
1471
+ text="No order found."
1472
+ className="h-[80vh]"
1473
+ buttonLabel="GO TO HOME"
1474
+ buttonVariant="secondary"
1475
+ onClick={() => route.push("/")}
1476
+ />
1477
+ );
1478
+ }
1479
+
1480
+ if (errorMsg && !loading && !completing) {
1481
+ return (
1482
+ <EmptyState
1483
+ text={errorMsg}
1484
+ className="h-[80vh]"
1485
+ buttonLabel="GO TO HOME"
1486
+ buttonVariant="secondary"
1487
+ onClick={() => route.push("/")}
1488
+ />
1489
+ );
1490
+ }
1491
+
1492
+ return (
1493
+ <div className="container mx-auto px-4 py-10 lg:py-24 grid grid-cols-1 lg:grid-cols-3 gap-14">
1494
+ <div className="lg:col-span-2 lg:border-r lg:border-[var(--color-secondary-200)] lg:pr-14">
1495
+ <div className="flex flex-col md:flex-row md:items-center w-full justify-between pb-8 gap-6">
1496
+ <div className="flex flex-col md:flex-row md:items-center gap-2">
1497
+ <span className="[&>svg]:size-10">{SuccessTickIcon}</span>
1498
+ <div className="space-y-1">
1499
+ <p className="uppercase font-medium text-xl font-secondary text-[var(--color-secondary-800)]">
1500
+ THANK YOU,{" "}
1501
+ {orderData ? getBillingFirstName(orderData) : "Customer"}!
1502
+ </p>
1503
+ <p className="font-normal text-sm font-secondary text-[var(--color-secondary-600)]">
1504
+ YOUR ORDER HAS BEEN CONFIRMED.
1505
+ </p>
1506
+ </div>
1507
+ </div>
1508
+ <div className="flex items-center gap-1 cursor-pointer">
1509
+ <CommonButton
1510
+ onClick={() => route.push("/")}
1511
+ className="p-0"
1512
+ content="CONTINUE SHOPPING"
1513
+ variant="tertiary"
1514
+ />
1515
+ <span className="size-5 text-[var(--color-primary-600)]">
1516
+ {ArrowIcon}
1517
+ </span>
1518
+ </div>
1519
+ </div>
1520
+
1521
+ <div className="p-4 lg:p-10 border border-[var(--color-secondary-200)]">
1522
+ {orderData && (
1523
+ <>
1524
+ <div className="grid gap-3">
1525
+ <div className="space-y-5">
1526
+ <p className="text-xl font-semibold leading-7 tracking-[-0.05px] text-[var(--color-secondary-800)]">
1527
+ ORDER DETAILS
1528
+ </p>
1529
+ <div className="flex flex-col items-start gap-3 uppercase text-[var(--color-secondary-600)] font-normal text-sm font-secondary">
1530
+ <div className="flex items-center gap-1">
1531
+ <p className=" text-sm font-normal leading-5 tracking-[-0.035px] text-[var(--color-secondary-600)]">
1532
+ Order Number
1533
+ </p>
1534
+ <p className="text-[var(--color-secondary-800)] text-sm font-semibold leading-5 tracking-[-0.035px]">
1535
+ {isOrder(orderData) && orderData.number
1536
+ ? orderData.number
1537
+ : orderData.id}
1538
+ </p>
1539
+ </div>
1540
+ <div className="flex items-center gap-1">
1541
+ <p className=" text-sm font-normal leading-5 tracking-[-0.035px] text-[var(--color-secondary-600)]">
1542
+ Placed on
1543
+ </p>
1544
+ <p className="text-[var(--color-secondary-800)] text-sm font-semibold leading-5 tracking-[-0.035px]">
1545
+ {formatDateTime(orderData.created)}
1546
+ </p>
1547
+ </div>
1548
+ </div>
1549
+ </div>
1550
+
1551
+ <hr className="border border-[var(--color-secondary-200)]" />
1552
+
1553
+ <div className="flex flex-col gap-4">
1554
+ {getLines(orderData).map((item) => (
1555
+ <div
1556
+ className="flex flex-col md:flex-row lg:items-start justify-between gap-5 pb-4 border-b border-[var(--color-secondary-100)] last:border-b-0 last:pb-0"
1557
+ key={item.id}
1558
+ >
1559
+ <div className="flex gap-4">
1560
+ <div className="relative w-20 h-20 flex-shrink-0 rounded-md overflow-hidden">
1561
+ <Image
1562
+ src={
1563
+ item?.thumbnail?.url || "/no-image-avail-large.png"
1564
+ }
1565
+ alt={item?.thumbnail?.alt || "Product Image"}
1566
+ className="object-contain w-full h-full"
1567
+ width={100}
1568
+ height={100}
1569
+ />
1570
+ </div>
1571
+ <div className="flex flex-col gap-1">
1572
+ <span className="text-sm font-normal leading-5 tracking-[-0.035px] text-[var(--color-secondary-600)]">
1573
+ {item.category}
1574
+ </span>
1575
+ <span className="font-medium text-lg md:text-xl leading-7 tracking[-0.05px] text-[var(--color-secondary-800)]">
1576
+ {item.name}
1577
+ </span>
1578
+ {/* Display selected SKU-based options */}
1579
+ {item.options.length > 0 && (
1580
+ <div className="mt-1 space-y-1">
1581
+ {item.options.map((option) => (
1582
+ <div
1583
+ key={option.lineId}
1584
+ className="text-sm text-[var(--color-secondary-600)] flex items-center gap-2"
1585
+ >
1586
+ <span className="text-[var(--color-primary-600)]">+</span>
1587
+ <span className="font-medium">{option.optionSetLabel}:</span>
1588
+ <span>{option.variantName}</span>
1589
+ {option.price > 0 && (
1590
+ <span className="text-[var(--color-primary-600)]">
1591
+ (+{new Intl.NumberFormat(undefined, {
1592
+ style: "currency",
1593
+ currency: option.currency,
1594
+ }).format(option.price)})
1595
+ </span>
1596
+ )}
1597
+ </div>
1598
+ ))}
1599
+ </div>
1600
+ )}
1601
+ {/* Display non-SKU custom inputs */}
1602
+ {item.customInputs.length > 0 && (
1603
+ <div className="mt-1 space-y-1">
1604
+ {item.customInputs.map((input, idx) => (
1605
+ <div
1606
+ key={`${input.key}-${idx}`}
1607
+ className="text-sm text-[var(--color-secondary-600)] flex items-center gap-2"
1608
+ >
1609
+ <span className="font-medium">{input.key}:</span>
1610
+ <span>{input.value}</span>
1611
+ </div>
1612
+ ))}
1613
+ </div>
1614
+ )}
1615
+ <span className="mt-2">
1616
+ <span
1617
+ style={{ color: "var(--color-secondary-600)" }}
1618
+ className="text-sm font-normal leading-5 tracking-[-0.035px]"
1619
+ >
1620
+ QTY
1621
+ </span>{" "}
1622
+ <span className="text-sm font-semibold leading-5 tracking-[-0.035px] uppercase text-[var(--color-secondary-800)]">
1623
+ {item.quantity}
1624
+ </span>
1625
+ </span>
1626
+ </div>
1627
+ </div>
1628
+ <span className="text-xl font-semibold leading-7 tracking-[-0.05px] text-[var(--color-secondary-800)] whitespace-nowrap">
1629
+ {new Intl.NumberFormat(undefined, {
1630
+ style: "currency",
1631
+ currency: item.totalPrice.gross.currency,
1632
+ }).format(item.totalPrice.gross.amount)}
1633
+ </span>
1634
+ </div>
1635
+ ))}
1636
+ </div>
1637
+ </div>
1638
+
1639
+ <hr className="my-4 border border-[var(--color-secondary-200)]" />
1640
+
1641
+ <div>
1642
+ <h2 className="text-lg lg:text-xl not-italic font-semibold leading-7 tracking-[-0.05px] text-[var(--color-secondary-800)] uppercase mb-4">
1643
+ Shipping Address
1644
+ </h2>
1645
+ <p className="flex flex-col md:flex-row md:items-center gap-2 text-base md:text-lg lg:text-xl not-italic font-medium leading-7 tracking-[-0.05px] font-secondary text-[var(--color-secondary-800)]">
1646
+ <span>{orderData.shippingAddress?.streetAddress1}</span>
1647
+ <span>
1648
+ {orderData.shippingAddress?.city},{" "}
1649
+ {orderData.shippingAddress?.countryArea}{" "}
1650
+ {orderData.shippingAddress?.postalCode}
1651
+ </span>
1652
+ <span>{orderData.shippingAddress?.country.country}</span>
1653
+ </p>
1654
+ <p className="text-lg not-italic font-normal leading-7 tracking-[-0.045px] text-[var(--color-secondary-500)] mt-1">
1655
+ {orderData.shippingAddress?.phone}
1656
+ </p>
1657
+ </div>
1658
+
1659
+ <hr className="my-4 border border-[var(--color-secondary-200)]" />
1660
+ <div>
1661
+ <h2 className="text-lg lg:text-xl font-secondary text-[var(--color-secondary-800)] uppercase font-semibold mb-4">
1662
+ Billing Address
1663
+ </h2>
1664
+ <p className="flex flex-col md:flex-row md:items-center gap-2 text-medium text-base md:text-lg lg:text-xl font-secondary text-[var(--color-secondary-800)]">
1665
+ <span>{orderData.billingAddress?.streetAddress1}</span>
1666
+ <span>
1667
+ {orderData.billingAddress?.city},{" "}
1668
+ {orderData.billingAddress?.countryArea}{" "}
1669
+ {orderData.billingAddress?.postalCode}
1670
+ </span>
1671
+ <span>{orderData.billingAddress?.country.country}</span>
1672
+ </p>
1673
+ <p className="text-medium text-lg font-secondary text-[var(--color-secondary-500)] mt-1">
1674
+ {orderData.billingAddress?.phone}
1675
+ </p>
1676
+ </div>
1677
+ {orderData?.shippingMethod?.name && (
1678
+ <>
1679
+ <hr className="my-4 border border-[var(--color-secondary-200)]" />
1680
+ <div>
1681
+ <h2 className="text-lg lg:text-xl font-secondary text-[var(--color-secondary-800)] uppercase font-semibold mb-4">
1682
+ Delivery Method
1683
+ </h2>
1684
+ <p className="text-base md:text-lg not-italic font-normal leading-7 tracking-[-0.045px] uppercase mt-1 text-[var(--color-secondary-500)]">
1685
+ {orderData?.shippingMethod?.name}
1686
+ </p>
1687
+ </div>
1688
+ </>
1689
+ )}
1690
+ </>
1691
+ )}
1692
+ </div>
1693
+ </div>
1694
+
1695
+ <div className="lg:col-span-1 flex flex-col">
1696
+ <h2 className="font-medium font-secondary text-base text-[var(--color-secondary-800)] text-start pb-4 uppercase">
1697
+ Summary
1698
+ </h2>
1699
+
1700
+ <div className="w-full text-normal text-[var(--color-secondary-600)] text-base">
1701
+ {orderData &&
1702
+ (() => {
1703
+ const pricing = getPricing(orderData);
1704
+ return (
1705
+ <>
1706
+ <div className="flex justify-between mb-2">
1707
+ <span>Sub-Total</span>
1708
+ <span className="font-medium">
1709
+ {pricing.subtotal.net?.amount ||
1710
+ pricing.subtotal.gross.amount}{" "}
1711
+ {pricing.subtotal.currency}
1712
+ </span>
1713
+ </div>
1714
+ <div className="flex justify-between mb-2">
1715
+ <span>Subtotal Tax</span>
1716
+ <span className="font-medium">
1717
+ {(() => {
1718
+ const subtotalTax =
1719
+ pricing.subtotal.tax?.amount ||
1720
+ pricing.subtotal.gross.amount -
1721
+ (pricing.subtotal.net?.amount ||
1722
+ pricing.subtotal.gross.amount);
1723
+
1724
+ if (subtotalTax > 0) {
1725
+ return new Intl.NumberFormat(undefined, {
1726
+ style: "currency",
1727
+ currency: pricing.total.gross.currency,
1728
+ }).format(subtotalTax);
1729
+ }
1730
+ return "N/A";
1731
+ })()}
1732
+ </span>
1733
+ </div>
1734
+ <div className="flex justify-between mb-2">
1735
+ <span>Shipping Tax</span>
1736
+ <span className="font-medium">
1737
+ {(() => {
1738
+ const shippingTax =
1739
+ pricing.shipping.tax?.amount ||
1740
+ pricing.shipping.gross.amount -
1741
+ (pricing.shipping.net?.amount ||
1742
+ pricing.shipping.gross.amount);
1743
+
1744
+ if (shippingTax > 0) {
1745
+ return new Intl.NumberFormat(undefined, {
1746
+ style: "currency",
1747
+ currency: pricing.total.gross.currency,
1748
+ }).format(shippingTax);
1749
+ }
1750
+ return "N/A";
1751
+ })()}
1752
+ </span>
1753
+ </div>
1754
+ <div className="flex justify-between mb-2">
1755
+ <span>Shipping Cost</span>
1756
+ <span className="font-medium">
1757
+ {pricing.shipping.net?.amount ||
1758
+ pricing.shipping.gross.amount}{" "}
1759
+ {pricing.shipping.currency}
1760
+ </span>
1761
+ </div>
1762
+ <div className="border-t border-gray-200 pt-4 flex justify-between text-xl text-[var(--color-secondary-600)] font-medium ">
1763
+ <span>TOTAL</span>
1764
+ <span className="font-semibold text-[var(--color-secondary-800)]">
1765
+ {pricing.total.gross.amount} {pricing.total.currency}
1766
+ </span>
1767
+ </div>
1768
+ </>
1769
+ );
1770
+ })()}
1771
+ </div>
1772
+ </div>
1773
+ </div>
1774
+ );
1775
+ }