Updates & fixes

This commit is contained in:
Catalin Pinte 2022-11-28 12:09:01 +02:00
parent 6699f2fed4
commit 36e68b461f
9 changed files with 1740 additions and 1551 deletions

View File

@ -5,10 +5,12 @@ import type {
import type { import type {
GetCustomerWishlistOperation, GetCustomerWishlistOperation,
Wishlist, Wishlist,
WishlistItem,
} from '@vercel/commerce/types/wishlist' } from '@vercel/commerce/types/wishlist'
import type { RecursivePartial, RecursiveRequired } from '../utils/types' import type { RecursivePartial } from '../utils/types'
import { BigcommerceConfig, Provider } from '..' import { BigcommerceConfig, Provider } from '..'
import getAllProducts, { ProductEdge } from './get-all-products'
import type { Product } from '@vercel/commerce/types/product'
export default function getCustomerWishlistOperation({ export default function getCustomerWishlistOperation({
commerce, commerce,
@ -47,6 +49,12 @@ export default function getCustomerWishlistOperation({
const wishlist = data[0] const wishlist = data[0]
if (!wishlist) {
return {}
}
const items: WishlistItem[] = []
if (includeProducts && wishlist?.items?.length) { if (includeProducts && wishlist?.items?.length) {
const ids = wishlist.items const ids = wishlist.items
?.map((item) => (item?.productId ? String(item?.productId) : null)) ?.map((item) => (item?.productId ? String(item?.productId) : null))
@ -57,25 +65,36 @@ export default function getCustomerWishlistOperation({
variables: { first: 50, ids }, variables: { first: 50, ids },
config, config,
}) })
// Put the products in an object that we can use to get them by id // Put the products in an object that we can use to get them by id
const productsById = graphqlData.products.reduce<{ const productsById = graphqlData.products.reduce<{
[k: number]: ProductEdge [k: number]: Product
}>((prods, p) => { }>((prods, p) => {
prods[Number(p.id)] = p as any prods[Number(p.id)] = p as any
return prods return prods
}, {}) }, {})
// Populate the wishlist items with the graphql products // Populate the wishlist items with the graphql products
wishlist.items.forEach((item) => { wishlist.items.forEach((item) => {
const product = item && productsById[Number(item.productId)] const product = item && productsById[Number(item.productId)]
if (item && product) { if (item && product) {
// @ts-ignore Fix this type when the wishlist type is properly defined items.push({
item.product = product id: String(item.id),
productId: String(item.productId),
variantId: String(item.variantId),
product,
})
} }
}) })
} }
} }
return { wishlist: wishlist as RecursiveRequired<typeof wishlist> } return {
wishlist: {
id: String(wishlist.id),
items,
},
}
} }
return getCustomerWishlist return getCustomerWishlist

View File

@ -100,7 +100,7 @@ export default function getAllProductPathsOperation({
const variables: GetProductQueryVariables = { const variables: GetProductQueryVariables = {
locale, locale,
hasLocale: !!locale, hasLocale: !!locale,
path: slug ? `/${slug}` : vars.path!, path: `/${slug}`,
} }
const { data } = await config.fetch<GetProductQuery>(query, { variables }) const { data } = await config.fetch<GetProductQuery>(query, { variables })
const product = data.site?.route?.node const product = data.site?.route?.node

View File

@ -83,32 +83,38 @@ export interface ProductVariant {
*/ */
image?: Image image?: Image
} }
/**
*
* The product metafield object, which is a custom field attached to a product. It can be used to store additional information about the product in a structured format.
* @example `reviews`
*/
export interface ProductMetafield { export interface ProductMetafield {
/** /**
* The key name for the metafield. * The unique identifier for the metafield.
*/ */
key: string key: string
/** /**
* The namespace for the metafield. * The namespace for the metafield, which is a container for a set of metadata.
* @example `rating` * @example `rating`
*/ */
namespace: string namespace: string
/** /**
* The value of the metafield. * The value of the metafield, usually a string that can be might parsed into JSON.
* @example `{"value": 5, "scale_max": 5}` * @example `{"value": 5, "scale_max": 5}`
*/ */
value: any value: any
/** /**
* Automatically transformed value of the metafield. * The value of the metafield, complete with HTML formatting.
*/ */
html?: string valueHtml?: string
/** /**
* The type of the metafield. * The type of the metafield, used to determine how the value should be interpreted.
* @example `date` * For example: `string`, `integer`, `boolean`, `json` ...
*/ */
type?: string type?: string
@ -119,11 +125,12 @@ export interface ProductMetafield {
} }
/** /**
* Product Metafields, grouped by namespace. * The product metafields are custom fields that can be added to a product. They are used to store additional information about the product.
* The namespace is the key of the object, and the value is an object with the metafield key and an object with the metafield data.
* @example * @example
* { * {
* // Namespace, the container for a set of metadata
* reviews: { * reviews: {
* // Key of the metafield, used to differentiate between metafields of the same namespace
* rating: { * rating: {
* key: 'rating', * key: 'rating',
* value: 5, * value: 5,
@ -132,10 +139,19 @@ export interface ProductMetafield {
* } * }
*/ */
export interface ProductMetafields { export interface ProductMetafields {
/**
* The namespace for the metafield, which is a container for a set of metadata.
* @example `reviews`, `specifications`
*/
[namespace: string]: { [namespace: string]: {
/**
* The key of the metafield, which is the name of the metafield.
* @example `rating`
*/
[key: string]: ProductMetafield [key: string]: ProductMetafield
} }
} }
export interface Product { export interface Product {
/** /**
* The unique identifier for the product. * The unique identifier for the product.
@ -170,8 +186,22 @@ export interface Product {
*/ */
images: Image[] images: Image[]
/** /**
* The products metafields. This is a list of metafields that are attached to the product. * The products custom fields. They are used to store simple key-value additional information about the product.
* */
customFields?: Record<string, string>
/**
* The product metafields are advanced custom fields that can be added to a product. They are used to store additional information about the product, usually in a structured format.
* @example
* {
* // Namespace, the container for a set of metadata
* reviews: {
* // Key of the metafield, used to differentiate between metafields of the same namespace
* rating: {
* key: 'rating',
* value: 5,
* // ... other metafield properties
* }
* }
*/ */
metafields?: ProductMetafields metafields?: ProductMetafields
/** /**
@ -265,40 +295,24 @@ export type GetAllProductsOperation = {
} }
} }
export type MetafieldsIdentifiers = export type MetafieldsIdentifiers = Array<{
| Record<string, string[]> namespace: string
| Array<{ key: string
namespace: string }>
key: string
}>
export type GetProductOperation = { export type GetProductOperation = {
data: { product?: Product } data: { product?: Product }
variables: variables: {
| { slug: string
path: string /**
slug?: never * Metafields identifiers used to fetch the product metafields, represented as an array of objects with the namespace and key.
} *
| ({ * @example
path?: never * withMetafields: [
slug: string * {namespace: 'reviews', key: 'rating'},
} & { * {namespace: 'reviews', key: 'count'},
/** * ]
* Metafields identifiers used to fetch the product metafields. */
* It can be an array of objects with the namespace and key, or an object with the namespace as the key and an array of keys as the value. withMetafields?: MetafieldsIdentifiers
* }
* @example
* metafields: {
* reviews: ['rating', 'count']
* }
*
* // or
*
* metafields: [
* {namespace: 'reviews', key: 'rating'},
* {namespace: 'reviews', key: 'count'},
* ]
*/
withMetafields?: MetafieldsIdentifiers
})
} }

View File

@ -1,30 +1,24 @@
import type { OperationContext } from '@vercel/commerce/api/operations' import type { OperationContext } from '@vercel/commerce/api/operations'
import { normalizeProduct } from '../../utils' import type { GetProductOperation } from '@vercel/commerce/types/product'
import type { Provider, SaleorConfig } from '..' import type { Provider, SaleorConfig } from '..'
import { normalizeProduct } from '../../utils'
import * as Query from '../../utils/queries' import * as Query from '../../utils/queries'
type Variables = {
slug: string
}
type ReturnType = {
product: any
}
export default function getProductOperation({ export default function getProductOperation({
commerce, commerce,
}: OperationContext<Provider>) { }: OperationContext<Provider>) {
async function getProduct({ async function getProduct<T extends GetProductOperation>({
query = Query.ProductOneBySlug, query = Query.ProductOneBySlug,
variables, variables,
config: cfg, config: cfg,
}: { }: {
query?: string query?: string
variables: Variables variables: T['variables']
config?: Partial<SaleorConfig> config?: Partial<SaleorConfig>
preview?: boolean preview?: boolean
}): Promise<ReturnType> { }): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(cfg) const { fetch, locale } = commerce.getConfig(cfg)
const { data } = await fetch( const { data } = await fetch(
@ -37,9 +31,9 @@ export default function getProductOperation({
} }
) )
return { return data && data.product
product: data && data.product ? normalizeProduct(data.product) : null, ? { product: normalizeProduct(data.product) }
} : {}
} }
return getProduct return getProduct

View File

@ -1,3 +1,5 @@
import type { GetProductOperation } from '@vercel/commerce/types/product'
import { normalizeProduct } from '../../utils' import { normalizeProduct } from '../../utils'
import { Product } from '@vercel/commerce/types/product' import { Product } from '@vercel/commerce/types/product'
@ -7,15 +9,15 @@ import { Provider, SwellConfig } from '../'
export default function getProductOperation({ export default function getProductOperation({
commerce, commerce,
}: OperationContext<Provider>) { }: OperationContext<Provider>) {
async function getProduct({ async function getProduct<T extends GetProductOperation>({
variables, variables,
config: cfg, config: cfg,
}: { }: {
query?: string query?: string
variables: { slug: string } variables: T['variables']
config?: Partial<SwellConfig> config?: Partial<SwellConfig>
preview?: boolean preview?: boolean
}): Promise<Product | {} | any> { }): Promise<T['data']> {
const config = commerce.getConfig(cfg) const config = commerce.getConfig(cfg)
const product = await config.fetch('products', 'get', [variables.slug]) const product = await config.fetch('products', 'get', [variables.slug])
@ -24,9 +26,7 @@ export default function getProductOperation({
product.variants = product.variants?.results product.variants = product.variants?.results
} }
return { return product ? { product: normalizeProduct(product) } : {}
product: product ? normalizeProduct(product) : null,
}
} }
return getProduct return getProduct

View File

@ -1,22 +1,26 @@
import { Product } from '@vercel/commerce/types/product' import type {
import { OperationContext } from '@vercel/commerce/api/operations' Product,
import { Provider, VendureConfig } from '../' GetProductOperation,
import { GetProductQuery } from '../../../schema' } from '@vercel/commerce/types/product'
import type { OperationContext } from '@vercel/commerce/api/operations'
import type { Provider, VendureConfig } from '../'
import type { GetProductQuery } from '../../../schema'
import { getProductQuery } from '../../utils/queries/get-product-query' import { getProductQuery } from '../../utils/queries/get-product-query'
export default function getProductOperation({ export default function getProductOperation({
commerce, commerce,
}: OperationContext<Provider>) { }: OperationContext<Provider>) {
async function getProduct({ async function getProduct<T extends GetProductOperation>({
query = getProductQuery, query = getProductQuery,
variables, variables,
config: cfg, config: cfg,
}: { }: {
query?: string query?: string
variables: { slug: string } variables: T['variables']
config?: Partial<VendureConfig> config?: Partial<VendureConfig>
preview?: boolean preview?: boolean
}): Promise<Product | {} | any> { }): Promise<T['data']> {
const config = commerce.getConfig(cfg) const config = commerce.getConfig(cfg)
const locale = config.locale const locale = config.locale

3092
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -94,20 +94,20 @@ const ProductSidebar: FC<ProductSidebarProps> = ({ product, className }) => {
<Collapse title="Care"> <Collapse title="Care">
<Text <Text
className="leading-0" className="leading-0"
html={product.metafields.descriptors.care_guide.html} html={product.metafields.descriptors.care_guide.valueHtml}
/> />
</Collapse> </Collapse>
)} )}
{product.metafields?.my_fields && ( {product.metafields?.my_fields && (
<Collapse title="Details"> <Collapse title="Details">
{Object.entries(product.metafields.my_fields).map(([_, field]) => ( {Object.values(product.metafields.my_fields).map((field) => (
<div <div
key={field.key} key={field.key}
className="flex gap-2 border-b py-3 border-accent-2 border-dashed last:border-b-0" className="flex gap-2 border-b py-3 border-accent-2 border-dashed last:border-b-0"
> >
<strong className="leading-7">{field.name}:</strong> <strong className="leading-7">{field.name}:</strong>
<Text html={field.html || field.value} className="!mx-0" /> <Text html={field.valueHtml || field.value} className="!mx-0" />
</div> </div>
))} ))}
</Collapse> </Collapse>

View File

@ -23,8 +23,8 @@
"@components/*": ["components/*"], "@components/*": ["components/*"],
"@commerce": ["../packages/commerce/src"], "@commerce": ["../packages/commerce/src"],
"@commerce/*": ["../packages/commerce/src/*"], "@commerce/*": ["../packages/commerce/src/*"],
"@framework": ["../packages/shopify/src"], "@framework": ["../packages/local/src"],
"@framework/*": ["../packages/shopify/src/*"] "@framework/*": ["../packages/local/src/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"], "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],