├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── LICENSE.md ├── README.md ├── jest.config.js ├── package.json ├── packages ├── components │ ├── .eslintignore │ ├── .eslintrc.js │ ├── package.json │ ├── src │ │ ├── hooks │ │ │ ├── factory │ │ │ │ └── createHandlerSetter.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── safeHasOwnProperty.ts │ │ │ │ ├── swipeUtils.ts │ │ │ │ └── types.ts │ │ │ ├── useEvent.ts │ │ │ ├── useMouseEvents.ts │ │ │ ├── usePrice.ts │ │ │ ├── usePriceModifier.ts │ │ │ ├── useProductDetailsVariant.tsx │ │ │ ├── useProductTabs.ts │ │ │ ├── useSwipe.ts │ │ │ └── useTouchEvents.ts │ │ ├── index.ts │ │ └── products │ │ │ ├── ProductAttributes │ │ │ ├── ProductAttributeSelector.test.tsx │ │ │ ├── ProductAttributeSelector.tsx │ │ │ └── index.ts │ │ │ └── index.ts │ └── tsconfig.json └── storefront │ ├── package.json │ ├── scripts │ ├── generate.ts │ └── tsconfig.json │ ├── src │ ├── client.ts │ ├── gen │ │ ├── Client.ts │ │ └── openapi-types.ts │ ├── index.ts │ ├── request.ts │ ├── schema.ts │ ├── types.ts │ └── utils.ts │ └── tsconfig.json ├── templates ├── auto │ ├── .env │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .storybook │ │ ├── main.ts │ │ ├── manager.ts │ │ ├── platform.ts │ │ ├── preview.css │ │ └── preview.ts │ ├── app │ │ ├── (checkout) │ │ │ ├── checkout │ │ │ │ ├── (steps) │ │ │ │ │ ├── AddressBookDrawer.tsx │ │ │ │ │ ├── ApplyCoupon.tsx │ │ │ │ │ ├── CartItemsDrawer.tsx │ │ │ │ │ ├── CartProvider.tsx │ │ │ │ │ ├── CartState.tsx │ │ │ │ │ ├── EditAddressDrawer.tsx │ │ │ │ │ ├── Navigation.tsx │ │ │ │ │ ├── Summary.tsx │ │ │ │ │ ├── addresses │ │ │ │ │ │ ├── AddressForm.tsx │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── payment │ │ │ │ │ │ ├── PaymentMethodForm.tsx │ │ │ │ │ │ ├── ShippingMethod.tsx │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── widget.js │ │ │ │ │ └── shipping │ │ │ │ │ │ ├── CartAddresses.tsx │ │ │ │ │ │ ├── Form.tsx │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── actions.tsx │ │ │ │ ├── helpers.tsx │ │ │ │ ├── hooks.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── loading.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── store.tsx │ │ │ └── layout.tsx │ │ ├── (shop) │ │ │ ├── (product) │ │ │ │ └── [...slug] │ │ │ │ │ ├── ProductPage.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ ├── not-found.tsx │ │ │ │ │ └── page.tsx │ │ │ ├── bestsellers │ │ │ │ └── page.tsx │ │ │ ├── brands │ │ │ │ ├── [...slug] │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ │ ├── c │ │ │ │ └── [...slug] │ │ │ │ │ ├── loading.tsx │ │ │ │ │ ├── not-found.tsx │ │ │ │ │ └── page.tsx │ │ │ ├── checkout-success │ │ │ │ ├── SuccessPage.tsx │ │ │ │ └── page.tsx │ │ │ ├── error.tsx │ │ │ ├── layout.tsx │ │ │ ├── mmy │ │ │ │ └── [...slug] │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ ├── page.tsx │ │ │ ├── profile │ │ │ │ └── page.tsx │ │ │ ├── recent-arrivals │ │ │ │ └── page.tsx │ │ │ ├── sale │ │ │ │ └── page.tsx │ │ │ ├── search │ │ │ │ └── page.tsx │ │ │ ├── shop-by │ │ │ │ └── [[...slug]] │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ └── ~offline │ │ │ │ └── page.tsx │ │ ├── DefaultTags.tsx │ │ ├── api │ │ │ └── revalidate │ │ │ │ └── route.ts │ │ ├── client.ts │ │ ├── getQueryClient.tsx │ │ ├── hydrateOnClient.tsx │ │ ├── layout.tsx │ │ └── providers.tsx │ ├── components │ │ ├── auth │ │ │ ├── ForgotPassword.tsx │ │ │ ├── MyAccount.tsx │ │ │ ├── RememberMe.tsx │ │ │ ├── SignIn.tsx │ │ │ ├── SignUp.tsx │ │ │ └── Views │ │ │ │ ├── ForgotPasswordView.tsx │ │ │ │ ├── MyAccountView.tsx │ │ │ │ ├── SignInSignUpView.tsx │ │ │ │ ├── SignInView.tsx │ │ │ │ └── SignUpView.tsx │ │ ├── brands │ │ │ ├── AlphabeticalSearchByBrand.tsx │ │ │ └── BrandsTile.tsx │ │ ├── cart │ │ │ ├── AddToCartButton.tsx │ │ │ ├── Cart.tsx │ │ │ ├── CartDrawer.tsx │ │ │ ├── CartDrawerWrapper.tsx │ │ │ ├── CartItemImage.tsx │ │ │ ├── DeleteProductFromCart.tsx │ │ │ ├── functions │ │ │ │ ├── cartActions.ts │ │ │ │ ├── getInitCart.ts │ │ │ │ ├── getSavedCartFromCookies.ts │ │ │ │ ├── helpers.ts │ │ │ │ └── mergeAnonymousCart.ts │ │ │ ├── interface.ts │ │ │ └── store.ts │ │ ├── checkout │ │ │ ├── Header.tsx │ │ │ └── Notification.tsx │ │ ├── elements │ │ │ ├── AlphabeticalSearch.tsx │ │ │ ├── Badge.tsx │ │ │ ├── Button │ │ │ │ ├── Button.tsx │ │ │ │ ├── ButtonBase.tsx │ │ │ │ ├── ButtonIcon.tsx │ │ │ │ ├── ButtonWithSpinner.tsx │ │ │ │ └── index.ts │ │ │ ├── Checkbox.tsx │ │ │ ├── Drawer │ │ │ │ ├── BackToView.tsx │ │ │ │ ├── Drawer.tsx │ │ │ │ ├── DrawerPane.tsx │ │ │ │ ├── DrawerTitle.tsx │ │ │ │ ├── PaneTriggerButton.tsx │ │ │ │ ├── SecondaryAction.tsx │ │ │ │ └── index.ts │ │ │ ├── Grid.tsx │ │ │ ├── Icons.tsx │ │ │ ├── Input.tsx │ │ │ ├── Logo.tsx │ │ │ ├── PasswordInput.tsx │ │ │ ├── RadioGroup.tsx │ │ │ ├── SearchField.tsx │ │ │ ├── Section.tsx │ │ │ ├── Select │ │ │ │ ├── Select.tsx │ │ │ │ ├── SelectBase.tsx │ │ │ │ └── index.ts │ │ │ ├── SelectNative.tsx │ │ │ ├── Spinner.tsx │ │ │ └── Textarea.tsx │ │ ├── global │ │ │ ├── 404.tsx │ │ │ ├── Accordion │ │ │ │ ├── AccordionBase.tsx │ │ │ │ ├── AccordionMultiple.tsx │ │ │ │ ├── AccordionSingle.tsx │ │ │ │ └── index.ts │ │ │ ├── Alert.tsx │ │ │ ├── Categories.tsx │ │ │ ├── ColorModeToggle.tsx │ │ │ ├── DropdownMenu.tsx │ │ │ ├── Filters │ │ │ │ ├── Components │ │ │ │ │ ├── Filter.tsx │ │ │ │ │ ├── FilterTypes │ │ │ │ │ │ ├── Blocks.tsx │ │ │ │ │ │ ├── CategoriesRefinementList.tsx │ │ │ │ │ │ ├── ColorSwatches.tsx │ │ │ │ │ │ ├── PriceRangeInput.tsx │ │ │ │ │ │ └── RefinementList.tsx │ │ │ │ │ ├── Filters.tsx │ │ │ │ │ ├── Reset.tsx │ │ │ │ │ ├── ShowResultsButton.tsx │ │ │ │ │ └── Trigger.tsx │ │ │ │ ├── FiltersDrawerWrapper.tsx │ │ │ │ ├── FiltersPane.tsx │ │ │ │ ├── FiltersWrapper.tsx │ │ │ │ ├── api.ts │ │ │ │ ├── hooks │ │ │ │ │ ├── use-apply-filters.ts │ │ │ │ │ └── use-cloud-search.ts │ │ │ │ └── store.ts │ │ │ ├── Footer │ │ │ │ ├── Footer.tsx │ │ │ │ ├── FooterBottom.tsx │ │ │ │ ├── FooterMenuDesktop.tsx │ │ │ │ ├── FooterMenuMobile.tsx │ │ │ │ ├── FooterNewsletter.tsx │ │ │ │ └── index.ts │ │ │ ├── Header │ │ │ │ ├── Components │ │ │ │ │ ├── DesktopHeaderBase.tsx │ │ │ │ │ └── MobileHeaderBase.tsx │ │ │ │ ├── DesktopHeader.tsx │ │ │ │ ├── DesktopMyAccount.tsx │ │ │ │ ├── Header.tsx │ │ │ │ ├── Headers.tsx │ │ │ │ ├── MobileHeader.tsx │ │ │ │ ├── MobileMenu.tsx │ │ │ │ ├── Search.tsx │ │ │ │ └── index.ts │ │ │ ├── Modal.tsx │ │ │ ├── NavigationMenu.tsx │ │ │ ├── PageContent.tsx │ │ │ ├── PageSearchField.tsx │ │ │ ├── PageTitle.tsx │ │ │ ├── Pagination.tsx │ │ │ ├── Price.tsx │ │ │ ├── ScrollToTop.tsx │ │ │ ├── SortingSelector.tsx │ │ │ ├── Subcategories.tsx │ │ │ ├── Tabs.tsx │ │ │ ├── Tooltip.tsx │ │ │ ├── TopBanner.tsx │ │ │ └── skeleton │ │ │ │ ├── BrandItemSkeleton.tsx │ │ │ │ ├── BrandsPageSkeleton.tsx │ │ │ │ ├── BrandsTileSkeleton.tsx │ │ │ │ ├── MMYPageSkeleton.tsx │ │ │ │ ├── ProductDetailsSkeleton.tsx │ │ │ │ ├── ProductSkeleton.tsx │ │ │ │ ├── ProductsGridSkeleton.tsx │ │ │ │ ├── ProductsPageSkeleton.tsx │ │ │ │ ├── ShopByLevelItemSkeleton.tsx │ │ │ │ ├── ShopByLevelPageSkeleton.tsx │ │ │ │ └── ShopByLevelTileSkeleton.tsx │ │ ├── homepage │ │ │ ├── CenterBanners.tsx │ │ │ ├── MainBanner.tsx │ │ │ ├── SectionWithButton.tsx │ │ │ └── SubcategoriesTiles.tsx │ │ ├── mmy │ │ │ ├── MMYFilter │ │ │ │ ├── MMYFilter.tsx │ │ │ │ └── MMYFilterWrapper.tsx │ │ │ ├── MMYFilterComponents │ │ │ │ ├── LevelSelect.tsx │ │ │ │ └── MakeModelYear.tsx │ │ │ ├── VIN │ │ │ │ ├── FindByVINDrawer.tsx │ │ │ │ ├── FindByVINField.tsx │ │ │ │ ├── FindByVINFilter.tsx │ │ │ │ └── helpers.ts │ │ │ ├── VehiclePageTitle.tsx │ │ │ ├── functions │ │ │ │ ├── getInitGarage.ts │ │ │ │ ├── getInitMMY.ts │ │ │ │ ├── getMMYLevelsByPage.ts │ │ │ │ ├── getMMYPath.ts │ │ │ │ ├── getStoredSelectedVehicle.ts │ │ │ │ ├── getUpdatedSearchParams.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── interface.ts │ │ │ │ ├── mmyActions.ts │ │ │ │ ├── productTabsWithFitments.ts │ │ │ │ ├── useInitGarage.ts │ │ │ │ └── useInitMMY.ts │ │ │ ├── garage │ │ │ │ ├── DeleteVehicleFromGarage.tsx │ │ │ │ ├── FindByVIN.tsx │ │ │ │ ├── GarageDrawerTitle.tsx │ │ │ │ ├── MyGarage.tsx │ │ │ │ ├── MyGarageDrawer.tsx │ │ │ │ ├── MyGarageDrawerWrapper.tsx │ │ │ │ ├── SelectYourVehiclePane.tsx │ │ │ │ └── Views │ │ │ │ │ ├── FindByVINView.tsx │ │ │ │ │ ├── MyGarageView.tsx │ │ │ │ │ ├── SelectOrFindView.tsx │ │ │ │ │ └── SelectYourVehicleView.tsx │ │ │ └── store.ts │ │ ├── navigation │ │ │ └── link.tsx │ │ ├── product │ │ │ ├── AttributeCheckbox.tsx │ │ │ ├── AttributeTextarea │ │ │ │ ├── AttributeTextarea.tsx │ │ │ │ └── store.ts │ │ │ ├── AttributesBlocks.tsx │ │ │ ├── AttributesSelect.tsx │ │ │ ├── Carousel.tsx │ │ │ ├── DiscountedPrice.tsx │ │ │ ├── Fitment.tsx │ │ │ ├── Label.tsx │ │ │ ├── ProductAccordion.tsx │ │ │ ├── ProductAttributes.tsx │ │ │ ├── ProductBreadcrumbs.tsx │ │ │ ├── ProductBuyBox.tsx │ │ │ ├── ProductImagesDesktop.tsx │ │ │ ├── ProductImagesMobile.tsx │ │ │ ├── ProductTitle.tsx │ │ │ ├── RelatedProducts.tsx │ │ │ ├── interface.ts │ │ │ └── pagination.css │ │ ├── products │ │ │ ├── ProductCard.tsx │ │ │ ├── ProductCardGrid.tsx │ │ │ ├── ProductCardTitle.tsx │ │ │ ├── ProductImage.tsx │ │ │ ├── ProductPrice.tsx │ │ │ ├── ProductsGrid.tsx │ │ │ ├── ProductsList.tsx │ │ │ ├── ProductsListPaginated.tsx │ │ │ └── ProductsListWithLoader.tsx │ │ ├── profile │ │ │ └── ProfileDetails.tsx │ │ ├── providers │ │ │ ├── ColorModeContext.tsx │ │ │ └── ModalContext.tsx │ │ ├── shop-by │ │ │ ├── AlphabeticalSearchByLevel.tsx │ │ │ ├── ShopByTile.tsx │ │ │ └── functions │ │ │ │ ├── getLevelsFirstLetters.ts │ │ │ │ └── getShopByPaginatedLevelItems.ts │ │ ├── theme │ │ │ ├── InlineCssVariables.tsx │ │ │ ├── colors.ts │ │ │ └── tailwindMergeConfig.ts │ │ └── wishlist │ │ │ ├── Wishlist.tsx │ │ │ ├── WishlistButton.tsx │ │ │ ├── WishlistDrawer.tsx │ │ │ ├── WishlistDrawerWrapper.tsx │ │ │ ├── functions │ │ │ ├── helpers.ts │ │ │ └── wishlistActions.ts │ │ │ └── store.ts │ ├── lib │ │ └── getClient.ts │ ├── middleware.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ │ └── api │ │ │ └── auth │ │ │ └── [...nextauth].ts │ ├── postcss.config.js │ ├── public │ │ ├── 404.svg │ │ ├── arrowDown.svg │ │ ├── bestsellers.jpeg │ │ ├── favicon.ico │ │ ├── icon-192x192.png │ │ ├── icon-512x512.png │ │ ├── logo-mobile.svg │ │ ├── logo.svg │ │ ├── main_banner.jpeg │ │ ├── manifest.json │ │ ├── recent_arrivals.jpeg │ │ ├── robots.txt │ │ ├── storybook-product-image.jpg │ │ ├── storybook-product-image2.jpg │ │ ├── storybook-product-image3.jpg │ │ ├── stylish_banner1.jpeg │ │ ├── stylish_banner2.jpeg │ │ └── touch-icon-iphone.png │ ├── stories │ │ ├── Cart │ │ │ └── CartDrawer.stories.tsx │ │ ├── Categories.stories.tsx │ │ ├── Elements │ │ │ ├── Button.stories.tsx │ │ │ ├── ButtonIcon.stories.tsx │ │ │ ├── Input.stories.tsx │ │ │ ├── Logo.stories.tsx │ │ │ ├── Select.stories.tsx │ │ │ └── Spinner.stories.tsx │ │ ├── Footer │ │ │ ├── Footer.stories.tsx │ │ │ ├── FooterMenuDesktop.stories.tsx │ │ │ ├── FooterMenuMobile.stories.tsx │ │ │ └── NewsletterSubscription.stories.tsx │ │ ├── Garage │ │ │ └── MyGarageDrawer.stories.tsx │ │ ├── Header │ │ │ ├── DesktopHeader.stories.tsx │ │ │ ├── DesktopHeader.tsx │ │ │ ├── MobileHeader.stories.tsx │ │ │ ├── MobileHeader.tsx │ │ │ ├── MobileNav.stories.tsx │ │ │ ├── Search.stories.tsx │ │ │ └── Search.tsx │ │ ├── Homepage │ │ │ ├── CenterBanners.stories.tsx │ │ │ ├── MainBanner.stories.tsx │ │ │ ├── SectionWithButton.stories.tsx │ │ │ ├── SectionWithButton.tsx │ │ │ └── TabsMain.stories.tsx │ │ ├── Icons │ │ │ ├── IconArrow.stories.tsx │ │ │ ├── IconArrowBack.stories.tsx │ │ │ ├── IconArrowDown.stories.tsx │ │ │ ├── IconCar.stories.tsx │ │ │ ├── IconCart.stories.tsx │ │ │ ├── IconCheckMark.stories.tsx │ │ │ ├── IconClose.stories.tsx │ │ │ ├── IconCross.stories.tsx │ │ │ ├── IconDark.stories.tsx │ │ │ ├── IconEdit.stories.tsx │ │ │ ├── IconFilter.stories.tsx │ │ │ ├── IconHeart.stories.tsx │ │ │ ├── IconHeartFilled.stories.tsx │ │ │ ├── IconHidden.stories.tsx │ │ │ ├── IconImagePlaceholder.stories.tsx │ │ │ ├── IconInfo.stories.tsx │ │ │ ├── IconLight.stories.tsx │ │ │ ├── IconLoader.stories.tsx │ │ │ ├── IconMenu.stories.tsx │ │ │ ├── IconMinus.stories.tsx │ │ │ ├── IconNotFound.stories.tsx │ │ │ ├── IconPlus.stories.tsx │ │ │ ├── IconSearch.stories.tsx │ │ │ ├── IconSecureCheckout.stories.tsx │ │ │ ├── IconSuccess.stories.tsx │ │ │ ├── IconThinArrow.stories.tsx │ │ │ ├── IconTick.stories.tsx │ │ │ ├── IconTrash.stories.tsx │ │ │ ├── IconVisible.stories.tsx │ │ │ └── IconWarning.stories.tsx │ │ ├── Introduction.stories.mdx │ │ ├── MMY │ │ │ └── MMYFilter.stories.tsx │ │ ├── NotFound.stories.tsx │ │ ├── Product │ │ │ ├── DiscountedPrice.stories.tsx │ │ │ ├── ProductAccordion.stories.tsx │ │ │ ├── ProductAttributes.stories.tsx │ │ │ ├── ProductBreadcrumbs.stories.tsx │ │ │ ├── ProductBuyBox.stories.tsx │ │ │ ├── ProductImagesDesktop.stories.tsx │ │ │ ├── ProductImagesMobile.stories.tsx │ │ │ └── ProductTitle.stories.tsx │ │ ├── Products │ │ │ ├── ProductCardGrid.stories.tsx │ │ │ ├── ProductsGrid.stories.tsx │ │ │ └── ProductsListWithLoader.stories.tsx │ │ ├── Skeleton │ │ │ ├── ProductDetailsPageSkeleton.stories.tsx │ │ │ ├── ProductsGridSkeleton.stories.tsx │ │ │ ├── ProductsPageSkeleton.stories.tsx │ │ │ └── ProductsSkeleton.stories.tsx │ │ ├── Subcategories.stories.tsx │ │ ├── Tooltip.stories.tsx │ │ ├── TopBanner.stories.tsx │ │ ├── assets │ │ │ ├── code-brackets.svg │ │ │ ├── colors.svg │ │ │ ├── comments.svg │ │ │ ├── direction.svg │ │ │ ├── flow.svg │ │ │ ├── plugin.svg │ │ │ ├── repo.svg │ │ │ ├── stackalt.svg │ │ │ ├── storybook-product-image.jpeg │ │ │ ├── storybook-product-image2.jpeg │ │ │ └── storybook-product-image3.jpeg │ │ └── constants.ts │ ├── styles │ │ └── index.css │ ├── tailwind.config.js │ ├── tsconfig.json │ └── utils │ │ ├── cloud-search │ │ ├── helpers.ts │ │ └── search.ts │ │ ├── constants.ts │ │ ├── get-sorted-paginated-products.ts │ │ ├── helpers.ts │ │ └── unauthenticated-client.ts └── demo-platform │ ├── .env │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .storybook │ ├── main.ts │ ├── manager.ts │ ├── platform.ts │ ├── preview.css │ └── preview.ts │ ├── app │ ├── (checkout) │ │ ├── checkout │ │ │ ├── (steps) │ │ │ │ ├── AddressBookDrawer.tsx │ │ │ │ ├── ApplyCoupon.tsx │ │ │ │ ├── CartItemsDrawer.tsx │ │ │ │ ├── CartProvider.tsx │ │ │ │ ├── CartState.tsx │ │ │ │ ├── EditAddressDrawer.tsx │ │ │ │ ├── Navigation.tsx │ │ │ │ ├── Summary.tsx │ │ │ │ ├── addresses │ │ │ │ │ ├── AddressForm.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── payment │ │ │ │ │ ├── PaymentMethodForm.tsx │ │ │ │ │ ├── ShippingMethod.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── widget.js │ │ │ │ └── shipping │ │ │ │ │ ├── CartAddresses.tsx │ │ │ │ │ ├── Form.tsx │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ ├── actions.tsx │ │ │ ├── helpers.tsx │ │ │ ├── hooks.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ ├── page.tsx │ │ │ └── store.tsx │ │ └── layout.tsx │ ├── (shop) │ │ ├── (product) │ │ │ └── [...slug] │ │ │ │ ├── ProductPage.tsx │ │ │ │ ├── loading.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ ├── bestsellers │ │ │ └── page.tsx │ │ ├── c │ │ │ └── [...slug] │ │ │ │ ├── loading.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ ├── checkout-success │ │ │ ├── SuccessPage.tsx │ │ │ └── page.tsx │ │ ├── error.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── profile │ │ │ └── page.tsx │ │ ├── recent-arrivals │ │ │ └── page.tsx │ │ ├── sale │ │ │ └── page.tsx │ │ ├── search │ │ │ └── page.tsx │ │ └── ~offline │ │ │ └── page.tsx │ ├── DefaultTags.tsx │ ├── client.ts │ ├── getQueryClient.tsx │ ├── hydrateOnClient.tsx │ ├── layout.tsx │ └── providers.tsx │ ├── components │ ├── auth │ │ ├── ForgotPassword.tsx │ │ ├── MyAccount.tsx │ │ ├── RememberMe.tsx │ │ ├── SignIn.tsx │ │ ├── SignUp.tsx │ │ └── Views │ │ │ ├── ForgotPasswordView.tsx │ │ │ ├── MyAccountView.tsx │ │ │ ├── SignInSignUpView.tsx │ │ │ ├── SignInView.tsx │ │ │ └── SignUpView.tsx │ ├── cart │ │ ├── AddToCartButton.tsx │ │ ├── Cart.tsx │ │ ├── CartDrawer.tsx │ │ ├── CartDrawerWrapper.tsx │ │ ├── CartItemImage.tsx │ │ ├── DeleteProductFromCart.tsx │ │ ├── functions │ │ │ ├── cartActions.ts │ │ │ ├── getInitCart.ts │ │ │ ├── getSavedCartFromCookies.ts │ │ │ ├── helpers.ts │ │ │ └── mergeAnonymousCart.ts │ │ ├── interface.ts │ │ └── store.ts │ ├── checkout │ │ ├── Header.tsx │ │ └── Notification.tsx │ ├── elements │ │ ├── Badge.tsx │ │ ├── Button │ │ │ ├── Button.tsx │ │ │ ├── ButtonBase.tsx │ │ │ ├── ButtonIcon.tsx │ │ │ ├── ButtonWithSpinner.tsx │ │ │ └── index.ts │ │ ├── Checkbox.tsx │ │ ├── Drawer │ │ │ ├── BackToView.tsx │ │ │ ├── Drawer.tsx │ │ │ ├── DrawerPane.tsx │ │ │ ├── DrawerTitle.tsx │ │ │ ├── PaneTriggerButton.tsx │ │ │ ├── SecondaryAction.tsx │ │ │ └── index.ts │ │ ├── Grid.tsx │ │ ├── Icons.tsx │ │ ├── Input.tsx │ │ ├── Logo.tsx │ │ ├── PasswordInput.tsx │ │ ├── RadioGroup.tsx │ │ ├── Section.tsx │ │ ├── Select │ │ │ ├── Select.tsx │ │ │ ├── SelectBase.tsx │ │ │ └── index.ts │ │ ├── SelectNative.tsx │ │ ├── Spinner.tsx │ │ └── Textarea.tsx │ ├── global │ │ ├── 404.tsx │ │ ├── Accordion │ │ │ ├── AccordionBase.tsx │ │ │ ├── AccordionMultiple.tsx │ │ │ ├── AccordionSingle.tsx │ │ │ └── index.ts │ │ ├── Alert.tsx │ │ ├── Categories.tsx │ │ ├── ColorModeToggle.tsx │ │ ├── DropdownMenu.tsx │ │ ├── Filters │ │ │ ├── Components │ │ │ │ ├── Filter.tsx │ │ │ │ ├── FilterTypes │ │ │ │ │ ├── Blocks.tsx │ │ │ │ │ ├── CategoriesRefinementList.tsx │ │ │ │ │ ├── ColorSwatches.tsx │ │ │ │ │ ├── PriceRangeInput.tsx │ │ │ │ │ └── RefinementList.tsx │ │ │ │ ├── Filters.tsx │ │ │ │ ├── Reset.tsx │ │ │ │ ├── ShowResultsButton.tsx │ │ │ │ └── Trigger.tsx │ │ │ ├── FiltersDrawerWrapper.tsx │ │ │ ├── FiltersPane.tsx │ │ │ ├── FiltersWrapper.tsx │ │ │ ├── api.ts │ │ │ ├── hooks │ │ │ │ ├── use-apply-filters.ts │ │ │ │ └── use-cloud-search.ts │ │ │ └── store.ts │ │ ├── Footer │ │ │ ├── Footer.tsx │ │ │ ├── FooterBottom.tsx │ │ │ ├── FooterMenuDesktop.tsx │ │ │ ├── FooterMenuMobile.tsx │ │ │ ├── FooterNewsletter.tsx │ │ │ └── index.ts │ │ ├── Header │ │ │ ├── Components │ │ │ │ ├── DesktopHeaderBase.tsx │ │ │ │ └── MobileHeaderBase.tsx │ │ │ ├── DesktopHeader.tsx │ │ │ ├── DesktopMyAccount.tsx │ │ │ ├── Header.tsx │ │ │ ├── Headers.tsx │ │ │ ├── MobileHeader.tsx │ │ │ ├── MobileMenu.tsx │ │ │ ├── Search.tsx │ │ │ └── index.ts │ │ ├── Modal.tsx │ │ ├── NavigationMenu.tsx │ │ ├── PageContent.tsx │ │ ├── Pagination.tsx │ │ ├── Price.tsx │ │ ├── ScrollToTop.tsx │ │ ├── SortingSelector.tsx │ │ ├── Subcategories.tsx │ │ ├── Tabs.tsx │ │ ├── Tooltip.tsx │ │ ├── TopBanner.tsx │ │ └── skeleton │ │ │ ├── ProductDetailsSkeleton.tsx │ │ │ ├── ProductSkeleton.tsx │ │ │ ├── ProductsGridSkeleton.tsx │ │ │ └── ProductsPageSkeleton.tsx │ ├── homepage │ │ ├── CenterBanners.tsx │ │ ├── MainBanner.tsx │ │ ├── SectionWithButton.tsx │ │ └── SubcategoriesTiles.tsx │ ├── navigation │ │ └── link.tsx │ ├── product │ │ ├── AttributeCheckbox.tsx │ │ ├── AttributeTextarea │ │ │ ├── AttributeTextarea.tsx │ │ │ └── store.ts │ │ ├── AttributesBlocks.tsx │ │ ├── AttributesSelect.tsx │ │ ├── Carousel.tsx │ │ ├── DiscountedPrice.tsx │ │ ├── Label.tsx │ │ ├── ProductAccordion.tsx │ │ ├── ProductAttributes.tsx │ │ ├── ProductBreadcrumbs.tsx │ │ ├── ProductBuyBox.tsx │ │ ├── ProductImagesDesktop.tsx │ │ ├── ProductImagesMobile.tsx │ │ ├── ProductTitle.tsx │ │ ├── RelatedProducts.tsx │ │ ├── interface.ts │ │ └── pagination.css │ ├── products │ │ ├── ProductCard.tsx │ │ ├── ProductCardGrid.tsx │ │ ├── ProductCardTitle.tsx │ │ ├── ProductImage.tsx │ │ ├── ProductsGrid.tsx │ │ ├── ProductsList.tsx │ │ ├── ProductsListPaginated.tsx │ │ └── ProductsListWithLoader.tsx │ ├── profile │ │ └── ProfileDetails.tsx │ ├── providers │ │ ├── ColorModeContext.tsx │ │ └── ModalContext.tsx │ ├── theme │ │ ├── InlineCssVariables.tsx │ │ ├── colors.ts │ │ └── tailwindMergeConfig.ts │ └── wishlist │ │ ├── Wishlist.tsx │ │ ├── WishlistButton.tsx │ │ ├── WishlistDrawer.tsx │ │ ├── WishlistDrawerWrapper.tsx │ │ ├── functions │ │ ├── helpers.ts │ │ └── wishlistActions.ts │ │ └── store.ts │ ├── lib │ └── getClient.ts │ ├── middleware.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ └── api │ │ └── auth │ │ └── [...nextauth].ts │ ├── postcss.config.js │ ├── public │ ├── 404.svg │ ├── arrowDown.svg │ ├── bestsellers.jpeg │ ├── favicon.ico │ ├── icon-192x192.png │ ├── icon-512x512.png │ ├── logo-mobile.svg │ ├── logo.svg │ ├── main_banner.jpeg │ ├── manifest.json │ ├── recent_arrivals.jpeg │ ├── robots.txt │ ├── storybook-product-image.jpg │ ├── storybook-product-image2.jpg │ ├── storybook-product-image3.jpg │ ├── stylish_banner1.jpeg │ ├── stylish_banner2.jpeg │ └── touch-icon-iphone.png │ ├── stories │ ├── Cart │ │ └── CartDrawer.stories.tsx │ ├── Categories.stories.tsx │ ├── Elements │ │ ├── Button.stories.tsx │ │ ├── ButtonIcon.stories.tsx │ │ ├── Input.stories.tsx │ │ ├── Logo.stories.tsx │ │ ├── Select.stories.tsx │ │ └── Spinner.stories.tsx │ ├── Footer │ │ ├── Footer.stories.tsx │ │ ├── FooterMenuDesktop.stories.tsx │ │ ├── FooterMenuMobile.stories.tsx │ │ └── NewsletterSubscription.stories.tsx │ ├── Header │ │ ├── DesktopHeader.stories.tsx │ │ ├── DesktopHeader.tsx │ │ ├── MobileHeader.stories.tsx │ │ ├── MobileHeader.tsx │ │ ├── MobileNav.stories.tsx │ │ └── Search.stories.tsx │ ├── Homepage │ │ ├── CenterBanners.stories.tsx │ │ ├── MainBanner.stories.tsx │ │ ├── SectionWithButton.stories.tsx │ │ └── TabsMain.stories.tsx │ ├── Icons │ │ ├── IconArrow.stories.tsx │ │ ├── IconArrowBack.stories.tsx │ │ ├── IconArrowDown.stories.tsx │ │ ├── IconCart.stories.tsx │ │ ├── IconCheckMark.stories.tsx │ │ ├── IconClose.stories.tsx │ │ ├── IconCross.stories.tsx │ │ ├── IconDark.stories.tsx │ │ ├── IconEdit.stories.tsx │ │ ├── IconFilter.stories.tsx │ │ ├── IconHeart.stories.tsx │ │ ├── IconHeartFilled.stories.tsx │ │ ├── IconHidden.stories.tsx │ │ ├── IconInfo.stories.tsx │ │ ├── IconLight.stories.tsx │ │ ├── IconLoader.stories.tsx │ │ ├── IconMenu.stories.tsx │ │ ├── IconMinus.stories.tsx │ │ ├── IconNotFound.stories.tsx │ │ ├── IconPlus.stories.tsx │ │ ├── IconSearch.stories.tsx │ │ ├── IconSecureCheckout.stories.tsx │ │ ├── IconSuccess.stories.tsx │ │ ├── IconThinArrow.stories.tsx │ │ ├── IconTick.stories.tsx │ │ ├── IconTrash.stories.tsx │ │ ├── IconVisible.stories.tsx │ │ └── IconWarning.stories.tsx │ ├── Introduction.stories.mdx │ ├── NotFound.stories.tsx │ ├── Product │ │ ├── DiscountedPrice.stories.tsx │ │ ├── ProductAccordion.stories.tsx │ │ ├── ProductAttributes.stories.tsx │ │ ├── ProductBreadcrumbs.stories.tsx │ │ ├── ProductBuyBox.stories.tsx │ │ ├── ProductImagesDesktop.stories.tsx │ │ ├── ProductImagesMobile.stories.tsx │ │ └── ProductTitle.stories.tsx │ ├── Products │ │ ├── ProductCardGrid.stories.tsx │ │ ├── ProductsGrid.stories.tsx │ │ └── ProductsListWithLoader.stories.tsx │ ├── Skeleton │ │ ├── ProductDetailsPageSkeleton.stories.tsx │ │ ├── ProductsGridSkeleton.stories.tsx │ │ ├── ProductsPageSkeleton.stories.tsx │ │ └── ProductsSkeleton.stories.tsx │ ├── Subcategories.stories.tsx │ ├── Tooltip.stories.tsx │ ├── TopBanner.stories.tsx │ ├── assets │ │ ├── code-brackets.svg │ │ ├── colors.svg │ │ ├── comments.svg │ │ ├── direction.svg │ │ ├── flow.svg │ │ ├── plugin.svg │ │ ├── repo.svg │ │ ├── stackalt.svg │ │ ├── storybook-product-image.jpg │ │ ├── storybook-product-image2.jpg │ │ └── storybook-product-image3.jpg │ └── constants.ts │ ├── styles │ └── index.css │ ├── tailwind.config.js │ ├── tsconfig.json │ └── utils │ ├── cloud-search │ ├── helpers.ts │ └── search.ts │ ├── constants.ts │ ├── get-sorted-paginated-products.ts │ ├── helpers.ts │ └── unauthenticated-client.ts ├── test └── jest-setup.ts ├── turbo.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # testing 5 | coverage 6 | 7 | # next.js 8 | .next/ 9 | out/ 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | *.pem 15 | 16 | # debug 17 | yarn-debug.log* 18 | yarn-error.log* 19 | 20 | # local env files 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | 26 | # turbo 27 | .turbo 28 | 29 | # build 30 | dist 31 | 32 | # vscode settings 33 | .vscode 34 | 35 | # Web/PhpStorm settings 36 | .idea 37 | 38 | # PWA 39 | **/public/workbox-*.js 40 | **/public/workbox-*.js.map 41 | **/public/sw.js 42 | **/public/sw.js.map 43 | **/public/fallback-*.js 44 | **/public/fallback-*.js.map 45 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": false, 4 | "endOfLine": "lf", 5 | "htmlWhitespaceSensitivity": "css", 6 | "insertPragma": false, 7 | "jsxSingleQuote": false, 8 | "printWidth": 80, 9 | "proseWrap": "always", 10 | "quoteProps": "as-needed", 11 | "requirePragma": false, 12 | "semi": false, 13 | "singleQuote": true, 14 | "tabWidth": 2, 15 | "trailingComma": "all", 16 | "useTabs": false, 17 | "plugins": ["prettier-plugin-tailwindcss"], 18 | "tailwindConfig": "./templates/demo-platform/tailwind.config.js" 19 | } 20 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rootDir: '.', 3 | resetMocks: true, 4 | testEnvironment: 'jsdom', 5 | setupFilesAfterEnv: ['/test/jest-setup.ts'], 6 | modulePathIgnorePatterns: ['/packages/components/dist'], 7 | moduleFileExtensions: ['js', 'jsx', 'json', 'ts', 'tsx'], 8 | transform: { 9 | '^.+\\.tsx?$': 'esbuild-jest', 10 | '^.+\\.jsx?$': 'esbuild-jest', 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /packages/components/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .eslintrc.* 3 | -------------------------------------------------------------------------------- /packages/components/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useProductTabs' 2 | export * from './usePrice' 3 | export * from './usePriceModifier' 4 | export * from './useProductDetailsVariant' 5 | -------------------------------------------------------------------------------- /packages/components/src/hooks/shared/safeHasOwnProperty.ts: -------------------------------------------------------------------------------- 1 | const safeHasOwnProperty = (obj: any, prop: string): boolean => 2 | obj ? Object.prototype.hasOwnProperty.call(obj, prop) : false 3 | 4 | export default safeHasOwnProperty 5 | -------------------------------------------------------------------------------- /packages/components/src/hooks/shared/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Typed generic callback function, used mostly internally 3 | * to defined callback setters 4 | */ 5 | export interface SomeCallback { 6 | (...args: TArgs[]): TResult 7 | } 8 | 9 | /** 10 | * A callback setter is generally used to set the value of 11 | * a callback that will be used to perform updates 12 | */ 13 | export interface CallbackSetter { 14 | (nextCallback: SomeCallback): void 15 | } 16 | -------------------------------------------------------------------------------- /packages/components/src/hooks/usePrice.ts: -------------------------------------------------------------------------------- 1 | export const usePrice = ({ 2 | basePrice, 3 | salePrice, 4 | }: { 5 | basePrice: number 6 | salePrice?: number 7 | }): number => { 8 | return salePrice && salePrice > 0 ? salePrice : basePrice 9 | } 10 | -------------------------------------------------------------------------------- /packages/components/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './products' 2 | export * from './hooks' 3 | -------------------------------------------------------------------------------- /packages/components/src/products/ProductAttributes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ProductAttributeSelector' 2 | -------------------------------------------------------------------------------- /packages/components/src/products/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ProductAttributes' 2 | -------------------------------------------------------------------------------- /packages/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "declaration": true, 5 | "target": "ES2022", 6 | "outDir": "./dist", 7 | "allowJs": true, 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "isolatedModules": true, 12 | "preserveWatchOutput": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noUnusedLocals": true, 16 | "jsx": "react-jsx" 17 | }, 18 | "include": ["./src"], 19 | "exclude": ["node_modules", "src/**/*.test.tsx"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/storefront/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@xcart/storefront", 3 | "version": "0.0.0", 4 | "main": "./dist/index.js", 5 | "types": "./dist/index.d.ts", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "tsc --p tsconfig.json", 9 | "generate": "ts-node scripts/generate.ts", 10 | "dev": "yarn build -w --incremental" 11 | }, 12 | "dependencies": {}, 13 | "devDependencies": { 14 | "openapi-typescript": "^5.4.0", 15 | "ts-node": "^10.9.1", 16 | "typescript": "^4.8.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/storefront/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ES2022"], 4 | "module": "NodeNext", 5 | "target": "ES2022", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/storefront/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export {Client} from './client' 3 | export {Client as default} from './client' 4 | -------------------------------------------------------------------------------- /packages/storefront/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function buildQueryString(query: Record): string { 2 | return Object.entries(query) 3 | .map(([key, value]) => 4 | key && value ? `${key}=${encodeURIComponent(value)}` : '', 5 | ) 6 | .join('&') 7 | } 8 | -------------------------------------------------------------------------------- /packages/storefront/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "target": "ES2022", 5 | "outDir": "./dist", 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "isolatedModules": true, 10 | "preserveWatchOutput": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noUnusedLocals": true, 14 | "jsx": "react" 15 | }, 16 | "include": ["./src"] 17 | } 18 | -------------------------------------------------------------------------------- /templates/auto/.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_XCART_API_URL= 2 | NEXT_PUBLIC_API_KEY= 3 | NEXT_PUBLIC_CLOUD_SEARCH_API_URL=https://cloudsearch.x-cart.com/api/v1/search 4 | NEXT_PUBLIC_CLOUD_SEARCH_API_KEY= 5 | NEXTAUTH_SECRET= 6 | NEXTAUTH_URL= 7 | 8 | # Setting APP_ENV to dev will disable PWA support 9 | # Using PWA in development mode (`yarn dev` running) will break the app 10 | # Set APP_ENV variable to `dev` before running development mode `yarn dev` 11 | APP_ENV=prod 12 | -------------------------------------------------------------------------------- /templates/auto/.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist 2 | next* 3 | xcart.* 4 | *.config.js 5 | .eslintrc.* 6 | server.js 7 | **/sw.js 8 | **/workbox-*.js 9 | **/widget.js 10 | -------------------------------------------------------------------------------- /templates/auto/.storybook/manager.ts: -------------------------------------------------------------------------------- 1 | import {addons} from '@storybook/addons' 2 | import platform from './platform' 3 | 4 | addons.setConfig({ 5 | theme: platform, 6 | }) 7 | -------------------------------------------------------------------------------- /templates/auto/.storybook/platform.ts: -------------------------------------------------------------------------------- 1 | import {create} from '@storybook/theming' 2 | 3 | export default create({ 4 | base: 'light', 5 | 6 | brandTitle: 'Auto', 7 | brandUrl: process.env.NEXTAUTH_URL, 8 | }) 9 | -------------------------------------------------------------------------------- /templates/auto/.storybook/preview.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); 2 | 3 | :root { 4 | --font-poppins: 'Poppins'; 5 | } 6 | 7 | * { 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | body { 13 | color: var(--color-primary); 14 | } 15 | 16 | #root { 17 | @apply leading-base 18 | } 19 | -------------------------------------------------------------------------------- /templates/auto/app/(checkout)/checkout/(steps)/CartProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {Provider} from 'jotai' 4 | 5 | export function CartProvider({children}: {children: React.ReactNode}) { 6 | return {children} 7 | } 8 | -------------------------------------------------------------------------------- /templates/auto/app/(checkout)/checkout/layout.tsx: -------------------------------------------------------------------------------- 1 | import Header from '~/components/checkout/Header' 2 | import {NotificationProvider} from '~/components/checkout/Notification' 3 | 4 | export default async function Layout({children}: {children: React.ReactNode}) { 5 | return ( 6 |
7 | 8 |
9 |
10 | {children} 11 |
12 | 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /templates/auto/app/(checkout)/checkout/page.tsx: -------------------------------------------------------------------------------- 1 | import {redirect} from 'next/navigation' 2 | 3 | export default async function Page() { 4 | redirect('/checkout/addresses') 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/app/(checkout)/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function RootLayout({children}: {children: React.ReactNode}) { 2 | return
{children}
3 | } 4 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/(product)/[...slug]/loading.tsx: -------------------------------------------------------------------------------- 1 | import {ProductDetailsSkeleton} from '~/components/global/skeleton/ProductDetailsSkeleton' 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/(product)/[...slug]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import {NotFoundPage} from '~/components/global/404' 2 | 3 | export default function NotFound() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/brands/[...slug]/loading.tsx: -------------------------------------------------------------------------------- 1 | import {ProductsPageSkeleton} from '~/components/global/skeleton/ProductsPageSkeleton' 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/brands/loading.tsx: -------------------------------------------------------------------------------- 1 | import {BrandsPageSkeleton} from '~/components/global/skeleton/BrandsPageSkeleton' 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/c/[...slug]/loading.tsx: -------------------------------------------------------------------------------- 1 | import {ProductsPageSkeleton} from '~/components/global/skeleton/ProductsPageSkeleton' 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/c/[...slug]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import {NotFoundPage} from '~/components/global/404' 2 | 3 | export default function NotFound() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/checkout-success/page.tsx: -------------------------------------------------------------------------------- 1 | import {SuccessPage} from './SuccessPage' 2 | 3 | export default async function Page({ 4 | searchParams, 5 | }: { 6 | searchParams?: {[key: string]: string | string[] | undefined} 7 | }) { 8 | return 9 | } 10 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {useEffect} from 'react' 4 | import PageContent from '~/components/global/PageContent' 5 | 6 | export default function Error({ 7 | error, 8 | reset, 9 | }: { 10 | error: Error 11 | reset: () => void 12 | }) { 13 | useEffect(() => { 14 | // Log the error to an error reporting service 15 | console.error(error) 16 | }, [error]) 17 | 18 | return ( 19 | 20 |

Something went wrong!

21 | 29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from '~/components/global/Footer' 2 | import Header from '~/components/global/Header' 3 | import {MMYFilterWrapper} from '~/components/mmy/MMYFilter/MMYFilterWrapper' 4 | 5 | export default function RootLayout({children}: {children: React.ReactNode}) { 6 | return ( 7 |
8 | {/* @ts-expect-error Server Component */} 9 |
10 | {/* @ts-expect-error Server Component */} 11 | 12 |
{children}
13 |
14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/mmy/[...slug]/loading.tsx: -------------------------------------------------------------------------------- 1 | import {MMYPageSkeleton} from '~/components/global/skeleton/MMYPageSkeleton' 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/profile/page.tsx: -------------------------------------------------------------------------------- 1 | import {notFound} from 'next/navigation' 2 | import PageContent from '~/components/global/PageContent' 3 | import {ProfileDetails} from '~/components/profile/ProfileDetails' 4 | import {getXCartClient} from '../../client' 5 | 6 | export async function generateMetadata() { 7 | return { 8 | title: 'Profile Details', 9 | } 10 | } 11 | 12 | export default async function Page() { 13 | const client = await getXCartClient() 14 | 15 | if (!client.hasAccessToken()) { 16 | notFound() 17 | } 18 | 19 | const userData = await client.other.getUserItem() 20 | 21 | return ( 22 | 23 | 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/shop-by/[[...slug]]/loading.tsx: -------------------------------------------------------------------------------- 1 | import {ShopByLevelPageSkeleton} from '~/components/global/skeleton/ShopByLevelPageSkeleton' 2 | 3 | export default function Loading() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/app/(shop)/~offline/page.tsx: -------------------------------------------------------------------------------- 1 | import PageContent from '~/components/global/PageContent' 2 | 3 | export default function Page() { 4 | return ( 5 | 6 |

You are offline

7 |

Please check your internet connection

8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /templates/auto/app/api/revalidate/route.ts: -------------------------------------------------------------------------------- 1 | import {revalidateTag} from 'next/cache' 2 | import {NextRequest, NextResponse} from 'next/server' 3 | 4 | export async function GET(request: NextRequest) { 5 | const tag = request.nextUrl.searchParams.get('tag') 6 | // @ts-ignore 7 | revalidateTag(tag) 8 | return NextResponse.json({revalidated: true, now: Date.now()}) 9 | } 10 | -------------------------------------------------------------------------------- /templates/auto/app/getQueryClient.tsx: -------------------------------------------------------------------------------- 1 | import {cache} from 'react' 2 | import {QueryClient} from '@tanstack/query-core' 3 | 4 | const getQueryClient = cache(() => new QueryClient()) 5 | 6 | export default getQueryClient 7 | -------------------------------------------------------------------------------- /templates/auto/app/hydrateOnClient.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {Hydrate as HydrateOnClient} from '@tanstack/react-query' 4 | 5 | export default HydrateOnClient 6 | -------------------------------------------------------------------------------- /templates/auto/app/providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {ReactNode, useState, FC} from 'react' 4 | import {QueryClient, QueryClientProvider} from '@tanstack/react-query' 5 | import {SessionProvider} from 'next-auth/react' 6 | 7 | // eslint-disable-next-line func-names 8 | export const Providers: FC<{children: ReactNode}> = function ({children}) { 9 | const [queryClient] = useState(() => new QueryClient()) 10 | 11 | return ( 12 | 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /templates/auto/components/auth/RememberMe.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {useEffect, useState} from 'react' 4 | import Cookies from 'js-cookie' 5 | import {Checkbox} from '~/components/elements/Checkbox' 6 | 7 | export function RememberMe() { 8 | const [remember, setRemember] = useState( 9 | () => Cookies.get('remember_me') === 'true', 10 | ) 11 | 12 | useEffect(() => { 13 | Cookies.set('remember_me', remember ? 'true' : 'false', {expires: 365}) 14 | }, [remember]) 15 | 16 | return ( 17 | Remember me} 19 | onChange={() => setRemember(v => !v)} 20 | checked={remember} 21 | /> 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /templates/auto/components/auth/Views/ForgotPasswordView.tsx: -------------------------------------------------------------------------------- 1 | import {ForgotPassword} from '~/components/auth/ForgotPassword' 2 | import {tailwindMerge} from '~/helpers' 3 | 4 | export function ForgotPasswordView({visible = true}: {visible?: boolean}) { 5 | return ( 6 |
12 |
13 |

Forgot password?

14 |
15 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /templates/auto/components/auth/Views/MyAccountView.tsx: -------------------------------------------------------------------------------- 1 | import {MyAccount} from '~/components/auth/MyAccount' 2 | 3 | export function MyAccountView({ 4 | instantLogout = () => {}, 5 | }: { 6 | instantLogout: () => void 7 | }) { 8 | return ( 9 |
10 |
11 |

My account

12 |
13 |
14 | 15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /templates/auto/components/auth/Views/SignInSignUpView.tsx: -------------------------------------------------------------------------------- 1 | import {useState} from 'react' 2 | import {SignInView} from './SignInView' 3 | import {SignUpView} from './SignUpView' 4 | 5 | export function SignInSignUpView({nextView}: {nextView: () => void}) { 6 | const [page, setPage] = useState('sign_in') 7 | return ( 8 |
9 | {page === 'sign_in' && ( 10 | setPage(f)} nextView={nextView} /> 11 | )} 12 | {page === 'sign_up' && setPage(f)} />} 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /templates/auto/components/auth/Views/SignUpView.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react' 2 | import {SignUp} from '~/components/auth/SignUp' 3 | import {useDrawer} from '~/components/elements/Drawer' 4 | 5 | export function SignUpView({changePage}: {changePage: (view: string) => void}) { 6 | const {setAlert} = useDrawer() 7 | 8 | useEffect(() => { 9 | setAlert(null) 10 | }, [changePage, setAlert]) 11 | 12 | return ( 13 | <> 14 |
15 |

Sign up

16 | 25 |
26 | 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /templates/auto/components/cart/CartDrawerWrapper.tsx: -------------------------------------------------------------------------------- 1 | import {CartDrawer} from '~/components/cart/CartDrawer' 2 | import getInitCart from '~/components/cart/functions/getInitCart' 3 | 4 | export async function CartDrawerWrapper() { 5 | const cart = await getInitCart() 6 | 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /templates/auto/components/cart/CartItemImage.tsx: -------------------------------------------------------------------------------- 1 | import type {CartItem} from '@xcart/storefront' 2 | import Image from 'next/image' 3 | 4 | export function CartItemImage({item}: {item: CartItem}) { 5 | return item.variant && 6 | Object.keys(item.variant).length > 0 && 7 | item.variant.image ? ( 8 | {item.variant.sku 14 | ) : ( 15 | {item.sku 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /templates/auto/components/cart/functions/getSavedCartFromCookies.ts: -------------------------------------------------------------------------------- 1 | import {cookies} from 'next/headers' 2 | 3 | export default function getSavedCartFromCookies() { 4 | const savedCartId = cookies().get('xc-cart') 5 | 6 | return savedCartId || null 7 | } 8 | -------------------------------------------------------------------------------- /templates/auto/components/cart/functions/mergeAnonymousCart.ts: -------------------------------------------------------------------------------- 1 | import {Cart} from '@xcart/storefront' 2 | import Cookies from 'js-cookie' 3 | import {mergeCart} from '~/components/cart/functions/cartActions' 4 | 5 | export async function mergeAnonymousCart( 6 | cartId: string, 7 | setCart: (values: Cart) => void, 8 | ) { 9 | if (cartId) { 10 | const mergedCart = await mergeCart(cartId) 11 | 12 | if (mergedCart && mergedCart.id) { 13 | Cookies.set('xc-cart', mergedCart.id) 14 | setCart(mergedCart) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /templates/auto/components/cart/interface.ts: -------------------------------------------------------------------------------- 1 | export interface AttributeForProductToAdd { 2 | name?: string 3 | attributeId: number 4 | attributeValueId: number 5 | attributeValue: string 6 | } 7 | 8 | export interface ProductToAdd { 9 | id: number 10 | amount: number 11 | attributes: AttributeForProductToAdd[] 12 | } 13 | -------------------------------------------------------------------------------- /templates/auto/components/cart/store.ts: -------------------------------------------------------------------------------- 1 | import {atom} from 'jotai' 2 | 3 | export const cartAtom = atom({}) 4 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Badge.tsx: -------------------------------------------------------------------------------- 1 | import {tailwindMerge} from '~/helpers' 2 | 3 | export function Badge({ 4 | children, 5 | className, 6 | }: { 7 | children: React.ReactElement | number | string 8 | className?: string 9 | }) { 10 | const badgeClasses = tailwindMerge( 11 | 'absolute bottom-0 right-0 flex h-unit-3 min-w-unit-3 px-[2px] items-center justify-center rounded-full border border-contrast bg-primary text-3xs font-semibold text-contrast leading-[15px]', 12 | className, 13 | ) 14 | return {children} 15 | } 16 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Button/ButtonBase.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | export type ButtonProps = JSX.IntrinsicElements['button'] 4 | 5 | export interface IButtonBase { 6 | buttonTitle?: string 7 | children?: React.ReactNode 8 | } 9 | 10 | export function ButtonBase({ 11 | type = 'button', 12 | buttonTitle, 13 | children, 14 | onClick, 15 | disabled, 16 | ...props 17 | }: ButtonProps & IButtonBase) { 18 | return ( 19 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Button/index.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | export * from './ButtonBase' 4 | export * from './Button' 5 | export * from './ButtonIcon' 6 | export * from './ButtonWithSpinner' 7 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Drawer/BackToView.tsx: -------------------------------------------------------------------------------- 1 | import {IconArrowDown} from '~/components/elements/Icons' 2 | 3 | export function BackToView({ 4 | label, 5 | setView, 6 | }: { 7 | label: string 8 | setView: () => void 9 | }) { 10 | return ( 11 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Drawer/DrawerTitle.tsx: -------------------------------------------------------------------------------- 1 | import {componentOrNull} from '~/helpers' 2 | 3 | export function DrawerTitle({ 4 | children, 5 | count, 6 | }: { 7 | children: React.ReactElement | string 8 | count?: number 9 | }) { 10 | const itemsCount = count as number 11 | 12 | return ( 13 |

14 | {children} 15 | {componentOrNull( 16 | itemsCount > 0, 17 |  ({count}), 18 | )} 19 |

20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Drawer/PaneTriggerButton.tsx: -------------------------------------------------------------------------------- 1 | import {Badge} from '~/components/elements/Badge' 2 | import {ButtonIcon} from '~/components/elements/Button' 3 | 4 | export function PaneTriggerButton({ 5 | children, 6 | count, 7 | ariaLabel, 8 | ...props 9 | }: { 10 | children: React.ReactElement 11 | count: number 12 | ariaLabel: string 13 | }) { 14 | return ( 15 | 20 | {children} 21 | {count > 0 && {count}} 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Drawer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Drawer' 2 | export * from './SecondaryAction' 3 | export * from './DrawerTitle' 4 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Section.tsx: -------------------------------------------------------------------------------- 1 | export interface ISection { 2 | children?: React.ReactNode 3 | heading?: string 4 | headingClasses?: string 5 | sectionClasses?: string 6 | sectionInnerClasses?: string 7 | } 8 | 9 | export function Section({ 10 | children, 11 | heading, 12 | headingClasses, 13 | sectionClasses = 'w-full gap-unit-5 md:gap-unit-8 grid', 14 | sectionInnerClasses = 'md:scroll-px-8 lg:scroll-px-12', 15 | }: ISection) { 16 | return ( 17 |
18 | {heading &&

{heading}

} 19 |
{children}
20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Select/index.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | export * from './Select' 4 | export * from './SelectBase' 5 | -------------------------------------------------------------------------------- /templates/auto/components/elements/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import {tailwindMerge} from '~/helpers' 2 | 3 | export function Spinner({ 4 | size = 'h-[22px] w-[22px]', 5 | wrapperClasses, 6 | spinnerClasses, 7 | }: { 8 | size?: string 9 | wrapperClasses?: string 10 | spinnerClasses?: string 11 | }) { 12 | return ( 13 |
14 |
21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /templates/auto/components/global/Accordion/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AccordionMultiple' 2 | -------------------------------------------------------------------------------- /templates/auto/components/global/Filters/Components/Filters.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {useAtomValue} from 'jotai' 4 | import {useHydrateAtoms} from 'jotai/utils' 5 | import {Filter} from './Filter' 6 | import {facetsAtom, getFacets, priceRangeAtom} from '../store' 7 | 8 | export function Filters(props: any) { 9 | useHydrateAtoms([ 10 | [facetsAtom, props?.facets], 11 | [priceRangeAtom, props?.priceRange], 12 | ]) 13 | 14 | const facets = useAtomValue(getFacets) 15 | 16 | return ( 17 | <> 18 | {facets 19 | ? facets.map(facet => ) 20 | : null} 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /templates/auto/components/global/Filters/FiltersDrawerWrapper.tsx: -------------------------------------------------------------------------------- 1 | import {Suspense} from 'react' 2 | import {Params} from 'utils/cloud-search/helpers' 3 | import FiltersPane from './FiltersPane' 4 | import {FiltersWrapper} from './FiltersWrapper' 5 | 6 | export function FiltersDrawerWrapper({params}: {params: Params}) { 7 | if (!process.env.NEXT_PUBLIC_CLOUD_SEARCH_API_KEY) { 8 | return null 9 | } 10 | 11 | return ( 12 | 13 | 14 | {/* @ts-expect-error Server Component */} 15 | 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /templates/auto/components/global/Filters/FiltersWrapper.tsx: -------------------------------------------------------------------------------- 1 | import {getBaseSearchParams, Params} from 'utils/cloud-search/helpers' 2 | import * as CloudSearch from './api' 3 | import {Filters} from './Components/Filters' 4 | 5 | export async function FiltersWrapper({params}: {params: Params}) { 6 | const defaultParams = getBaseSearchParams(params) 7 | 8 | const response = await CloudSearch.requestWithJson('', { 9 | ...defaultParams, 10 | facet: true, 11 | limits: { 12 | products: 0, 13 | categories: 0, 14 | manufacturers: 0, 15 | pages: 0, 16 | }, 17 | }) 18 | 19 | if (response && typeof response.stats === 'undefined') { 20 | return null 21 | } 22 | 23 | return ( 24 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /templates/auto/components/global/Footer/FooterBottom.tsx: -------------------------------------------------------------------------------- 1 | import {ColorModeToggle} from '~/components/global/ColorModeToggle' 2 | 3 | export function FooterBottom() { 4 | return ( 5 |
6 | 7 |
8 | Copyrights © 2022 All Rights Reserved by Company 9 |
10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /templates/auto/components/global/Footer/FooterMenuDesktop.tsx: -------------------------------------------------------------------------------- 1 | import {IAccordionItems} from '~/components/global/Accordion/AccordionBase' 2 | 3 | export function FooterMenuDesktop({items}: {items: IAccordionItems[]}) { 4 | return ( 5 |
6 | {items.map((item, index) => { 7 | const itemKey = item.name 8 | ? item.name.replaceAll(' ', '') + index 9 | : index 10 | 11 | return ( 12 |
13 |

{item.name}

14 | {/* @ts-ignore */} 15 |
16 |
17 | ) 18 | })} 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /templates/auto/components/global/Footer/FooterMenuMobile.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {AccordionMultiple} from '~/components/global/Accordion' 4 | import {IAccordionItems} from '~/components/global/Accordion/AccordionBase' 5 | 6 | export function FooterMenuMobile({items}: {items: IAccordionItems[]}) { 7 | return ( 8 |
9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /templates/auto/components/global/Footer/index.ts: -------------------------------------------------------------------------------- 1 | import {Footer} from './Footer' 2 | 3 | export default Footer 4 | -------------------------------------------------------------------------------- /templates/auto/components/global/Header/Components/DesktopHeaderBase.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import {Logo} from '~/components/elements/Logo' 3 | 4 | export function DesktopHeaderBase({ 5 | children, 6 | menu, 7 | }: { 8 | children: React.ReactNode 9 | menu: React.ReactNode 10 | }) { 11 | return ( 12 |
13 |
14 |
15 | 16 | 17 | 18 |
{menu}
19 |
20 |
{children}
21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /templates/auto/components/global/Header/Components/MobileHeaderBase.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import {Logo} from '~/components/elements/Logo' 3 | 4 | export function MobileHeaderBase({children}: {children: React.ReactNode}) { 5 | return ( 6 |
7 | 8 | 9 | 10 |
{children}
11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /templates/auto/components/global/Header/Headers.tsx: -------------------------------------------------------------------------------- 1 | import {getXCartClient} from 'app/client' 2 | import {DesktopHeader} from '~/components/global/Header/DesktopHeader' 3 | import {MobileHeader} from '~/components/global/Header/MobileHeader' 4 | 5 | export async function Headers() { 6 | const client = await getXCartClient() 7 | const rootCategories = await client.getRootCategories({categoriesCount: 3}) 8 | 9 | return ( 10 | <> 11 | 15 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /templates/auto/components/global/Header/index.ts: -------------------------------------------------------------------------------- 1 | import {Header} from './Header' 2 | 3 | export default Header 4 | -------------------------------------------------------------------------------- /templates/auto/components/global/Price.tsx: -------------------------------------------------------------------------------- 1 | // TODO add real currency 2 | const CURRENCY = 'USD' 3 | const LOCALES = 'en-En' 4 | 5 | export function Price({ 6 | price, 7 | productPriceClasses, 8 | }: { 9 | price?: number 10 | productPriceClasses?: string 11 | }) { 12 | const formatter = new Intl.NumberFormat(LOCALES, { 13 | style: 'currency', 14 | currency: CURRENCY, 15 | }) 16 | 17 | if (typeof price === 'undefined') { 18 | return null 19 | } 20 | 21 | return ( 22 | 23 | {formatter.format(price as number)} 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /templates/auto/components/global/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {useEffect} from 'react' 4 | 5 | export function ScrollToTop({page, children}: {page: number, children: React.ReactNode}) { 6 | useEffect(() => { 7 | window.scrollTo({ 8 | top: 0, 9 | left: 0, 10 | behavior: 'smooth', 11 | }) 12 | }, [page]) 13 | 14 | return <>{children} 15 | } 16 | -------------------------------------------------------------------------------- /templates/auto/components/global/skeleton/BrandItemSkeleton.tsx: -------------------------------------------------------------------------------- 1 | export function BrandItemSkeleton() { 2 | return ( 3 |
4 |
5 |
6 |
7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /templates/auto/components/global/skeleton/BrandsPageSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import PageContent from '~/components/global/PageContent' 2 | import {BrandsTileSkeleton} from '~/components/global/skeleton/BrandsTileSkeleton' 3 | 4 | export function BrandsPageSkeleton() { 5 | return ( 6 | 7 |
8 |
9 |
10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /templates/auto/components/global/skeleton/BrandsTileSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import {BrandItemSkeleton} from '~/components/global/skeleton/BrandItemSkeleton' 2 | 3 | export function BrandsTileSkeleton() { 4 | return ( 5 |
6 |
7 | {[...Array(20)].map((_item, index) => { 8 | const key = `item-${index}` 9 | return 10 | })} 11 |
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /templates/auto/components/global/skeleton/MMYPageSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import {ProductsGridSkeleton} from '~/components/global/skeleton/ProductsGridSkeleton' 2 | 3 | export function MMYPageSkeleton() { 4 | return ( 5 |
6 |
7 |
8 | 9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /templates/auto/components/global/skeleton/ProductSkeleton.tsx: -------------------------------------------------------------------------------- 1 | export function ProductSkeleton() { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /templates/auto/components/global/skeleton/ProductsGridSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import {Grid} from '~/components/elements/Grid' 2 | import {ProductSkeleton} from '~/components/global/skeleton/ProductSkeleton' 3 | 4 | export function ProductsGridSkeleton({ 5 | count = 9, 6 | gridClasses, 7 | }: { 8 | count?: number 9 | gridClasses?: string 10 | }) { 11 | return ( 12 | 13 | {[...Array(count)].map((item, index) => { 14 | const itemKey = `${item}-${index}` 15 | return 16 | })} 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /templates/auto/components/global/skeleton/ShopByLevelItemSkeleton.tsx: -------------------------------------------------------------------------------- 1 | export function ShopByLevelItemSkeleton() { 2 | return ( 3 |
  • 4 |
    5 |
  • 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /templates/auto/components/global/skeleton/ShopByLevelPageSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import PageContent from '~/components/global/PageContent' 2 | import {ShopByLevelTileSkeleton} from '~/components/global/skeleton/ShopByLevelTileSkeleton' 3 | 4 | export function ShopByLevelPageSkeleton() { 5 | return ( 6 | 7 |
    8 |
    9 |
    10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /templates/auto/components/global/skeleton/ShopByLevelTileSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import {ShopByLevelItemSkeleton} from '~/components/global/skeleton/ShopByLevelItemSkeleton' 2 | 3 | export function ShopByLevelTileSkeleton() { 4 | return ( 5 |
      6 | {[...Array(16)].map((_item, index) => { 7 | const key = `item-${index}` 8 | return 9 | })} 10 |
    11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/VIN/FindByVINDrawer.tsx: -------------------------------------------------------------------------------- 1 | import {useDrawer} from '~/components/elements/Drawer' 2 | import {FindByVINField} from '~/components/mmy/VIN/FindByVINField' 3 | 4 | export function FindByVINDrawer() { 5 | const {setAlert} = useDrawer() 6 | 7 | return ( 8 |
    9 |

    10 | Select your vehicle, or find it by VIN, and add it to your garage. Then 11 | easily find parts & accessories. 12 |

    13 | 20 |
    21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/VIN/FindByVINFilter.tsx: -------------------------------------------------------------------------------- 1 | import {FindByVINField} from '~/components/mmy/VIN/FindByVINField' 2 | 3 | export function FindByVINFilter({ 4 | setAlert, 5 | }: { 6 | setAlert: (value: string | null) => void 7 | }) { 8 | return ( 9 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/VIN/helpers.ts: -------------------------------------------------------------------------------- 1 | export function validateVIN(id: string) { 2 | let result: { 3 | error?: string 4 | ok?: boolean 5 | } = {} 6 | 7 | if (id.length < 17) { 8 | result = { 9 | error: 10 | id.length === 0 11 | ? "This field can't be empty" 12 | : 'Please check your VIN number. It should have 17 characters', 13 | } 14 | } else if (!/^[A-Za-z0-9]*$/.test(id)) { 15 | result = { 16 | error: 17 | 'Please check your VIN number. It should have only letters and digits', 18 | } 19 | } else { 20 | result = { 21 | ok: true, 22 | } 23 | } 24 | 25 | return result 26 | } 27 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/functions/getInitGarage.ts: -------------------------------------------------------------------------------- 1 | import {getXCartClient} from '../../../app/client' 2 | 3 | export const getInitGarage = async () => { 4 | const client = await getXCartClient() 5 | 6 | if (!client.hasAccessToken()) { 7 | return null 8 | } 9 | 10 | const response = await client.garage.getGarage() 11 | 12 | return response 13 | } 14 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/functions/getInitMMY.ts: -------------------------------------------------------------------------------- 1 | import {MMYLevelsSetupItem} from '@xcart/storefront' 2 | import {client} from 'utils/unauthenticated-client' 3 | import {getMMYLevelsByPage} from '~/components/mmy/functions/getMMYLevelsByPage' 4 | 5 | export const getInitMMY = async () => { 6 | const [levels, rootLevel] = await Promise.all([ 7 | await client.mmy.getMMYLevels(), 8 | await getMMYLevelsByPage({client, depth: '1'}), 9 | ]) 10 | 11 | const availLevels: MMYLevelsSetupItem[] = [] 12 | 13 | levels.levels?.forEach((level: MMYLevelsSetupItem) => { 14 | if (level.enabled) { 15 | availLevels.push(level) 16 | } 17 | }) 18 | 19 | return { 20 | levels: availLevels, 21 | rootLevel, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/functions/getMMYPath.ts: -------------------------------------------------------------------------------- 1 | import {Level} from '@xcart/storefront' 2 | 3 | export function getMMYPath(levels?: Level[]) { 4 | if (!levels) { 5 | return '/' 6 | } 7 | 8 | let path = '' 9 | 10 | levels.forEach((level: Level) => { 11 | if (level.name) { 12 | path += `/${level?.name.replaceAll(' ', '__')}` 13 | } 14 | }) 15 | 16 | return `/mmy${path}` 17 | } 18 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/functions/getStoredSelectedVehicle.ts: -------------------------------------------------------------------------------- 1 | import {cookies} from 'next/headers' 2 | 3 | export function getStoredSelectedVehicle() { 4 | const selectedVehicle = cookies().get('xc-selected-vehicle') 5 | 6 | return selectedVehicle?.value ? JSON.parse(selectedVehicle.value) : {} 7 | } 8 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/functions/getUpdatedSearchParams.ts: -------------------------------------------------------------------------------- 1 | import {ReadonlyURLSearchParams} from 'next/navigation' 2 | 3 | export function getUpdatedSearchParams( 4 | searchParams: ReadonlyURLSearchParams | null, 5 | tag: string, 6 | ) { 7 | const newSearchParams = new URLSearchParams() 8 | 9 | if (searchParams?.toString()) { 10 | // eslint-disable-next-line no-restricted-syntax 11 | for (const [key, value] of searchParams.entries()) { 12 | if (Array.isArray(value)) { 13 | value.map(v => newSearchParams.append(key, String(v))) 14 | } else { 15 | newSearchParams.set(key, String(value)) 16 | } 17 | } 18 | } 19 | 20 | if (tag === '') { 21 | newSearchParams.delete('tag') 22 | } else { 23 | newSearchParams.set('tag', tag) 24 | } 25 | 26 | return newSearchParams 27 | } 28 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/functions/interface.ts: -------------------------------------------------------------------------------- 1 | import {Level, MMYLevel} from '@xcart/storefront' 2 | 3 | export interface GarageItem { 4 | id?: number 5 | name?: string 6 | depth?: number 7 | levels?: Level[] 8 | } 9 | 10 | export interface MMYLevels { 11 | [key: string]: MMYLevel[] | [] 12 | } 13 | 14 | export interface SelectedMMYLevel { 15 | [key: string]: { 16 | id?: string 17 | value?: string 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/functions/useInitGarage.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react' 2 | import {Garage} from '@xcart/storefront' 3 | import {useSetAtom} from 'jotai/index' 4 | import {updatedGarage} from '~/components/mmy/functions/helpers' 5 | import {myGarageItemsAtom} from '~/components/mmy/store' 6 | 7 | export function useInitGarage(garage?: Garage) { 8 | const setMyGarage = useSetAtom(myGarageItemsAtom) 9 | 10 | useEffect(() => { 11 | if (garage && garage?.vehicles?.length) { 12 | const myGarage = updatedGarage(garage) 13 | 14 | if (myGarage && myGarage.length) { 15 | setMyGarage(myGarage) 16 | } 17 | } 18 | }, [garage, setMyGarage]) 19 | } 20 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/functions/useInitMMY.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react' 2 | import {MMYLevel, MMYLevelsSetupItem} from '@xcart/storefront' 3 | import {useSetAtom} from 'jotai' 4 | import {prepareLevels} from '~/components/mmy/functions/helpers' 5 | import {allLevelsAtom, levelsValuesAtom} from '~/components/mmy/store' 6 | 7 | export function useInitMMY(levels: MMYLevelsSetupItem[], rootLevel?: MMYLevel) { 8 | const setAllLevels = useSetAtom(allLevelsAtom) 9 | const setLevelsValues = useSetAtom(levelsValuesAtom) 10 | 11 | useEffect(() => { 12 | if (levels && levels.length > 0) { 13 | setAllLevels(levels) 14 | } 15 | 16 | const levelValues = prepareLevels(levels, rootLevel) 17 | 18 | if (levelValues) { 19 | setLevelsValues(levelValues) 20 | } 21 | }, [setAllLevels, levels, rootLevel, setLevelsValues]) 22 | } 23 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/garage/FindByVIN.tsx: -------------------------------------------------------------------------------- 1 | import {FindByVINDrawer} from '~/components/mmy/VIN/FindByVINDrawer' 2 | 3 | export function FindByVIN() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/garage/GarageDrawerTitle.tsx: -------------------------------------------------------------------------------- 1 | export function GarageDrawerTitle({ 2 | title, 3 | buttonTitle, 4 | clickHandler, 5 | }: { 6 | title: string 7 | buttonTitle: string 8 | clickHandler: () => void 9 | }) { 10 | return ( 11 |
    12 |

    {title}

    13 | 20 |
    21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/garage/MyGarageDrawerWrapper.tsx: -------------------------------------------------------------------------------- 1 | import {cookies} from 'next/headers' 2 | import {getInitGarage} from '~/components/mmy/functions/getInitGarage' 3 | import {getInitMMY} from '~/components/mmy/functions/getInitMMY' 4 | import {MyGarageDrawer} from '~/components/mmy/garage/MyGarageDrawer' 5 | 6 | function getInitGarageItemsCount() { 7 | const count = cookies().get('xc-garage') 8 | 9 | return Number(count?.value) 10 | } 11 | 12 | export async function MyGarageDrawerWrapper() { 13 | const {levels, rootLevel} = await getInitMMY() 14 | const myGarage = await getInitGarage() 15 | 16 | return ( 17 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/garage/SelectYourVehiclePane.tsx: -------------------------------------------------------------------------------- 1 | import {MakeModelYear} from '~/components/mmy/MMYFilterComponents/MakeModelYear' 2 | 3 | export function SelectYourVehiclePane() { 4 | return ( 5 |
    6 |

    7 | Select your vehicle, or find it by VIN, and add it to your garage. Then 8 | easily find parts & accessories. 9 |

    10 | 11 |
    12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/garage/Views/FindByVINView.tsx: -------------------------------------------------------------------------------- 1 | import {FindByVIN} from '~/components/mmy/garage/FindByVIN' 2 | import {GarageDrawerTitle} from '~/components/mmy/garage/GarageDrawerTitle' 3 | 4 | export function FindByVINView({ 5 | changePage, 6 | }: { 7 | changePage: (page: string) => void 8 | }) { 9 | return ( 10 |
    11 | changePage('select_your_vehicle')} 15 | /> 16 |
    17 | 18 |
    19 |
    20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/garage/Views/SelectOrFindView.tsx: -------------------------------------------------------------------------------- 1 | import {useState} from 'react' 2 | import {useAtomValue} from 'jotai' 3 | import {FindByVINView} from '~/components/mmy/garage/Views/FindByVINView' 4 | import {SelectYourVehicleView} from '~/components/mmy/garage/Views/SelectYourVehicleView' 5 | import {allLevelsAtom} from '~/components/mmy/store' 6 | 7 | export function SelectOrFindView() { 8 | const allLevels = useAtomValue(allLevelsAtom) 9 | const [page, setPage] = useState('select_your_vehicle') 10 | 11 | return ( 12 |
    13 | {allLevels.length > 0 && page === 'select_your_vehicle' && ( 14 | setPage(f)} /> 15 | )} 16 | {page === 'find_by_vin' && setPage(f)} />} 17 |
    18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /templates/auto/components/mmy/garage/Views/SelectYourVehicleView.tsx: -------------------------------------------------------------------------------- 1 | import {GarageDrawerTitle} from '~/components/mmy/garage/GarageDrawerTitle' 2 | import {SelectYourVehiclePane} from '~/components/mmy/garage/SelectYourVehiclePane' 3 | 4 | export function SelectYourVehicleView({ 5 | changePage, 6 | }: { 7 | changePage: (page: string) => void 8 | }) { 9 | return ( 10 |
    11 | changePage('find_by_vin')} 15 | /> 16 |
    17 | 18 |
    19 |
    20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /templates/auto/components/navigation/link.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import NextLink, {LinkProps} from 'next/link' 4 | import {useModal} from '~/components/providers/ModalContext' 5 | 6 | type Props = Omit< 7 | React.AnchorHTMLAttributes, 8 | keyof LinkProps 9 | > & 10 | LinkProps & { 11 | children?: React.ReactNode 12 | } & React.RefAttributes 13 | 14 | export function Link(props: Props) { 15 | const modalContext = useModal() 16 | 17 | const clickHandler = (e: any) => { 18 | if (typeof props?.onClick !== 'undefined') { 19 | // eslint-disable-next-line react/destructuring-assignment 20 | props.onClick(e) 21 | } 22 | if (modalContext.open) { 23 | modalContext.setOpen(false) 24 | } 25 | } 26 | 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /templates/auto/components/product/AttributeTextarea/store.ts: -------------------------------------------------------------------------------- 1 | import {atom} from 'jotai' 2 | 3 | export const attributeTextareaAtom = atom({}) 4 | -------------------------------------------------------------------------------- /templates/auto/components/product/Label.tsx: -------------------------------------------------------------------------------- 1 | import {componentOrNull} from '~/helpers' 2 | 3 | export function Label({ 4 | productPrice, 5 | productSalePrice, 6 | }: { 7 | productPrice?: number | null 8 | productSalePrice?: number 9 | }) { 10 | const hasSaleLabel = 11 | productSalePrice && productPrice ? productSalePrice < productPrice : false 12 | 13 | return ( 14 | <> 15 | {componentOrNull( 16 | hasSaleLabel, 17 |
    18 | sale 19 |
    , 20 | )} 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /templates/auto/components/product/ProductAccordion.tsx: -------------------------------------------------------------------------------- 1 | import {AccordionMultiple} from '~/components/global/Accordion' 2 | import {IAccordionItems} from '~/components/global/Accordion/AccordionBase' 3 | 4 | export function ProductAccordion({items}: {items: IAccordionItems[]}) { 5 | return ( 6 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /templates/auto/components/product/ProductTitle.tsx: -------------------------------------------------------------------------------- 1 | export function ProductTitle({title, brand}: {title: string; brand?: string}) { 2 | return ( 3 |
    4 |

    {title}

    5 | {brand &&
    {brand}
    } 6 |
    7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /templates/auto/components/product/RelatedProducts.tsx: -------------------------------------------------------------------------------- 1 | import {getXCartClient} from 'app/client' 2 | import {Section} from '~/components/elements/Section' 3 | import {ProductsGrid} from '~/components/products/ProductsGrid' 4 | 5 | export async function RelatedProducts({productId}: {productId: number}) { 6 | const client = await getXCartClient() 7 | 8 | const products = await client.getRelatedProducts({ 9 | productId, 10 | }) 11 | 12 | if (products.length === 0) { 13 | return null 14 | } 15 | 16 | return ( 17 |
    18 | 19 |
    20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /templates/auto/components/product/interface.ts: -------------------------------------------------------------------------------- 1 | export interface SelectedAttributes { 2 | [key: string]: {id?: string; name?: string; value?: string} 3 | } 4 | 5 | export interface CartItem { 6 | id: number 7 | selectedAttributes?: SelectedAttributes 8 | variantId?: number 9 | amount: number 10 | } 11 | -------------------------------------------------------------------------------- /templates/auto/components/product/pagination.css: -------------------------------------------------------------------------------- 1 | .swiper-pagination { 2 | @apply absolute bottom-unit-2 left-1/2 -translate-x-1/2 bg-contrast h-unit-2 rounded px-unit-2 flex items-center z-[1] 3 | } 4 | 5 | .swiper-pagination-bullet { 6 | @apply rounded-full cursor-pointer bg-gray-500 w-unit h-unit ml-unit-2 first:ml-0 7 | } 8 | 9 | .swiper-pagination-bullet.swiper-pagination-bullet-active { 10 | @apply bg-primary 11 | } 12 | -------------------------------------------------------------------------------- /templates/auto/components/products/ProductCard.tsx: -------------------------------------------------------------------------------- 1 | export function ProductCard({ 2 | children, 3 | productCardClasses, 4 | }: { 5 | children: React.ReactNode 6 | productCardClasses?: string 7 | }) { 8 | return
    {children}
    9 | } 10 | -------------------------------------------------------------------------------- /templates/auto/components/products/ProductCardTitle.tsx: -------------------------------------------------------------------------------- 1 | import type {Product} from '@xcart/storefront' 2 | import {Link} from '~/components/navigation/link' 3 | 4 | export function ProductCardTitle({ 5 | product, 6 | titleClasses, 7 | linkClasses, 8 | }: { 9 | product: Product 10 | titleClasses?: string 11 | linkClasses?: string 12 | }) { 13 | return ( 14 | 15 | 16 | {product.name} 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /templates/auto/components/products/ProductPrice.tsx: -------------------------------------------------------------------------------- 1 | // TODO add real currency 2 | const CURRENCY = 'USD' 3 | const LOCALES = 'en-En' 4 | 5 | export function ProductPrice({ 6 | price, 7 | productPriceClasses, 8 | }: { 9 | price: number 10 | productPriceClasses?: string 11 | }) { 12 | const formatter = new Intl.NumberFormat(LOCALES, { 13 | style: 'currency', 14 | currency: CURRENCY, 15 | }) 16 | 17 | return ( 18 | 19 | {formatter.format(price)} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /templates/auto/components/products/ProductsList.tsx: -------------------------------------------------------------------------------- 1 | import {Product} from '@xcart/storefront' 2 | import {Params} from 'utils/cloud-search/helpers' 3 | import {FiltersDrawerWrapper} from '~/components/global/Filters/FiltersDrawerWrapper' 4 | import {ProductsListPaginated} from './ProductsListPaginated' 5 | 6 | export function ProductsList(props: { 7 | page: number 8 | products: Product[] 9 | lastPage: number 10 | total?: number 11 | params: Params 12 | }) { 13 | const {params} = props 14 | const {searchParams} = params 15 | 16 | return ( 17 | <> 18 | } 22 | /> 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /templates/auto/components/providers/ModalContext.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import React, {createContext, useContext} from 'react' 4 | 5 | interface ModalContextInterface { 6 | open: boolean 7 | setOpen: (v: boolean) => void 8 | } 9 | 10 | export const ModalContext = createContext({ 11 | open: false, 12 | setOpen: () => {}, 13 | }) 14 | ModalContext.displayName = 'ModalContext' 15 | 16 | export function useModal() { 17 | return useContext(ModalContext) 18 | } 19 | 20 | export function ModalProvider({ 21 | value, 22 | children, 23 | }: { 24 | value: ModalContextInterface 25 | children: React.ReactNode 26 | }) { 27 | return {children} 28 | } 29 | -------------------------------------------------------------------------------- /templates/auto/components/shop-by/functions/getLevelsFirstLetters.ts: -------------------------------------------------------------------------------- 1 | import {MMYLevel} from '@xcart/storefront' 2 | 3 | export function getLevelsFirstLetters(items: MMYLevel[]) { 4 | return items.map((item: MMYLevel) => item.name?.charAt(0).toUpperCase()) 5 | } 6 | -------------------------------------------------------------------------------- /templates/auto/components/shop-by/functions/getShopByPaginatedLevelItems.ts: -------------------------------------------------------------------------------- 1 | import {getXCartClient} from '../../../app/client' 2 | 3 | export async function getShopByPaginatedLevelItems( 4 | depth: string, 5 | page: number, 6 | perPage?: number, 7 | parentId?: string, 8 | firstLetter?: string, 9 | substring?: string, 10 | ) { 11 | const client = await getXCartClient() 12 | 13 | const data = await client.getMMYLevelsPaginated(String(depth), { 14 | page, 15 | ...(parentId ? {'filter.parent': Number(parentId)} : null), 16 | ...(firstLetter ? {'filter.firstLetter': firstLetter} : null), 17 | ...(substring ? {'filter.substring': substring} : null), 18 | ...(perPage ? {itemsPerPage: perPage} : null), 19 | }) 20 | 21 | return data 22 | } 23 | -------------------------------------------------------------------------------- /templates/auto/components/wishlist/functions/helpers.ts: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | import {clearWishlist} from '~/components/wishlist/functions/wishlistActions' 3 | 4 | export function clearCustomerWishlist(handleClearAction: () => void) { 5 | const hasWishlist = Cookies.get('xc-wishlist') 6 | 7 | if (hasWishlist) { 8 | clearWishlist().then(() => { 9 | Cookies.remove('xc-wishlist') 10 | handleClearAction() 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /templates/auto/components/wishlist/store.ts: -------------------------------------------------------------------------------- 1 | import {atom} from 'jotai' 2 | import {atomWithStorage} from 'jotai/utils' 3 | import Cookies from 'js-cookie' 4 | 5 | export const wishlistItemsAtom = atomWithStorage('xc-wishlist', []) 6 | export const wishlistItems = atom( 7 | get => get(wishlistItemsAtom), 8 | (get, set, itemIds: number[]) => { 9 | const items = itemIds?.length 10 | 11 | if (items === 0) { 12 | Cookies.remove('xc-wishlist') 13 | } 14 | 15 | set(wishlistItemsAtom, itemIds) 16 | 17 | if (items > 0) { 18 | Cookies.set('xc-wishlist', `${items}`, {expires: 7}) 19 | } 20 | }, 21 | ) 22 | -------------------------------------------------------------------------------- /templates/auto/lib/getClient.ts: -------------------------------------------------------------------------------- 1 | import {Client} from '@xcart/storefront' 2 | import {getSession} from 'next-auth/react' 3 | 4 | let client: Client | null = null 5 | 6 | export const getClient = async () => { 7 | if (!client) { 8 | client = new Client( 9 | process.env.NEXT_PUBLIC_XCART_API_URL as string, 10 | process.env.NEXT_PUBLIC_API_KEY as string, 11 | ) 12 | } 13 | 14 | const session = await getSession() 15 | 16 | if ( 17 | session && 18 | // @ts-ignore 19 | session.access_token && 20 | // @ts-ignore 21 | Date.now() < (session.access_token_exp as number) * 1000 22 | ) { 23 | // @ts-ignore 24 | client.setAccessToken(session.access_token as string) 25 | } 26 | 27 | return client 28 | } 29 | 30 | export const clearClient = () => { 31 | client = null 32 | 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /templates/auto/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /templates/auto/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | experimental: { 6 | serverActions: true, 7 | }, 8 | } 9 | 10 | if (process.env.NEXT_PUBLIC_XCART_API_URL) { 11 | try { 12 | const url = new URL(process.env.NEXT_PUBLIC_XCART_API_URL) 13 | if (url.hostname) { 14 | nextConfig.images = { 15 | domains: [url.hostname], 16 | } 17 | } 18 | } catch (e) {} 19 | } 20 | 21 | const withPWA = require('@ducanh2912/next-pwa').default({ 22 | disable: process.env.APP_ENV === 'dev', 23 | dest: 'public', 24 | register: true, 25 | }) 26 | 27 | module.exports = withPWA({ 28 | ...nextConfig, 29 | }) 30 | -------------------------------------------------------------------------------- /templates/auto/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /templates/auto/public/bestsellers.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/bestsellers.jpeg -------------------------------------------------------------------------------- /templates/auto/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/favicon.ico -------------------------------------------------------------------------------- /templates/auto/public/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/icon-192x192.png -------------------------------------------------------------------------------- /templates/auto/public/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/icon-512x512.png -------------------------------------------------------------------------------- /templates/auto/public/main_banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/main_banner.jpeg -------------------------------------------------------------------------------- /templates/auto/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme_color": "#ffffff", 3 | "background_color": "#ffffff", 4 | "display": "standalone", 5 | "orientation": "portrait", 6 | "start_url": "/", 7 | "scope": "/", 8 | "prefer_related_applications": true, 9 | "name": "X-Cart Auto front app", 10 | "short_name": "X-Cart Auto app", 11 | "description": "X-Cart Auto demo store front app", 12 | "icons": [ 13 | { 14 | "src": "/icon-192x192.png", 15 | "sizes": "192x192", 16 | "type": "image/png", 17 | "purpose": "any maskable" 18 | }, 19 | { 20 | "src": "/icon-512x512.png", 21 | "sizes": "512x512", 22 | "type": "image/png" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /templates/auto/public/recent_arrivals.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/recent_arrivals.jpeg -------------------------------------------------------------------------------- /templates/auto/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /templates/auto/public/storybook-product-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/storybook-product-image.jpg -------------------------------------------------------------------------------- /templates/auto/public/storybook-product-image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/storybook-product-image2.jpg -------------------------------------------------------------------------------- /templates/auto/public/storybook-product-image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/storybook-product-image3.jpg -------------------------------------------------------------------------------- /templates/auto/public/stylish_banner1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/stylish_banner1.jpeg -------------------------------------------------------------------------------- /templates/auto/public/stylish_banner2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/stylish_banner2.jpeg -------------------------------------------------------------------------------- /templates/auto/public/touch-icon-iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xcart/frontapp/2024741650d0ad547c4a584d7fc8318e4e7b1a82/templates/auto/public/touch-icon-iphone.png -------------------------------------------------------------------------------- /templates/auto/stories/Cart/CartDrawer.stories.tsx: -------------------------------------------------------------------------------- 1 | import {StoryObj, Meta} from '@storybook/react' 2 | import {CartDrawer as Cart} from '~/components/cart/CartDrawer' 3 | 4 | const meta: Meta = { 5 | title: 'Components/Cart/CartDrawer', 6 | component: Cart, 7 | } 8 | 9 | export default meta 10 | 11 | export const CartDrawer: StoryObj = { 12 | render: args => , 13 | } 14 | -------------------------------------------------------------------------------- /templates/auto/stories/Categories.stories.tsx: -------------------------------------------------------------------------------- 1 | import {StoryObj, Meta} from '@storybook/react' 2 | import {Categories as Menu} from '~/components/global/Categories' 3 | import {ROOT_CATEGORIES} from './constants' 4 | 5 | const meta: Meta = { 6 | title: 'Components/Global/Categories', 7 | component: Menu, 8 | } 9 | 10 | export default meta 11 | 12 | export const Categories: StoryObj = { 13 | render: args => , 14 | } 15 | 16 | Categories.args = { 17 | rootClasses: 'flex items-center', 18 | itemClasses: 'px-unit-2 whitespace-nowrap', 19 | linkClasses: 'text-gray-700 hover:text-primary hover:no-underline', 20 | categories: ROOT_CATEGORIES, 21 | } 22 | -------------------------------------------------------------------------------- /templates/auto/stories/Elements/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | import {StoryObj, Meta} from '@storybook/react' 2 | import {Button} from '~/components/elements/Button' 3 | 4 | const meta: Meta = { 5 | title: 'Components/Elements/Button', 6 | component: Button, 7 | } 8 | 9 | export default meta 10 | 11 | type Story = StoryObj 12 | 13 | export const Primary: Story = { 14 | render: () =>