mirror of
https://github.com/vercel/commerce.git
synced 2025-06-20 06:01: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 CartItem from '../CartItem'
|
||||||
import s from './CartSidebarView.module.css'
|
import s from './CartSidebarView.module.css'
|
||||||
import { Button } from '@components/ui'
|
import { Button } from '@components/ui'
|
||||||
import { UserNav } from '@components/common'
|
// import { UserNav } from '@components/common'
|
||||||
import { useUI } from '@components/ui/context'
|
import { useUI } from '@components/ui/context'
|
||||||
import { Bag, Cross, Check } from '@components/icons'
|
import { Bag, Cross, Check } from '@components/icons'
|
||||||
import useCart from '@framework/cart/use-cart'
|
import useCart from '@framework/cart/use-cart'
|
||||||
@ -48,9 +48,7 @@ const CartSidebarView: FC = () => {
|
|||||||
<Cross className="h-6 w-6" />
|
<Cross className="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">{/* <UserNav /> */}</div>
|
||||||
<UserNav />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ const Navbar: FC<NavbarProps> = ({ links }) => (
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end flex-1 space-x-8">
|
<div className="flex justify-end flex-1 space-x-8">
|
||||||
<UserNav />
|
{/* <UserNav /> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { Swatch, ProductSlider } from '@components/product'
|
|||||||
import { Button, Container, Text, useUI } from '@components/ui'
|
import { Button, Container, Text, useUI } from '@components/ui'
|
||||||
import type { Product } from '@commerce/types/product'
|
import type { Product } from '@commerce/types/product'
|
||||||
import usePrice from '@framework/product/use-price'
|
import usePrice from '@framework/product/use-price'
|
||||||
import { useAddItem } from '@framework/cart'
|
// import { useAddItem } from '@framework/cart'
|
||||||
import { getVariant, SelectedOptions } from '../helpers'
|
import { getVariant, SelectedOptions } from '../helpers'
|
||||||
import WishlistButton from '@components/wishlist/WishlistButton'
|
import WishlistButton from '@components/wishlist/WishlistButton'
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ interface Props {
|
|||||||
const ProductView: FC<Props> = ({ product }) => {
|
const ProductView: FC<Props> = ({ product }) => {
|
||||||
// TODO: fix this missing argument issue
|
// TODO: fix this missing argument issue
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
const addItem = useAddItem()
|
// const addItem = useAddItem()
|
||||||
const { price } = usePrice({
|
const { price } = usePrice({
|
||||||
amount: product.price.value,
|
amount: product.price.value,
|
||||||
baseAmount: product.price.retailPrice,
|
baseAmount: product.price.retailPrice,
|
||||||
@ -32,28 +32,28 @@ const ProductView: FC<Props> = ({ product }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Selects the default option
|
// Selects the default option
|
||||||
product.variants[0].options?.forEach((v) => {
|
// product.variants[0].options?.forEach((v) => {
|
||||||
setChoices((choices) => ({
|
// setChoices((choices) => ({
|
||||||
...choices,
|
// ...choices,
|
||||||
[v.displayName.toLowerCase()]: v.values[0].label.toLowerCase(),
|
// [v.displayName.toLowerCase()]: v.values[0].label.toLowerCase(),
|
||||||
}))
|
// }))
|
||||||
})
|
// })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const variant = getVariant(product, choices)
|
const variant = getVariant(product, choices)
|
||||||
|
|
||||||
const addToCart = async () => {
|
const addToCart = async () => {
|
||||||
setLoading(true)
|
// setLoading(true)
|
||||||
try {
|
// try {
|
||||||
await addItem({
|
// await addItem({
|
||||||
productId: String(product.id),
|
// productId: String(product.id),
|
||||||
variantId: String(variant ? variant.id : product.variants[0].id),
|
// variantId: String(variant ? variant.id : product.variants[0].id),
|
||||||
})
|
// })
|
||||||
openSidebar()
|
// openSidebar()
|
||||||
setLoading(false)
|
// setLoading(false)
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
setLoading(false)
|
// setLoading(false)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,10 +2,10 @@ import React, { FC, useState } from 'react'
|
|||||||
import cn from 'classnames'
|
import cn from 'classnames'
|
||||||
import { useUI } from '@components/ui'
|
import { useUI } from '@components/ui'
|
||||||
import { Heart } from '@components/icons'
|
import { Heart } from '@components/icons'
|
||||||
import useAddItem from '@framework/wishlist/use-add-item'
|
// import useAddItem from '@framework/wishlist/use-add-item'
|
||||||
import useCustomer from '@framework/customer/use-customer'
|
// import useCustomer from '@framework/customer/use-customer'
|
||||||
import useWishlist from '@framework/wishlist/use-wishlist'
|
// import useWishlist from '@framework/wishlist/use-wishlist'
|
||||||
import useRemoveItem from '@framework/wishlist/use-remove-item'
|
// import useRemoveItem from '@framework/wishlist/use-remove-item'
|
||||||
import type { Product, ProductVariant } from '@commerce/types/product'
|
import type { Product, ProductVariant } from '@commerce/types/product'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -19,10 +19,11 @@ const WishlistButton: FC<Props> = ({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = useWishlist()
|
// const { data } = useWishlist()
|
||||||
const addItem = useAddItem()
|
const data = {}
|
||||||
const removeItem = useRemoveItem()
|
// const addItem = useAddItem()
|
||||||
const { data: customer } = useCustomer()
|
// const removeItem = useRemoveItem()
|
||||||
|
// const { data: customer } = useCustomer()
|
||||||
const { openModal, setModalView } = useUI()
|
const { openModal, setModalView } = useUI()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
@ -40,21 +41,21 @@ const WishlistButton: FC<Props> = ({
|
|||||||
if (loading) return
|
if (loading) return
|
||||||
|
|
||||||
// A login is required before adding an item to the wishlist
|
// A login is required before adding an item to the wishlist
|
||||||
if (!customer) {
|
// if (!customer) {
|
||||||
setModalView('LOGIN_VIEW')
|
// setModalView('LOGIN_VIEW')
|
||||||
return openModal()
|
// return openModal()
|
||||||
}
|
// }
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (itemInWishlist) {
|
if (itemInWishlist) {
|
||||||
await removeItem({ id: itemInWishlist.id! })
|
// await removeItem({ id: itemInWishlist.id! })
|
||||||
} else {
|
} else {
|
||||||
await addItem({
|
// await addItem({
|
||||||
productId,
|
// productId,
|
||||||
variantId: variant?.id!,
|
// variantId: variant?.id!,
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
@ -7,7 +7,13 @@ const fs = require('fs')
|
|||||||
const merge = require('deepmerge')
|
const merge = require('deepmerge')
|
||||||
const prettier = require('prettier')
|
const prettier = require('prettier')
|
||||||
|
|
||||||
const PROVIDERS = ['bigcommerce', 'shopify', 'swell', 'vendure']
|
const PROVIDERS = [
|
||||||
|
'bigcommerce',
|
||||||
|
'shopify',
|
||||||
|
'swell',
|
||||||
|
'vendure',
|
||||||
|
'commercetools',
|
||||||
|
]
|
||||||
|
|
||||||
function getProviderName() {
|
function getProviderName() {
|
||||||
return (
|
return (
|
||||||
@ -18,6 +24,8 @@ function getProviderName() {
|
|||||||
? 'shopify'
|
? 'shopify'
|
||||||
: process.env.NEXT_PUBLIC_SWELL_STORE_ID
|
: process.env.NEXT_PUBLIC_SWELL_STORE_ID
|
||||||
? 'swell'
|
? 'swell'
|
||||||
|
: process.env.CTP_API_URL
|
||||||
|
? 'commercetools'
|
||||||
: null)
|
: 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
|
// Declarations for modules without types
|
||||||
declare module 'next-themes'
|
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"
|
"node": "14.x"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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",
|
"@reach/portal": "^0.11.2",
|
||||||
"@vercel/fetch": "^6.1.0",
|
"@vercel/fetch": "^6.1.0",
|
||||||
"autoprefixer": "^10.2.4",
|
"autoprefixer": "^10.2.4",
|
||||||
|
@ -22,8 +22,8 @@
|
|||||||
"@components/*": ["components/*"],
|
"@components/*": ["components/*"],
|
||||||
"@commerce": ["framework/commerce"],
|
"@commerce": ["framework/commerce"],
|
||||||
"@commerce/*": ["framework/commerce/*"],
|
"@commerce/*": ["framework/commerce/*"],
|
||||||
"@framework": ["framework/shopify"],
|
"@framework": ["framework/commercetools"],
|
||||||
"@framework/*": ["framework/shopify/*"]
|
"@framework/*": ["framework/commercetools/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
||||||
|
39
yarn.lock
39
yarn.lock
@ -464,6 +464,43 @@
|
|||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
to-fast-properties "^2.0.0"
|
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":
|
"@csstools/convert-colors@^1.4.0":
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
|
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:
|
dependencies:
|
||||||
lodash.toarray "^4.4.0"
|
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"
|
version "2.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user