From 3654cb47d5924acbe880838ac665c2e7ae3582e4 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Sun, 25 Oct 2020 15:14:54 -0500 Subject: [PATCH] Added locale metadata for products and product page --- lib/bigcommerce/api/fragments/product.ts | 9 ++++++ lib/bigcommerce/api/index.ts | 4 +++ .../api/operations/get-all-products.ts | 22 +++++++++++--- lib/bigcommerce/api/operations/get-product.ts | 17 +++++++++-- .../api/utils/set-product-locale-meta.ts | 21 ++++++++++++++ lib/bigcommerce/schema.d.ts | 18 ++++++++++++ pages/index.tsx | 10 +++++-- pages/product/[slug].tsx | 29 +++++++++++++++---- 8 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 lib/bigcommerce/api/utils/set-product-locale-meta.ts diff --git a/lib/bigcommerce/api/fragments/product.ts b/lib/bigcommerce/api/fragments/product.ts index 7c81e0b2e..69773cea5 100644 --- a/lib/bigcommerce/api/fragments/product.ts +++ b/lib/bigcommerce/api/fragments/product.ts @@ -78,6 +78,15 @@ export const productInfoFragment = /* GraphQL */ ` } } } + localeMeta: metafields(namespace: $locale, keys: ["name", "description"]) + @include(if: $hasLocale) { + edges { + node { + key + value + } + } + } } ${responsiveImageFragment} diff --git a/lib/bigcommerce/api/index.ts b/lib/bigcommerce/api/index.ts index 1ab33756c..57e2c7a2f 100644 --- a/lib/bigcommerce/api/index.ts +++ b/lib/bigcommerce/api/index.ts @@ -28,6 +28,9 @@ export type ProductImageVariables = Pick< > export interface BigcommerceConfigOptions extends CommerceAPIConfig { + // Indicates if the returned metadata with translations should be applied to the + // data or returned as it is + applyLocale?: boolean images?: Images storeApiUrl: string storeApiToken: string @@ -113,6 +116,7 @@ const config = new Config({ cartCookie: process.env.BIGCOMMERCE_CART_COOKIE ?? 'bc_cartId', cartCookieMaxAge: ONE_DAY * 30, fetch: fetchGraphqlApi, + applyLocale: true, // REST API only storeApiUrl: STORE_API_URL, storeApiToken: STORE_API_TOKEN, diff --git a/lib/bigcommerce/api/operations/get-all-products.ts b/lib/bigcommerce/api/operations/get-all-products.ts index 699fa8310..64c9e7baa 100644 --- a/lib/bigcommerce/api/operations/get-all-products.ts +++ b/lib/bigcommerce/api/operations/get-all-products.ts @@ -4,11 +4,14 @@ import type { } from '@lib/bigcommerce/schema' import type { RecursivePartial, RecursiveRequired } from '../utils/types' import filterEdges from '../utils/filter-edges' +import setProductLocaleMeta from '../utils/set-product-locale-meta' import { productConnectionFragment } from '../fragments/product' import { BigcommerceConfig, getConfig, Images, ProductImageVariables } from '..' export const getAllProductsQuery = /* GraphQL */ ` query getAllProducts( + $hasLocale: Boolean = false + $locale: String = "null" $entityIds: [Int!] $first: Int = 10 $imgSmallWidth: Int = 320 @@ -69,7 +72,10 @@ export type ProductTypes = | 'newestProducts' export type ProductVariables = { field?: ProductTypes } & Images & - Omit + Omit< + GetAllProductsQueryVariables, + ProductTypes | keyof ProductImageVariables | 'hasLocale' + > async function getAllProducts(opts?: { variables?: ProductVariables @@ -96,9 +102,12 @@ async function getAllProducts({ } = {}): Promise { config = getConfig(config) + const locale = vars.locale || config.locale const variables: GetAllProductsQueryVariables = { ...config.imageVariables, ...vars, + locale, + hasLocale: !!locale, } if (!FIELDS.includes(field)) { @@ -115,11 +124,16 @@ async function getAllProducts({ query, { variables } ) - const products = data.site?.[field]?.edges + const edges = data.site?.[field]?.edges + const products = filterEdges(edges as RecursiveRequired) - return { - products: filterEdges(products as RecursiveRequired), + if (locale && config.applyLocale) { + products.forEach((product: RecursivePartial) => { + if (product.node) setProductLocaleMeta(product.node) + }) } + + return { products } } export default getAllProducts diff --git a/lib/bigcommerce/api/operations/get-product.ts b/lib/bigcommerce/api/operations/get-product.ts index 95b3bdc31..2fe06413f 100644 --- a/lib/bigcommerce/api/operations/get-product.ts +++ b/lib/bigcommerce/api/operations/get-product.ts @@ -3,11 +3,14 @@ import type { GetProductQueryVariables, } from 'lib/bigcommerce/schema' import type { RecursivePartial, RecursiveRequired } from '../utils/types' +import setProductLocaleMeta from '../utils/set-product-locale-meta' import { productInfoFragment } from '../fragments/product' import { BigcommerceConfig, getConfig, Images } from '..' export const getProductQuery = /* GraphQL */ ` query getProduct( + $hasLocale: Boolean = false + $locale: String = "null" $path: String! $imgSmallWidth: Int = 320 $imgSmallHeight: Int @@ -42,8 +45,10 @@ export type GetProductResult< T extends { product?: any } = { product?: ProductNode } > = T -export type ProductVariables = Images & - ({ path: string; slug?: never } | { path?: never; slug: string }) +export type ProductVariables = Images & { locale?: string } & ( + | { path: string; slug?: never } + | { path?: never; slug: string } + ) async function getProduct(opts: { variables: ProductVariables @@ -66,9 +71,13 @@ async function getProduct({ config?: BigcommerceConfig }): Promise { config = getConfig(config) + + const locale = vars.locale || config.locale const variables: GetProductQueryVariables = { ...config.imageVariables, ...vars, + locale, + hasLocale: !!locale, path: slug ? `/${slug}/` : vars.path!, } const { data } = await config.fetch>( @@ -78,6 +87,10 @@ async function getProduct({ const product = data.site?.route?.node if (product?.__typename === 'Product') { + if (locale && config.applyLocale) { + setProductLocaleMeta(product) + } + return { product: product as RecursiveRequired, } diff --git a/lib/bigcommerce/api/utils/set-product-locale-meta.ts b/lib/bigcommerce/api/utils/set-product-locale-meta.ts new file mode 100644 index 000000000..767286477 --- /dev/null +++ b/lib/bigcommerce/api/utils/set-product-locale-meta.ts @@ -0,0 +1,21 @@ +import type { ProductNode } from '../operations/get-all-products' +import type { RecursivePartial } from './types' + +export default function setProductLocaleMeta( + node: RecursivePartial +) { + if (node.localeMeta?.edges) { + node.localeMeta.edges = node.localeMeta.edges.filter((edge) => { + const { key, value } = edge?.node ?? {} + if (key && key in node) { + ;(node as any)[key] = value + return false + } + return true + }) + + if (!node.localeMeta.edges.length) { + delete node.localeMeta + } + } +} diff --git a/lib/bigcommerce/schema.d.ts b/lib/bigcommerce/schema.d.ts index cf7168992..06c56be51 100644 --- a/lib/bigcommerce/schema.d.ts +++ b/lib/bigcommerce/schema.d.ts @@ -1807,6 +1807,20 @@ export type ProductInfoFragment = { __typename?: 'Product' } & Pick< > > } + localeMeta: { __typename?: 'MetafieldConnection' } & { + edges?: Maybe< + Array< + Maybe< + { __typename?: 'MetafieldEdge' } & { + node: { __typename?: 'Metafields' } & Pick< + Metafields, + 'key' | 'value' + > + } + > + > + > + } } export type ProductConnnectionFragment = { @@ -1848,6 +1862,8 @@ export type GetAllProductPathsQuery = { __typename?: 'Query' } & { } export type GetAllProductsQueryVariables = Exact<{ + hasLocale?: Maybe + locale?: Maybe entityIds?: Maybe> first?: Maybe imgSmallWidth?: Maybe @@ -1880,6 +1896,8 @@ export type GetAllProductsQuery = { __typename?: 'Query' } & { } export type GetProductQueryVariables = Exact<{ + hasLocale?: Maybe + locale?: Maybe path: Scalars['String'] imgSmallWidth?: Maybe imgSmallHeight?: Maybe diff --git a/pages/index.tsx b/pages/index.tsx index 856777cfa..3476f2cc7 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,5 +1,6 @@ import { useMemo } from 'react' import { GetStaticPropsContext, InferGetStaticPropsType } from 'next' +import { getConfig } from '@lib/bigcommerce/api' import getAllProducts from '@lib/bigcommerce/api/operations/get-all-products' import getSiteInfo from '@lib/bigcommerce/api/operations/get-site-info' import getAllPages from '@lib/bigcommerce/api/operations/get-all-pages' @@ -12,19 +13,22 @@ export async function getStaticProps({ preview, locale, }: GetStaticPropsContext) { - console.log('LOCALE', locale) + const config = getConfig({ locale }) const { products: featuredProducts } = await getAllProducts({ variables: { field: 'featuredProducts', first: 6 }, + config, }) const { products: bestSellingProducts } = await getAllProducts({ variables: { field: 'bestSellingProducts', first: 6 }, + config, }) const { products: newestProducts } = await getAllProducts({ variables: { field: 'newestProducts', first: 12 }, + config, }) - const { categories, brands } = await getSiteInfo() - const { pages } = await getAllPages() + const { categories, brands } = await getSiteInfo({ config }) + const { pages } = await getAllPages({ config }) return { props: { diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index 7d73b859c..30f708997 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -1,5 +1,10 @@ -import { GetStaticPropsContext, InferGetStaticPropsType } from 'next' +import { + GetStaticPathsContext, + GetStaticPropsContext, + InferGetStaticPropsType, +} from 'next' import { useRouter } from 'next/router' +import { getConfig } from '@lib/bigcommerce/api' import getAllPages from '@lib/bigcommerce/api/operations/get-all-pages' import getProduct from '@lib/bigcommerce/api/operations/get-product' import { Layout } from '@components/core' @@ -8,9 +13,15 @@ import getAllProductPaths from '@lib/bigcommerce/api/operations/get-all-product- export async function getStaticProps({ params, + locale, }: GetStaticPropsContext<{ slug: string }>) { - const { pages } = await getAllPages() - const { product } = await getProduct({ variables: { slug: params!.slug } }) + const config = getConfig({ locale }) + + const { pages } = await getAllPages({ config }) + const { product } = await getProduct({ + variables: { slug: params!.slug }, + config, + }) if (!product) { throw new Error(`Product with slug '${params!.slug}' not found`) @@ -22,11 +33,19 @@ export async function getStaticProps({ } } -export async function getStaticPaths() { +export async function getStaticPaths({ locales }: GetStaticPathsContext) { const { products } = await getAllProductPaths() return { - paths: products.map((product) => `/product${product.node.path}`), + paths: locales + ? locales.reduce((arr, locale) => { + // Add a product path for every locale + products.forEach((product) => { + arr.push(`/${locale}/product${product.node.path}`) + }) + return arr + }, []) + : products.map((product) => `/product${product.node.path}`), // If your store has tons of products, enable fallback mode to improve build times! fallback: false, }