@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,249 @@
1
+ import React from "react";
2
+
3
+ export type EditorJsParagraphBlock = { id?: string; type: "paragraph"; data: { text?: string } };
4
+ export type EditorJsHeaderBlock = { id?: string; type: "header"; data: { text?: string; level?: number } };
5
+ export type EditorJsListBlock = { id?: string; type: "list"; data: { style?: "ordered" | "unordered"; items?: string[] } };
6
+ export type EditorJsQuoteBlock = { id?: string; type: "quote"; data: { text?: string; caption?: string; alignment?: string } };
7
+ export type EditorJsLinkBlock = { id?: string; type: "linkTool"; data: { link?: string; meta?: { title?: string; description?: string; image?: { url?: string } } } };
8
+ export type EditorJsImageBlock = { id?: string; type: "image"; data: { file?: { url?: string }; caption?: string; withBorder?: boolean; withBackground?: boolean; stretched?: boolean } };
9
+ export type EditorJsDelimiterBlock = { id?: string; type: "delimiter"; data?: Record<string, unknown> };
10
+ export type EditorJsTableBlock = { id?: string; type: "table"; data: { withHeadings?: boolean; content?: string[][] } };
11
+ export type EditorJsCodeBlock = { id?: string; type: "code"; data: { code?: string } };
12
+ export type EditorJsGenericBlock = { id?: string; type: string; data?: Record<string, unknown> | null };
13
+ export type EditorJsBlock =
14
+ | EditorJsParagraphBlock
15
+ | EditorJsHeaderBlock
16
+ | EditorJsListBlock
17
+ | EditorJsQuoteBlock
18
+ | EditorJsLinkBlock
19
+ | EditorJsImageBlock
20
+ | EditorJsDelimiterBlock
21
+ | EditorJsTableBlock
22
+ | EditorJsCodeBlock
23
+ | EditorJsGenericBlock;
24
+ export type EditorJsDoc = { time?: number; blocks?: EditorJsBlock[]; version?: string };
25
+
26
+ export function parseEditorJsToSections(content: string | null | undefined) {
27
+ if (!content) return null;
28
+ try {
29
+ const parsed = JSON.parse(content) as EditorJsDoc;
30
+ const blocks = parsed.blocks || [];
31
+ if (!blocks.length) return null;
32
+
33
+ const sections: Array<{ title: string; description: React.ReactNode }> = [];
34
+ let currentTitle = "";
35
+ let currentContent: React.ReactNode[] = [];
36
+
37
+ const flushCurrentSection = () => {
38
+ if (currentTitle && currentContent.length > 0) {
39
+ sections.push({
40
+ title: currentTitle,
41
+ description: (
42
+ <div className="space-y-4 [&_a]:text-blue-600 [&_a]:underline [&_a]:font-medium [&_a:hover]:text-blue-800 [&_a:hover]:no-underline [&_a]:transition-colors [&_a]:duration-200">
43
+ {currentContent.map((content, idx) => (
44
+ <div key={idx}>{content}</div>
45
+ ))}
46
+ </div>
47
+ )
48
+ });
49
+ currentContent = [];
50
+ }
51
+ };
52
+
53
+ blocks.forEach((b: EditorJsBlock, idx: number) => {
54
+ const key = b.id || String(idx);
55
+
56
+ if (b.type === "header") {
57
+ const headerData = (b as EditorJsHeaderBlock).data;
58
+ const level = Math.min(6, Math.max(1, Number(headerData?.level) || 2));
59
+ const html = String(headerData?.text || "");
60
+
61
+ // H3 starts a new section
62
+ if (level === 3) {
63
+ flushCurrentSection();
64
+ // Strip HTML tags for the title, but keep the HTML for rendering
65
+ currentTitle = html.replace(/<[^>]*>/g, "");
66
+ } else if (level === 1) {
67
+ // H1 is the main title, skip it as it's already in the page heading
68
+ return;
69
+ } else {
70
+ // Other headers become content
71
+ if (level === 2) {
72
+ currentContent.push(<h2 key={key} dangerouslySetInnerHTML={{ __html: html }} />);
73
+ } else if (level === 4) {
74
+ currentContent.push(<h4 key={key} dangerouslySetInnerHTML={{ __html: html }} />);
75
+ } else if (level === 5) {
76
+ currentContent.push(<h5 key={key} dangerouslySetInnerHTML={{ __html: html }} />);
77
+ } else {
78
+ currentContent.push(<h6 key={key} dangerouslySetInnerHTML={{ __html: html }} />);
79
+ }
80
+ }
81
+ } else if (b.type === "paragraph") {
82
+ const paragraphData = (b as EditorJsParagraphBlock).data;
83
+ const html = String(paragraphData?.text || "");
84
+ currentContent.push(<p key={key} dangerouslySetInnerHTML={{ __html: html }} />);
85
+ } else if (b.type === "list") {
86
+ const listData = (b as EditorJsListBlock).data || {};
87
+ const style = listData.style === "ordered" ? "ol" : "ul";
88
+ const items: string[] = Array.isArray(listData.items) ? listData.items : [];
89
+ if (style === "ol") {
90
+ currentContent.push(
91
+ <ol key={key} className="list-inside list-decimal">
92
+ {items.map((it, i) => (
93
+ <li key={i} dangerouslySetInnerHTML={{ __html: String(it) }} />
94
+ ))}
95
+ </ol>
96
+ );
97
+ } else {
98
+ currentContent.push(
99
+ <ul key={key} className="list-inside list-disc">
100
+ {items.map((it, i) => (
101
+ <li key={i} dangerouslySetInnerHTML={{ __html: String(it) }} />
102
+ ))}
103
+ </ul>
104
+ );
105
+ }
106
+ } else if (b.type === "quote") {
107
+ const quoteData = (b as EditorJsQuoteBlock).data || {};
108
+ const text = String(quoteData.text || "");
109
+ const caption = String(quoteData.caption || "");
110
+ const alignment = quoteData.alignment || "left";
111
+ currentContent.push(
112
+ <blockquote
113
+ key={key}
114
+ className={`border-l-4 border-gray-300 pl-4 py-2 ${alignment === "center" ? "text-center" : ""} italic text-gray-700`}
115
+ >
116
+ <div dangerouslySetInnerHTML={{ __html: text }} />
117
+ {caption ? (
118
+ <cite
119
+ className="block mt-2 text-sm text-gray-500 not-italic"
120
+ dangerouslySetInnerHTML={{ __html: caption }}
121
+ />
122
+ ) : null}
123
+ </blockquote>
124
+ );
125
+ } else if (b.type === "linkTool") {
126
+ const linkData = (b as EditorJsLinkBlock).data || {};
127
+ const link = linkData.link || "";
128
+ const title = linkData.meta?.title || link;
129
+ const description = linkData.meta?.description || "";
130
+ const image = linkData.meta?.image?.url || "";
131
+ currentContent.push(
132
+ <div key={key} className="border-2 border-blue-200 bg-blue-50 rounded-lg p-4 hover:border-blue-300 hover:bg-blue-100 transition-all duration-200">
133
+ <a href={link} target="_blank" rel="noopener noreferrer" className="block group">
134
+ <div className="flex gap-4">
135
+ {image && (
136
+ <img
137
+ src={image}
138
+ alt={title}
139
+ className="w-16 h-16 object-cover rounded flex-shrink-0 border border-blue-200"
140
+ />
141
+ )}
142
+ <div className="flex-1">
143
+ <h4 className="font-bold text-blue-700 group-hover:text-blue-900 line-clamp-2 text-lg">{title}</h4>
144
+ {description && (
145
+ <p className="text-sm text-gray-700 mt-1 line-clamp-2 font-medium">{description}</p>
146
+ )}
147
+ <div className="flex items-center mt-2">
148
+ <svg className="w-4 h-4 text-blue-500 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
149
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
150
+ </svg>
151
+ <p className="text-xs text-blue-600 font-medium">{new URL(link).hostname}</p>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </a>
156
+ </div>
157
+ );
158
+ } else if (b.type === "image") {
159
+ const imageData = (b as EditorJsImageBlock).data || {};
160
+ const url = imageData.file?.url || "";
161
+ const caption = imageData.caption || "";
162
+ const withBorder = imageData.withBorder;
163
+ const withBackground = imageData.withBackground;
164
+ const stretched = imageData.stretched;
165
+ if (url) {
166
+ currentContent.push(
167
+ <figure key={key} className={`${stretched ? "w-full" : "max-w-2xl mx-auto"}`}>
168
+ <img
169
+ src={url}
170
+ alt={caption || "Image"}
171
+ className={`w-full h-auto rounded-lg ${withBorder ? "border border-gray-200" : ""} ${withBackground ? "bg-gray-50 p-4" : ""}`}
172
+ />
173
+ {caption && (
174
+ <figcaption className="text-sm text-gray-600 text-center mt-2 italic">
175
+ {caption}
176
+ </figcaption>
177
+ )}
178
+ </figure>
179
+ );
180
+ }
181
+ } else if (b.type === "delimiter") {
182
+ currentContent.push(
183
+ <div key={key} className="flex justify-center py-6">
184
+ <div className="flex space-x-1">
185
+ <div className="w-1 h-1 bg-gray-400 rounded-full"></div>
186
+ <div className="w-1 h-1 bg-gray-400 rounded-full"></div>
187
+ <div className="w-1 h-1 bg-gray-400 rounded-full"></div>
188
+ </div>
189
+ </div>
190
+ );
191
+ } else if (b.type === "table") {
192
+ const tableData = (b as EditorJsTableBlock).data || {};
193
+ const withHeadings = tableData.withHeadings || false;
194
+ const content = tableData.content || [];
195
+ if (content.length > 0) {
196
+ currentContent.push(
197
+ <div key={key} className="overflow-x-auto">
198
+ <table className="min-w-full border-collapse border border-gray-200">
199
+ {withHeadings && content[0] && (
200
+ <thead>
201
+ <tr className="bg-gray-50">
202
+ {content[0].map((cell, cellIdx) => (
203
+ <th
204
+ key={cellIdx}
205
+ className="border border-gray-200 px-4 py-2 text-left font-semibold text-gray-900"
206
+ >
207
+ {cell}
208
+ </th>
209
+ ))}
210
+ </tr>
211
+ </thead>
212
+ )}
213
+ <tbody>
214
+ {content.slice(withHeadings ? 1 : 0).map((row, rowIdx) => (
215
+ <tr key={rowIdx} className={rowIdx % 2 === 0 ? "bg-white" : "bg-gray-50"}>
216
+ {row.map((cell, cellIdx) => (
217
+ <td
218
+ key={cellIdx}
219
+ className="border border-gray-200 px-4 py-2 text-gray-700"
220
+ >
221
+ {cell}
222
+ </td>
223
+ ))}
224
+ </tr>
225
+ ))}
226
+ </tbody>
227
+ </table>
228
+ </div>
229
+ );
230
+ }
231
+ } else if (b.type === "code") {
232
+ const codeData = (b as EditorJsCodeBlock).data || {};
233
+ const code = codeData.code || "";
234
+ currentContent.push(
235
+ <pre key={key} className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
236
+ <code className="text-sm">{code}</code>
237
+ </pre>
238
+ );
239
+ }
240
+ });
241
+
242
+ // Flush the last section
243
+ flushCurrentSection();
244
+
245
+ return sections;
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
@@ -0,0 +1,146 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export type Theme = "asphalt";
5
+
6
+ export function cn(...inputs: ClassValue[]) {
7
+ return twMerge(clsx(inputs));
8
+ }
9
+
10
+ // utils/function.ts
11
+
12
+ export const EXTENSIONS = ["webp", "png", "jpeg", "jpg"] as const;
13
+
14
+ /** Remove trailing slashes */
15
+ export function trimSlash(s?: string | null): string {
16
+ return (s || "").replace(/\/+$/, "");
17
+ }
18
+
19
+ /** Strip known image extensions from a URL */
20
+ export function stripImageExt(url: string): string {
21
+ return url.replace(/\.(webp|png|jpe?g)$/i, "");
22
+ }
23
+
24
+ export type SrcRoot =
25
+ | string
26
+ | {
27
+ basePath: string;
28
+ fileName: string; // without extension
29
+ };
30
+
31
+ /**
32
+ * Build candidate URLs in preferred extension order.
33
+ */
34
+ export function getImageCandidates(
35
+ input: SrcRoot,
36
+ extensions: readonly string[] = EXTENSIONS
37
+ ): string[] {
38
+ if (typeof input === "string") {
39
+ const root = stripImageExt(input);
40
+ return extensions.map((ext) => `${root}.${ext}`);
41
+ }
42
+
43
+ const basePath = trimSlash(input.basePath);
44
+ const fileRoot = `${basePath}/${input.fileName}`;
45
+ return extensions.map((ext) => `${fileRoot}.${ext}`);
46
+ }
47
+
48
+ /**
49
+ * Normalize a Saleor product slug for URL display by replacing multiple consecutive dashes with single dash.
50
+ *
51
+ * Saleor often generates slugs with multiple dashes (e.g., --- or --) that should be
52
+ * displayed as single dashes in URLs for better readability.
53
+ *
54
+ * @example normalizeSlugForUrl("aero-exhaust---blue-flame-dual-tip---30")
55
+ * // Returns: "aero-exhaust-blue-flame-dual-tip-30"
56
+ */
57
+ export function normalizeSlugForUrl(slug: string): string {
58
+ if (!slug) return "";
59
+
60
+ // Replace 2 or more consecutive dashes with a single dash
61
+ return slug.replace(/-{2,}/g, "-");
62
+ }
63
+
64
+ /**
65
+ * Denormalize a URL slug back to Saleor's format for API queries.
66
+ *
67
+ * Since we normalized the slug by replacing multiple consecutive dashes with single dashes,
68
+ * we simply return the slug as-is because Saleor should be able to match it.
69
+ *
70
+ * If Saleor can't match the normalized slug, consider using product ID for queries instead.
71
+ *
72
+ * @example denormalizeSlugForApi("aero-exhaust-blue-flame-dual-tip-30-outlet-30497695")
73
+ * // Returns: "aero-exhaust-blue-flame-dual-tip-30-outlet-30497695" (as-is)
74
+ */
75
+ export function denormalizeSlugForApi(urlSlug: string): string {
76
+ if (!urlSlug) return "";
77
+
78
+ // Return the slug as-is. Saleor's GraphQL API should handle slug matching
79
+ // flexibly enough to match both normalized and non-normalized formats.
80
+ return urlSlug;
81
+ }
82
+
83
+ /**
84
+ * Create an SEO-friendly product URL with normalized slug + product ID.
85
+ * Format: /product/{normalized-slug}-{product-id}
86
+ * @example createProductUrl("aero-exhaust---turbine", "UHJvZHVjdDozMDQ5NzYwNg==")
87
+ * Returns: "aero-exhaust-turbine-UHJvZHVjdDozMDQ5NzYwNg"
88
+ */
89
+ export function createProductUrl(slug: string, productId: string): string {
90
+ const normalizedSlug = normalizeSlugForUrl(slug);
91
+ // Append product ID to the end for reliable querying
92
+ return `${normalizedSlug}-${productId}`;
93
+ }
94
+
95
+ /**
96
+ * Extract product ID from a Saleor slug that has numeric ID at the end.
97
+ * Saleor slugs often end with the product ID number.
98
+ * @example extractProductIdFromSlug("aero-exhaust---turbine-30497612")
99
+ * Returns: "30497612"
100
+ */
101
+ export function extractProductIdFromSlug(slug: string): string | null {
102
+ if (!slug) return null;
103
+
104
+ // Match numeric ID at the end of the slug
105
+ const match = slug.match(/-(\d+)$/);
106
+
107
+ if (match && match[1]) {
108
+ return match[1];
109
+ }
110
+
111
+ return null;
112
+ }
113
+
114
+ /**
115
+ * Remove the product ID from the end of a Saleor slug.
116
+ * @example removeProductIdFromSlug("aero-exhaust---turbine-30497612")
117
+ * Returns: "aero-exhaust---turbine"
118
+ */
119
+ export function removeProductIdFromSlug(slug: string): string {
120
+ if (!slug) return "";
121
+
122
+ // Remove numeric ID from the end
123
+ return slug.replace(/-\d+$/, "");
124
+ }
125
+
126
+ /**
127
+ * Ensures image URL has the S3 base URL if it's a relative path
128
+ * @param imageUrl - The image URL (can be relative or absolute)
129
+ * @returns Full image URL with S3 base if needed
130
+ */
131
+ export function getFullImageUrl(imageUrl: string | null | undefined): string {
132
+ if (!imageUrl) return "";
133
+
134
+ // If already has protocol (http:// or https://), return as is
135
+ if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
136
+ return imageUrl;
137
+ }
138
+ if (imageUrl.endsWith("/no-image-avail-large.png")) {
139
+ return imageUrl;
140
+ }
141
+
142
+ // If it's a relative path, prepend S3 base URL
143
+ const S3_BASE_URL = "https://wsm-saleor-assets.s3.us-west-2.amazonaws.com";
144
+ const cleanPath = imageUrl.startsWith("/") ? imageUrl.slice(1) : imageUrl;
145
+ return `${S3_BASE_URL}/${cleanPath}`;
146
+ }
@@ -0,0 +1,168 @@
1
+ "use client";
2
+
3
+ import { sendGAEvent } from '@next/third-parties/google';
4
+
5
+ // GA_MEASUREMENT_ID is now obtained from app configuration service, not process.env
6
+ let GA_MEASUREMENT_ID: string | null = null;
7
+
8
+ // Function to get GA Measurement ID from app configuration
9
+ export const getGAMeasurementId = async (): Promise<string | null> => {
10
+ if (GA_MEASUREMENT_ID) return GA_MEASUREMENT_ID;
11
+
12
+ try {
13
+ // Import app config service dynamically to avoid circular dependencies
14
+ const { appConfigService } = await import('./appConfiguration');
15
+ await appConfigService.fetchConfiguration();
16
+ const gaConfig = appConfigService.getGoogleAnalyticsConfig();
17
+ GA_MEASUREMENT_ID = gaConfig?.measurement_id || null;
18
+ return GA_MEASUREMENT_ID;
19
+ } catch (error) {
20
+ console.error('Failed to get GA configuration:', error);
21
+ return null;
22
+ }
23
+ };
24
+
25
+ // For client-side components that use useAppConfiguration hook
26
+ export const getGAMeasurementIdFromContext = (gaConfig: { measurement_id: string } | null): string | null => {
27
+ return gaConfig?.measurement_id || null;
28
+ };
29
+
30
+ // Legacy support - will be deprecated
31
+ export { GA_MEASUREMENT_ID };
32
+
33
+ export const gtag = (...args: unknown[]) => {
34
+ if (typeof window !== "undefined" && window.gtag) {
35
+ window.gtag(...args);
36
+ }
37
+ };
38
+
39
+
40
+ export const trackPageView = (url: string, title?: string, measurementId?: string | null) => {
41
+ // Use provided measurementId or fall back to legacy GA_MEASUREMENT_ID
42
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
43
+ if (!activeMeasurementId) return;
44
+
45
+ sendGAEvent({
46
+ event: 'page_view',
47
+ page_title: title || document.title,
48
+ page_location: url,
49
+ });
50
+ };
51
+
52
+ export const trackEvent = (action: string, parameters?: Record<string, unknown>, measurementId?: string | null) => {
53
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
54
+ if (!activeMeasurementId) return;
55
+
56
+ sendGAEvent({
57
+ event: action,
58
+ ...parameters,
59
+ });
60
+ };
61
+
62
+ export const trackCustomEvent = (eventName: string, parameters?: Record<string, unknown>, measurementId?: string | null) => {
63
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
64
+ if (!activeMeasurementId) return;
65
+
66
+ sendGAEvent({
67
+ event: eventName,
68
+ custom_parameter: true,
69
+ ...parameters,
70
+ });
71
+ };
72
+
73
+ export const trackUserEngagement = (engagementTimeMs: number, measurementId?: string | null) => {
74
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
75
+ if (!activeMeasurementId) return;
76
+
77
+ sendGAEvent({
78
+ event: 'user_engagement',
79
+ engagement_time_msec: engagementTimeMs,
80
+ });
81
+ };
82
+
83
+ export const trackScrollDepth = (scrollPercent: number, measurementId?: string | null) => {
84
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
85
+ if (!activeMeasurementId) return;
86
+
87
+ sendGAEvent({
88
+ event: 'scroll',
89
+ percent_scrolled: scrollPercent,
90
+ });
91
+ };
92
+
93
+ export const trackOutboundClick = (url: string, linkText?: string, measurementId?: string | null) => {
94
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
95
+ if (!activeMeasurementId) return;
96
+
97
+ sendGAEvent({
98
+ event: 'click',
99
+ event_category: 'outbound',
100
+ event_label: url,
101
+ link_text: linkText,
102
+ transport_type: 'beacon',
103
+ });
104
+ };
105
+
106
+ export const trackFileDownload = (fileName: string, fileType?: string, measurementId?: string | null) => {
107
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
108
+ if (!activeMeasurementId) return;
109
+
110
+ sendGAEvent({
111
+ event: 'file_download',
112
+ file_name: fileName,
113
+ file_extension: fileType,
114
+ });
115
+ };
116
+
117
+ export const trackSiteSearch = (searchTerm: string, resultCount?: number, measurementId?: string | null) => {
118
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
119
+ if (!activeMeasurementId) return;
120
+
121
+ sendGAEvent({
122
+ event: 'search',
123
+ search_term: searchTerm,
124
+ number_of_results: resultCount,
125
+ });
126
+ };
127
+
128
+ export const trackTimingEvent = (name: string, value: number, category?: string, measurementId?: string | null) => {
129
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
130
+ if (!activeMeasurementId) return;
131
+
132
+ sendGAEvent({
133
+ event: 'timing_complete',
134
+ name: name,
135
+ value: value,
136
+ event_category: category || 'performance',
137
+ });
138
+ };
139
+
140
+ export const trackEcommerce = (action: string, parameters: Record<string, unknown>, measurementId?: string | null) => {
141
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
142
+ if (!activeMeasurementId) return;
143
+
144
+ sendGAEvent({
145
+ event: action,
146
+ ...parameters,
147
+ });
148
+ };
149
+
150
+ export const setUserProperties = (properties: Record<string, unknown>, measurementId?: string | null) => {
151
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
152
+ if (!activeMeasurementId) return;
153
+
154
+ gtag("config", activeMeasurementId, {
155
+ user_properties: properties,
156
+ });
157
+ };
158
+
159
+ export const trackException = (description: string, fatal = false, measurementId?: string | null) => {
160
+ const activeMeasurementId = measurementId !== undefined ? measurementId : GA_MEASUREMENT_ID;
161
+ if (!activeMeasurementId) return;
162
+
163
+ sendGAEvent({
164
+ event: 'exception',
165
+ description: description,
166
+ fatal: fatal,
167
+ });
168
+ };