From fdb306a988bc47b3e175a0106f809d101205053c Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Thu, 1 Oct 2020 14:53:29 -0500 Subject: [PATCH] Improved types and added method for product --- .../api/operations/get-all-products.ts | 23 +-- lib/bigcommerce/api/operations/get-product.ts | 131 ++++++++++++++++++ lib/bigcommerce/api/types/index.ts | 4 + lib/bigcommerce/schema.d.ts | 77 ++++++++++ pages/product/{[id].tsx => [slug].tsx} | 30 ++-- 5 files changed, 245 insertions(+), 20 deletions(-) create mode 100644 lib/bigcommerce/api/operations/get-product.ts rename pages/product/{[id].tsx => [slug].tsx} (52%) diff --git a/lib/bigcommerce/api/operations/get-all-products.ts b/lib/bigcommerce/api/operations/get-all-products.ts index c69bea34d..f120e2e43 100644 --- a/lib/bigcommerce/api/operations/get-all-products.ts +++ b/lib/bigcommerce/api/operations/get-all-products.ts @@ -1,9 +1,9 @@ -import { +import type { GetAllProductsQuery, GetAllProductsQueryVariables, } from 'lib/bigcommerce/schema'; +import type { RecursivePartial, RecursiveRequired } from '../types'; import { getConfig, Images, ProductImageVariables } from '..'; -import { RecursivePartial } from '../types'; export const getAllProductsQuery = /* GraphQL */ ` query getAllProducts( @@ -104,42 +104,45 @@ export const getAllProductsQuery = /* GraphQL */ ` export interface GetAllProductsResult { products: T extends GetAllProductsQuery - ? T['site']['products']['edges'] + ? NonNullable : unknown; } export type ProductVariables = Images & Omit; -async function getAllProducts(opts: { - query: string; - variables?: V; -}): Promise>; - async function getAllProducts(opts?: { query?: string; variables?: ProductVariables; }): Promise>; +async function getAllProducts(opts: { + query: string; + variables?: V; +}): Promise>; + async function getAllProducts({ query = getAllProductsQuery, variables: vars, }: { query?: string; variables?: ProductVariables; -} = {}): Promise>> { +} = {}): Promise> { const config = getConfig(); const variables: GetAllProductsQueryVariables = { ...config.imageVariables, ...vars, }; + // RecursivePartial forces the method to check for every prop in the data, which is + // required in case there's a custom `query` const data = await config.fetch>( query, { variables } ); + const products = data.site?.products?.edges; return { - products: data?.site?.products?.edges, + products: (products as RecursiveRequired) ?? [], }; } diff --git a/lib/bigcommerce/api/operations/get-product.ts b/lib/bigcommerce/api/operations/get-product.ts new file mode 100644 index 000000000..007959d1e --- /dev/null +++ b/lib/bigcommerce/api/operations/get-product.ts @@ -0,0 +1,131 @@ +import type { + GetProductQuery, + GetProductQueryVariables, +} from 'lib/bigcommerce/schema'; +import type { RecursivePartial, RecursiveRequired } from '../types'; +import { getConfig, Images, ProductImageVariables } from '..'; + +export const getProductQuery = /* GraphQL */ ` + query getProduct( + $slug: String! + $imgSmallWidth: Int = 320 + $imgSmallHeight: Int + $imgMediumWidth: Int = 640 + $imgMediumHeight: Int + $imgLargeWidth: Int = 960 + $imgLargeHeight: Int + $imgXLWidth: Int = 1280 + $imgXLHeight: Int + ) { + site { + route(path: $slug) { + node { + __typename + ... on Product { + entityId + name + path + brand { + name + } + description + prices { + price { + currencyCode + value + } + salePrice { + currencyCode + value + } + } + images { + edges { + node { + urlSmall: url(width: $imgSmallWidth, height: $imgSmallHeight) + urlMedium: url( + width: $imgMediumWidth + height: $imgMediumHeight + ) + urlLarge: url(width: $imgLargeWidth, height: $imgLargeHeight) + urlXL: url(width: $imgXLWidth, height: $imgXLHeight) + } + } + } + variants { + edges { + node { + entityId + } + } + } + options { + edges { + node { + entityId + displayName + isRequired + values { + edges { + node { + entityId + label + } + } + } + } + } + } + } + } + } + } + } +`; + +export interface GetProductResult { + product?: T extends GetProductQuery + ? Extract + : unknown; +} + +export type ProductVariables = Images & + Omit; + +async function getProduct(opts: { + query?: string; + variables: ProductVariables; +}): Promise>; + +async function getProduct(opts: { + query: string; + variables: V; +}): Promise>; + +async function getProduct({ + query = getProductQuery, + variables: vars, +}: { + query?: string; + variables: ProductVariables; +}): Promise> { + const config = getConfig(); + const variables: GetProductQueryVariables = { + ...config.imageVariables, + ...vars, + }; + const data = await config.fetch>(query, { + variables, + }); + const product = data.site?.route?.node; + + if (product?.__typename === 'Product') { + return { + product: product as RecursiveRequired, + }; + } + + return {}; +} + +export default getProduct; diff --git a/lib/bigcommerce/api/types/index.ts b/lib/bigcommerce/api/types/index.ts index 2c0042c2e..74f310f68 100644 --- a/lib/bigcommerce/api/types/index.ts +++ b/lib/bigcommerce/api/types/index.ts @@ -1,3 +1,7 @@ export type RecursivePartial = { [P in keyof T]?: RecursivePartial; }; + +export type RecursiveRequired = { + [P in keyof T]-?: RecursiveRequired; +}; diff --git a/lib/bigcommerce/schema.d.ts b/lib/bigcommerce/schema.d.ts index 52c89ecbf..5d8f5bd6c 100644 --- a/lib/bigcommerce/schema.d.ts +++ b/lib/bigcommerce/schema.d.ts @@ -1736,3 +1736,80 @@ export type GetAllProductsQuery = ( ) } ) } ); + +export type GetProductQueryVariables = Exact<{ + slug: Scalars['String']; + imgSmallWidth?: Maybe; + imgSmallHeight?: Maybe; + imgMediumWidth?: Maybe; + imgMediumHeight?: Maybe; + imgLargeWidth?: Maybe; + imgLargeHeight?: Maybe; + imgXLWidth?: Maybe; + imgXLHeight?: Maybe; +}>; + + +export type GetProductQuery = ( + { __typename?: 'Query' } + & { site: ( + { __typename?: 'Site' } + & { route: ( + { __typename?: 'Route' } + & { node?: Maybe<{ __typename: 'Brand' } | { __typename: 'Category' } | ( + { __typename: 'Product' } + & Pick + & { brand?: Maybe<( + { __typename?: 'Brand' } + & Pick + )>, prices?: Maybe<( + { __typename?: 'Prices' } + & { price: ( + { __typename?: 'Money' } + & Pick + ), salePrice?: Maybe<( + { __typename?: 'Money' } + & Pick + )> } + )>, images: ( + { __typename?: 'ImageConnection' } + & { edges?: Maybe>> } + ), variants: ( + { __typename?: 'VariantConnection' } + & { edges?: Maybe + ) } + )>>> } + ), options: ( + { __typename?: 'OptionConnection' } + & { edges?: Maybe + & { values: ( + { __typename?: 'OptionValueConnection' } + & { edges?: Maybe + ) } + )>>> } + ) } + ) } + )>>> } + ) } + ) | { __typename: 'Variant' }> } + ) } + ) } +); diff --git a/pages/product/[id].tsx b/pages/product/[slug].tsx similarity index 52% rename from pages/product/[id].tsx rename to pages/product/[slug].tsx index e5a7801fe..fbf1376e3 100644 --- a/pages/product/[id].tsx +++ b/pages/product/[slug].tsx @@ -1,10 +1,18 @@ -import { useRouter } from "next/router"; -import { Layout } from "@components/core"; -import { ProductView } from "@components/product"; +import { GetStaticPropsContext, InferGetStaticPropsType } from 'next'; +import { useRouter } from 'next/router'; +import getProduct from 'lib/bigcommerce/api/operations/get-product'; +import { Layout } from '@components/core'; +import { ProductView } from '@components/product'; + +export async function getStaticProps({ + params, +}: GetStaticPropsContext<{ slug: string }>) { + const { product } = await getProduct({ variables: { slug: params!.slug } }); + + console.log('PRODUCT', product); -export async function getStaticProps() { const productData = { - title: "T-Shirt", + title: 'T-Shirt', description: ` Nothing undercover about this tee. Nope. This is the official Bad Boys tee. Printed in white or black ink on Black, Brown, or Oatmeal. @@ -13,9 +21,9 @@ export async function getStaticProps() { run. Printing starts when the drop ends. Reminder: Bad Boys For Life. Shipping may take 10+ days due to COVID-19. `, - price: "$50", - colors: ["black", "white", "pink"], - sizes: ["s", "m", "l", "xl", "xxl"], + price: '$50', + colors: ['black', 'white', 'pink'], + sizes: ['s', 'm', 'l', 'xl', 'xxl'], }; return { props: { @@ -28,11 +36,13 @@ export async function getStaticProps() { export async function getStaticPaths() { return { paths: [], - fallback: "unstable_blocking", + fallback: 'unstable_blocking', }; } -export default function Home({ productData }) { +export default function Home({ + productData, +}: InferGetStaticPropsType) { const router = useRouter(); return (