diff --git a/packages/bigcommerce/package.json b/packages/bigcommerce/package.json index 551d0ea68..ebdcd5327 100644 --- a/packages/bigcommerce/package.json +++ b/packages/bigcommerce/package.json @@ -63,6 +63,7 @@ "react-dom": "^18" }, "devDependencies": { + "@manifoldco/swagger-to-ts": "^2.1.0", "@taskr/clear": "^1.1.0", "@taskr/esnext": "^1.1.0", "@taskr/watch": "^1.1.0", @@ -74,6 +75,7 @@ "@types/react": "^18.0.14", "lint-staged": "^12.1.7", "next": "^12.0.8", + "node-fetch": "^2.6.7", "prettier": "^2.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/bigcommerce/src/api/fragments/product.ts b/packages/bigcommerce/src/api/fragments/product.ts index 734e0ab63..a1c4e8598 100644 --- a/packages/bigcommerce/src/api/fragments/product.ts +++ b/packages/bigcommerce/src/api/fragments/product.ts @@ -80,6 +80,15 @@ export const productInfoFragment = /* GraphQL */ ` } } } + customFields { + edges { + node { + entityId + name + value + } + } + } localeMeta: metafields(namespace: $locale, keys: ["name", "description"]) @include(if: $hasLocale) { edges { diff --git a/packages/bigcommerce/src/api/operations/get-customer-wishlist.ts b/packages/bigcommerce/src/api/operations/get-customer-wishlist.ts index cdfd05acf..1f1f8aee8 100644 --- a/packages/bigcommerce/src/api/operations/get-customer-wishlist.ts +++ b/packages/bigcommerce/src/api/operations/get-customer-wishlist.ts @@ -5,10 +5,12 @@ import type { import type { GetCustomerWishlistOperation, Wishlist, + WishlistItem, } from '@vercel/commerce/types/wishlist' -import type { RecursivePartial, RecursiveRequired } from '../utils/types' +import type { RecursivePartial } from '../utils/types' import { BigcommerceConfig, Provider } from '..' -import getAllProducts, { ProductEdge } from './get-all-products' + +import type { Product } from '@vercel/commerce/types/product' export default function getCustomerWishlistOperation({ commerce, @@ -47,6 +49,12 @@ export default function getCustomerWishlistOperation({ const wishlist = data[0] + if (!wishlist) { + return {} + } + + const items: WishlistItem[] = [] + if (includeProducts && wishlist?.items?.length) { const ids = wishlist.items ?.map((item) => (item?.productId ? String(item?.productId) : null)) @@ -57,25 +65,36 @@ export default function getCustomerWishlistOperation({ variables: { first: 50, ids }, config, }) + // Put the products in an object that we can use to get them by id const productsById = graphqlData.products.reduce<{ - [k: number]: ProductEdge + [k: number]: Product }>((prods, p) => { prods[Number(p.id)] = p as any return prods }, {}) + // Populate the wishlist items with the graphql products wishlist.items.forEach((item) => { const product = item && productsById[Number(item.productId)] if (item && product) { - // @ts-ignore Fix this type when the wishlist type is properly defined - item.product = product + items.push({ + id: String(item.id), + productId: String(item.productId), + variantId: String(item.variantId), + product, + }) } }) } } - return { wishlist: wishlist as RecursiveRequired } + return { + wishlist: { + id: String(wishlist.id), + items, + }, + } } return getCustomerWishlist diff --git a/packages/bigcommerce/src/api/operations/get-product.ts b/packages/bigcommerce/src/api/operations/get-product.ts index 534d173c7..064fde9ed 100644 --- a/packages/bigcommerce/src/api/operations/get-product.ts +++ b/packages/bigcommerce/src/api/operations/get-product.ts @@ -100,7 +100,7 @@ export default function getAllProductPathsOperation({ const variables: GetProductQueryVariables = { locale, hasLocale: !!locale, - path: slug ? `/${slug}` : vars.path!, + path: `/${slug}`, } const { data } = await config.fetch(query, { variables }) const product = data.site?.route?.node diff --git a/packages/bigcommerce/src/lib/normalize.ts b/packages/bigcommerce/src/lib/normalize.ts index e83394c91..fe0e579c1 100644 --- a/packages/bigcommerce/src/lib/normalize.ts +++ b/packages/bigcommerce/src/lib/normalize.ts @@ -1,10 +1,12 @@ import type { Page } from '@vercel/commerce/types/page' import type { Product } from '@vercel/commerce/types/product' +import type { CustomField } from '@vercel/commerce/types/common' import type { Cart, LineItem } from '@vercel/commerce/types/cart' import type { Category, Brand } from '@vercel/commerce/types/site' import type { BigcommerceCart, BCCategory, BCBrand } from '../types' import type { ProductNode } from '../api/operations/get-all-products' import type { definitions } from '../api/definitions/store-content' +import type { CustomFieldEdge } from '../../schema' import getSlug from './get-slug' @@ -20,10 +22,20 @@ function normalizeProductOption(productOption: any) { } } -export function normalizeProduct(productNode: ProductNode): Product { +// TODO: change this after schema definition is updated +interface ProductNodeWithCustomFields extends ProductNode { + customFields: { + edges?: CustomFieldEdge[] + } +} + +export function normalizeProduct( + productNode: ProductNodeWithCustomFields +): Product { const { entityId: id, productOptions, + customFields, prices, path, images, @@ -52,6 +64,7 @@ export function normalizeProduct(productNode: ProductNode): Product { }) ) || [], options: productOptions?.edges?.map(normalizeProductOption) || [], + customFields: customFields?.edges?.map(normalizeCustomFieldsValue) || [], slug: path?.replace(/^\/+|\/+$/g, ''), price: { value: prices?.price.value, @@ -137,3 +150,15 @@ export function normalizeBrand(brand: BCBrand): Brand { path: `/${slug}`, } } + +function normalizeCustomFieldsValue(field: CustomFieldEdge): CustomField { + const { + node: { entityId, name, value }, + } = field + + return { + id: String(entityId), + name, + value, + } +} diff --git a/packages/commerce/src/types/common.ts b/packages/commerce/src/types/common.ts index d63dfc0b9..111c2573b 100644 --- a/packages/commerce/src/types/common.ts +++ b/packages/commerce/src/types/common.ts @@ -34,3 +34,67 @@ export interface Image { */ height?: number } + +export interface CustomField { + /** + * The unique identifier for the custom field. + */ + id: string + /** + * The name of the custom field. + */ + name: string + /** + * The value of the custom field. + */ + value: string +} + +export interface Metafield { + /** + * The unique identifier for the metafield. + */ + key: string + + /** + * The namespace for the metafield, which is a container for a set of metadata. + * @example `rating` + */ + namespace: string + + /** + * The value of the metafield, usually a string that can be might parsed into JSON. + * @example `{"value": 5, "scale_max": 5}` + */ + value: any + + /** + * The value of the metafield, complete with HTML formatting. + */ + valueHtml?: string + + /** + * The type of the metafield, used to determine how the value should be interpreted. + * For example: `string`, `integer`, `boolean`, `json` ... + */ + type?: string + + /** + * The name of the metafield, that can be used as a label. + */ + name?: string +} + +export interface Metafields { + /** + * The namespace for the metafield, which is a container for a set of metadata. + * @example `reviews`, `specifications` + */ + [namespace: string]: { + /** + * The key of the metafield, which is the name of the metafield. + * @example `rating` + */ + [key: string]: Metafield + } +} diff --git a/packages/commerce/src/types/product.ts b/packages/commerce/src/types/product.ts index 5f3633627..76942216c 100644 --- a/packages/commerce/src/types/product.ts +++ b/packages/commerce/src/types/product.ts @@ -1,4 +1,4 @@ -import { Image } from './common' +import { CustomField, Image, Metafields } from './common' export interface ProductPrice { /** @@ -126,6 +126,33 @@ export interface Product { * List of images associated with the product. */ images: Image[] + /** + * List of custom fields / properties associated with the product. + * @example + * customFields: [{ + * id: '1', + * name: 'Warehouse Location', + * value: 'Aisle 3, Shelf 5, Bin 6' + * }] + */ + customFields?: CustomField[] + /** + * Advanced custom fields that can be added to a product. They are used to store additional information about the product, in a structured format, grouped by namespaces. + * @example + * { + * // Namespace, the container for a set of metadata + * reviews: { + * // Key of the metafield + * rating: { + * key: 'rating', + * value: 4, + * valueHtml: '★★★★☆', + * type: 'integer', + * name: 'Rating', + * } + * } + */ + metafields?: Metafields /** * List of the product’s variants. */ @@ -204,7 +231,6 @@ export type ProductsSchema = { /** * Product operations */ - export type GetAllProductPathsOperation = { data: { products: Pick[] } variables: { first?: number } @@ -219,7 +245,24 @@ export type GetAllProductsOperation = { } } +export type MetafieldsIdentifiers = Array<{ + namespace: string + key: string +}> + export type GetProductOperation = { data: { product?: Product } - variables: { path: string; slug?: never } | { path?: never; slug: string } + variables: { + slug: string + /** + * Metafields identifiers used to fetch the product metafields, represented as an array of objects with the namespace and key. + * + * @example + * withMetafields: [ + * {namespace: 'reviews', key: 'rating'}, + * {namespace: 'reviews', key: 'count'}, + * ] + */ + withMetafields?: MetafieldsIdentifiers + } } diff --git a/packages/saleor/src/api/operations/get-product.ts b/packages/saleor/src/api/operations/get-product.ts index c9bdf364b..74a013c0a 100644 --- a/packages/saleor/src/api/operations/get-product.ts +++ b/packages/saleor/src/api/operations/get-product.ts @@ -1,30 +1,24 @@ 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 { normalizeProduct } from '../../utils' + import * as Query from '../../utils/queries' -type Variables = { - slug: string -} - -type ReturnType = { - product: any -} - export default function getProductOperation({ commerce, }: OperationContext) { - async function getProduct({ + async function getProduct({ query = Query.ProductOneBySlug, variables, config: cfg, }: { query?: string - variables: Variables + variables: T['variables'] config?: Partial preview?: boolean - }): Promise { + }): Promise { const { fetch, locale } = commerce.getConfig(cfg) const { data } = await fetch( @@ -37,9 +31,9 @@ export default function getProductOperation({ } ) - return { - product: data && data.product ? normalizeProduct(data.product) : null, - } + return data && data.product + ? { product: normalizeProduct(data.product) } + : {} } return getProduct diff --git a/packages/shopify/src/api/operations/get-product.ts b/packages/shopify/src/api/operations/get-product.ts index e8aa28120..0e1793d5a 100644 --- a/packages/shopify/src/api/operations/get-product.ts +++ b/packages/shopify/src/api/operations/get-product.ts @@ -55,7 +55,7 @@ export default function getProductOperation({ return { ...(productByHandle && { - product: normalizeProduct(productByHandle as ShopifyProduct), + product: normalizeProduct(productByHandle as ShopifyProduct, locale), }), } } diff --git a/packages/shopify/src/api/utils/fetch-graphql-api.ts b/packages/shopify/src/api/utils/fetch-graphql-api.ts index 1eac16ef1..bbc3de675 100644 --- a/packages/shopify/src/api/utils/fetch-graphql-api.ts +++ b/packages/shopify/src/api/utils/fetch-graphql-api.ts @@ -30,14 +30,7 @@ const fetchGraphqlApi: GraphQLFetcher = async ( return { data, res } } catch (err) { - throw getError( - [ - { - message: `${err} \n Most likely related to an unexpected output. E.g: NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN & NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN might be incorect.`, - }, - ], - 500 - ) + throw getError([err], 500) } } export default fetchGraphqlApi diff --git a/packages/shopify/src/types/metafields.ts b/packages/shopify/src/types/metafields.ts new file mode 100644 index 000000000..d3b4cb53a --- /dev/null +++ b/packages/shopify/src/types/metafields.ts @@ -0,0 +1,39 @@ +type LiteralUnion = T | Omit + +export type MetafieldTypes = + | 'integer' + | 'boolean' + | 'color' + | 'json' + | 'date' + | 'file_reference' + | 'date_time' + | 'dimension' + | 'multi_line_text_field' + | 'number_decimal' + | 'number_integer' + | 'page_reference' + | 'product_reference' + | 'rating' + | 'single_line_text_field' + | 'url' + | 'variant_reference' + | 'volume' + | 'weight' + | 'list.color' + | 'list.date' + | 'list.date_time' + | 'list.dimension' + | 'list.file_reference' + | 'list.number_integer' + | 'list.number_decimal' + | 'list.page_reference' + | 'list.product_reference' + | 'list.rating' + | 'list.single_line_text_field' + | 'list.url' + | 'list.variant_reference' + | 'list.volume' + | 'list.weight' + +export type MetafieldType = LiteralUnion diff --git a/packages/shopify/src/utils/handle-fetch-response.ts b/packages/shopify/src/utils/handle-fetch-response.ts index 927ab54f1..ff2b516a1 100644 --- a/packages/shopify/src/utils/handle-fetch-response.ts +++ b/packages/shopify/src/utils/handle-fetch-response.ts @@ -1,7 +1,12 @@ import { FetcherError } from '@vercel/commerce/utils/errors' export function getError(errors: any[] | null, status: number) { - errors = errors ?? [{ message: 'Failed to fetch Shopify API' }] + errors = errors ?? [ + { + message: + 'Failed to fetch Shopify API, most likely related to an unexpected output. E.g: NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN & NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN might be incorect', + }, + ] return new FetcherError({ errors, status }) } diff --git a/packages/shopify/src/utils/metafields.ts b/packages/shopify/src/utils/metafields.ts new file mode 100644 index 000000000..1bb66c5dd --- /dev/null +++ b/packages/shopify/src/utils/metafields.ts @@ -0,0 +1,118 @@ +import type { MetafieldType } from '../types/metafields' + +export const parseJson = (value: string): any => { + try { + return JSON.parse(value) + } catch (e) { + return value + } +} + +const unitConversion: Record = { + mm: 'millimeter', + cm: 'centimeter', + m: 'meter', + in: 'inch', + ft: 'foot', + yd: 'yard', + + ml: 'milliliter', + l: 'liter', + us_fl_oz: 'fluid-ounce', + us_gal: 'gallon', + + kg: 'kilogram', + g: 'gram', + lb: 'pound', + oz: 'ounce', + + MILLIMETERS: 'millimeter', + CENTIMETERS: 'centimeter', + METERS: 'meter', + + MILLILITERS: 'milliliter', + LITERS: 'liter', + FLUID_OUNCES: 'fluid-ounce', + IMPERIAL_FLUID_OUNCES: 'fluid-ounce', + GALLONS: 'gallon', + + KILOGRAMS: 'kilogram', + GRAMS: 'gram', + OUNCES: 'ounce', + POUNDS: 'pound', + + FEET: 'foot', +} + +export const getMeasurment = (input: string, locale: string = 'en-US') => { + try { + let { unit, value } = JSON.parse(input) + return new Intl.NumberFormat(locale, { + unit: unitConversion[unit], + style: 'unit', + }).format(parseFloat(value)) + } catch (e) { + console.error(e) + return input + } +} + +export const getMetafieldValue = ( + type: MetafieldType, + value: string, + locale: string = 'en-US' +) => { + switch (type) { + case 'boolean': + return value === 'true' ? '✓' : '✕' + case 'number_integer': + return parseInt(value).toLocaleString(locale) + case 'number_decimal': + return parseFloat(value).toLocaleString(locale) + case 'date': + return Intl.DateTimeFormat(locale, { + dateStyle: 'medium', + }).format(new Date(value)) + case 'date_time': + return Intl.DateTimeFormat(locale, { + dateStyle: 'medium', + timeStyle: 'long', + }).format(new Date(value)) + case 'dimension': + case 'volume': + case 'weight': + return getMeasurment(value, locale) + case 'rating': + const { scale_max: length, value: val } = JSON.parse(value) + return Array.from({ length }, (_, i) => + i <= val - 1 ? '★' : '☆' + ).join('') + case 'color': + return `
` + case 'url': + return `${value}` + case 'multi_line_text_field': + return value + .split('\n') + .map((line) => `${line}
`) + .join('') + case 'json': + case 'single_line_text_field': + case 'product_reference': + case 'page_reference': + case 'variant_reference': + case 'file_reference': + default: + return value + } +} + +export const toLabel = (string: string) => + string + .toLowerCase() + .replace(/[_-]+/g, ' ') + .replace(/\s{2,}/g, ' ') + .trim() + .split(' ') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' ') diff --git a/packages/shopify/src/utils/normalize.ts b/packages/shopify/src/utils/normalize.ts index dcf4b93ef..710ba7423 100644 --- a/packages/shopify/src/utils/normalize.ts +++ b/packages/shopify/src/utils/normalize.ts @@ -1,7 +1,9 @@ import type { Page } from '@vercel/commerce/types/page' import type { Product } from '@vercel/commerce/types/product' +import type { Metafield } from '@vercel/commerce/types/common' import type { Cart, LineItem } from '@vercel/commerce/types/cart' import type { Category } from '@vercel/commerce/types/site' +import type { MetafieldType } from '../types/metafields' import type { Product as ShopifyProduct, @@ -15,14 +17,12 @@ import type { Page as ShopifyPage, PageEdge, Collection, - MetafieldConnection, - MediaConnection, - Model3d, - Metafield, Maybe, + Metafield as ShopifyMetafield, } from '../../schema' import { colorMap } from './colors' +import { getMetafieldValue, toLabel, parseJson } from './metafields' const money = ({ amount, currencyCode }: MoneyV2) => { return { @@ -92,7 +92,6 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => { name, values: [value], }) - return options }), } @@ -100,35 +99,23 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => { ) } -const normalizeProductMedia = ({ edges }: MediaConnection) => { - return edges - .filter(({ node }) => Object.keys(node).length !== 0) - .map(({ node }) => { - return { - sources: (node as Model3d).sources.map(({ format, url }) => { - return { - format: format, - url: url, - } - }), - } - }) -} - -export function normalizeProduct({ - id, - title: name, - vendor, - images, - variants, - description, - descriptionHtml, - handle, - priceRange, - options, - metafields, - ...rest -}: ShopifyProduct): Product { +export function normalizeProduct( + { + id, + title: name, + vendor, + images, + variants, + description, + descriptionHtml, + handle, + priceRange, + options, + metafields, + ...rest + }: ShopifyProduct, + locale?: string +): Product { return { id, name, @@ -143,12 +130,48 @@ export function normalizeProduct({ .filter((o) => o.name !== 'Title') // By default Shopify adds a 'Title' name when there's only one option. We don't need it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095 .map((o) => normalizeProductOption(o)) : [], + metafields: normalizeMetafields(metafields, locale), description: description || '', ...(descriptionHtml && { descriptionHtml }), ...rest, } } +export function normalizeMetafields( + metafields: Maybe[], + locale?: string +) { + const output: Record> = {} + + if (!metafields) return output + + for (const metafield of metafields) { + if (!metafield) continue + + const { key, type, namespace, value, ...rest } = metafield + + const newField = { + ...rest, + key, + name: toLabel(key), + type, + namespace, + value, + valueHtml: getMetafieldValue(type, value, locale), + } + + if (!output[namespace]) { + output[namespace] = { + [key]: newField, + } + } else { + output[namespace][key] = newField + } + } + + return output +} + export function normalizeCart(checkout: Checkout): Cart { return { id: checkout.id, @@ -217,3 +240,19 @@ export const normalizeCategory = ({ slug: handle, path: `/${handle}`, }) + +export const normalizeMetafieldValue = ( + type: MetafieldType, + value: string, + locale?: string +) => { + if (type.startsWith('list.')) { + const arr = parseJson(value) + return Array.isArray(arr) + ? arr + .map((v) => getMetafieldValue(type.split('.')[1], v, locale)) + .join(' • ') + : value + } + return getMetafieldValue(type, value, locale) +} diff --git a/packages/shopify/src/utils/queries/get-product-query.ts b/packages/shopify/src/utils/queries/get-product-query.ts index c71bd954c..169846ee8 100644 --- a/packages/shopify/src/utils/queries/get-product-query.ts +++ b/packages/shopify/src/utils/queries/get-product-query.ts @@ -1,5 +1,8 @@ const getProductQuery = /* GraphQL */ ` - query getProductBySlug($slug: String!) { + query getProductBySlug( + $slug: String! + $withMetafields: [HasMetafieldsIdentifier!] = [] + ) { productByHandle(handle: $slug) { id handle @@ -24,15 +27,7 @@ const getProductQuery = /* GraphQL */ ` currencyCode } } - metafields(first: 30) { - edges { - node { - key - value - } - } - } - variants(first: 250) { + variants(first: 25) { pageInfo { hasNextPage hasPreviousPage @@ -59,7 +54,7 @@ const getProductQuery = /* GraphQL */ ` } } } - images(first: 250) { + images(first: 25) { pageInfo { hasNextPage hasPreviousPage @@ -88,6 +83,12 @@ const getProductQuery = /* GraphQL */ ` } } } + metafields(identifiers: $withMetafields) { + key + value + namespace + description + type } } } diff --git a/packages/swell/src/api/operations/get-product.ts b/packages/swell/src/api/operations/get-product.ts index fff62570f..1469b99a5 100644 --- a/packages/swell/src/api/operations/get-product.ts +++ b/packages/swell/src/api/operations/get-product.ts @@ -1,3 +1,5 @@ +import type { GetProductOperation } from '@vercel/commerce/types/product' + import { normalizeProduct } from '../../utils' import { Product } from '@vercel/commerce/types/product' @@ -7,15 +9,15 @@ import { Provider, SwellConfig } from '../' export default function getProductOperation({ commerce, }: OperationContext) { - async function getProduct({ + async function getProduct({ variables, config: cfg, }: { query?: string - variables: { slug: string } + variables: T['variables'] config?: Partial preview?: boolean - }): Promise { + }): Promise { const config = commerce.getConfig(cfg) const product = await config.fetch('products', 'get', [variables.slug]) @@ -24,9 +26,7 @@ export default function getProductOperation({ product.variants = product.variants?.results } - return { - product: product ? normalizeProduct(product) : null, - } + return product ? { product: normalizeProduct(product) } : {} } return getProduct diff --git a/packages/vendure/src/api/operations/get-product.ts b/packages/vendure/src/api/operations/get-product.ts index 93fb50f1e..19d24923d 100644 --- a/packages/vendure/src/api/operations/get-product.ts +++ b/packages/vendure/src/api/operations/get-product.ts @@ -1,22 +1,26 @@ -import { Product } from '@vercel/commerce/types/product' -import { OperationContext } from '@vercel/commerce/api/operations' -import { Provider, VendureConfig } from '../' -import { GetProductQuery } from '../../../schema' +import type { + Product, + GetProductOperation, +} 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' export default function getProductOperation({ commerce, }: OperationContext) { - async function getProduct({ + async function getProduct({ query = getProductQuery, variables, config: cfg, }: { query?: string - variables: { slug: string } + variables: T['variables'] config?: Partial preview?: boolean - }): Promise { + }): Promise { const config = commerce.getConfig(cfg) const locale = config.locale diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64a6a5c97..2297bbe7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,7 @@ importers: packages/bigcommerce: specifiers: '@cfworker/uuid': ^1.12.4 + '@manifoldco/swagger-to-ts': ^2.1.0 '@taskr/clear': ^1.1.0 '@taskr/esnext': ^1.1.0 '@taskr/watch': ^1.1.0 @@ -33,6 +34,7 @@ importers: lint-staged: ^12.1.7 lodash.debounce: ^4.0.8 next: ^12.0.8 + node-fetch: ^2.6.7 prettier: ^2.5.1 react: ^18.2.0 react-dom: ^18.2.0 @@ -51,6 +53,7 @@ importers: lodash.debounce: 4.0.8 uuidv4: 6.2.13 devDependencies: + '@manifoldco/swagger-to-ts': 2.1.0 '@taskr/clear': 1.1.0 '@taskr/esnext': 1.1.0 '@taskr/watch': 1.1.0 @@ -62,6 +65,7 @@ importers: '@types/react': 18.0.20 lint-staged: 12.5.0 next: 12.3.0_biqbaboplfbrettd7655fr4n2y + node-fetch: 2.6.7 prettier: 2.7.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -3493,6 +3497,7 @@ packages: - supports-color dev: false +<<<<<<< HEAD /@motionone/animation/10.15.1: resolution: {integrity: sha512-mZcJxLjHor+bhcPuIFErMDNyrdb2vJur8lSfMCsuCB4UyV8ILZLvK+t+pg56erv8ud9xQGK/1OGPt10agPrCyQ==} dependencies: @@ -3539,6 +3544,19 @@ packages: hey-listen: 1.0.8 tslib: 2.4.0 dev: false +======= + /@manifoldco/swagger-to-ts/2.1.0: + resolution: {integrity: sha512-IH0FAHhwWHR3Gs3rnVHNEscZujGn+K6/2Zu5cWfZre3Vz2tx1SvvJKEbSM89MztfDDRjOpb+6pQD/vqdEoTBVg==} + engines: {node: '>= 10.0.0'} + deprecated: This package has changed to openapi-typescript + hasBin: true + dependencies: + chalk: 4.1.2 + js-yaml: 3.14.1 + meow: 7.1.1 + prettier: 2.7.1 + dev: true +>>>>>>> 1ba9d3bd6e79da1f0b05df2f1c0c1ca3632b6c16 /@next/bundle-analyzer/12.3.0: resolution: {integrity: sha512-hzRLHIrtwOiGEku9rmG7qZk+OQhnqQOL+ycl2XrjBaztBN/xaqnjoG4+HEf9L7ELN943BR+K/ZlaF2OEgbGm+Q==} @@ -4431,6 +4449,10 @@ packages: /@types/lodash/4.14.185: resolution: {integrity: sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA==} + /@types/minimist/1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + /@types/node-fetch/2.6.2: resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} dependencies: @@ -4446,6 +4468,10 @@ packages: resolution: {integrity: sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==} dev: true + /@types/normalize-package-data/2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true + /@types/parse-json/4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} @@ -4768,6 +4794,12 @@ packages: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} dev: false + /argparse/1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + /argparse/2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true @@ -4870,6 +4902,11 @@ packages: es-shim-unscopables: 1.0.0 dev: true + /arrify/1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + /asap/2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} dev: true @@ -5204,6 +5241,15 @@ packages: engines: {node: '>= 6'} dev: false + /camelcase-keys/6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: true + /camelcase/5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -5728,6 +5774,14 @@ packages: supports-color: 9.2.3 dev: true + /decamelize-keys/1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + /decamelize/1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -6285,6 +6339,12 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /esprima/4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + /esquery/1.4.0: resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} engines: {node: '>=0.10'} @@ -6971,6 +7031,11 @@ packages: duplexer: 0.1.2 dev: true + /hard-rejection/2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true + /has-ansi/2.0.0: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} @@ -7052,6 +7117,7 @@ packages: tslib: 2.4.0 dev: true +<<<<<<< HEAD /hey-listen/1.0.8: resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} dev: false @@ -7061,6 +7127,11 @@ packages: dependencies: react-is: 16.13.1 dev: false +======= + /hosted-git-info/2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true +>>>>>>> 1ba9d3bd6e79da1f0b05df2f1c0c1ca3632b6c16 /http-cache-semantics/4.1.0: resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} @@ -7512,6 +7583,11 @@ packages: symbol-observable: 1.2.0 dev: true + /is-plain-obj/1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + /is-plain-object/2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -7660,6 +7736,14 @@ packages: /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + /js-yaml/3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: true + /js-yaml/4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -8154,6 +8238,16 @@ packages: engines: {node: '>=0.10.0'} dev: true + /map-obj/1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj/4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true + /map-visit/1.0.0: resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} engines: {node: '>=0.10.0'} @@ -8172,6 +8266,23 @@ packages: resolution: {integrity: sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==} dev: true + /meow/7.1.1: + resolution: {integrity: sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==} + engines: {node: '>=10'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 2.5.0 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.13.1 + yargs-parser: 18.1.3 + dev: true + /merge-stream/2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -8283,6 +8394,11 @@ packages: engines: {node: '>=4'} dev: true + /min-indent/1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -8295,6 +8411,15 @@ packages: brace-expansion: 1.1.11 dev: true + /minimist-options/4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + dev: true + /minimist/1.2.6: resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} @@ -8513,6 +8638,15 @@ packages: /node-releases/2.0.6: resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} + /normalize-package-data/2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.1 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + dev: true + /normalize-path/2.1.1: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} engines: {node: '>=0.10.0'} @@ -9424,6 +9558,11 @@ packages: /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + /quick-lru/4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: true + /quick-lru/5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -9628,6 +9767,25 @@ packages: pify: 2.3.0 dev: false + /read-pkg-up/7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + dev: true + + /read-pkg/5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + dev: true + /readable-stream/2.3.7: resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} dependencies: @@ -9666,6 +9824,14 @@ packages: dependencies: picomatch: 2.3.1 + /redent/3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + dev: true + /redis-commands/1.7.0: resolution: {integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==} dev: false @@ -10123,6 +10289,28 @@ packages: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} + /spdx-correct/3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-exceptions/2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse/3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-license-ids/3.0.12: + resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + dev: true + /split-string/3.1.0: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} @@ -10136,6 +10324,10 @@ packages: tslib: 2.4.0 dev: true + /sprintf-js/1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true + /ssri/8.0.1: resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} engines: {node: '>= 8'} @@ -10292,6 +10484,13 @@ packages: engines: {node: '>=12'} dev: true + /strip-indent/3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + /strip-json-comments/2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -10529,6 +10728,11 @@ packages: /tr46/0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + /trim-newlines/3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + dev: true + /ts-log/2.2.4: resolution: {integrity: sha512-DEQrfv6l7IvN2jlzc/VTdZJYsWUnQNCsueYjMkC/iXoEoi5fNan6MjeDqkvhfzbmHgdz9UxDUluX3V5HdjTydQ==} dev: true @@ -10764,6 +10968,11 @@ packages: prelude-ls: 1.2.1 dev: true + /type-fest/0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + dev: true + /type-fest/0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -10774,6 +10983,16 @@ packages: engines: {node: '>=10'} dev: true + /type-fest/0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + dev: true + + /type-fest/0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + /typescript/4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} @@ -10972,6 +11191,13 @@ packages: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true + /validate-npm-package-license/3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + dev: true + /value-or-promise/1.0.11: resolution: {integrity: sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==} engines: {node: '>=12'} diff --git a/site/components/product/ProductCustomFields/ProductCustomFields.tsx b/site/components/product/ProductCustomFields/ProductCustomFields.tsx new file mode 100644 index 000000000..c0254bf17 --- /dev/null +++ b/site/components/product/ProductCustomFields/ProductCustomFields.tsx @@ -0,0 +1,23 @@ +import type { CustomField } from '@commerce/types/common' + +interface Props { + customFields: CustomField[] +} + +const ProductCustomFields: React.FC = ({ customFields }) => { + return ( + <> + {customFields.map((field) => ( +
+ {field.name}: + {field.value} +
+ ))} + + ) +} + +export default ProductCustomFields diff --git a/site/components/product/ProductCustomFields/index.ts b/site/components/product/ProductCustomFields/index.ts new file mode 100644 index 000000000..1c63f7ea6 --- /dev/null +++ b/site/components/product/ProductCustomFields/index.ts @@ -0,0 +1 @@ +export { default as ProductCustomFields } from './ProductCustomFields' diff --git a/site/components/product/ProductMetafields/ProductMetafields.tsx b/site/components/product/ProductMetafields/ProductMetafields.tsx new file mode 100644 index 000000000..df8072cdd --- /dev/null +++ b/site/components/product/ProductMetafields/ProductMetafields.tsx @@ -0,0 +1,29 @@ +import type { FC } from 'react' +import type { Metafields } from '@commerce/types/common' +import Text from '@components/ui/Text' + +interface Props { + metafields: Metafields + /** + * The namespace of the metafields to display. + */ + namespace: string +} + +const ProductMetafields: FC = ({ metafields, namespace }) => { + return ( + <> + {Object.values(metafields[namespace] ?? {}).map((field) => ( +
+ {field.name}: + +
+ ))} + + ) +} + +export default ProductMetafields diff --git a/site/components/product/ProductMetafields/index.ts b/site/components/product/ProductMetafields/index.ts new file mode 100644 index 000000000..2e362c321 --- /dev/null +++ b/site/components/product/ProductMetafields/index.ts @@ -0,0 +1 @@ +export { default as ProductMetafields } from './ProductMetafields' diff --git a/site/components/product/ProductSidebar/ProductSidebar.tsx b/site/components/product/ProductSidebar/ProductSidebar.tsx index 67c3dab9f..3165ce3a7 100644 --- a/site/components/product/ProductSidebar/ProductSidebar.tsx +++ b/site/components/product/ProductSidebar/ProductSidebar.tsx @@ -10,6 +10,8 @@ import { SelectedOptions, } from '../helpers' import ErrorMessage from '@components/ui/ErrorMessage' +import { ProductCustomFields } from '../ProductCustomFields' +import { ProductMetafields } from '../ProductMetafields' interface ProductSidebarProps { product: Product @@ -62,10 +64,16 @@ const ProductSidebar: FC = ({ product, className }) => { className="pb-4 break-words w-full max-w-xl" html={product.descriptionHtml || product.description} /> -
- -
36 reviews
-
+ + {product.metafields?.reviews?.rating && ( +
+ +
+ {product.metafields.reviews.count?.value ?? 0} reviews +
+
+ )} +
{error && } {process.env.COMMERCE_CART_ENABLED && ( @@ -88,11 +96,27 @@ const ProductSidebar: FC = ({ product, className }) => { This is a limited edition production run. Printing starts when the drop ends. + This is a limited edition production run. Printing starts when the drop ends. Reminder: Bad Boys For Life. Shipping may take 10+ days due to COVID-19. + + {product.customFields && product.customFields?.length > 0 && ( + + + + )} + + {product.metafields?.my_fields && ( + + + + )}
) diff --git a/site/pages/product/[slug].tsx b/site/pages/product/[slug].tsx index b49aa6398..f82c4a6be 100644 --- a/site/pages/product/[slug].tsx +++ b/site/pages/product/[slug].tsx @@ -3,11 +3,22 @@ import type { GetStaticPropsContext, InferGetStaticPropsType, } from 'next' -import { useRouter } from 'next/router' + import commerce from '@lib/api/commerce' + +import { useRouter } from 'next/router' import { Layout } from '@components/common' import { ProductView } from '@components/product' +// Used by the Shopify Example +const withMetafields = [ + { namespace: 'reviews', key: 'rating' }, + { namespace: 'reviews', key: 'count' }, + { namespace: 'my_fields', key: 'width' }, + { namespace: 'my_fields', key: 'weight' }, + { namespace: 'my_fields', key: 'length' }, +] + export async function getStaticProps({ params, locale, @@ -18,7 +29,10 @@ export async function getStaticProps({ const pagesPromise = commerce.getAllPages({ config, preview }) const siteInfoPromise = commerce.getSiteInfo({ config, preview }) const productPromise = commerce.getProduct({ - variables: { slug: params!.slug }, + variables: { + slug: params!.slug, + withMetafields, + }, config, preview, })