From a5b10aa85621a27332ce867db9dd36245262ceb5 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Thu, 7 Oct 2021 10:53:19 +0700 Subject: [PATCH 1/4] :sparkles: feat: get product detail :%s --- pages/product/[slug].tsx | 57 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index a8e925df9..ee0730314 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -1,9 +1,13 @@ +import { Product } from '@framework/schema' +import commerce from '@lib/api/commerce' +import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next' import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common' import { ProductInfoDetail, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail' import { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data' -export default function Slug() { +export default function Slug({ product } : InferGetStaticPropsType) { + console.log("product: ", product) return <> @@ -11,8 +15,57 @@ export default function Slug() { - + } +export async function getStaticProps({ + params, + locale, + locales, + preview, +}: GetStaticPropsContext<{ slug: string }>) { + const config = { locale, locales } + const productPromise = commerce.getProduct({ + variables: { slug: params!.slug }, + config, + preview, + }) + + console.log('slug: ', params!.slug) + + + const { product } = await productPromise + + if (!product) { + throw new Error(`Product with slug '${params!.slug}' not found`) + } + + return { + props: { + product, + }, + revalidate: 60, + } +} + + +export async function getStaticPaths({ locales }: GetStaticPathsContext) { + const { products } = await commerce.getAllProductPaths() + + return { + paths: locales + ? locales.reduce((arr, locale) => { + // Add a product path for every locale + products.forEach((product: any) => { + arr.push(`/${locale}/product${product.path}`) + }) + return arr + }, []) + : products.map((product: any) => `/product${product.path}`), + fallback: 'blocking', + } +} + + Slug.Layout = Layout From 5c71671bf2bdcce4308180f4966a87dd8719dc2a Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Thu, 7 Oct 2021 10:56:22 +0700 Subject: [PATCH 2/4] :sparkles: feat: getStaticProps in product detail :%s --- pages/product/[slug].tsx | 45 +++++++++++++++++++++--------------- pages/products.tsx | 4 ++-- src/utils/constanst.utils.ts | 1 + 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index ee0730314..fa307d1c2 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -1,12 +1,14 @@ -import { Product } from '@framework/schema' import commerce from '@lib/api/commerce' import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next' import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common' import { ProductInfoDetail, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail' +import { REVALIDATE_TIME } from 'src/utils/constanst.utils' import { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data' +import { getAllPromies } from 'src/utils/funtion.utils' +import { PromiseWithKey } from 'src/utils/types.utils' -export default function Slug({ product } : InferGetStaticPropsType) { +export default function Slug({ product }: InferGetStaticPropsType) { console.log("product: ", product) return <> @@ -26,26 +28,31 @@ export async function getStaticProps({ preview, }: GetStaticPropsContext<{ slug: string }>) { const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + const productPromise = commerce.getProduct({ variables: { slug: params!.slug }, config, preview, }) + promisesWithKey.push({ key: 'product', promise: productPromise, keyResult: 'product' }) - console.log('slug: ', params!.slug) + try { + const promises = getAllPromies(promisesWithKey) + const rs = await Promise.all(promises) + promisesWithKey.map((item, index) => { + props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] + return null + }) - const { product } = await productPromise + return { + props, + revalidate: REVALIDATE_TIME, + } + } catch (err) { - if (!product) { - throw new Error(`Product with slug '${params!.slug}' not found`) - } - - return { - props: { - product, - }, - revalidate: 60, } } @@ -56,12 +63,12 @@ export async function getStaticPaths({ locales }: GetStaticPathsContext) { return { paths: locales ? locales.reduce((arr, locale) => { - // Add a product path for every locale - products.forEach((product: any) => { - arr.push(`/${locale}/product${product.path}`) - }) - return arr - }, []) + // Add a product path for every locale + products.forEach((product: any) => { + arr.push(`/${locale}/product${product.path}`) + }) + return arr + }, []) : products.map((product: any) => `/product${product.path}`), fallback: 'blocking', } diff --git a/pages/products.tsx b/pages/products.tsx index 9559538df..4a37ab097 100644 --- a/pages/products.tsx +++ b/pages/products.tsx @@ -5,7 +5,7 @@ import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; import { ViewedProducts } from 'src/components/modules/product-detail'; import ProductListFilter from 'src/components/modules/product-list/ProductListFilter/ProductListFilter'; -import { CODE_FACET_BRAND, CODE_FACET_FEATURED, DEFAULT_PAGE_SIZE } from 'src/utils/constanst.utils'; +import { CODE_FACET_BRAND, CODE_FACET_FEATURED, DEFAULT_PAGE_SIZE, REVALIDATE_TIME } from 'src/utils/constanst.utils'; import { getAllPromies } from 'src/utils/funtion.utils'; import { PromiseWithKey, SortOrder } from 'src/utils/types.utils'; import ProductListBanner from '../src/components/modules/product-list/ProductListBanner/ProductListBanner'; @@ -87,7 +87,7 @@ export async function getStaticProps({ return { props, - revalidate: 60, + revalidate: REVALIDATE_TIME, } } catch (err) { diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index 15c1004f2..6736e77c0 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -1,5 +1,6 @@ import DefaultImg from '../../public/assets/images/default_img.jpg' +export const REVALIDATE_TIME = 60 export const BLUR_DATA_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mN8fBIAApUBruKYvzsAAAAASUVORK5CYII=' export const DEFAULT_IMG = DefaultImg From fed42bac87b1e599db4ca390c25bdf531b9da2d1 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Thu, 7 Oct 2021 14:09:23 +0700 Subject: [PATCH 3/4] :sparkles: feat:(product detail) get relevant product :%s --- framework/commerce/types/product.ts | 4 +- .../vendure/api/operations/get-product.ts | 17 +++----- framework/vendure/schema.d.ts | 12 ++++++ .../utils/queries/get-product-query.ts | 3 ++ pages/product/[slug].tsx | 41 +++++++++++++++---- .../ReleventProducts/ReleventProducts.tsx | 10 +++-- src/utils/constanst.utils.ts | 1 + 7 files changed, 66 insertions(+), 22 deletions(-) diff --git a/framework/commerce/types/product.ts b/framework/commerce/types/product.ts index e1a8da200..e02113814 100644 --- a/framework/commerce/types/product.ts +++ b/framework/commerce/types/product.ts @@ -43,8 +43,10 @@ export type Product = { slug?: string path?: string images: ProductImage[] - price: ProductPrice + price: number + currencyCode: CurrencyCode options: ProductOption[] + facetValueIds?: string[] } export type ProductCard = { diff --git a/framework/vendure/api/operations/get-product.ts b/framework/vendure/api/operations/get-product.ts index 4ab9ed2d9..239eb5b61 100644 --- a/framework/vendure/api/operations/get-product.ts +++ b/framework/vendure/api/operations/get-product.ts @@ -16,10 +16,8 @@ export default function getProductOperation({ variables: { slug: string } config?: Partial preview?: boolean - }): Promise { + }): Promise { const config = commerce.getConfig(cfg) - - const locale = config.locale const { data } = await config.fetch(query, { variables }) const product = data.product @@ -28,7 +26,6 @@ export default function getProductOperation({ return product.optionGroups.find((og) => og.id === id)!.name } return { - product: { id: product.id, name: product.name, description: product.description, @@ -49,20 +46,18 @@ export default function getProductOperation({ values: [{ label: o.name }], })), })), - price: { - value: product.variants[0].priceWithTax / 100, - currencyCode: product.variants[0].currencyCode, - }, + price: product.variants[0].priceWithTax / 100, + currencyCode: product.variants[0].currencyCode, options: product.optionGroups.map((og) => ({ id: og.id, displayName: og.name, values: og.options.map((o) => ({ label: o.name })), })), - } as Product, - } + facetValueIds: product.facetValues.map(item=> item.id) + } as Product } - return {} + return null } return getProduct diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 8f49e2d2d..d83b0276a 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3338,6 +3338,18 @@ export type GetProductQuery = { __typename?: 'Query' } & { > } > + facetValues: Array< + { __typename?: 'FacetValue' } & Pick< + FacetValue, + 'id' + > + > + collections: Array< + { __typename?: 'Collection' } & Pick< + Collection, + 'id' + > + > } > } diff --git a/framework/vendure/utils/queries/get-product-query.ts b/framework/vendure/utils/queries/get-product-query.ts index b2c502da9..f9d4e7002 100644 --- a/framework/vendure/utils/queries/get-product-query.ts +++ b/framework/vendure/utils/queries/get-product-query.ts @@ -36,6 +36,9 @@ export const getProductQuery = /* GraphQL */ ` name } } + facetValues { + id + } } } ` diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index fa307d1c2..f3e31a655 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -1,21 +1,21 @@ +import { Product } from '@framework/schema' import commerce from '@lib/api/commerce' import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next' import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common' import { ProductInfoDetail, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail' -import { REVALIDATE_TIME } from 'src/utils/constanst.utils' +import { MAX_PRODUCT_CAROUSEL, REVALIDATE_TIME } from 'src/utils/constanst.utils' import { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data' import { getAllPromies } from 'src/utils/funtion.utils' import { PromiseWithKey } from 'src/utils/types.utils' -export default function Slug({ product }: InferGetStaticPropsType) { - console.log("product: ", product) +export default function Slug({ product, relevantProducts }: InferGetStaticPropsType) { return <> - + @@ -31,12 +31,34 @@ export async function getStaticProps({ let promisesWithKey = [] as PromiseWithKey[] let props = {} as any - const productPromise = commerce.getProduct({ + const product = await commerce.getProduct({ variables: { slug: params!.slug }, config, preview, }) - promisesWithKey.push({ key: 'product', promise: productPromise, keyResult: 'product' }) + props.product = product + + + if (!product) { + throw new Error(`Product with slug '${params!.slug}' not found`) + } + + // relevant product + const relevantFacetIds = product.facetValueIds + if (relevantFacetIds && relevantFacetIds.length > 0) { + const relevantProductsPromise = commerce.getAllProducts({ + variables: { + first: MAX_PRODUCT_CAROUSEL, + facetValueIds: relevantFacetIds, + }, + config, + preview, + }) + promisesWithKey.push({ key: 'relevantProducts', promise: relevantProductsPromise, keyResult: 'products' }) + } else { + props.relevantProducts = [] + } + try { const promises = getAllPromies(promisesWithKey) @@ -47,12 +69,17 @@ export async function getStaticProps({ return null }) + if (props.relevantProducts.length > 0) { + const relevantProducts = props.relevantProducts.filter((item: Product) => item.id !== product.id) + props.relevantProducts = relevantProducts + } + return { props, revalidate: REVALIDATE_TIME, } } catch (err) { - + console.log('err: ', err) } } diff --git a/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx b/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx index e11d31065..d6480beb4 100644 --- a/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx +++ b/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx @@ -1,13 +1,17 @@ +import { ProductCard } from '@commerce/types/product'; import React from 'react'; import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo'; -import { PRODUCT_DATA_TEST } from 'src/utils/demo-data'; -const ReleventProducts = () => { +interface Props { + data: ProductCard[] +} + +const ReleventProducts = ({ data }: Props) => { return ( ); }; diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index 6736e77c0..7d0cf9ba5 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -1,6 +1,7 @@ import DefaultImg from '../../public/assets/images/default_img.jpg' export const REVALIDATE_TIME = 60 +export const MAX_PRODUCT_CAROUSEL = 20 export const BLUR_DATA_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mN8fBIAApUBruKYvzsAAAAASUVORK5CYII=' export const DEFAULT_IMG = DefaultImg From 49295234a09acd8460e2fc88b7bc61f7ae66897d Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Thu, 7 Oct 2021 14:16:43 +0700 Subject: [PATCH 4/4] :sparkles: feat: (product detail) show category in relevant products :%s --- pages/product/[slug].tsx | 15 +++++++++--- .../home/FreshProducts/FreshProducts.tsx | 10 +------- .../ReleventProducts/ReleventProducts.tsx | 23 ++++++++++++++++--- src/utils/funtion.utils.ts | 13 +++++++++-- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index f3e31a655..24901277e 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -9,13 +9,13 @@ import { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/uti import { getAllPromies } from 'src/utils/funtion.utils' import { PromiseWithKey } from 'src/utils/types.utils' -export default function Slug({ product, relevantProducts }: InferGetStaticPropsType) { +export default function Slug({ product, relevantProducts, collections }: InferGetStaticPropsType) { return <> - + @@ -43,7 +43,7 @@ export async function getStaticProps({ throw new Error(`Product with slug '${params!.slug}' not found`) } - // relevant product + // relevant product (filter by product detail's facetIds) const relevantFacetIds = product.facetValueIds if (relevantFacetIds && relevantFacetIds.length > 0) { const relevantProductsPromise = commerce.getAllProducts({ @@ -60,6 +60,15 @@ export async function getStaticProps({ } + // collection + const collectionsPromise = commerce.getAllCollections({ + variables: {}, + config, + preview, + }) + promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) + + try { const promises = getAllPromies(promisesWithKey) const rs = await Promise.all(promises) diff --git a/src/components/modules/home/FreshProducts/FreshProducts.tsx b/src/components/modules/home/FreshProducts/FreshProducts.tsx index cf773d495..6d30459f3 100644 --- a/src/components/modules/home/FreshProducts/FreshProducts.tsx +++ b/src/components/modules/home/FreshProducts/FreshProducts.tsx @@ -2,21 +2,13 @@ import { ProductCard } from '@commerce/types/product' import { Collection } from '@framework/schema' import React, { useMemo } from 'react' import { OPTION_ALL, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' +import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils' import { CollectionCarcousel } from '..' interface FreshProductsProps { data: ProductCard[] collections: Collection[] } -const getCategoryNameFromCollectionId = (colelctions: Collection[], collectionId?: string ) => { - if (!collectionId) { - return '' - } - - const collection = colelctions.find(item => item.id === collectionId) - return collection?.name || '' -} - const FreshProducts = ({ data, collections }: FreshProductsProps) => { const dataWithCategory = useMemo(() => { return data.map(item => { diff --git a/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx b/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx index d6480beb4..147115dc2 100644 --- a/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx +++ b/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx @@ -1,17 +1,34 @@ import { ProductCard } from '@commerce/types/product'; -import React from 'react'; +import { Collection } from '@framework/schema'; +import React, { useMemo } from 'react'; import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo'; +import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils'; interface Props { data: ProductCard[] + collections: Collection[] + } -const ReleventProducts = ({ data }: Props) => { +const ReleventProducts = ({ data, collections }: Props) => { + const dataWithCategoryName = useMemo(() => { + return data.map(item => { + return { + ...item, + collection: getCategoryNameFromCollectionId(collections, item.collectionIds ? item.collectionIds[0] : undefined) + } + }) + }, [data, collections]) + + if (data.length === 0) { + return null + } + return ( ); }; diff --git a/src/utils/funtion.utils.ts b/src/utils/funtion.utils.ts index 9f712326d..408d9d0fd 100644 --- a/src/utils/funtion.utils.ts +++ b/src/utils/funtion.utils.ts @@ -1,5 +1,5 @@ import { Facet } from "@commerce/types/facet"; -import { FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d'; +import { Collection, FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d'; import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils"; import { PromiseWithKey, SortOrder } from "./types.utils"; @@ -116,6 +116,15 @@ export function getFacetIdsFromCodes(facets: FacetValue[], codes?: string[]): st return ids } +export const getCategoryNameFromCollectionId = (colelctions: Collection[], collectionId?: string ) => { + if (!collectionId) { + return '' + } + + const collection = colelctions.find(item => item.id === collectionId) + return collection?.name || '' +} + export function getAllPromies(promies: PromiseWithKey[]) { return promies.map(item => item.promise) -} +} \ No newline at end of file