mirror of
https://github.com/vercel/commerce.git
synced 2025-06-19 21:51:21 +00:00
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>
This commit is contained in:
parent
0e804d09f9
commit
716b540966
@ -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>
|
||||
|
||||
|
@ -40,7 +40,7 @@ const Navbar: FC<NavbarProps> = ({ links }) => (
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end flex-1 space-x-8">
|
||||
<UserNav />
|
||||
{/* <UserNav /> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
8
framework/commercetools/.env.template
Normal file
8
framework/commercetools/.env.template
Normal file
@ -0,0 +1,8 @@
|
||||
COMMERCE_PROVIDER=commercetools
|
||||
|
||||
CTP_PROJECT_KEY=
|
||||
CTP_CLIENT_SECRET=
|
||||
CTP_CLIENT_ID=
|
||||
CTP_AUTH_URL=
|
||||
CTP_API_URL=
|
||||
CTP_CONCURRENCY=
|
18
framework/commercetools/README.md
Normal file
18
framework/commercetools/README.md
Normal file
@ -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
|
@ -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
|
@ -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
|
101
framework/commercetools/api/index.ts
Normal file
101
framework/commercetools/api/index.ts
Normal file
@ -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)
|
||||
}
|
40
framework/commercetools/api/operations/get-all-pages.ts
Normal file
40
framework/commercetools/api/operations/get-all-pages.ts
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
31
framework/commercetools/api/operations/get-all-products.ts
Normal file
31
framework/commercetools/api/operations/get-all-products.ts
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
45
framework/commercetools/api/operations/get-page.ts
Normal file
45
framework/commercetools/api/operations/get-page.ts
Normal file
@ -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
|
||||
}
|
38
framework/commercetools/api/operations/get-product.ts
Normal file
38
framework/commercetools/api/operations/get-product.ts
Normal file
@ -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
|
||||
}
|
39
framework/commercetools/api/operations/get-site-info.ts
Normal file
39
framework/commercetools/api/operations/get-site-info.ts
Normal file
@ -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
|
||||
}
|
43
framework/commercetools/api/operations/login.ts
Normal file
43
framework/commercetools/api/operations/login.ts
Normal file
@ -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
|
||||
}
|
25
framework/commercetools/api/utils/errors.ts
Normal file
25
framework/commercetools/api/utils/errors.ts
Normal file
@ -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'
|
||||
}
|
||||
}
|
37
framework/commercetools/api/utils/fetch-graphql-api.ts
Normal file
37
framework/commercetools/api/utils/fetch-graphql-api.ts
Normal file
@ -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
|
33
framework/commercetools/api/utils/fetch-products.ts
Normal file
33
framework/commercetools/api/utils/fetch-products.ts
Normal file
@ -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
|
3
framework/commercetools/api/utils/fetch.ts
Normal file
3
framework/commercetools/api/utils/fetch.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import zeitFetch from '@vercel/fetch'
|
||||
|
||||
export default zeitFetch()
|
3
framework/commercetools/auth/index.ts
Normal file
3
framework/commercetools/auth/index.ts
Normal file
@ -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'
|
0
framework/commercetools/auth/use-login.ts
Normal file
0
framework/commercetools/auth/use-login.ts
Normal file
0
framework/commercetools/auth/use-logout.ts
Normal file
0
framework/commercetools/auth/use-logout.ts
Normal file
0
framework/commercetools/auth/use-signup.ts
Normal file
0
framework/commercetools/auth/use-signup.ts
Normal file
4
framework/commercetools/cart/index.ts
Normal file
4
framework/commercetools/cart/index.ts
Normal file
@ -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'
|
0
framework/commercetools/cart/use-add-item.ts
Normal file
0
framework/commercetools/cart/use-add-item.ts
Normal file
0
framework/commercetools/cart/use-cart.ts
Normal file
0
framework/commercetools/cart/use-cart.ts
Normal file
0
framework/commercetools/cart/use-remove-item.ts
Normal file
0
framework/commercetools/cart/use-remove-item.ts
Normal file
0
framework/commercetools/cart/use-update-item.ts
Normal file
0
framework/commercetools/cart/use-update-item.ts
Normal file
9
framework/commercetools/commerce.config.json
Normal file
9
framework/commercetools/commerce.config.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"provider": "commercetools",
|
||||
"features": {
|
||||
"wishlist": false,
|
||||
"customer": false,
|
||||
"cart": false,
|
||||
"auth": false
|
||||
}
|
||||
}
|
1
framework/commercetools/customer/index.ts
Normal file
1
framework/commercetools/customer/index.ts
Normal file
@ -0,0 +1 @@
|
||||
// export { default as useCustomer } from './use-customer'
|
0
framework/commercetools/customer/use-customer.ts
Normal file
0
framework/commercetools/customer/use-customer.ts
Normal file
41
framework/commercetools/fetcher.ts
Normal file
41
framework/commercetools/fetcher.ts
Normal file
@ -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
|
38
framework/commercetools/index.tsx
Normal file
38
framework/commercetools/index.tsx
Normal file
@ -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>()
|
70
framework/commercetools/lib/array-to-tree.ts
Normal file
70
framework/commercetools/lib/array-to-tree.ts
Normal file
@ -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
|
||||
}
|
5
framework/commercetools/lib/get-slug.ts
Normal file
5
framework/commercetools/lib/get-slug.ts
Normal file
@ -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
|
185
framework/commercetools/lib/normalize.ts
Normal file
185
framework/commercetools/lib/normalize.ts
Normal file
@ -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,
|
||||
}
|
||||
}
|
8
framework/commercetools/next.config.js
Normal file
8
framework/commercetools/next.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
const commerce = require('./commerce.config.json')
|
||||
|
||||
module.exports = {
|
||||
commerce,
|
||||
images: {
|
||||
domains: ['s3-eu-west-1.amazonaws.com'],
|
||||
},
|
||||
}
|
2
framework/commercetools/product/index.ts
Normal file
2
framework/commercetools/product/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as usePrice } from './use-price'
|
||||
export { default as useSearch } from './use-search'
|
2
framework/commercetools/product/use-price.ts
Normal file
2
framework/commercetools/product/use-price.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from '@commerce/product/use-price'
|
||||
export { default } from '@commerce/product/use-price'
|
54
framework/commercetools/product/use-search.ts
Normal file
54
framework/commercetools/product/use-search.ts
Normal file
@ -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,
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
24
framework/commercetools/provider.ts
Normal file
24
framework/commercetools/provider.ts
Normal file
@ -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
|
103
framework/commercetools/types/cart.ts
Normal file
103
framework/commercetools/types/cart.ts
Normal file
@ -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']
|
1
framework/commercetools/types/checkout.ts
Normal file
1
framework/commercetools/types/checkout.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from '@commerce/types/checkout'
|
1
framework/commercetools/types/common.ts
Normal file
1
framework/commercetools/types/common.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from '@commerce/types/common'
|
20
framework/commercetools/types/customer.ts
Normal file
20
framework/commercetools/types/customer.ts
Normal file
@ -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
|
25
framework/commercetools/types/index.ts
Normal file
25
framework/commercetools/types/index.ts
Normal file
@ -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,
|
||||
}
|
13
framework/commercetools/types/login.ts
Normal file
13
framework/commercetools/types/login.ts
Normal file
@ -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
|
||||
}
|
1
framework/commercetools/types/logout.ts
Normal file
1
framework/commercetools/types/logout.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from '@commerce/types/logout'
|
11
framework/commercetools/types/page.ts
Normal file
11
framework/commercetools/types/page.ts
Normal file
@ -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>
|
66
framework/commercetools/types/product.ts
Normal file
66
framework/commercetools/types/product.ts
Normal file
@ -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
|
1
framework/commercetools/types/signup.ts
Normal file
1
framework/commercetools/types/signup.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from '@commerce/types/signup'
|
13
framework/commercetools/types/site.ts
Normal file
13
framework/commercetools/types/site.ts
Normal file
@ -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
|
23
framework/commercetools/types/wishlist.ts
Normal file
23
framework/commercetools/types/wishlist.ts
Normal file
@ -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>
|
51
framework/commercetools/utils/commercetools/index.ts
Normal file
51
framework/commercetools/utils/commercetools/index.ts
Normal file
@ -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
|
||||
}
|
29
framework/commercetools/utils/queries/get-category.ts
Normal file
29
framework/commercetools/utils/queries/get-category.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
30
framework/commercetools/utils/queries/get-product-query.ts
Normal file
30
framework/commercetools/utils/queries/get-product-query.ts
Normal file
@ -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
|
3
framework/commercetools/wishlist/index.ts
Normal file
3
framework/commercetools/wishlist/index.ts
Normal file
@ -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'
|
0
framework/commercetools/wishlist/use-add-item.ts
Normal file
0
framework/commercetools/wishlist/use-add-item.ts
Normal file
0
framework/commercetools/wishlist/use-remove-item.ts
Normal file
0
framework/commercetools/wishlist/use-remove-item.ts
Normal file
0
framework/commercetools/wishlist/use-wishlist.ts
Normal file
0
framework/commercetools/wishlist/use-wishlist.ts
Normal file
4
global.d.ts
vendored
4
global.d.ts
vendored
@ -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'
|
||||
|
@ -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",
|
||||
|
@ -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"],
|
||||
|
39
yarn.lock
39
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==
|
||||
|
Loading…
x
Reference in New Issue
Block a user