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 a8e925df9..24901277e 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -1,18 +1,114 @@ +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 { 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() { +export default function Slug({ product, relevantProducts, collections }: InferGetStaticPropsType) { return <> - + - + } +export async function getStaticProps({ + params, + locale, + locales, + preview, +}: GetStaticPropsContext<{ slug: string }>) { + const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + + const product = await commerce.getProduct({ + variables: { slug: params!.slug }, + config, + preview, + }) + props.product = product + + + if (!product) { + throw new Error(`Product with slug '${params!.slug}' not found`) + } + + // relevant product (filter by product detail's facetIds) + 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 = [] + } + + + // 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) + + promisesWithKey.map((item, index) => { + props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] + 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) + } +} + + +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 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/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 e11d31065..147115dc2 100644 --- a/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx +++ b/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx @@ -1,13 +1,34 @@ -import React from 'react'; +import { ProductCard } from '@commerce/types/product'; +import { Collection } from '@framework/schema'; +import React, { useMemo } from 'react'; import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo'; -import { PRODUCT_DATA_TEST } from 'src/utils/demo-data'; +import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils'; + +interface Props { + data: ProductCard[] + collections: Collection[] + +} + +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 + } -const ReleventProducts = () => { return ( ); }; diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index 15c1004f2..7d0cf9ba5 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -1,5 +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 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