Merge pull request #86 from KieIO/feature/m3-viewed-product

Feature/m3 viewed product
This commit is contained in:
datnguyenkieio
2021-10-18 14:45:38 +07:00
committed by GitHub
12 changed files with 103 additions and 19 deletions

View File

@@ -30,6 +30,7 @@ export type ProductOptionValues = {
export type ProductVariant = { export type ProductVariant = {
id: string | number id: string | number
name:string,
options: ProductOption[] options: ProductOption[]
availableForSale?: boolean availableForSale?: boolean
} }

View File

@@ -36,6 +36,7 @@ export default function getProductOperation({
})), })),
variants: product.variants.map((v) => ({ variants: product.variants.map((v) => ({
id: v.id, id: v.id,
name:v.name,
options: v.options.map((o) => ({ options: v.options.map((o) => ({
// This __typename property is required in order for the correct // This __typename property is required in order for the correct
// variant selection to work, see `components/product/helpers.ts` // variant selection to work, see `components/product/helpers.ts`

View File

@@ -3381,7 +3381,7 @@ export type GetProductQuery = { __typename?: 'Query' } & {
variants: Array< variants: Array<
{ __typename?: 'ProductVariant' } & Pick< { __typename?: 'ProductVariant' } & Pick<
ProductVariant, ProductVariant,
'id' | 'priceWithTax' | 'currencyCode' | 'price' 'id' | 'priceWithTax' | 'currencyCode' | 'price' | "name"
> & { > & {
options: Array< options: Array<
{ __typename?: 'ProductOption' } & Pick< { __typename?: 'ProductOption' } & Pick<

View File

@@ -1,6 +1,6 @@
import { Cart } from '@commerce/types/cart' import { Cart } from '@commerce/types/cart'
import { ProductCard } from '@commerce/types/product' import { ProductCard, Product } from '@commerce/types/product'
import { CartFragment, SearchResultFragment,Favorite,ActiveCustomerQuery } from '../schema' import { CartFragment, SearchResultFragment,Favorite } from '../schema'
export function normalizeSearchResult(item: SearchResultFragment): ProductCard { export function normalizeSearchResult(item: SearchResultFragment): ProductCard {
return { return {
@@ -69,3 +69,18 @@ export function normalizeCart(order: CartFragment): Cart {
})), })),
} }
} }
export function normalizeProductCard(product: Product): ProductCard {
return {
id: product.id,
name: product.name,
slug: product.slug,
imageSrc: product.images[0].url,
price: product.price,
currencyCode: product.currencyCode,
productVariantId: product.variants?.[0].id.toString(),
productVariantName:product.variants?.[0].name,
facetValueIds: product.facetValueIds,
collectionIds: product.collectionIds,
}
}

View File

@@ -12,6 +12,7 @@ export const getProductQuery = /* GraphQL */ `
} }
variants { variants {
id id
name
priceWithTax priceWithTax
currencyCode currencyCode
options { options {

View File

@@ -1,22 +1,43 @@
import { Product } from '@framework/schema' import { Collection } from '@commerce/types/collection'
import { Product, ProductCard } from '@commerce/types/product'
import commerce from '@lib/api/commerce' import commerce from '@lib/api/commerce'
import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next' import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next'
import { useEffect, useState } from 'react'
import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common' import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common'
import { useLocalStorage } from 'src/components/hooks/useLocalStorage'
import { ProductInfoDetail, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail' import { ProductInfoDetail, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail'
import { MAX_PRODUCT_CAROUSEL, REVALIDATE_TIME } from 'src/utils/constanst.utils' import { LOCAL_STORAGE_KEY, 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 { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data'
import { getAllPromies } from 'src/utils/funtion.utils' import { getAllPromies } from 'src/utils/funtion.utils'
import { normalizeProductCard } from '@framework/utils/normalize';
import { PromiseWithKey } from 'src/utils/types.utils' import { PromiseWithKey } from 'src/utils/types.utils'
interface Props {
export default function Slug({ product, relevantProducts, collections }: InferGetStaticPropsType<typeof getStaticProps>) { relevantProducts: ProductCard[],
product: Product,
collections: Collection[]
}
export default function Slug({ product, relevantProducts, collections }: Props) {
const [local,setLocal] = useLocalStorage<Product[]>(LOCAL_STORAGE_KEY.VIEWEDPRODUCT, []);
const [viewed, setViewed] = useState<ProductCard[]>([])
useEffect(() => {
if(local){
if(!local.find(p => p.id === product.id)){
setLocal([...local, product])
}else{
setViewed(local.filter((p)=>p.id !== product.id).map((p)=>normalizeProductCard(p)))
}
}else{
setLocal([product])
}
}, [product])
return <> return <>
<ProductInfoDetail productDetail={product}/> <ProductInfoDetail productDetail={product}/>
<RecipeDetail ingredients={INGREDIENT_DATA_TEST} /> <RecipeDetail ingredients={INGREDIENT_DATA_TEST} />
<RecommendedRecipes data={RECIPE_DATA_TEST} /> <RecommendedRecipes data={RECIPE_DATA_TEST} />
<ReleventProducts data={relevantProducts} collections={collections}/> <ReleventProducts data={relevantProducts} collections={collections}/>
<ViewedProducts /> <ViewedProducts data={viewed}/>
<RelevantBlogPosts data={BLOGS_DATA_TEST} title="relevent blog posts" /> <RelevantBlogPosts data={BLOGS_DATA_TEST} title="relevent blog posts" />
</> </>
} }
@@ -68,7 +89,6 @@ export async function getStaticProps({
}) })
promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' })
try { try {
const promises = getAllPromies(promisesWithKey) const promises = getAllPromies(promisesWithKey)
const rs = await Promise.all(promises) const rs = await Promise.all(promises)

View File

@@ -0,0 +1,36 @@
import { useState } from "react";
// Hook
export function useLocalStorage<T>(key: string, initialValue: T) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState<T>(() => {
try {
// Get from local storage by key
const item = localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
// console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value: T | ((val: T) => T)) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
// A more advanced implementation would handle the error case
// console.log(error);
}
};
return [storedValue, setValue] as const;
}

View File

@@ -3,7 +3,6 @@ import ProductImgs from './components/ProductImgs/ProductImgs'
import ProductInfo from './components/ProductInfo/ProductInfo' import ProductInfo from './components/ProductInfo/ProductInfo'
import s from './ProductInfoDetail.module.scss' import s from './ProductInfoDetail.module.scss'
import { Product } from '@commerce/types/product' import { Product } from '@commerce/types/product'
import { Collection } from '@framework/schema'
interface Props { interface Props {
productDetail: Product, productDetail: Product,

View File

@@ -1,5 +1,5 @@
import { Collection } from '@commerce/types/collection';
import { ProductCard } from '@commerce/types/product'; import { ProductCard } from '@commerce/types/product';
import { Collection } from '@framework/schema';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo'; import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo';
import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils'; import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils';

View File

@@ -1,13 +1,22 @@
import React from 'react'; import { Product } from '@commerce/types/product';
import React, { useEffect, useState } from 'react';
import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo'; import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo';
import { PRODUCT_DATA_TEST } from 'src/utils/demo-data'; import { ProductCardProps } from 'src/components/common/ProductCard/ProductCard';
import { LOCAL_STORAGE_KEY } from 'src/utils/constanst.utils'
const ViewedProducts = () => { import { normalizeProductCard } from '@framework/utils/normalize';
import { useLocalStorage } from 'src/components/hooks/useLocalStorage';
interface Props {
data: ProductCardProps[]
}
const ViewedProducts = ({data}:Props) => {
if (data.length===0){
return <div></div>
}
return ( return (
<ListProductWithInfo <ListProductWithInfo
title="viewed Products" title="viewed Products"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can." subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
data={PRODUCT_DATA_TEST} data={data}
hasBorderBottomMobile={true} hasBorderBottomMobile={true}
/> />
); );

View File

@@ -44,7 +44,8 @@ export const ACCOUNT_TAB = {
} }
export const LOCAL_STORAGE_KEY = { export const LOCAL_STORAGE_KEY = {
TOKEN: 'token' TOKEN: 'token',
VIEWEDPRODUCT: "viewed-product"
} }
export const QUERY_SPLIT_SEPERATOR = ',' export const QUERY_SPLIT_SEPERATOR = ','

View File

@@ -1,6 +1,7 @@
import { Collection } from '@commerce/types/collection';
import { Facet } from "@commerce/types/facet"; import { Facet } from "@commerce/types/facet";
import { Product, ProductCard, ProductOption, ProductOptionValues } from "@commerce/types/product"; import { Product, ProductCard, ProductOption, ProductOptionValues } from "@commerce/types/product";
import { Collection, FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d'; import { FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d';
import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, FACET, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils"; import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, FACET, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils";
import { PromiseWithKey, SelectedOptions, SortOrder } from "./types.utils"; import { PromiseWithKey, SelectedOptions, SortOrder } from "./types.utils";