mirror of
https://github.com/vercel/commerce.git
synced 2025-05-22 01:16:58 +00:00
336 lines
9.8 KiB
TypeScript
336 lines
9.8 KiB
TypeScript
import { BigCommerceSortKeys, VercelSortKeys, vercelToBigCommerceSortKeys } from 'lib/constants';
|
|
import {
|
|
BigCommerceCart,
|
|
BigCommerceCheckout,
|
|
BigCommerceCollection,
|
|
BigCommercePage,
|
|
BigCommerceProduct,
|
|
BigCommerceProductOption,
|
|
BigCommerceProductVariant,
|
|
CartCustomItem,
|
|
DigitalOrPhysicalItem,
|
|
VercelCart,
|
|
VercelCartItem,
|
|
VercelCollection,
|
|
VercelPage,
|
|
VercelProduct,
|
|
VercelProductOption,
|
|
VercelProductVariant
|
|
} from './types';
|
|
|
|
type ProductsList = { productId: number; productData: BigCommerceProduct }[];
|
|
|
|
const vercelFromBigCommerceLineItems = (lineItems: BigCommerceCart['lineItems']) => {
|
|
const { physicalItems, digitalItems, customItems } = lineItems;
|
|
const cartItemMapper = ({ entityId, quantity, productEntityId }: DigitalOrPhysicalItem | CartCustomItem) => ({
|
|
merchandiseId: productEntityId ? productEntityId.toString() : entityId.toString(),
|
|
quantity
|
|
});
|
|
|
|
return [physicalItems, digitalItems, customItems].flatMap((list) => list.map(cartItemMapper));
|
|
};
|
|
|
|
const bigcommerceToVercelOptions = (options: BigCommerceProductOption[]): VercelProductOption[] => {
|
|
return options.map((option) => {
|
|
return {
|
|
id: option.entityId.toString(),
|
|
name: option.displayName.toString(),
|
|
values: option.values ? option.values.edges.map(({ node: value }) => value.label) : []
|
|
};
|
|
});
|
|
};
|
|
const bigcommerceToVercelVariants = (
|
|
variants: BigCommerceProductVariant[],
|
|
productId: number
|
|
): VercelProductVariant[] => {
|
|
return variants.map((variant) => {
|
|
return {
|
|
parentId: productId.toString(),
|
|
id: variant.entityId.toString(),
|
|
title: '',
|
|
availableForSale: variant.isPurchasable,
|
|
selectedOptions: variant.options?.edges.map(({ node: option }) => ({
|
|
name: option.displayName ?? '',
|
|
value: option.values.edges.map(({ node }) => node.label)[0] ?? ''
|
|
})) || [
|
|
{
|
|
name: '',
|
|
value: ''
|
|
}
|
|
],
|
|
price: {
|
|
amount:
|
|
variant.prices?.price.value.toString() ||
|
|
variant.prices?.priceRange.max.value.toString() ||
|
|
'0',
|
|
currencyCode:
|
|
variant.prices?.price.currencyCode || variant.prices?.priceRange.max.currencyCode || ''
|
|
}
|
|
};
|
|
});
|
|
};
|
|
|
|
const bigcommerceToVercelProduct = (product: BigCommerceProduct): VercelProduct => {
|
|
const createVercelProductImage = (img: { url: string; altText: string }) => {
|
|
return {
|
|
url: img.url,
|
|
altText: img.altText,
|
|
width: 2048,
|
|
height: 2048
|
|
};
|
|
};
|
|
const options = product.productOptions.edges.length
|
|
? bigcommerceToVercelOptions(product.productOptions.edges.map((item) => item.node))
|
|
: [];
|
|
const variants = product.variants.edges.length
|
|
? bigcommerceToVercelVariants(
|
|
product.variants.edges.map((item) => item.node),
|
|
product.entityId
|
|
)
|
|
: [];
|
|
|
|
return {
|
|
id: product.id.toString(),
|
|
handle: product.entityId.toString(),
|
|
availableForSale: product.availabilityV2.status === 'Available' ? true : false,
|
|
title: product.name,
|
|
description: product.plainTextDescription || '',
|
|
descriptionHtml: product.description ?? '',
|
|
options,
|
|
priceRange: {
|
|
maxVariantPrice: {
|
|
amount:
|
|
product.prices.priceRange.max.value.toString() ||
|
|
product.prices.price.value.toString() ||
|
|
'0',
|
|
currencyCode:
|
|
product.prices.priceRange.max.currencyCode || product.prices.price.currencyCode || ''
|
|
},
|
|
minVariantPrice: {
|
|
amount:
|
|
product.prices.priceRange.min.value.toString() ||
|
|
product.prices.price.value.toString() ||
|
|
'0',
|
|
currencyCode:
|
|
product.prices.priceRange.min.currencyCode || product.prices.price.currencyCode || ''
|
|
}
|
|
},
|
|
variants,
|
|
images: product.images
|
|
? product.images.edges.map(({ node: img }) => createVercelProductImage(img))
|
|
: [],
|
|
featuredImage: createVercelProductImage(product.defaultImage),
|
|
seo: {
|
|
title: product.seo.pageTitle || product.name,
|
|
description: product.seo.metaDescription || ''
|
|
},
|
|
tags: [product.seo.metaKeywords] || [],
|
|
updatedAt: product.createdAt.utc.toString()
|
|
};
|
|
};
|
|
|
|
const bigcommerceToVercelProducts = (products: BigCommerceProduct[]) => {
|
|
const reshapedProducts = [];
|
|
|
|
for (const product of products) {
|
|
if (product) {
|
|
const reshapedProduct = bigcommerceToVercelProduct(product);
|
|
|
|
if (reshapedProduct) {
|
|
reshapedProducts.push(reshapedProduct);
|
|
}
|
|
}
|
|
}
|
|
|
|
return reshapedProducts;
|
|
};
|
|
|
|
const bigcommerceToVercelCartItems = (
|
|
lineItems: BigCommerceCart['lineItems'],
|
|
products: ProductsList
|
|
) => {
|
|
const getItemMapper = (products: ProductsList, isCustomItem: boolean = false) => {
|
|
return (item: CartCustomItem | DigitalOrPhysicalItem): VercelCartItem => {
|
|
const vercelProductFallback = {
|
|
id: '',
|
|
handle: '',
|
|
availableForSale: false,
|
|
title: '',
|
|
description: '',
|
|
descriptionHtml: '',
|
|
options: [],
|
|
priceRange: {
|
|
maxVariantPrice: { amount: '', currencyCode: '' },
|
|
minVariantPrice: { amount: '', currencyCode: '' }
|
|
},
|
|
variants: [],
|
|
featuredImage: {
|
|
url: '',
|
|
altText: '',
|
|
width: 0,
|
|
height: 0
|
|
},
|
|
images: [
|
|
{
|
|
url: '',
|
|
altText: '',
|
|
width: 0,
|
|
height: 0
|
|
}
|
|
],
|
|
seo: { title: '', description: '' },
|
|
tags: [],
|
|
updatedAt: ''
|
|
};
|
|
let product;
|
|
let selectedOptions;
|
|
|
|
if (isCustomItem) {
|
|
product = vercelProductFallback;
|
|
selectedOptions = [{ name: '', value: '' }];
|
|
} else {
|
|
const productData = products.filter(
|
|
({ productId }) => productId === (item as DigitalOrPhysicalItem).productEntityId
|
|
)[0]?.productData;
|
|
|
|
product = productData ? bigcommerceToVercelProduct(productData) : vercelProductFallback;
|
|
selectedOptions = (item as DigitalOrPhysicalItem).selectedOptions.map((option) => ({
|
|
name: option.name,
|
|
value: option.value || option.text || option.number?.toString() || option.fileName || ''
|
|
}));
|
|
}
|
|
|
|
return {
|
|
id: item.entityId.toString(), // NOTE: used as lineId || lineItemId
|
|
quantity: item.quantity,
|
|
cost: {
|
|
totalAmount: {
|
|
amount:
|
|
item.extendedListPrice.value.toString() || item.listPrice.value.toString() || '0',
|
|
currencyCode: item.extendedListPrice.currencyCode || item.listPrice.currencyCode || ''
|
|
}
|
|
},
|
|
merchandise: {
|
|
id: isCustomItem ? item.entityId.toString() : (item as DigitalOrPhysicalItem).variantEntityId!.toString(),
|
|
title: `${item.name}`,
|
|
selectedOptions,
|
|
product
|
|
}
|
|
};
|
|
};
|
|
};
|
|
|
|
const { physicalItems, digitalItems, customItems } = lineItems;
|
|
const areCustomItemsInCart = customItems.length > 0;
|
|
const line1 = physicalItems.map((item) => getItemMapper(products)(item));
|
|
const line2 = digitalItems.map((item) => getItemMapper(products)(item));
|
|
const line3 = areCustomItemsInCart
|
|
? customItems.map((item) => getItemMapper(products, areCustomItemsInCart)(item))
|
|
: [];
|
|
|
|
return [...line1, ...line2, ...line3];
|
|
};
|
|
|
|
const bigcommerceToVercelCart = (
|
|
cart: BigCommerceCart,
|
|
products: ProductsList,
|
|
checkout: BigCommerceCheckout
|
|
): VercelCart => {
|
|
return {
|
|
id: cart.entityId,
|
|
checkoutUrl: '', // TODO: add later
|
|
cost: {
|
|
subtotalAmount: {
|
|
amount: checkout.subtotal.value.toString(),
|
|
currencyCode: checkout.subtotal.currencyCode
|
|
},
|
|
totalAmount: {
|
|
amount: checkout.grandTotal.value.toString(),
|
|
currencyCode: checkout.grandTotal.currencyCode
|
|
},
|
|
totalTaxAmount: {
|
|
amount: checkout.taxTotal.value.toString(),
|
|
currencyCode: checkout.taxTotal.currencyCode
|
|
}
|
|
},
|
|
lines: bigcommerceToVercelCartItems(cart.lineItems, products),
|
|
totalQuantity: cart.lineItems.totalQuantity
|
|
};
|
|
};
|
|
|
|
const bigCommerceToVercelCollection = (collection: BigCommerceCollection): VercelCollection => {
|
|
if (!collection) {
|
|
return {
|
|
handle: '',
|
|
title: '',
|
|
description: '',
|
|
seo: {
|
|
title: '',
|
|
description: ''
|
|
},
|
|
updatedAt: '',
|
|
path: ''
|
|
};
|
|
}
|
|
|
|
return {
|
|
handle: collection.entityId.toString() || collection.name,
|
|
title: collection.name,
|
|
description: collection.description,
|
|
seo: {
|
|
title: collection.seo.pageTitle,
|
|
description: collection.seo.metaDescription
|
|
},
|
|
updatedAt: new Date().toISOString(),
|
|
path: `/search${collection.path}`
|
|
};
|
|
};
|
|
|
|
export {
|
|
bigcommerceToVercelCart,
|
|
bigcommerceToVercelProduct,
|
|
bigcommerceToVercelProducts,
|
|
bigCommerceToVercelCollection,
|
|
vercelFromBigCommerceLineItems
|
|
};
|
|
|
|
export const vercelToBigCommerceSorting = (
|
|
isReversed: boolean,
|
|
sortKey?: string
|
|
): keyof typeof BigCommerceSortKeys | null => {
|
|
const VercelSorting: Record<string, string> = {
|
|
RELEVANCE: 'RELEVANCE',
|
|
BEST_SELLING: 'BEST_SELLING',
|
|
CREATED_AT: 'CREATED_AT',
|
|
PRICE: 'PRICE'
|
|
};
|
|
|
|
if (!sortKey || VercelSorting[sortKey] === undefined) {
|
|
return null;
|
|
}
|
|
|
|
if (sortKey === VercelSortKeys.PRICE) {
|
|
return isReversed
|
|
? vercelToBigCommerceSortKeys.PRICE_ON_REVERSE
|
|
: vercelToBigCommerceSortKeys.PRICE;
|
|
}
|
|
|
|
return vercelToBigCommerceSortKeys[sortKey as keyof typeof VercelSortKeys];
|
|
};
|
|
|
|
export const bigCommerceToVercelPageContent = (page: BigCommercePage): VercelPage => {
|
|
return {
|
|
id: page.entityId.toString(),
|
|
title: page.name,
|
|
handle: page.path,
|
|
body: page.htmlBody ?? '',
|
|
bodySummary: page.plainTextSummary ?? '',
|
|
seo: {
|
|
title: page.seo.pageTitle,
|
|
description: page.seo.metaDescription
|
|
},
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
};
|