mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
viewed prodyct
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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`
|
||||||
|
2
framework/vendure/schema.d.ts
vendored
2
framework/vendure/schema.d.ts
vendored
@@ -3303,7 +3303,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<
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
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 } from '../schema'
|
import { CartFragment, SearchResultFragment } from '../schema'
|
||||||
|
|
||||||
export function normalizeSearchResult(item: SearchResultFragment): ProductCard {
|
export function normalizeSearchResult(item: SearchResultFragment): ProductCard {
|
||||||
@@ -57,3 +57,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,
|
||||||
|
}
|
||||||
|
}
|
@@ -12,6 +12,7 @@ export const getProductQuery = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
variants {
|
variants {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
priceWithTax
|
priceWithTax
|
||||||
currencyCode
|
currencyCode
|
||||||
options {
|
options {
|
||||||
|
@@ -1,22 +1,48 @@
|
|||||||
|
|
||||||
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 } 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 { 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 [viewedProduct,setViewedProduct] = useLocalStorage<Product[]>(LOCAL_STORAGE_KEY.VIEWEDPRODUCT, []);
|
||||||
|
useEffect(() => {
|
||||||
|
// const local = localStorage.getItem(LOCAL_STORAGE_KEY.VIEWEDPRODUCT)
|
||||||
|
// if(local){
|
||||||
|
// const oldList:Product[] = JSON.parse(local)
|
||||||
|
// if(!oldList.find(p => p.id === product.id)){
|
||||||
|
// localStorage.setItem(LOCAL_STORAGE_KEY.VIEWEDPRODUCT,JSON.stringify([...oldList,product]))
|
||||||
|
// }
|
||||||
|
// }else{
|
||||||
|
// localStorage.setItem(LOCAL_STORAGE_KEY.VIEWEDPRODUCT,JSON.stringify([product]))
|
||||||
|
// }
|
||||||
|
if(viewedProduct){
|
||||||
|
if(!viewedProduct.find(p => p.id === product.id)){
|
||||||
|
setViewedProduct([...viewedProduct, product])
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
setViewedProduct([product])
|
||||||
|
}
|
||||||
|
}, [product])
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<ProductInfoDetail productDetail={product} collections={collections}/>
|
<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/>
|
||||||
<RelevantBlogPosts data={BLOGS_DATA_TEST} title="relevent blog posts" />
|
<RelevantBlogPosts data={BLOGS_DATA_TEST} title="relevent blog posts" />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@@ -68,7 +94,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)
|
||||||
|
36
src/components/hooks/useLocalStorage.tsx
Normal file
36
src/components/hooks/useLocalStorage.tsx
Normal 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;
|
||||||
|
}
|
@@ -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,
|
||||||
|
@@ -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';
|
||||||
|
@@ -1,13 +1,28 @@
|
|||||||
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'
|
||||||
|
import { normalizeProductCard } from '@framework/utils/normalize';
|
||||||
|
import { useLocalStorage } from 'src/components/hooks/useLocalStorage';
|
||||||
|
interface Props {
|
||||||
|
}
|
||||||
|
const ViewedProducts = ({}:Props) => {
|
||||||
|
const [data, setData] = useState<ProductCardProps[]>([])
|
||||||
|
const [viewedProduct] = useLocalStorage<Product[]>(LOCAL_STORAGE_KEY.VIEWEDPRODUCT, []);
|
||||||
|
|
||||||
const ViewedProducts = () => {
|
useEffect(() => {
|
||||||
|
setData(viewedProduct.map((p)=>normalizeProductCard(p)))
|
||||||
|
}, [viewedProduct])
|
||||||
|
|
||||||
|
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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@@ -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 = ','
|
||||||
|
@@ -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";
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user