├── .npmrc ├── .prettierignore ├── __mocks__ ├── styleMock.js ├── fileMock.js ├── stories │ ├── orderSubscriptionNowMock.ts │ ├── productPriceMock.ts │ ├── orderShipmentMock.ts │ ├── createCustomerAccountCardMock.ts │ ├── updateCustomerAccountCardMock.ts │ ├── returnReasonsMock.ts │ ├── LocationInventoryCollectionMock.ts │ ├── shippingRateMock.ts │ ├── shippingRates.ts │ ├── productOptionSelectMock.ts │ ├── customerAccountCardsMock.ts │ ├── fulfillmentOptionsMock.ts │ ├── productOptionTextBoxMock.ts │ ├── updateOrderBillingInfoMock.ts │ └── updateCustomerAccountContact.ts └── msw │ └── server.js ├── __test__ ├── utils │ └── index.ts └── e2e │ └── integration │ ├── cart │ └── CartItemList │ │ └── CartItemList.spec.tsx │ └── common │ └── ProductItem │ └── ProductItem.spec.tsx ├── lib ├── api │ ├── util │ │ ├── index.ts │ │ ├── cache.ts │ │ ├── api-auth-client.ts │ │ ├── fetch-gql.ts │ │ └── config-helpers.ts │ ├── handlers │ │ ├── index.ts │ │ ├── category-tree.ts │ │ ├── search.ts │ │ └── graphql.ts │ └── operations │ │ ├── get-product.ts │ │ ├── get-search-suggestions.ts │ │ ├── index.ts │ │ ├── get-product-search.ts │ │ ├── get-cart.ts │ │ ├── get-category.ts │ │ ├── get-multi-ship-checkout.ts │ │ ├── get-checkout.ts │ │ └── get-category-tree.ts ├── types │ ├── Breadcrumb.ts │ ├── Icons.ts │ ├── GeoCoords.ts │ ├── NavigationLink.ts │ ├── ProductProperties.ts │ ├── FulfilmentInfo.ts │ ├── KiboRequest.ts │ ├── CategoryTreeResponse.ts │ ├── BillingInfo.ts │ ├── NextExtensions.ts │ ├── Fulfillment.ts │ ├── ProductCustom.ts │ ├── AddressBook.ts │ ├── Price.ts │ ├── OrderReturnItems.ts │ ├── SearchParams.ts │ ├── LocationCustom.ts │ ├── FacetResultDataType.ts │ ├── MyProfile.ts │ ├── Checkout.ts │ ├── index.ts │ └── Wishlist.ts ├── createEmotionCache.ts ├── helpers │ ├── buildCreateOrderParams.ts │ ├── __tests__ │ │ ├── cookieHelper.spec.ts │ │ ├── uiHelpers.spec.ts │ │ ├── buildBreadcrumbsParams.spec.js │ │ ├── buildCreateWishlistItemParams.spec.ts │ │ ├── buildAddToWishlistParams.spec.ts │ │ ├── buildRemoveWishlistItemParams.spec.ts │ │ ├── buildProductSearchParams.spec.ts │ │ └── buildAddOrRemoveWishlistItemParams.spec.ts │ ├── buildWishlistParams.ts │ ├── buildCreateWishlistItemParams.ts │ ├── uiHelpers.ts │ ├── buildBreadcrumbsParams.ts │ ├── buildSubscriptionFulfillmentInfoParams.ts │ ├── buildAddToCartParams.ts │ ├── buildAddressParams.ts │ ├── buildRemoveWishlistItemParams.ts │ ├── buildAddOrRemoveWishlistItemParams.ts │ ├── buildCreateOrderReturnItemsParams.ts │ ├── findParentNode.ts │ ├── buildAddToWishlistParams.ts │ ├── index.ts │ ├── buildSubscriptionParams.ts │ └── tokenizeCreditCardPayment.ts ├── gql │ ├── queries │ │ ├── get-return-reasons.ts │ │ ├── cart │ │ │ └── get-cart-query.ts │ │ ├── getWishlistQuery.ts │ │ ├── get-product.ts │ │ ├── get-shipping-rates.ts │ │ ├── checkout │ │ │ ├── get-customer-account-cards.ts │ │ │ ├── get-checkout-destinations.ts │ │ │ ├── get-multi-ship-checkout-shipping-methods.ts │ │ │ ├── get-checkout-destination.ts │ │ │ ├── get-or-create-checkout-from-cart-mutation.ts │ │ │ ├── get-checkout-query.ts │ │ │ └── get-multi-ship-checkout-query.ts │ │ ├── get-search-suggestions.ts │ │ ├── get-product-price.ts │ │ ├── get-product-location-inventory.ts │ │ ├── get-user-addresses-query.ts │ │ ├── get-orders.ts │ │ ├── get-returns.ts │ │ ├── product-search.ts │ │ └── get-category-tree.ts │ ├── mutations │ │ ├── cart │ │ │ ├── deleteCartItemMutation.ts │ │ │ ├── updateCartItemQuantityMutation.ts │ │ │ └── updateCartItemMutation.ts │ │ ├── subscription │ │ │ ├── orderSubscriptionNow.ts │ │ │ ├── update-subscription-payment-mutation.ts │ │ │ ├── edit-subscription-frequency-mutation.ts │ │ │ ├── pause-subscription-mutation.ts │ │ │ ├── update-subscription-next-order-date-mutation.ts │ │ │ ├── skip-next-subscription-mutation.ts │ │ │ └── delete-subscription-mutation.ts │ │ ├── user │ │ │ ├── getUser.ts │ │ │ ├── updateAccount.ts │ │ │ ├── updatePassword.ts │ │ │ └── login.ts │ │ ├── my-account │ │ │ ├── delete-customer-account-card.ts │ │ │ ├── delete-customer-account-contact.ts │ │ │ ├── create-customer-account-card.ts │ │ │ ├── update-customer-account-card.ts │ │ │ ├── create-customer-account-contact.ts │ │ │ └── update-customer-account-contact.ts │ │ ├── delete-customer-account-contact.ts │ │ ├── wishlist │ │ │ ├── deleteWishlistItemMutation.ts │ │ │ ├── createWishlistMutation.ts │ │ │ └── createWishlistItemMutation.ts │ │ ├── coupon │ │ │ ├── deleteOrderCoupon.ts │ │ │ ├── delete-checkout-coupon.ts │ │ │ ├── deleteCheckoutCouponMutation.ts │ │ │ ├── deleteCartCoupon.ts │ │ │ ├── updateCartCoupon.ts │ │ │ ├── updateCheckoutCouponMutation.ts │ │ │ └── updateOrderCoupon.ts │ │ ├── checkout │ │ │ ├── create-order-mutation.ts │ │ │ ├── set-billing-info.ts │ │ │ ├── create-checkout-shipping-method.ts │ │ │ ├── set-shipping-info.ts │ │ │ ├── create-checkout-payment-action.ts │ │ │ ├── create-checkout-action-mutation.ts │ │ │ ├── create-checkout-destination.ts │ │ │ ├── update-checkout-payment-action.ts │ │ │ ├── update-checkout-destination.ts │ │ │ ├── set-personal-info.ts │ │ │ ├── add-payment-method-to-checkout.ts │ │ │ └── split-order-shipment.ts │ │ ├── update-customer-account-contact.ts │ │ ├── order-return-items │ │ │ └── createReturnItemMutation.ts │ │ └── product │ │ │ └── configureProductMutation.ts │ ├── fragments │ │ ├── category-info.ts │ │ ├── paymentCardItems.ts │ │ ├── index.ts │ │ ├── destinationContact.ts │ │ ├── userContacts.ts │ │ ├── wishlist.ts │ │ ├── search.ts │ │ └── configureProduct.ts │ └── client │ │ ├── index.ts │ │ └── client.spec.js ├── getters │ └── index.ts └── react-query │ └── queryClient.ts ├── components ├── common │ ├── ProductItemList │ │ └── index.tsx │ ├── Layout │ │ └── Layout.tsx │ ├── GlobalFetchingIndicator │ │ └── GlobalFetchingIndicator.tsx │ ├── KiboLogo │ │ ├── KiboLogo.stories.tsx │ │ └── KiboLogo.tsx │ ├── PasswordValidation │ │ └── PasswordValidation.stories.tsx │ ├── FilterTiles │ │ └── FilterTiles.stories.tsx │ ├── KiboImage │ │ ├── KiboImage.stories.tsx │ │ └── KiboImage.tsx │ ├── PaymentCard │ │ ├── PaymentCard.stories.tsx │ │ └── PaymentCard.spec.js │ ├── AddressCard │ │ ├── AddressCard.stories.tsx │ │ └── AddressCard.spec.js │ ├── ReturnItemList │ │ └── ReturnItemList.stories.tsx │ ├── FullWidthDivider │ │ └── FullWidthDivider.tsx │ ├── SearchBar │ │ └── SearchBar.stories.tsx │ ├── FulfillmentOptions │ │ └── FulfillmentOptions.stories.tsx │ ├── ReviewProductItemsWithAddresses │ │ └── ReviewProductItemsWithAddresses.stories.tsx │ └── OrderSummary │ │ └── OrderSummary.spec.js ├── core │ ├── index.tsx │ └── Breadcrumbs │ │ └── KiboBreadcrumbs.stories.tsx ├── home │ ├── index.ts │ ├── SmallBanner │ │ └── SmallBanner.stories.tsx │ └── ContentTile │ │ └── ContentTile.spec.tsx ├── layout │ ├── FullWidthLayout │ │ └── FullWidthLayout.tsx │ ├── DefaultLayout │ │ └── DefaultLayout.tsx │ ├── Footer │ │ └── Footer.stories.tsx │ ├── SearchSuggestions │ │ └── SearchSuggestions.stories.tsx │ ├── Login │ │ ├── LoginDialog │ │ │ └── LoginDialog.stories.tsx │ │ └── LoginContent │ │ │ └── LoginContent.stories.tsx │ ├── RegisterAccount │ │ ├── Content │ │ │ └── Content.stories.tsx │ │ └── RegisterAccountDialog │ │ │ └── RegisterAccountDialog.stories.tsx │ ├── AppHeader │ │ ├── Icons │ │ │ ├── HamburgerIcon │ │ │ │ └── HamburgerIcon.tsx │ │ │ ├── AccountIcon │ │ │ │ └── AccountIcon.tsx │ │ │ └── CartIcon │ │ │ │ └── CartIcon.tsx │ │ └── Checkout │ │ │ └── CheckoutHeader.spec.tsx │ └── MegaMenu │ │ └── MegaMenu.stories.tsx ├── cart │ ├── index.ts │ ├── CartItemActions │ │ ├── CartItemActions.stories.tsx │ │ └── CartItemActions.spec.tsx │ ├── CartItemList │ │ └── CartItemList.spec.tsx │ └── CartItemActionsMobile │ │ └── CartItemActionsMobile.stories.tsx ├── order │ ├── index.ts │ ├── OrderConfirmation │ │ └── OrderConfirmation.stories.tsx │ ├── OrderHistoryItem │ │ └── OrderHistoryItem.stories.tsx │ ├── ViewOrderStatus │ │ └── ViewOrderStatus.stories.tsx │ ├── OrderReturnItems │ │ └── OrderReturnItems.stories.tsx │ └── ViewOrderDetails │ │ └── ViewOrderDetails.stories.tsx ├── product-listing │ ├── index.ts │ ├── FacetList │ │ └── FacetList.stories.tsx │ ├── FacetItem │ │ ├── FacetItem.stories.tsx │ │ └── FacetItem.spec.tsx │ ├── CategoryFacet │ │ └── CategoryFacet.stories.tsx │ └── FacetItemList │ │ └── FacetItemList.spec.tsx ├── dialogs │ ├── AddToCartConfirmation │ │ ├── Title │ │ │ ├── Title.stories.tsx │ │ │ └── Title.spec.tsx │ │ ├── Content │ │ │ ├── Content.stories.tsx │ │ │ └── Content.spec.tsx │ │ ├── Actions │ │ │ └── Actions.stories.tsx │ │ └── AddToCartDialog │ │ │ └── AddToCartDialog.stories.tsx │ ├── WishlistPopover │ │ └── WishlistPopover.stories.tsx │ ├── EditOrderDateDialog │ │ └── EditOrderDateDialog.stories.tsx │ ├── ConfirmationDialog │ │ └── ConfirmationDialog.stories.tsx │ ├── OrderReturnItemsDialog │ │ └── OrderReturnItemsDialog.stories.tsx │ └── Store │ │ └── MyStoreDialog │ │ └── MyStoreDialog.stories.tsx ├── my-account │ ├── index.ts │ └── MyProfile │ │ └── MyProfile.stories.tsx ├── page-templates │ ├── OrderStatusTemplate │ │ └── OrderStatusTemplate.stories.tsx │ ├── MyAccountTemplate │ │ └── MyAccountTemplate.stories.tsx │ └── OrderHistoryTemplate │ │ └── OrderHistoryTemplate.stories.tsx ├── checkout │ ├── CardDetailsForm │ │ └── CardDetailsForm.stories.tsx │ └── PaymentCardDetailsView │ │ └── PaymentCardDetailsView.stories.tsx └── product │ ├── ProductOptionCheckbox │ ├── ProductOptionCheckbox.stories.tsx │ ├── ProductOptionCheckbox.spec.js │ └── ProductOptionCheckbox.tsx │ ├── ProductOption │ ├── ProductOption.stories.tsx │ └── ProductOption.spec.tsx │ ├── ProductQuickViewModal │ └── ProductQuickViewDialog.stories.tsx │ ├── ProductRecommendations │ └── ProductRecommendations.stories.tsx │ └── ProductOptionList │ ├── ProductOptionList.tsx │ └── ProductOptionList.spec.tsx ├── .vs └── slnx.sqlite ├── commitlint.config.js ├── public ├── favicon.ico ├── kibo_logo.png ├── locales │ └── es │ │ └── common.json └── icons │ └── facebook.svg ├── src ├── pages │ ├── api │ │ ├── search.ts │ │ ├── graphql.ts │ │ └── category-tree.ts │ ├── order-status.tsx │ └── my-account │ │ └── index.tsx └── middleware.ts ├── .env.development ├── .env.production ├── docs ├── images │ ├── private-key-flow.png │ └── builder-io-organizations.png └── hooks │ └── .nojekyll ├── .husky └── pre-commit ├── .prettierrc.json ├── codegen.yml ├── .env.template ├── context └── index.tsx ├── next-env.d.ts ├── next-i18next.config.js ├── .storybook └── i18n.js ├── .github ├── workflows │ └── commit-lint.yml └── lighthouse │ └── budget.json ├── hooks ├── custom │ ├── usePaymentTypes │ │ ├── usePaymentTypes.spec.tsx │ │ └── usePaymentTypes.ts │ └── useCurrentLocation │ │ └── useCurrentLocation.spec.ts ├── queries │ ├── useCartQueries │ │ └── useCartQueries.spec.ts │ ├── useUserQueries │ │ └── useUserQueries.spec.ts │ ├── useWishlistQueries │ │ └── useWishlistQueries.spec.ts │ ├── useProductPriceQueries │ │ └── useProductPriceQueries.spec.ts │ ├── useProductsQueries │ │ └── useProductsQueries.spec.ts │ ├── useCategoryTreeQueries │ │ └── useCategoryTreeQueries.spec.js │ ├── useCustomerCardsQueries │ │ └── useCustomerCardsQueries.spec.ts │ ├── useCustomerContactsQueries │ │ └── useCustomerContactsQueries.spec.ts │ ├── subscription │ │ └── useSubscriptionsQueries │ │ │ └── useSubscriptionsQueries.spec.ts │ ├── useCheckoutQueries │ │ └── useCheckoutQueries.spec.ts │ ├── useSearchSuggestionsQueries │ │ └── useSearchSuggestionsQueries.spec.ts │ ├── useUserOrderQueries │ │ └── useUserOrderQueries.spec.ts │ ├── multiShip │ │ ├── useCheckoutQueries │ │ │ └── useCheckoutQueries.spec.ts │ │ ├── useCheckoutDestinationsQueries │ │ │ └── useCheckoutDestinationsQueries.spec.tsx │ │ └── useCheckoutDestinationQueries │ │ │ └── useCheckoutDestinationQueries.spec.tsx │ ├── useStoreLocationsQueries │ │ └── useStoreLocationsQueries.spec.ts │ └── useProductSearchQueries │ │ └── useProductSearchQueries.spec.ts └── mutations │ ├── useCartMutations │ └── useRemoveCartItem │ │ └── useRemoveCartItemMutation.spec.ts │ ├── useCreateOrderMutations │ └── useCreateOrderMutation.spec.tsx │ ├── useCouponMutations │ ├── useDeleteCartCouponMutation │ │ └── useDeleteCartCouponMutation.spec.ts │ ├── useDeleteOrderCouponMutation │ │ └── useDeleteOrderCouponMutation.spec.ts │ ├── useUpdateCartCouponMutation │ │ └── useUpdateCartCouponMutation.spec.ts │ └── useUpdateOrderCouponMutation │ │ └── useUpdateOrderCouponMutation.spec.ts │ ├── useUserMutations │ └── useUserMutations.spec.ts │ ├── multiShip │ ├── useCreateCheckoutActionMutation │ │ └── useCreateCheckoutActionMutation.spec.tsx │ ├── useDeleteCheckoutCouponMutation │ │ └── useDeleteCheckoutCouponMutation.spec.ts │ └── useUpdateCheckoutCouponMutation │ │ └── useUpdateCheckoutCouponMutation.spec.ts │ └── useCheckoutMutations │ └── useCreateFromCartMutation.spec.ts ├── .gitignore ├── sonar-project.properties ├── styles └── global.css ├── cms └── components │ └── CmsHomePageProducts │ └── CmsHomePageProducts.stories.tsx └── tsconfig.json /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | .husky -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub' 2 | -------------------------------------------------------------------------------- /__test__/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './renderWithQueryClient' 2 | -------------------------------------------------------------------------------- /lib/api/util/index.ts: -------------------------------------------------------------------------------- 1 | export { default as fetcher } from './fetch-gql' 2 | -------------------------------------------------------------------------------- /components/common/ProductItemList/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './ProductItemList' 2 | -------------------------------------------------------------------------------- /lib/types/Breadcrumb.ts: -------------------------------------------------------------------------------- 1 | export type BreadCrumb = Record 2 | -------------------------------------------------------------------------------- /lib/types/Icons.ts: -------------------------------------------------------------------------------- 1 | export type IconProps = { 2 | size: 'small' | 'medium' | 'large' 3 | } 4 | -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/kibocommerce-nextjs-starter/main/.vs/slnx.sqlite -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /lib/types/GeoCoords.ts: -------------------------------------------------------------------------------- 1 | export interface GeoCoords { 2 | longitude: number 3 | latitude: number 4 | } 5 | -------------------------------------------------------------------------------- /lib/types/NavigationLink.ts: -------------------------------------------------------------------------------- 1 | export interface NavigationLink { 2 | link: string 3 | text: string 4 | } 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/kibocommerce-nextjs-starter/main/public/favicon.ico -------------------------------------------------------------------------------- /public/kibo_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/kibocommerce-nextjs-starter/main/public/kibo_logo.png -------------------------------------------------------------------------------- /src/pages/api/search.ts: -------------------------------------------------------------------------------- 1 | import { searchHandler } from '@/lib/api/handlers' 2 | 3 | export default searchHandler 4 | -------------------------------------------------------------------------------- /src/pages/api/graphql.ts: -------------------------------------------------------------------------------- 1 | import { graphQLHandler } from '@/lib/api/handlers' 2 | 3 | export default graphQLHandler 4 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | KIBO_API_HOST= 2 | KIBO_AUTH_HOST= 3 | KIBO_CLIENT_ID= 4 | KIBO_SHARED_SECRET= 5 | KIBO_PCI_HOST= 6 | BUILDER_IO_API_KEY= -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | KIBO_API_HOST= 2 | KIBO_AUTH_HOST= 3 | KIBO_CLIENT_ID= 4 | KIBO_SHARED_SECRET= 5 | KIBO_PCI_HOST= 6 | BUILDER_IO_API_KEY= -------------------------------------------------------------------------------- /docs/images/private-key-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/kibocommerce-nextjs-starter/main/docs/images/private-key-flow.png -------------------------------------------------------------------------------- /src/pages/api/category-tree.ts: -------------------------------------------------------------------------------- 1 | import { categoryTreeHandler } from '@/lib/api/handlers' 2 | 3 | export default categoryTreeHandler 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | npm run lint 6 | npm run validate-types -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "trailingComma": "es5", 6 | "printWidth": 100 7 | } 8 | -------------------------------------------------------------------------------- /docs/images/builder-io-organizations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/kibocommerce-nextjs-starter/main/docs/images/builder-io-organizations.png -------------------------------------------------------------------------------- /__mocks__/stories/orderSubscriptionNowMock.ts: -------------------------------------------------------------------------------- 1 | export const orderSubscriptionNowMock = { 2 | orderSubscriptionNow: { 3 | id: 'test-id', 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /docs/hooks/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /components/core/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as KiboBreadcrumbs } from './Breadcrumbs/KiboBreadcrumbs' 2 | export { default as ImageGallery } from './ImageGallery/ImageGallery' 3 | -------------------------------------------------------------------------------- /lib/types/ProductProperties.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from '../gql/types' 2 | 3 | export interface ProductProperties { 4 | name: Maybe | undefined 5 | value: string | undefined 6 | } 7 | -------------------------------------------------------------------------------- /__mocks__/stories/productPriceMock.ts: -------------------------------------------------------------------------------- 1 | import type { ProductPrice } from '@/lib/gql/types' 2 | 3 | export const productPriceMock: ProductPrice = { 4 | price: 100, 5 | salePrice: null, 6 | } 7 | -------------------------------------------------------------------------------- /codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: 'https://t17194-s21127.dev10.kubedevext.kibo-dev.com/graphql' 3 | generates: 4 | types.ts: 5 | plugins: 6 | - '@graphql-codegen/typescript' 7 | -------------------------------------------------------------------------------- /lib/types/FulfilmentInfo.ts: -------------------------------------------------------------------------------- 1 | import type { SbContact } from '@/lib/gql/types' 2 | 3 | export interface FulfillmentInfo { 4 | formattedAddress: string 5 | fulfillmentContact: SbContact 6 | } 7 | -------------------------------------------------------------------------------- /lib/api/handlers/index.ts: -------------------------------------------------------------------------------- 1 | export { default as graphQLHandler } from './graphql' 2 | export { default as searchHandler } from './search' 3 | export { default as categoryTreeHandler } from './category-tree' 4 | -------------------------------------------------------------------------------- /lib/createEmotionCache.ts: -------------------------------------------------------------------------------- 1 | import createCache from '@emotion/cache' 2 | 3 | const createEmotionCache = () => { 4 | return createCache({ key: 'css' }) 5 | } 6 | 7 | export default createEmotionCache 8 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | KIBO_API_HOST=t1234-s1234.sandbox.mozu.com 2 | KIBO_AUTH_HOST=home.mozu.com 3 | KIBO_CLIENT_ID=KIBO_APP.1.0.0.Release 4 | KIBO_SHARED_SECRET=12345_Secret 5 | BUILDER_IO_API_KEY=1f098a44b17d4df688d2afdc8a10ac7d -------------------------------------------------------------------------------- /context/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './AuthContext' 2 | export * from './ModalContext' 3 | export * from './CheckoutStepContext/CheckoutStepContext' 4 | export * from './HeaderContext' 5 | export * from './SnackbarContext/SnackbarContext' 6 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /lib/types/KiboRequest.ts: -------------------------------------------------------------------------------- 1 | import type { IncomingMessage } from 'http' 2 | 3 | export type RequestCookies = { 4 | [key: string]: string | undefined 5 | } 6 | 7 | export type KiboRequest = IncomingMessage & { cookies: RequestCookies } 8 | -------------------------------------------------------------------------------- /__mocks__/stories/orderShipmentMock.ts: -------------------------------------------------------------------------------- 1 | export const orderShipment = { 2 | shipments: [ 3 | { 4 | id: '137a94b6402be000013718d80000678b', 5 | orderNumber: 3234, 6 | shippingTotal: 434, 7 | }, 8 | ], 9 | } 10 | -------------------------------------------------------------------------------- /components/home/index.ts: -------------------------------------------------------------------------------- 1 | export { default as KiboHeroCarousel } from './Carousel/KiboHeroCarousel' 2 | export { default as ContentTile } from './ContentTile/ContentTile' 3 | export { default as SmallBanner } from './SmallBanner/SmallBanner' 4 | -------------------------------------------------------------------------------- /lib/helpers/buildCreateOrderParams.ts: -------------------------------------------------------------------------------- 1 | import { CrOrder } from '../gql/types' 2 | 3 | export const buildCreateOrderParams = (order: CrOrder) => ({ 4 | orderId: order.id as string, 5 | orderActionInput: { actionName: 'SubmitOrder' }, 6 | }) 7 | -------------------------------------------------------------------------------- /__mocks__/msw/server.js: -------------------------------------------------------------------------------- 1 | import { setupServer } from 'msw/node' 2 | 3 | import { handlers } from './handlers' 4 | 5 | // This configures a request mocking server with the given request handlers. 6 | export const server = setupServer(...handlers) 7 | -------------------------------------------------------------------------------- /lib/gql/queries/get-return-reasons.ts: -------------------------------------------------------------------------------- 1 | const getReturnReasonsQuery = /* GraphQL */ ` 2 | query returnReasons { 3 | returnReasons { 4 | items 5 | totalCount 6 | } 7 | } 8 | ` 9 | 10 | export default getReturnReasonsQuery 11 | -------------------------------------------------------------------------------- /lib/types/CategoryTreeResponse.ts: -------------------------------------------------------------------------------- 1 | import type { CategoryCollection } from '@/lib/gql/types' 2 | 3 | export interface CategoryTreeResponse { 4 | data: { 5 | categoriesTree: { 6 | items: CategoryCollection 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/gql/mutations/cart/deleteCartItemMutation.ts: -------------------------------------------------------------------------------- 1 | const deleteCartItemMutation = /* GraphQL */ ` 2 | mutation deleteCartItem($itemId: String!) { 3 | deleteCurrentCartItem(cartItemId: $itemId) 4 | } 5 | ` 6 | 7 | export default deleteCartItemMutation 8 | -------------------------------------------------------------------------------- /next-i18next.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | i18n: { 5 | defaultLocale: 'en', 6 | locales: ['en', 'es'], 7 | localeDetection: true, 8 | localePath: path.resolve('./public/locales'), 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /lib/types/BillingInfo.ts: -------------------------------------------------------------------------------- 1 | import type { SavedCard } from '@/lib/types' 2 | 3 | import type { SbContact } from '@/lib/gql/types' 4 | 5 | export interface BillingInfo { 6 | formattedAddress: string 7 | cardInfo: SavedCard 8 | billingAddressInfo: SbContact 9 | } 10 | -------------------------------------------------------------------------------- /lib/types/NextExtensions.ts: -------------------------------------------------------------------------------- 1 | import type { ReactElement, ReactNode } from 'react' 2 | 3 | import type { NextPage } from 'next' 4 | 5 | export type NextPageWithLayout

, IP = P> = NextPage & { 6 | getLayout?: (page: ReactElement) => ReactNode 7 | } 8 | -------------------------------------------------------------------------------- /components/layout/FullWidthLayout/FullWidthLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react' 2 | 3 | import { Box } from '@mui/material' 4 | 5 | const FullWidthLayout = (page: ReactElement) => { 6 | return {page} 7 | } 8 | 9 | export default FullWidthLayout 10 | -------------------------------------------------------------------------------- /lib/gql/queries/cart/get-cart-query.ts: -------------------------------------------------------------------------------- 1 | import { cartDetails } from '../../fragments/cart' 2 | const getCartQuery = /* GraphQL */ ` 3 | ${cartDetails} 4 | 5 | query cart { 6 | currentCart { 7 | ...cartDetails 8 | } 9 | } 10 | ` 11 | export default getCartQuery 12 | -------------------------------------------------------------------------------- /lib/types/Fulfillment.ts: -------------------------------------------------------------------------------- 1 | export interface FulfillmentOption { 2 | shortName?: string 3 | value?: string 4 | code?: string 5 | name?: string 6 | label?: string 7 | details?: string 8 | unavailableDetails?: string 9 | isRequired?: boolean 10 | disabled?: boolean 11 | } 12 | -------------------------------------------------------------------------------- /lib/gql/fragments/category-info.ts: -------------------------------------------------------------------------------- 1 | export const categoryInfo = /* GraphQL */ ` 2 | fragment categoryInfo on PrCategory { 3 | count 4 | categoryId 5 | categoryCode 6 | isDisplayed 7 | content { 8 | name 9 | slug 10 | description 11 | } 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /components/common/Layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react' 2 | 3 | import { Container } from '@mui/material' 4 | 5 | const Layout = ({ children }: { children: ReactElement }) => { 6 | return {children} 7 | } 8 | 9 | export default Layout 10 | -------------------------------------------------------------------------------- /lib/gql/fragments/paymentCardItems.ts: -------------------------------------------------------------------------------- 1 | export const paymentCardItems = /* GraphQL */ ` 2 | fragment customerAccountCardItems on Card { 3 | id 4 | nameOnCard 5 | cardType 6 | expireMonth 7 | expireYear 8 | cardNumberPart 9 | contactId 10 | isDefaultPayMethod 11 | } 12 | ` 13 | -------------------------------------------------------------------------------- /lib/gql/mutations/subscription/orderSubscriptionNow.ts: -------------------------------------------------------------------------------- 1 | const orderSubscriptionNow = /* GraphQL */ ` 2 | mutation orderSubscriptionNow($subscriptionId: String!) { 3 | orderSubscriptionNow(subscriptionId: $subscriptionId) { 4 | id 5 | } 6 | } 7 | ` 8 | 9 | export default orderSubscriptionNow 10 | -------------------------------------------------------------------------------- /lib/gql/mutations/user/getUser.ts: -------------------------------------------------------------------------------- 1 | export const getCurrentUser = /* GraphQL */ ` 2 | query getUser { 3 | customerAccount: getCurrentAccount { 4 | id 5 | userId 6 | firstName 7 | lastName 8 | emailAddress 9 | userName 10 | isAnonymous 11 | } 12 | } 13 | ` 14 | -------------------------------------------------------------------------------- /lib/gql/mutations/my-account/delete-customer-account-card.ts: -------------------------------------------------------------------------------- 1 | const deleteCustomerAccountCard = /* GraphQL */ ` 2 | mutation deleteCustomerAccountCard($accountId: Int!, $cardId: String!) { 3 | deleteCustomerAccountCard(accountId: $accountId, cardId: $cardId) 4 | } 5 | ` 6 | export default deleteCustomerAccountCard 7 | -------------------------------------------------------------------------------- /lib/types/ProductCustom.ts: -------------------------------------------------------------------------------- 1 | import { Product } from '../gql/types' 2 | 3 | export type ProductWithFulfillmentOptions = { 4 | fulfillmentMethod: string 5 | fulfillmentMethodShortName: string 6 | purchaseLocationCode: string 7 | } 8 | 9 | export type ProductCustom = Product & ProductWithFulfillmentOptions 10 | -------------------------------------------------------------------------------- /lib/gql/queries/getWishlistQuery.ts: -------------------------------------------------------------------------------- 1 | import { wishlist } from '@/lib/gql/fragments' 2 | 3 | const getWishlistQuery = /* GraphQL */ ` 4 | query wishlists { 5 | wishlists { 6 | items { 7 | ...wishlist 8 | } 9 | } 10 | } 11 | ${wishlist} 12 | ` 13 | 14 | export default getWishlistQuery 15 | -------------------------------------------------------------------------------- /lib/gql/mutations/delete-customer-account-contact.ts: -------------------------------------------------------------------------------- 1 | export const deleteCustomerAccountContact = /* GraphQL */ ` 2 | mutation deleteUserAddress($accountId: Int!, $contactId: Int!) { 3 | deleteCustomerAccountContact(accountId: $accountId, contactId: $contactId) 4 | } 5 | ` 6 | 7 | export default deleteCustomerAccountContact 8 | -------------------------------------------------------------------------------- /lib/types/AddressBook.ts: -------------------------------------------------------------------------------- 1 | import type { Address } from '@/lib/types' 2 | 3 | export interface AddressParams { 4 | accountId: number 5 | address: Address 6 | isDefaultAddress: boolean 7 | addressType: string 8 | } 9 | 10 | export interface DeleteAddressParams { 11 | accountId: number 12 | contactId: number 13 | } 14 | -------------------------------------------------------------------------------- /lib/types/Price.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from '../gql/types' 2 | 3 | export interface PriceOnly { 4 | price?: Maybe 5 | } 6 | 7 | export interface SalePrice { 8 | price?: Maybe 9 | salePrice?: Maybe 10 | } 11 | 12 | export interface PriceRange { 13 | lower: SalePrice 14 | upper: SalePrice 15 | } 16 | -------------------------------------------------------------------------------- /components/layout/DefaultLayout/DefaultLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react' 2 | 3 | import { Container } from '@mui/material' 4 | 5 | const DefaultLayout = ({ children }: { children: ReactElement }) => { 6 | return {children} 7 | } 8 | 9 | export default DefaultLayout 10 | -------------------------------------------------------------------------------- /lib/gql/mutations/my-account/delete-customer-account-contact.ts: -------------------------------------------------------------------------------- 1 | const deleteCustomerAccountContact = /* GraphQL */ ` 2 | mutation deleteCustomerAccountContact($accountId: Int!, $contactId: Int!) { 3 | deleteCustomerAccountContact(accountId: $accountId, contactId: $contactId) 4 | } 5 | ` 6 | export default deleteCustomerAccountContact 7 | -------------------------------------------------------------------------------- /lib/gql/mutations/wishlist/deleteWishlistItemMutation.ts: -------------------------------------------------------------------------------- 1 | export const deleteWishListItemMutation = /* GraphQL */ ` 2 | mutation deletewishlistitem($wishlistId: String!, $wishlistItemId: String!) { 3 | deleteWishlistItem(wishlistId: $wishlistId, wishlistItemId: $wishlistItemId) 4 | } 5 | ` 6 | export default deleteWishListItemMutation 7 | -------------------------------------------------------------------------------- /lib/gql/mutations/coupon/deleteOrderCoupon.ts: -------------------------------------------------------------------------------- 1 | const deleteOrderCouponMutation = /* GraphQL */ ` 2 | mutation deleteOrderCoupon($orderId: String!, $couponCode: String!) { 3 | deleteOrderCoupon(orderId: $orderId, couponCode: $couponCode) { 4 | orderNumber 5 | } 6 | } 7 | ` 8 | 9 | export default deleteOrderCouponMutation 10 | -------------------------------------------------------------------------------- /lib/gql/mutations/coupon/delete-checkout-coupon.ts: -------------------------------------------------------------------------------- 1 | const deleteCheckoutCouponMutation = /* GraphQL */ ` 2 | mutation deleteCheckoutCoupon($checkoutId: String!, $couponCode: String!) { 3 | deleteCheckoutCoupon(checkoutId: $checkoutId, couponCode: $couponCode) { 4 | id 5 | } 6 | } 7 | ` 8 | 9 | export default deleteCheckoutCouponMutation 10 | -------------------------------------------------------------------------------- /lib/api/operations/get-product.ts: -------------------------------------------------------------------------------- 1 | import { fetcher } from '@/lib/api/util' 2 | import { getProductQuery } from '@/lib/gql/queries' 3 | 4 | export default async function getProduct(productCode: any) { 5 | const variables = { 6 | productCode, 7 | } 8 | const response = await fetcher({ query: getProductQuery, variables }, null) 9 | return response.data?.product 10 | } 11 | -------------------------------------------------------------------------------- /lib/gql/mutations/cart/updateCartItemQuantityMutation.ts: -------------------------------------------------------------------------------- 1 | const updateCartItemQuantityMutation = /* GraphQL */ ` 2 | mutation updateCartItemQuantity($itemId: String!, $quantity: Int!) { 3 | updateCurrentCartItemQuantity(cartItemId: $itemId, quantity: $quantity) { 4 | id 5 | quantity 6 | } 7 | } 8 | ` 9 | 10 | export default updateCartItemQuantityMutation 11 | -------------------------------------------------------------------------------- /lib/gql/queries/get-product.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-named-as-default */ 2 | 3 | import { productInfo } from '../fragments' 4 | 5 | const getProductQuery = /* GraphQL */ ` 6 | ${productInfo} 7 | 8 | query product($productCode: String!) { 9 | product(productCode: $productCode) { 10 | ...productInfo 11 | } 12 | } 13 | ` 14 | export default getProductQuery 15 | -------------------------------------------------------------------------------- /lib/gql/fragments/index.ts: -------------------------------------------------------------------------------- 1 | export * from './search' 2 | export * from './checkout' 3 | export * from './product' 4 | export * from './category-info' 5 | export * from './configureProduct' 6 | export * from './cart' 7 | export * from './wishlist' 8 | export * from './userContacts' 9 | export * from './orders' 10 | export * from './subscription' 11 | export * from './destinationContact' 12 | -------------------------------------------------------------------------------- /lib/gql/mutations/wishlist/createWishlistMutation.ts: -------------------------------------------------------------------------------- 1 | import { wishlist } from '../../fragments' 2 | 3 | const createWishlistMutation = /* GraphQL */ ` 4 | mutation createWishlist($wishlistInput: CrWishlistInput!) { 5 | createWishlist(wishlistInput: $wishlistInput) { 6 | ...wishlist 7 | } 8 | } 9 | ${wishlist} 10 | ` 11 | 12 | export default createWishlistMutation 13 | -------------------------------------------------------------------------------- /lib/types/OrderReturnItems.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe, CrOrderItem } from '@/lib/gql/types' 2 | 3 | export interface CreateOrderReturnItemsParams { 4 | items: Maybe[] 5 | returnType: string 6 | reason: string 7 | } 8 | export interface CreateOrderReturnItemsInputParams extends CreateOrderReturnItemsParams { 9 | originalOrderId: string 10 | locationCode: string 11 | } 12 | -------------------------------------------------------------------------------- /components/cart/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CartItem } from '@/components/cart/CartItem/CartItem' 2 | export { default as CartItemList } from '@/components/cart/CartItemList/CartItemList' 3 | export { default as CartItemActions } from '@/components/cart/CartItemActions/CartItemActions' 4 | export { default as CartItemActionsMobile } from '@/components/cart/CartItemActionsMobile/CartItemActionsMobile' 5 | -------------------------------------------------------------------------------- /__mocks__/stories/createCustomerAccountCardMock.ts: -------------------------------------------------------------------------------- 1 | export const createCustomerAccountCardMock = { 2 | createCustomerAccountCard: { 3 | id: 'ec66e2c7625842c191d0870aed5ec0e7', 4 | nameOnCard: null, 5 | cardType: 'VISA', 6 | expireMonth: 1, 7 | expireYear: 2026, 8 | cardNumberPart: '***********1111', 9 | contactId: 1495, 10 | isDefaultPayMethod: false, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /__mocks__/stories/updateCustomerAccountCardMock.ts: -------------------------------------------------------------------------------- 1 | export const updateCustomerAccountCardMock = { 2 | updateCustomerAccountCard: { 3 | id: '48e1dd6dedcd46e68237f16bb80f787f', 4 | nameOnCard: null, 5 | cardType: '', 6 | expireMonth: 1, 7 | expireYear: 2030, 8 | cardNumberPart: '************1111', 9 | contactId: 1495, 10 | isDefaultPayMethod: false, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /lib/gql/mutations/coupon/deleteCheckoutCouponMutation.ts: -------------------------------------------------------------------------------- 1 | const deleteCheckoutCouponMutation = /* GraphQL */ ` 2 | mutation deleteCheckoutCoupon($checkoutId: String!, $couponCode: String!) { 3 | deleteCheckoutCoupon(checkoutId: $checkoutId, couponCode: $couponCode) { 4 | id 5 | siteId 6 | tenantId 7 | } 8 | } 9 | ` 10 | 11 | export default deleteCheckoutCouponMutation 12 | -------------------------------------------------------------------------------- /lib/types/SearchParams.ts: -------------------------------------------------------------------------------- 1 | import type { GeoCoords } from './GeoCoords' 2 | 3 | export interface CategorySearchParams { 4 | categoryCode?: string 5 | pageSize?: number 6 | filters?: Array 7 | startIndex?: number 8 | sort?: string 9 | search?: string 10 | filter?: string 11 | } 12 | 13 | export interface LocationSearchParams { 14 | zipcode?: string 15 | currentLocation?: GeoCoords 16 | } 17 | -------------------------------------------------------------------------------- /lib/gql/mutations/coupon/deleteCartCoupon.ts: -------------------------------------------------------------------------------- 1 | import { cartDetails } from '@/lib/gql/fragments' 2 | 3 | const deleteCartCouponMutation = /* GraphQL */ ` 4 | ${cartDetails} 5 | 6 | mutation deleteCartCoupon($cartId: String!, $couponCode: String!) { 7 | deleteCartCoupon(cartId: $cartId, couponCode: $couponCode) { 8 | ...cartDetails 9 | } 10 | } 11 | ` 12 | 13 | export default deleteCartCouponMutation 14 | -------------------------------------------------------------------------------- /lib/gql/mutations/coupon/updateCartCoupon.ts: -------------------------------------------------------------------------------- 1 | import { cartDetails } from '@/lib/gql/fragments' 2 | 3 | const updateCartCouponMutation = /* GraphQL */ ` 4 | ${cartDetails} 5 | 6 | mutation updateCartCoupon($cartId: String!, $couponCode: String!) { 7 | updateCartCoupon(cartId: $cartId, couponCode: $couponCode) { 8 | ...cartDetails 9 | } 10 | } 11 | ` 12 | 13 | export default updateCartCouponMutation 14 | -------------------------------------------------------------------------------- /lib/gql/mutations/user/updateAccount.ts: -------------------------------------------------------------------------------- 1 | export const updateCustomerData = /* GraphQL */ ` 2 | mutation updateCustomerData( 3 | $accountId: Int!, 4 | $customerAccountInput: CustomerAccountInput 5 | ) { 6 | updateCustomerAccount( 7 | accountId: $accountId, 8 | customerAccountInput: $customerAccountInput 9 | ) { 10 | firstName, 11 | lastName, 12 | emailAddress 13 | } 14 | }` 15 | -------------------------------------------------------------------------------- /lib/helpers/__tests__/cookieHelper.spec.ts: -------------------------------------------------------------------------------- 1 | import * as cookienext from 'cookies-next' 2 | 3 | import { setPurchaseLocationCookie } from '..' 4 | 5 | describe('[helpers] cookie helper', () => { 6 | it('should set cookie value', async () => { 7 | const mockSetCookie = jest.spyOn(cookienext, 'setCookie') 8 | 9 | setPurchaseLocationCookie('RICHMOND') 10 | expect(mockSetCookie).toHaveBeenCalled() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /lib/api/util/cache.ts: -------------------------------------------------------------------------------- 1 | import NodeCache from 'node-cache' 2 | const nodeCache = new NodeCache() 3 | 4 | const cache = { 5 | get: (cacheKey: string) => { 6 | if (nodeCache.has(cacheKey)) return nodeCache.get(cacheKey) 7 | return null 8 | }, 9 | 10 | set: (cacheKey: string, value: string, cacheTimeOut: number) => { 11 | nodeCache.set(cacheKey, value, cacheTimeOut) 12 | }, 13 | } 14 | 15 | export default cache 16 | -------------------------------------------------------------------------------- /__mocks__/stories/returnReasonsMock.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe, ReturnReason } from '@/lib/gql/types' 2 | 3 | export const returnReasonsMock: Maybe | any = { 4 | returnReasons: { 5 | items: [ 6 | 'Damaged', 7 | 'Defective', 8 | 'Missing Parts', 9 | 'Different Expectations', 10 | 'Late', 11 | 'No Longer Wanted', 12 | 'Other', 13 | ], 14 | totalCount: 7, 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /components/common/GlobalFetchingIndicator/GlobalFetchingIndicator.tsx: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress' 2 | import { useIsFetching } from 'react-query' 3 | 4 | function GlobalFetchingIndicator() { 5 | const isLoadingFetching = useIsFetching() 6 | if (isLoadingFetching > 1) { 7 | NProgress.start() 8 | } else { 9 | NProgress.done() 10 | } 11 | 12 | return null 13 | } 14 | 15 | export default GlobalFetchingIndicator 16 | -------------------------------------------------------------------------------- /lib/gql/queries/get-shipping-rates.ts: -------------------------------------------------------------------------------- 1 | const getShippingRatesQuery = /* GraphQL */ ` 2 | query getShippingRates($checkoutId: String!) { 3 | orderShipmentMethods(orderId: $checkoutId) { 4 | shippingMethodCode 5 | shippingMethodName 6 | shippingZoneCode 7 | isValid 8 | messages 9 | data 10 | currencyCode 11 | price 12 | } 13 | } 14 | ` 15 | export default getShippingRatesQuery 16 | -------------------------------------------------------------------------------- /components/order/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ViewOrderDetails } from './ViewOrderDetails/ViewOrderDetails' 2 | export { default as OrderHistoryItem } from './OrderHistoryItem/OrderHistoryItem' 3 | export { default as OrderConfirmation } from './OrderConfirmation/OrderConfirmation' 4 | export { default as ViewOrderStatus } from './ViewOrderStatus/ViewOrderStatus' 5 | export { default as OrderReturnItems } from './OrderReturnItems/OrderReturnItems' 6 | -------------------------------------------------------------------------------- /lib/gql/queries/checkout/get-customer-account-cards.ts: -------------------------------------------------------------------------------- 1 | import { paymentCardItems } from '../../fragments/paymentCardItems' 2 | 3 | const getCustomerAccountCards = /* GraphQL */ ` 4 | query customerAccountCards($accountId: Int!) { 5 | customerAccountCards(accountId: $accountId) { 6 | items { 7 | ...customerAccountCardItems 8 | } 9 | } 10 | } 11 | ${paymentCardItems} 12 | ` 13 | export default getCustomerAccountCards 14 | -------------------------------------------------------------------------------- /components/product-listing/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CategoryFacet } from './CategoryFacet/CategoryFacet' 2 | export { default as CategoryFilterByMobile } from './CategoryFilterByMobile/CategoryFilterByMobile' 3 | export { default as Facet } from './Facet/Facet' 4 | export { default as FacetItem } from './FacetItem/FacetItem' 5 | export { default as FacetItemList } from './FacetItemList/FacetItemList' 6 | export { default as FacetList } from './FacetList/FacetList' 7 | -------------------------------------------------------------------------------- /lib/gql/client/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLClient } from 'graphql-request' 2 | 3 | export function makeGraphQLClient(endpoint?: string) { 4 | endpoint = 5 | endpoint || `${process.env.NEXT_PUBLIC_URL ? process.env.NEXT_PUBLIC_URL : ''}/api/graphql` 6 | return new GraphQLClient(endpoint) 7 | } 8 | 9 | export function makeCategoryTreeGraphEndPoint() { 10 | return `${process.env.NEXT_PUBLIC_URL ? process.env.NEXT_PUBLIC_URL : ''}/api/category-tree` 11 | } 12 | -------------------------------------------------------------------------------- /lib/gql/mutations/cart/updateCartItemMutation.ts: -------------------------------------------------------------------------------- 1 | import { cartItemDetails } from '../../fragments/cart' 2 | 3 | const updateCartItemMutation = /* GraphQL */ ` 4 | ${cartItemDetails} 5 | 6 | mutation updateCurrentCartItem($cartItemId: String!, $cartItemInput: CrCartItemInput) { 7 | updateCurrentCartItem(cartItemId: $cartItemId, cartItemInput: $cartItemInput) { 8 | ...cartItemDetails 9 | } 10 | } 11 | ` 12 | export default updateCartItemMutation 13 | -------------------------------------------------------------------------------- /lib/helpers/buildWishlistParams.ts: -------------------------------------------------------------------------------- 1 | import { WishlistParams } from '../types' 2 | 3 | export const buildWishlistParams = (params: WishlistParams) => { 4 | const { productCode, variationProductCode, isPackagedStandAlone, options, currentWishlist } = 5 | params 6 | 7 | return { 8 | product: { 9 | productCode, 10 | isPackagedStandAlone, 11 | variationProductCode, 12 | options, 13 | }, 14 | currentWishlist, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/gql/queries/get-search-suggestions.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-named-as-default */ 2 | 3 | const getSearchSuggestionsQuery = /* GraphQL */ ` 4 | query getSearchSuggestions($query: String!) { 5 | suggestionSearch(query: $query, groups: "pages,categories") { 6 | suggestionGroups { 7 | name 8 | suggestions { 9 | suggestion 10 | } 11 | } 12 | } 13 | } 14 | ` 15 | 16 | export default getSearchSuggestionsQuery 17 | -------------------------------------------------------------------------------- /.storybook/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next' 2 | import { initReactI18next } from 'react-i18next' 3 | 4 | i18n.use(initReactI18next).init({ 5 | fallbackLng: 'en', 6 | debug: true, 7 | react: { 8 | wait: true, 9 | useSuspense: false, 10 | }, 11 | }) 12 | 13 | const localeAssets = ['common'] 14 | for (const asset of localeAssets) { 15 | i18n.addResourceBundle('en', asset, require(`../public/locales/en/${asset}.json`)) 16 | } 17 | 18 | export default i18n 19 | -------------------------------------------------------------------------------- /components/dialogs/AddToCartConfirmation/Title/Title.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import Title from './Title' 6 | 7 | export default { 8 | title: 'Dialogs/AddToCartDialog/Title', 9 | component: Title, 10 | } as ComponentMeta 11 | 12 | const Template: ComponentStory = () => 13 | 14 | // Common 15 | export const Common = Template.bind({}) 16 | -------------------------------------------------------------------------------- /lib/gql/mutations/wishlist/createWishlistItemMutation.ts: -------------------------------------------------------------------------------- 1 | import { wishlistItem } from '../../fragments' 2 | 3 | const createWishlistItemMutation = /* GraphQL */ ` 4 | mutation createWishlistItem($wishlistId: String!, $wishlistItemInput: CrWishlistItemInput) { 5 | createWishlistItem(wishlistId: $wishlistId, wishlistItemInput: $wishlistItemInput) { 6 | ...wishlistItem 7 | } 8 | } 9 | ${wishlistItem} 10 | ` 11 | export default createWishlistItemMutation 12 | -------------------------------------------------------------------------------- /.github/workflows/commit-lint.yml: -------------------------------------------------------------------------------- 1 | name: Commitlint 2 | on: 3 | pull_request: 4 | types: ['opened', 'edited', 'reopened', 'synchronize'] 5 | 6 | jobs: 7 | lint: 8 | name: Validate Pull Request Name (conventional-commit) 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Install Dependencies 13 | run: npm install @commitlint/config-conventional 14 | - uses: JulienKode/pull-request-name-linter-action@v0.1.2 -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/create-order-mutation.ts: -------------------------------------------------------------------------------- 1 | const createOrderMutation = /* GraphQL */ ` 2 | mutation createOrderAction($orderId: String!, $orderActionInput: OrderActionInput) { 3 | createOrderAction(orderId: $orderId, orderActionInput: $orderActionInput) { 4 | orderNumber 5 | parentOrderId 6 | parentOrderNumber 7 | paymentStatus 8 | submittedDate 9 | total 10 | } 11 | } 12 | ` 13 | 14 | export default createOrderMutation 15 | -------------------------------------------------------------------------------- /lib/gql/mutations/user/updatePassword.ts: -------------------------------------------------------------------------------- 1 | export const updatePassword = /* GraphQL */ ` 2 | mutation changeCustomerAccountPassword( 3 | $accountId: Int! 4 | $unlockAccount: Boolean 5 | $userId: String 6 | $passwordInfoInput: PasswordInfoInput 7 | ) { 8 | changeCustomerAccountPassword( 9 | accountId: $accountId 10 | unlockAccount: $unlockAccount 11 | userId: $userId 12 | passwordInfoInput: $passwordInfoInput 13 | ) 14 | } 15 | ` 16 | -------------------------------------------------------------------------------- /lib/helpers/buildCreateWishlistItemParams.ts: -------------------------------------------------------------------------------- 1 | import getConfig from 'next/config' 2 | 3 | import type { CrWishlistInput } from '@/lib/gql/types' 4 | export const buildCreateWishlistItemParams = ( 5 | customerAccountId: number 6 | ): { wishlistInput: CrWishlistInput } => { 7 | const { publicRuntimeConfig } = getConfig() 8 | 9 | return { 10 | wishlistInput: { 11 | customerAccountId, 12 | name: publicRuntimeConfig.defaultWishlistName, 13 | }, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/gql/mutations/my-account/create-customer-account-card.ts: -------------------------------------------------------------------------------- 1 | import { paymentCardItems } from '@/lib/gql/fragments/paymentCardItems' 2 | 3 | const createCustomerAccountCard = /* GraphQL */ ` 4 | mutation createCustomerAccountCard($accountId: Int!, $cardInput: CardInput) { 5 | createCustomerAccountCard(accountId: $accountId, cardInput: $cardInput) { 6 | ...customerAccountCardItems 7 | } 8 | } 9 | ${paymentCardItems} 10 | ` 11 | 12 | export default createCustomerAccountCard 13 | -------------------------------------------------------------------------------- /lib/gql/queries/get-product-price.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-named-as-default */ 2 | 3 | import { productPrices } from '../fragments' 4 | 5 | const getProductPriceQuery = /* GraphQL */ ` 6 | ${productPrices} 7 | 8 | query getProductPrice($productCode: String!, $useSubscriptionPricing: Boolean) { 9 | product(productCode: $productCode, useSubscriptionPricing: $useSubscriptionPricing) { 10 | ...productPrices 11 | } 12 | } 13 | ` 14 | export default getProductPriceQuery 15 | -------------------------------------------------------------------------------- /lib/getters/index.ts: -------------------------------------------------------------------------------- 1 | export * from './productGetters' 2 | export * from './productSearchGetters' 3 | export * from './facetGetters' 4 | export * from './orderGetters' 5 | export * from './wishlistGetters' 6 | export * from './cardGetters' 7 | export * from './addressGetters' 8 | export * from './storeLocationGetters' 9 | export * from './userGetters' 10 | export * from './cartGetters' 11 | export * from './subscriptionGetters' 12 | export * from './checkoutGetters' 13 | export * from './addressGetters' 14 | -------------------------------------------------------------------------------- /components/my-account/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PaymentMethod } from './PaymentMethod/PaymentMethod' 2 | export { default as MyProfile } from './MyProfile/MyProfile' 3 | export { default as AddressBook } from './AddressBook/AddressBook' 4 | export { default as ProfileDetailsForm } from './ProfileDetailsForm/ProfileDetailsForm' 5 | export { default as SubscriptionList } from './Subscription/SubscriptionList/SubscriptionList' 6 | export { default as SubscriptionItem } from './Subscription/SubscriptionItem/SubscriptionItem' 7 | -------------------------------------------------------------------------------- /lib/api/operations/get-search-suggestions.ts: -------------------------------------------------------------------------------- 1 | import { fetcher } from '@/lib/api/util' 2 | import { getSearchSuggestionsQuery } from '@/lib/gql/queries' 3 | 4 | export default async function getSearchSuggestions(searchKey: string) { 5 | try { 6 | const variables = { 7 | searchKey, 8 | } 9 | const response = await fetcher({ query: getSearchSuggestionsQuery, variables }, null) 10 | return response.data?.suggestionGroups 11 | } catch (error) { 12 | console.error(error) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/types/LocationCustom.ts: -------------------------------------------------------------------------------- 1 | import type { Hours, CrAddress } from '@/lib/gql/types' 2 | 3 | export type HoursCustom = { 4 | day?: string | Hours 5 | storeTime?: string 6 | } 7 | export type LocationCustom = { 8 | code?: string 9 | name?: string 10 | phone?: string 11 | address1?: string 12 | address2?: string 13 | streetAddress?: string 14 | cityState?: string 15 | city?: string 16 | state?: string 17 | zip?: string 18 | hours?: HoursCustom[] | null 19 | fullAddress?: CrAddress 20 | } 21 | -------------------------------------------------------------------------------- /__mocks__/stories/LocationInventoryCollectionMock.ts: -------------------------------------------------------------------------------- 1 | import type { LocationInventoryCollection } from '@/lib/gql/types' 2 | export const locationInventoryCollectionMock: { 3 | productLocationInventory: LocationInventoryCollection 4 | } = { 5 | productLocationInventory: { 6 | totalCount: 1, 7 | items: [ 8 | { 9 | productCode: 'BackP_006', 10 | locationCode: 'RICHMOND', 11 | stockAvailable: 100, 12 | softStockAvailable: null, 13 | }, 14 | ], 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /__mocks__/stories/shippingRateMock.ts: -------------------------------------------------------------------------------- 1 | import type { CrShippingRate } from '@/lib/gql/types' 2 | 3 | export const shippingRateMock: { orderShipmentMethods: CrShippingRate[] } = { 4 | orderShipmentMethods: [ 5 | { 6 | shippingMethodCode: '691f94b2b57e47239456ada600cdcc9e', 7 | shippingMethodName: 'Flat Rate', 8 | shippingZoneCode: 'Americas', 9 | isValid: true, 10 | messages: [], 11 | data: null, 12 | currencyCode: 'USD', 13 | price: 15, 14 | }, 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /lib/gql/mutations/my-account/update-customer-account-card.ts: -------------------------------------------------------------------------------- 1 | import { paymentCardItems } from '@/lib/gql/fragments/paymentCardItems' 2 | 3 | const updateCustomerAccountCard = /* GraphQL */ ` 4 | mutation updateCustomerAccountCard($accountId: Int!, $cardId: String!, $cardInput: CardInput) { 5 | updateCustomerAccountCard(accountId: $accountId, cardId: $cardId, cardInput: $cardInput) { 6 | ...customerAccountCardItems 7 | } 8 | } 9 | ${paymentCardItems} 10 | ` 11 | export default updateCustomerAccountCard 12 | -------------------------------------------------------------------------------- /lib/types/FacetResultDataType.ts: -------------------------------------------------------------------------------- 1 | import { Facet, Maybe, PrCategory, Product, Scalars } from '../gql/types' 2 | 3 | export type FacetResultsData = { 4 | items?: Maybe<Array<Maybe<Product>>> 5 | categories?: Maybe<Array<Maybe<PrCategory>>> 6 | facets?: Maybe<Array<Maybe<Facet>>> 7 | totalCount?: Scalars['Int'] 8 | itemsPerPage?: Scalars['Int'] 9 | startIndex?: Scalars['Int'] 10 | pageCount?: Scalars['Int'] 11 | pageSize?: Scalars['Int'] 12 | input?: { 13 | sort?: Maybe<Scalars['String']> 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/set-billing-info.ts: -------------------------------------------------------------------------------- 1 | import { billingContactFragment } from '../../fragments' 2 | 3 | const setBillingInformation = /* GraphQL */ ` 4 | mutation setBillingInformation($orderId: String!, $billingInfoInput: CrBillingInfoInput) { 5 | updateOrderBillingInfo(orderId: $orderId, billingInfoInput: $billingInfoInput) { 6 | billingContact { 7 | ...billingContactFragment 8 | } 9 | } 10 | } 11 | 12 | ${billingContactFragment} 13 | ` 14 | 15 | export default setBillingInformation 16 | -------------------------------------------------------------------------------- /lib/gql/queries/get-product-location-inventory.ts: -------------------------------------------------------------------------------- 1 | const getProductLocationInventoryQuery = /* GraphQL */ ` 2 | query productLocationInventory($productCode: String!, $locationCodes: String) { 3 | productLocationInventory(productCode: $productCode, locationCodes: $locationCodes) { 4 | totalCount 5 | items { 6 | productCode 7 | locationCode 8 | stockAvailable 9 | softStockAvailable 10 | } 11 | } 12 | } 13 | ` 14 | 15 | export default getProductLocationInventoryQuery 16 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/create-checkout-shipping-method.ts: -------------------------------------------------------------------------------- 1 | const createCheckoutShippingMethod = /* GraphQL */ ` 2 | mutation createCheckoutShippingMethod( 3 | $checkoutId: String! 4 | $checkoutGroupShippingMethodInput: [CheckoutGroupShippingMethodInput] 5 | ) { 6 | checkout: createCheckoutShippingMethod( 7 | checkoutId: $checkoutId 8 | checkoutGroupShippingMethodInput: $checkoutGroupShippingMethodInput 9 | ) { 10 | id 11 | } 12 | } 13 | ` 14 | export default createCheckoutShippingMethod 15 | -------------------------------------------------------------------------------- /lib/gql/queries/checkout/get-checkout-destinations.ts: -------------------------------------------------------------------------------- 1 | import { destinationContactFragment } from '../../fragments' 2 | 3 | const getCheckoutDestinationsQuery = /* GraphQL */ ` 4 | query getCheckoutDestinations($checkoutId: String!) { 5 | checkoutDestinations(checkoutId: $checkoutId) { 6 | destinationContact { 7 | ...destinationContactFragment 8 | } 9 | id 10 | isDestinationCommercial 11 | } 12 | } 13 | ${destinationContactFragment} 14 | ` 15 | export default getCheckoutDestinationsQuery 16 | -------------------------------------------------------------------------------- /lib/helpers/uiHelpers.ts: -------------------------------------------------------------------------------- 1 | interface UIHelpersType { 2 | getCategoryLink: (category?: string) => string 3 | getProductLink: (productCode?: string) => string 4 | } 5 | 6 | export const uiHelpers = (): UIHelpersType => { 7 | const getCategoryLink = (categoryCode?: string): string => { 8 | return `/category/${categoryCode}` 9 | } 10 | const getProductLink = (productCode?: string) => { 11 | return `/product/${productCode}` 12 | } 13 | 14 | return { 15 | getCategoryLink, 16 | getProductLink, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/set-shipping-info.ts: -------------------------------------------------------------------------------- 1 | import { fullfillmentInfoFragment } from '../../fragments' 2 | 3 | const setShippingInfoMutation = /* GraphQL */ ` 4 | mutation setShippingInformation( 5 | $orderId: String! 6 | $fulfillmentInfoInput: CrFulfillmentInfoInput 7 | ) { 8 | updateOrderFulfillmentInfo(orderId: $orderId, fulfillmentInfoInput: $fulfillmentInfoInput) { 9 | ...fullfillmentInfoFragment 10 | } 11 | } 12 | 13 | ${fullfillmentInfoFragment} 14 | ` 15 | export default setShippingInfoMutation 16 | -------------------------------------------------------------------------------- /__mocks__/stories/shippingRates.ts: -------------------------------------------------------------------------------- 1 | import { CrShippingRate } from '@/lib/gql/types' 2 | 3 | export const shippingRateMock: CrShippingRate = { 4 | data: { 5 | orderShipmentMethods: [ 6 | { 7 | shippingMethodCode: '9e7f0b9181bf43a8922caea70148720b', 8 | shippingMethodName: 'International Flat Rate', 9 | shippingZoneCode: 'Global', 10 | isValid: true, 11 | messages: [], 12 | data: null, 13 | currencyCode: 'USD', 14 | price: 25, 15 | }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /components/layout/Footer/Footer.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import Footer from './Footer' 6 | import { footerConfig } from '@/lib/constants' 7 | 8 | export default { 9 | title: 'Layout/Footer', 10 | component: Footer, 11 | } as ComponentMeta<typeof Footer> 12 | 13 | const Template: ComponentStory<typeof Footer> = (args) => <Footer {...args} /> 14 | 15 | export const Common = Template.bind({}) 16 | Common.args = { 17 | ...footerConfig, 18 | } 19 | -------------------------------------------------------------------------------- /lib/api/handlers/category-tree.ts: -------------------------------------------------------------------------------- 1 | import { getCategoryTree } from '../operations' 2 | 3 | import type { NextApiRequest, NextApiResponse } from 'next' 4 | 5 | export default async function categoryTreeHandler(req: NextApiRequest, res: NextApiResponse) { 6 | try { 7 | const response = await getCategoryTree() 8 | res.status(200).json(response) 9 | } catch (error) { 10 | console.error(error) 11 | const message = 'An unexpected error ocurred' 12 | res.status(500).json({ data: null, errors: [{ message }] }) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/api/operations/index.ts: -------------------------------------------------------------------------------- 1 | export { default as getCategory } from './get-category' 2 | export { default as getProduct } from './get-product' 3 | export { default as productSearch } from './get-product-search' 4 | export { default as categoryTreeSearchByCode } from './get-category-tree-by-code' 5 | export { default as getCheckout } from './get-checkout' 6 | export { default as getCart } from './get-cart' 7 | export { default as getCategoryTree } from './get-category-tree' 8 | export { default as getMultiShipCheckout } from './get-multi-ship-checkout' 9 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/create-checkout-payment-action.ts: -------------------------------------------------------------------------------- 1 | const createCheckoutPaymentActionMutation = /* GraphQL */ ` 2 | mutation checkoutPaymentAction($checkoutId: String!, $paymentAction: PaymentActionInput) { 3 | createCheckoutPaymentAction(checkoutId: $checkoutId, paymentActionInput: $paymentAction) { 4 | id 5 | originalCartId 6 | destinations { 7 | id 8 | data 9 | } 10 | total 11 | subTotal 12 | } 13 | } 14 | ` 15 | 16 | export default createCheckoutPaymentActionMutation 17 | -------------------------------------------------------------------------------- /lib/gql/queries/get-user-addresses-query.ts: -------------------------------------------------------------------------------- 1 | import { userContactFields } from '../fragments' 2 | 3 | const getUserAddressesQuery = /* GraphQL */ ` 4 | query getUserAddresses($accountId: Int!, $startIndex: Int, $pageSize: Int) { 5 | customerAccountContacts(accountId: $accountId, startIndex: $startIndex, pageSize: $pageSize) { 6 | pageCount 7 | totalCount 8 | items { 9 | ...userContactFields 10 | } 11 | } 12 | } 13 | 14 | ${userContactFields} 15 | ` 16 | 17 | export default getUserAddressesQuery 18 | -------------------------------------------------------------------------------- /components/layout/SearchSuggestions/SearchSuggestions.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import SearchSuggestions from './SearchSuggestions' 6 | 7 | // Common 8 | export default { 9 | title: 'Layout/SearchSuggestions', 10 | component: SearchSuggestions, 11 | } as ComponentMeta<typeof SearchSuggestions> 12 | 13 | const Template: ComponentStory<typeof SearchSuggestions> = () => <SearchSuggestions /> 14 | 15 | // Default 16 | export const Common = Template.bind({}) 17 | -------------------------------------------------------------------------------- /lib/gql/mutations/subscription/update-subscription-payment-mutation.ts: -------------------------------------------------------------------------------- 1 | const updateSubscriptionPaymentMutation = /* GraphQL */ ` 2 | mutation updateSubscriptionPayment( 3 | $subscriptionId: String! 4 | $updateMode: String 5 | $paymentInput: SBPaymentInput 6 | ) { 7 | subscription: updateSubscriptionPayment( 8 | subscriptionId: $subscriptionId 9 | updateMode: $updateMode 10 | paymentInput: $paymentInput 11 | ) { 12 | id 13 | } 14 | } 15 | ` 16 | 17 | export default updateSubscriptionPaymentMutation 18 | -------------------------------------------------------------------------------- /lib/gql/mutations/subscription/edit-subscription-frequency-mutation.ts: -------------------------------------------------------------------------------- 1 | const EditSubscriptionFrequencyMutation = /* GraphQL */ ` 2 | mutation updateSubscriptionFrequency( 3 | $subscriptionId: String! 4 | $frequencyInput: SBFrequencyInput 5 | ) { 6 | subscription: updateSubscriptionFrequency( 7 | subscriptionId: $subscriptionId 8 | frequencyInput: $frequencyInput 9 | ) { 10 | frequency { 11 | unit 12 | value 13 | } 14 | } 15 | } 16 | ` 17 | 18 | export default EditSubscriptionFrequencyMutation 19 | -------------------------------------------------------------------------------- /hooks/custom/usePaymentTypes/usePaymentTypes.spec.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | import getConfig from 'next/config' 3 | 4 | import { usePaymentTypes } from './usePaymentTypes' 5 | 6 | const { publicRuntimeConfig } = getConfig() 7 | 8 | describe('usePaymentTypes', () => { 9 | it('Should provide a payment', () => { 10 | const { result } = renderHook(usePaymentTypes) 11 | const { loadPaymentTypes } = result.current 12 | 13 | expect(loadPaymentTypes()).toEqual(publicRuntimeConfig.paymentTypes) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /lib/api/operations/get-product-search.ts: -------------------------------------------------------------------------------- 1 | import { fetcher } from '@/lib/api/util' 2 | import { searchProductsQuery } from '@/lib/gql/queries' 3 | import { buildProductSearchParams } from '@/lib/helpers' 4 | import { CategorySearchParams } from '@/lib/types' 5 | 6 | export default async function search(searchParams: CategorySearchParams) { 7 | try { 8 | const variables = buildProductSearchParams(searchParams) 9 | return await fetcher({ query: searchProductsQuery, variables }, null) 10 | } catch (error) { 11 | console.error(error) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/create-checkout-action-mutation.ts: -------------------------------------------------------------------------------- 1 | const createCheckoutActionMutation = /* GraphQL */ ` 2 | mutation createCheckoutAction($checkoutId: String!, $checkoutActionInput: CheckoutActionInput) { 3 | createCheckoutAction(checkoutId: $checkoutId, checkoutActionInput: $checkoutActionInput) { 4 | id 5 | originalCartId 6 | customerAccountId 7 | email 8 | subTotal 9 | shippingTaxTotal 10 | shippingTotal 11 | total 12 | } 13 | } 14 | ` 15 | 16 | export default createCheckoutActionMutation 17 | -------------------------------------------------------------------------------- /lib/api/operations/get-cart.ts: -------------------------------------------------------------------------------- 1 | import { ServerResponse } from 'http' 2 | 3 | import getUserClaimsFromRequest from '../util/getUserClaimsFromRequest' 4 | import { fetcher } from '@/lib/api/util' 5 | import { getCartQuery } from '@/lib/gql/queries' 6 | import type { KiboRequest } from '@/lib/types' 7 | 8 | export default async function getCart(req: KiboRequest, res: ServerResponse) { 9 | const userClaims = await getUserClaimsFromRequest(req, res) 10 | const response = await fetcher({ query: getCartQuery, variables: {} }, { userClaims }) 11 | return response?.data 12 | } 13 | -------------------------------------------------------------------------------- /lib/gql/mutations/subscription/pause-subscription-mutation.ts: -------------------------------------------------------------------------------- 1 | const performSubscriptionActionMutation = /* GraphQL */ ` 2 | mutation performSubscriptionAction( 3 | $subscriptionId: String! 4 | $subscriptionActionInput: SubscriptionActionInput 5 | ) { 6 | subscription: performSubscriptionAction( 7 | subscriptionId: $subscriptionId 8 | subscriptionActionInput: $subscriptionActionInput 9 | ) { 10 | id 11 | reasons { 12 | actionName 13 | } 14 | } 15 | } 16 | ` 17 | 18 | export default performSubscriptionActionMutation 19 | -------------------------------------------------------------------------------- /lib/gql/queries/checkout/get-multi-ship-checkout-shipping-methods.ts: -------------------------------------------------------------------------------- 1 | const getCheckoutShippingMethodsQuery = /* GraphQL */ ` 2 | query getCheckoutShippingMethods($checkoutId: String!) { 3 | checkoutShippingMethods(checkoutId: $checkoutId) { 4 | groupingId 5 | shippingRates { 6 | shippingMethodCode 7 | shippingMethodName 8 | shippingZoneCode 9 | isValid 10 | messages 11 | data 12 | currencyCode 13 | price 14 | } 15 | } 16 | } 17 | ` 18 | export default getCheckoutShippingMethodsQuery 19 | -------------------------------------------------------------------------------- /lib/types/MyProfile.ts: -------------------------------------------------------------------------------- 1 | export interface ProfileDetails { 2 | firstName?: string 3 | lastName?: string 4 | emailAddress?: string 5 | currentPassword?: string 6 | newPassword?: string 7 | } 8 | 9 | export interface PasswordTypes { 10 | oldPassword?: string 11 | newPassword?: string 12 | } 13 | 14 | export type ProfileFormNameParam = Pick<ProfileDetails, 'firstName' | 'lastName'> 15 | export type ProfileFormEmailParam = Pick<ProfileDetails, 'emailAddress'> 16 | 17 | export type UpdateProfileDataParam = ProfileFormNameParam & ProfileFormEmailParam & PasswordTypes 18 | -------------------------------------------------------------------------------- /components/cart/CartItemActions/CartItemActions.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import CartItemActions from './CartItemActions' 6 | 7 | export default { 8 | title: 'cart/CartItemActions', 9 | component: CartItemActions, 10 | 11 | argTypes: { 12 | backgroundColor: { control: 'color' }, 13 | }, 14 | } as ComponentMeta<typeof CartItemActions> 15 | 16 | const Template: ComponentStory<typeof CartItemActions> = () => <CartItemActions /> 17 | 18 | export const Common = Template.bind({}) 19 | -------------------------------------------------------------------------------- /components/common/KiboLogo/KiboLogo.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import KiboLogo from './KiboLogo' 6 | import Logo from '@/public/kibo_logo.png' 7 | 8 | export default { 9 | title: 'Common/KiboLogo', 10 | component: KiboLogo, 11 | } as ComponentMeta<typeof KiboLogo> 12 | 13 | const Template: ComponentStory<typeof KiboLogo> = (args) => <KiboLogo {...args} /> 14 | 15 | export const Common = Template.bind({}) 16 | 17 | Common.args = { 18 | logo: Logo, 19 | alt: 'kibo logo', 20 | } 21 | -------------------------------------------------------------------------------- /lib/gql/queries/checkout/get-checkout-destination.ts: -------------------------------------------------------------------------------- 1 | import { destinationContactFragment } from '../../fragments' 2 | 3 | const getCheckoutDestinationQuery = /* GraphQL */ ` 4 | query getCheckoutDestination($checkoutId: String!, $destinationId: String!) { 5 | checkoutDestination(orderId: $checkoutId, destinationId: $destinationId) { 6 | destinationContact { 7 | ...destinationContactFragment 8 | } 9 | id 10 | isDestinationCommercial 11 | } 12 | } 13 | ${destinationContactFragment} 14 | ` 15 | export default getCheckoutDestinationQuery 16 | -------------------------------------------------------------------------------- /__mocks__/stories/productOptionSelectMock.ts: -------------------------------------------------------------------------------- 1 | import type { ProductOptionValue } from '@/lib/gql/types' 2 | 3 | export const productOptionSelectValuesMock: ProductOptionValue[] = [ 4 | { 5 | value: '125', 6 | attributeValueId: 125, 7 | stringValue: 'MS-CAM-005', 8 | isEnabled: true, 9 | }, 10 | { 11 | value: '126', 12 | attributeValueId: 126, 13 | stringValue: 'MS-CAM-006', 14 | isEnabled: true, 15 | }, 16 | { 17 | value: '127', 18 | attributeValueId: 127, 19 | stringValue: 'MS-CAM-007', 20 | isEnabled: false, 21 | }, 22 | ] 23 | -------------------------------------------------------------------------------- /lib/helpers/__tests__/uiHelpers.spec.ts: -------------------------------------------------------------------------------- 1 | import { uiHelpers } from '../uiHelpers' 2 | 3 | describe('[helpers] UIHelpers', () => { 4 | const helper = uiHelpers() 5 | it('should run getCategoryLink function and return a categorylink', () => { 6 | const categoryLink = helper.getCategoryLink('CampHike') 7 | expect(categoryLink).toBe('/category/CampHike') 8 | }) 9 | 10 | it('should run getProductLink function and return a productlink', () => { 11 | const categoryLink = helper.getProductLink('BTL001') 12 | expect(categoryLink).toBe('/product/BTL001') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /components/common/PasswordValidation/PasswordValidation.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import PasswordValidation from './PasswordValidation' 6 | 7 | // Common 8 | export default { 9 | title: 'Common/PasswordValidation', 10 | component: PasswordValidation, 11 | } as ComponentMeta<typeof PasswordValidation> 12 | 13 | const Template: ComponentStory<typeof PasswordValidation> = (args) => ( 14 | <PasswordValidation {...args} /> 15 | ) 16 | 17 | // Default 18 | export const Common = Template.bind({}) 19 | -------------------------------------------------------------------------------- /components/page-templates/OrderStatusTemplate/OrderStatusTemplate.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import OrderStatusTemplate from './OrderStatusTemplate' 6 | 7 | // Common 8 | export default { 9 | title: 'OrderStatusTemplate/OrderStatusTemplate', 10 | component: OrderStatusTemplate, 11 | } as ComponentMeta<typeof OrderStatusTemplate> 12 | 13 | const Template: ComponentStory<typeof OrderStatusTemplate> = () => <OrderStatusTemplate /> 14 | 15 | // Default 16 | export const Common = Template.bind({}) 17 | -------------------------------------------------------------------------------- /lib/gql/mutations/user/login.ts: -------------------------------------------------------------------------------- 1 | export const loginMutation = /* GraphQL */ ` 2 | mutation login($loginInput: CustomerUserAuthInfoInput!) { 3 | account: createCustomerAuthTicket(customerUserAuthInfoInput: $loginInput) { 4 | customerAccount { 5 | id 6 | userId 7 | firstName 8 | lastName 9 | emailAddress 10 | userName 11 | isAnonymous 12 | } 13 | accessToken 14 | accessTokenExpiration 15 | refreshToken 16 | refreshTokenExpiration 17 | userId 18 | jwtAccessToken 19 | } 20 | } 21 | ` 22 | -------------------------------------------------------------------------------- /components/page-templates/MyAccountTemplate/MyAccountTemplate.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import MyAccountTemplate from './MyAccountTemplate' 6 | 7 | export default { 8 | title: 'MyAccountTemplate/MyAccountTemplate', 9 | component: MyAccountTemplate, 10 | parameters: { 11 | layout: 'fullscreen', 12 | }, 13 | } as ComponentMeta<typeof MyAccountTemplate> 14 | 15 | const Template: ComponentStory<typeof MyAccountTemplate> = () => <MyAccountTemplate /> 16 | 17 | export const Common = Template.bind({}) 18 | -------------------------------------------------------------------------------- /lib/gql/mutations/my-account/create-customer-account-contact.ts: -------------------------------------------------------------------------------- 1 | import { userContactFields } from '@/lib/gql/fragments/userContacts' 2 | 3 | const createCustomerAccountContact = /* GraphQL */ ` 4 | ${userContactFields} 5 | 6 | mutation createCustomerAccountContact( 7 | $accountId: Int! 8 | $customerContactInput: CustomerContactInput! 9 | ) { 10 | createCustomerAccountContact( 11 | accountId: $accountId 12 | customerContactInput: $customerContactInput 13 | ) { 14 | ...userContactFields 15 | } 16 | } 17 | ` 18 | export default createCustomerAccountContact 19 | -------------------------------------------------------------------------------- /lib/gql/mutations/subscription/update-subscription-next-order-date-mutation.ts: -------------------------------------------------------------------------------- 1 | const updateSubscriptionNextOrderDateMutation = /* GraphQL */ ` 2 | mutation updateSubscriptionNextOrderDate( 3 | $subscriptionId: String! 4 | $subscriptionNextOrderDateInput: SubscriptionNextOrderDateInput 5 | ) { 6 | subscription: updateSubscriptionNextOrderDate( 7 | subscriptionId: $subscriptionId 8 | subscriptionNextOrderDateInput: $subscriptionNextOrderDateInput 9 | ) { 10 | nextOrderDate 11 | } 12 | } 13 | ` 14 | 15 | export default updateSubscriptionNextOrderDateMutation 16 | -------------------------------------------------------------------------------- /lib/helpers/buildBreadcrumbsParams.ts: -------------------------------------------------------------------------------- 1 | import type { PrCategory } from '../gql/types' 2 | import type { BreadCrumb } from '../types' 3 | 4 | const buildBreadcrumbsList = (rootCat: PrCategory, bc: BreadCrumb[]): BreadCrumb[] => { 5 | const newBc = [ 6 | ...bc, 7 | { 8 | text: rootCat.content?.name, 9 | link: `${rootCat.categoryCode}`, 10 | }, 11 | ] 12 | return rootCat.parentCategory ? buildBreadcrumbsList(rootCat.parentCategory, newBc) : newBc 13 | } 14 | 15 | export const buildBreadcrumbsParams = (rootCat: PrCategory) => 16 | buildBreadcrumbsList(rootCat, []).reverse() 17 | -------------------------------------------------------------------------------- /components/dialogs/WishlistPopover/WishlistPopover.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import WishlistPopover from './WishlistPopover' 6 | 7 | export default { 8 | title: 'Dialogs/WishlistPopover', 9 | component: WishlistPopover, 10 | } as ComponentMeta<typeof WishlistPopover> 11 | 12 | const Template: ComponentStory<typeof WishlistPopover> = ({ ...args }) => ( 13 | <WishlistPopover {...args} /> 14 | ) 15 | 16 | // Common 17 | export const Common = Template.bind({}) 18 | Common.args = { 19 | isInWishlist: true, 20 | } 21 | -------------------------------------------------------------------------------- /components/layout/Login/LoginDialog/LoginDialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import LoginDialog from './LoginDialog' 6 | export default { 7 | component: LoginDialog, 8 | argTypes: { action: { onclick: 'toggleLoginDialog' } }, 9 | title: 'Layout/Login/LoginDialog', 10 | } as ComponentMeta<typeof LoginDialog> 11 | 12 | const Template: ComponentStory<typeof LoginDialog> = () => <LoginDialog /> 13 | 14 | export const Common = Template.bind({}) 15 | 16 | // Common 17 | 18 | Common.args = { 19 | isOpen: true, 20 | } 21 | -------------------------------------------------------------------------------- /__mocks__/stories/customerAccountCardsMock.ts: -------------------------------------------------------------------------------- 1 | import { CardCollection } from '@/lib/gql/types' 2 | 3 | export const customerAccountCardsMock: { customerAccountCards: CardCollection } = { 4 | customerAccountCards: { 5 | totalCount: 1, 6 | items: [ 7 | { 8 | id: '726df82aaf8a406fac8efdecb54964dd', 9 | nameOnCard: 'Chandradeepta Laha', 10 | cardType: 'VISA', 11 | expireMonth: 12, 12 | expireYear: 2027, 13 | cardNumberPart: '************1111', 14 | contactId: 1495, 15 | isDefaultPayMethod: true, 16 | }, 17 | ], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /__mocks__/stories/fulfillmentOptionsMock.ts: -------------------------------------------------------------------------------- 1 | import { FulfillmentOption } from '@/lib/types' 2 | 3 | export const fulfillmentOptionsMock: FulfillmentOption[] = [ 4 | { 5 | value: 'DirectShip', 6 | code: 'DS', 7 | name: 'Direct Ship', 8 | label: 'Ship to Home', 9 | details: 'Available to Ship', 10 | isRequired: false, 11 | shortName: 'Ship', 12 | }, 13 | { 14 | value: 'InStorePickup', 15 | code: 'SP', 16 | name: 'In Store Pickup', 17 | label: 'Pickup in Store', 18 | details: 'Available at', 19 | isRequired: false, 20 | shortName: 'Pickup', 21 | }, 22 | ] 23 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/create-checkout-destination.ts: -------------------------------------------------------------------------------- 1 | import { destinationContactFragment } from '../../fragments' 2 | 3 | const createCheckoutDestination = /* GraphQL */ ` 4 | mutation createCheckoutDestination($checkoutId: String!, $destinationInput: CrDestinationInput) { 5 | createCheckoutDestination(checkoutId: $checkoutId, destinationInput: $destinationInput) { 6 | destinationContact { 7 | ...destinationContactFragment 8 | } 9 | id 10 | isDestinationCommercial 11 | } 12 | } 13 | ${destinationContactFragment} 14 | ` 15 | 16 | export default createCheckoutDestination 17 | -------------------------------------------------------------------------------- /components/common/FilterTiles/FilterTiles.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import FilterTiles from './FilterTiles' 6 | import { facetValueMock } from '@/__mocks__/stories/facetValueMock' 7 | 8 | export default { 9 | title: 'Common/FilterTiles', 10 | component: FilterTiles, 11 | } as ComponentMeta<typeof FilterTiles> 12 | 13 | const Template: ComponentStory<typeof FilterTiles> = (args) => <FilterTiles {...args} /> 14 | 15 | export const Tiles = Template.bind({}) 16 | 17 | Tiles.args = { 18 | appliedFilters: facetValueMock, 19 | } 20 | -------------------------------------------------------------------------------- /components/layout/RegisterAccount/Content/Content.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import Content from './Content' 6 | 7 | export default { 8 | title: 'Layout/Register Account/Content', 9 | component: Content, 10 | argTypes: { 11 | onRegisterNow: { action: 'register form data' }, 12 | }, 13 | } as ComponentMeta<typeof Content> 14 | 15 | const Template: ComponentStory<typeof Content> = (args) => <Content {...args} /> 16 | 17 | export const Common = Template.bind({}) 18 | 19 | Common.args = { 20 | setAutoFocus: true, 21 | } 22 | -------------------------------------------------------------------------------- /lib/gql/fragments/destinationContact.ts: -------------------------------------------------------------------------------- 1 | export const destinationContactFragment = ` 2 | fragment destinationContactFragment on CrContact { 3 | id 4 | email 5 | firstName 6 | middleNameOrInitial 7 | lastNameOrSurname 8 | phoneNumbers{ 9 | home 10 | } 11 | address{ 12 | address1 13 | address2 14 | address3 15 | address4 16 | cityOrTown 17 | stateOrProvince 18 | postalOrZipCode 19 | countryCode 20 | isValidated 21 | addressType 22 | } 23 | } 24 | ` 25 | -------------------------------------------------------------------------------- /components/common/KiboImage/KiboImage.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import KiboImage from './KiboImage' 6 | import Logo from '@/public/kibo_logo.png' 7 | 8 | export default { 9 | title: 'Common/KiboImage', 10 | component: KiboImage, 11 | } as ComponentMeta<typeof KiboImage> 12 | 13 | const Template: ComponentStory<typeof KiboImage> = (args) => <KiboImage {...args} /> 14 | 15 | export const Common = Template.bind({}) 16 | Common.args = { 17 | src: Logo, 18 | alt: 'test-alt-text', 19 | width: '100', 20 | height: '100', 21 | } 22 | -------------------------------------------------------------------------------- /components/dialogs/AddToCartConfirmation/Content/Content.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import Content from './Content' 6 | import { cartItemMock } from '@/__mocks__/stories/cartItemMock' 7 | 8 | export default { 9 | title: 'Dialogs/AddToCartDialog/Content', 10 | component: Content, 11 | } as ComponentMeta<typeof Content> 12 | 13 | const Template: ComponentStory<typeof Content> = ({ ...args }) => <Content {...args} /> 14 | 15 | // Common 16 | export const Common = Template.bind({}) 17 | 18 | Common.args = { 19 | cartItem: cartItemMock, 20 | } 21 | -------------------------------------------------------------------------------- /lib/helpers/__tests__/buildBreadcrumbsParams.spec.js: -------------------------------------------------------------------------------- 1 | import { ProductCustomMock } from '../../../__mocks__/stories/ProductCustomMock' 2 | import { buildBreadcrumbsParams } from '../buildBreadcrumbsParams' 3 | 4 | describe('[helpers] buildBreadcrumbsParams function', () => { 5 | it('should return the breadcrumbs', () => { 6 | const breadcrumbs = [ 7 | { 8 | text: 'Biking', 9 | link: `30`, 10 | }, 11 | { 12 | text: 'Mountain', 13 | link: `42`, 14 | }, 15 | ] 16 | expect(buildBreadcrumbsParams(ProductCustomMock.categories[0])).toStrictEqual(breadcrumbs) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/pages/order-status.tsx: -------------------------------------------------------------------------------- 1 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations' 2 | 3 | import { OrderStatusTemplate } from '@/components/page-templates' 4 | 5 | import type { NextPage, GetServerSidePropsContext } from 'next' 6 | 7 | export async function getServerSideProps(context: GetServerSidePropsContext) { 8 | const { locale } = context 9 | 10 | return { 11 | props: { 12 | ...(await serverSideTranslations(locale as string, ['common'])), 13 | }, 14 | } 15 | } 16 | 17 | const OrderStatusPage: NextPage = (props: any) => <OrderStatusTemplate {...props} /> 18 | 19 | export default OrderStatusPage 20 | -------------------------------------------------------------------------------- /components/dialogs/AddToCartConfirmation/Actions/Actions.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import Actions from './Actions' 6 | 7 | export default { 8 | title: 'Dialogs/AddToCartDialog/Actions', 9 | component: Actions, 10 | } as ComponentMeta<typeof Actions> 11 | 12 | const Template: ComponentStory<typeof Actions> = ({ ...args }) => <Actions {...args} /> 13 | 14 | // Common 15 | export const Common = Template.bind({}) 16 | 17 | Common.args = { 18 | onGoToCart: () => 'go to cart clicked', 19 | onContinueShopping: () => 'continue shopping clicked', 20 | } 21 | -------------------------------------------------------------------------------- /components/my-account/MyProfile/MyProfile.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import MyProfile from './MyProfile' 6 | 7 | export default { 8 | title: 'My Account/My Profile', 9 | component: MyProfile, 10 | } as ComponentMeta<typeof MyProfile> 11 | 12 | const Template: ComponentStory<typeof MyProfile> = (args) => <MyProfile {...args} /> 13 | 14 | export const Common = Template.bind({}) 15 | 16 | Common.args = { 17 | user: { 18 | id: 1012, 19 | firstName: 'Kripa', 20 | lastName: 'Babu', 21 | emailAddress: 'kripa@gmail.com', 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /lib/api/operations/get-category.ts: -------------------------------------------------------------------------------- 1 | import { fetcher } from '@/lib/api/util' 2 | 3 | const query = /* GraphQL */ ` 4 | query getProduct($productCode: String!) { 5 | product(productCode: $productCode) { 6 | content { 7 | productName 8 | productImages { 9 | imageUrl 10 | } 11 | } 12 | price { 13 | price 14 | salePrice 15 | } 16 | } 17 | } 18 | ` 19 | 20 | export default async function getProduct(productCode: any) { 21 | const variables = { 22 | productCode, 23 | } 24 | const response = await fetcher({ query, variables }, null) 25 | return response.data?.product 26 | } 27 | -------------------------------------------------------------------------------- /lib/gql/fragments/userContacts.ts: -------------------------------------------------------------------------------- 1 | export const userContactFields = /* GraphQL */ ` 2 | fragment userContactFields on CustomerContact { 3 | accountId 4 | types { 5 | name 6 | isPrimary 7 | } 8 | id 9 | email 10 | firstName 11 | middleNameOrInitial 12 | lastNameOrSurname 13 | phoneNumbers { 14 | home 15 | mobile 16 | work 17 | } 18 | address { 19 | address1 20 | address2 21 | address3 22 | address4 23 | cityOrTown 24 | stateOrProvince 25 | postalOrZipCode 26 | countryCode 27 | addressType 28 | isValidated 29 | } 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /lib/helpers/__tests__/buildCreateWishlistItemParams.spec.ts: -------------------------------------------------------------------------------- 1 | import getConfig from 'next/config' 2 | 3 | import { buildCreateWishlistItemParams } from '../buildCreateWishlistItemParams' 4 | 5 | describe('[helpers] buildCreateWishlistItemParams function', () => { 6 | it('should return the buildCreateWishlistItemParams variables', () => { 7 | const { publicRuntimeConfig } = getConfig() 8 | const customerAccountId = 1143 9 | expect(buildCreateWishlistItemParams(customerAccountId)).toStrictEqual({ 10 | wishlistInput: { 11 | customerAccountId, 12 | name: publicRuntimeConfig.defaultWishlistName, 13 | }, 14 | }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /hooks/queries/useCartQueries/useCartQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { createQueryClientWrapper } from '../../../__test__/utils/renderWithQueryClient' 4 | import { useCartQueries } from './useCartQueries' 5 | import { cartMock } from '@/__mocks__/stories/cartMock' 6 | 7 | describe('[hooks] useUser', () => { 8 | it('should return cart ', async () => { 9 | const { result, waitFor } = renderHook(() => useCartQueries({}), { 10 | wrapper: createQueryClientWrapper(), 11 | }) 12 | 13 | await waitFor(() => { 14 | expect(result.current.data).toStrictEqual(cartMock.currentCart) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/update-checkout-payment-action.ts: -------------------------------------------------------------------------------- 1 | const updateCheckoutPaymentActionMutation = /* GraphQL */ ` 2 | mutation updateCheckoutPaymentAction( 3 | $checkoutId: String! 4 | $paymentId: String! 5 | $paymentActionInput: PaymentActionInput 6 | ) { 7 | updateCheckoutPaymentAction( 8 | checkoutId: $checkoutId 9 | paymentId: $paymentId 10 | paymentActionInput: $paymentActionInput 11 | ) { 12 | id 13 | originalCartId 14 | destinations { 15 | id 16 | data 17 | } 18 | total 19 | subTotal 20 | } 21 | } 22 | ` 23 | 24 | export default updateCheckoutPaymentActionMutation 25 | -------------------------------------------------------------------------------- /lib/gql/mutations/update-customer-account-contact.ts: -------------------------------------------------------------------------------- 1 | import { userContactFields } from '../fragments/userContacts' 2 | 3 | const updateCustomerAccountContact = /* GraphQL */ ` 4 | ${userContactFields} 5 | 6 | mutation updateUserAddress( 7 | $accountId: Int! 8 | $contactId: Int! 9 | $userId: String 10 | $customerContactInput: CustomerContactInput! 11 | ) { 12 | updateCustomerAccountContact( 13 | accountId: $accountId 14 | contactId: $contactId 15 | userId: $userId 16 | customerContactInput: $customerContactInput 17 | ) { 18 | ...userContactFields 19 | } 20 | } 21 | ` 22 | export default updateCustomerAccountContact 23 | -------------------------------------------------------------------------------- /lib/api/handlers/search.ts: -------------------------------------------------------------------------------- 1 | import { productSearch } from '../operations' 2 | import type { CategorySearchParams } from '@/lib/types' 3 | 4 | import type { NextApiRequest, NextApiResponse } from 'next' 5 | 6 | export default async function searchHandler(req: NextApiRequest, res: NextApiResponse) { 7 | try { 8 | // get variables 9 | const response = await productSearch(req.query as unknown as CategorySearchParams) 10 | res.status(200).json({ results: response?.data?.products }) 11 | } catch (error) { 12 | console.error(error) 13 | const message = 'An unexpected error ocurred' 14 | res.status(500).json({ data: null, errors: [{ message }] }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /components/common/PaymentCard/PaymentCard.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import PaymentCard from './PaymentCard' 6 | 7 | export default { 8 | title: 'Common/PaymentCard', 9 | component: PaymentCard, 10 | } as ComponentMeta<typeof PaymentCard> 11 | 12 | const Template: ComponentStory<typeof PaymentCard> = (args) => <PaymentCard {...args} /> 13 | 14 | export const Common = Template.bind({}) 15 | 16 | Common.args = { 17 | title: 'Payment Method', 18 | cardNumberPart: '****************1234', 19 | expireMonth: 4, 20 | expireYear: 2026, 21 | cardType: 'VISA', 22 | radio: false, 23 | } 24 | -------------------------------------------------------------------------------- /components/layout/AppHeader/Icons/HamburgerIcon/HamburgerIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import CloseIcon from '@mui/icons-material/Close' 4 | import MenuIcon from '@mui/icons-material/Menu' 5 | 6 | import { HeaderAction } from '@/components/common' 7 | import { useHeaderContext } from '@/context' 8 | 9 | const HamburgerIcon = () => { 10 | const { headerState, toggleHamburgerMenu } = useHeaderContext() 11 | 12 | return ( 13 | <HeaderAction 14 | icon={headerState?.isHamburgerMenuVisible ? CloseIcon : MenuIcon} 15 | iconFontSize={'medium'} 16 | onClick={() => toggleHamburgerMenu()} 17 | /> 18 | ) 19 | } 20 | 21 | export default HamburgerIcon 22 | -------------------------------------------------------------------------------- /lib/api/operations/get-multi-ship-checkout.ts: -------------------------------------------------------------------------------- 1 | import getUserClaimsFromRequest from '../util/getUserClaimsFromRequest' 2 | import { fetcher } from '@/lib/api/util' 3 | import { getMultiShipCheckoutQuery as query } from '@/lib/gql/queries' 4 | 5 | import type { Checkout } from '@/lib/gql/types' 6 | 7 | export default async function getMultiShipCheckout( 8 | checkoutId: string, 9 | req: any, 10 | res: any 11 | ): Promise<Checkout> { 12 | const variables = { 13 | checkoutId, 14 | } 15 | 16 | const userClaims = await getUserClaimsFromRequest(req, res) 17 | const response = await fetcher({ query, variables }, { userClaims }) 18 | 19 | return response.data?.checkout 20 | } 21 | -------------------------------------------------------------------------------- /components/layout/Login/LoginContent/LoginContent.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import LoginContent from './LoginContent' 6 | 7 | export default { 8 | component: LoginContent, 9 | title: 'Layout/Login/LoginContent', 10 | argTypes: { 11 | onClose: { action: 'onClose' }, 12 | onLogin: { action: 'login' }, 13 | onForgotPasswordClick: { action: 'clickForgotPassword' }, 14 | }, 15 | } as ComponentMeta<typeof LoginContent> 16 | 17 | const Template: ComponentStory<typeof LoginContent> = (args) => <LoginContent {...args} /> 18 | 19 | // Common 20 | export const Common = Template.bind({}) 21 | -------------------------------------------------------------------------------- /lib/gql/mutations/subscription/skip-next-subscription-mutation.ts: -------------------------------------------------------------------------------- 1 | import { baseSkipSubscriptionFragment, subscriptionItemFragment } from '@/lib/gql/fragments' 2 | 3 | const skipNextSubscriptionMutation = /* GraphQL */ ` 4 | mutation skipNextSubscription($subscriptionId: String!) { 5 | subscription: skipNextSubscription(subscriptionId: $subscriptionId) { 6 | ...baseSkipSubscriptionFragment 7 | items { 8 | ...subscriptionItemFragment 9 | } 10 | frequency { 11 | unit 12 | value 13 | } 14 | } 15 | } 16 | ${baseSkipSubscriptionFragment} 17 | ${subscriptionItemFragment} 18 | ` 19 | 20 | export default skipNextSubscriptionMutation 21 | -------------------------------------------------------------------------------- /components/common/AddressCard/AddressCard.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import AddressCard from './AddressCard' 6 | 7 | export default { 8 | title: 'Common/AddressCard', 9 | component: AddressCard, 10 | } as ComponentMeta<typeof AddressCard> 11 | 12 | const Template: ComponentStory<typeof AddressCard> = (args) => <AddressCard {...args} /> 13 | 14 | export const Common = Template.bind({}) 15 | 16 | Common.args = { 17 | title: 'Billing Address', 18 | address1: '1234, My Address', 19 | address2: '1104', 20 | cityOrTown: 'Austin', 21 | stateOrProvince: 'Texas', 22 | postalOrZipCode: '78727', 23 | } 24 | -------------------------------------------------------------------------------- /hooks/queries/useUserQueries/useUserQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useUserQueries } from './useUserQueries' 4 | import { userMock } from '@/__mocks__/stories/userMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useUser', () => { 8 | it('should return current user when user is logged in', async () => { 9 | const { result, waitFor } = renderHook(() => useUserQueries(), { 10 | wrapper: createQueryClientWrapper(), 11 | }) 12 | 13 | await waitFor(() => { 14 | expect(result.current.data).toStrictEqual(userMock.customerAccount) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /lib/gql/mutations/subscription/delete-subscription-mutation.ts: -------------------------------------------------------------------------------- 1 | const deleteSubscriptionMutation = /* GraphQL */ ` 2 | mutation deleteSubscriptionItem( 3 | $subscriptionId: String! 4 | $subscriptionItemId: String! 5 | $subscriptionReasonInput: SubscriptionReasonInput 6 | ) { 7 | subscription: deleteSubscriptionItem( 8 | subscriptionId: $subscriptionId 9 | subscriptionItemId: $subscriptionItemId 10 | subscriptionReasonInput: $subscriptionReasonInput 11 | ) { 12 | reasons { 13 | actionName 14 | reasonCode 15 | description 16 | moreInfo 17 | } 18 | } 19 | } 20 | ` 21 | 22 | export default deleteSubscriptionMutation 23 | -------------------------------------------------------------------------------- /public/locales/es/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "add-to-cart":"", 3 | "add-to-wishlist":"", 4 | "added-to-cart":"", 5 | "available-at":"", 6 | "available-to-ship":"", 7 | "back":"", 8 | "cancel":"", 9 | "cart":"", 10 | "clear-all":"", 11 | "color":"", 12 | "continue-shopping":"", 13 | "edit":"", 14 | "filter-by":"", 15 | "go-to-cart":"", 16 | "log-in":"", 17 | "my-account":"", 18 | "password":"", 19 | "price":"", 20 | "register":"", 21 | "select-store":"", 22 | "set-store":"", 23 | "showing-results-for":"", 24 | "sign-in":"", 25 | "size":"", 26 | "sort-by":"", 27 | "total":"", 28 | "use-current-location":"" 29 | } -------------------------------------------------------------------------------- /lib/gql/queries/get-orders.ts: -------------------------------------------------------------------------------- 1 | import { baseOrderFragment, orderItemFragment, orderPaymentFragment } from '../fragments/orders' 2 | 3 | const getOrdersQuery = /* GraphQL */ ` 4 | query getOrders($filter: String, $startIndex: Int, $pageSize: Int) { 5 | orders(filter: $filter, startIndex: $startIndex, pageSize: $pageSize) { 6 | pageCount 7 | items { 8 | ...baseOrderFragment 9 | items { 10 | ...orderItemFragment 11 | } 12 | payments { 13 | ...orderPaymentFragment 14 | } 15 | } 16 | } 17 | } 18 | ${orderItemFragment} 19 | ${baseOrderFragment} 20 | ${orderPaymentFragment} 21 | ` 22 | 23 | export default getOrdersQuery 24 | -------------------------------------------------------------------------------- /components/dialogs/EditOrderDateDialog/EditOrderDateDialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | import dayjs from 'dayjs' 5 | 6 | import EditOrderDateDialog from './EditOrderDateDialog' 7 | 8 | export default { 9 | title: 'Dialogs/Edit Order Date Dialog', 10 | component: EditOrderDateDialog, 11 | } as ComponentMeta<typeof EditOrderDateDialog> 12 | 13 | const Template: ComponentStory<typeof EditOrderDateDialog> = ({ ...args }) => ( 14 | <EditOrderDateDialog {...args} /> 15 | ) 16 | 17 | // Common 18 | export const Common = Template.bind({}) 19 | 20 | Common.args = { 21 | subscriptionId: '1', 22 | orderDate: dayjs(), 23 | } 24 | -------------------------------------------------------------------------------- /lib/gql/mutations/my-account/update-customer-account-contact.ts: -------------------------------------------------------------------------------- 1 | import { userContactFields } from '@/lib/gql/fragments/userContacts' 2 | 3 | const updateCustomerAccountContact = /* GraphQL */ ` 4 | ${userContactFields} 5 | 6 | mutation updateCustomerAccountContact( 7 | $accountId: Int! 8 | $contactId: Int! 9 | $userId: String 10 | $customerContactInput: CustomerContactInput! 11 | ) { 12 | updateCustomerAccountContact( 13 | accountId: $accountId 14 | contactId: $contactId 15 | userId: $userId 16 | customerContactInput: $customerContactInput 17 | ) { 18 | ...userContactFields 19 | } 20 | } 21 | ` 22 | 23 | export default updateCustomerAccountContact 24 | -------------------------------------------------------------------------------- /lib/helpers/buildSubscriptionFulfillmentInfoParams.ts: -------------------------------------------------------------------------------- 1 | import type { Address } from '@/lib/types' 2 | 3 | import type { Subscription } from '@/lib/gql/types' 4 | 5 | export const buildSubscriptionFulfillmentInfoParams = ( 6 | subscriptionDetailsData: Subscription, 7 | data: Address 8 | ) => { 9 | return { 10 | subscriptionId: subscriptionDetailsData.id as string, 11 | fulfillmentInfoInput: { 12 | fulfillmentContact: { 13 | ...data.contact, 14 | }, 15 | shippingMethodCode: subscriptionDetailsData?.fulfillmentInfo?.shippingMethodCode as string, 16 | shippingMethodName: subscriptionDetailsData?.fulfillmentInfo?.shippingMethodName as string, 17 | }, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/types/Checkout.ts: -------------------------------------------------------------------------------- 1 | import { FormStates } from '../constants' 2 | 3 | import type { CrContact, Maybe } from '../gql/types' 4 | 5 | export interface PersonalDetails { 6 | email: Maybe<string> | undefined 7 | } 8 | 9 | export interface Action { 10 | type: FormStates.COMPLETE | FormStates.INCOMPLETE | FormStates.VALIDATE 11 | } 12 | 13 | export type MultiShipAddress = { 14 | destinationId: string 15 | address: CrContact 16 | } 17 | 18 | export type ShipOption = { 19 | value: string 20 | code: string 21 | name: string 22 | label: string 23 | shortName: string 24 | } 25 | 26 | export interface CustomDestinationInput extends CrContact { 27 | destinationId?: string 28 | itemId: string 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | .env 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | 40 | storybook-static 41 | .scannerwork 42 | .test-report 43 | 44 | # vscode 45 | /.vscode/ 46 | 47 | test-report.xml -------------------------------------------------------------------------------- /components/order/OrderConfirmation/OrderConfirmation.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import OrderConfirmation from './OrderConfirmation' 6 | import { orderMock } from '@/__mocks__/stories/orderMock' 7 | 8 | export default { 9 | title: 'Order/OrderConfirmation', 10 | component: OrderConfirmation, 11 | parameters: { 12 | layout: 'fullscreen', 13 | }, 14 | } as ComponentMeta<typeof OrderConfirmation> 15 | 16 | const Template: ComponentStory<typeof OrderConfirmation> = (args) => <OrderConfirmation {...args} /> 17 | 18 | export const Common = Template.bind({}) 19 | Common.args = { 20 | order: orderMock.checkout, 21 | } 22 | -------------------------------------------------------------------------------- /lib/helpers/buildAddToCartParams.ts: -------------------------------------------------------------------------------- 1 | import { AddToCartProductInput } from '@/hooks' 2 | 3 | import type { CrCartItemInput, CrSubscriptionInfo } from '../gql/types' 4 | 5 | export const buildAddToCartParams = ( 6 | product: AddToCartProductInput, 7 | quantity: number, 8 | subscription?: CrSubscriptionInfo 9 | ): CrCartItemInput => { 10 | return { 11 | product: { 12 | options: product?.options, 13 | productCode: product?.productCode || '', 14 | variationProductCode: product?.variationProductCode || '', 15 | }, 16 | quantity, 17 | fulfillmentMethod: product?.fulfillmentMethod, 18 | fulfillmentLocationCode: product?.purchaseLocationCode, 19 | subscription, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/react-query/queryClient.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from 'react-query' 2 | 3 | const queryClientHandler = (error: unknown) => { 4 | const id = 'react-query-error' 5 | const title = error instanceof Error ? error.message : 'Unable to connect server' 6 | const status = 'error' 7 | 8 | console.log(`id: ${id}, title: ${title}, status: ${status}`) 9 | } 10 | 11 | export const generateQueryClient = (): QueryClient => { 12 | return new QueryClient({ 13 | defaultOptions: { 14 | queries: { 15 | onError: queryClientHandler, 16 | }, 17 | mutations: { 18 | onError: queryClientHandler, 19 | }, 20 | }, 21 | }) 22 | } 23 | 24 | export const queryClient = generateQueryClient() 25 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse, NextRequest } from 'next/server' 2 | 3 | const checkIsAuthenticated = (req: NextRequest) => { 4 | const cookie = req.headers.get('cookie') 5 | const cookieValue = cookie?.split('kibo_at=')[1] 6 | const decodedCookie = JSON.parse(atob(cookieValue as string)) 7 | return decodedCookie?.userId 8 | } 9 | 10 | export function middleware(request: NextRequest) { 11 | if (request.nextUrl.pathname.startsWith('/my-account')) { 12 | if (checkIsAuthenticated(request)) { 13 | return NextResponse.next() 14 | } 15 | 16 | 17 | const homeUrl = new URL('/', request.url) 18 | console.log('homeUrl' , homeUrl) 19 | return NextResponse.redirect(homeUrl) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/api/handlers/graphql.ts: -------------------------------------------------------------------------------- 1 | import { fetcher } from '../util' 2 | import getUserClaimsFromRequest from '../util/getUserClaimsFromRequest' 3 | 4 | import type { NextApiRequest, NextApiResponse } from 'next' 5 | 6 | export default async function graphQLHandler(req: NextApiRequest, res: NextApiResponse) { 7 | try { 8 | const { query, variables } = req.body 9 | const userClaims = await getUserClaimsFromRequest(req, res) 10 | const response = await fetcher({ query, variables }, { userClaims }) 11 | res.status(200).json(response) 12 | } catch (error) { 13 | console.error(error) 14 | const message = 'An unexpected error ocurred' 15 | res.status(500).json({ data: null, errors: [{ message }] }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /components/order/OrderHistoryItem/OrderHistoryItem.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import OrderHistoryItem from './OrderHistoryItem' 6 | 7 | // Common 8 | export default { 9 | title: 'Order/OrderHistoryItem', 10 | component: OrderHistoryItem, 11 | } as ComponentMeta<typeof OrderHistoryItem> 12 | 13 | const Template: ComponentStory<typeof OrderHistoryItem> = (args) => <OrderHistoryItem {...args} /> 14 | 15 | // Default 16 | export const Common = Template.bind({}) 17 | Common.args = { 18 | id: '1', 19 | submittedDate: 'March 16, 2022', 20 | productNames: 'Katahdin 50 Pack', 21 | orderTotal: 90.0, 22 | orderStatus: 'Abandoned', 23 | } 24 | -------------------------------------------------------------------------------- /hooks/custom/usePaymentTypes/usePaymentTypes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module usePaymentTypes 3 | */ 4 | import getConfig from 'next/config' 5 | 6 | /** 7 | * [Custom Hook] Loads the payment method types 8 | * 9 | * Return function and value: 10 | * 1. loadPaymentTypes() => Returns the paymentTypes available 11 | * 2. error => default value returned as null 12 | * 3. loading => It returns true if request is in process and once completed it return false 13 | */ 14 | 15 | export const usePaymentTypes = () => { 16 | const { publicRuntimeConfig } = getConfig() 17 | const loadPaymentTypes = () => publicRuntimeConfig.paymentTypes 18 | 19 | return { 20 | loadPaymentTypes, 21 | error: null, 22 | loading: false, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /hooks/queries/useWishlistQueries/useWishlistQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useWishlistQueries } from './useWishlistQueries' 4 | import { wishlistMock } from '@/__mocks__/stories/wishlistMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useWishlistQueries', () => { 8 | it('should return current wishlists', async () => { 9 | const { result, waitFor } = renderHook(() => useWishlistQueries(), { 10 | wrapper: createQueryClientWrapper(), 11 | }) 12 | 13 | await waitFor(() => result.current.isSuccess) 14 | 15 | expect(result.current.data).toStrictEqual(wishlistMock?.items[0]) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /lib/api/operations/get-checkout.ts: -------------------------------------------------------------------------------- 1 | import getUserClaimsFromRequest from '../util/getUserClaimsFromRequest' 2 | import { fetcher } from '@/lib/api/util' 3 | import { getCheckoutQuery as query } from '@/lib/gql/queries' 4 | 5 | import type { CrOrder } from '@/lib/gql/types' 6 | 7 | export default async function getCheckout( 8 | checkoutId: string, 9 | req: any, 10 | res: any 11 | ): Promise<CrOrder> { 12 | const variables = { 13 | checkoutId, 14 | } 15 | 16 | const userClaims = await getUserClaimsFromRequest(req, res) 17 | // const userClaims = req ? await getUserClaimsFromRequest(req) : null 18 | const response = await fetcher({ query, variables }, { userClaims }) 19 | 20 | return response.data?.checkout 21 | } 22 | -------------------------------------------------------------------------------- /components/layout/MegaMenu/MegaMenu.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import MegaMenu from './MegaMenu' 6 | import { categoryTreeDataMock } from '@/__mocks__/stories/categoryTreeDataMock' 7 | 8 | export default { 9 | title: 'Layout/MegaMenu', 10 | component: MegaMenu, 11 | } as ComponentMeta<typeof MegaMenu> 12 | 13 | const Template: ComponentStory<typeof MegaMenu> = (args) => <MegaMenu {...args} /> 14 | 15 | export const Common = Template.bind({}) 16 | 17 | Common.args = { 18 | categoryTree: categoryTreeDataMock.categoriesTree.items?.filter((item) => item?.isDisplayed), 19 | onBackdropToggle: (isOpen) => { 20 | return isOpen 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /lib/gql/client/client.spec.js: -------------------------------------------------------------------------------- 1 | import { URL } from 'url' 2 | 3 | import { makeGraphQLClient } from './index' 4 | 5 | describe('[lib] - GraphQL Client', () => { 6 | it('should create a new graphql client using .env for endpoint', () => { 7 | const expected = process.env.NEXT_PUBLIC_URL 8 | const client = makeGraphQLClient() 9 | const clientUrl = new URL(client.url) 10 | expect(clientUrl.origin).toEqual(expected) 11 | }) 12 | it('should create a new graphql client using endpoint as parameter', () => { 13 | const endpoint = 'https://kiboshop/api/graphql' 14 | const client = makeGraphQLClient(endpoint) 15 | const clientUrl = new URL(client.url) 16 | expect(clientUrl.href).toEqual(endpoint) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /components/order/ViewOrderStatus/ViewOrderStatus.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import ViewOrderStatus from './ViewOrderStatus' 6 | 7 | // Common 8 | export default { 9 | title: 'Order/ViewOrderStatus', 10 | component: ViewOrderStatus, 11 | } as ComponentMeta<typeof ViewOrderStatus> 12 | 13 | const Template: ComponentStory<typeof ViewOrderStatus> = (args) => <ViewOrderStatus {...args} /> 14 | 15 | // Default 16 | export const Common = Template.bind({}) 17 | Common.args = { 18 | lookupErrorMessage: undefined, 19 | } 20 | 21 | export const WithErrorMeesage = Template.bind({}) 22 | WithErrorMeesage.args = { 23 | lookupErrorMessage: 'Error 500', 24 | } 25 | -------------------------------------------------------------------------------- /lib/helpers/__tests__/buildAddToWishlistParams.spec.ts: -------------------------------------------------------------------------------- 1 | import { buildAddToWishlistItemParams } from '../buildAddToWishlistParams' 2 | 3 | describe('[helpers] buildAddToWishlistInput function', () => { 4 | it('should return the buildAddToWishlistItemParams variables', () => { 5 | const product = { 6 | productCode: 'MS-BTL-005', 7 | isPackagedStandAlone: true, 8 | variationProductCode: 'MS-BTL-005', 9 | options: [], 10 | } 11 | const wishlistId = '13cc2e5236615b000102f572000045a4' 12 | 13 | expect(buildAddToWishlistItemParams(product, wishlistId)).toStrictEqual({ 14 | wishlistId, 15 | wishlistItemInput: { 16 | product, 17 | quantity: 1, 18 | }, 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/pages/my-account/index.tsx: -------------------------------------------------------------------------------- 1 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations' 2 | 3 | import { MyAccountTemplate } from '@/components/page-templates' 4 | 5 | import type { GetServerSideProps, GetServerSidePropsContext, NextPage } from 'next' 6 | 7 | export const getServerSideProps: GetServerSideProps = async ( 8 | context: GetServerSidePropsContext 9 | ) => { 10 | const { locale } = context 11 | 12 | return { 13 | props: { 14 | ...(await serverSideTranslations(locale as string, ['common'])), 15 | }, 16 | } 17 | } 18 | 19 | const MyAccountPage: NextPage = () => { 20 | return ( 21 | <> 22 | <MyAccountTemplate /> 23 | </> 24 | ) 25 | } 26 | 27 | export default MyAccountPage 28 | -------------------------------------------------------------------------------- /components/product-listing/FacetList/FacetList.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import FacetList from './FacetList' 6 | import { productSearchResultMock } from '@/__mocks__/stories/productSearchResultMock' 7 | 8 | import type { Facet } from '@/lib/gql/types' 9 | 10 | // default 11 | export default { 12 | title: 'product-listing/FacetList', 13 | component: FacetList, 14 | } as ComponentMeta<typeof FacetList> 15 | 16 | const Template: ComponentStory<typeof FacetList> = (args) => <FacetList {...args} /> 17 | 18 | // Common 19 | export const Common = Template.bind({}) 20 | 21 | Common.args = { 22 | facetList: productSearchResultMock?.facets as Facet[], 23 | } 24 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/update-checkout-destination.ts: -------------------------------------------------------------------------------- 1 | import { destinationContactFragment } from '../../fragments' 2 | 3 | const updateCheckoutDestination = /* GraphQL */ ` 4 | mutation updateCheckoutDestination( 5 | $checkoutId: String! 6 | $destinationId: String! 7 | $destinationInput: CrDestinationInput 8 | ) { 9 | updateCheckoutDestination( 10 | checkoutId: $checkoutId 11 | destinationId: $destinationId 12 | destinationInput: $destinationInput 13 | ) { 14 | destinationContact { 15 | ...destinationContactFragment 16 | } 17 | id 18 | isDestinationCommercial 19 | } 20 | } 21 | ${destinationContactFragment} 22 | ` 23 | 24 | export default updateCheckoutDestination 25 | -------------------------------------------------------------------------------- /lib/gql/mutations/order-return-items/createReturnItemMutation.ts: -------------------------------------------------------------------------------- 1 | import { checkoutItemProductFragment } from '@/lib/gql/fragments' 2 | 3 | const createReturnItemMutation = /* GraphQL */ ` 4 | mutation createReturn($returnObjInput: ReturnObjInput) { 5 | createReturn(returnObjInput: $returnObjInput) { 6 | customerAccountId 7 | returnNumber 8 | returnOrderId 9 | status 10 | items { 11 | orderItemId 12 | reasons { 13 | reason 14 | quantity 15 | } 16 | returnType 17 | product { 18 | ...checkoutItemProductFragment 19 | } 20 | } 21 | } 22 | } 23 | ${checkoutItemProductFragment} 24 | ` 25 | 26 | export default createReturnItemMutation 27 | -------------------------------------------------------------------------------- /lib/gql/queries/checkout/get-or-create-checkout-from-cart-mutation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | baseCheckoutFragment, 3 | checkoutLineItemFragment, 4 | checkoutPaymentFragment, 5 | } from '../../fragments/checkout' 6 | 7 | const getOrCreateCheckoutFromCartMutation = /* GraphQL */ ` 8 | mutation getOrCreateCheckoutFromCart($cartId: String!) { 9 | checkout: createOrder(cartId: $cartId) { 10 | ...baseCheckoutFragment 11 | items { 12 | ...checkoutLineItemFragment 13 | } 14 | payments { 15 | ...checkoutPaymentFragment 16 | } 17 | } 18 | } 19 | ${baseCheckoutFragment} 20 | ${checkoutLineItemFragment} 21 | ${checkoutPaymentFragment} 22 | ` 23 | 24 | export default getOrCreateCheckoutFromCartMutation 25 | -------------------------------------------------------------------------------- /lib/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Breadcrumb' 2 | export * from './ProductCustom' 3 | export * from './ProductProperties' 4 | export * from './NavigationLink' 5 | export * from './SearchParams' 6 | export * from './Price' 7 | export * from './FacetResultDataType' 8 | export * from './Fulfillment' 9 | export * from './LocationCustom' 10 | export * from './KiboRequest' 11 | export * from './Wishlist' 12 | export * from './PaymentTypes' 13 | export * from './CategoryTreeResponse' 14 | export * from './NextExtensions' 15 | export * from './AddressBook' 16 | export * from './MyProfile' 17 | export * from './Icons' 18 | export * from './OrderReturnItems' 19 | export * from './Checkout' 20 | export * from './FulfilmentInfo' 21 | export * from './BillingInfo' 22 | -------------------------------------------------------------------------------- /components/checkout/CardDetailsForm/CardDetailsForm.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import CardDetailsForm from './CardDetailsForm' 6 | import { CardForm } from '@/lib/types' 7 | 8 | export default { 9 | component: CardDetailsForm, 10 | title: 'checkout/CardDetailsForm', 11 | } as ComponentMeta<typeof CardDetailsForm> 12 | 13 | const Template: ComponentStory<typeof CardDetailsForm> = (args) => <CardDetailsForm {...args} /> 14 | 15 | // Common 16 | export const Common = Template.bind({}) 17 | 18 | Common.args = { 19 | onSaveCardData: (cardData: CardForm) => cardData, 20 | onFormStatusChange: (saveCardData) => { 21 | console.log(saveCardData) 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /hooks/queries/useProductPriceQueries/useProductPriceQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useProductPriceQueries } from './useProductPriceQueries' 4 | import { productPriceMock } from '@/__mocks__/stories/productPriceMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useProductPriceQueries', () => { 8 | it('should return product price', async () => { 9 | const { result, waitFor } = renderHook(() => useProductPriceQueries('Bot-123', true), { 10 | wrapper: createQueryClientWrapper(), 11 | }) 12 | 13 | await waitFor(() => result?.current?.isSuccess) 14 | expect(result.current.data).toEqual(productPriceMock) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /lib/gql/fragments/wishlist.ts: -------------------------------------------------------------------------------- 1 | export const wishlistItem = ` 2 | fragment wishlistItem on CrWishlistItem { 3 | id 4 | quantity 5 | total 6 | subtotal 7 | product { 8 | productCode 9 | name 10 | description 11 | imageUrl 12 | variationProductCode 13 | options { 14 | attributeFQN 15 | name 16 | value 17 | } 18 | price { 19 | price 20 | salePrice 21 | } 22 | } 23 | } 24 | 25 | ` 26 | 27 | export const wishlist = /* GraphQL */ ` 28 | fragment wishlist on CrWishlist { 29 | customerAccountId 30 | name 31 | id 32 | items { 33 | ...wishlistItem 34 | } 35 | } 36 | 37 | ${wishlistItem} 38 | ` 39 | -------------------------------------------------------------------------------- /components/home/SmallBanner/SmallBanner.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import SmallBanner from './SmallBanner' 6 | 7 | export default { 8 | title: 'home/SmallBanner', 9 | component: SmallBanner, 10 | } as ComponentMeta<typeof SmallBanner> 11 | 12 | const Template: ComponentStory<typeof SmallBanner> = (args) => <SmallBanner {...args} /> 13 | 14 | export const Common = Template.bind({}) 15 | 16 | const bannerItems = { 17 | title: 'Save up to 50% + Free Shipping', 18 | subtitle: 'Valid through 10/31.', 19 | callToAction: { title: 'Shop Now', url: '/category/deals' }, 20 | backgroundColor: '#A12E87', 21 | } 22 | 23 | Common.args = { 24 | bannerProps: bannerItems, 25 | } 26 | -------------------------------------------------------------------------------- /lib/api/util/api-auth-client.ts: -------------------------------------------------------------------------------- 1 | import { APIAuthClient, AppAuthTicket, ShopperAuthClient } from '@kibocommerce/graphql-client' 2 | import vercelFetch from '@vercel/fetch' 3 | 4 | import { getApiConfig } from './config-helpers' 5 | 6 | const fetch = vercelFetch() 7 | let authTicket: AppAuthTicket | undefined 8 | const authTicketMemCache = { 9 | // eslint-disable-next-line require-await 10 | getAuthTicket: async () => authTicket, 11 | setAuthTicket: (newAuthTicket: AppAuthTicket) => { 12 | authTicket = newAuthTicket 13 | }, 14 | } 15 | const apiAuthClient = new APIAuthClient(getApiConfig(), fetch, authTicketMemCache) 16 | const shopperAuthClient = new ShopperAuthClient(getApiConfig(), fetch, apiAuthClient) 17 | 18 | export { apiAuthClient, shopperAuthClient } 19 | -------------------------------------------------------------------------------- /lib/api/util/fetch-gql.ts: -------------------------------------------------------------------------------- 1 | import vercelFetch from '@vercel/fetch' 2 | 3 | import { apiAuthClient } from './api-auth-client' 4 | import { getGraphqlUrl } from './config-helpers' 5 | 6 | const fetch = vercelFetch() 7 | 8 | const fetcher = async ({ query, variables }: any, options: any) => { 9 | const authToken = await apiAuthClient.getAccessToken() 10 | const response = await fetch(getGraphqlUrl(), { 11 | method: 'POST', 12 | headers: { 13 | Authorization: `Bearer ${authToken}`, 14 | 'x-vol-user-claims': options?.userClaims, 15 | 'Content-Type': 'application/json', 16 | }, 17 | body: JSON.stringify({ 18 | query, 19 | variables, 20 | }), 21 | }) 22 | return await response.json() 23 | } 24 | export default fetcher 25 | -------------------------------------------------------------------------------- /lib/helpers/buildAddressParams.ts: -------------------------------------------------------------------------------- 1 | import type { AddressParams } from '../types' 2 | 3 | export const buildAddressParams = (params: AddressParams) => { 4 | const { accountId, address, isDefaultAddress, addressType } = params 5 | const contactId = address?.contact?.id as number 6 | 7 | const addressParams = { 8 | accountId: accountId, 9 | customerContactInput: { 10 | ...address?.contact, 11 | types: [ 12 | { 13 | name: addressType, 14 | isPrimary: isDefaultAddress, 15 | }, 16 | ], 17 | accountId: accountId, 18 | }, 19 | } 20 | 21 | const updateAddressParams = { 22 | ...addressParams, 23 | contactId, 24 | } 25 | 26 | return contactId ? updateAddressParams : addressParams 27 | } 28 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # Project identification 2 | sonar.host.url=https://sonarcloud.io 3 | sonar.organization=kibosoftware 4 | sonar.projectKey=KiboSoftware_nextjs-storefront 5 | sonar.sources=. 6 | 7 | # TS Metrics 8 | #sonar.typescript.tsconfigPath=./tsconfig.json 9 | sonar.javascript.lcov.reportPaths=coverage/lcov.info 10 | sonar.coverage.exclusions=**/*.spec.*,**/*.stories.*,__mocks__/**/*,*.config.*,__test__/**/*,lib/**/*,jestSetup.js,public/**,middleware.ts,pages/_app.tsx,components/common/Layout/Layout.tsx,components/layout/DefaultLayout/DefaultLayout.tsx,components/layout/FullWidthLayout/FullWidthLayout.tsx 11 | sonar.testExecutionReportPaths=test-report.xml 12 | sonar.exclusions=public/** 13 | 14 | #----- Default source code encoding 15 | sonar.sourceEncoding=UTF-8 -------------------------------------------------------------------------------- /components/dialogs/ConfirmationDialog/ConfirmationDialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import ConfirmationDialog from './ConfirmationDialog' 6 | 7 | export default { 8 | title: 'Dialogs/MyAccount/ConfirmationDialog', 9 | component: ConfirmationDialog, 10 | argTypes: { onConfirm: { action: 'onConfirm' } }, 11 | } as ComponentMeta<typeof ConfirmationDialog> 12 | 13 | const Template: ComponentStory<typeof ConfirmationDialog> = ({ ...args }) => ( 14 | <ConfirmationDialog {...args} /> 15 | ) 16 | 17 | // Common 18 | export const Common = Template.bind({}) 19 | 20 | Common.args = { 21 | contentText: 'Are you sure you want to delete ?', 22 | primaryButtonText: 'Delete', 23 | } 24 | -------------------------------------------------------------------------------- /components/product/ProductOptionCheckbox/ProductOptionCheckbox.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import ProductOptionCheckbox, { ProductOptionCheckboxProps } from './ProductOptionCheckbox' 6 | 7 | export default { 8 | title: 'Product/Product Option Checkbox', 9 | component: ProductOptionCheckbox, 10 | argTypes: { onCheckboxChange: { action: 'onCheckboxChange' } }, 11 | } as ComponentMeta<typeof ProductOptionCheckbox> 12 | 13 | const Template: ComponentStory<typeof ProductOptionCheckbox> = ( 14 | args: ProductOptionCheckboxProps 15 | ) => <ProductOptionCheckbox {...args} /> 16 | 17 | export const Common = Template.bind({}) 18 | Common.args = { 19 | label: 'Include Warranty', 20 | } 21 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/set-personal-info.ts: -------------------------------------------------------------------------------- 1 | import { 2 | baseCheckoutFragment, 3 | checkoutLineItemFragment, 4 | fullfillmentInfoFragment, 5 | } from '../../fragments' 6 | 7 | const setPersonalInfo = /* GraphQL */ ` 8 | mutation setPersonalInfo($orderId: String!, $updateMode: String, $orderInput: CrOrderInput) { 9 | checkout: updateOrder(orderId: $orderId, updateMode: $updateMode, orderInput: $orderInput) { 10 | ...baseCheckoutFragment 11 | items { 12 | ...checkoutLineItemFragment 13 | } 14 | fulfillmentInfo { 15 | ...fullfillmentInfoFragment 16 | } 17 | } 18 | } 19 | 20 | ${baseCheckoutFragment} 21 | ${checkoutLineItemFragment} 22 | ${fullfillmentInfoFragment} 23 | ` 24 | export default setPersonalInfo 25 | -------------------------------------------------------------------------------- /styles/global.css: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | #nprogress { 3 | pointer-events: none; 4 | z-index: 2000; 5 | } 6 | 7 | #nprogress .bar { 8 | background: #2ea195; 9 | 10 | position: fixed; 11 | z-index: 2000; 12 | top: 0; 13 | left: 0; 14 | 15 | width: 100%; 16 | height: 2px; 17 | } 18 | 19 | /* Fancy blur effect */ 20 | #nprogress .peg { 21 | display: block; 22 | position: absolute; 23 | right: 0px; 24 | width: 100px; 25 | height: 100%; 26 | box-shadow: 0 0 10px #2ea195, 0 0 5px #2ea195; 27 | opacity: 1; 28 | 29 | -webkit-transform: rotate(3deg) translate(0px, -4px); 30 | -ms-transform: rotate(3deg) translate(0px, -4px); 31 | transform: rotate(3deg) translate(0px, -4px); 32 | } 33 | 34 | a { 35 | text-decoration: none; 36 | } 37 | -------------------------------------------------------------------------------- /components/product/ProductOption/ProductOption.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import ProductOption from './ProductOption' 6 | 7 | import type { CrProductOption } from '@/lib/gql/types' 8 | 9 | export default { 10 | title: 'Product/ProductOption', 11 | component: ProductOption, 12 | } as ComponentMeta<typeof ProductOption> 13 | 14 | const productOption: CrProductOption = { 15 | attributeFQN: 'Tenant~color', 16 | name: 'Color', 17 | value: 'Blue', 18 | } 19 | 20 | // Default Line Item 21 | const Template: ComponentStory<typeof ProductOption> = (args) => <ProductOption {...args} /> 22 | 23 | export const Common = Template.bind({}) 24 | Common.args = { 25 | option: productOption, 26 | } 27 | -------------------------------------------------------------------------------- /components/dialogs/AddToCartConfirmation/Title/Title.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { composeStories } from '@storybook/testing-react' 4 | import { render, screen } from '@testing-library/react' 5 | 6 | import * as stories from './Title.stories' // import all stories from the stories file 7 | 8 | const { Common } = composeStories(stories) 9 | 10 | describe('[components] Add To Cart Dialog Title', () => { 11 | const setup = () => render(<Common />) 12 | 13 | it('should render component', () => { 14 | setup() 15 | 16 | const component = screen.getByTestId('title-component') 17 | const title = screen.getByRole('heading', { name: /added-to-cart/i }) 18 | 19 | expect(component).toBeInTheDocument() 20 | expect(title).toBeVisible() 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /components/product-listing/FacetItem/FacetItem.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import FacetItem from './FacetItem' 6 | 7 | // Common 8 | export default { 9 | title: 'product-listing/FacetItem', 10 | component: FacetItem, 11 | } as ComponentMeta<typeof FacetItem> 12 | 13 | const Template: ComponentStory<typeof FacetItem> = (args) => <FacetItem {...args} /> 14 | 15 | // Common 16 | export const Common = Template.bind({}) 17 | Common.args = { 18 | filterValue: 'Tenant~color:grey', 19 | label: 'Nike', 20 | count: 132, 21 | isApplied: false, 22 | } 23 | 24 | // Selected 25 | export const Selected = Template.bind({}) 26 | Selected.args = { 27 | ...Common.args, 28 | isApplied: true, 29 | } 30 | -------------------------------------------------------------------------------- /components/common/ReturnItemList/ReturnItemList.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import ReturnItemList from './ReturnItemList' 6 | import { orderMock } from '@/__mocks__/stories/orderMock' 7 | 8 | import type { CrOrderItem } from '@/lib/gql/types' 9 | 10 | export default { 11 | title: 'Common/ReturnItemList', 12 | component: ReturnItemList, 13 | argTypes: {}, 14 | } as ComponentMeta<typeof ReturnItemList> 15 | 16 | const orderItems = orderMock?.checkout?.items 17 | 18 | // Default Line Item 19 | const Template: ComponentStory<typeof ReturnItemList> = (args) => <ReturnItemList {...args} /> 20 | 21 | export const Common = Template.bind({}) 22 | Common.args = { 23 | items: orderItems as CrOrderItem[], 24 | } 25 | -------------------------------------------------------------------------------- /components/dialogs/AddToCartConfirmation/AddToCartDialog/AddToCartDialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import AddToCartDialog from './AddToCartDialog' 6 | import { cartItemMock } from '@/__mocks__/stories/cartItemMock' 7 | 8 | export default { 9 | title: 'Dialogs/AddToCartDialog/Dialog', 10 | component: AddToCartDialog, 11 | argTypes: { closeModal: { action: 'closeModal' } }, 12 | } as ComponentMeta<typeof AddToCartDialog> 13 | 14 | const Template: ComponentStory<typeof AddToCartDialog> = ({ ...args }) => ( 15 | <AddToCartDialog {...args} /> 16 | ) 17 | 18 | // Common 19 | export const Common = Template.bind({}) 20 | 21 | Common.args = { 22 | cartItem: cartItemMock, 23 | isDialogCentered: false, 24 | } 25 | -------------------------------------------------------------------------------- /components/product/ProductQuickViewModal/ProductQuickViewDialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import ProductQuickViewDialog from './ProductQuickViewDialog' 6 | import { ProductCustomMock } from '@/__mocks__/stories' 7 | 8 | export default { 9 | component: ProductQuickViewDialog, 10 | argTypes: { onClose: { action: 'onClose' } }, 11 | title: 'Product/ProductQuickViewDialog', 12 | } as ComponentMeta<typeof ProductQuickViewDialog> 13 | 14 | const Template: ComponentStory<typeof ProductQuickViewDialog> = (args) => ( 15 | <ProductQuickViewDialog {...args} /> 16 | ) 17 | 18 | export const Common = Template.bind({}) 19 | 20 | Common.args = { 21 | product: ProductCustomMock, 22 | isQuickViewModal: true, 23 | } 24 | -------------------------------------------------------------------------------- /hooks/queries/useProductsQueries/useProductsQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useProductsQueries } from './useProductsQueries' 4 | import { productSearchResultMock } from '@/__mocks__/stories/productSearchResultMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useProductsQueries', () => { 8 | it('should return product codes filter', async () => { 9 | const productCodes = ['SHOE12'] 10 | const { result, waitFor } = renderHook(() => useProductsQueries(productCodes), { 11 | wrapper: createQueryClientWrapper(), 12 | }) 13 | 14 | await waitFor(() => result.current.isSuccess) 15 | expect(result.current.data).toEqual(productSearchResultMock) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /lib/helpers/buildRemoveWishlistItemParams.ts: -------------------------------------------------------------------------------- 1 | import { CrWishlistItem, Maybe } from '../gql/types' 2 | import type { RemoveWishlistItemInput } from '@/lib/types' 3 | 4 | export const buildRemoveWishlistItemParams = ( 5 | params: RemoveWishlistItemInput 6 | ): { wishlistId: string; wishlistItemId: string } => { 7 | const { product, currentWishlist } = params 8 | const removedItem = currentWishlist?.items?.find((item: Maybe<CrWishlistItem>) => { 9 | if (!item?.product?.variationProductCode) { 10 | return item?.product?.productCode === product.productCode 11 | } 12 | return item?.product?.variationProductCode === product.variationProductCode 13 | }) 14 | 15 | return { 16 | wishlistId: currentWishlist?.id as string, 17 | wishlistItemId: removedItem?.id as string, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/gql/queries/checkout/get-checkout-query.ts: -------------------------------------------------------------------------------- 1 | import { 2 | baseCheckoutFragment, 3 | checkoutLineItemFragment, 4 | fullfillmentInfoFragment, 5 | checkoutPaymentFragment, 6 | } from '../../fragments/checkout' 7 | 8 | const getCheckoutQuery = /* GraphQL */ ` 9 | query getCheckout($checkoutId: String!) { 10 | checkout: order(orderId: $checkoutId) { 11 | ...baseCheckoutFragment 12 | items { 13 | ...checkoutLineItemFragment 14 | } 15 | fulfillmentInfo { 16 | ...fullfillmentInfoFragment 17 | } 18 | payments { 19 | ...checkoutPaymentFragment 20 | } 21 | } 22 | } 23 | ${baseCheckoutFragment} 24 | ${checkoutLineItemFragment} 25 | ${fullfillmentInfoFragment} 26 | ${checkoutPaymentFragment} 27 | ` 28 | export default getCheckoutQuery 29 | -------------------------------------------------------------------------------- /lib/helpers/buildAddOrRemoveWishlistItemParams.ts: -------------------------------------------------------------------------------- 1 | import { productGetters } from '../getters' 2 | 3 | import type { ProductCustom, WishlistProductInput } from '../types' 4 | 5 | export const buildAddOrRemoveWishlistItemParams = ( 6 | product: ProductCustom 7 | ): WishlistProductInput => { 8 | const { productCode, variationProductCode, isPackagedStandAlone } = 9 | productGetters.getProductDetails(product) 10 | 11 | return { 12 | productCode, 13 | variationProductCode, 14 | isPackagedStandAlone, 15 | options: 16 | product?.options?.map((productOption) => ({ 17 | attributeFQN: productOption?.attributeFQN, 18 | name: productOption?.attributeDetail?.name, 19 | value: productOption?.values?.find((v) => v?.isSelected)?.value, 20 | })) || [], 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cms/components/CmsHomePageProducts/CmsHomePageProducts.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import CmsHomePageProducts from './CmsHomePageProducts' 6 | 7 | // Home 8 | export default { 9 | title: 'Home/CmsHomePageProducts', 10 | component: CmsHomePageProducts, 11 | } as ComponentMeta<typeof CmsHomePageProducts> 12 | 13 | const Template: ComponentStory<typeof CmsHomePageProducts> = (args) => ( 14 | <CmsHomePageProducts {...args} /> 15 | ) 16 | 17 | export const Common = Template.bind({}) 18 | Common.args = { 19 | recentlyViewed: { 20 | title: 'Recently Viewed and Related', 21 | productCodes: ['SHOE12'], 22 | }, 23 | topSellings: { 24 | title: 'Top-selling products', 25 | productCodes: ['ACC1'], 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /components/order/OrderReturnItems/OrderReturnItems.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import OrderReturnItems from './OrderReturnItems' 6 | import { orderMock } from '@/__mocks__/stories/orderMock' 7 | 8 | export default { 9 | title: 'Order/OrderReturnItems', 10 | component: OrderReturnItems, 11 | argTypes: { onChange: { action: 'onChange' } }, 12 | } as ComponentMeta<typeof OrderReturnItems> 13 | 14 | const Template: ComponentStory<typeof OrderReturnItems> = (args) => <OrderReturnItems {...args} /> 15 | 16 | export const Common = Template.bind({}) 17 | Common.args = { 18 | order: orderMock.checkout, 19 | title: 'choose-items-to-return', 20 | onGoBackToOrderDetails: () => console.log('go back to to order details'), 21 | } 22 | -------------------------------------------------------------------------------- /components/page-templates/OrderHistoryTemplate/OrderHistoryTemplate.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import OrderHistoryTemplate from './OrderHistoryTemplate' 6 | 7 | // Common 8 | export default { 9 | title: 'OrderHistoryTemplate/OrderHistoryTemplate', 10 | component: OrderHistoryTemplate, 11 | argTypes: { 12 | onAccountTitleClick: { action: 'onAccountTitleClick' }, 13 | }, 14 | } as ComponentMeta<typeof OrderHistoryTemplate> 15 | 16 | const Template: ComponentStory<typeof OrderHistoryTemplate> = (args) => ( 17 | <OrderHistoryTemplate {...args} /> 18 | ) 19 | 20 | // Default 21 | const filters = ['m-1', 'm-6'] 22 | 23 | export const Common = Template.bind({}) 24 | Common.args = { 25 | queryFilters: filters, 26 | } 27 | -------------------------------------------------------------------------------- /hooks/queries/useCategoryTreeQueries/useCategoryTreeQueries.spec.js: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useCategoryTreeQueries } from './useCategoryTreeQueries' 4 | import { categoryTreeDataMock } from '@/__mocks__/stories/categoryTreeDataMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useCategoryTreeQueries', () => { 8 | it('should return category tree if initial data not passed', async () => { 9 | const { result, waitFor } = renderHook(() => useCategoryTreeQueries({}), { 10 | wrapper: createQueryClientWrapper(), 11 | }) 12 | 13 | await waitFor(() => result.current.isSuccess) 14 | 15 | expect(result.current.data).toStrictEqual(categoryTreeDataMock.categoriesTree.items) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /hooks/queries/useCustomerCardsQueries/useCustomerCardsQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useCustomerCardsQueries } from './useCustomerCardsQueries' 4 | import { customerAccountCardsMock } from '@/__mocks__/stories/customerAccountCardsMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useCustomerCardsQueries', () => { 8 | it('should return customer saved cards', async () => { 9 | const { result, waitFor } = renderHook(() => useCustomerCardsQueries(1012), { 10 | wrapper: createQueryClientWrapper(), 11 | }) 12 | 13 | await waitFor(() => result.current.isSuccess) 14 | 15 | expect(result.current.data).toStrictEqual(customerAccountCardsMock.customerAccountCards) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /hooks/queries/useCustomerContactsQueries/useCustomerContactsQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useCustomerContactsQueries } from './useCustomerContactsQueries' 4 | import { userAddressMock } from '@/__mocks__/stories/userAddressMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useCustomerContactsQueries', () => { 8 | it('should return customer saved contacts', async () => { 9 | const { result, waitFor } = renderHook(() => useCustomerContactsQueries(1012), { 10 | wrapper: createQueryClientWrapper(), 11 | }) 12 | 13 | await waitFor(() => result.current.isSuccess) 14 | 15 | expect(result.current.data).toStrictEqual(userAddressMock.customerAccountContacts) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /components/product/ProductRecommendations/ProductRecommendations.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import ProductRecommendations from './ProductRecommendations' 6 | 7 | export default { 8 | title: 'Product/Product Recommendations', 9 | component: ProductRecommendations, 10 | argTypes: { onChange: { action: 'onChange' } }, 11 | } as ComponentMeta<typeof ProductRecommendations> 12 | 13 | const Template: ComponentStory<typeof ProductRecommendations> = (args) => ( 14 | <ProductRecommendations {...args} /> 15 | ) 16 | 17 | export const Common = Template.bind({}) 18 | Common.args = { 19 | title: 'Product Recommendations', 20 | productCodes: ['HKFT_023', 'Hammock_022', 'SleepBag_006', 'HKFT_026', 'BackP_007', 'Hammock_021'], 21 | } 22 | -------------------------------------------------------------------------------- /hooks/queries/subscription/useSubscriptionsQueries/useSubscriptionsQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useSubscriptionsQueries } from './useSubscriptionsQueries' 4 | import { subscriptionCollectionMock } from '@/__mocks__/stories/subscriptionCollectionMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useSubscriptionsQueries', () => { 8 | it('should return subscription collection', async () => { 9 | const { result, waitFor } = renderHook(() => useSubscriptionsQueries(), { 10 | wrapper: createQueryClientWrapper(), 11 | }) 12 | 13 | await waitFor(() => result.current.isSuccess) 14 | expect(result.current.data).toStrictEqual(subscriptionCollectionMock.subscriptions) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /hooks/queries/useCheckoutQueries/useCheckoutQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useCheckoutQueries } from './useCheckoutQueries' 4 | import { orderMock } from '@/__mocks__/stories/orderMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useCheckoutQueries', () => { 8 | it('should return checkout details when user provides valid checkoutId', async () => { 9 | const checkoutId = '137a979305c65d00010800230000678b' 10 | const { result, waitFor } = renderHook(() => useCheckoutQueries({ checkoutId }), { 11 | wrapper: createQueryClientWrapper(), 12 | }) 13 | await waitFor(() => result.current.isSuccess) 14 | expect(result.current.data).toStrictEqual(orderMock.checkout) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /lib/helpers/buildCreateOrderReturnItemsParams.ts: -------------------------------------------------------------------------------- 1 | import type { CreateOrderReturnItemsParams } from '@/lib/types' 2 | 3 | export const buildCreateOrderReturnItemsParams = (params: CreateOrderReturnItemsParams) => { 4 | const { items, returnType, reason } = params 5 | 6 | return items.map((item) => { 7 | return { 8 | product: item?.product, 9 | quantityReceived: item?.quantity, 10 | quantityShipped: item?.quantity, 11 | quantityRestockable: item?.quantity, 12 | quantityRestocked: item?.quantity, 13 | quantityRefunded: 0, 14 | orderLineId: item?.lineId, 15 | returnType, 16 | orderItemOptionAttributeFQN: '', 17 | excludeProductExtras: false, 18 | reasons: { 19 | reason, 20 | quantity: item?.quantity, 21 | }, 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /lib/types/Wishlist.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe, ProductOptionSelectionInput, CrWishlist } from '@/lib/gql/types' 2 | export interface WishlistProductInput { 3 | options: ProductOptionSelectionInput[] 4 | productCode: string 5 | isPackagedStandAlone: boolean 6 | variationProductCode?: string 7 | } 8 | 9 | export interface RemoveWishlistItemInput { 10 | product: WishlistProductInput 11 | currentWishlist?: Maybe<CrWishlist> 12 | } 13 | export interface WishlistParams extends WishlistProductInput { 14 | currentWishlist?: Maybe<CrWishlist> 15 | } 16 | export interface WishlistItemInWishlistParams { 17 | productCode: string 18 | variationProductCode?: string 19 | userWishlist?: Maybe<CrWishlist> 20 | } 21 | 22 | export interface WishlistHookParams { 23 | isRemovedFromWishlist?: boolean 24 | delay?: number 25 | } 26 | -------------------------------------------------------------------------------- /components/core/Breadcrumbs/KiboBreadcrumbs.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import KiboBreadcrumbs from './KiboBreadcrumbs' 6 | 7 | const breadcrumbList = [ 8 | { 9 | text: 'Home', 10 | link: '/', 11 | }, 12 | { 13 | text: 'Sports', 14 | link: '/sports', 15 | }, 16 | { 17 | text: 'Shoes', 18 | link: '/sports/shoes', 19 | }, 20 | ] 21 | 22 | export default { 23 | title: 'Core/Breadcrumbs', 24 | component: KiboBreadcrumbs, 25 | } as ComponentMeta<typeof KiboBreadcrumbs> 26 | 27 | const Template: ComponentStory<typeof KiboBreadcrumbs> = (args) => <KiboBreadcrumbs {...args} /> 28 | 29 | export const common = Template.bind({}) 30 | common.args = { 31 | breadcrumbs: breadcrumbList, 32 | separator: '|', 33 | } 34 | -------------------------------------------------------------------------------- /hooks/custom/useCurrentLocation/useCurrentLocation.spec.ts: -------------------------------------------------------------------------------- 1 | import { useCurrentLocation } from './useCurrentLocation' 2 | 3 | const mockGeolocation = { 4 | getCurrentPosition: jest.fn().mockImplementation((success) => 5 | Promise.resolve( 6 | success({ 7 | coords: { 8 | latitude: 10, 9 | longitude: 10, 10 | }, 11 | }) 12 | ) 13 | ), 14 | } 15 | 16 | Object.defineProperty(global.navigator, 'geolocation', { 17 | value: mockGeolocation, 18 | }) 19 | 20 | describe('[hooks] useCurrentLocation', () => { 21 | const { getCurrentLocation } = useCurrentLocation() 22 | 23 | it('should return geo location coordinates', async () => { 24 | const { longitude, latitude } = await getCurrentLocation() 25 | expect(longitude).toBe(10) 26 | expect(latitude).toBe(10) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /__mocks__/stories/productOptionTextBoxMock.ts: -------------------------------------------------------------------------------- 1 | import type { ProductOption } from '@/lib/gql/types' 2 | 3 | export const productOptionTextBoxMock: ProductOption[] = [ 4 | { 5 | attributeFQN: 'Tenant~optional-mount', 6 | attributeDetail: { 7 | dataTypeSequence: 13, 8 | name: 'Optional Mount', 9 | }, 10 | values: [ 11 | { 12 | value: 'MS-CAM-004', 13 | attributeValueId: 125, 14 | shopperEnteredValue: null, 15 | isEnabled: true, 16 | }, 17 | ], 18 | }, 19 | { 20 | attributeFQN: 'Tenant~size', 21 | attributeDetail: { 22 | dataTypeSequence: 13, 23 | name: 'Size', 24 | }, 25 | values: [ 26 | { 27 | value: 'L', 28 | attributeValueId: 125, 29 | shopperEnteredValue: 'Large', 30 | }, 31 | ], 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /components/layout/RegisterAccount/RegisterAccountDialog/RegisterAccountDialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import RegisterAccountDialog from './RegisterAccountDialog' 6 | 7 | export default { 8 | title: 'Layout/Register Account/Dialog', 9 | component: RegisterAccountDialog, 10 | argTypes: { 11 | onLoginToYourAccountDialogToggle: { action: 'Login Dialog Open' }, 12 | onRegisterNow: { action: 'register form data' }, 13 | }, 14 | } as ComponentMeta<typeof RegisterAccountDialog> 15 | 16 | const Template: ComponentStory<typeof RegisterAccountDialog> = () => <RegisterAccountDialog /> 17 | 18 | export const Common = Template.bind({}) 19 | 20 | Common.args = { 21 | isOpen: true, 22 | isDialogCentered: false, 23 | setAutoFocus: true, 24 | } 25 | -------------------------------------------------------------------------------- /hooks/queries/useSearchSuggestionsQueries/useSearchSuggestionsQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useSearchSuggestionsQueries } from './useSearchSuggestionsQueries' 4 | import { searchSuggestionResultMock } from '@/__mocks__/stories/searchSuggestionResultMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useSearchSuggestionsQueries', () => { 8 | it('should return search suggestions when entered search term', async () => { 9 | const { result, waitFor } = renderHook(() => useSearchSuggestionsQueries('dog gear'), { 10 | wrapper: createQueryClientWrapper(), 11 | }) 12 | 13 | await waitFor(() => result.current.isSuccess) 14 | expect(result.current.data).toEqual(searchSuggestionResultMock) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /components/common/KiboImage/KiboImage.tsx: -------------------------------------------------------------------------------- 1 | import { SyntheticEvent } from 'react' 2 | 3 | import { SvgIconComponent } from '@mui/icons-material' 4 | import Image, { ImageProps } from 'next/image' 5 | 6 | import DefaultImage from '@/public/product_placeholder.svg' 7 | 8 | interface KiboImageProps extends ImageProps { 9 | errorimage?: ImageData | SvgIconComponent 10 | } 11 | 12 | const errorImage = { image: DefaultImage } 13 | 14 | const onImageError = ( 15 | event: SyntheticEvent<HTMLImageElement, Event> & { 16 | target: HTMLImageElement 17 | } 18 | ) => { 19 | const { target } = event 20 | target.src = errorImage.image 21 | } 22 | 23 | const KiboImage = (props: KiboImageProps) => { 24 | errorImage.image = props.errorimage 25 | return <Image {...props} alt={props.alt} onError={onImageError} /> 26 | } 27 | 28 | export default KiboImage 29 | -------------------------------------------------------------------------------- /hooks/mutations/useCartMutations/useRemoveCartItem/useRemoveCartItemMutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useRemoveCartItemMutation } from './useRemoveCartItemMutation' 4 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 5 | 6 | describe('[hooks] useRemoveCartItemMutation', () => { 7 | it('should use useRemoveCartItemMutation when removeCartItem', async () => { 8 | renderHook( 9 | async () => { 10 | const { removeCartItem } = useRemoveCartItemMutation() 11 | const response = await removeCartItem.mutateAsync({ 12 | cartItemId: 'fjsdhfjsdh53472bkjsdffdf', 13 | }) 14 | expect(response).toEqual(true) 15 | }, 16 | { 17 | wrapper: createQueryClientWrapper(), 18 | } 19 | ) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /__mocks__/stories/updateOrderBillingInfoMock.ts: -------------------------------------------------------------------------------- 1 | import { CrBillingInfo } from '@/lib/gql/types' 2 | 3 | export const updateOrderBillingInfoMock: { updateOrderBillingInfo: CrBillingInfo } = { 4 | updateOrderBillingInfo: { 5 | billingContact: { 6 | id: null, 7 | firstName: 'John', 8 | middleNameOrInitial: null, 9 | lastNameOrSurname: 'Doe', 10 | email: 'chandradeepta.laha@kibocommerce.com', 11 | address: { 12 | address1: 'Lamar Street', 13 | address2: '23/1', 14 | address3: null, 15 | addressType: null, 16 | stateOrProvince: 'TX', 17 | postalOrZipCode: '87878', 18 | cityOrTown: 'Austin', 19 | countryCode: 'US', 20 | isValidated: false, 21 | }, 22 | phoneNumbers: { 23 | home: '9898495849', 24 | }, 25 | }, 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /hooks/queries/useUserOrderQueries/useUserOrderQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useUserOrderQueries } from './useUserOrderQueries' 4 | import { orderCollection } from '@/__mocks__/stories/orderCollection' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useUserOrderQueries', () => { 8 | it('should return order collection when user provides valid order filters', async () => { 9 | const { result, waitFor } = renderHook( 10 | () => useUserOrderQueries({ filters: ['M-6'], isRefetching: true }), 11 | { 12 | wrapper: createQueryClientWrapper(), 13 | } 14 | ) 15 | 16 | await waitFor(() => result.current.isSuccess) 17 | expect(result.current.data).toStrictEqual(orderCollection.orders) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /__test__/e2e/integration/cart/CartItemList/CartItemList.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import '@testing-library/jest-dom' 4 | import { composeStories } from '@storybook/testing-react' 5 | import { render, screen } from '@testing-library/react' 6 | 7 | import * as stories from '../../../../../components/cart/CartItemList/CartItemList.stories' 8 | 9 | const { Common } = composeStories(stories) 10 | 11 | describe('[components] - CartItemList Integration', () => { 12 | const setup = () => { 13 | render(<Common {...Common.args} />) 14 | } 15 | 16 | it('should render component', () => { 17 | // arrange 18 | setup() 19 | 20 | // act 21 | const cartItem = screen.getAllByRole('group') 22 | 23 | // assert 24 | const count = Common.args?.cartItems?.length || 0 25 | expect(cartItem).toHaveLength(count) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /lib/gql/queries/get-returns.ts: -------------------------------------------------------------------------------- 1 | const getReturnsQuery = /* GraphQL */ ` 2 | query getReturns($filter: String, $startIndex: Int, $pageSize: Int) { 3 | returns(filter: $filter, startIndex: $startIndex, pageSize: $pageSize) { 4 | startIndex 5 | pageSize 6 | pageCount 7 | totalCount 8 | items { 9 | id 10 | customerAccountId 11 | returnNumber 12 | locationCode 13 | originalOrderId 14 | originalOrderNumber 15 | returnOrderId 16 | currencyCode 17 | status 18 | receiveStatus 19 | refundStatus 20 | replaceStatus 21 | returnType 22 | refundAmount 23 | items { 24 | id 25 | orderItemId 26 | orderLineId 27 | } 28 | } 29 | } 30 | } 31 | ` 32 | 33 | export default getReturnsQuery 34 | -------------------------------------------------------------------------------- /lib/helpers/__tests__/buildRemoveWishlistItemParams.spec.ts: -------------------------------------------------------------------------------- 1 | import { buildRemoveWishlistItemParams } from '../buildRemoveWishlistItemParams' 2 | import { wishlistMock } from '@/__mocks__/stories/wishlistMock' 3 | 4 | describe('[helpers] buildRemoveWishlistItemParams function', () => { 5 | it('should return the buildRemoveWishlistItemParams variables', () => { 6 | const product = { 7 | productCode: 'MS-BTL-005', 8 | isPackagedStandAlone: true, 9 | variationProductCode: 'MS-BTL-005', 10 | options: [], 11 | } 12 | const currentWishlist = wishlistMock?.items[0] 13 | 14 | const { id: wishlistItemId } = wishlistMock?.items[0]?.items[1] 15 | expect(buildRemoveWishlistItemParams({ product, currentWishlist })).toStrictEqual({ 16 | wishlistId: currentWishlist?.id, 17 | wishlistItemId, 18 | }) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /components/common/FullWidthDivider/FullWidthDivider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { Divider, Theme } from '@mui/material' 4 | 5 | interface FullWidthDividerProps { 6 | color?: string 7 | [x: string]: any 8 | } 9 | 10 | const FullWidthDivider = (props: FullWidthDividerProps) => { 11 | const { color = 'grey.500', ...rest } = props 12 | return ( 13 | <> 14 | <Divider 15 | {...rest} 16 | sx={(theme: Theme) => ({ 17 | borderColor: color, 18 | marginLeft: { md: '-1.5rem', xs: '-1rem' }, 19 | marginRight: { md: '-1.5rem', xs: '-1rem' }, 20 | [theme.breakpoints.between('sm', 'md')]: { 21 | marginLeft: '-1.5rem', 22 | marginRight: '-1.5rem', 23 | }, 24 | })} 25 | /> 26 | </> 27 | ) 28 | } 29 | 30 | export default FullWidthDivider 31 | -------------------------------------------------------------------------------- /lib/helpers/findParentNode.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe, PrCategory } from '../gql/types' 2 | 3 | export const findParentNode = ( 4 | items: Maybe<PrCategory>[], 5 | categoryCode?: string | null, 6 | parent: Maybe<PrCategory | null> = null 7 | ): Maybe<PrCategory | undefined | null> => { 8 | /* looping through all the categories to find the provided categoryCode. 9 | If a match is found and it's the root label, return null else return the immediate parent. 10 | findParent will be called recursively */ 11 | for (const item of items) { 12 | const res: Maybe<PrCategory | undefined | null> = 13 | item?.categoryCode === categoryCode 14 | ? parent 15 | : item?.childrenCategories && 16 | findParentNode(item?.childrenCategories as PrCategory[], categoryCode, item) 17 | if (res || res === null) return res 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components/dialogs/AddToCartConfirmation/Content/Content.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { composeStories } from '@storybook/testing-react' 4 | import { render, screen } from '@testing-library/react' 5 | 6 | import * as stories from './Content.stories' // import all stories from the stories file 7 | 8 | const { Common } = composeStories(stories) 9 | 10 | const orderPriceMock = () => <div data-testid="order-price-component" /> 11 | jest.mock('@/components/common/OrderPrice/OrderPrice', () => () => orderPriceMock()) 12 | 13 | describe('[components] Add To Cart Dialog Content', () => { 14 | const setup = () => render(<Common {...Common.args} />) 15 | 16 | it('should render component', () => { 17 | setup() 18 | 19 | const component = screen.getByTestId('content-component') 20 | 21 | expect(component).toBeInTheDocument() 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /components/dialogs/OrderReturnItemsDialog/OrderReturnItemsDialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import OrderReturnItemsDialog from './OrderReturnItemsDialog' 6 | import { orderReturnItemsMock } from '@/__mocks__/stories/orderMock' 7 | 8 | export default { 9 | title: 'Dialogs/OrderReturnItemsDialog', 10 | component: OrderReturnItemsDialog, 11 | argTypes: { closeModal: { action: 'closeModal' } }, 12 | } as ComponentMeta<typeof OrderReturnItemsDialog> 13 | 14 | const Template: ComponentStory<typeof OrderReturnItemsDialog> = ({ ...args }) => ( 15 | <OrderReturnItemsDialog {...args} /> 16 | ) 17 | 18 | // Common 19 | export const Common = Template.bind({}) 20 | 21 | Common.args = { 22 | orderItems: orderReturnItemsMock?.createReturn?.items || [], 23 | reason: 'Damaged', 24 | } 25 | -------------------------------------------------------------------------------- /hooks/mutations/useCreateOrderMutations/useCreateOrderMutation.spec.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useCreateOrderMutation } from './useCreateOrderMutation' 4 | import { orderMock } from '@/__mocks__/stories/orderMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useCreateOrderMutation', () => { 8 | it('should use useCreateOrderMutation', async () => { 9 | const expectedOrder = orderMock?.checkout 10 | 11 | renderHook( 12 | async () => { 13 | const createOrder = useCreateOrderMutation() 14 | const response = await createOrder.mutateAsync(expectedOrder) 15 | 16 | expect(response).toEqual(expectedOrder) 17 | }, 18 | { 19 | wrapper: createQueryClientWrapper(), 20 | } 21 | ) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@/*": ["./*"], 20 | "@/lib/*": ["lib/*"], 21 | "@/components/*": ["components/*"], 22 | "@/types/*": ["types/*"], 23 | "@/hooks/*": ["hooks/*"], 24 | "@/pages/*": ["src/pages/*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /components/dialogs/Store/MyStoreDialog/MyStoreDialog.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import MyStoreDialog from './MyStoreDialog' 6 | import { locationCollectionMock } from '@/__mocks__/stories/locationCollectionMock' 7 | 8 | export default { 9 | title: 'Dialogs/Store/MyStoreDialog', 10 | component: MyStoreDialog, 11 | argTypes: { onClose: { action: 'onClose' } }, 12 | } as ComponentMeta<typeof MyStoreDialog> 13 | 14 | const Template: ComponentStory<typeof MyStoreDialog> = ({ ...args }) => <MyStoreDialog {...args} /> 15 | 16 | // Common 17 | export const Common = Template.bind({}) 18 | 19 | Common.args = { 20 | isOpen: true, 21 | isDialogCentered: false, 22 | location: 23 | locationCollectionMock?.spLocations?.items && locationCollectionMock?.spLocations?.items[0], 24 | } 25 | -------------------------------------------------------------------------------- /hooks/mutations/useCouponMutations/useDeleteCartCouponMutation/useDeleteCartCouponMutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useDeleteCartCouponMutation } from './useDeleteCartCouponMutation' 4 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 5 | 6 | describe('[hooks] useDeleteCartCouponMutation', () => { 7 | it('should remove deleted coupon', async () => { 8 | renderHook( 9 | async () => { 10 | const deleteCartCoupon = useDeleteCartCouponMutation() 11 | const variables = { cartId: '43245kjg5j43543hj', couponCode: 'OFF10' } 12 | const response = await deleteCartCoupon.mutateAsync(variables) 13 | expect(response).toStrictEqual('1234') 14 | }, 15 | { 16 | wrapper: createQueryClientWrapper(), 17 | } 18 | ) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /lib/gql/queries/product-search.ts: -------------------------------------------------------------------------------- 1 | import { searchResults } from '@/lib/gql/fragments' 2 | 3 | const searchProductsQuery = /* GraphQL */ ` 4 | query ProductSearch( 5 | $query: String 6 | $startIndex: Int 7 | $filter: String 8 | $pageSize: Int 9 | $sortBy: String 10 | $facet: String 11 | $facetHierValue: String 12 | $facetTemplate: String 13 | $facetValueFilter: String 14 | ) { 15 | products: productSearch( 16 | query: $query 17 | filter: $filter 18 | startIndex: $startIndex 19 | pageSize: $pageSize 20 | facet: $facet 21 | sortBy: $sortBy 22 | facetHierValue: $facetHierValue 23 | facetTemplate: $facetTemplate 24 | facetValueFilter: $facetValueFilter 25 | ) { 26 | ...searchResults 27 | } 28 | } 29 | ${searchResults} 30 | ` 31 | 32 | export default searchProductsQuery 33 | -------------------------------------------------------------------------------- /components/cart/CartItemActions/CartItemActions.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import '@testing-library/jest-dom' 4 | import { composeStories } from '@storybook/testing-react' 5 | import { render, screen } from '@testing-library/react' 6 | 7 | import * as stories from './CartItemActions.stories' 8 | 9 | const { Common } = composeStories(stories) 10 | 11 | describe('[components] - CartItemActions', () => { 12 | const setup = () => { 13 | render(<Common {...Common.args} />) 14 | } 15 | 16 | it('should render component', () => { 17 | setup() 18 | 19 | const editLink = screen.getByText('edit') 20 | const saveLink = screen.getByText('save-for-later') 21 | const addLink = screen.getByText('add-to-favorites') 22 | 23 | expect(editLink).toBeVisible() 24 | expect(saveLink).toBeVisible() 25 | expect(addLink).toBeVisible() 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /components/common/PaymentCard/PaymentCard.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { composeStories } from '@storybook/testing-react' 4 | import { render, screen } from '@testing-library/react' 5 | 6 | import * as stories from './PaymentCard.stories' // import all stories from the stories file 7 | 8 | const { Common } = composeStories(stories) 9 | 10 | describe('[component] - PaymentCard', () => { 11 | it('should display the payment card details', () => { 12 | render(<Common {...Common.args} />) 13 | 14 | expect(screen.getByText(Common.args.title)).toBeVisible() 15 | expect(screen.getByText(Common.args.cardNumberPart)).toBeVisible() 16 | expect( 17 | screen.getByText(new RegExp(Common.args.expireMonth + '/' + Common.args.expireYear)) 18 | ).toBeVisible() 19 | expect(screen.getByAltText(Common.args.cardType)).toBeVisible() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /components/layout/AppHeader/Checkout/CheckoutHeader.spec.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import { screen } from '@testing-library/react' 3 | 4 | import '@testing-library/jest-dom' 5 | import CheckoutHeader from './CheckoutHeader' 6 | import { renderWithQueryClient } from '@/__test__/utils' 7 | 8 | describe('[component] MobileHeader component', () => { 9 | it('should render the component', () => { 10 | renderWithQueryClient(<CheckoutHeader isMultiShipEnabled />) 11 | 12 | expect(screen.getByText(/checkout/)).toBeVisible() 13 | expect(screen.queryByTestId(/top-bar/)).not.toBeInTheDocument() 14 | expect(screen.queryByTestId(/header-action-area/)).not.toBeInTheDocument() 15 | expect(screen.queryByTestId(/mega-menu-container/)).not.toBeInTheDocument() 16 | expect(screen.getByAltText('kibo-logo')).toBeVisible() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /lib/gql/fragments/search.ts: -------------------------------------------------------------------------------- 1 | import { productInfo } from './product' 2 | 3 | export const searchFacets = /* GraphQL */ ` 4 | fragment searchFacets on Facet { 5 | label 6 | facetType 7 | field 8 | values { 9 | label 10 | value 11 | filterValue 12 | isDisplayed 13 | count 14 | isApplied 15 | childrenFacetValues { 16 | label 17 | count 18 | value 19 | filterValue 20 | isDisplayed 21 | count 22 | } 23 | } 24 | } 25 | ` 26 | 27 | export const searchResults = /* GraphQL */ ` 28 | fragment searchResults on ProductSearchResult { 29 | totalCount 30 | pageSize 31 | pageCount 32 | startIndex 33 | items { 34 | ...productInfo 35 | } 36 | facets { 37 | ...searchFacets 38 | } 39 | } 40 | ${searchFacets} 41 | ${productInfo} 42 | ` 43 | -------------------------------------------------------------------------------- /hooks/mutations/useCouponMutations/useDeleteOrderCouponMutation/useDeleteOrderCouponMutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useDeleteOrderCouponMutation } from './useDeleteOrderCouponMutation' 4 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 5 | 6 | describe('[hooks] useDeleteOrderCouponMutation', () => { 7 | it('should remove deleted coupon', async () => { 8 | renderHook( 9 | async () => { 10 | const deleteOrderCoupon = useDeleteOrderCouponMutation() 11 | const variables = { checkoutId: '43245kjg5j43543hj', couponCode: 'OFF10' } 12 | const response = await deleteOrderCoupon.mutateAsync(variables) 13 | expect(response).toStrictEqual('1234') 14 | }, 15 | { 16 | wrapper: createQueryClientWrapper(), 17 | } 18 | ) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /components/common/SearchBar/SearchBar.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import SearchBar from './SearchBar' 6 | 7 | // Common 8 | export default { 9 | title: 'Common/SearchBar', 10 | component: SearchBar, 11 | } as ComponentMeta<typeof SearchBar> 12 | 13 | const Template: ComponentStory<typeof SearchBar> = (args) => <SearchBar {...args} /> 14 | 15 | // Default 16 | export const Common = Template.bind({}) 17 | Common.args = { 18 | placeHolder: 'Search Brand', 19 | searchTerm: '', 20 | onSearch: () => { 21 | /*parent will handle SearchBar*/ 22 | }, 23 | childInputRef: undefined, 24 | showClearButton: false, 25 | } 26 | 27 | // WithCancelButton 28 | export const WithCancelButton = Template.bind({}) 29 | WithCancelButton.args = { 30 | ...Common.args, 31 | showClearButton: true, 32 | } 33 | -------------------------------------------------------------------------------- /components/home/ContentTile/ContentTile.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { composeStories } from '@storybook/testing-react' 4 | import { render, screen } from '@testing-library/react' 5 | 6 | import * as stories from './ContentTile.stories' 7 | 8 | const { Common } = composeStories(stories) 9 | 10 | describe('[Component] - Contenttile ', () => { 11 | const setup = () => render(<Common {...Common?.args} />) 12 | it('should render component', () => { 13 | setup() 14 | 15 | const tile = Common?.args?.largeTileProps || [] 16 | const title = screen.getAllByText(tile[0].title) 17 | const subtitle = screen.getAllByText(tile[0].subtitle) 18 | const link1 = screen.getAllByText(tile[0].link1.title) 19 | 20 | expect(title[0]).toBeInTheDocument() 21 | expect(subtitle[0]).toBeInTheDocument() 22 | expect(link1[0]).toBeInTheDocument() 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /components/product/ProductOptionList/ProductOptionList.tsx: -------------------------------------------------------------------------------- 1 | import { Variant } from '@mui/material/styles/createTypography' 2 | 3 | import { ProductOption } from '@/components/product' 4 | 5 | import type { CrProductOption } from '@/lib/gql/types' 6 | 7 | interface ProductOptionListProps { 8 | options: CrProductOption[] 9 | variant?: Variant 10 | fontWeight?: 'bold' | 'normal' 11 | } 12 | 13 | const ProductOptionList = (props: ProductOptionListProps) => { 14 | const { options = [], variant, fontWeight } = props 15 | 16 | return ( 17 | <> 18 | {options.map((option: CrProductOption) => ( 19 | <ProductOption 20 | key={`${option?.name}-${option.value}`} 21 | option={option} 22 | variant={variant} 23 | fontWeight={fontWeight} 24 | /> 25 | ))} 26 | </> 27 | ) 28 | } 29 | 30 | export default ProductOptionList 31 | -------------------------------------------------------------------------------- /hooks/mutations/useUserMutations/useUserMutations.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useUserMutations } from './useUserMutations' 4 | import { loginUserMock } from '@/__mocks__/stories/userMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useUserMutations', () => { 8 | it('should use useUserMutations', async () => { 9 | const userCredentials = { 10 | username: 'abcd@email.com', 11 | password: '', 12 | } 13 | 14 | renderHook( 15 | async () => { 16 | const { mutateAsync } = useUserMutations() 17 | const response = await mutateAsync(userCredentials) 18 | 19 | expect(response).toStrictEqual(loginUserMock.account) 20 | }, 21 | { 22 | wrapper: createQueryClientWrapper(), 23 | } 24 | ) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /__mocks__/stories/updateCustomerAccountContact.ts: -------------------------------------------------------------------------------- 1 | export const updateCustomerAccountContactMock = { 2 | updateCustomerAccountContact: { 3 | accountId: 1012, 4 | types: [ 5 | { 6 | name: 'Billing', 7 | isPrimary: false, 8 | }, 9 | ], 10 | id: 1495, 11 | email: null, 12 | firstName: 'Chandradeepta', 13 | middleNameOrInitial: null, 14 | lastNameOrSurname: 'Laha', 15 | phoneNumbers: { 16 | home: '7875675849', 17 | mobile: null, 18 | work: null, 19 | }, 20 | address: { 21 | address1: '300, Lamar street', 22 | address2: null, 23 | address3: null, 24 | address4: null, 25 | cityOrTown: 'Austin', 26 | stateOrProvince: 'TX', 27 | postalOrZipCode: '34543', 28 | countryCode: 'US', 29 | addressType: 'Residential', 30 | isValidated: false, 31 | }, 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /components/common/FulfillmentOptions/FulfillmentOptions.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import FulfillmentOptions from './FulfillmentOptions' 6 | import { fulfillmentOptionsMock } from '@/__mocks__/stories/fulfillmentOptionsMock' 7 | 8 | export default { 9 | title: 'Common/FulfillmentOptions', 10 | component: FulfillmentOptions, 11 | argTypes: { 12 | onFulfillmentOptionChange: { action: 'clicked' }, 13 | onStoreSetOrUpdate: { action: 'clicked' }, 14 | }, 15 | } as ComponentMeta<typeof FulfillmentOptions> 16 | 17 | const Template: ComponentStory<typeof FulfillmentOptions> = ({ ...args }) => ( 18 | <FulfillmentOptions {...args} /> 19 | ) 20 | 21 | export const Common = Template.bind({}) 22 | 23 | Common.args = { 24 | fulfillmentOptions: fulfillmentOptionsMock, 25 | selected: 'Pickup', 26 | } 27 | -------------------------------------------------------------------------------- /lib/gql/mutations/coupon/updateCheckoutCouponMutation.ts: -------------------------------------------------------------------------------- 1 | import { checkoutLineItemFragment } from '@/lib/gql/fragments' 2 | 3 | const updateCheckoutCouponMutation = /* GraphQL */ ` 4 | mutation updateCheckoutCoupon($checkoutId: String!, $couponCode: String!) { 5 | updateCheckoutCoupon(checkoutId: $checkoutId, couponCode: $couponCode) { 6 | id 7 | email 8 | amountRemainingForPayment 9 | total 10 | shippingTotal 11 | couponCodes 12 | invalidCoupons { 13 | couponCode 14 | reason 15 | } 16 | orderDiscounts { 17 | impact 18 | discount { 19 | id 20 | name 21 | } 22 | couponCode 23 | } 24 | 25 | items { 26 | ...checkoutLineItemFragment 27 | } 28 | } 29 | } 30 | ${checkoutLineItemFragment} 31 | ` 32 | 33 | export default updateCheckoutCouponMutation 34 | -------------------------------------------------------------------------------- /lib/gql/queries/get-category-tree.ts: -------------------------------------------------------------------------------- 1 | import { categoryInfo } from '../fragments' 2 | 3 | const getCategoryTreeQuery = /* GraphQL */ ` 4 | ${categoryInfo} 5 | 6 | query getCategoryTreeQuery { 7 | categoriesTree { 8 | items { 9 | ...categoryInfo 10 | childrenCategories { 11 | ...categoryInfo 12 | childrenCategories { 13 | ...categoryInfo 14 | childrenCategories { 15 | ...categoryInfo 16 | childrenCategories { 17 | ...categoryInfo 18 | childrenCategories { 19 | ...categoryInfo 20 | childrenCategories { 21 | ...categoryInfo 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | ` 32 | export default getCategoryTreeQuery 33 | -------------------------------------------------------------------------------- /components/product-listing/FacetItem/FacetItem.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import '@testing-library/jest-dom' 4 | import { composeStories } from '@storybook/testing-react' 5 | import { render, screen } from '@testing-library/react' 6 | 7 | import * as stories from './FacetItem.stories' 8 | 9 | const { Common } = composeStories(stories) 10 | 11 | describe('[components] - FacetItem', () => { 12 | const setup = () => { 13 | render(<Common {...Common.args} />) 14 | } 15 | 16 | it('should render component', () => { 17 | // arrange 18 | setup() 19 | 20 | // act 21 | const checkbox = screen.getByRole('checkbox') 22 | const label = screen.getByTestId('label') 23 | const count = screen.getByTestId('count') 24 | 25 | // assert 26 | expect(checkbox).toBeInTheDocument() 27 | expect(label).toBeVisible() 28 | expect(count).toBeVisible() 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /lib/gql/mutations/coupon/updateOrderCoupon.ts: -------------------------------------------------------------------------------- 1 | import { 2 | baseCheckoutFragment, 3 | checkoutLineItemFragment, 4 | fullfillmentInfoFragment, 5 | checkoutPaymentFragment, 6 | } from '@/lib/gql/fragments' 7 | 8 | const updateOrderCouponMutation = /* GraphQL */ ` 9 | mutation updateOrderCoupon($orderId: String!, $couponCode: String!) { 10 | updateOrderCoupon(orderId: $orderId, couponCode: $couponCode) { 11 | ...baseCheckoutFragment 12 | couponCodes 13 | items { 14 | ...checkoutLineItemFragment 15 | } 16 | fulfillmentInfo { 17 | ...fullfillmentInfoFragment 18 | } 19 | payments { 20 | ...checkoutPaymentFragment 21 | } 22 | } 23 | } 24 | ${baseCheckoutFragment} 25 | ${checkoutLineItemFragment} 26 | ${fullfillmentInfoFragment} 27 | ${checkoutPaymentFragment} 28 | ` 29 | 30 | export default updateOrderCouponMutation 31 | -------------------------------------------------------------------------------- /lib/helpers/__tests__/buildProductSearchParams.spec.ts: -------------------------------------------------------------------------------- 1 | import { buildProductSearchParams } from '../buildProductSearchParams' 2 | 3 | describe('[helpers] buildProductSearchParams function', () => { 4 | it('should return the product search input according to search params', () => { 5 | const searchParams = { 6 | filters: ['tenant~brand:adidas,tenant~color:grey'], 7 | categoryCode: '30', 8 | } 9 | const buildProductSearchParamsMock = { 10 | query: '', 11 | startIndex: 0, 12 | pageSize: 16, 13 | sortBy: '', 14 | facet: 'categoryCode', 15 | facetHierValue: 'categoryCode:30', 16 | facetTemplate: 'categoryCode:30', 17 | facetValueFilter: 'categoryCode:30,tenant~brand:adidas,tenant~color:grey', 18 | filter: '', 19 | } 20 | expect(buildProductSearchParams(searchParams)).toStrictEqual(buildProductSearchParamsMock) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /lib/api/util/config-helpers.ts: -------------------------------------------------------------------------------- 1 | import { UserAuthTicket } from '@kibocommerce/graphql-client' 2 | 3 | const protocolRegx = new RegExp(/https?:\/\//) 4 | const addProtocolToHost = (hostname: string | undefined) => 5 | hostname && !hostname.match(protocolRegx) ? `https://${hostname}` : hostname 6 | 7 | export const getGraphqlUrl = () => `${addProtocolToHost(process.env.KIBO_API_HOST)}/graphql` 8 | 9 | export const getApiConfig = () => { 10 | return { 11 | clientId: process.env.KIBO_CLIENT_ID as string, 12 | sharedSecret: process.env.KIBO_SHARED_SECRET as string, 13 | authHost: process.env.KIBO_AUTH_HOST as string, 14 | apiHost: process.env.KIBO_API_HOST as string, 15 | } 16 | } 17 | 18 | export const isShopperAuthExpired = (userAuthTicket: UserAuthTicket) => { 19 | const { accessTokenExpiration } = userAuthTicket 20 | return new Date(accessTokenExpiration).getTime() < Date.now() 21 | } 22 | -------------------------------------------------------------------------------- /lib/gql/fragments/configureProduct.ts: -------------------------------------------------------------------------------- 1 | export const configureProductOptions = ` 2 | fragment configureProductOptions on ConfiguredProduct { 3 | options { 4 | attributeFQN 5 | attributeDetail { 6 | name 7 | inputType 8 | } 9 | isProductImageGroupSelector 10 | isRequired 11 | isMultiValue 12 | values { 13 | value 14 | isSelected 15 | deltaPrice 16 | stringValue 17 | shopperEnteredValue 18 | } 19 | } 20 | } 21 | ` 22 | export const configureProductInfo = ` 23 | fragment configureProductInfo on ConfiguredProduct { 24 | productCode 25 | variationProductCode 26 | productImages { 27 | imageUrl 28 | altText 29 | cmsId 30 | } 31 | purchasableState { 32 | isPurchasable 33 | } 34 | ...configureProductOptions 35 | } 36 | ${configureProductOptions} 37 | ` 38 | -------------------------------------------------------------------------------- /components/product-listing/CategoryFacet/CategoryFacet.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import CategoryFacet from './CategoryFacet' 6 | import { categoryFacetDataMock } from '@/__mocks__/stories/categoryFacetDataMock' 7 | 8 | export default { 9 | title: 'product-listing/CategoryFacet', 10 | component: CategoryFacet, 11 | } as ComponentMeta<typeof CategoryFacet> 12 | 13 | const Template: ComponentStory<typeof CategoryFacet> = (args) => <CategoryFacet {...args} /> 14 | 15 | export const CategoryFacetDesktop = Template.bind({}) 16 | 17 | CategoryFacetDesktop.args = { 18 | initialItemsToShow: 5, 19 | categoryFacet: categoryFacetDataMock, 20 | breadcrumbs: [ 21 | { 22 | text: 'Home', 23 | link: '/', 24 | }, 25 | { 26 | text: 'Jacket', 27 | link: '/categoryCode/40', 28 | }, 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /hooks/mutations/multiShip/useCreateCheckoutActionMutation/useCreateCheckoutActionMutation.spec.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useCreateMultiShipCheckoutMutation } from './useCreateCheckoutActionMutation' 4 | import { checkoutMock } from '@/__mocks__/stories' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useCreateMultiShipCheckoutMutation', () => { 8 | it('should use useCreateMultiShipCheckoutMutation', async () => { 9 | renderHook( 10 | async () => { 11 | const createMultiShipCheckout = useCreateMultiShipCheckoutMutation() 12 | const response = await createMultiShipCheckout.mutateAsync(checkoutMock?.checkout) 13 | 14 | expect(response).toEqual(checkoutMock) 15 | }, 16 | { 17 | wrapper: createQueryClientWrapper(), 18 | } 19 | ) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /hooks/mutations/useCheckoutMutations/useCreateFromCartMutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useCreateFromCartMutation } from './useCreateFromCartMutation' 4 | import { orderMock } from '@/__mocks__/stories/orderMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useCreateFromCartMutation', () => { 8 | it('should return cart details when user provides valid cartId', async () => { 9 | renderHook( 10 | async () => { 11 | const cartId = '137a94b6402be000013718d80000678b' 12 | const { createFromCart } = useCreateFromCartMutation() 13 | const response = await createFromCart.mutateAsync(cartId) 14 | expect(response).toStrictEqual(orderMock.checkout) 15 | }, 16 | { 17 | wrapper: createQueryClientWrapper(), 18 | } 19 | ) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /hooks/queries/multiShip/useCheckoutQueries/useCheckoutQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useMultiShipCheckoutQueries } from './useCheckoutQueries' 4 | import { checkoutMock } from '@/__mocks__/stories' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] MultiShip useCheckoutQueries', () => { 8 | it('should return multiShip checkout details when user provides valid checkoutId', async () => { 9 | const checkoutId = '137a979305c65d00010800230000678b' 10 | const { result, waitFor } = renderHook( 11 | () => useMultiShipCheckoutQueries({ checkoutId, isMultiship: true }), 12 | { 13 | wrapper: createQueryClientWrapper(), 14 | } 15 | ) 16 | await waitFor(() => result.current.isSuccess) 17 | expect(result.current.data).toStrictEqual(checkoutMock.checkout) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /lib/helpers/buildAddToWishlistParams.ts: -------------------------------------------------------------------------------- 1 | import type { WishlistProductInput } from '@/lib/types' 2 | 3 | import type { MutationCreateWishlistItemArgs } from '@/lib/gql/types' 4 | 5 | export const buildAddToWishlistItemParams = ( 6 | product: WishlistProductInput, 7 | wishlistId: string 8 | ): MutationCreateWishlistItemArgs => { 9 | return { 10 | wishlistId: wishlistId, 11 | wishlistItemInput: { 12 | product: { 13 | options: product?.options?.map((option) => { 14 | return { 15 | attributeFQN: option?.attributeFQN, 16 | value: option?.value || option?.shopperEnteredValue, 17 | } 18 | }), 19 | productCode: product?.productCode || '', 20 | variationProductCode: product?.variationProductCode || '', 21 | isPackagedStandAlone: product?.isPackagedStandAlone || true, 22 | }, 23 | quantity: 1, 24 | }, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/add-payment-method-to-checkout.ts: -------------------------------------------------------------------------------- 1 | import { 2 | baseCheckoutFragment, 3 | checkoutLineItemFragment, 4 | checkoutPaymentFragment, 5 | fullfillmentInfoFragment, 6 | } from '../../fragments' 7 | 8 | const addPaymentMethodToCheckout = /* GraphQL */ ` 9 | mutation addPaymentMethod($orderId: String!, $paymentAction: PaymentActionInput) { 10 | createOrderPaymentAction(orderId: $orderId, paymentActionInput: $paymentAction) { 11 | ...baseCheckoutFragment 12 | items { 13 | ...checkoutLineItemFragment 14 | } 15 | fulfillmentInfo { 16 | ...fullfillmentInfoFragment 17 | } 18 | payments { 19 | ...checkoutPaymentFragment 20 | } 21 | } 22 | } 23 | 24 | ${baseCheckoutFragment} 25 | ${checkoutLineItemFragment} 26 | ${fullfillmentInfoFragment} 27 | ${checkoutPaymentFragment} 28 | ` 29 | export default addPaymentMethodToCheckout 30 | -------------------------------------------------------------------------------- /hooks/queries/useStoreLocationsQueries/useStoreLocationsQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useStoreLocationsQueries } from './useStoreLocationsQueries' 4 | import { locationCollectionMock } from '@/__mocks__/stories/locationCollectionMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | const location = locationCollectionMock.spLocations?.items || [] 8 | describe('[hooks] useStoreLocationsQueries', () => { 9 | it('should return loactions with the filter by geo location', async () => { 10 | const { result, waitFor } = renderHook( 11 | () => useStoreLocationsQueries({ filter: 'geo near(87110,160934)' }), 12 | { 13 | wrapper: createQueryClientWrapper(), 14 | } 15 | ) 16 | 17 | await waitFor(() => result.current.isSuccess) 18 | expect(result.current.data).toEqual(location) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /lib/gql/mutations/checkout/split-order-shipment.ts: -------------------------------------------------------------------------------- 1 | import { shipmentItemFragment } from '../../fragments' 2 | 3 | const splitOrderShipment = /* GraphQL */ ` 4 | mutation splitOrderShipment( 5 | $orderId: String! 6 | $shipmentNumber: String! 7 | $splitShipmentsObjectInput: SplitShipmentsObjectInput 8 | ) { 9 | splitOrderShipment( 10 | orderId: $orderId 11 | shipmentNumber: $shipmentNumber 12 | splitShipmentsObjectInput: $splitShipmentsObjectInput 13 | ) { 14 | externalShipmentId 15 | number 16 | orderId 17 | orderNumber 18 | originalShipmentNumber 19 | customerAccountId 20 | shipmentType 21 | shippingMethodCode 22 | shippingMethodName 23 | fulfillmentLocationCode 24 | items { 25 | ...shipmentItemFragment 26 | } 27 | } 28 | } 29 | ${shipmentItemFragment} 30 | ` 31 | 32 | export default splitOrderShipment 33 | -------------------------------------------------------------------------------- /hooks/queries/multiShip/useCheckoutDestinationsQueries/useCheckoutDestinationsQueries.spec.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useCheckoutDestinationsQueries } from './useCheckoutDestinationsQueries' 4 | import { checkoutDestinationsMock } from '@/__mocks__/stories' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useCheckoutDestinationsQueries', () => { 8 | it('should return checkout details when user provides valid checkoutId', async () => { 9 | const checkoutId = '137a979305c65d00010800230000678b' 10 | const { result, waitFor } = renderHook(() => useCheckoutDestinationsQueries({ checkoutId }), { 11 | wrapper: createQueryClientWrapper(), 12 | }) 13 | await waitFor(() => result.current.isSuccess) 14 | expect(result.current.data).toStrictEqual(checkoutDestinationsMock.checkoutDestinations) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /lib/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './buildBreadcrumbsParams' 2 | export * from './findParentNode' 3 | export * from './swipeDetect' 4 | export * from './uiHelpers' 5 | export * from './cookieHelper' 6 | export * from './buildCheckoutShippingParams' 7 | export * from './buildAddToWishlistParams' 8 | export * from './buildCreateWishlistItemParams' 9 | export * from './buildRemoveWishlistItemParams' 10 | export * from './buildWishlistParams' 11 | export * from './tokenizeCreditCardPayment' 12 | export * from './buildOrdersFilterParams' 13 | export * from './buildAddressParams' 14 | export * from './buildAddOrRemoveWishlistItemParams' 15 | export * from './buildCreateOrderParams' 16 | export * from './buildProductSearchParams' 17 | export * from './buildCreateOrderReturnItemsParams' 18 | export * from './buildSubscriptionFulfillmentInfoParams' 19 | export * from './buildSubscriptionParams' 20 | export * from './buildUpdateSubscriptionPaymentParams' 21 | -------------------------------------------------------------------------------- /__test__/e2e/integration/common/ProductItem/ProductItem.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { composeStories } from '@storybook/testing-react' 4 | import { render, screen } from '@testing-library/react' 5 | 6 | import * as stories from '@/components/common/ProductItem/ProductItem.stories' 7 | 8 | const { Common } = composeStories(stories) 9 | 10 | describe('[component] - ProductItem Integration', () => { 11 | const setup = () => { 12 | render(<Common {...Common.args} />) 13 | } 14 | 15 | it('should render component', () => { 16 | setup() 17 | 18 | const image = screen.getByRole('img') 19 | const options = Common.args?.options || [] 20 | 21 | expect(image).toHaveAttribute('alt', Common.args?.name) 22 | 23 | options?.map((option) => { 24 | expect(screen.getByText(`${option?.name}:`)).toBeVisible() 25 | expect(screen.getByText(`${option?.value}`)).toBeVisible() 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /components/order/ViewOrderDetails/ViewOrderDetails.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import ViewOrderDetails from './ViewOrderDetails' 6 | import { orderMock, orderReturnItemsMock } from '@/__mocks__/stories/orderMock' 7 | 8 | export default { 9 | title: 'Order/ViewOrderDetails', 10 | component: ViewOrderDetails, 11 | argTypes: { onChange: { action: 'onChange' } }, 12 | } as ComponentMeta<typeof ViewOrderDetails> 13 | 14 | const Template: ComponentStory<typeof ViewOrderDetails> = (args) => <ViewOrderDetails {...args} /> 15 | 16 | export const Common = Template.bind({}) 17 | Common.args = { 18 | order: orderMock.checkout, 19 | title: 'view-order-details', 20 | } 21 | 22 | export const WithReturnItemButton = Template.bind({}) 23 | WithReturnItemButton.args = { 24 | order: orderReturnItemsMock.createReturn, 25 | title: 'view-order-details', 26 | } 27 | -------------------------------------------------------------------------------- /components/common/AddressCard/AddressCard.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { composeStories } from '@storybook/testing-react' 4 | import { render, screen } from '@testing-library/react' 5 | 6 | import * as stories from './AddressCard.stories' // import all stories from the stories file 7 | 8 | const { Common } = composeStories(stories) 9 | 10 | describe('[component] - AddressCard', () => { 11 | it('should display the address', () => { 12 | render(<Common {...Common.args} />) 13 | 14 | expect(screen.getByText(Common.args.title)).toBeVisible() 15 | expect(screen.getByText(Common.args.address1)).toBeVisible() 16 | expect(screen.getByText(new RegExp(Common.args.address2))).toBeVisible() 17 | expect(screen.getByText(Common.args.cityOrTown)).toBeVisible() 18 | expect(screen.getByText(Common.args.stateOrProvince)).toBeVisible() 19 | expect(screen.getByText(Common.args.postalOrZipCode)).toBeVisible() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /lib/api/operations/get-category-tree.ts: -------------------------------------------------------------------------------- 1 | import getConfig from 'next/config' 2 | 3 | import { fetcher } from '@/lib/api/util' 4 | import cache from '@/lib/api/util/cache' 5 | import { getCategoryTreeQuery } from '@/lib/gql/queries' 6 | 7 | const { serverRuntimeConfig } = getConfig() 8 | const cacheKey = serverRuntimeConfig.cacheKey 9 | const cacheTimeOut = serverRuntimeConfig.cacheTimeOut 10 | 11 | export default async function getCategoryTree() { 12 | try { 13 | const cachedItems = cache.get(cacheKey) 14 | if (cachedItems) return cachedItems 15 | 16 | if (!cachedItems) { 17 | const response = await fetcher({ query: getCategoryTreeQuery, variables: {} }, null) 18 | const items = response?.data?.categoriesTree?.items 19 | if (items && items.length) { 20 | cache.set(cacheKey, items, cacheTimeOut) 21 | } 22 | 23 | return items 24 | } 25 | } catch (error) { 26 | console.log(error) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /components/checkout/PaymentCardDetailsView/PaymentCardDetailsView.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import PaymentCardDetailsView from './PaymentCardDetailsView' 6 | 7 | export default { 8 | title: 'Checkout/PaymentCardDetailsView', 9 | component: PaymentCardDetailsView, 10 | } as ComponentMeta<typeof PaymentCardDetailsView> 11 | 12 | const Template: ComponentStory<typeof PaymentCardDetailsView> = (args) => ( 13 | <PaymentCardDetailsView {...args} /> 14 | ) 15 | 16 | export const Common = Template.bind({}) 17 | 18 | Common.args = { 19 | withoutRadioTitle: 'Payment Method', 20 | cardNumberPart: '***********1234', 21 | expireMonth: 4, 22 | expireYear: 2026, 23 | radio: false, 24 | } 25 | 26 | export const Radio = Template.bind({}) 27 | 28 | Radio.args = { 29 | ...Common.args, 30 | radio: true, 31 | radioGroupTitle: 'Your Default Payment method', 32 | } 33 | -------------------------------------------------------------------------------- /components/common/KiboLogo/KiboLogo.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system' 2 | import { StaticImageData } from 'next/image' 3 | 4 | import KiboImage from '../KiboImage/KiboImage' 5 | import Logo from '@/public/kibo_logo.png' 6 | 7 | interface KiboLogoProps { 8 | logo?: string | StaticImageData // URL or File 9 | alt?: string 10 | small?: boolean 11 | } 12 | 13 | const styles = { 14 | logoContainer: { 15 | width: { 16 | xs: 33, 17 | md: 78, 18 | }, 19 | height: { 20 | xs: 33, 21 | md: 78, 22 | }, 23 | }, 24 | smallLogo: { 25 | width: 40, 26 | height: 40, 27 | }, 28 | } 29 | 30 | const KiboLogo = ({ logo = Logo, alt = 'kibo-logo', small }: KiboLogoProps) => { 31 | return ( 32 | <Box width={'100%'} sx={small ? styles.smallLogo : styles.logoContainer}> 33 | <KiboImage src={logo} alt={alt} layout="fill" objectFit="contain" /> 34 | </Box> 35 | ) 36 | } 37 | 38 | export default KiboLogo 39 | -------------------------------------------------------------------------------- /hooks/mutations/multiShip/useDeleteCheckoutCouponMutation/useDeleteCheckoutCouponMutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useDeleteCheckoutCouponMutation } from './useDeleteCheckoutCouponMutation' 4 | import { checkoutMock } from '@/__mocks__/stories' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useDeleteCheckoutCouponMutation', () => { 8 | it('should remove deleted coupon', async () => { 9 | renderHook( 10 | async () => { 11 | const deleteCheckoutCoupon = useDeleteCheckoutCouponMutation() 12 | const variables = { checkoutId: '43245kjg5j43543hj', couponCode: 'OFF10' } 13 | const response = await deleteCheckoutCoupon.mutateAsync(variables) 14 | expect(response).toStrictEqual(checkoutMock) 15 | }, 16 | { 17 | wrapper: createQueryClientWrapper(), 18 | } 19 | ) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /components/common/ReviewProductItemsWithAddresses/ReviewProductItemsWithAddresses.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ComponentStory, ComponentMeta } from '@storybook/react' 4 | 5 | import ReviewProductItemsWithAddresses from './ReviewProductItemsWithAddresses' 6 | import { orderMock } from '@/__mocks__/stories/orderMock' 7 | 8 | import type { CrOrderItem } from '@/lib/gql/types' 9 | 10 | export default { 11 | title: 'Common/ReviewProductItemsWithAddresses', 12 | component: ReviewProductItemsWithAddresses, 13 | argTypes: {}, 14 | } as ComponentMeta<typeof ReviewProductItemsWithAddresses> 15 | 16 | const orderItems = orderMock?.checkout?.items 17 | 18 | // Default Line Item 19 | const Template: ComponentStory<typeof ReviewProductItemsWithAddresses> = (args) => ( 20 | <ReviewProductItemsWithAddresses {...args} /> 21 | ) 22 | 23 | export const Common = Template.bind({}) 24 | Common.args = { 25 | items: orderItems as CrOrderItem[], 26 | } 27 | -------------------------------------------------------------------------------- /components/cart/CartItemList/CartItemList.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import '@testing-library/jest-dom' 4 | import { composeStories } from '@storybook/testing-react' 5 | import { render, screen } from '@testing-library/react' 6 | 7 | import * as stories from './CartItemList.stories' 8 | 9 | const { Common } = composeStories(stories) 10 | 11 | const cartItemMock = () => <div data-testid="cart-item-component" /> 12 | jest.mock('@/components/cart/CartItem/CartItem', () => () => cartItemMock()) 13 | 14 | describe('[components] - CartItemList', () => { 15 | const setup = () => { 16 | render(<Common {...Common.args} />) 17 | } 18 | 19 | it('should render component', () => { 20 | // arrange 21 | setup() 22 | 23 | // act 24 | const cartItem = screen.getAllByTestId(/cart-item-component/i) 25 | 26 | // assert 27 | const count = Common.args?.cartItems?.length || 0 28 | expect(cartItem).toHaveLength(count) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /components/product/ProductOption/ProductOption.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { composeStories } from '@storybook/testing-react' 4 | import { render, screen } from '@testing-library/react' 5 | 6 | import * as stories from './ProductOption.stories' 7 | 8 | const { Common } = composeStories(stories) 9 | 10 | describe('[component] - ProductOption', () => { 11 | const setup = () => { 12 | render(<Common {...Common.args} />) 13 | } 14 | 15 | it('should render component', () => { 16 | setup() 17 | 18 | const productOption = screen.getByTestId('productOption') 19 | const option = Common.args?.option 20 | const value = option?.value || '' 21 | const optionValue = screen.getByText(value) 22 | const name = option?.value || '' 23 | const optionName = screen.getByText(name) 24 | 25 | expect(productOption).toBeVisible() 26 | expect(optionValue).toBeVisible() 27 | expect(optionName).toBeVisible() 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /hooks/mutations/multiShip/useUpdateCheckoutCouponMutation/useUpdateCheckoutCouponMutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useUpdateCheckoutCouponMutation } from './useUpdateCheckoutCouponMutation' 4 | import { checkoutMock } from '@/__mocks__/stories' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useUpdateCheckoutCouponMutation', () => { 8 | it('should use useUpdateCheckoutCouponMutation ', async () => { 9 | renderHook( 10 | async () => { 11 | const updateCheckoutCoupon = useUpdateCheckoutCouponMutation() 12 | const response = await updateCheckoutCoupon.mutateAsync({ 13 | checkoutId: 'fFG7657', 14 | couponCode: '11OFF', 15 | }) 16 | expect(response).toStrictEqual(checkoutMock) 17 | }, 18 | { 19 | wrapper: createQueryClientWrapper(), 20 | } 21 | ) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /public/icons/facebook.svg: -------------------------------------------------------------------------------- 1 | <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M0 16C0 7.16344 7.16344 0 16 0C24.8366 0 32 7.16344 32 16C32 24.8366 24.8366 32 16 32C7.16344 32 0 24.8366 0 16Z" fill="white"/> 3 | <path d="M18.7499 10.0023L17.0639 10C15.1703 10 13.946 11.1592 13.946 12.9531V14.3148H12.2514C12.1052 14.3148 11.9863 14.4245 11.9863 14.5595V16.5325C11.9863 16.6675 12.1052 16.7771 12.2514 16.7771H13.9465V21.7552C13.9465 21.8902 14.0653 21.9999 14.2116 21.9999H16.4231C16.5694 21.9999 16.6882 21.8902 16.6882 21.7552L16.6877 16.7771H18.6697C18.8159 16.7771 18.9348 16.6675 18.9348 16.5325L18.9358 14.5595C18.9358 14.4948 18.9078 14.4325 18.8581 14.3866C18.8083 14.3406 18.7408 14.3148 18.6707 14.3148H16.6877V13.1608C16.6877 12.6058 16.8309 12.3245 17.6139 12.3245L18.7494 12.3241C18.8957 12.3241 19.0145 12.2144 19.0145 12.0794V10.247C19.0145 10.1124 18.8962 10.0027 18.7499 10.0023L18.7499 10.0023Z" fill="#172ACB"/> 4 | </svg> 5 | -------------------------------------------------------------------------------- /components/product-listing/FacetItemList/FacetItemList.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import '@testing-library/jest-dom' 4 | import { composeStories } from '@storybook/testing-react' 5 | import { render, screen } from '@testing-library/react' 6 | 7 | import * as stories from './FacetItemList.stories' 8 | 9 | const { Common } = composeStories(stories) 10 | 11 | const facetItemMock = () => <div data-testid="facet-item-component" /> 12 | jest.mock('../FacetItem/FacetItem', () => () => facetItemMock()) 13 | 14 | describe('[components] - FacetItemList', () => { 15 | const setup = () => { 16 | render(<Common {...Common.args} />) 17 | } 18 | 19 | it('should render component', () => { 20 | // arrange 21 | setup() 22 | 23 | // act 24 | const facetItem = screen.getAllByTestId(/facet-item-component/i) 25 | 26 | // assert 27 | const count = Common.args?.itemList?.length || 0 28 | expect(facetItem).toHaveLength(count) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /components/product/ProductOptionCheckbox/ProductOptionCheckbox.spec.js: -------------------------------------------------------------------------------- 1 | import { composeStories } from '@storybook/testing-react' 2 | import { render, screen } from '@testing-library/react' 3 | 4 | import '@testing-library/jest-dom' 5 | import '@testing-library/jest-dom/extend-expect' 6 | 7 | import * as stories from './ProductOptionCheckbox.stories' // import all stories from the stories file 8 | 9 | const { Common } = composeStories(stories) 10 | 11 | describe('[component] ProductOptionCheckbox component', () => { 12 | it('should render checkbox option', () => { 13 | render(<Common {...Common.args} />) 14 | 15 | const checkbox = screen.getByTestId('kibo-product-option-checkbox') 16 | 17 | expect(checkbox).toBeVisible() 18 | }) 19 | 20 | it('should render checkbox label', () => { 21 | render(<Common {...Common.args} />) 22 | 23 | const checkbox = screen.getByText(/include warranty/i) 24 | 25 | expect(checkbox).toBeVisible() 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /components/product/ProductOptionCheckbox/ProductOptionCheckbox.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox, FormControlLabel } from '@mui/material' 2 | export interface ProductOptionCheckboxProps { 3 | label: string 4 | checked?: boolean 5 | attributeFQN: string 6 | onCheckboxChange: (attributeFQN: string, value: string, checked?: boolean) => void 7 | } 8 | 9 | const ProductOptionCheckbox = (props: ProductOptionCheckboxProps) => { 10 | const { label = '', checked = false, attributeFQN, onCheckboxChange } = props 11 | return ( 12 | <FormControlLabel 13 | label={label} 14 | control={ 15 | <Checkbox 16 | checked={checked} 17 | inputProps={{ 18 | 'aria-label': label, 19 | }} 20 | onChange={(_, isChecked) => onCheckboxChange(attributeFQN, '', isChecked)} 21 | data-testid={`kibo-product-option-checkbox`} 22 | /> 23 | } 24 | /> 25 | ) 26 | } 27 | 28 | export default ProductOptionCheckbox 29 | -------------------------------------------------------------------------------- /lib/helpers/__tests__/buildAddOrRemoveWishlistItemParams.spec.ts: -------------------------------------------------------------------------------- 1 | import { buildAddOrRemoveWishlistItemParams } from '../buildAddOrRemoveWishlistItemParams' 2 | import { ProductCustomMock } from '@/__mocks__/stories' 3 | 4 | describe('[helpers] buildAddOrRemoveWishlistItemParams function', () => { 5 | it('should return the addOrRemoveWishlistItem variables', () => { 6 | const mockedProduct = ProductCustomMock 7 | expect(buildAddOrRemoveWishlistItemParams(mockedProduct)).toStrictEqual({ 8 | productCode: mockedProduct.productCode, 9 | variationProductCode: mockedProduct.variationProductCode, 10 | isPackagedStandAlone: true, 11 | options: 12 | mockedProduct?.options?.map((productOption) => ({ 13 | attributeFQN: productOption?.attributeFQN, 14 | name: productOption?.attributeDetail?.name, 15 | value: productOption?.values?.find((v) => v?.isSelected)?.value, 16 | })) || [], 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /.github/lighthouse/budget.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "/*", 4 | "timings": [ 5 | { 6 | "metric": "interactive", 7 | "budget": 3000 8 | }, 9 | { 10 | "metric": "first-contentful-paint", 11 | "budget": 1800 12 | }, 13 | { 14 | "metric": "cumulative-layout-shift", 15 | "budget": 0.1 16 | }, 17 | { 18 | "metric": "total-blocking-time", 19 | "budget": 200 20 | }, 21 | { 22 | "metric": "largest-contentful-paint", 23 | "budget": 3500 24 | }, 25 | { 26 | "metric": "speed-index", 27 | "budget": 3500 28 | } 29 | ], 30 | "resourceSizes": [ 31 | { 32 | "resourceType": "script", 33 | "budget": 100 34 | }, 35 | { 36 | "resourceType": "total", 37 | "budget": 400 38 | }, 39 | { 40 | "resourceType": "image", 41 | "budget": 300 42 | } 43 | ], 44 | "resourceCounts": [ 45 | { 46 | "resourceType": "third-party", 47 | "budget": 14 48 | } 49 | ] 50 | } 51 | ] -------------------------------------------------------------------------------- /components/product/ProductOptionList/ProductOptionList.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { composeStories } from '@storybook/testing-react' 4 | import { render, screen } from '@testing-library/react' 5 | 6 | import * as stories from './ProductOptionList.stories' 7 | 8 | const { Common } = composeStories(stories) 9 | 10 | const productOptionMock = () => <div data-testid="product-option-component" /> 11 | jest.mock('@/components/product/ProductOption/ProductOption', () => () => productOptionMock()) 12 | 13 | describe('[component] - ProductOptionList', () => { 14 | const setup = () => { 15 | render(<Common {...Common.args} />) 16 | } 17 | 18 | it('should render component', () => { 19 | setup() 20 | 21 | const productItemOptions = screen.getAllByTestId('product-option-component') 22 | const items = Common.args?.options || [] 23 | 24 | const count = items.length || 0 25 | expect(productItemOptions).toHaveLength(count) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /lib/helpers/buildSubscriptionParams.ts: -------------------------------------------------------------------------------- 1 | import { SbSubscriptionItem, Subscription } from '../gql/types' 2 | 3 | export const buildPauseAndCancelSubscriptionParams = ( 4 | subscriptionDetailsData: Subscription, 5 | actionName: string 6 | ) => { 7 | return { 8 | subscriptionId: subscriptionDetailsData.id as string, 9 | subscriptionActionInput: { 10 | actionName: actionName, 11 | reason: { 12 | actionName: actionName, 13 | }, 14 | }, 15 | } 16 | } 17 | 18 | export const buildCancelSubscriptionParams = ( 19 | subscriptionDetailsData: Subscription, 20 | subscriptionItem: SbSubscriptionItem[] 21 | ) => { 22 | return { 23 | subscriptionId: subscriptionDetailsData?.id as string, 24 | subscriptionItemId: subscriptionItem[0]?.id as string, 25 | subscriptionReasonInput: { 26 | actionName: 'cancel', 27 | reasonCode: 'cancel', 28 | description: 'cancel', 29 | moreInfo: 'cancel', 30 | }, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /components/common/OrderSummary/OrderSummary.spec.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import React from 'react' 4 | 5 | import { composeStories } from '@storybook/testing-react' 6 | import { render, screen } from '@testing-library/react' 7 | 8 | import * as stories from './OrderSummary.stories' // import all stories from the stories file 9 | 10 | const { Checkout } = composeStories(stories) 11 | 12 | const orderPriceMock = () => <div data-testid="order-price-component" /> 13 | jest.mock('@/components/common/OrderPrice/OrderPrice', () => () => orderPriceMock()) 14 | 15 | describe('checkout Component', () => { 16 | it('should render order summary heading', () => { 17 | render(<Checkout {...Checkout.args} />) 18 | const orderSummaryHeading = screen.getByText(Checkout.args.nameLabel) 19 | const orderPriceComponent = screen.getByTestId('order-price-component') 20 | expect(orderSummaryHeading).toBeVisible() 21 | expect(orderPriceComponent).toBeInTheDocument() 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /components/layout/AppHeader/Icons/AccountIcon/AccountIcon.tsx: -------------------------------------------------------------------------------- 1 | import AccountCircleIcon from '@mui/icons-material/AccountCircle' 2 | import { useTranslation } from 'next-i18next' 3 | 4 | import { HeaderAction } from '@/components/common' 5 | import { useAuthContext } from '@/context' 6 | import type { IconProps } from '@/lib/types' 7 | 8 | interface AccountIconProps extends IconProps { 9 | onAccountIconClick: () => void 10 | } 11 | 12 | const AccountIcon = ({ size, onAccountIconClick }: AccountIconProps) => { 13 | const { isAuthenticated, user } = useAuthContext() 14 | const { t } = useTranslation('common') 15 | 16 | return ( 17 | <HeaderAction 18 | title={isAuthenticated ? `${t('hi')}, ${user?.firstName}` : t('my-account')} 19 | subtitle={isAuthenticated ? t('go-to-my-account') : t('log-in')} 20 | icon={AccountCircleIcon} 21 | iconFontSize={size} 22 | onClick={onAccountIconClick} 23 | /> 24 | ) 25 | } 26 | 27 | export default AccountIcon 28 | -------------------------------------------------------------------------------- /hooks/mutations/useCouponMutations/useUpdateCartCouponMutation/useUpdateCartCouponMutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useUpdateCartCouponMutation } from './useUpdateCartCouponMutation' 4 | import { cartCouponMock } from '@/__mocks__/stories/cartMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useUpdateCartCouponMutation', () => { 8 | it('should use useUpdateCartCouponMutation ', async () => { 9 | renderHook( 10 | async () => { 11 | const updateCartCoupon = useUpdateCartCouponMutation() 12 | const response = await updateCartCoupon.mutateAsync({ 13 | cartId: 'fjsdhfjsdh53472bkjsdffdf', 14 | couponCode: '10OFF', 15 | }) 16 | expect(response).toStrictEqual(cartCouponMock.updateCartCoupon) 17 | }, 18 | { 19 | wrapper: createQueryClientWrapper(), 20 | } 21 | ) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /components/cart/CartItemActionsMobile/CartItemActionsMobile.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box } from '@mui/material' 4 | import { ComponentStory, ComponentMeta } from '@storybook/react' 5 | 6 | import CartItemActionsMobile from './CartItemActionsMobile' 7 | 8 | export default { 9 | title: 'cart/CartItemActionsMobile', 10 | component: CartItemActionsMobile, 11 | argTypes: { onMenuItemSelection: { action: 'clicked' } }, 12 | } as ComponentMeta<typeof CartItemActionsMobile> 13 | 14 | const Template: ComponentStory<typeof CartItemActionsMobile> = (args) => ( 15 | <Box 16 | height={'100%'} 17 | width={'100%'} 18 | display={'flex'} 19 | justifyContent={'center'} 20 | alignItems={'center'} 21 | > 22 | <CartItemActionsMobile {...args} /> 23 | </Box> 24 | ) 25 | 26 | export const CartAction = Template.bind({}) 27 | 28 | const actions = ['Edit', 'Save For Later', 'Add to Favorites'] 29 | 30 | CartAction.args = { 31 | actions, 32 | } 33 | -------------------------------------------------------------------------------- /hooks/mutations/useCouponMutations/useUpdateOrderCouponMutation/useUpdateOrderCouponMutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useUpdateOrderCouponMutation } from './useUpdateOrderCouponMutation' 4 | import { orderCouponMock } from '@/__mocks__/stories/orderMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useUpdateOrderCouponMutation', () => { 8 | it('should use useUpdateOrderCouponMutation ', async () => { 9 | renderHook( 10 | async () => { 11 | const updateOrderCoupon = useUpdateOrderCouponMutation() 12 | const response = await updateOrderCoupon.mutateAsync({ 13 | checkoutId: 'fskd657657', 14 | couponCode: '10OFF', 15 | }) 16 | expect(response).toStrictEqual(orderCouponMock.updateOrderCoupon) 17 | }, 18 | { 19 | wrapper: createQueryClientWrapper(), 20 | } 21 | ) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /hooks/queries/multiShip/useCheckoutDestinationQueries/useCheckoutDestinationQueries.spec.tsx: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useCheckoutDestinationQueries } from './useCheckoutDestinationQueries' 4 | import { checkoutDestinationsMock } from '@/__mocks__/stories' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useCheckoutDestinationQueries', () => { 8 | it('should return checkout details when user provides valid checkoutId', async () => { 9 | const checkoutId = '137a979305c65d00010800230000678b' 10 | const { result, waitFor } = renderHook( 11 | () => useCheckoutDestinationQueries({ checkoutId, destinationId: '' }), 12 | { 13 | wrapper: createQueryClientWrapper(), 14 | } 15 | ) 16 | await waitFor(() => result.current.isSuccess) 17 | expect(result.current.data).toStrictEqual(checkoutDestinationsMock.checkoutDestinations[0]) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /lib/gql/mutations/product/configureProductMutation.ts: -------------------------------------------------------------------------------- 1 | import { configureProductInfo } from '../../fragments/configureProduct' 2 | 3 | export const productConfigInventoryInfo = /* GraphQL */ ` 4 | fragment productConfigInventoryInfo on ConfiguredProduct { 5 | inventoryInfo { 6 | manageStock 7 | onlineLocationCode 8 | onlineSoftStockAvailable 9 | onlineStockAvailable 10 | } 11 | } 12 | ` 13 | 14 | const configureProductMutation = /* GraphQL */ ` 15 | mutation configureProduct( 16 | $productCode: String! 17 | $selectedOptions: ProductOptionSelectionsInput! 18 | ) { 19 | configureProduct( 20 | productCode: $productCode 21 | includeOptionDetails: true 22 | productOptionSelectionsInput: $selectedOptions 23 | ) { 24 | ...configureProductInfo 25 | ...productConfigInventoryInfo 26 | } 27 | } 28 | ${configureProductInfo} 29 | ${productConfigInventoryInfo} 30 | ` 31 | export default configureProductMutation 32 | -------------------------------------------------------------------------------- /lib/gql/queries/checkout/get-multi-ship-checkout-query.ts: -------------------------------------------------------------------------------- 1 | import { 2 | checkoutLineItemFragment, 3 | destinationContactFragment, 4 | checkoutGroupingsFragment, 5 | baseMultiShipCheckoutFragment, 6 | } from '../../fragments' 7 | 8 | const getMultiShipCheckoutQuery = /* GraphQL */ ` 9 | query getMultiShipCheckout($checkoutId: String!) { 10 | checkout(checkoutId: $checkoutId) { 11 | ...baseMultiShipCheckoutFragment 12 | couponCodes 13 | items { 14 | destinationId 15 | ...checkoutLineItemFragment 16 | } 17 | destinations { 18 | id 19 | destinationContact { 20 | ...destinationContactFragment 21 | } 22 | } 23 | groupings { 24 | ...checkoutGroupingsFragment 25 | } 26 | } 27 | } 28 | ${checkoutLineItemFragment} 29 | ${destinationContactFragment} 30 | ${checkoutGroupingsFragment} 31 | ${baseMultiShipCheckoutFragment} 32 | ` 33 | export default getMultiShipCheckoutQuery 34 | -------------------------------------------------------------------------------- /lib/helpers/tokenizeCreditCardPayment.ts: -------------------------------------------------------------------------------- 1 | import { CardForm } from '../types' 2 | 3 | export const tokenizeCreditCardPayment = async ( 4 | creditCardData: CardForm, 5 | pciHost: string, 6 | apiHost: string 7 | ) => { 8 | try { 9 | const url = `https://${pciHost}/payments/commerce/payments/cards/` 10 | const tenantId = apiHost.split('-')[0].split('t')[1].toString() 11 | const { cardNumber, cardType, cvv } = creditCardData 12 | const ccData = { cardNumber, cardType, cvv, persistCard: true } 13 | 14 | const res = await fetch(url, { 15 | method: 'POST', 16 | headers: { 17 | accept: 'application/json', 18 | 'x-vol-tenant': tenantId, 19 | 'Content-Type': 'application/json', 20 | }, 21 | body: JSON.stringify(ccData), 22 | }) 23 | const tokenizedCCData = await res.json() 24 | if (tokenizedCCData.isSuccessful) { 25 | return tokenizedCCData 26 | } 27 | } catch (e) { 28 | console.error(e) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /components/layout/AppHeader/Icons/CartIcon/CartIcon.tsx: -------------------------------------------------------------------------------- 1 | import ShoppingCartIcon from '@mui/icons-material/ShoppingCart' 2 | import { useTranslation } from 'next-i18next' 3 | import { useRouter } from 'next/router' 4 | 5 | import { HeaderAction } from '@/components/common' 6 | import { useCartQueries } from '@/hooks' 7 | import { cartGetters } from '@/lib/getters' 8 | import type { IconProps } from '@/lib/types' 9 | 10 | const CartIcon = ({ size }: IconProps) => { 11 | const { t } = useTranslation('common') 12 | 13 | const { data: cart } = useCartQueries({}) 14 | const itemCount = cartGetters.getCartItemCount(cart) 15 | 16 | const router = useRouter() 17 | 18 | const gotoCart = () => { 19 | router.push('/cart') 20 | } 21 | 22 | return ( 23 | <HeaderAction 24 | subtitle={t('cart')} 25 | icon={ShoppingCartIcon} 26 | badgeContent={itemCount} 27 | iconFontSize={size} 28 | onClick={gotoCart} 29 | /> 30 | ) 31 | } 32 | 33 | export default CartIcon 34 | -------------------------------------------------------------------------------- /hooks/queries/useProductSearchQueries/useProductSearchQueries.spec.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useProductSearchQueries } from './useProductSearchQueries' 4 | import { productSearchResultMock } from '@/__mocks__/stories/productSearchResultMock' 5 | import { createQueryClientWrapper } from '@/__test__/utils/renderWithQueryClient' 6 | 7 | describe('[hooks] useProductSearchQueries', () => { 8 | it('should return product search result when facet filters is selected', async () => { 9 | const { result, waitFor } = renderHook( 10 | () => 11 | useProductSearchQueries( 12 | { categoryCode: '41', filters: ['Tenant~color:black,Tenant~color:blue'] }, 13 | productSearchResultMock 14 | ), 15 | { 16 | wrapper: createQueryClientWrapper(), 17 | } 18 | ) 19 | 20 | await waitFor(() => result.current.isSuccess) 21 | expect(result.current.data).toEqual(productSearchResultMock) 22 | }) 23 | }) 24 | --------------------------------------------------------------------------------