@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.
- package/.env.example +57 -0
- package/APPLE_PAY_QUICK_START.md +165 -0
- package/APPLE_PAY_SETUP.md +331 -0
- package/README.md +46 -0
- package/SEO_AUDIT_CHECKLIST_STATUS.md +244 -0
- package/SEO_AUDIT_REPORT.md +66 -0
- package/eslint.config.mjs +16 -0
- package/next-env.d.ts +5 -0
- package/next.config.ts +109 -0
- package/package.json +47 -0
- package/postcss.config.mjs +5 -0
- package/public/.well-known/apple-developer-merchantid-domain-association +1 -0
- package/public/Logo.png +0 -0
- package/public/brand-video.mp4 +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/footer/facebook.tsx +34 -0
- package/public/footer/instagram.tsx +27 -0
- package/public/footer/mail.tsx +5 -0
- package/public/footer/x.tsx +35 -0
- package/public/globe.svg +1 -0
- package/public/icons/Authorize.net.webp +0 -0
- package/public/icons/amex.gif +0 -0
- package/public/icons/appIcon.png +0 -0
- package/public/icons/discover.gif +0 -0
- package/public/icons/master.gif +0 -0
- package/public/icons/paypal.png +0 -0
- package/public/icons/stripe.png +0 -0
- package/public/icons/visa.gif +0 -0
- package/public/images/BackgroundNoise.png +0 -0
- package/public/images/footer-background.png +0 -0
- package/public/next.svg +1 -0
- package/public/no-image-avail-large.png +0 -0
- package/public/random-car-1.jpeg +0 -0
- package/public/random-car-2.png +0 -0
- package/public/random-car-3.jpg +0 -0
- package/public/random-car-4.jpg +0 -0
- package/public/random-car-5.jpg +0 -0
- package/public/star.svg +3 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/seo-audit/generate-checklist.mjs +156 -0
- package/src/app/(auth)/account/forgot-password/layout.tsx +16 -0
- package/src/app/(auth)/account/forgot-password/page.tsx +135 -0
- package/src/app/(auth)/account/login/layout.tsx +16 -0
- package/src/app/(auth)/account/login/page.tsx +288 -0
- package/src/app/(auth)/account/otp/layout.tsx +16 -0
- package/src/app/(auth)/account/otp/page.tsx +108 -0
- package/src/app/(auth)/account/register/layout.tsx +16 -0
- package/src/app/(auth)/account/register/page.tsx +431 -0
- package/src/app/(auth)/account/reset-password/layout.tsx +16 -0
- package/src/app/(auth)/account/reset-password/page.tsx +222 -0
- package/src/app/[slug]/page.tsx +43 -0
- package/src/app/about/loading.tsx +17 -0
- package/src/app/about/page.tsx +61 -0
- package/src/app/account/address/layout.tsx +15 -0
- package/src/app/account/address/page.tsx +166 -0
- package/src/app/account/head.tsx +4 -0
- package/src/app/account/layout.tsx +62 -0
- package/src/app/account/orders/[id]/layout.tsx +17 -0
- package/src/app/account/orders/[id]/page.tsx +115 -0
- package/src/app/account/orders/components/orderDetailsModal.tsx +410 -0
- package/src/app/account/orders/layout.tsx +15 -0
- package/src/app/account/orders/page.tsx +146 -0
- package/src/app/account/page.tsx +39 -0
- package/src/app/account/settings/components/editProfileSuccessModal.tsx +28 -0
- package/src/app/account/settings/layout.tsx +15 -0
- package/src/app/account/settings/page.tsx +260 -0
- package/src/app/api/affirm/check-status/route.ts +94 -0
- package/src/app/api/affirm/create-checkout/route.ts +109 -0
- package/src/app/api/affirm/get-config/route.ts +108 -0
- package/src/app/api/affirm/process-payment/route.ts +244 -0
- package/src/app/api/affirm/test-connection/route.ts +45 -0
- package/src/app/api/auth/clear/route.ts +16 -0
- package/src/app/api/auth/clear-cookies/route.ts +42 -0
- package/src/app/api/auth/set/route.ts +47 -0
- package/src/app/api/configuration/route.ts +18 -0
- package/src/app/api/dynamic-page/[slug]/route.ts +24 -0
- package/src/app/api/form-submission/route.ts +237 -0
- package/src/app/api/paypal/capture-order/route.ts +303 -0
- package/src/app/api/paypal/create-order/route.ts +211 -0
- package/src/app/api/paypal/get-config/route.ts +240 -0
- package/src/app/api/search-proxy/route.ts +52 -0
- package/src/app/authorize-net-success/layout.tsx +19 -0
- package/src/app/authorize-net-success/page.tsx +12 -0
- package/src/app/authorize-net-success/summary.tsx +486 -0
- package/src/app/blog/[slug]/blogContentRenderer.tsx +369 -0
- package/src/app/blog/[slug]/layout.tsx +17 -0
- package/src/app/blog/[slug]/page.tsx +151 -0
- package/src/app/blog/constant.tsx +147 -0
- package/src/app/blog/layout.tsx +31 -0
- package/src/app/blog/page.tsx +81 -0
- package/src/app/brand/[id]/BrandPageClient.tsx +188 -0
- package/src/app/brand/[id]/layout.tsx +17 -0
- package/src/app/brand/[id]/page.tsx +176 -0
- package/src/app/brands/components/brandsListingClient.tsx +97 -0
- package/src/app/brands/layout.tsx +31 -0
- package/src/app/brands/page.tsx +40 -0
- package/src/app/cancellation-policy/page.tsx +53 -0
- package/src/app/cart/layout.tsx +19 -0
- package/src/app/cart/page.tsx +752 -0
- package/src/app/category/[slug]/CategoryPageClient.tsx +377 -0
- package/src/app/category/[slug]/layout.tsx +17 -0
- package/src/app/category/[slug]/page.tsx +224 -0
- package/src/app/category/page.tsx +114 -0
- package/src/app/checkout/components/addNewAddressModal.tsx +474 -0
- package/src/app/checkout/layout.tsx +19 -0
- package/src/app/checkout/page.tsx +3312 -0
- package/src/app/components/account/AccountTabs.tsx +40 -0
- package/src/app/components/ads/GoogleAdSense.tsx +74 -0
- package/src/app/components/analytics/AnalyticsScripts.tsx +78 -0
- package/src/app/components/analytics/ConditionalGTMNoscript.tsx +24 -0
- package/src/app/components/analytics/ConditionalGoogleAnalytics.tsx +16 -0
- package/src/app/components/ancillary/AncillaryContent.tsx +7 -0
- package/src/app/components/auth/TokenExpirationHandler.tsx +8 -0
- package/src/app/components/blog/BlogList.tsx +112 -0
- package/src/app/components/checkout/AddressInformationSection.tsx +34 -0
- package/src/app/components/checkout/AddressManagement.tsx +571 -0
- package/src/app/components/checkout/CheckoutHeader.tsx +51 -0
- package/src/app/components/checkout/CheckoutQuestions.tsx +454 -0
- package/src/app/components/checkout/CheckoutTermsModal.tsx +81 -0
- package/src/app/components/checkout/ContactDetailsSection.tsx +52 -0
- package/src/app/components/checkout/DealerShippingSection.tsx +359 -0
- package/src/app/components/checkout/DeliveryMethodSection.tsx +249 -0
- package/src/app/components/checkout/OrderSummary.tsx +386 -0
- package/src/app/components/checkout/TermsContentRenderer.tsx +147 -0
- package/src/app/components/checkout/WillCallSection.tsx +133 -0
- package/src/app/components/checkout/affirmPayment.tsx +383 -0
- package/src/app/components/checkout/checkoutProcessingModal.tsx +96 -0
- package/src/app/components/checkout/googlePayButton.tsx +334 -0
- package/src/app/components/checkout/paymentStep.tsx +180 -0
- package/src/app/components/checkout/paypalPayment.tsx +1083 -0
- package/src/app/components/checkout/saleorNativePayment.tsx +1758 -0
- package/src/app/components/dynamicPage/DynamicPageRenderer.tsx +13 -0
- package/src/app/components/dynamicPage/HtmlWidgetRenderer.tsx +144 -0
- package/src/app/components/filtersCollapsible/index.tsx +365 -0
- package/src/app/components/globalSearch/index.tsx +423 -0
- package/src/app/components/layout/cartDropDown.tsx +628 -0
- package/src/app/components/layout/components/FooterNewsletter.tsx +21 -0
- package/src/app/components/layout/footer.tsx +283 -0
- package/src/app/components/layout/header/accountMenuDropdown.tsx +53 -0
- package/src/app/components/layout/header/components/CartBadge.tsx +18 -0
- package/src/app/components/layout/header/components/LoadingState.tsx +17 -0
- package/src/app/components/layout/header/components/MenuItemDropdown.tsx +124 -0
- package/src/app/components/layout/header/components/MobileNavbar.tsx +123 -0
- package/src/app/components/layout/header/components/NavbarActions.tsx +125 -0
- package/src/app/components/layout/header/components/NavbarBrand.tsx +29 -0
- package/src/app/components/layout/header/components/NavigationLinks.tsx +131 -0
- package/src/app/components/layout/header/hamMenuSlide.tsx +318 -0
- package/src/app/components/layout/header/header.tsx +44 -0
- package/src/app/components/layout/header/hooks/useDropdown.ts +45 -0
- package/src/app/components/layout/header/hooks/useNavbarData.ts +138 -0
- package/src/app/components/layout/header/hooks/useNavbarState.ts +66 -0
- package/src/app/components/layout/header/megaMenuDropdown.tsx +116 -0
- package/src/app/components/layout/header/navBar.tsx +121 -0
- package/src/app/components/layout/header/search.tsx +418 -0
- package/src/app/components/layout/header/styles/navbarStyles.ts +27 -0
- package/src/app/components/layout/header/topBar.tsx +214 -0
- package/src/app/components/layout/joinNewsletterForm/index.tsx +72 -0
- package/src/app/components/layout/mobileAccordian/index.tsx +92 -0
- package/src/app/components/layout/paymentMethods.tsx +75 -0
- package/src/app/components/layout/rootLayout.tsx +23 -0
- package/src/app/components/layout/siteInfo.tsx +103 -0
- package/src/app/components/layout/socialLinks.tsx +65 -0
- package/src/app/components/newsletterSection/emailListSection.tsx +224 -0
- package/src/app/components/newsletterSection/emailSectionServer.tsx +8 -0
- package/src/app/components/providers/ApolloWrapper.tsx +12 -0
- package/src/app/components/providers/AppConfigurationProvider.tsx +108 -0
- package/src/app/components/providers/GoogleAnalyticsProvider.tsx +149 -0
- package/src/app/components/providers/GoogleTagManagerProvider.tsx +31 -0
- package/src/app/components/providers/RecaptchaProvider.tsx +18 -0
- package/src/app/components/providers/ServerAppConfigurationProvider.tsx +133 -0
- package/src/app/components/providers/YMMStatusProvider.tsx +15 -0
- package/src/app/components/reuseableUI/AboutUs.tsx +115 -0
- package/src/app/components/reuseableUI/AddToCartClient.tsx +125 -0
- package/src/app/components/reuseableUI/EditorJsRenderer.tsx +219 -0
- package/src/app/components/reuseableUI/HeroSectionsearchByVehicle.tsx +188 -0
- package/src/app/components/reuseableUI/ImageWithFallback.tsx +41 -0
- package/src/app/components/reuseableUI/Toast.tsx +101 -0
- package/src/app/components/reuseableUI/blogCard.tsx +52 -0
- package/src/app/components/reuseableUI/brandCard.tsx +68 -0
- package/src/app/components/reuseableUI/breadcrumb.tsx +38 -0
- package/src/app/components/reuseableUI/categoryCard.tsx +37 -0
- package/src/app/components/reuseableUI/categorySkeleton.tsx +31 -0
- package/src/app/components/reuseableUI/commonButton.tsx +48 -0
- package/src/app/components/reuseableUI/defaultInputField/index.tsx +84 -0
- package/src/app/components/reuseableUI/emptyState.tsx +29 -0
- package/src/app/components/reuseableUI/errorTag.tsx +15 -0
- package/src/app/components/reuseableUI/heading/index.tsx +20 -0
- package/src/app/components/reuseableUI/input.tsx +117 -0
- package/src/app/components/reuseableUI/listCard.tsx +137 -0
- package/src/app/components/reuseableUI/loadingUI.tsx +12 -0
- package/src/app/components/reuseableUI/modalLayout.tsx +76 -0
- package/src/app/components/reuseableUI/newsletter/newsletterClient.tsx +622 -0
- package/src/app/components/reuseableUI/newsletter/newslettersHomeModal.tsx +68 -0
- package/src/app/components/reuseableUI/offerCard.tsx +42 -0
- package/src/app/components/reuseableUI/passwordRules/passwordRules.tsx +56 -0
- package/src/app/components/reuseableUI/primaryButton/index.tsx +34 -0
- package/src/app/components/reuseableUI/productCard.tsx +118 -0
- package/src/app/components/reuseableUI/productSkeleton.tsx +34 -0
- package/src/app/components/reuseableUI/searchByVehicle.tsx +187 -0
- package/src/app/components/reuseableUI/secondaryButton/index.tsx +34 -0
- package/src/app/components/reuseableUI/section.tsx +20 -0
- package/src/app/components/reuseableUI/select/index.tsx +98 -0
- package/src/app/components/reuseableUI/skeletonLoader.tsx +117 -0
- package/src/app/components/reuseableUI/statusTag.tsx +24 -0
- package/src/app/components/reuseableUI/tags/saleTag.tsx +19 -0
- package/src/app/components/reuseableUI/testimonialCard.tsx +93 -0
- package/src/app/components/richText/EditorRenderer.tsx +318 -0
- package/src/app/components/search/HierarchicalCategoryFilter.tsx +155 -0
- package/src/app/components/search/SearchFilters.tsx +155 -0
- package/src/app/components/search/YMMSearchSidebar.tsx +187 -0
- package/src/app/components/seo/ServerProductCard.tsx +91 -0
- package/src/app/components/seo/ServerProductGrid.tsx +45 -0
- package/src/app/components/shop/CategoryFilter.tsx +184 -0
- package/src/app/components/shop/ItemsPerPageSelect.tsx +69 -0
- package/src/app/components/shop/ItemsPerPageSelectClient.tsx +58 -0
- package/src/app/components/shop/MobileFilters.tsx +103 -0
- package/src/app/components/shop/ProductGridSkeleton.tsx +16 -0
- package/src/app/components/shop/ProductsGrid.tsx +230 -0
- package/src/app/components/shop/SearchFilter.tsx +218 -0
- package/src/app/components/shop/SearchFilterClient.tsx +122 -0
- package/src/app/components/shop/SearchLoadingOverlay.tsx +32 -0
- package/src/app/components/shop/ShopMobileFilters.tsx +205 -0
- package/src/app/components/showroom/VehicleSearchDropdowns.tsx +187 -0
- package/src/app/components/showroom/brandsSwiper.tsx +49 -0
- package/src/app/components/showroom/brandsSwiperClient copy.tsx +93 -0
- package/src/app/components/showroom/brandsSwiperClient.tsx +122 -0
- package/src/app/components/showroom/brandsSwiperServer.tsx +42 -0
- package/src/app/components/showroom/bundleProducts.tsx +120 -0
- package/src/app/components/showroom/categoryGrid.tsx +51 -0
- package/src/app/components/showroom/categoryGridServer.tsx +45 -0
- package/src/app/components/showroom/categorySwiper.tsx +115 -0
- package/src/app/components/showroom/featureStrip.tsx +139 -0
- package/src/app/components/showroom/offersSwiper.tsx +181 -0
- package/src/app/components/showroom/productGrid.tsx +56 -0
- package/src/app/components/showroom/productSwiper.tsx +119 -0
- package/src/app/components/showroom/promotion-slider.tsx +138 -0
- package/src/app/components/showroom/promotion.tsx +207 -0
- package/src/app/components/showroom/promotionsSwiper.tsx +174 -0
- package/src/app/components/showroom/showroomHeroCarousel.tsx +141 -0
- package/src/app/components/showroom/testimonialsGrid.tsx +106 -0
- package/src/app/components/skeletons/ContentSkeleton.tsx +14 -0
- package/src/app/components/sortDropdown/index.tsx +116 -0
- package/src/app/components/tertiaryButton/index.tsx +25 -0
- package/src/app/components/theme/theme-provider.tsx +82 -0
- package/src/app/contact/layout.tsx +32 -0
- package/src/app/contact/page.tsx +591 -0
- package/src/app/content/[slug]/layout.tsx +17 -0
- package/src/app/content/[slug]/page.tsx +159 -0
- package/src/app/content/layout.tsx +31 -0
- package/src/app/content/page.tsx +88 -0
- package/src/app/core-policies/page.tsx +55 -0
- package/src/app/discounts/page.tsx +54 -0
- package/src/app/frequently-asked-questions/page.tsx +57 -0
- package/src/app/globals.css +440 -0
- package/src/app/hooks/useDealerLocations.ts +259 -0
- package/src/app/hooks/useGTMEngagement.ts +71 -0
- package/src/app/hooks/useGoogleAnalytics.ts +145 -0
- package/src/app/layout.tsx +149 -0
- package/src/app/not-found.tsx +31 -0
- package/src/app/order-confirmation/layout.tsx +19 -0
- package/src/app/order-confirmation/page.tsx +12 -0
- package/src/app/order-confirmation/summary.tsx +1775 -0
- package/src/app/page.tsx +194 -0
- package/src/app/privacy-policy/loading.tsx +17 -0
- package/src/app/privacy-policy/page.tsx +56 -0
- package/src/app/product/[id]/ProductDetailClient.tsx +2448 -0
- package/src/app/product/[id]/components/itemInquiryModal.tsx +461 -0
- package/src/app/product/[id]/layout.tsx +116 -0
- package/src/app/product/[id]/page.tsx +200 -0
- package/src/app/product/layout.tsx +15 -0
- package/src/app/products/all/AllProductsClient.tsx +743 -0
- package/src/app/products/all/page.tsx +176 -0
- package/src/app/products/components/shopEmptyState.tsx +29 -0
- package/src/app/request-return/layout.tsx +36 -0
- package/src/app/request-return/page.tsx +597 -0
- package/src/app/robots.txt/route.ts +27 -0
- package/src/app/search/layout.tsx +16 -0
- package/src/app/search/page.tsx +736 -0
- package/src/app/shipping-returns/page.tsx +60 -0
- package/src/app/site-map/layout.tsx +33 -0
- package/src/app/site-map/page.tsx +113 -0
- package/src/app/sitemap-index.xml/route.ts +20 -0
- package/src/app/sitemap.ts +10 -0
- package/src/app/terms-and-conditions/loading.tsx +17 -0
- package/src/app/terms-and-conditions/page.tsx +56 -0
- package/src/app/utils/appConfiguration.ts +327 -0
- package/src/app/utils/branding.ts +52 -0
- package/src/app/utils/configurationService.ts +202 -0
- package/src/app/utils/constant.tsx +242 -0
- package/src/app/utils/editorJsUtils.tsx +249 -0
- package/src/app/utils/functions.ts +146 -0
- package/src/app/utils/googleAnalytics.ts +168 -0
- package/src/app/utils/googleTagManager.ts +475 -0
- package/src/app/utils/ipDetection.ts +270 -0
- package/src/app/utils/serverConfigurationService.ts +209 -0
- package/src/app/utils/svgs/GridIcon.tsx +45 -0
- package/src/app/utils/svgs/account/myAccount/listDotIcon.tsx +3 -0
- package/src/app/utils/svgs/account/myAccount/tickIcon.tsx +10 -0
- package/src/app/utils/svgs/account/orderHistory/InfoIcon.tsx +49 -0
- package/src/app/utils/svgs/arrowDownIcon.tsx +17 -0
- package/src/app/utils/svgs/arrowIcon.tsx +25 -0
- package/src/app/utils/svgs/arrowUpIcon.tsx +16 -0
- package/src/app/utils/svgs/brandsSearchIcon.tsx +25 -0
- package/src/app/utils/svgs/cart/cartIcon.tsx +31 -0
- package/src/app/utils/svgs/cart/plusIcon.tsx +13 -0
- package/src/app/utils/svgs/cart/subtractIcon.tsx +13 -0
- package/src/app/utils/svgs/cart/successTickIcon.tsx +14 -0
- package/src/app/utils/svgs/chevronDownIcon.tsx +21 -0
- package/src/app/utils/svgs/closeEyeIcon.tsx +47 -0
- package/src/app/utils/svgs/crossIcon.tsx +25 -0
- package/src/app/utils/svgs/eyeIcon.tsx +29 -0
- package/src/app/utils/svgs/featureTag.tsx +20 -0
- package/src/app/utils/svgs/filterIcon.tsx +3 -0
- package/src/app/utils/svgs/globleIcon.tsx +41 -0
- package/src/app/utils/svgs/infoIcon.tsx +34 -0
- package/src/app/utils/svgs/listIcon.tsx +50 -0
- package/src/app/utils/svgs/logOutIcon.tsx +35 -0
- package/src/app/utils/svgs/menuIcon.tsx +8 -0
- package/src/app/utils/svgs/minusIcon.tsx +18 -0
- package/src/app/utils/svgs/newsletterIcon.tsx +19 -0
- package/src/app/utils/svgs/noDataFoundIcon-.tsx +26 -0
- package/src/app/utils/svgs/noProductFoundIcon.tsx +43 -0
- package/src/app/utils/svgs/passwordIcons/errorIcon.tsx +31 -0
- package/src/app/utils/svgs/passwordIcons/successIcon.tsx +24 -0
- package/src/app/utils/svgs/paymentProcessingIcons/hourglassIcon.tsx +43 -0
- package/src/app/utils/svgs/paymentProcessingIcons/modalCrossIcon.tsx +23 -0
- package/src/app/utils/svgs/paymentProcessingIcons/paymentFailedIcon.tsx +47 -0
- package/src/app/utils/svgs/pencilIcon.tsx +11 -0
- package/src/app/utils/svgs/plusIcon.tsx +25 -0
- package/src/app/utils/svgs/productInquiryIcon.tsx +40 -0
- package/src/app/utils/svgs/searchIcon.tsx +31 -0
- package/src/app/utils/svgs/shoppingCart.tsx +32 -0
- package/src/app/utils/svgs/spinnerIcon.tsx +22 -0
- package/src/app/utils/svgs/spinnerLoadingIcon.tsx +26 -0
- package/src/app/utils/svgs/successTickIcon.tsx +40 -0
- package/src/app/utils/svgs/swiperArrowIconLeft.tsx +18 -0
- package/src/app/utils/svgs/swiperArrowIconRight.tsx +18 -0
- package/src/app/utils/svgs/userProfileIcon.tsx +31 -0
- package/src/app/utils/svgs/warningCircleIcon.tsx +15 -0
- package/src/app/warranty/constant.tsx +63 -0
- package/src/app/warranty/loading.tsx +17 -0
- package/src/app/warranty/page.tsx +56 -0
- package/src/graphql/client.ts +288 -0
- package/src/graphql/mutations/accountAddressCreate.ts +56 -0
- package/src/graphql/mutations/accountAddressDelete.ts +23 -0
- package/src/graphql/mutations/accountAddressUpdate.ts +55 -0
- package/src/graphql/mutations/accountSetDefaultAddress.ts +32 -0
- package/src/graphql/mutations/accountUpdate.ts +34 -0
- package/src/graphql/mutations/changePassword.ts +25 -0
- package/src/graphql/mutations/checkout.ts +117 -0
- package/src/graphql/mutations/checkoutAddVoucher.ts +63 -0
- package/src/graphql/mutations/checkoutComplete.ts +79 -0
- package/src/graphql/mutations/checkoutCreate.ts +131 -0
- package/src/graphql/mutations/checkoutCustomerAttach.ts +50 -0
- package/src/graphql/mutations/checkoutEmailUpdate.ts +15 -0
- package/src/graphql/mutations/checkoutLineMetadataUpdate.ts +52 -0
- package/src/graphql/mutations/checkoutPaymentCreate.ts +82 -0
- package/src/graphql/mutations/paymentGatewayInitialize.ts +58 -0
- package/src/graphql/mutations/registerAccount.ts +65 -0
- package/src/graphql/mutations/requestPasswordReset.ts +32 -0
- package/src/graphql/mutations/setPassword.ts +49 -0
- package/src/graphql/mutations/signIn.ts +50 -0
- package/src/graphql/mutations/tokenRefresh.ts +19 -0
- package/src/graphql/mutations/updateCheckoutMetadata.ts +49 -0
- package/src/graphql/mutations/updateProfile.ts +18 -0
- package/src/graphql/mutations/willCallDeliveryMethod.ts +81 -0
- package/src/graphql/queries/checkout.ts +168 -0
- package/src/graphql/queries/findProductByOldSlug.ts +58 -0
- package/src/graphql/queries/getAboutPage.ts +24 -0
- package/src/graphql/queries/getAboutPageId.ts +9 -0
- package/src/graphql/queries/getAboutUs.ts +38 -0
- package/src/graphql/queries/getAddressInformation.ts +38 -0
- package/src/graphql/queries/getAllCategories.ts +41 -0
- package/src/graphql/queries/getAllCategoriesTree.ts +67 -0
- package/src/graphql/queries/getAllCategoriesWithProducts.ts +29 -0
- package/src/graphql/queries/getAllCollectionsWithProducts.ts +16 -0
- package/src/graphql/queries/getBlogs.ts +222 -0
- package/src/graphql/queries/getBrands.ts +17 -0
- package/src/graphql/queries/getBundles.ts +43 -0
- package/src/graphql/queries/getCategories.ts +20 -0
- package/src/graphql/queries/getChannels.ts +77 -0
- package/src/graphql/queries/getCheckoutQuestions.ts +115 -0
- package/src/graphql/queries/getCheckoutTermsAndConditions.ts +37 -0
- package/src/graphql/queries/getContactPage.ts +117 -0
- package/src/graphql/queries/getContentPage.ts +191 -0
- package/src/graphql/queries/getDiscountOffers.ts +18 -0
- package/src/graphql/queries/getDynamicPageBySlug.ts +251 -0
- package/src/graphql/queries/getFeaturedProducts.ts +48 -0
- package/src/graphql/queries/getHeroMetadata.ts +23 -0
- package/src/graphql/queries/getMenuBySlug.ts +84 -0
- package/src/graphql/queries/getMyProfile.ts +23 -0
- package/src/graphql/queries/getNewsletter.ts +122 -0
- package/src/graphql/queries/getNewsletterPage.ts +111 -0
- package/src/graphql/queries/getPageBySlug.ts +52 -0
- package/src/graphql/queries/getPageTypeId.ts +27 -0
- package/src/graphql/queries/getPaymentMethods.ts +61 -0
- package/src/graphql/queries/getProducts.ts +78 -0
- package/src/graphql/queries/getPromotions.ts +24 -0
- package/src/graphql/queries/getRequestReturnPage.ts +121 -0
- package/src/graphql/queries/getSiteInfo.ts +54 -0
- package/src/graphql/queries/getSocialLinks.ts +52 -0
- package/src/graphql/queries/getTestimonials.ts +25 -0
- package/src/graphql/queries/getUserWithCheckout.ts +27 -0
- package/src/graphql/queries/getVehicleMakes.ts +21 -0
- package/src/graphql/queries/getVehicleModels.ts +21 -0
- package/src/graphql/queries/getVehicleYears.ts +21 -0
- package/src/graphql/queries/meAddresses.ts +56 -0
- package/src/graphql/queries/myOrders.ts +37 -0
- package/src/graphql/queries/orderDetail.ts +231 -0
- package/src/graphql/queries/productDetailsById.ts +197 -0
- package/src/graphql/queries/productInquiry.ts +115 -0
- package/src/graphql/queries/productsByCategoriesAndCollections.ts +39 -0
- package/src/graphql/queries/willCallCollectionPoints.ts +55 -0
- package/src/graphql/server-client.ts +54 -0
- package/src/graphql/types/categories.ts +9 -0
- package/src/graphql/types/checkout.ts +168 -0
- package/src/graphql/types/offer.ts +12 -0
- package/src/graphql/types/product.ts +44 -0
- package/src/hooks/scrollPageTop.ts +9 -0
- package/src/hooks/serverNavbarData.ts +79 -0
- package/src/hooks/useCartSync.ts +24 -0
- package/src/hooks/useRecaptcha.ts +33 -0
- package/src/hooks/useTokenExpiration.ts +81 -0
- package/src/hooks/useVehicleData.ts +346 -0
- package/src/lib/api/kount.ts +165 -0
- package/src/lib/api/shop.ts +1445 -0
- package/src/lib/saleor/getSaleorApiUrl.ts +25 -0
- package/src/lib/schema.ts +303 -0
- package/src/lib/seo/extractTextFromEditorJs.ts +58 -0
- package/src/lib/seo/site.ts +10 -0
- package/src/lib/urls/normalizeInternalUrl.ts +53 -0
- package/src/middleware.ts +134 -0
- package/src/sitemaps/README.md +105 -0
- package/src/sitemaps/dynamic-pages-sitemap.ts +247 -0
- package/src/sitemaps/sitemap-index.ts +21 -0
- package/src/sitemaps/static-pages-sitemap.ts +36 -0
- package/src/store/useGlobalStore.tsx +1656 -0
- package/src/types/global.d.ts +148 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Suspense } from "react";
|
|
2
|
+
import type { Metadata } from "next";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import AncillaryContent from "@/app/components/ancillary/AncillaryContent";
|
|
5
|
+
import ContentSkeleton from "@/app/components/skeletons/ContentSkeleton";
|
|
6
|
+
import { getStoreName } from "@/app/utils/branding";
|
|
7
|
+
import Breadcrumb from "../components/reuseableUI/breadcrumb";
|
|
8
|
+
import Heading from "../components/reuseableUI/heading";
|
|
9
|
+
|
|
10
|
+
export const metadata: Metadata = {
|
|
11
|
+
title: `Shipping & Returns - ${getStoreName()}`,
|
|
12
|
+
description:
|
|
13
|
+
"Learn about our shipping options, delivery times, return policy, and how to process returns for your products.",
|
|
14
|
+
alternates: {
|
|
15
|
+
canonical: "/shipping-returns",
|
|
16
|
+
},
|
|
17
|
+
openGraph: {
|
|
18
|
+
title: `Shipping & Returns - ${getStoreName()}`,
|
|
19
|
+
description:
|
|
20
|
+
"Learn about our shipping options, delivery times, return policy, and how to process returns for your products.",
|
|
21
|
+
type: "website",
|
|
22
|
+
url: "/shipping-returns",
|
|
23
|
+
images: [{ url: "/Logo.png" }],
|
|
24
|
+
},
|
|
25
|
+
twitter: {
|
|
26
|
+
card: "summary_large_image",
|
|
27
|
+
title: `Shipping & Returns - ${getStoreName()}`,
|
|
28
|
+
description:
|
|
29
|
+
"Learn about our shipping options, delivery times, return policy, and how to process returns for your products.",
|
|
30
|
+
images: ["/Logo.png"],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default function ShippingAndReturnsPage() {
|
|
35
|
+
const pageTitle = "Shipping & Returns";
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<main className="h-full w-full">
|
|
39
|
+
<div className="container mx-auto max-w-[1276px]">
|
|
40
|
+
<div className="flex flex-col items-start w-full px-4 md:px-6 py-12 md:py-16 lg:py-24">
|
|
41
|
+
<div className="flex flex-col items-start gap-5 mb-6 w-full">
|
|
42
|
+
<Breadcrumb
|
|
43
|
+
items={[
|
|
44
|
+
{ text: "Home", link: "/" },
|
|
45
|
+
{ text: "Shipping & Returns", link: "/shipping-returns" },
|
|
46
|
+
]}
|
|
47
|
+
/>
|
|
48
|
+
<Heading as="h1" content={pageTitle} />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<section className="w-full">
|
|
52
|
+
<Suspense fallback={<ContentSkeleton />}>
|
|
53
|
+
<AncillaryContent slug="shipping-returns" />
|
|
54
|
+
</Suspense>
|
|
55
|
+
</section>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</main>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { getStoreName } from "@/app/utils/branding";
|
|
3
|
+
|
|
4
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
5
|
+
return {
|
|
6
|
+
title: `Site Map - ${getStoreName()}`,
|
|
7
|
+
description: `Explore the comprehensive site map of ${getStoreName()} to easily navigate through all sections and find what you're looking for quickly.`,
|
|
8
|
+
alternates: {
|
|
9
|
+
canonical: "/site-map",
|
|
10
|
+
},
|
|
11
|
+
openGraph: {
|
|
12
|
+
title: `Site Map - ${getStoreName()}`,
|
|
13
|
+
description: `Explore the comprehensive site map of ${getStoreName()} to easily navigate through all sections and find what you're looking for quickly.`,
|
|
14
|
+
type: "website",
|
|
15
|
+
url: "/site-map",
|
|
16
|
+
images: [{ url: "/Logo.png" }],
|
|
17
|
+
},
|
|
18
|
+
twitter: {
|
|
19
|
+
card: "summary_large_image",
|
|
20
|
+
title: `Site Map - ${getStoreName()}`,
|
|
21
|
+
description: `Explore the comprehensive site map of ${getStoreName()} to easily navigate through all sections and find what you're looking for quickly.`,
|
|
22
|
+
images: ["/Logo.png"],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function SiteMapPostLayout({
|
|
28
|
+
children,
|
|
29
|
+
}: {
|
|
30
|
+
children: React.ReactNode;
|
|
31
|
+
}) {
|
|
32
|
+
return children;
|
|
33
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Metadata } from "next";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import Breadcrumb from "@/app/components/reuseableUI/breadcrumb";
|
|
5
|
+
import Heading from "@/app/components/reuseableUI/heading";
|
|
6
|
+
import EditorRenderer from "@/app/components/richText/EditorRenderer";
|
|
7
|
+
import { fetchBlogBySlug } from "@/graphql/queries/getBlogs";
|
|
8
|
+
import { getStoreName } from "@/app/utils/branding";
|
|
9
|
+
import {
|
|
10
|
+
generateBlogPostingSchema,
|
|
11
|
+
generateBreadcrumbSchema,
|
|
12
|
+
} from "@/lib/schema";
|
|
13
|
+
|
|
14
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
15
|
+
const post = await fetchBlogBySlug("site-map");
|
|
16
|
+
|
|
17
|
+
if (!post || !post.title) {
|
|
18
|
+
return {
|
|
19
|
+
title: `Site Map - ${getStoreName()}`,
|
|
20
|
+
description: `Explore the comprehensive site map of ${getStoreName()} to easily navigate through all sections and find what you're looking for quickly.`,
|
|
21
|
+
alternates: {
|
|
22
|
+
canonical: "/site-map",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
title: `${post.title} - ${getStoreName()}`,
|
|
29
|
+
description:
|
|
30
|
+
post.title ||
|
|
31
|
+
`Explore the comprehensive site map of ${getStoreName()} to easily navigate through all sections and find what you're looking for quickly.`,
|
|
32
|
+
alternates: {
|
|
33
|
+
canonical: "/site-map",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default async function SiteMapPage() {
|
|
39
|
+
const post = await fetchBlogBySlug("site-map");
|
|
40
|
+
|
|
41
|
+
if (!post || !post.title) {
|
|
42
|
+
return (
|
|
43
|
+
<main className="container mx-auto px-4 md:px-6 py-12 text-center">
|
|
44
|
+
<h1 className="text-3xl font-bold mb-4">Content Not Found</h1>
|
|
45
|
+
<p className="mb-6">The requested site map could not be found.</p>
|
|
46
|
+
<Link
|
|
47
|
+
href="/"
|
|
48
|
+
className="text-[var(--color-primary-600)] hover:underline"
|
|
49
|
+
>
|
|
50
|
+
← Back to Home
|
|
51
|
+
</Link>
|
|
52
|
+
</main>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Generate schema.org structured data
|
|
57
|
+
const blogSchema = generateBlogPostingSchema(
|
|
58
|
+
post.title,
|
|
59
|
+
post.title,
|
|
60
|
+
"/site-map",
|
|
61
|
+
post.created || new Date().toISOString(),
|
|
62
|
+
post.created || new Date().toISOString()
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const breadcrumbSchema = generateBreadcrumbSchema([
|
|
66
|
+
{ name: "Home", url: "/" },
|
|
67
|
+
{ name: post.title, url: "/site-map" },
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<main className="h-full w-full">
|
|
72
|
+
{/* Schema.org structured data */}
|
|
73
|
+
<script
|
|
74
|
+
type="application/ld+json"
|
|
75
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(blogSchema) }}
|
|
76
|
+
/>
|
|
77
|
+
<script
|
|
78
|
+
type="application/ld+json"
|
|
79
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
|
|
80
|
+
/>
|
|
81
|
+
<div className="container mx-auto max-w-[1276px]">
|
|
82
|
+
<div className="flex flex-col items-start w-full gap-8 px-4 md:px-6 py-12 md:py-16 lg:py-24">
|
|
83
|
+
<div className="flex flex-col items-start gap-5 w-full">
|
|
84
|
+
<Breadcrumb
|
|
85
|
+
items={[
|
|
86
|
+
{ text: "Home", link: "/" },
|
|
87
|
+
{ text: post.title, link: "/site-map" },
|
|
88
|
+
]}
|
|
89
|
+
/>
|
|
90
|
+
<Heading as="h1" content={post.title} />
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="w-full flex flex-col items-start gap-6">
|
|
94
|
+
{/* Site Map Content */}
|
|
95
|
+
<div className="w-full">
|
|
96
|
+
<EditorRenderer content={post.content} />
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{/* Back to Home Link */}
|
|
100
|
+
<div className="w-full pt-8 border-t border-[var(--color-secondary-200)]">
|
|
101
|
+
<Link
|
|
102
|
+
href="/"
|
|
103
|
+
className="text-[var(--color-primary-600)] hover:underline inline-flex items-center gap-2"
|
|
104
|
+
>
|
|
105
|
+
← Back to Home
|
|
106
|
+
</Link>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</main>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
export async function GET() {
|
|
4
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'
|
|
5
|
+
|
|
6
|
+
const sitemapIndex = `<?xml version="1.0" encoding="UTF-8"?>
|
|
7
|
+
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
8
|
+
<sitemap>
|
|
9
|
+
<loc>${baseUrl}/sitemap.xml</loc>
|
|
10
|
+
<lastmod>${new Date().toISOString()}</lastmod>
|
|
11
|
+
</sitemap>
|
|
12
|
+
</sitemapindex>`
|
|
13
|
+
|
|
14
|
+
return new NextResponse(sitemapIndex, {
|
|
15
|
+
headers: {
|
|
16
|
+
'Content-Type': 'application/xml',
|
|
17
|
+
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MetadataRoute } from 'next'
|
|
2
|
+
import { generateStaticPagesSitemap } from '../sitemaps/static-pages-sitemap'
|
|
3
|
+
import { generateDynamicPagesSitemap } from '../sitemaps/dynamic-pages-sitemap'
|
|
4
|
+
|
|
5
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
6
|
+
const staticPages = generateStaticPagesSitemap()
|
|
7
|
+
const dynamicPages = await generateDynamicPagesSitemap()
|
|
8
|
+
|
|
9
|
+
return [...staticPages, ...dynamicPages]
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ContentSkeleton from "@/app/components/skeletons/ContentSkeleton"
|
|
2
|
+
|
|
3
|
+
export default function Loading() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="h-full w-full">
|
|
6
|
+
<div className="container mx-auto max-w-[1276px]">
|
|
7
|
+
<div className="flex flex-col items-start w-full py-24">
|
|
8
|
+
<div className="flex flex-col items-start gap-5 mb-6 w-full">
|
|
9
|
+
<div className="h-5 w-24 bg-[var(--color-secondary-800)]/30 rounded" />
|
|
10
|
+
<div className="h-9 w-64 bg-[var(--color-secondary-800)]/30 rounded" />
|
|
11
|
+
</div>
|
|
12
|
+
<ContentSkeleton />
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</main>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import AncillaryContent from "@/app/components/ancillary/AncillaryContent";
|
|
2
|
+
import ContentSkeleton from "@/app/components/skeletons/ContentSkeleton";
|
|
3
|
+
import { getStoreName } from "@/app/utils/branding";
|
|
4
|
+
import type { Metadata } from "next";
|
|
5
|
+
import { Suspense } from "react";
|
|
6
|
+
import Breadcrumb from "../components/reuseableUI/breadcrumb";
|
|
7
|
+
import Heading from "../components/reuseableUI/heading";
|
|
8
|
+
|
|
9
|
+
export const metadata: Metadata = {
|
|
10
|
+
title: `Terms & Conditions - ${getStoreName()}`,
|
|
11
|
+
description: `Read our terms and conditions for purchasing products and using ${getStoreName()} services.`,
|
|
12
|
+
alternates: {
|
|
13
|
+
canonical: "/terms-and-conditions",
|
|
14
|
+
},
|
|
15
|
+
openGraph: {
|
|
16
|
+
title: `Terms & Conditions - ${getStoreName()}`,
|
|
17
|
+
description: `Read our terms and conditions for purchasing products and using ${getStoreName()} services.`,
|
|
18
|
+
type: "website",
|
|
19
|
+
url: "/terms-and-conditions",
|
|
20
|
+
images: [{ url: "/Logo.png" }],
|
|
21
|
+
},
|
|
22
|
+
twitter: {
|
|
23
|
+
card: "summary_large_image",
|
|
24
|
+
title: `Terms & Conditions - ${getStoreName()}`,
|
|
25
|
+
description: `Read our terms and conditions for purchasing products and using ${getStoreName()} services.`,
|
|
26
|
+
images: ["/Logo.png"],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default function TermsAndConditionsPage() {
|
|
31
|
+
const derivedTitle = "Terms & Conditions";
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<main className="h-full w-full">
|
|
35
|
+
<div className="container mx-auto max-w-[1276px]">
|
|
36
|
+
<div className="flex flex-col items-start w-full px-4 md:px-6 py-12 md:py-16 lg:py-24">
|
|
37
|
+
<div className="flex flex-col items-start gap-5 mb-6 w-full">
|
|
38
|
+
<Breadcrumb
|
|
39
|
+
items={[
|
|
40
|
+
{ text: "Home", link: "/" },
|
|
41
|
+
{ text: "Terms & Conditions", link: "/terms-and-conditions" },
|
|
42
|
+
]}
|
|
43
|
+
/>
|
|
44
|
+
<Heading as="h1" content="Terms & Conditions" />
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<section className="w-full">
|
|
48
|
+
<Suspense fallback={<ContentSkeleton />}>
|
|
49
|
+
<AncillaryContent slug="terms-and-conditions" />
|
|
50
|
+
</Suspense>
|
|
51
|
+
</section>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</main>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
export interface GoogleRecaptchaConfig {
|
|
2
|
+
site_key: string;
|
|
3
|
+
locations?: {
|
|
4
|
+
login?: boolean;
|
|
5
|
+
signup?: boolean;
|
|
6
|
+
checkout?: boolean;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface GoogleTagManagerConfig {
|
|
11
|
+
container_id: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface GoogleMapsConfig {
|
|
15
|
+
api_key: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GoogleAnalyticsConfig {
|
|
19
|
+
measurement_id: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GoogleAdSenseConfig {
|
|
23
|
+
publisher_id: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GoogleSearchConsoleConfig {
|
|
27
|
+
verification_meta_tag: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DealerLocatorConfig {
|
|
31
|
+
token: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WillCallConfig {
|
|
35
|
+
// Will call doesn't need any configurations for now, but we maintain this for consistency
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AppConfig {
|
|
40
|
+
app_name: string; // Allow any app name since API can return unknown apps
|
|
41
|
+
configurations?: DealerLocatorConfig | GoogleRecaptchaConfig | GoogleTagManagerConfig | GoogleMapsConfig | GoogleAnalyticsConfig | GoogleAdSenseConfig | GoogleSearchConsoleConfig | WillCallConfig | Record<string, unknown>;
|
|
42
|
+
is_active: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ExternalAppConfig {
|
|
46
|
+
app_name: 'dealer_locator' | 'google_recaptcha' | 'google_tag_manager' | 'google_maps' | 'google_analytics' | 'google_adsense' | 'google_search_console' | 'will-call';
|
|
47
|
+
configurations: DealerLocatorConfig | GoogleRecaptchaConfig | GoogleTagManagerConfig | GoogleMapsConfig | GoogleAnalyticsConfig | GoogleAdSenseConfig | GoogleSearchConsoleConfig | WillCallConfig;
|
|
48
|
+
is_active: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface InternalAppConfig {
|
|
52
|
+
app_name: 'tiered_pricing';
|
|
53
|
+
is_active: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface AppConfigurationResponse {
|
|
57
|
+
internal?: InternalAppConfig[];
|
|
58
|
+
external?: ExternalAppConfig[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class AppConfigurationService {
|
|
62
|
+
private configuration: AppConfigurationResponse | null = null;
|
|
63
|
+
private loading = false;
|
|
64
|
+
private error: string | null = null;
|
|
65
|
+
private lastFetch: number = 0;
|
|
66
|
+
private cacheTimeout: number = 10000; // 10 seconds cache timeout
|
|
67
|
+
|
|
68
|
+
async fetchConfiguration(): Promise<AppConfigurationResponse> {
|
|
69
|
+
if (this.configuration && !this.shouldRefetch()) {
|
|
70
|
+
return this.configuration;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (this.loading) {
|
|
74
|
+
while (this.loading) {
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
76
|
+
}
|
|
77
|
+
return this.configuration!;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
this.loading = true;
|
|
82
|
+
this.error = null;
|
|
83
|
+
|
|
84
|
+
// Use the API proxy route instead of direct external API call
|
|
85
|
+
const configUrl = '/api/configuration';
|
|
86
|
+
|
|
87
|
+
const response = await fetch(configUrl, {
|
|
88
|
+
method: 'GET',
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new Error(`Failed to fetch configuration: ${response.status}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const rawData = await response.json();
|
|
99
|
+
|
|
100
|
+
// Handle flat array format or empty array
|
|
101
|
+
let data: AppConfigurationResponse;
|
|
102
|
+
if (rawData.length === 0) {
|
|
103
|
+
data = this.getFallbackConfiguration();
|
|
104
|
+
} else {
|
|
105
|
+
data = this.transformFlatArrayToStructured(rawData);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.configuration = data;
|
|
109
|
+
this.lastFetch = Date.now();
|
|
110
|
+
|
|
111
|
+
return data;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
this.error = error instanceof Error ? error.message : 'Unknown error';
|
|
114
|
+
console.error('Failed to fetch app configuration:', error);
|
|
115
|
+
|
|
116
|
+
return this.getFallbackConfiguration();
|
|
117
|
+
} finally {
|
|
118
|
+
this.loading = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private shouldRefetch(): boolean {
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
return (now - this.lastFetch) > this.cacheTimeout;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private getFallbackConfiguration(): AppConfigurationResponse {
|
|
128
|
+
// Return minimal fallback - all features disabled when API fails
|
|
129
|
+
return {
|
|
130
|
+
internal: [
|
|
131
|
+
{
|
|
132
|
+
app_name: 'tiered_pricing',
|
|
133
|
+
is_active: false
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
external: [
|
|
137
|
+
{
|
|
138
|
+
app_name: 'dealer_locator',
|
|
139
|
+
configurations: {
|
|
140
|
+
token: ''
|
|
141
|
+
},
|
|
142
|
+
is_active: false
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
app_name: 'google_recaptcha',
|
|
146
|
+
configurations: {
|
|
147
|
+
site_key: '',
|
|
148
|
+
locations: {
|
|
149
|
+
login: false,
|
|
150
|
+
signup: false,
|
|
151
|
+
checkout: false
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
is_active: false
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
app_name: 'google_tag_manager',
|
|
158
|
+
configurations: {
|
|
159
|
+
container_id: ''
|
|
160
|
+
},
|
|
161
|
+
is_active: false
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
app_name: 'google_maps',
|
|
165
|
+
configurations: {
|
|
166
|
+
api_key: ''
|
|
167
|
+
},
|
|
168
|
+
is_active: false
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
app_name: 'google_analytics',
|
|
172
|
+
configurations: {
|
|
173
|
+
measurement_id: ''
|
|
174
|
+
},
|
|
175
|
+
is_active: false
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
app_name: 'google_adsense',
|
|
179
|
+
configurations: {
|
|
180
|
+
publisher_id: ''
|
|
181
|
+
},
|
|
182
|
+
is_active: false
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
app_name: 'google_search_console',
|
|
186
|
+
configurations: {
|
|
187
|
+
verification_meta_tag: ''
|
|
188
|
+
},
|
|
189
|
+
is_active: false
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private transformFlatArrayToStructured(flatArray: AppConfig[]): AppConfigurationResponse {
|
|
196
|
+
const internalApps: InternalAppConfig[] = [];
|
|
197
|
+
const externalApps: ExternalAppConfig[] = [];
|
|
198
|
+
|
|
199
|
+
flatArray.forEach(app => {
|
|
200
|
+
// Only apps without configurations go to internal
|
|
201
|
+
if (!app.configurations && app.app_name === 'tiered_pricing') {
|
|
202
|
+
internalApps.push({
|
|
203
|
+
app_name: app.app_name as 'tiered_pricing',
|
|
204
|
+
is_active: app.is_active
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
// Apps with configurations go to external (including dealer_locator)
|
|
208
|
+
const knownExternalApps = [
|
|
209
|
+
'dealer_locator', 'google_recaptcha', 'google_tag_manager',
|
|
210
|
+
'google_maps', 'google_analytics', 'google_adsense', 'google_search_console', 'will-call'
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
if (knownExternalApps.includes(app.app_name)) {
|
|
214
|
+
externalApps.push({
|
|
215
|
+
app_name: app.app_name as 'dealer_locator' | 'google_recaptcha' | 'google_tag_manager' | 'google_maps' | 'google_analytics' | 'google_adsense' | 'google_search_console' | 'will-call',
|
|
216
|
+
configurations: app.configurations || {},
|
|
217
|
+
is_active: app.is_active
|
|
218
|
+
} as ExternalAppConfig);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
internal: internalApps,
|
|
225
|
+
external: externalApps
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
getExternalConfig<T>(appName: string): T | null {
|
|
230
|
+
if (!this.configuration) return null;
|
|
231
|
+
|
|
232
|
+
const app = this.configuration.external?.find(app => app.app_name === appName);
|
|
233
|
+
return app?.is_active ? (app.configurations as T) : null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getInternalConfig(appName: string): boolean {
|
|
237
|
+
if (!this.configuration) return false;
|
|
238
|
+
|
|
239
|
+
const app = this.configuration.internal?.find(app => app.app_name === appName);
|
|
240
|
+
return app?.is_active ?? false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
isAppActive(appName: string): boolean {
|
|
244
|
+
if (!this.configuration) return false;
|
|
245
|
+
|
|
246
|
+
const externalApp = this.configuration.external?.find(app => app.app_name === appName);
|
|
247
|
+
if (externalApp) return externalApp.is_active;
|
|
248
|
+
|
|
249
|
+
const internalApp = this.configuration.internal?.find(app => app.app_name === appName);
|
|
250
|
+
return internalApp?.is_active ?? false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
getGoogleRecaptchaConfig(): GoogleRecaptchaConfig | null {
|
|
254
|
+
return this.getExternalConfig<GoogleRecaptchaConfig>('google_recaptcha');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
isRecaptchaEnabledFor(location: 'login' | 'signup' | 'checkout'): boolean {
|
|
258
|
+
if (!this.isAppActive('google_recaptcha')) return false;
|
|
259
|
+
|
|
260
|
+
const config = this.getGoogleRecaptchaConfig();
|
|
261
|
+
return config?.locations?.[location] ?? false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
getGoogleTagManagerConfig(): GoogleTagManagerConfig | null {
|
|
265
|
+
return this.getExternalConfig<GoogleTagManagerConfig>('google_tag_manager');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
getGoogleMapsConfig(): GoogleMapsConfig | null {
|
|
269
|
+
return this.getExternalConfig<GoogleMapsConfig>('google_maps');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
getGoogleAnalyticsConfig(): GoogleAnalyticsConfig | null {
|
|
273
|
+
return this.getExternalConfig<GoogleAnalyticsConfig>('google_analytics');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
getGoogleAdSenseConfig(): GoogleAdSenseConfig | null {
|
|
277
|
+
return this.getExternalConfig<GoogleAdSenseConfig>('google_adsense');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getGoogleSearchConsoleConfig(): GoogleSearchConsoleConfig | null {
|
|
281
|
+
return this.getExternalConfig<GoogleSearchConsoleConfig>('google_search_console');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
isDealerLocatorEnabled(): boolean {
|
|
285
|
+
return this.getInternalConfig('dealer_locator');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
isWillCallEnabled(): boolean {
|
|
289
|
+
return this.isAppActive('will-call');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
getLastError(): string | null {
|
|
293
|
+
return this.error;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Force refresh configuration (useful for development)
|
|
297
|
+
clearCache(): void {
|
|
298
|
+
this.configuration = null;
|
|
299
|
+
this.lastFetch = 0;
|
|
300
|
+
this.error = null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Reduce cache timeout for development
|
|
304
|
+
setCacheTimeout(timeoutMs: number): void {
|
|
305
|
+
this.cacheTimeout = timeoutMs;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export const appConfigService = new AppConfigurationService();
|
|
310
|
+
|
|
311
|
+
// Development helper: expose cache clearing functions to window for easy access
|
|
312
|
+
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
|
|
313
|
+
(window as Record<string, unknown>).clearAppConfigCache = () => {
|
|
314
|
+
appConfigService.clearCache();
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
(window as Record<string, unknown>).refreshAppConfig = async () => {
|
|
318
|
+
appConfigService.clearCache();
|
|
319
|
+
const config = await appConfigService.fetchConfiguration();
|
|
320
|
+
return config;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
(window as Record<string, unknown>).setAppConfigCacheTimeout = (timeoutMs: number) => {
|
|
324
|
+
appConfigService.setCacheTimeout(timeoutMs);
|
|
325
|
+
|
|
326
|
+
};
|
|
327
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function getTenantName() {
|
|
2
|
+
return process.env.NEXT_PUBLIC_TENANT_NAME || "default";
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function getStoreName() {
|
|
6
|
+
const tenantName = getTenantName();
|
|
7
|
+
return tenantName.charAt(0).toUpperCase() + tenantName.slice(1).replace(/-/g, ' ') + " Store";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getAssetsBaseUrl() {
|
|
11
|
+
// e.g. https://zgnluljrtbddtwugakuf.supabase.co
|
|
12
|
+
return process.env.NEXT_PUBLIC_ASSETS_BASE_URL || "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function joinUrl(...parts: string[]) {
|
|
16
|
+
return parts
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.map((p, i) => (i === 0 ? p.replace(/\/$/, "") : p.replace(/^\/+|\/+$/g, "")))
|
|
19
|
+
.join("/");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Helpers to sanitize env inputs
|
|
23
|
+
function pickFirstUrlToken(input?: string) {
|
|
24
|
+
if (!input) return undefined;
|
|
25
|
+
const trimmed = input.trim();
|
|
26
|
+
// If there are accidental extra tokens (e.g., ".../favicon.png favicon.png"), take the first token
|
|
27
|
+
const firstToken = trimmed.split(/\s+/)[0];
|
|
28
|
+
return firstToken.replace(/^\"|\"$/g, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function cleanPathToken(input?: string, fallback = "") {
|
|
32
|
+
const v = (input ?? fallback).trim();
|
|
33
|
+
return v.replace(/^\"|\"$/g, "");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getAppIconUrl() {
|
|
37
|
+
const explicit = pickFirstUrlToken(process.env.NEXT_PUBLIC_APP_ICON);
|
|
38
|
+
if (explicit && /^https?:\/\//i.test(explicit)) return explicit;
|
|
39
|
+
|
|
40
|
+
const base = getAssetsBaseUrl();
|
|
41
|
+
const tenant = getTenantName();
|
|
42
|
+
const file = cleanPathToken(process.env.NEXT_PUBLIC_APP_ICON_FILENAME, "favicon.ico");
|
|
43
|
+
const dir = cleanPathToken(process.env.NEXT_PUBLIC_APP_ICON_DIR, ""); // optional subfolder
|
|
44
|
+
if (!base) return explicit || "/icons/appIcon.png";
|
|
45
|
+
const pathParts = file.includes("/") ? [file] : (dir ? [dir, file] : [file]);
|
|
46
|
+
return joinUrl(
|
|
47
|
+
base,
|
|
48
|
+
"storage/v1/object/public/storefront-assets",
|
|
49
|
+
tenant,
|
|
50
|
+
...pathParts
|
|
51
|
+
);
|
|
52
|
+
}
|