@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,1083 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { PaymentProcessingState } from "@/graphql/types/checkout";
6
+ import LoadingUI from "../reuseableUI/loadingUI";
7
+
8
+ // PayPal SDK Types
9
+ interface ApplePayConfig {
10
+ countryCode: string;
11
+ currencyCode: string;
12
+ merchantCapabilities: string[];
13
+ supportedNetworks: string[];
14
+ }
15
+
16
+ interface GooglePayConfigResponse {
17
+ allowedPaymentMethods: AllowedPaymentMethod[];
18
+ merchantInfo: {
19
+ merchantId?: string;
20
+ merchantName?: string;
21
+ };
22
+ isEligible: boolean;
23
+ }
24
+
25
+ interface PayPalButtonsConfig {
26
+ createOrder: () => Promise<string>;
27
+ onApprove: (data: { orderID: string }) => Promise<void>;
28
+ onError?: (err: Error) => void;
29
+ onCancel?: () => void;
30
+ style?: {
31
+ layout?: "vertical" | "horizontal";
32
+ color?: "gold" | "blue" | "silver" | "white" | "black";
33
+ shape?: "rect" | "pill";
34
+ label?: "paypal" | "checkout" | "buynow" | "pay";
35
+ height?: number;
36
+ };
37
+ }
38
+
39
+ interface PayPalPaymentProps {
40
+ checkoutId: string;
41
+ totalAmount: number;
42
+ currency?: string;
43
+ onSuccess: () => void;
44
+ onError: (message: string) => void;
45
+ setIsProcessingPayment: (state: PaymentProcessingState) => void;
46
+ paypalClientId?: string;
47
+ environment?: "sandbox" | "live";
48
+ userEmail?: string;
49
+ guestEmail?: string;
50
+ termsAccepted?: boolean;
51
+ termsData?: { page?: { isPublished: boolean } | null };
52
+ onTermsModalOpen?: () => void;
53
+ onTermsAcceptedChange?: (accepted: boolean) => void;
54
+ questionsValid?: boolean;
55
+ }
56
+
57
+ export function PayPalPayment({
58
+ checkoutId,
59
+ totalAmount,
60
+ currency = "USD",
61
+ onSuccess,
62
+ onError,
63
+ setIsProcessingPayment,
64
+ paypalClientId,
65
+ environment = "sandbox",
66
+ userEmail,
67
+ guestEmail,
68
+ termsAccepted = true,
69
+ termsData,
70
+ onTermsModalOpen,
71
+ onTermsAcceptedChange,
72
+ questionsValid = true,
73
+ }: PayPalPaymentProps) {
74
+ const router = useRouter();
75
+ const [sdkLoaded, setSdkLoaded] = useState(false);
76
+ const [sdkError, setSdkError] = useState<string | null>(null);
77
+ const [isCapturingPayment, setIsCapturingPayment] = useState(false);
78
+ const [paypalConfig, setPaypalConfig] = useState<{
79
+ clientId: string;
80
+ merchantId: string | null;
81
+ paymentMethodReadiness?: {
82
+ applePay: boolean;
83
+ googlePay: boolean;
84
+ paypalButtons: boolean;
85
+ advancedCardProcessing: boolean;
86
+ vaulting: boolean;
87
+ };
88
+ } | null>(null);
89
+ const [isLoadingConfig, setIsLoadingConfig] = useState(true);
90
+ const [googlePaySdkLoaded, setGooglePaySdkLoaded] = useState(false);
91
+ const paypalContainerRef = useRef<HTMLDivElement>(null);
92
+ const buttonsRendered = useRef(false);
93
+ const applePayRendered = useRef(false);
94
+ const googlePayRendered = useRef(false);
95
+ const configFetched = useRef(false);
96
+
97
+ // Fetch PayPal configuration dynamically from Saleor using GraphQL
98
+ useEffect(() => {
99
+ // Prevent duplicate calls
100
+ if (configFetched.current) {
101
+ return;
102
+ }
103
+
104
+ const fetchPayPalConfig = async () => {
105
+ try {
106
+ setIsLoadingConfig(true);
107
+
108
+ // Call the API route to get PayPal configuration
109
+ const response = await fetch("/api/paypal/get-config", {
110
+ method: "POST",
111
+ headers: {
112
+ "Content-Type": "application/json",
113
+ },
114
+ body: JSON.stringify({
115
+ checkoutId,
116
+ amount: totalAmount,
117
+ }),
118
+ });
119
+
120
+ if (!response.ok) {
121
+ const errorData = await response.json();
122
+ throw new Error(errorData.error || `Failed to fetch PayPal configuration (HTTP ${response.status})`);
123
+ }
124
+
125
+ const result = await response.json();
126
+
127
+ if (!result.clientId) {
128
+ throw new Error("PayPal client ID not configured in the payment app");
129
+ }
130
+
131
+ setPaypalConfig({
132
+ clientId: result.clientId,
133
+ merchantId: result.merchantId || null,
134
+ paymentMethodReadiness: result.paymentMethodReadiness,
135
+ });
136
+
137
+
138
+ // `paymentMethodReadiness` is optional and not required to render the PayPal flow.
139
+
140
+ // Mark config as fetched
141
+ configFetched.current = true;
142
+ } catch (error) {
143
+ setSdkError(
144
+ error instanceof Error
145
+ ? error.message
146
+ : "Failed to load PayPal configuration"
147
+ );
148
+ } finally {
149
+ setIsLoadingConfig(false);
150
+ }
151
+ };
152
+
153
+ fetchPayPalConfig();
154
+ }, [checkoutId, totalAmount]);
155
+
156
+ // Load Google Pay SDK
157
+ useEffect(() => {
158
+ // Check if already loaded
159
+ if (window.google?.payments?.api) {
160
+ setGooglePaySdkLoaded(true);
161
+ return;
162
+ }
163
+
164
+ // Load Google Pay SDK
165
+ const script = document.createElement("script");
166
+ script.src = "https://pay.google.com/gp/p/js/pay.js";
167
+ script.async = true;
168
+ script.onload = () => {
169
+ setGooglePaySdkLoaded(true);
170
+ };
171
+ script.onerror = () => {
172
+ console.error("Failed to load Google Pay SDK");
173
+ };
174
+
175
+ document.head.appendChild(script);
176
+
177
+ return () => {
178
+ if (document.head.contains(script)) {
179
+ document.head.removeChild(script);
180
+ }
181
+ };
182
+ }, []);
183
+
184
+ // Load PayPal SDK once config is available
185
+ useEffect(() => {
186
+ // Wait for config to be loaded
187
+ if (!paypalConfig || isLoadingConfig) {
188
+ return;
189
+ }
190
+
191
+ // Check if SDK already loaded
192
+ if (window.paypal) {
193
+ setSdkLoaded(true);
194
+ return;
195
+ }
196
+
197
+ // Create script element with dynamic configuration
198
+ const script = document.createElement("script");
199
+
200
+ // Build SDK URL with proper format (same as hardcoded version)
201
+ let sdkUrl = `https://www.paypal.com/sdk/js?client-id=${paypalConfig.clientId}`;
202
+
203
+ // Add merchant-id if available
204
+ if (paypalConfig.merchantId) {
205
+ sdkUrl += `&merchant-id=${paypalConfig.merchantId}`;
206
+ }
207
+
208
+ // Add remaining parameters with Apple Pay and Google Pay components
209
+ sdkUrl += `&currency=${currency}&intent=capture&components=buttons,applepay,googlepay`;
210
+
211
+ script.src = sdkUrl;
212
+ script.async = true;
213
+ script.setAttribute("data-partner-attribution-id", "bnCode");
214
+
215
+ script.onload = () => {
216
+ setSdkLoaded(true);
217
+ };
218
+
219
+ script.onerror = () => {
220
+ setSdkError("Failed to load PayPal payment system. Please check the client ID configuration.");
221
+ };
222
+
223
+ document.body.appendChild(script);
224
+
225
+ return () => {
226
+ // Cleanup script on unmount
227
+ if (document.body.contains(script)) {
228
+ document.body.removeChild(script);
229
+ }
230
+ };
231
+ }, [paypalConfig, isLoadingConfig, currency]);
232
+
233
+ // Render PayPal buttons
234
+ useEffect(() => {
235
+ if (!sdkLoaded || !window.paypal || buttonsRendered.current) {
236
+ return;
237
+ }
238
+
239
+ const container = paypalContainerRef.current;
240
+ if (!container) {
241
+ return;
242
+ }
243
+
244
+ // Check if buttons are already rendered in the container
245
+ if (container.children.length > 0) {
246
+ buttonsRendered.current = true;
247
+ return;
248
+ }
249
+
250
+ try {
251
+ window.paypal
252
+ .Buttons({
253
+ createOrder: async () => {
254
+ setIsProcessingPayment({
255
+ isModalOpen: true,
256
+ paymentProcessingLoading: true,
257
+ error: false,
258
+ success: false,
259
+ });
260
+
261
+ try {
262
+ // Call transaction initialize endpoint
263
+ const response = await fetch("/api/paypal/create-order", {
264
+ method: "POST",
265
+ headers: {
266
+ "Content-Type": "application/json",
267
+ },
268
+ body: JSON.stringify({
269
+ checkoutId,
270
+ amount: totalAmount,
271
+ currency,
272
+ }),
273
+ });
274
+
275
+ const data = await response.json();
276
+
277
+ if (!response.ok || !data.orderId) {
278
+ throw new Error(data.error || "Failed to create PayPal order");
279
+ }
280
+
281
+ // Store transaction ID for later use
282
+ if (data.transactionId) {
283
+ sessionStorage.setItem(`paypal-txn-${checkoutId}`, data.transactionId);
284
+ }
285
+
286
+ return data.orderId;
287
+ } catch (error) {
288
+ setIsProcessingPayment({
289
+ isModalOpen: false,
290
+ paymentProcessingLoading: false,
291
+ error: true,
292
+ success: false,
293
+ });
294
+ if (error instanceof Error) {
295
+ onError(`Failed to create PayPal order: ${error.message}`);
296
+ } else {
297
+ onError("Failed to create PayPal order");
298
+ }
299
+ throw error;
300
+ }
301
+ },
302
+
303
+ onApprove: async (data: { orderID: string }) => {
304
+ // Set capturing state to disable buttons
305
+ setIsCapturingPayment(true);
306
+
307
+ setIsProcessingPayment({
308
+ isModalOpen: true,
309
+ paymentProcessingLoading: true,
310
+ error: false,
311
+ success: false,
312
+ });
313
+
314
+ try {
315
+ // Get stored transaction ID if available
316
+ const transactionId = sessionStorage.getItem(`paypal-txn-${checkoutId}`);
317
+
318
+ // Capture/complete the payment
319
+ const response = await fetch("/api/paypal/capture-order", {
320
+ method: "POST",
321
+ headers: {
322
+ "Content-Type": "application/json",
323
+ },
324
+ body: JSON.stringify({
325
+ checkoutId,
326
+ orderId: data.orderID,
327
+ transactionId,
328
+ }),
329
+ });
330
+
331
+ const result = await response.json();
332
+
333
+ if (!response.ok || result.error) {
334
+ throw new Error(result.error || "Failed to capture payment");
335
+ }
336
+
337
+ // Extract order details from the response
338
+ const orderData = result.order;
339
+
340
+ if (orderData?.id && orderData?.number) {
341
+ setIsProcessingPayment({
342
+ isModalOpen: false,
343
+ paymentProcessingLoading: false,
344
+ error: false,
345
+ success: true,
346
+ });
347
+
348
+ // Redirect to order confirmation page with order details
349
+ router.push(
350
+ `/order-confirmation?orderId=${orderData.id}&orderNumber=${orderData.number}&total=${orderData.total}`
351
+ );
352
+
353
+ // Call onSuccess callback
354
+ onSuccess();
355
+ } else {
356
+ throw new Error("Order data not found in response");
357
+ }
358
+ } catch (error) {
359
+ // Reset capturing state on error
360
+ setIsCapturingPayment(false);
361
+
362
+ setIsProcessingPayment({
363
+ isModalOpen: false,
364
+ paymentProcessingLoading: false,
365
+ error: true,
366
+ success: false,
367
+ });
368
+ if (error instanceof Error) {
369
+ onError(`Payment capture failed: ${error.message}`);
370
+ } else {
371
+ onError("Payment capture failed");
372
+ }
373
+ }
374
+ },
375
+
376
+ onError: () => {
377
+ setIsProcessingPayment({
378
+ isModalOpen: false,
379
+ paymentProcessingLoading: false,
380
+ error: true,
381
+ success: false,
382
+ });
383
+ onError("PayPal payment error occurred");
384
+ },
385
+
386
+ onCancel: () => {
387
+ setIsProcessingPayment({
388
+ isModalOpen: false,
389
+ paymentProcessingLoading: false,
390
+ error: false,
391
+ success: false,
392
+ });
393
+ },
394
+
395
+ style: {
396
+ layout: "vertical",
397
+ color: "gold",
398
+ shape: "rect",
399
+ label: "paypal",
400
+ height: 45,
401
+ },
402
+ })
403
+ .render("#paypal-button-container")
404
+ .then(() => {
405
+ buttonsRendered.current = true;
406
+ })
407
+ .catch(() => {
408
+ setSdkError("Failed to render PayPal buttons");
409
+ });
410
+ } catch (error) {
411
+ setSdkError("Failed to initialize PayPal buttons");
412
+ }
413
+ }, [
414
+ sdkLoaded,
415
+ checkoutId,
416
+ totalAmount,
417
+ currency,
418
+ onSuccess,
419
+ onError,
420
+ setIsProcessingPayment,
421
+ router,
422
+ ]);
423
+
424
+ // Render Apple Pay button
425
+ useEffect(() => {
426
+ if (!sdkLoaded || !window.paypal || applePayRendered.current) {
427
+ return;
428
+ }
429
+
430
+ // Check if Apple Pay is enabled
431
+ if (!paypalConfig?.paymentMethodReadiness?.applePay) {
432
+ return;
433
+ }
434
+
435
+ const applePayContainer = document.getElementById("applepay-container");
436
+ if (!applePayContainer) {
437
+ return;
438
+ }
439
+
440
+ // Check if buttons are already rendered in the container
441
+ if (applePayContainer.children.length > 0) {
442
+ applePayRendered.current = true;
443
+ return;
444
+ }
445
+
446
+ // Mark as rendered immediately to prevent race conditions
447
+ applePayRendered.current = true;
448
+
449
+ try {
450
+ // Check if ApplePaySession is available on this device/browser
451
+ if (!window.ApplePaySession || !window.ApplePaySession.canMakePayments()) {
452
+ // Apple Pay not available on this device/browser.
453
+ applePayContainer.style.display = "none";
454
+ return;
455
+ }
456
+
457
+ const applepay = window.paypal.Applepay();
458
+
459
+ // Configure Apple Pay
460
+ applepay
461
+ .config()
462
+ .then((applePayConfig) => {
463
+ // Apple Pay is available and configured
464
+ // Apple Pay configured.
465
+
466
+ // Create Apple Pay button
467
+ const button = document.createElement("button");
468
+ button.className = "apple-pay-button apple-pay-button-black";
469
+ button.style.cssText =
470
+ "width: 100%; height: 45px; display: block; cursor: pointer; -webkit-appearance: -apple-pay-button; -apple-pay-button-type: plain;";
471
+
472
+ button.addEventListener("click", () => {
473
+ try {
474
+ // Step 1: Create Apple Pay Payment Request (must be synchronous with click)
475
+ // Creating Apple Pay payment session.
476
+
477
+ const paymentRequest = {
478
+ countryCode: applePayConfig.countryCode || "US",
479
+ currencyCode: currency,
480
+ merchantCapabilities: applePayConfig.merchantCapabilities,
481
+ supportedNetworks: applePayConfig.supportedNetworks,
482
+ total: {
483
+ label: "Total",
484
+ type: "final",
485
+ amount: totalAmount.toFixed(2)
486
+ }
487
+ };
488
+
489
+ // Step 2: Create Apple Pay Session (MUST be called synchronously in click handler)
490
+ if (!window.ApplePaySession) {
491
+ console.error("ApplePaySession not available");
492
+ onError("Apple Pay is not available on this device");
493
+ return;
494
+ }
495
+ const session = new window.ApplePaySession(4, paymentRequest);
496
+
497
+ // Handle merchant validation
498
+ session.onvalidatemerchant = (event: { validationURL: string }) => {
499
+ // Validating merchant.
500
+ applepay.validateMerchant({
501
+ validationUrl: event.validationURL,
502
+ displayName: "Web Shop Manager"
503
+ })
504
+ .then((validateResult: { merchantSession: unknown }) => {
505
+ // Merchant validated.
506
+ session.completeMerchantValidation(validateResult.merchantSession);
507
+ })
508
+ .catch((validateError: Error) => {
509
+ console.error("❌ Merchant validation failed:", validateError);
510
+ session.abort();
511
+ onError("Apple Pay validation failed");
512
+ });
513
+ };
514
+
515
+ // Handle payment authorization
516
+ session.onpaymentauthorized = async (event: { payment: { token: unknown; billingContact?: unknown } }) => {
517
+ try {
518
+ // Payment authorized by user.
519
+
520
+ setIsProcessingPayment({
521
+ isModalOpen: true,
522
+ paymentProcessingLoading: true,
523
+ error: false,
524
+ success: false,
525
+ });
526
+
527
+ // Step 3: Create PayPal order
528
+ // Creating PayPal order.
529
+ const response = await fetch("/api/paypal/create-order", {
530
+ method: "POST",
531
+ headers: {
532
+ "Content-Type": "application/json",
533
+ },
534
+ body: JSON.stringify({
535
+ checkoutId,
536
+ amount: totalAmount,
537
+ currency,
538
+ }),
539
+ });
540
+
541
+ const data = await response.json();
542
+
543
+ if (!response.ok || !data.orderId) {
544
+ throw new Error(data.error || "Failed to create PayPal order");
545
+ }
546
+
547
+ // PayPal order created.
548
+
549
+ // Store transaction ID for later use
550
+ if (data.transactionId) {
551
+ sessionStorage.setItem(`paypal-txn-${checkoutId}`, data.transactionId);
552
+ }
553
+
554
+ // Step 4: Confirm order with PayPal using Apple Pay token
555
+ // Confirming order with PayPal.
556
+ await applepay.confirmOrder({
557
+ orderId: data.orderId,
558
+ token: event.payment.token,
559
+ billingContact: event.payment.billingContact
560
+ });
561
+
562
+ // Order confirmed with PayPal.
563
+
564
+ // Complete the Apple Pay session as successful
565
+ if (window.ApplePaySession) {
566
+ session.completePayment(window.ApplePaySession.STATUS_SUCCESS);
567
+ }
568
+
569
+ // Step 5: Capture payment
570
+ setIsCapturingPayment(true);
571
+ // Capturing payment.
572
+
573
+ const transactionId = sessionStorage.getItem(`paypal-txn-${checkoutId}`);
574
+
575
+ const captureResponse = await fetch("/api/paypal/capture-order", {
576
+ method: "POST",
577
+ headers: {
578
+ "Content-Type": "application/json",
579
+ },
580
+ body: JSON.stringify({
581
+ checkoutId,
582
+ orderId: data.orderId,
583
+ transactionId,
584
+ }),
585
+ });
586
+
587
+ const result = await captureResponse.json();
588
+
589
+ if (!captureResponse.ok || result.error) {
590
+ throw new Error(result.error || "Failed to capture payment");
591
+ }
592
+
593
+ const orderData = result.order;
594
+
595
+ if (orderData?.id && orderData?.number) {
596
+ // Apple Pay payment successful.
597
+
598
+ setIsProcessingPayment({
599
+ isModalOpen: false,
600
+ paymentProcessingLoading: false,
601
+ error: false,
602
+ success: true,
603
+ });
604
+
605
+ router.push(
606
+ `/order-confirmation?orderId=${orderData.id}&orderNumber=${orderData.number}&total=${orderData.total}`
607
+ );
608
+
609
+ onSuccess();
610
+ } else {
611
+ throw new Error("Order data not found in response");
612
+ }
613
+ } catch (error) {
614
+ console.error("❌ Payment processing error:", error);
615
+ if (window.ApplePaySession) {
616
+ session.completePayment(window.ApplePaySession.STATUS_FAILURE);
617
+ }
618
+ setIsCapturingPayment(false);
619
+ setIsProcessingPayment({
620
+ isModalOpen: false,
621
+ paymentProcessingLoading: false,
622
+ error: true,
623
+ success: false,
624
+ });
625
+ if (error instanceof Error) {
626
+ onError(`Apple Pay payment failed: ${error.message}`);
627
+ } else {
628
+ onError("Apple Pay payment failed");
629
+ }
630
+ }
631
+ };
632
+
633
+ // Handle cancel event
634
+ session.oncancel = () => {
635
+ // Apple Pay session cancelled by user.
636
+ setIsProcessingPayment({
637
+ isModalOpen: false,
638
+ paymentProcessingLoading: false,
639
+ error: false,
640
+ success: false,
641
+ });
642
+ };
643
+
644
+ // Begin the session
645
+ session.begin();
646
+ // Apple Pay session started.
647
+ } catch (error) {
648
+ console.error("❌ Apple Pay session error:", error);
649
+ setIsProcessingPayment({
650
+ isModalOpen: false,
651
+ paymentProcessingLoading: false,
652
+ error: true,
653
+ success: false,
654
+ });
655
+ if (error instanceof Error) {
656
+ onError(`Apple Pay initialization failed: ${error.message}`);
657
+ } else {
658
+ onError("Apple Pay initialization failed");
659
+ }
660
+ }
661
+ });
662
+
663
+ applePayContainer.appendChild(button);
664
+ })
665
+ .catch((error) => {
666
+ console.error("Apple Pay not available:", error);
667
+ // Apple Pay is not available on this device/browser
668
+ // Hide the container
669
+ applePayContainer.style.display = "none";
670
+ // Reset flag if initialization failed
671
+ applePayRendered.current = false;
672
+ });
673
+ } catch (error) {
674
+ console.error("Failed to initialize Apple Pay:", error);
675
+ applePayContainer.style.display = "none";
676
+ // Reset flag if initialization failed
677
+ applePayRendered.current = false;
678
+ }
679
+ }, [
680
+ sdkLoaded,
681
+ paypalConfig,
682
+ checkoutId,
683
+ totalAmount,
684
+ currency,
685
+ onSuccess,
686
+ onError,
687
+ setIsProcessingPayment,
688
+ router,
689
+ ]);
690
+
691
+ // Render Google Pay button
692
+ useEffect(() => {
693
+ if (!sdkLoaded || !window.paypal || googlePayRendered.current) {
694
+ return;
695
+ }
696
+
697
+ // Check if Google Pay is enabled
698
+ if (!paypalConfig?.paymentMethodReadiness?.googlePay) {
699
+ return;
700
+ }
701
+
702
+ // Check if Google Pay SDK is loaded
703
+ if (!googlePaySdkLoaded || !window.google?.payments?.api) {
704
+ // Google Pay SDK not loaded yet.
705
+ return;
706
+ }
707
+
708
+ const googlePayContainer = document.getElementById("googlepay-container");
709
+ if (!googlePayContainer) {
710
+ return;
711
+ }
712
+
713
+ // Check if buttons are already rendered in the container
714
+ if (googlePayContainer.children.length > 0) {
715
+ googlePayRendered.current = true;
716
+ return;
717
+ }
718
+
719
+ // Mark as rendered immediately to prevent race conditions
720
+ googlePayRendered.current = true;
721
+
722
+ try {
723
+ const googlepay = window.paypal.Googlepay();
724
+
725
+ // Configure Google Pay to check if it's available
726
+ googlepay
727
+ .config()
728
+ .then(async (googlePayConfig) => {
729
+ // Google Pay is available and configured
730
+ // Google Pay configured.
731
+
732
+ // Verify Google Pay SDK is still available (TypeScript safety check)
733
+ if (!window.google?.payments?.api) {
734
+ console.error("Google Pay SDK not available");
735
+ googlePayContainer.style.display = "none";
736
+ return;
737
+ }
738
+
739
+ // Create Google Payments client
740
+ const paymentsClient = new window.google.payments.api.PaymentsClient({
741
+ environment: environment === "sandbox" ? "TEST" : "PRODUCTION",
742
+ });
743
+
744
+ // Check if Google Pay is available on this device/browser
745
+ const isReadyToPayRequest = {
746
+ apiVersion: 2,
747
+ apiVersionMinor: 0,
748
+ allowedPaymentMethods: googlePayConfig.allowedPaymentMethods,
749
+ };
750
+
751
+ const { result: isReadyToPay } = await paymentsClient.isReadyToPay(isReadyToPayRequest);
752
+ if (!isReadyToPay) {
753
+ // Google Pay not available on this device.
754
+ googlePayContainer.style.display = "none";
755
+ return;
756
+ }
757
+
758
+ // Create a regular button (not using paymentsClient.createButton)
759
+ const button = document.createElement("button");
760
+ button.className = "gpay-button";
761
+ button.style.cssText =
762
+ "width: 100%; height: 45px; background-color: #000; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: 500; display: flex; align-items: center; justify-content: center; font-size: 14px;";
763
+ button.textContent = "Google Pay";
764
+
765
+ button.addEventListener("click", async () => {
766
+ try {
767
+ // Step 1: Show Google Pay payment sheet IMMEDIATELY (must be synchronous with user click)
768
+ // This preserves the user activation context required by Payment Request API
769
+ // Showing Google Pay payment sheet.
770
+
771
+ const paymentDataRequest: PaymentDataRequest = {
772
+ apiVersion: 2,
773
+ apiVersionMinor: 0,
774
+ allowedPaymentMethods: googlePayConfig.allowedPaymentMethods,
775
+ merchantInfo: googlePayConfig.merchantInfo,
776
+ transactionInfo: {
777
+ totalPriceStatus: "FINAL",
778
+ totalPrice: totalAmount.toFixed(2),
779
+ currencyCode: currency,
780
+ },
781
+ };
782
+
783
+ // Show Google Pay payment sheet - MUST be called synchronously after user click
784
+ const paymentData = await paymentsClient.loadPaymentData(paymentDataRequest);
785
+ // Payment data received from Google Pay.
786
+
787
+ // Step 2: User has authorized payment, now show processing modal
788
+ setIsProcessingPayment({
789
+ isModalOpen: true,
790
+ paymentProcessingLoading: true,
791
+ error: false,
792
+ success: false,
793
+ });
794
+
795
+ // Step 3: Create PayPal order (after user authorization)
796
+ // Creating PayPal order.
797
+ const response = await fetch("/api/paypal/create-order", {
798
+ method: "POST",
799
+ headers: {
800
+ "Content-Type": "application/json",
801
+ },
802
+ body: JSON.stringify({
803
+ checkoutId,
804
+ amount: totalAmount,
805
+ currency,
806
+ }),
807
+ });
808
+
809
+ const data = await response.json();
810
+
811
+ if (!response.ok || !data.orderId) {
812
+ throw new Error(data.error || "Failed to create PayPal order");
813
+ }
814
+
815
+ // PayPal order created.
816
+
817
+ // Store transaction ID for later use
818
+ if (data.transactionId) {
819
+ sessionStorage.setItem(`paypal-txn-${checkoutId}`, data.transactionId);
820
+ }
821
+
822
+ // Step 4: Confirm order with PayPal using the payment method data
823
+ // Confirming order with PayPal SDK.
824
+ const confirmResult = await googlepay.confirmOrder({
825
+ orderId: data.orderId,
826
+ paymentMethodData: paymentData.paymentMethodData,
827
+ });
828
+
829
+ // PayPal confirm result received.
830
+
831
+ // Check if payment was approved
832
+ if (confirmResult.status !== "APPROVED" && confirmResult.status !== "COMPLETED") {
833
+ throw new Error(`Payment was not approved. Status: ${confirmResult.status}`);
834
+ }
835
+
836
+ // Step 4: Capture the payment with retry logic
837
+ setIsCapturingPayment(true);
838
+ // Capturing payment.
839
+
840
+ // Get stored transaction ID
841
+ const transactionId = sessionStorage.getItem(`paypal-txn-${checkoutId}`);
842
+
843
+ // Retry logic for order creation (webhook processing can take time)
844
+ const maxRetries = 3;
845
+ const retryDelay = 2000; // 2 seconds
846
+
847
+ let result: { order?: { id: string; number: string; total: number }; error?: string; status?: string } | undefined;
848
+ let captureResponse: Response | undefined;
849
+ let retryCount = 0;
850
+
851
+ while (retryCount < maxRetries) {
852
+ // Capture payment
853
+ captureResponse = await fetch("/api/paypal/capture-order", {
854
+ method: "POST",
855
+ headers: {
856
+ "Content-Type": "application/json",
857
+ },
858
+ body: JSON.stringify({
859
+ checkoutId,
860
+ orderId: data.orderId,
861
+ transactionId,
862
+ }),
863
+ });
864
+
865
+ result = await captureResponse.json();
866
+
867
+ // If order is being processed (202), wait and retry
868
+ if (captureResponse.status === 202 && result?.status === "processing") {
869
+ // Retrying while order is processing.
870
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
871
+ retryCount++;
872
+ continue;
873
+ }
874
+
875
+ // If successful or error, break out of loop
876
+ break;
877
+ }
878
+
879
+ if (!captureResponse || !captureResponse.ok || result?.error) {
880
+ // If still processing after all retries, show a different message
881
+ if (captureResponse && captureResponse.status === 202) {
882
+ throw new Error("Your payment is being processed. Please check your email for order confirmation.");
883
+ }
884
+ throw new Error(result?.error || "Failed to capture payment");
885
+ }
886
+
887
+ // Extract order details
888
+ const orderData = result?.order;
889
+
890
+ if (orderData?.id && orderData?.number) {
891
+ // Order completed successfully.
892
+
893
+ setIsProcessingPayment({
894
+ isModalOpen: false,
895
+ paymentProcessingLoading: false,
896
+ error: false,
897
+ success: true,
898
+ });
899
+
900
+ // Redirect to order confirmation
901
+ router.push(
902
+ `/order-confirmation?orderId=${orderData.id}&orderNumber=${orderData.number}&total=${orderData.total}`
903
+ );
904
+
905
+ onSuccess();
906
+ } else {
907
+ throw new Error("Order data not found in response");
908
+ }
909
+ } catch (error) {
910
+ console.error("❌ Google Pay payment error:", error);
911
+ setIsCapturingPayment(false);
912
+ setIsProcessingPayment({
913
+ isModalOpen: false,
914
+ paymentProcessingLoading: false,
915
+ error: true,
916
+ success: false,
917
+ });
918
+ if (error instanceof Error) {
919
+ onError(`Google Pay payment failed: ${error.message}`);
920
+ } else {
921
+ onError("Google Pay payment failed");
922
+ }
923
+ }
924
+ });
925
+
926
+ // Append button to container
927
+ googlePayContainer.appendChild(button);
928
+ })
929
+ .catch((error) => {
930
+ console.error("Google Pay not available:", error);
931
+ // Google Pay is not available on this device/browser
932
+ // Hide the container
933
+ googlePayContainer.style.display = "none";
934
+ // Reset flag if initialization failed
935
+ googlePayRendered.current = false;
936
+ });
937
+ } catch (error) {
938
+ console.error("Failed to initialize Google Pay:", error);
939
+ googlePayContainer.style.display = "none";
940
+ // Reset flag if initialization failed
941
+ googlePayRendered.current = false;
942
+ }
943
+ }, [
944
+ sdkLoaded,
945
+ googlePaySdkLoaded,
946
+ paypalConfig,
947
+ checkoutId,
948
+ totalAmount,
949
+ currency,
950
+ environment,
951
+ onSuccess,
952
+ onError,
953
+ setIsProcessingPayment,
954
+ router,
955
+ ]);
956
+
957
+ // Show error if SDK failed to load
958
+ if (sdkError) {
959
+ return (
960
+ <div className="bg-red-50 border border-red-200 rounded-md p-4">
961
+ <p className="text-red-700 text-sm">{sdkError}</p>
962
+ <p className="text-red-600 text-xs mt-2">
963
+ Please refresh the page or contact support.
964
+ </p>
965
+ </div>
966
+ );
967
+ }
968
+
969
+ // Show loading while fetching config or loading SDK
970
+ if (isLoadingConfig || !paypalConfig || !sdkLoaded) {
971
+ return (
972
+ <div className="space-y-4">
973
+ <LoadingUI className="h-32" />
974
+ <p className="text-center text-sm text-[var(--color-secondary-600)]">
975
+ {isLoadingConfig
976
+ ? "Loading PayPal configuration..."
977
+ : "Loading PayPal payment system..."}
978
+ </p>
979
+ </div>
980
+ );
981
+ }
982
+
983
+ // Validation checks
984
+ const hasEmail = userEmail || guestEmail;
985
+ const needsTermsAcceptance = termsData?.page?.isPublished && !termsAccepted;
986
+ const isDisabled = !questionsValid || needsTermsAcceptance || !hasEmail || isCapturingPayment;
987
+
988
+ return (
989
+ <div className="space-y-6">
990
+ {/* Payment Capturing Loading Overlay */}
991
+ {isCapturingPayment && (
992
+ <div className="space-y-4">
993
+ <LoadingUI className="h-32" />
994
+ <p className="text-center text-sm text-[var(--color-secondary-600)]">
995
+ Processing payment...
996
+ </p>
997
+ </div>
998
+ )}
999
+
1000
+ {/* Main PayPal UI - hidden when capturing */}
1001
+ <div className={isCapturingPayment ? "hidden" : ""}>
1002
+ {/* Validation Messages - Only email and questions at top */}
1003
+ {!hasEmail && (
1004
+ <div className="bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4">
1005
+ <p className="text-yellow-700 text-sm">
1006
+ Please provide an email address to continue with payment.
1007
+ </p>
1008
+ </div>
1009
+ )}
1010
+
1011
+ {!questionsValid && (
1012
+ <div className="bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4">
1013
+ <p className="text-yellow-700 text-sm">
1014
+ Please complete all required questions below.
1015
+ </p>
1016
+ </div>
1017
+ )}
1018
+
1019
+ {/* Payment Buttons Container */}
1020
+ <div
1021
+ className={`transition-opacity space-y-3 mb-4 ${isDisabled ? "opacity-50 pointer-events-none" : ""}`}
1022
+ >
1023
+ {/* Apple Pay Button */}
1024
+ {paypalConfig?.paymentMethodReadiness?.applePay && (
1025
+ <div id="applepay-container" className="min-h-[50px]" />
1026
+ )}
1027
+
1028
+ {/* Google Pay Button */}
1029
+ {paypalConfig?.paymentMethodReadiness?.googlePay && (
1030
+ <div id="googlepay-container" className="min-h-[50px]" />
1031
+ )}
1032
+
1033
+ {/* PayPal Buttons */}
1034
+ {paypalConfig?.paymentMethodReadiness?.paypalButtons !== false && (
1035
+ <div
1036
+ id="paypal-button-container"
1037
+ ref={paypalContainerRef}
1038
+ className="min-h-[50px]"
1039
+ />
1040
+ )}
1041
+ </div>
1042
+
1043
+ {/* Terms Warning - After PayPal Buttons */}
1044
+ {needsTermsAcceptance && (
1045
+ <div className="bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4">
1046
+ <p className="text-yellow-700 text-sm">
1047
+ Please accept the Terms and Conditions to continue.
1048
+ </p>
1049
+ </div>
1050
+ )}
1051
+
1052
+ {/* Terms and Conditions Checkbox - At the end, before info message */}
1053
+ {termsData?.page?.isPublished && (
1054
+ <div className="flex items-start gap-2 w-full py-2 mb-4">
1055
+ <input
1056
+ style={{ accentColor: "var(--color-primary-600)" }}
1057
+ type="checkbox"
1058
+ id="termsAcceptedPayPal"
1059
+ className="w-5 h-5 cursor-pointer mt-0.5"
1060
+ checked={termsAccepted}
1061
+ onChange={(e) => onTermsAcceptedChange?.(e.target.checked)}
1062
+ />
1063
+ <label
1064
+ htmlFor="termsAcceptedPayPal"
1065
+ style={{ color: "var(--color-secondary-600)" }}
1066
+ className="text-sm lg:text-base tracking-[-0.04px] cursor-pointer"
1067
+ >
1068
+ I agree to the{" "}
1069
+ <button
1070
+ type="button"
1071
+ onClick={onTermsModalOpen}
1072
+ className="font-semibold text-[var(--color-primary-600)] hover:underline focus:underline focus:outline-none"
1073
+ >
1074
+ Terms and Conditions
1075
+ </button>
1076
+ </label>
1077
+ </div>
1078
+ )}
1079
+
1080
+ </div>
1081
+ </div>
1082
+ );
1083
+ }