From 716b54096639c6846603a3d4263e5fad48b49135 Mon Sep 17 00:00:00 2001
From: matias-delavega-dg-dmi
 <79322706+matias-delavega-dg-dmi@users.noreply.github.com>
Date: Wed, 30 Jun 2021 10:05:26 -0300
Subject: [PATCH] TEC-252 and TEC-256: Implementing fetchers, products list and
 search (#5)

* TEC-252: Base integration with commercetools using fetchers for REST and GraphQL endpoints

* TEC-252: WIP commenting some components that are failing while we don't have all the hooks defined

* add sdk integration

* TEC-256: Implementing product search

* TEC-256: removing unnecessary env variables

* TEC-256: review comments

* TEC-256: other remaining review fixes

Co-authored-by: nicossosa93 <nicolas.sosa@devgurus.io>
---
 .../cart/CartSidebarView/CartSidebarView.tsx  |   6 +-
 components/common/Navbar/Navbar.tsx           |   2 +-
 .../product/ProductView/ProductView.tsx       |  38 ++--
 .../WishlistButton/WishlistButton.tsx         |  35 ++--
 framework/commerce/config.js                  |  10 +-
 framework/commercetools/.env.template         |   8 +
 framework/commercetools/README.md             |  18 ++
 .../catalog/products/get-products.ts          |  54 +++++
 .../api/endpoints/catalog/products/index.ts   |  18 ++
 framework/commercetools/api/index.ts          | 101 ++++++++++
 .../api/operations/get-all-pages.ts           |  40 ++++
 .../api/operations/get-all-product-paths.ts   |  50 +++++
 .../api/operations/get-all-products.ts        |  31 +++
 .../api/operations/get-customer-wishlist.ts   |  23 +++
 .../commercetools/api/operations/get-page.ts  |  45 +++++
 .../api/operations/get-product.ts             |  38 ++++
 .../api/operations/get-site-info.ts           |  39 ++++
 .../commercetools/api/operations/login.ts     |  43 ++++
 framework/commercetools/api/utils/errors.ts   |  25 +++
 .../api/utils/fetch-graphql-api.ts            |  37 ++++
 .../commercetools/api/utils/fetch-products.ts |  33 ++++
 framework/commercetools/api/utils/fetch.ts    |   3 +
 framework/commercetools/auth/index.ts         |   3 +
 framework/commercetools/auth/use-login.ts     |   0
 framework/commercetools/auth/use-logout.ts    |   0
 framework/commercetools/auth/use-signup.ts    |   0
 framework/commercetools/cart/index.ts         |   4 +
 framework/commercetools/cart/use-add-item.ts  |   0
 framework/commercetools/cart/use-cart.ts      |   0
 .../commercetools/cart/use-remove-item.ts     |   0
 .../commercetools/cart/use-update-item.ts     |   0
 framework/commercetools/commerce.config.json  |   9 +
 framework/commercetools/customer/index.ts     |   1 +
 .../commercetools/customer/use-customer.ts    |   0
 framework/commercetools/fetcher.ts            |  41 ++++
 framework/commercetools/index.tsx             |  38 ++++
 framework/commercetools/lib/array-to-tree.ts  |  70 +++++++
 framework/commercetools/lib/get-slug.ts       |   5 +
 framework/commercetools/lib/normalize.ts      | 185 ++++++++++++++++++
 framework/commercetools/next.config.js        |   8 +
 framework/commercetools/product/index.ts      |   2 +
 framework/commercetools/product/use-price.ts  |   2 +
 framework/commercetools/product/use-search.ts |  54 +++++
 framework/commercetools/provider.ts           |  24 +++
 framework/commercetools/types/cart.ts         | 103 ++++++++++
 framework/commercetools/types/checkout.ts     |   1 +
 framework/commercetools/types/common.ts       |   1 +
 framework/commercetools/types/customer.ts     |  20 ++
 framework/commercetools/types/index.ts        |  25 +++
 framework/commercetools/types/login.ts        |  13 ++
 framework/commercetools/types/logout.ts       |   1 +
 framework/commercetools/types/page.ts         |  11 ++
 framework/commercetools/types/product.ts      |  66 +++++++
 framework/commercetools/types/signup.ts       |   1 +
 framework/commercetools/types/site.ts         |  13 ++
 framework/commercetools/types/wishlist.ts     |  23 +++
 .../utils/commercetools/index.ts              |  51 +++++
 .../utils/queries/get-category.ts             |  29 +++
 .../utils/queries/get-product-query.ts        |  30 +++
 framework/commercetools/wishlist/index.ts     |   3 +
 .../commercetools/wishlist/use-add-item.ts    |   0
 .../commercetools/wishlist/use-remove-item.ts |   0
 .../commercetools/wishlist/use-wishlist.ts    |   0
 global.d.ts                                   |   4 +
 package.json                                  |   5 +
 tsconfig.json                                 |   4 +-
 yarn.lock                                     |  39 +++-
 67 files changed, 1541 insertions(+), 45 deletions(-)
 create mode 100644 framework/commercetools/.env.template
 create mode 100644 framework/commercetools/README.md
 create mode 100644 framework/commercetools/api/endpoints/catalog/products/get-products.ts
 create mode 100644 framework/commercetools/api/endpoints/catalog/products/index.ts
 create mode 100644 framework/commercetools/api/index.ts
 create mode 100644 framework/commercetools/api/operations/get-all-pages.ts
 create mode 100644 framework/commercetools/api/operations/get-all-product-paths.ts
 create mode 100644 framework/commercetools/api/operations/get-all-products.ts
 create mode 100644 framework/commercetools/api/operations/get-customer-wishlist.ts
 create mode 100644 framework/commercetools/api/operations/get-page.ts
 create mode 100644 framework/commercetools/api/operations/get-product.ts
 create mode 100644 framework/commercetools/api/operations/get-site-info.ts
 create mode 100644 framework/commercetools/api/operations/login.ts
 create mode 100644 framework/commercetools/api/utils/errors.ts
 create mode 100644 framework/commercetools/api/utils/fetch-graphql-api.ts
 create mode 100644 framework/commercetools/api/utils/fetch-products.ts
 create mode 100644 framework/commercetools/api/utils/fetch.ts
 create mode 100644 framework/commercetools/auth/index.ts
 create mode 100644 framework/commercetools/auth/use-login.ts
 create mode 100644 framework/commercetools/auth/use-logout.ts
 create mode 100644 framework/commercetools/auth/use-signup.ts
 create mode 100644 framework/commercetools/cart/index.ts
 create mode 100644 framework/commercetools/cart/use-add-item.ts
 create mode 100644 framework/commercetools/cart/use-cart.ts
 create mode 100644 framework/commercetools/cart/use-remove-item.ts
 create mode 100644 framework/commercetools/cart/use-update-item.ts
 create mode 100644 framework/commercetools/commerce.config.json
 create mode 100644 framework/commercetools/customer/index.ts
 create mode 100644 framework/commercetools/customer/use-customer.ts
 create mode 100644 framework/commercetools/fetcher.ts
 create mode 100644 framework/commercetools/index.tsx
 create mode 100644 framework/commercetools/lib/array-to-tree.ts
 create mode 100644 framework/commercetools/lib/get-slug.ts
 create mode 100644 framework/commercetools/lib/normalize.ts
 create mode 100644 framework/commercetools/next.config.js
 create mode 100644 framework/commercetools/product/index.ts
 create mode 100644 framework/commercetools/product/use-price.ts
 create mode 100644 framework/commercetools/product/use-search.ts
 create mode 100644 framework/commercetools/provider.ts
 create mode 100644 framework/commercetools/types/cart.ts
 create mode 100644 framework/commercetools/types/checkout.ts
 create mode 100644 framework/commercetools/types/common.ts
 create mode 100644 framework/commercetools/types/customer.ts
 create mode 100644 framework/commercetools/types/index.ts
 create mode 100644 framework/commercetools/types/login.ts
 create mode 100644 framework/commercetools/types/logout.ts
 create mode 100644 framework/commercetools/types/page.ts
 create mode 100644 framework/commercetools/types/product.ts
 create mode 100644 framework/commercetools/types/signup.ts
 create mode 100644 framework/commercetools/types/site.ts
 create mode 100644 framework/commercetools/types/wishlist.ts
 create mode 100644 framework/commercetools/utils/commercetools/index.ts
 create mode 100644 framework/commercetools/utils/queries/get-category.ts
 create mode 100644 framework/commercetools/utils/queries/get-product-query.ts
 create mode 100644 framework/commercetools/wishlist/index.ts
 create mode 100644 framework/commercetools/wishlist/use-add-item.ts
 create mode 100644 framework/commercetools/wishlist/use-remove-item.ts
 create mode 100644 framework/commercetools/wishlist/use-wishlist.ts

diff --git a/components/cart/CartSidebarView/CartSidebarView.tsx b/components/cart/CartSidebarView/CartSidebarView.tsx
index 326390327..e4f1ff951 100644
--- a/components/cart/CartSidebarView/CartSidebarView.tsx
+++ b/components/cart/CartSidebarView/CartSidebarView.tsx
@@ -4,7 +4,7 @@ import Link from 'next/link'
 import CartItem from '../CartItem'
 import s from './CartSidebarView.module.css'
 import { Button } from '@components/ui'
-import { UserNav } from '@components/common'
+// import { UserNav } from '@components/common'
 import { useUI } from '@components/ui/context'
 import { Bag, Cross, Check } from '@components/icons'
 import useCart from '@framework/cart/use-cart'
@@ -48,9 +48,7 @@ const CartSidebarView: FC = () => {
               <Cross className="h-6 w-6" />
             </button>
           </div>
-          <div className="space-y-1">
-            <UserNav />
-          </div>
+          <div className="space-y-1">{/* <UserNav /> */}</div>
         </div>
       </header>
 
diff --git a/components/common/Navbar/Navbar.tsx b/components/common/Navbar/Navbar.tsx
index 16088485f..d0ed2bdae 100644
--- a/components/common/Navbar/Navbar.tsx
+++ b/components/common/Navbar/Navbar.tsx
@@ -40,7 +40,7 @@ const Navbar: FC<NavbarProps> = ({ links }) => (
         </div>
 
         <div className="flex justify-end flex-1 space-x-8">
-          <UserNav />
+          {/* <UserNav /> */}
         </div>
       </div>
 
diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx
index a9385b2f0..070333d2f 100644
--- a/components/product/ProductView/ProductView.tsx
+++ b/components/product/ProductView/ProductView.tsx
@@ -7,7 +7,7 @@ import { Swatch, ProductSlider } from '@components/product'
 import { Button, Container, Text, useUI } from '@components/ui'
 import type { Product } from '@commerce/types/product'
 import usePrice from '@framework/product/use-price'
-import { useAddItem } from '@framework/cart'
+// import { useAddItem } from '@framework/cart'
 import { getVariant, SelectedOptions } from '../helpers'
 import WishlistButton from '@components/wishlist/WishlistButton'
 
@@ -20,7 +20,7 @@ interface Props {
 const ProductView: FC<Props> = ({ product }) => {
   // TODO: fix this missing argument issue
   /* @ts-ignore */
-  const addItem = useAddItem()
+  // const addItem = useAddItem()
   const { price } = usePrice({
     amount: product.price.value,
     baseAmount: product.price.retailPrice,
@@ -32,28 +32,28 @@ const ProductView: FC<Props> = ({ product }) => {
 
   useEffect(() => {
     // Selects the default option
-    product.variants[0].options?.forEach((v) => {
-      setChoices((choices) => ({
-        ...choices,
-        [v.displayName.toLowerCase()]: v.values[0].label.toLowerCase(),
-      }))
-    })
+    // product.variants[0].options?.forEach((v) => {
+    //   setChoices((choices) => ({
+    //     ...choices,
+    //     [v.displayName.toLowerCase()]: v.values[0].label.toLowerCase(),
+    //   }))
+    // })
   }, [])
 
   const variant = getVariant(product, choices)
 
   const addToCart = async () => {
-    setLoading(true)
-    try {
-      await addItem({
-        productId: String(product.id),
-        variantId: String(variant ? variant.id : product.variants[0].id),
-      })
-      openSidebar()
-      setLoading(false)
-    } catch (err) {
-      setLoading(false)
-    }
+    // setLoading(true)
+    // try {
+    //   await addItem({
+    //     productId: String(product.id),
+    //     variantId: String(variant ? variant.id : product.variants[0].id),
+    //   })
+    //   openSidebar()
+    //   setLoading(false)
+    // } catch (err) {
+    //   setLoading(false)
+    // }
   }
 
   return (
diff --git a/components/wishlist/WishlistButton/WishlistButton.tsx b/components/wishlist/WishlistButton/WishlistButton.tsx
index 5841de11e..ddb3b05cd 100644
--- a/components/wishlist/WishlistButton/WishlistButton.tsx
+++ b/components/wishlist/WishlistButton/WishlistButton.tsx
@@ -2,10 +2,10 @@ import React, { FC, useState } from 'react'
 import cn from 'classnames'
 import { useUI } from '@components/ui'
 import { Heart } from '@components/icons'
-import useAddItem from '@framework/wishlist/use-add-item'
-import useCustomer from '@framework/customer/use-customer'
-import useWishlist from '@framework/wishlist/use-wishlist'
-import useRemoveItem from '@framework/wishlist/use-remove-item'
+// import useAddItem from '@framework/wishlist/use-add-item'
+// import useCustomer from '@framework/customer/use-customer'
+// import useWishlist from '@framework/wishlist/use-wishlist'
+// import useRemoveItem from '@framework/wishlist/use-remove-item'
 import type { Product, ProductVariant } from '@commerce/types/product'
 
 type Props = {
@@ -19,10 +19,11 @@ const WishlistButton: FC<Props> = ({
   className,
   ...props
 }) => {
-  const { data } = useWishlist()
-  const addItem = useAddItem()
-  const removeItem = useRemoveItem()
-  const { data: customer } = useCustomer()
+  // const { data } = useWishlist()
+  const data = {}
+  // const addItem = useAddItem()
+  // const removeItem = useRemoveItem()
+  // const { data: customer } = useCustomer()
   const { openModal, setModalView } = useUI()
   const [loading, setLoading] = useState(false)
 
@@ -40,21 +41,21 @@ const WishlistButton: FC<Props> = ({
     if (loading) return
 
     // A login is required before adding an item to the wishlist
-    if (!customer) {
-      setModalView('LOGIN_VIEW')
-      return openModal()
-    }
+    // if (!customer) {
+    //   setModalView('LOGIN_VIEW')
+    //   return openModal()
+    // }
 
     setLoading(true)
 
     try {
       if (itemInWishlist) {
-        await removeItem({ id: itemInWishlist.id! })
+        // await removeItem({ id: itemInWishlist.id! })
       } else {
-        await addItem({
-          productId,
-          variantId: variant?.id!,
-        })
+        // await addItem({
+        //   productId,
+        //   variantId: variant?.id!,
+        // })
       }
 
       setLoading(false)
diff --git a/framework/commerce/config.js b/framework/commerce/config.js
index 48f0d526b..d95ed1c26 100644
--- a/framework/commerce/config.js
+++ b/framework/commerce/config.js
@@ -7,7 +7,13 @@ const fs = require('fs')
 const merge = require('deepmerge')
 const prettier = require('prettier')
 
-const PROVIDERS = ['bigcommerce', 'shopify', 'swell', 'vendure']
+const PROVIDERS = [
+  'bigcommerce',
+  'shopify',
+  'swell',
+  'vendure',
+  'commercetools',
+]
 
 function getProviderName() {
   return (
@@ -18,6 +24,8 @@ function getProviderName() {
       ? 'shopify'
       : process.env.NEXT_PUBLIC_SWELL_STORE_ID
       ? 'swell'
+      : process.env.CTP_API_URL
+      ? 'commercetools'
       : null)
   )
 }
diff --git a/framework/commercetools/.env.template b/framework/commercetools/.env.template
new file mode 100644
index 000000000..5694588d3
--- /dev/null
+++ b/framework/commercetools/.env.template
@@ -0,0 +1,8 @@
+COMMERCE_PROVIDER=commercetools
+
+CTP_PROJECT_KEY=
+CTP_CLIENT_SECRET=
+CTP_CLIENT_ID=
+CTP_AUTH_URL=
+CTP_API_URL=
+CTP_CONCURRENCY=
\ No newline at end of file
diff --git a/framework/commercetools/README.md b/framework/commercetools/README.md
new file mode 100644
index 000000000..c9d35760e
--- /dev/null
+++ b/framework/commercetools/README.md
@@ -0,0 +1,18 @@
+# Commercetools Provider
+
+To deploy you will need a commercetools account with an existing project.
+Then copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git):
+
+```bash
+cp framework/commercetools/.env.template .env.local
+```
+
+Then, set the environment variables in `.env.local` to match the ones from your project.
+
+## Contribute
+
+Our commitment to Open Source can be found [here](https://vercel.com/oss).
+
+If you find an issue with the provider or want a new feature, feel free to open a PR or [create a new issue](https://github.com/vercel/commerce/issues).
+
+## Troubleshoot
diff --git a/framework/commercetools/api/endpoints/catalog/products/get-products.ts b/framework/commercetools/api/endpoints/catalog/products/get-products.ts
new file mode 100644
index 000000000..987170bdb
--- /dev/null
+++ b/framework/commercetools/api/endpoints/catalog/products/get-products.ts
@@ -0,0 +1,54 @@
+import { ProductsEndpoint } from '.'
+import { normalizeProduct } from '@framework/lib/normalize'
+
+const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({
+  res,
+  body: { search, categoryId, brandId, sort },
+  config,
+}) => {
+  const queries: string[] = []
+  const isSearch = true
+  if (search) {
+    // TODO: TEC-264: Handle the locale properly
+    queries.push(`name.en: "${search}"`)
+  }
+  if (categoryId) {
+    queries.push(`categories.id: "${categoryId}"`)
+  }
+  if (brandId) {
+    queries.push(`variants.attributes.designer.key: "${brandId}"`)
+  }
+  let sorting
+  if (sort) {
+    sorting = getSortingValue(sort)
+  }
+
+  const query = {
+    filter: queries,
+    sort: sorting,
+  }
+
+  const data = await config.fetchProducts(query, isSearch)
+  const products = data.body.results
+
+  res.status(200).json({
+    data: {
+      found: data.body.total > 0,
+      products: products.map((item) => normalizeProduct(item)),
+    },
+  })
+}
+
+function getSortingValue(sort: string): string {
+  switch (sort) {
+    case 'price-asc':
+      return 'price asc'
+    case 'price-desc':
+      return 'price desc'
+    case 'latest-desc':
+    default:
+      return 'lastModifiedAt desc'
+  }
+}
+
+export default getProducts
diff --git a/framework/commercetools/api/endpoints/catalog/products/index.ts b/framework/commercetools/api/endpoints/catalog/products/index.ts
new file mode 100644
index 000000000..0438fba8f
--- /dev/null
+++ b/framework/commercetools/api/endpoints/catalog/products/index.ts
@@ -0,0 +1,18 @@
+import { GetAPISchema, createEndpoint } from '@commerce/api'
+import productsEndpoint from '@commerce/api/endpoints/catalog/products'
+import type { ProductsSchema } from '@commerce/types/product'
+import type { CommercetoolsAPI } from '@framework/api'
+import getProducts from './get-products'
+
+export type ProductsAPI = GetAPISchema<CommercetoolsAPI, ProductsSchema>
+
+export type ProductsEndpoint = ProductsAPI['endpoint']
+
+export const handlers: ProductsEndpoint['handlers'] = { getProducts }
+
+const productsApi = createEndpoint<ProductsAPI>({
+  handler: productsEndpoint,
+  handlers,
+})
+
+export default productsApi
diff --git a/framework/commercetools/api/index.ts b/framework/commercetools/api/index.ts
new file mode 100644
index 000000000..6b7b9fea4
--- /dev/null
+++ b/framework/commercetools/api/index.ts
@@ -0,0 +1,101 @@
+import type { RequestInit } from '@vercel/fetch'
+import {
+  CommerceAPI,
+  CommerceAPIConfig,
+  getCommerceApi as commerceApi,
+  GraphQLFetcherResult,
+  CommerceAPIFetchOptions,
+} from '@commerce/api'
+import fetchGraphql from '@framework/api/utils/fetch-graphql-api'
+import fetchProducts from '@framework/api/utils/fetch-products'
+import getProduct from '@framework/api/operations/get-product'
+import getAllProducts from '@framework/api/operations/get-all-products'
+import getAllProductPaths from '@framework/api/operations/get-all-product-paths'
+import getPage from '@framework/api/operations/get-page'
+import getAllPages from '@framework/api/operations/get-all-pages'
+import login from '@framework/api/operations/login'
+import getCustomerWishlist from '@framework/api/operations/get-customer-wishlist'
+import getSiteInfo from '@framework/api/operations/get-site-info'
+
+export interface CommercetoolsConfig extends CommerceAPIConfig {
+  locale: string
+  projectKey: string
+  clientId: string
+  clientSecret: string
+  host: string
+  oauthHost: string
+  concurrency: string | number
+  fetch<Data = any, Variables = any>(
+    query: string,
+    queryData?: CommerceAPIFetchOptions<Variables>,
+    fetchOptions?: RequestInit
+  ): Promise<GraphQLFetcherResult<Data>>
+  fetchProducts: typeof fetchProducts
+}
+
+const PROJECT_KEY = process.env.CTP_PROJECT_KEY || 'projectKey'
+const CLIENT_ID = process.env.CTP_CLIENT_ID || 'projectKey'
+const CLIENT_SECRET = process.env.CTP_CLIENT_SECRET || 'projectKey'
+const AUTH_URL = process.env.CTP_AUTH_URL || 'projectKey'
+const API_URL = process.env.CTP_API_URL || 'projectKey'
+const CONCURRENCY = process.env.CTP_CONCURRENCY || 0
+
+if (!API_URL) {
+  throw new Error(
+    `The environment variable CTP_API_URL is missing and it's required to access your store`
+  )
+}
+
+if (!PROJECT_KEY) {
+  throw new Error(
+    `The environment variable CTP_PROJECT_KEY is missing and it's required to access your store`
+  )
+}
+
+if (!AUTH_URL) {
+  throw new Error(
+    `The environment variables CTP_AUTH_URL have to be set in order to access your store`
+  )
+}
+
+const ONE_DAY = 60 * 60 * 24
+
+const config: CommercetoolsConfig = {
+  locale: '',
+  commerceUrl: '',
+  host: API_URL,
+  projectKey: PROJECT_KEY,
+  clientId: CLIENT_ID,
+  clientSecret: CLIENT_SECRET,
+  oauthHost: AUTH_URL,
+  concurrency: CONCURRENCY,
+  apiToken: '',
+  cartCookie: '',
+  cartCookieMaxAge: 0,
+  customerCookie: '',
+  fetch: fetchGraphql,
+  fetchProducts: fetchProducts,
+}
+
+const operations = {
+  getAllPages,
+  getPage,
+  getAllProductPaths,
+  getAllProducts,
+  getProduct,
+  getSiteInfo,
+  getCustomerWishlist,
+  login,
+}
+
+export const provider = { config, operations }
+
+export type Provider = typeof provider
+
+export type CommercetoolsAPI<P extends Provider = Provider> = CommerceAPI<P>
+
+export function getCommerceApi<P extends Provider>(
+  customProvider: P = provider as any
+): CommercetoolsAPI<P> {
+  return commerceApi(customProvider)
+}
diff --git a/framework/commercetools/api/operations/get-all-pages.ts b/framework/commercetools/api/operations/get-all-pages.ts
new file mode 100644
index 000000000..078157687
--- /dev/null
+++ b/framework/commercetools/api/operations/get-all-pages.ts
@@ -0,0 +1,40 @@
+import { CommercetoolsConfig, Provider } from '@framework/api'
+import { OperationContext } from '@commerce/api/operations'
+
+export type Page = any
+
+export type GetAllPagesResult<
+  T extends { pages: any[] } = { pages: Page[] }
+> = T
+
+export default function getAllPagesOperation({
+  commerce,
+}: OperationContext<Provider>) {
+  async function getAllPages(opts?: {
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  }): Promise<GetAllPagesResult>
+
+  async function getAllPages<T extends { pages: any[] }>(opts: {
+    url: string
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  }): Promise<GetAllPagesResult<T>>
+
+  async function getAllPages({
+    config: cfg,
+    preview,
+  }: {
+    url?: string
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  } = {}): Promise<GetAllPagesResult> {
+    const config = commerce.getConfig(cfg)
+
+    return {
+      pages: [],
+    }
+  }
+
+  return getAllPages
+}
diff --git a/framework/commercetools/api/operations/get-all-product-paths.ts b/framework/commercetools/api/operations/get-all-product-paths.ts
new file mode 100644
index 000000000..483aa5a45
--- /dev/null
+++ b/framework/commercetools/api/operations/get-all-product-paths.ts
@@ -0,0 +1,50 @@
+import { OperationContext, OperationOptions } from '@commerce/api/operations'
+import { GetAllProductPathsOperation } from '@commerce/types/product'
+import { CommercetoolsConfig, Provider } from '@framework/api'
+
+export type GetAllProductPathsResult = {
+  products: Array<{ node: { path: string } }>
+}
+
+export default function getAllProductPathsOperation({
+  commerce,
+}: OperationContext<Provider>) {
+  async function getAllProductPaths<
+    T extends GetAllProductPathsOperation
+  >(opts?: {
+    variables?: T['variables']
+    config?: CommercetoolsConfig
+  }): Promise<T['data']>
+
+  async function getAllProductPaths<T extends GetAllProductPathsOperation>(
+    opts: {
+      variables?: T['variables']
+      config?: CommercetoolsConfig
+    } & OperationOptions
+  ): Promise<T['data']>
+
+  async function getAllProductPaths<T extends GetAllProductPathsOperation>({
+    query,
+    variables,
+    config: cfg,
+  }: {
+    query?: string
+    variables?: T['variables']
+    config?: CommercetoolsConfig
+  } = {}): Promise<T['data']> {
+    const config = commerce.getConfig(cfg)
+    // RecursivePartial forces the method to check for every prop in the data, which is
+    // required in case there's a custom `query`
+    const data: any = await config.fetchProducts(query)
+    const paths = data.body.results.map((prod: any) => ({
+      // TODO: TEC-264: Handle the locale properly
+      path: `/${prod.slug.en}`,
+    }))
+
+    return {
+      products: paths,
+    }
+  }
+
+  return getAllProductPaths
+}
diff --git a/framework/commercetools/api/operations/get-all-products.ts b/framework/commercetools/api/operations/get-all-products.ts
new file mode 100644
index 000000000..04bc2da5d
--- /dev/null
+++ b/framework/commercetools/api/operations/get-all-products.ts
@@ -0,0 +1,31 @@
+import { Product } from '@commerce/types/product'
+import { Provider, CommercetoolsConfig } from '@framework/api'
+import { normalizeProduct } from '@framework/lib/normalize'
+import { OperationContext } from '@commerce/api/operations'
+
+export default function getAllProductsOperation({
+  commerce,
+}: OperationContext<Provider>) {
+  async function getAllProducts(opts?: {
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  }): Promise<{ products: Product[] }>
+
+  async function getAllProducts({
+    config: cfg,
+  }: {
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  } = {}): Promise<{ products: Product[] | any[] }> {
+    const config = commerce.getConfig(cfg)
+    const data: any = await config.fetchProducts()
+
+    const prods = data.body.results.map((prod: any) => normalizeProduct(prod))
+
+    return {
+      products: prods,
+    }
+  }
+
+  return getAllProducts
+}
diff --git a/framework/commercetools/api/operations/get-customer-wishlist.ts b/framework/commercetools/api/operations/get-customer-wishlist.ts
new file mode 100644
index 000000000..4ca0d879d
--- /dev/null
+++ b/framework/commercetools/api/operations/get-customer-wishlist.ts
@@ -0,0 +1,23 @@
+import { OperationContext } from '@commerce/api/operations'
+import { Provider, CommercetoolsConfig } from '@framework/api'
+
+export default function getCustomerWishlistOperation({
+  commerce,
+}: OperationContext<Provider>) {
+  async function getCustomerWishlist({
+    config: cfg,
+    variables,
+    includeProducts,
+  }: {
+    url?: string
+    variables: any
+    config?: Partial<CommercetoolsConfig>
+    includeProducts?: boolean
+  }): Promise<any> {
+    // Not implemented yet
+    const config = commerce.getConfig(cfg)
+    return { wishlist: {} }
+  }
+
+  return getCustomerWishlist
+}
diff --git a/framework/commercetools/api/operations/get-page.ts b/framework/commercetools/api/operations/get-page.ts
new file mode 100644
index 000000000..68d32125f
--- /dev/null
+++ b/framework/commercetools/api/operations/get-page.ts
@@ -0,0 +1,45 @@
+import { CommercetoolsConfig, Provider } from '@framework/api'
+import { OperationContext } from '@commerce/api/operations'
+
+export type Page = any
+
+export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
+
+export type PageVariables = {
+  id: number
+}
+
+export default function getPageOperation({
+  commerce,
+}: OperationContext<Provider>) {
+  async function getPage(opts: {
+    url?: string
+    variables: PageVariables
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  }): Promise<GetPageResult>
+
+  async function getPage<T extends { page?: any }, V = any>(opts: {
+    url: string
+    variables: V
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  }): Promise<GetPageResult<T>>
+
+  async function getPage({
+    url,
+    variables,
+    config: cfg,
+    preview,
+  }: {
+    url?: string
+    variables: PageVariables
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  }): Promise<GetPageResult> {
+    const config = commerce.getConfig(cfg)
+    return {}
+  }
+
+  return getPage
+}
diff --git a/framework/commercetools/api/operations/get-product.ts b/framework/commercetools/api/operations/get-product.ts
new file mode 100644
index 000000000..589fb4dd3
--- /dev/null
+++ b/framework/commercetools/api/operations/get-product.ts
@@ -0,0 +1,38 @@
+import { Product } from '@commerce/types/product'
+import { OperationContext } from '@commerce/api/operations'
+import { Provider, CommercetoolsConfig } from '@framework/api'
+import { normalizeProduct } from '@framework/lib/normalize'
+
+export default function getProductOperation({
+  commerce,
+}: OperationContext<Provider>) {
+  async function getProduct({
+    variables,
+    config: cfg,
+  }: {
+    variables: {
+      slug?: string
+      id?: string
+      locale?: string
+    }
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  }): Promise<Product | {} | any> {
+    const config = commerce.getConfig(cfg)
+
+    // TODO: TEC-264: Handle the locale properly
+    const queryArg = {
+      where: `slug(en="${variables.slug}")`,
+    }
+    const projection = await config.fetchProducts(queryArg)
+    const product = projection.body.results[0]
+
+    if (product) {
+      return { product: normalizeProduct(product) }
+    }
+
+    return {}
+  }
+
+  return getProduct
+}
diff --git a/framework/commercetools/api/operations/get-site-info.ts b/framework/commercetools/api/operations/get-site-info.ts
new file mode 100644
index 000000000..d1a113fdf
--- /dev/null
+++ b/framework/commercetools/api/operations/get-site-info.ts
@@ -0,0 +1,39 @@
+import { Provider, CommercetoolsConfig } from '@framework/api'
+import { OperationContext } from '@commerce/api/operations'
+import { Category } from '@commerce/types/site'
+import { getAllCategoriesAndBrandsQuery } from '@framework/utils/queries/get-category'
+import { normalizeSite } from '@framework/lib/normalize'
+
+export type GetSiteInfoResult<
+  T extends { categories: any[]; brands: any[] } = {
+    categories: Category[]
+    brands: any[]
+  }
+> = T
+
+export default function getSiteInfoOperation({
+  commerce,
+}: OperationContext<Provider>) {
+  async function getSiteInfo({
+    query = getAllCategoriesAndBrandsQuery,
+    variables,
+    config: cfg,
+  }: {
+    query?: string
+    variables?: any
+    config?: Partial<CommercetoolsConfig>
+    preview?: boolean
+  } = {}): Promise<GetSiteInfoResult> {
+    const config = commerce.getConfig(cfg)
+    const {
+      data: { categories, productTypes },
+    }: any = await config.fetch(query)
+    const ctCategories = categories.results
+    const ctBrands =
+      productTypes?.results[0]?.attributeDefinitions?.results[0]?.type?.values
+        ?.results
+    return normalizeSite(ctCategories, ctBrands)
+  }
+
+  return getSiteInfo
+}
diff --git a/framework/commercetools/api/operations/login.ts b/framework/commercetools/api/operations/login.ts
new file mode 100644
index 000000000..501890992
--- /dev/null
+++ b/framework/commercetools/api/operations/login.ts
@@ -0,0 +1,43 @@
+import type { ServerResponse } from 'http'
+import type {
+  OperationContext,
+  OperationOptions,
+} from '@commerce/api/operations'
+import { Provider, CommercetoolsConfig } from '@framework/api'
+
+export default function loginOperation({
+  commerce,
+}: OperationContext<Provider>) {
+  async function login<T extends { variables: any; data: any }>(opts: {
+    variables: T['variables']
+    config?: Partial<CommercetoolsConfig>
+    res: ServerResponse
+  }): Promise<T['data']>
+
+  async function login<T extends { variables: any; data: any }>(
+    opts: {
+      variables: T['variables']
+      config?: Partial<CommercetoolsConfig>
+      res: ServerResponse
+    } & OperationOptions
+  ): Promise<T['data']>
+
+  async function login<T extends { variables: any; data: any }>({
+    query = '',
+    variables,
+    res: response,
+    config: cfg,
+  }: {
+    query?: string
+    variables: T['variables']
+    res: ServerResponse
+    config?: Partial<CommercetoolsConfig>
+  }): Promise<T['data']> {
+    const config = commerce.getConfig(cfg)
+    return {
+      result: '',
+    }
+  }
+
+  return login
+}
diff --git a/framework/commercetools/api/utils/errors.ts b/framework/commercetools/api/utils/errors.ts
new file mode 100644
index 000000000..49839fa69
--- /dev/null
+++ b/framework/commercetools/api/utils/errors.ts
@@ -0,0 +1,25 @@
+import type { Response } from '@vercel/fetch'
+
+// Used for GraphQL errors
+export class CommercetoolsGraphQLError extends Error {}
+
+export class CommercetoolsApiError extends Error {
+  status: number
+  res: Response
+  data: any
+
+  constructor(msg: string, res: Response, data?: any) {
+    super(msg)
+    this.name = 'CommercetoolsApiError'
+    this.status = res.status
+    this.res = res
+    this.data = data
+  }
+}
+
+export class CommercetoolsNetworkError extends Error {
+  constructor(msg: string) {
+    super(msg)
+    this.name = 'CommercetoolsNetworkError'
+  }
+}
diff --git a/framework/commercetools/api/utils/fetch-graphql-api.ts b/framework/commercetools/api/utils/fetch-graphql-api.ts
new file mode 100644
index 000000000..579596aac
--- /dev/null
+++ b/framework/commercetools/api/utils/fetch-graphql-api.ts
@@ -0,0 +1,37 @@
+import { FetcherError } from '@commerce/utils/errors'
+import type { GraphQLFetcher } from '@commerce/api'
+import Commercetools from '@framework/utils/commercetools'
+import { provider } from '@framework/api'
+
+const fetchGraphqlApi: GraphQLFetcher = async (
+  query: string,
+  { variables } = {}
+) => {
+  const { config } = provider
+  const commercetools = Commercetools({
+    clientId: config.clientId,
+    clientSecret: config.clientSecret,
+    projectKey: config.projectKey,
+    host: config.host,
+    oauthHost: config.oauthHost,
+    concurrency: config.concurrency,
+  })
+  const { requestExecute } = commercetools
+  try {
+    const result = await requestExecute
+      .graphql()
+      .post({
+        body: {
+          query,
+          variables,
+        },
+      })
+      .execute()
+
+    return result.body
+  } catch (err) {
+    throw err
+  }
+}
+
+export default fetchGraphqlApi
diff --git a/framework/commercetools/api/utils/fetch-products.ts b/framework/commercetools/api/utils/fetch-products.ts
new file mode 100644
index 000000000..56501199b
--- /dev/null
+++ b/framework/commercetools/api/utils/fetch-products.ts
@@ -0,0 +1,33 @@
+import Commercetools from '@framework/utils/commercetools'
+import { provider } from '@framework/api'
+
+const fetchProducts = async (query?: any, isSearch?: boolean) => {
+  const { config } = provider
+  const commercetools = Commercetools({
+    clientId: config.clientId,
+    clientSecret: config.clientSecret,
+    projectKey: config.projectKey,
+    host: config.host,
+    oauthHost: config.oauthHost,
+    concurrency: config.concurrency,
+  })
+  const { requestExecute } = commercetools
+  try {
+    if (isSearch) {
+      return await requestExecute
+        .productProjections()
+        .search()
+        .get({ queryArgs: query })
+        .execute()
+    } else {
+      return await requestExecute
+        .productProjections()
+        .get({ queryArgs: query })
+        .execute()
+    }
+  } catch (err) {
+    throw err
+  }
+}
+
+export default fetchProducts
diff --git a/framework/commercetools/api/utils/fetch.ts b/framework/commercetools/api/utils/fetch.ts
new file mode 100644
index 000000000..9d9fff3ed
--- /dev/null
+++ b/framework/commercetools/api/utils/fetch.ts
@@ -0,0 +1,3 @@
+import zeitFetch from '@vercel/fetch'
+
+export default zeitFetch()
diff --git a/framework/commercetools/auth/index.ts b/framework/commercetools/auth/index.ts
new file mode 100644
index 000000000..6e321b17f
--- /dev/null
+++ b/framework/commercetools/auth/index.ts
@@ -0,0 +1,3 @@
+// export { default as useLogin } from './use-login'
+// export { default as useLogout } from './use-logout'
+// export { default as useSignup } from './use-signup'
diff --git a/framework/commercetools/auth/use-login.ts b/framework/commercetools/auth/use-login.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/auth/use-logout.ts b/framework/commercetools/auth/use-logout.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/auth/use-signup.ts b/framework/commercetools/auth/use-signup.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/cart/index.ts b/framework/commercetools/cart/index.ts
new file mode 100644
index 000000000..0782570b1
--- /dev/null
+++ b/framework/commercetools/cart/index.ts
@@ -0,0 +1,4 @@
+// export { default as useAddItem } from './use-add-item'
+// export { default as useCart } from './use-cart'
+// export { default as useRemoveItem } from './use-remove-item'
+// export { default as useUpdateItem } from './use-update-item'
diff --git a/framework/commercetools/cart/use-add-item.ts b/framework/commercetools/cart/use-add-item.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/cart/use-cart.ts b/framework/commercetools/cart/use-cart.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/cart/use-remove-item.ts b/framework/commercetools/cart/use-remove-item.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/cart/use-update-item.ts b/framework/commercetools/cart/use-update-item.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/commerce.config.json b/framework/commercetools/commerce.config.json
new file mode 100644
index 000000000..b40393a75
--- /dev/null
+++ b/framework/commercetools/commerce.config.json
@@ -0,0 +1,9 @@
+{
+  "provider": "commercetools",
+  "features": {
+    "wishlist": false,
+    "customer": false,
+    "cart": false,
+    "auth": false
+  }
+}
diff --git a/framework/commercetools/customer/index.ts b/framework/commercetools/customer/index.ts
new file mode 100644
index 000000000..89f003631
--- /dev/null
+++ b/framework/commercetools/customer/index.ts
@@ -0,0 +1 @@
+// export { default as useCustomer } from './use-customer'
diff --git a/framework/commercetools/customer/use-customer.ts b/framework/commercetools/customer/use-customer.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/fetcher.ts b/framework/commercetools/fetcher.ts
new file mode 100644
index 000000000..f8ca0c578
--- /dev/null
+++ b/framework/commercetools/fetcher.ts
@@ -0,0 +1,41 @@
+import { FetcherError } from '@commerce/utils/errors'
+import type { Fetcher } from '@commerce/utils/types'
+
+async function getText(res: Response) {
+  try {
+    return (await res.text()) || res.statusText
+  } catch (error) {
+    return res.statusText
+  }
+}
+
+async function getError(res: Response) {
+  if (res.headers.get('Content-Type')?.includes('application/json')) {
+    const data = await res.json()
+    return new FetcherError({ errors: data.errors, status: res.status })
+  }
+  return new FetcherError({ message: await getText(res), status: res.status })
+}
+
+const fetcher: Fetcher = async ({
+  url,
+  method = 'GET',
+  variables,
+  body: bodyObj,
+}) => {
+  const hasBody = Boolean(variables || bodyObj)
+  const body = hasBody
+    ? JSON.stringify(variables ? { variables } : bodyObj)
+    : undefined
+  const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
+  const res = await fetch(url!, { method, body, headers })
+
+  if (res.ok) {
+    const { data } = await res.json()
+    return data
+  }
+
+  throw await getError(res)
+}
+
+export default fetcher
diff --git a/framework/commercetools/index.tsx b/framework/commercetools/index.tsx
new file mode 100644
index 000000000..87c7bdae4
--- /dev/null
+++ b/framework/commercetools/index.tsx
@@ -0,0 +1,38 @@
+import type { ReactNode } from 'react'
+import {
+  CommerceConfig,
+  CommerceProvider as CoreCommerceProvider,
+  useCommerce as useCoreCommerce,
+} from '@commerce'
+import {
+  commercetoolsProvider,
+  CommercetoolsProvider,
+} from '@framework/provider'
+
+export { commercetoolsProvider }
+export type { CommercetoolsProvider }
+
+export const commercetoolsConfig: CommerceConfig = {
+  locale: 'en-US',
+  cartCookie: '',
+}
+
+export type CommercetoolsConfig = Partial<CommerceConfig>
+
+export type CommercetoolsProps = {
+  children?: ReactNode
+  locale: string
+} & CommercetoolsConfig
+
+export function CommerceProvider({ children, ...config }: CommercetoolsProps) {
+  return (
+    <CoreCommerceProvider
+      provider={commercetoolsProvider}
+      config={{ ...commercetoolsConfig, ...config }}
+    >
+      {children}
+    </CoreCommerceProvider>
+  )
+}
+
+export const useCommerce = () => useCoreCommerce<CommercetoolsProvider>()
diff --git a/framework/commercetools/lib/array-to-tree.ts b/framework/commercetools/lib/array-to-tree.ts
new file mode 100644
index 000000000..f220a4cbe
--- /dev/null
+++ b/framework/commercetools/lib/array-to-tree.ts
@@ -0,0 +1,70 @@
+export type HasParent = { id: string; parent?: { id: string } | null }
+export type TreeNode<T extends HasParent> = T & {
+  children: Array<TreeNode<T>>
+  expanded: boolean
+}
+export type RootNode<T extends HasParent> = {
+  id?: string
+  children: Array<TreeNode<T>>
+}
+
+export function arrayToTree<T extends HasParent>(
+  nodes: T[],
+  currentState?: RootNode<T>
+): RootNode<T> {
+  const topLevelNodes: Array<TreeNode<T>> = []
+  const mappedArr: { [id: string]: TreeNode<T> } = {}
+  const currentStateMap = treeToMap(currentState)
+
+  // First map the nodes of the array to an object -> create a hash table.
+  for (const node of nodes) {
+    mappedArr[node.id] = { ...(node as any), children: [] }
+  }
+
+  for (const id of nodes.map((n) => n.id)) {
+    if (mappedArr.hasOwnProperty(id)) {
+      const mappedElem = mappedArr[id]
+      mappedElem.expanded = currentStateMap.get(id)?.expanded ?? false
+      const parent = mappedElem.parent
+      if (!parent) {
+        continue
+      }
+      // If the element is not at the root level, add it to its parent array of children.
+      const parentIsRoot = !mappedArr[parent.id]
+      if (!parentIsRoot) {
+        if (mappedArr[parent.id]) {
+          mappedArr[parent.id].children.push(mappedElem)
+        } else {
+          mappedArr[parent.id] = { children: [mappedElem] } as any
+        }
+      } else {
+        topLevelNodes.push(mappedElem)
+      }
+    }
+  }
+  // // tslint:disable-next-line:no-non-null-assertion
+  // const rootId = topLevelNodes.length ? topLevelNodes[0].id : undefined
+  // const children = topLevelNodes.length ? topLevelNodes[0].children : []
+  // return { id: "root", children: topLevelNodes }
+
+  return { children: topLevelNodes }
+}
+
+/**
+ * Converts an existing tree (as generated by the arrayToTree function) into a flat
+ * Map. This is used to persist certain states (e.g. `expanded`) when re-building the
+ * tree.
+ */
+function treeToMap<T extends HasParent>(
+  tree?: RootNode<T>
+): Map<string, TreeNode<T>> {
+  const nodeMap = new Map<string, TreeNode<T>>()
+  function visit(node: TreeNode<T>) {
+    nodeMap.set(node.id, node)
+    node.children.forEach(visit)
+  }
+  if (tree) {
+    visit(tree as TreeNode<T>)
+  }
+  return nodeMap
+}
diff --git a/framework/commercetools/lib/get-slug.ts b/framework/commercetools/lib/get-slug.ts
new file mode 100644
index 000000000..329c5a27e
--- /dev/null
+++ b/framework/commercetools/lib/get-slug.ts
@@ -0,0 +1,5 @@
+// Remove trailing and leading slash, usually included in nodes
+// returned by the BigCommerce API
+const getSlug = (path: string) => path.replace(/^\/|\/$/g, '')
+
+export default getSlug
diff --git a/framework/commercetools/lib/normalize.ts b/framework/commercetools/lib/normalize.ts
new file mode 100644
index 000000000..da3118adf
--- /dev/null
+++ b/framework/commercetools/lib/normalize.ts
@@ -0,0 +1,185 @@
+import type {
+  CommercetoolsProduct,
+  Product,
+  ProductVariant,
+  CommercetoolsProductVariant,
+  CommerceToolsProductPrice,
+  ProductPrice,
+} from '@framework/types/product'
+import type {
+  Cart,
+  CommercetoolsCart,
+  CommercetoolsLineItems,
+  LineItem,
+} from '@framework/types/cart'
+
+import type {
+  CommercetoolsBrands,
+  CommercetoolsCategory,
+  Category,
+  Brand,
+} from '@framework/types/site'
+
+import { arrayToTree } from '@framework/lib/array-to-tree'
+
+function normalizeVariants(
+  variants: CommercetoolsProductVariant[],
+  published: boolean
+): ProductVariant[] {
+  return variants.map((variant) => {
+    return {
+      id: variant.id,
+      options: [],
+      availableForSale: published,
+    }
+  })
+}
+
+function normalizePrice(price: CommerceToolsProductPrice): ProductPrice {
+  const value =
+    price.discounted && price.discounted.value
+      ? price.discounted.value.centAmount
+      : price.value.centAmount
+  return {
+    value: value / 100,
+    currencyCode: price.value.currencyCode,
+    retailPrice: 0,
+    salePrice: 0,
+    listPrice: 0,
+    extendedListPrice: 0,
+    extendedSalePrice: 0,
+  }
+}
+
+export function normalizeProduct(data: CommercetoolsProduct): Product {
+  return {
+    id: data.id,
+    name: data.name.en,
+    description:
+      data.description && data.description.en
+        ? data.description.en
+        : 'No description',
+    slug: data.slug.en,
+    path: data.slug.en,
+    images: data.masterVariant.images,
+    variants: normalizeVariants(data.variants, data.published),
+    options: [],
+    price: normalizePrice(
+      data.masterVariant.price
+        ? data.masterVariant.price
+        : data.masterVariant.prices[0]
+    ),
+    sku: data.masterVariant.sku,
+  }
+}
+
+function convertTaxMode(data: CommercetoolsCart): boolean {
+  return data && data.taxMode && data.taxMode === 'Disabled'
+    ? false
+    : data && data.taxMode
+    ? true
+    : false
+}
+export function normalizeCart(data: CommercetoolsCart): Cart {
+  const totalPrice =
+    data.taxedPrice &&
+    data.taxedPrice.totalGross &&
+    data.taxedPrice.totalGross.centAmount
+      ? data.taxedPrice.totalGross.centAmount / 100
+      : data.totalPrice.centAmount / 100
+  return {
+    id: data.id,
+    customerId: data.customerId,
+    email: data.customerEmail,
+    createdAt: data.createdAt,
+    currency: { code: data.totalPrice.currencyCode },
+    taxesIncluded: convertTaxMode(data),
+    lineItems: data.lineItems.map((item) => normalizeLineItem(item)),
+    lineItemsSubtotalPrice: 0,
+    subtotalPrice: 0,
+    totalPrice,
+    discounts: [],
+  }
+}
+
+function normalizeLineItem(item: CommercetoolsLineItems): LineItem {
+  const price =
+    item.price && item.price.value && item.price.value.centAmount
+      ? item.price.value.centAmount
+      : item.variant.prices[0].value.centAmount
+  return {
+    id: item.id,
+    variantId: item.variant.id,
+    productId: item.productId,
+    name: item.name,
+    quantity: item.quantity,
+    variant: {
+      id: item.variant.id,
+      sku: item.variant.sku,
+      name: item.variant.key,
+      image: {
+        url:
+          item.variant.images &&
+          item.variant.images[0] &&
+          item.variant.images[0].url
+            ? item.variant.images[0].url
+            : '',
+        width:
+          item.variant.images &&
+          item.variant.images[0] &&
+          item.variant.images[0].dimensions &&
+          item.variant.images[0].dimensions.w
+            ? item.variant.images[0].dimensions.w
+            : undefined,
+        height:
+          item.variant.images &&
+          item.variant.images[0] &&
+          item.variant.images[0].dimensions &&
+          item.variant.images[0].dimensions.h
+            ? item.variant.images[0].dimensions.h
+            : undefined,
+      },
+      requiresShipping: false,
+      price: price / 100,
+      listPrice: 0,
+    },
+    path: item.productSlug,
+    discounts: [],
+  }
+}
+
+type Site = { categories: any[]; brands: Brand[] }
+
+export function normalizeSite(
+  ctCategories: CommercetoolsCategory[],
+  ctBrands: CommercetoolsBrands[]
+): Site {
+  const categories = ctCategories.map((ctCategory) => {
+    return {
+      id: ctCategory.id,
+      // TODO: TEC-264 we need to handle locale properly
+      name: ctCategory.name,
+      slug: ctCategory.slug,
+      path: ctCategory.slug,
+      //add a random parentId to add in children array
+      parent: ctCategory.parent ? ctCategory.parent : { id: 'idRoot' },
+    }
+  })
+
+  const treeCategories = arrayToTree(categories).children
+
+  const brands = ctBrands.map((ctBrand) => {
+    return {
+      node: {
+        name: ctBrand.label,
+        path: `brands/${ctBrand.key}`,
+        entityId: ctBrand.key,
+      },
+    }
+  })
+
+  return {
+    categories: treeCategories,
+    brands,
+  }
+}
diff --git a/framework/commercetools/next.config.js b/framework/commercetools/next.config.js
new file mode 100644
index 000000000..06887bece
--- /dev/null
+++ b/framework/commercetools/next.config.js
@@ -0,0 +1,8 @@
+const commerce = require('./commerce.config.json')
+
+module.exports = {
+  commerce,
+  images: {
+    domains: ['s3-eu-west-1.amazonaws.com'],
+  },
+}
diff --git a/framework/commercetools/product/index.ts b/framework/commercetools/product/index.ts
new file mode 100644
index 000000000..426a3edcd
--- /dev/null
+++ b/framework/commercetools/product/index.ts
@@ -0,0 +1,2 @@
+export { default as usePrice } from './use-price'
+export { default as useSearch } from './use-search'
diff --git a/framework/commercetools/product/use-price.ts b/framework/commercetools/product/use-price.ts
new file mode 100644
index 000000000..0174faf5e
--- /dev/null
+++ b/framework/commercetools/product/use-price.ts
@@ -0,0 +1,2 @@
+export * from '@commerce/product/use-price'
+export { default } from '@commerce/product/use-price'
diff --git a/framework/commercetools/product/use-search.ts b/framework/commercetools/product/use-search.ts
new file mode 100644
index 000000000..873974bb5
--- /dev/null
+++ b/framework/commercetools/product/use-search.ts
@@ -0,0 +1,54 @@
+import { SWRHook } from '@commerce/utils/types'
+import useSearch, { UseSearch } from '@commerce/product/use-search'
+import { Product } from '@commerce/types/product'
+import type { SearchProductsHook } from '../../commerce/types/product'
+
+export default useSearch as UseSearch<typeof handler>
+
+export type SearchProductsInput = {
+  search?: string
+  categoryId?: string
+  brandId?: string
+  sort?: string
+  locale?: string
+}
+
+export type SearchProductsData = {
+  products: Product[]
+  found: boolean
+}
+
+export const handler: SWRHook<SearchProductsHook> = {
+  fetchOptions: {
+    url: 'api/catalog/products',
+    method: 'GET',
+  },
+  async fetcher({ input, options, fetch }) {
+    const { search, categoryId, brandId, sort } = input
+    const url = new URL(options.url!, 'http://a')
+
+    if (search) url.searchParams.set('search', search)
+    if (categoryId) url.searchParams.set('categoryId', String(categoryId))
+    if (brandId) url.searchParams.set('brandId', String(brandId))
+    if (sort) url.searchParams.set('sort', sort)
+
+    return await fetch({
+      url: url.pathname + url.search,
+      method: options.method,
+    })
+  },
+  useHook: ({ useData }) => (input = {}) => {
+    return useData({
+      input: [
+        ['search', input.search],
+        ['categoryId', input.categoryId],
+        ['brandId', input.brandId],
+        ['sort', input.sort],
+      ],
+      swrOptions: {
+        revalidateOnFocus: false,
+        ...input.swrOptions,
+      },
+    })
+  },
+}
diff --git a/framework/commercetools/provider.ts b/framework/commercetools/provider.ts
new file mode 100644
index 000000000..525fd7549
--- /dev/null
+++ b/framework/commercetools/provider.ts
@@ -0,0 +1,24 @@
+import { Provider } from '@commerce'
+// import { handler as useCart } from './cart/use-cart'
+// import { handler as useAddItem } from './cart/use-add-item'
+// import { handler as useUpdateItem } from './cart/use-update-item'
+// import { handler as useRemoveItem } from './cart/use-remove-item'
+// import { handler as useCustomer } from './customer/use-customer'
+import { handler as useSearch } from './product/use-search'
+// import { handler as useLogin } from './auth/use-login'
+// import { handler as useLogout } from './auth/use-logout'
+// import { handler as useSignup } from './auth/use-signup'
+import fetcher from './fetcher'
+
+// Export a provider with the CommerceHooks
+export const commercetoolsProvider: Provider = {
+  locale: 'en-us',
+  cartCookie: 'session',
+  fetcher,
+  // cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
+  // customer: { useCustomer },
+  products: { useSearch },
+  // auth: { useLogin, useLogout, useSignup }
+}
+
+export type CommercetoolsProvider = typeof commercetoolsProvider
diff --git a/framework/commercetools/types/cart.ts b/framework/commercetools/types/cart.ts
new file mode 100644
index 000000000..ef27465f2
--- /dev/null
+++ b/framework/commercetools/types/cart.ts
@@ -0,0 +1,103 @@
+import * as Core from '@commerce/types/cart'
+import * as Products from './product'
+export * from '@commerce/types/cart'
+// TODO: this type should match:
+// https://developer.bigcommerce.com/api-reference/cart-checkout/server-server-cart-api/cart/getacart#responses
+export type CommercetoolsCart = {
+  id: string
+  version: number
+  customerId: string
+  customerEmail: string
+  createdAt: string
+  lastModifiedAt: string
+  lineItems: CommercetoolsLineItems[]
+  totalPrice: {
+    currencyCode: string
+    centAmount: number
+  }
+  cartState: string
+  inventoryMode: string
+  taxMode: string
+  taxRoundingMode: string
+  taxedPrice?: TaxedItemPrice
+  discountCodes: discountCodes[]
+}
+export type TaxedItemPrice = {
+  totalNet: Products.Money
+  totalGross: Products.Money
+}
+export type CommercetoolsLineItems = {
+  id: string
+  productId: string
+  productSlug: Products.LocalString
+  name: {
+    en: string
+  }
+  variant: Products.CommercetoolsProductVariant
+  price: Products.CommerceToolsProductPrice
+  totalPrice: totalPrice
+  quantity: number
+  state: {
+    quantity: number
+    state: {
+      id: string
+      key: string
+      version: number
+      createdAt: string
+      lastModifiedAt: string
+    }
+  }
+  priceMode: string
+}
+export type discountCodes = {
+  discountCode: {
+    id: string
+    version: number
+    createdAt: string
+    lastModifiedAt: string
+    code: string
+    cartDiscounts: {
+      id: string
+      version: number
+      createdAt: string
+      lastModifiedAt: string
+      name: string
+      isActive: boolean
+    }
+  }
+}
+export type totalPrice = {
+  currencyCode: string
+  centAmount: number
+}
+/**
+ * Extend core cart types
+ */
+export type Cart = Core.Cart & {
+  lineItems: Core.LineItem[]
+}
+export type LineItem = Core.LineItem
+export type OptionSelections = {
+  option_id: number
+  option_value: number | string
+}
+export type CartItemBody = Core.CartItemBody & {
+  productId: string // The product id is always required for BC
+  optionSelections?: OptionSelections
+}
+export type CartTypes = {
+  cart: Cart
+  item: Core.LineItem
+  itemBody: CartItemBody
+}
+export type CartHooks = Core.CartHooks<CartTypes>
+export type GetCartHook = CartHooks['getCart']
+export type AddItemHook = CartHooks['addItem']
+export type UpdateItemHook = CartHooks['updateItem']
+export type RemoveItemHook = CartHooks['removeItem']
+export type CartSchema = Core.CartSchema<CartTypes>
+export type CartHandlers = Core.CartHandlers<CartTypes>
+export type GetCartHandler = CartHandlers['getCart']
+export type AddItemHandler = CartHandlers['addItem']
+export type UpdateItemHandler = CartHandlers['updateItem']
+export type RemoveItemHandler = CartHandlers['removeItem']
diff --git a/framework/commercetools/types/checkout.ts b/framework/commercetools/types/checkout.ts
new file mode 100644
index 000000000..4e2412ef6
--- /dev/null
+++ b/framework/commercetools/types/checkout.ts
@@ -0,0 +1 @@
+export * from '@commerce/types/checkout'
diff --git a/framework/commercetools/types/common.ts b/framework/commercetools/types/common.ts
new file mode 100644
index 000000000..b52c33a4d
--- /dev/null
+++ b/framework/commercetools/types/common.ts
@@ -0,0 +1 @@
+export * from '@commerce/types/common'
diff --git a/framework/commercetools/types/customer.ts b/framework/commercetools/types/customer.ts
new file mode 100644
index 000000000..75cee4531
--- /dev/null
+++ b/framework/commercetools/types/customer.ts
@@ -0,0 +1,20 @@
+import * as Core from '@commerce/types/customer'
+
+export * from '@commerce/types/customer'
+
+export type Customers = {
+  id: string
+  version: number
+  createdAt: string
+  lastModifiedAt: string
+  email: string
+  password: string
+  isEmailVerified: boolean
+}
+
+// get Customer
+export type GetCustomerById = {
+  id: string
+}
+
+export type CustomerSchema = Core.CustomerSchema
diff --git a/framework/commercetools/types/index.ts b/framework/commercetools/types/index.ts
new file mode 100644
index 000000000..7ab0b7f64
--- /dev/null
+++ b/framework/commercetools/types/index.ts
@@ -0,0 +1,25 @@
+import * as Cart from './cart'
+import * as Checkout from './checkout'
+import * as Common from './common'
+import * as Customer from './customer'
+import * as Login from './login'
+import * as Logout from './logout'
+import * as Page from './page'
+import * as Product from './product'
+import * as Signup from './signup'
+import * as Site from './site'
+import * as Wishlist from './wishlist'
+
+export type {
+  Cart,
+  Checkout,
+  Common,
+  Customer,
+  Login,
+  Logout,
+  Page,
+  Product,
+  Signup,
+  Site,
+  Wishlist,
+}
diff --git a/framework/commercetools/types/login.ts b/framework/commercetools/types/login.ts
new file mode 100644
index 000000000..db536076f
--- /dev/null
+++ b/framework/commercetools/types/login.ts
@@ -0,0 +1,13 @@
+import * as Core from '@commerce/types/login'
+import type { LoginMutationVariables } from '../schema'
+
+export * from '@commerce/types/login'
+
+export type CommercetoolsLogin = {
+  email: string
+  password: string
+}
+
+export type LoginOperation = Core.LoginOperation & {
+  variables: LoginMutationVariables
+}
diff --git a/framework/commercetools/types/logout.ts b/framework/commercetools/types/logout.ts
new file mode 100644
index 000000000..9f0a466af
--- /dev/null
+++ b/framework/commercetools/types/logout.ts
@@ -0,0 +1 @@
+export * from '@commerce/types/logout'
diff --git a/framework/commercetools/types/page.ts b/framework/commercetools/types/page.ts
new file mode 100644
index 000000000..2bccfade2
--- /dev/null
+++ b/framework/commercetools/types/page.ts
@@ -0,0 +1,11 @@
+import * as Core from '@commerce/types/page'
+export * from '@commerce/types/page'
+
+export type Page = Core.Page
+
+export type PageTypes = {
+  page: Page
+}
+
+export type GetAllPagesOperation = Core.GetAllPagesOperation<PageTypes>
+export type GetPageOperation = Core.GetPageOperation<PageTypes>
diff --git a/framework/commercetools/types/product.ts b/framework/commercetools/types/product.ts
new file mode 100644
index 000000000..f304e3aa3
--- /dev/null
+++ b/framework/commercetools/types/product.ts
@@ -0,0 +1,66 @@
+import * as Core from '@commerce/types/product'
+export type CommercetoolsProduct = {
+  id: string
+  name: LocalString
+  description: LocalString
+  slug: LocalString
+  metaDescription: LocalString
+  masterVariant: CommercetoolsProductVariant
+  variants: CommercetoolsProductVariant[]
+  published: boolean
+}
+export type CommercetoolsProductVariant = {
+  id: string
+  key: string
+  sku: string
+  images: Images[]
+  price?: CommerceToolsProductPrice
+  prices: CommerceToolsProductPrice[]
+  attributes: ProductAttributes[]
+}
+export type ProductAttributes = {
+  name: string
+  value: string | AttributeDefinition | boolean | number
+}
+export type AttributeDefinition = {
+  key: string
+  label: string
+}
+export type Images = {
+  url: string
+  dimensions: {
+    w: number
+    h: number
+  }
+}
+export type CommerceToolsProductPrice = {
+  id: string
+  value: Money
+  discounted: DiscountedPrice
+}
+export type DiscountedPrice = {
+  value: Money
+}
+export type Money = {
+  type: string
+  currencyCode: string
+  centAmount: number
+  fractionDigits: number
+}
+export type LocalString = {
+  en: string
+  'es-AR': string
+  'es-CL': string
+  'es-PE': string
+  de: string
+}
+// get Product
+export type GetProductById = {
+  id: string
+}
+export type Product = Core.Product
+export type ProductVariant = Core.ProductVariant
+export type ProductPrice = Core.ProductPrice
+export type ProductOption = Core.ProductOption
+export type ProductOptionValue = Core.ProductOptionValues
+export type ProductImage = Core.ProductImage
diff --git a/framework/commercetools/types/signup.ts b/framework/commercetools/types/signup.ts
new file mode 100644
index 000000000..58543c6f6
--- /dev/null
+++ b/framework/commercetools/types/signup.ts
@@ -0,0 +1 @@
+export * from '@commerce/types/signup'
diff --git a/framework/commercetools/types/site.ts b/framework/commercetools/types/site.ts
new file mode 100644
index 000000000..0fbafdfe9
--- /dev/null
+++ b/framework/commercetools/types/site.ts
@@ -0,0 +1,13 @@
+import * as Core from '@commerce/types/site'
+export type CommercetoolsCategory = {
+  id: string
+  name: string
+  slug: string
+  parent: { id: string }
+}
+export type CommercetoolsBrands = {
+  key: string
+  label?: string
+}
+export type Brand = Core.Brand
+export type Category = Core.Category
diff --git a/framework/commercetools/types/wishlist.ts b/framework/commercetools/types/wishlist.ts
new file mode 100644
index 000000000..1e148b88c
--- /dev/null
+++ b/framework/commercetools/types/wishlist.ts
@@ -0,0 +1,23 @@
+import * as Core from '@commerce/types/wishlist'
+import { definitions } from '../api/definitions/wishlist'
+import type { ProductEdge } from '../api/operations/get-all-products'
+
+export * from '@commerce/types/wishlist'
+
+export type WishlistItem = NonNullable<
+  definitions['wishlist_Full']['items']
+>[0] & {
+  product?: ProductEdge['node']
+}
+
+export type Wishlist = Omit<definitions['wishlist_Full'], 'items'> & {
+  items?: WishlistItem[]
+}
+
+export type WishlistTypes = {
+  wishlist: Wishlist
+  itemBody: Core.WishlistItemBody
+}
+
+export type WishlistSchema = Core.WishlistSchema<WishlistTypes>
+export type GetCustomerWishlistOperation = Core.GetCustomerWishlistOperation<WishlistTypes>
diff --git a/framework/commercetools/utils/commercetools/index.ts b/framework/commercetools/utils/commercetools/index.ts
new file mode 100644
index 000000000..a80b47aea
--- /dev/null
+++ b/framework/commercetools/utils/commercetools/index.ts
@@ -0,0 +1,51 @@
+import { createAuthMiddlewareForClientCredentialsFlow } from '@commercetools/sdk-middleware-auth'
+import { createHttpMiddleware } from '@commercetools/sdk-middleware-http'
+import { createQueueMiddleware } from '@commercetools/sdk-middleware-queue'
+import { createClient } from '@commercetools/sdk-client'
+import { createApiBuilderFromCtpClient } from '@commercetools/platform-sdk'
+import fetch from 'node-fetch'
+
+interface Props {
+  clientId: string
+  clientSecret: string
+  projectKey: string
+  host: string
+  oauthHost: string
+  concurrency: string | number
+}
+
+export default ({
+  clientId,
+  clientSecret,
+  projectKey,
+  host,
+  oauthHost,
+  concurrency = 10,
+}: Props) => {
+  interface Commercetools {
+    requestExecute: any
+  }
+
+  const ctpClient = createClient({
+    middlewares: [
+      createAuthMiddlewareForClientCredentialsFlow({
+        host: oauthHost,
+        projectKey,
+        credentials: {
+          clientId,
+          clientSecret,
+        },
+        fetch,
+      }),
+      createQueueMiddleware({ concurrency }),
+      createHttpMiddleware({ host, fetch }),
+    ],
+  })
+
+  const apiRoot = createApiBuilderFromCtpClient(ctpClient)
+  const commercetools = <Commercetools>{
+    requestExecute: apiRoot.withProjectKey({ projectKey }),
+  }
+
+  return commercetools
+}
diff --git a/framework/commercetools/utils/queries/get-category.ts b/framework/commercetools/utils/queries/get-category.ts
new file mode 100644
index 000000000..b681ac4bc
--- /dev/null
+++ b/framework/commercetools/utils/queries/get-category.ts
@@ -0,0 +1,29 @@
+export const getAllCategoriesAndBrandsQuery = /* GraphQL */ `
+  query getCategoriesAndBrands {
+    categories {
+      results {
+        id
+        name(locale: "en")
+        slug(locale: "en")
+      }
+    }
+    productTypes {
+      results {
+        attributeDefinitions(includeNames: "designer") {
+          results {
+            type {
+              ... on EnumAttributeDefinitionType {
+                values {
+                  results {
+                    key
+                    label
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+`
diff --git a/framework/commercetools/utils/queries/get-product-query.ts b/framework/commercetools/utils/queries/get-product-query.ts
new file mode 100644
index 000000000..138a9940e
--- /dev/null
+++ b/framework/commercetools/utils/queries/get-product-query.ts
@@ -0,0 +1,30 @@
+const getProductQuery =
+  /* GraphQL */
+  `
+    query getProductQuery($id: String!, $locale: Locale) {
+      product(id: $id) {
+        id
+        masterData {
+          current {
+            name(locale: $locale)
+            metaDescription(locale: $locale)
+            slug(locale: $locale)
+            masterVariant {
+              prices {
+                value {
+                  centAmount
+                  currencyCode
+                }
+              }
+              sku
+              images {
+                url
+              }
+            }
+          }
+        }
+      }
+    }
+  `
+
+export default getProductQuery
diff --git a/framework/commercetools/wishlist/index.ts b/framework/commercetools/wishlist/index.ts
new file mode 100644
index 000000000..6493beb1b
--- /dev/null
+++ b/framework/commercetools/wishlist/index.ts
@@ -0,0 +1,3 @@
+// export { default as useAddItem } from './use-add-item'
+// export { default as useWishlist } from './use-wishlist'
+// export { default as useRemoveItem } from './use-remove-item'
diff --git a/framework/commercetools/wishlist/use-add-item.ts b/framework/commercetools/wishlist/use-add-item.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/wishlist/use-remove-item.ts b/framework/commercetools/wishlist/use-remove-item.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/framework/commercetools/wishlist/use-wishlist.ts b/framework/commercetools/wishlist/use-wishlist.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/global.d.ts b/global.d.ts
index 498a1f9fe..3826995f3 100644
--- a/global.d.ts
+++ b/global.d.ts
@@ -1,2 +1,6 @@
 // Declarations for modules without types
 declare module 'next-themes'
+declare module '@commercetools/sdk-middleware-auth'
+declare module '@commercetools/sdk-middleware-http'
+declare module '@commercetools/sdk-middleware-queue'
+declare module '@commercetools/sdk-client'
diff --git a/package.json b/package.json
index 85daa3158..6bb23b15f 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,11 @@
     "node": "14.x"
   },
   "dependencies": {
+    "@commercetools/platform-sdk": "^1.13.0",
+    "@commercetools/sdk-client": "^2.1.2",
+    "@commercetools/sdk-middleware-auth": "^6.1.4",
+    "@commercetools/sdk-middleware-http": "^6.0.11",
+    "@commercetools/sdk-middleware-queue": "^2.1.4",
     "@reach/portal": "^0.11.2",
     "@vercel/fetch": "^6.1.0",
     "autoprefixer": "^10.2.4",
diff --git a/tsconfig.json b/tsconfig.json
index 96e4359e5..1d19b894e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,8 +22,8 @@
       "@components/*": ["components/*"],
       "@commerce": ["framework/commerce"],
       "@commerce/*": ["framework/commerce/*"],
-      "@framework": ["framework/shopify"],
-      "@framework/*": ["framework/shopify/*"]
+      "@framework": ["framework/commercetools"],
+      "@framework/*": ["framework/commercetools/*"]
     }
   },
   "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
diff --git a/yarn.lock b/yarn.lock
index 1b1fce30e..d67d19b03 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -464,6 +464,43 @@
     lodash "^4.17.13"
     to-fast-properties "^2.0.0"
 
+"@commercetools/platform-sdk@^1.13.0":
+  version "1.14.0"
+  resolved "https://registry.yarnpkg.com/@commercetools/platform-sdk/-/platform-sdk-1.14.0.tgz#7e5410eb7ff56d8ed3a5422dccdac67bbd2e6156"
+  integrity sha512-JclsCi0H8VpR3rwt5ZVHXXKGLCy56UwM6RkB3esuxTYarg3p5sMDXVRiCUchoSyr1rScf4/ksjO3mNzKxEPd5g==
+  dependencies:
+    "@commercetools/sdk-client" "^2.1.1"
+    "@commercetools/sdk-middleware-auth" "^6.0.4"
+    "@commercetools/sdk-middleware-http" "^6.0.4"
+    "@commercetools/sdk-middleware-logger" "^2.1.1"
+
+"@commercetools/sdk-client@^2.1.1", "@commercetools/sdk-client@^2.1.2":
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/@commercetools/sdk-client/-/sdk-client-2.1.2.tgz#fe1e442f67a385f103470669784c0fa20d7a2314"
+  integrity sha512-YPpK39pkjfedjS1/BFg2d7CrvTeN7vIS5vfiqEkKOtAoUxiNkugv59gRSoh2Em8SOccxyM/skpgHyTqfmJzXug==
+
+"@commercetools/sdk-middleware-auth@^6.0.4", "@commercetools/sdk-middleware-auth@^6.1.4":
+  version "6.1.4"
+  resolved "https://registry.yarnpkg.com/@commercetools/sdk-middleware-auth/-/sdk-middleware-auth-6.1.4.tgz#c5f464ae1627336715681e4590b63777e034c890"
+  integrity sha512-49R1DWsA+pNHH7/2K6QU5wnJSXabljKA8dvzs5HcbLwutlDp3Io0XHgIJa9qpfYhgW6k0h9dPICcLbESrQBXYw==
+  dependencies:
+    node-fetch "^2.3.0"
+
+"@commercetools/sdk-middleware-http@^6.0.11", "@commercetools/sdk-middleware-http@^6.0.4":
+  version "6.0.11"
+  resolved "https://registry.yarnpkg.com/@commercetools/sdk-middleware-http/-/sdk-middleware-http-6.0.11.tgz#0ca16cefe881b68c1d2b77ddbd3a48733a5ee062"
+  integrity sha512-9Keb5rv6fvdA9qdehBEjk/JMrAzlBbg76TodsvhCZZZteaO0+ybjFgtV0ekdGyI4awxOxgsiPDZrTmQNvnI5Wg==
+
+"@commercetools/sdk-middleware-logger@^2.1.1":
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/@commercetools/sdk-middleware-logger/-/sdk-middleware-logger-2.1.1.tgz#9283fdc8c403a7e2d4d06637e6015770b864e64a"
+  integrity sha512-k/Jm3lsWbszPBHtPAvu0rINTq398p4ddv0zbAH8R4p6Yc1GkBEy6tNgHPzX/eFskI/qerPy9IsW1xK8pqgtHHQ==
+
+"@commercetools/sdk-middleware-queue@^2.1.4":
+  version "2.1.4"
+  resolved "https://registry.yarnpkg.com/@commercetools/sdk-middleware-queue/-/sdk-middleware-queue-2.1.4.tgz#d8b162ff83fc553cc5abef8599571389874983d6"
+  integrity sha512-8TxeUb+jdSemUt/wd9hEcPl2uK2sTRPd5BEwXzOYAlyQJBXMMju2GMo5ASDWz7xjKLoijEuY86Jo7D9JSP8DPQ==
+
 "@csstools/convert-colors@^1.4.0":
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
@@ -4486,7 +4523,7 @@ node-emoji@^1.8.1:
   dependencies:
     lodash.toarray "^4.4.0"
 
-node-fetch@2.6.1, node-fetch@^2.6.1:
+node-fetch@2.6.1, node-fetch@^2.3.0, node-fetch@^2.6.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
   integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==