From 47107e27027ca31d20fa4182792bb816afac0c05 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Mon, 4 Oct 2021 19:36:47 +0700 Subject: [PATCH 01/20] :sparkles: feat: featured products :%s --- framework/commerce/types/product.ts | 6 +- framework/vendure/schema.d.ts | 3 +- .../utils/fragments/search-result-fragment.ts | 4 +- framework/vendure/utils/normalize.ts | 5 +- pages/index.tsx | 16 +- .../FeaturedProductCard.tsx | 44 ++-- .../FeaturedProductsCarousel.tsx | 28 +-- src/utils/constanst.utils.ts | 206 +++++++++--------- src/utils/funtion.utils.ts | 21 +- 9 files changed, 174 insertions(+), 159 deletions(-) diff --git a/framework/commerce/types/product.ts b/framework/commerce/types/product.ts index 39f594a7a..ac7025158 100644 --- a/framework/commerce/types/product.ts +++ b/framework/commerce/types/product.ts @@ -1,4 +1,4 @@ -import { CurrencyCode } from './../../vendure/schema.d'; +import { CurrencyCode, FacetValue } from './../../vendure/schema.d'; import { FacetValueFilterInput, LogicalOperator, SearchResultSortParameter } from "@framework/schema" export type ProductImage = { @@ -54,9 +54,11 @@ export type ProductCard = { imageSrc: string price: number currencyCode: CurrencyCode - oldPrice?: number, + oldPrice?: number discount?: number weight?: number + facetValueIds?: string[], + collectionIds?: string[], // TODO: collection category?: string, isNotSell?: boolean diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index a817d6799..c5c3e8e89 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -1,3 +1,4 @@ +import { FacetValue } from './schema.d'; export type Maybe = T | null export type Exact = { [K in keyof T]: T[K] @@ -3038,7 +3039,7 @@ export type SearchResultFragment = { __typename?: 'SearchResult' } & Pick< SearchResult, 'productId' | 'sku' | 'productName' | 'description' | 'slug' | 'sku' | 'currencyCode' | 'productAsset' | 'price' | 'priceWithTax' | 'currencyCode' - | 'collectionIds' + | 'collectionIds' | 'facetValueIds' | 'collectionIds' > & { productAsset?: Maybe< { __typename?: 'SearchResultAsset' } & Pick< diff --git a/framework/vendure/utils/fragments/search-result-fragment.ts b/framework/vendure/utils/fragments/search-result-fragment.ts index 6155b5b47..d2d82f42e 100644 --- a/framework/vendure/utils/fragments/search-result-fragment.ts +++ b/framework/vendure/utils/fragments/search-result-fragment.ts @@ -19,6 +19,8 @@ export const searchResultFragment = /* GraphQL */ ` min max } - } + }, + facetValueIds, + collectionIds, } ` diff --git a/framework/vendure/utils/normalize.ts b/framework/vendure/utils/normalize.ts index a5cb96c8a..a790aa8cd 100644 --- a/framework/vendure/utils/normalize.ts +++ b/framework/vendure/utils/normalize.ts @@ -1,16 +1,17 @@ -import { Product, ProductCard } from '@commerce/types/product' import { Cart } from '@commerce/types/cart' +import { ProductCard } from '@commerce/types/product' import { CartFragment, SearchResultFragment } from '../schema' export function normalizeSearchResult(item: SearchResultFragment): ProductCard { return { - id: item.productId, name: item.productName, slug: item.slug, imageSrc: item.productAsset?.preview ? item.productAsset?.preview + '?w=800&mode=crop' : '', price: (item.priceWithTax as any).min / 100, currencyCode: item.currencyCode, + facetValueIds: item.facetValueIds, + collectionIds: item.collectionIds, // TODO: // oldPrice: item.price diff --git a/pages/index.tsx b/pages/index.tsx index d06f29013..97052fd1a 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -5,7 +5,7 @@ import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; import { FeaturedProductsCarousel, FreshProducts, HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home'; import HomeSpice from 'src/components/modules/home/HomeSpice/HomeSpice'; -import { getAllFeaturedFacetId, getFreshProductFacetId } from 'src/utils/funtion.utils'; +import { getAllFeaturedFacetId, getFreshFacetId } from 'src/utils/funtion.utils'; interface Props { freshProducts: ProductCard[], @@ -18,11 +18,11 @@ export default function Home({ freshProducts, featuredProducts }: Props) { - + - + @@ -48,7 +48,7 @@ export async function getStaticProps({ const freshProductvariables: ProductVariables = {} - const freshFacetId = getFreshProductFacetId(facets) + const freshFacetId = getFreshFacetId(facets) if (freshFacetId) { freshProductvariables.facetValueIds = [freshFacetId] @@ -60,6 +60,7 @@ export async function getStaticProps({ }) const allFeaturedFacetId = getAllFeaturedFacetId(facets) + console.log("featured** ", allFeaturedFacetId) const featuredProductsPromise = commerce.getAllProducts({ variables: { facetValueIds: allFeaturedFacetId @@ -68,12 +69,15 @@ export async function getStaticProps({ preview, }) - try { - const rs = await Promise.all([freshProductsPromise, featuredProductsPromise]) + const rs = await Promise.all([ + freshProductsPromise, + featuredProductsPromise, + ]) return { props: { + facets, freshProducts: freshFacetId ? rs[0].products : [], featuredProducts: rs[1].products }, diff --git a/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx b/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx index a82b6857b..4a492b6c1 100644 --- a/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx +++ b/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx @@ -1,39 +1,53 @@ +import { ProductCard } from '@commerce/types/product' +import Link from 'next/link' import React from 'react' -import { FeaturedProductProps } from 'src/utils/types.utils' -import s from './FeaturedProductCard.module.scss' -import { LANGUAGE } from '../../../utils/language.utils' -import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy' -import ButtonCommon from '../ButtonCommon/ButtonCommon' +import { ROUTE } from 'src/utils/constanst.utils' import { ImgWithLink } from '..' -export interface FeaturedProductCardProps extends FeaturedProductProps { +import { LANGUAGE } from '../../../utils/language.utils' +import ButtonCommon from '../ButtonCommon/ButtonCommon' +import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy' +import s from './FeaturedProductCard.module.scss' +export interface FeaturedProductCardProps extends ProductCard { buttonText?: string + featuredFacetId?: string, } const FeaturedProductCard = ({ imageSrc, - title, - subTitle, + name, + slug, price, - originPrice, + currencyCode, buttonText = LANGUAGE.BUTTON_LABEL.BUY_NOW, }: FeaturedProductCardProps) => { return (
- + + + + +
-
{title}
-
{subTitle}
+ + +
{name}
+
+ + + {/* TODO: */} + {/*
{subTitle}
*/}
-
{price}
-
{originPrice}
+
{price} {currencyCode}
+ {/* TODO: */} + {/*
{originPrice}
*/}
- +
{buttonText} diff --git a/src/components/modules/home/FeaturedProductsCarousel/FeaturedProductsCarousel.tsx b/src/components/modules/home/FeaturedProductsCarousel/FeaturedProductsCarousel.tsx index 452427616..b06c0214f 100644 --- a/src/components/modules/home/FeaturedProductsCarousel/FeaturedProductsCarousel.tsx +++ b/src/components/modules/home/FeaturedProductsCarousel/FeaturedProductsCarousel.tsx @@ -1,31 +1,15 @@ +import { ProductCard } from '@commerce/types/product' import React from 'react' import { ResponsiveType } from 'react-multi-carousel' import { CarouselCommon, FeaturedProductCard,HeadingCommon} from 'src/components/common' import { FeaturedProductCardProps } from 'src/components/common/FeaturedProductCard/FeaturedProductCard' import s from "./FeaturedProductsCarousel.module.scss" interface FeaturedProductsCarouselProps { - title?: string + title?: string, + data: ProductCard[] } -const dataDemo:FeaturedProductCardProps[] = [{ - title: "Sale 25% Coffee Bean", - subTitle: "50 first Orders within a day", - originPrice: "$20.00", - price: "$14.00", - imageSrc: "https://user-images.githubusercontent.com/76099413/133043628-db7813f9-1bb7-4ee1-b028-dc4295563494.png" -},{ - title: "Sale 20% Fruits", - subTitle: "50 first Orders within a day", - originPrice: "$20.00", - price: "$14.00", - imageSrc: "https://user-images.githubusercontent.com/76099413/133043630-07a353b9-573d-4c1d-b1de-2c932e3f14f7.png" -},{ - title: "Sale 25% Coffee Bean", - subTitle: "50 first Orders within a day", - originPrice: "$20.00", - price: "$14.00", - imageSrc: "https://user-images.githubusercontent.com/76099413/133043633-954c105b-c703-4e5c-8f5f-7943ad633ff0.png" -}] + const RESPONSIVE: ResponsiveType = { hugeScreen: { breakpoint: { max: 9999, min: 1500 }, @@ -78,13 +62,13 @@ const dataDemo:FeaturedProductCardProps[] = [{ }, } -const FeaturedProductsCarousel = ({title="Featured Products"}: FeaturedProductsCarouselProps) => { +const FeaturedProductsCarousel = ({title="Featured Products", data}: FeaturedProductsCarouselProps) => { return (
{title}
- data={dataDemo} Component={FeaturedProductCard} itemKey="featured-products" responsive={RESPONSIVE}/> + data={data} Component={FeaturedProductCard} itemKey="featured-products" responsive={RESPONSIVE}/>
) } diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index e6ae9fabc..bc4c00287 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -4,41 +4,41 @@ export const BLUR_DATA_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEA export const DEFAULT_IMG = DefaultImg export const SOCIAL_LINKS = { - FB: 'FB', - TWITTER: 'TWITTER', - YOUTUBE: 'YOUTUBE', - IG: 'IG', + FB: 'FB', + TWITTER: 'TWITTER', + YOUTUBE: 'YOUTUBE', + IG: 'IG', } export const ROUTE = { - HOME: '/', - ABOUT: '/about', - ACCOUNT: '/account', + HOME: '/', + ABOUT: '/about', + ACCOUNT: '/account', - PRODUCTS: '/products', - PRODUCT_DETAIL: '/product', - - BLOGS: '/blogs', - BLOG_DETAIL: '/blog', + PRODUCTS: '/products', + PRODUCT_DETAIL: '/product', - RECIPES: '/recipes', - RECIPE_DETAIL: '/recipe', + BLOGS: '/blogs', + BLOG_DETAIL: '/blog', - NOTIFICATION: '/notifications', - BUSSINESS: '/bussiness', - CONTACT: '/contact', - CHECKOUT: '/checkout', - FAQ: '/faq', - CUSTOMER_SERVICE: '/customer-service', - TERM_CONDITION: '/term-condition', - PRIVACY_POLICY: '/privacy-policy', - FORGOT_PASSWORD: '/forgot-password' + RECIPES: '/recipes', + RECIPE_DETAIL: '/recipe', + + NOTIFICATION: '/notifications', + BUSSINESS: '/bussiness', + CONTACT: '/contact', + CHECKOUT: '/checkout', + FAQ: '/faq', + CUSTOMER_SERVICE: '/customer-service', + TERM_CONDITION: '/term-condition', + PRIVACY_POLICY: '/privacy-policy', + FORGOT_PASSWORD: '/forgot-password' } export const ACCOUNT_TAB = { - CUSTOMER_INFO: '', - ORDER: 'orders', - FAVOURITE: 'wishlist', + CUSTOMER_INFO: '', + ORDER: 'orders', + FAVOURITE: 'wishlist', } export const LOCAL_STORAGE_KEY = { @@ -46,101 +46,99 @@ export const LOCAL_STORAGE_KEY = { } export const QUERY_KEY = { - TAB: 'tab', - CATEGORY: 'category', - BRAND: 'brand', - FEATURED: 'feature', - SORTBY:'sortby', - RECIPES:'recipes' + TAB: 'tab', + CATEGORY: 'category', + BRAND: 'brand', + FEATURED: 'feature', + SORTBY: 'sortby', + RECIPES: 'recipes' } export enum ProductFeature { - BestSellers = 'Best Sellers', - Sales = 'Sales', - NewItem = 'New Item', - Viewed = 'Viewed', + BestSellers = 'Best Sellers', + Sales = 'Sales', + NewItem = 'New Item', + Viewed = 'Viewed', } export const KEY = { - ENTER: 'Enter', + ENTER: 'Enter', } export const OPTION_ALL = 'all'; -export const DEFAULT_PAGE_SIZE=20; +export const DEFAULT_PAGE_SIZE = 20; export const CATEGORY = [ - { - name: 'All', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=${OPTION_ALL}`, - }, - { - name: 'Veggie', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=veggie`, - }, - { - name: 'Seafood', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=seafood`, - }, - { - name: 'Frozen', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=frozen`, - }, - { - name: 'Coffee Bean', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=coffee_bean`, - }, - { - name: 'Sauce', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=sauce`, - }, - ] - - export const BRAND = [ - { - name: 'Maggi', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=maggi`, - }, - { - name: 'Chomilex', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chomilex`, - }, - { - name: 'Chinsu', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chinsu`, - }, - ] + { + name: 'All', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=${OPTION_ALL}`, + }, + { + name: 'Veggie', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=veggie`, + }, + { + name: 'Seafood', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=seafood`, + }, + { + name: 'Frozen', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=frozen`, + }, + { + name: 'Coffee Bean', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=coffee_bean`, + }, + { + name: 'Sauce', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=sauce`, + }, +] -export const FACET = { - FEATURE: { - PARENT_NAME: 'Featured', - FRESH: 'Fresh', - BEST_SELLERS: 'Best seller' - } +export const BRAND = [ + { + name: 'Maggi', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=maggi`, + }, + { + name: 'Chomilex', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chomilex`, + }, + { + name: 'Chinsu', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chinsu`, + }, +] + +export const CODE_FACET_FEATURED = 'featured' +export const CODE_FACET_FEATURED_VARIANT = { + FRESH: 'fresh', + BEST_SELLERS: 'best-sellers' } export const FEATURED = [ - { - name: 'Best Sellers', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=best_sellers`, - }, - { - name: 'Sales', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=sales`, - }, - { - name: 'New Item', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=new_item`, - }, - { - name: 'Viewed', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=viewed`, - }, - ] - -export const DEFAULT_BLOG_PAGE_SIZE=6; + { + name: 'Best Sellers', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=best_sellers`, + }, + { + name: 'Sales', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=sales`, + }, + { + name: 'New Item', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=new_item`, + }, + { + name: 'Viewed', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=viewed`, + }, +] -export const FILTER_PAGE = [ROUTE.HOME,ROUTE.PRODUCTS] +export const DEFAULT_BLOG_PAGE_SIZE = 6; + +export const FILTER_PAGE = [ROUTE.HOME, ROUTE.PRODUCTS] export const STATE_OPTIONS = [ { diff --git a/src/utils/funtion.utils.ts b/src/utils/funtion.utils.ts index d2ad83be1..e8dc6f2d5 100644 --- a/src/utils/funtion.utils.ts +++ b/src/utils/funtion.utils.ts @@ -1,6 +1,6 @@ -import { FacetValue } from './../../framework/vendure/schema.d'; import { Facet } from "@commerce/types/facet"; -import { FACET } from "./constanst.utils"; +import { FacetValue } from './../../framework/vendure/schema.d'; +import { CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT } from "./constanst.utils"; export function isMobile() { return window.innerWidth < 768 @@ -14,15 +14,24 @@ export function removeItem(arr: Array, value: T): Array { return [...arr]; } -export function getFreshProductFacetId(facets: Facet[]) { - const featuredFacet = facets.find((item: Facet) => item.name === FACET.FEATURE.PARENT_NAME) - const freshFacetValue = featuredFacet?.values.find((item: FacetValue) => item.name === FACET.FEATURE.FRESH) +function findFacetByCode(code: string, facets?: Facet) { + return facets?.values.find((item: FacetValue) => item.code === code) +} + +export function getFeaturedFacetId(facets: Facet[]) { + const featuredFacet = facets.find((item: Facet) => item.code === CODE_FACET_FEATURED) + return featuredFacet?.id +} + +export function getFreshFacetId(facets: Facet[]) { + const featuredFacet = facets.find((item: Facet) => item.code === CODE_FACET_FEATURED) + const freshFacetValue = findFacetByCode(CODE_FACET_FEATURED_VARIANT.FRESH, featuredFacet) return freshFacetValue?.id } export function getAllFeaturedFacetId(facets: Facet[]) { - const featuredFacet = facets.find((item: Facet) => item.name === FACET.FEATURE.PARENT_NAME) + const featuredFacet = facets.find((item: Facet) => item.code === CODE_FACET_FEATURED) const rs = featuredFacet?.values.map((item: FacetValue) => item.id) return rs From 8898882a72952a435f443c9b4af9d4ae41919152 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Mon, 4 Oct 2021 20:41:38 +0700 Subject: [PATCH 02/20] :sparkles: feat: featured product sub text :%s --- pages/index.tsx | 13 +- .../FeaturedProductCard.module.scss | 3 + .../FeaturedProductCard.tsx | 12 +- .../FeaturedProductsCarousel.tsx | 145 ++++++++++-------- src/utils/funtion.utils.ts | 15 ++ 5 files changed, 112 insertions(+), 76 deletions(-) diff --git a/pages/index.tsx b/pages/index.tsx index 97052fd1a..0c1aae948 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,18 +1,21 @@ import { ProductCard } from '@commerce/types/product'; import { ProductVariables } from '@framework/api/operations/get-all-products'; +import { FacetValue } from '@framework/schema'; import commerce from '@lib/api/commerce'; import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; import { FeaturedProductsCarousel, FreshProducts, HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home'; import HomeSpice from 'src/components/modules/home/HomeSpice/HomeSpice'; -import { getAllFeaturedFacetId, getFreshFacetId } from 'src/utils/funtion.utils'; +import { getAllFeaturedFacetId, getAllFeaturedFacetValue, getFreshFacetId } from 'src/utils/funtion.utils'; interface Props { + featuredFacetsValue: FacetValue[], freshProducts: ProductCard[], featuredProducts: ProductCard[], } -export default function Home({ freshProducts, featuredProducts }: Props) { +export default function Home({ featuredFacetsValue, + freshProducts, featuredProducts }: Props) { return ( <> @@ -22,7 +25,7 @@ export default function Home({ freshProducts, featuredProducts }: Props) { - + @@ -45,7 +48,7 @@ export async function getStaticProps({ config, preview, }) - + const featuredFacetsValue = getAllFeaturedFacetValue(facets) const freshProductvariables: ProductVariables = {} const freshFacetId = getFreshFacetId(facets) @@ -60,7 +63,6 @@ export async function getStaticProps({ }) const allFeaturedFacetId = getAllFeaturedFacetId(facets) - console.log("featured** ", allFeaturedFacetId) const featuredProductsPromise = commerce.getAllProducts({ variables: { facetValueIds: allFeaturedFacetId @@ -78,6 +80,7 @@ export async function getStaticProps({ return { props: { facets, + featuredFacetsValue, freshProducts: freshFacetId ? rs[0].products : [], featuredProducts: rs[1].products }, diff --git a/src/components/common/FeaturedProductCard/FeaturedProductCard.module.scss b/src/components/common/FeaturedProductCard/FeaturedProductCard.module.scss index 036e84629..3f95f92da 100644 --- a/src/components/common/FeaturedProductCard/FeaturedProductCard.module.scss +++ b/src/components/common/FeaturedProductCard/FeaturedProductCard.module.scss @@ -21,6 +21,9 @@ width: 24rem; height: 24rem; } + img { + object-fit: contain; + } } .right{ margin-left: 1.2rem; diff --git a/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx b/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx index 4a492b6c1..23b9ac987 100644 --- a/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx +++ b/src/components/common/FeaturedProductCard/FeaturedProductCard.tsx @@ -1,4 +1,5 @@ import { ProductCard } from '@commerce/types/product' +import { Facet, FacetValue } from '@framework/schema' import Link from 'next/link' import React from 'react' import { ROUTE } from 'src/utils/constanst.utils' @@ -8,8 +9,8 @@ import ButtonCommon from '../ButtonCommon/ButtonCommon' import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy' import s from './FeaturedProductCard.module.scss' export interface FeaturedProductCardProps extends ProductCard { - buttonText?: string - featuredFacetId?: string, + buttonText?: string, + subText?: string, } const FeaturedProductCard = ({ @@ -17,9 +18,12 @@ const FeaturedProductCard = ({ name, slug, price, + subText, currencyCode, buttonText = LANGUAGE.BUTTON_LABEL.BUY_NOW, }: FeaturedProductCardProps) => { + + return (
@@ -36,9 +40,7 @@ const FeaturedProductCard = ({
{name}
- - {/* TODO: */} - {/*
{subTitle}
*/} +
{subText}
{price} {currencyCode}
{/* TODO: */} diff --git a/src/components/modules/home/FeaturedProductsCarousel/FeaturedProductsCarousel.tsx b/src/components/modules/home/FeaturedProductsCarousel/FeaturedProductsCarousel.tsx index b06c0214f..66865194b 100644 --- a/src/components/modules/home/FeaturedProductsCarousel/FeaturedProductsCarousel.tsx +++ b/src/components/modules/home/FeaturedProductsCarousel/FeaturedProductsCarousel.tsx @@ -1,76 +1,89 @@ -import { ProductCard } from '@commerce/types/product' -import React from 'react' +import { FacetValue } from '@framework/schema' +import React, { useMemo } from 'react' import { ResponsiveType } from 'react-multi-carousel' -import { CarouselCommon, FeaturedProductCard,HeadingCommon} from 'src/components/common' +import { CarouselCommon, FeaturedProductCard, HeadingCommon } from 'src/components/common' import { FeaturedProductCardProps } from 'src/components/common/FeaturedProductCard/FeaturedProductCard' +import { getFacetNamesFromIds } from 'src/utils/funtion.utils' import s from "./FeaturedProductsCarousel.module.scss" interface FeaturedProductsCarouselProps { - title?: string, - data: ProductCard[] + data: FeaturedProductCardProps[] + featuredFacetsValue: FacetValue[], } +const RESPONSIVE: ResponsiveType = { + hugeScreen: { + breakpoint: { max: 9999, min: 1500 }, + items: 2.25, + slidesToSlide: 1, // optional, default to 1. + }, + largeScreen: { + breakpoint: { max: 1500, min: 1440 }, + items: 2.075, + slidesToSlide: 1, // optional, default to 1. + }, + largeDesktop: { + breakpoint: { max: 1440, min: 1280 }, + items: 1.75, + slidesToSlide: 1, // optional, default to 1. + }, + desktop: { + breakpoint: { max: 1280, min: 1148 }, + items: 1.5, + slidesToSlide: 1, // optional, default to 1. + }, + smallDesktop: { + breakpoint: { max: 1148, min: 1024 }, + items: 1.375, + slidesToSlide: 1, // optional, default to 1. + }, + lap: { + breakpoint: { max: 1024, min: 968 }, + items: 1.7, + }, + tablet: { + breakpoint: { max: 968, min: 768 }, + items: 1.075, + }, + smallTablet: { + breakpoint: { max: 768, min: 640 }, + items: 1.25, + }, + largeMobile: { + breakpoint: { max: 640, min: 400 }, + items: 1.275, + }, + mobile: { + breakpoint: { max: 400, min: 300 }, + items: 1.1, + }, + smallMobile: { + breakpoint: { max: 300, min: 0 }, + items: 1, + }, +} - const RESPONSIVE: ResponsiveType = { - hugeScreen: { - breakpoint: { max: 9999, min: 1500 }, - items: 2.25, - slidesToSlide: 1, // optional, default to 1. - }, - largeScreen: { - breakpoint: { max: 1500, min: 1440 }, - items: 2.075, - slidesToSlide: 1, // optional, default to 1. - }, - largeDesktop: { - breakpoint: { max: 1440, min: 1280 }, - items: 1.75, - slidesToSlide: 1, // optional, default to 1. - }, - desktop: { - breakpoint: { max: 1280, min: 1148 }, - items: 1.5, - slidesToSlide: 1, // optional, default to 1. - }, - smallDesktop: { - breakpoint: { max: 1148, min: 1024 }, - items: 1.375, - slidesToSlide: 1, // optional, default to 1. - }, - lap: { - breakpoint: { max: 1024, min: 968 }, - items: 1.7, - }, - tablet: { - breakpoint: { max: 968, min: 768 }, - items: 1.075, - }, - smallTablet: { - breakpoint: { max: 768, min: 640 }, - items: 1.25, - }, - largeMobile: { - breakpoint: { max: 640, min: 400 }, - items: 1.275, - }, - mobile: { - breakpoint: { max: 400, min: 300 }, - items: 1.1, - }, - smallMobile: { - breakpoint: { max: 300, min: 0 }, - items: 1, - }, - } - -const FeaturedProductsCarousel = ({title="Featured Products", data}: FeaturedProductsCarouselProps) => { - return ( -
-
- {title} -
- data={data} Component={FeaturedProductCard} itemKey="featured-products" responsive={RESPONSIVE}/> -
- ) +const FeaturedProductsCarousel = ({ data, featuredFacetsValue }: FeaturedProductsCarouselProps) => { + const featuredProducts = useMemo(() => { + return data.map(item => { + return { + ...item, + subText: getFacetNamesFromIds(featuredFacetsValue, item.facetValueIds) + } + }) + }, [data, featuredFacetsValue]) + return ( +
+
+ Featured Products +
+ + data={featuredProducts} + defaultComponentProps={featuredFacetsValue} + Component={FeaturedProductCard} + itemKey="featured-products" + responsive={RESPONSIVE} /> +
+ ) } export default FeaturedProductsCarousel diff --git a/src/utils/funtion.utils.ts b/src/utils/funtion.utils.ts index e8dc6f2d5..e16a250b7 100644 --- a/src/utils/funtion.utils.ts +++ b/src/utils/funtion.utils.ts @@ -35,4 +35,19 @@ export function getAllFeaturedFacetId(facets: Facet[]) { const rs = featuredFacet?.values.map((item: FacetValue) => item.id) return rs +} + +export function getAllFeaturedFacetValue(facets: Facet[]) { + const featuredFacet = facets.find((item: Facet) => item.code === CODE_FACET_FEATURED) + return featuredFacet?.values +} + +export function getFacetNamesFromIds(facets: FacetValue[], ids?: string[]): string { + if (!ids || ids?.length === 0) { + return '' + } + + const facetItems = facets.filter((item: FacetValue) => ids.includes(item.id)) + const names = facetItems.map((item: FacetValue) => item.name) + return names.join(", ") } \ No newline at end of file From c6dc27ab59c43d646e0ee2502082e41838d4de07 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Mon, 4 Oct 2021 21:12:47 +0700 Subject: [PATCH 03/20] :sparkles: feat: get featured product base on facet Featured and Discount :%s --- pages/index.tsx | 27 ++++++++++++++++----------- src/utils/constanst.utils.ts | 2 +- src/utils/funtion.utils.ts | 19 ++++++++++++------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/pages/index.tsx b/pages/index.tsx index 0c1aae948..589c7ae30 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -6,15 +6,16 @@ import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; import { FeaturedProductsCarousel, FreshProducts, HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home'; import HomeSpice from 'src/components/modules/home/HomeSpice/HomeSpice'; -import { getAllFeaturedFacetId, getAllFeaturedFacetValue, getFreshFacetId } from 'src/utils/funtion.utils'; +import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED } from 'src/utils/constanst.utils'; +import { getAllFacetValueIdsByParentCode, getAllFacetValuesForFeatuedProducts, getFreshFacetId } from 'src/utils/funtion.utils'; interface Props { - featuredFacetsValue: FacetValue[], + featuredAndDiscountFacetsValue: FacetValue[], freshProducts: ProductCard[], featuredProducts: ProductCard[], } -export default function Home({ featuredFacetsValue, +export default function Home({ featuredAndDiscountFacetsValue, freshProducts, featuredProducts }: Props) { return ( <> @@ -25,7 +26,7 @@ export default function Home({ featuredFacetsValue, - + @@ -48,11 +49,12 @@ export async function getStaticProps({ config, preview, }) - const featuredFacetsValue = getAllFeaturedFacetValue(facets) + const featuredAndDiscountFacetsValue = getAllFacetValuesForFeatuedProducts(facets) + + // fresh products const freshProductvariables: ProductVariables = {} const freshFacetId = getFreshFacetId(facets) - if (freshFacetId) { freshProductvariables.facetValueIds = [freshFacetId] } @@ -62,15 +64,19 @@ export async function getStaticProps({ preview, }) - const allFeaturedFacetId = getAllFeaturedFacetId(facets) + // featured products + const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED) + const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT) + const facetValueIdsForFeaturedProducts = [...allFeaturedFacetIds, ...allDiscountFacetIds] const featuredProductsPromise = commerce.getAllProducts({ variables: { - facetValueIds: allFeaturedFacetId + facetValueIds: facetValueIdsForFeaturedProducts }, config, preview, }) + try { const rs = await Promise.all([ freshProductsPromise, @@ -79,10 +85,9 @@ export async function getStaticProps({ return { props: { - facets, - featuredFacetsValue, + featuredAndDiscountFacetsValue, freshProducts: freshFacetId ? rs[0].products : [], - featuredProducts: rs[1].products + featuredProducts: facetValueIdsForFeaturedProducts.length > 0 ? rs[1].products : [] }, revalidate: 60, } diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index bc4c00287..1db198178 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -112,9 +112,9 @@ export const BRAND = [ ] export const CODE_FACET_FEATURED = 'featured' +export const CODE_FACET_DISCOUNT = 'discount' export const CODE_FACET_FEATURED_VARIANT = { FRESH: 'fresh', - BEST_SELLERS: 'best-sellers' } export const FEATURED = [ diff --git a/src/utils/funtion.utils.ts b/src/utils/funtion.utils.ts index e16a250b7..d486ca4d0 100644 --- a/src/utils/funtion.utils.ts +++ b/src/utils/funtion.utils.ts @@ -1,6 +1,6 @@ import { Facet } from "@commerce/types/facet"; import { FacetValue } from './../../framework/vendure/schema.d'; -import { CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT } from "./constanst.utils"; +import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT } from "./constanst.utils"; export function isMobile() { return window.innerWidth < 768 @@ -30,16 +30,21 @@ export function getFreshFacetId(facets: Facet[]) { return freshFacetValue?.id } -export function getAllFeaturedFacetId(facets: Facet[]) { - const featuredFacet = facets.find((item: Facet) => item.code === CODE_FACET_FEATURED) +export function getAllFacetValueIdsByParentCode(facets: Facet[], code: string) { + const featuredFacet = facets.find((item: Facet) => item.code === code) const rs = featuredFacet?.values.map((item: FacetValue) => item.id) - return rs + return rs || [] } -export function getAllFeaturedFacetValue(facets: Facet[]) { - const featuredFacet = facets.find((item: Facet) => item.code === CODE_FACET_FEATURED) - return featuredFacet?.values +export function getAllFacetValuesForFeatuedProducts(facets: Facet[]) { + const facetsRs = facets.filter((item: Facet) => item.code === CODE_FACET_FEATURED || item.code === CODE_FACET_DISCOUNT) + let rs = [] as FacetValue[] + facetsRs.map((item: Facet) => { + rs = rs.concat(item.values) + return null + }) + return rs } export function getFacetNamesFromIds(facets: FacetValue[], ids?: string[]): string { From 1546d5df4b129ce67756052b127e8a32882fe4d1 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Tue, 5 Oct 2021 10:30:39 +0700 Subject: [PATCH 04/20] :sparkles: feat: get all collection :%s --- commerce.config.json | 6 +-- framework/commerce/api/operations.ts | 18 +++++++ framework/commerce/new-provider.md | 1 + framework/commerce/types/collection.ts | 54 +++++++++++++++++++ framework/commerce/types/product.ts | 5 +- framework/vendure/api/index.ts | 2 + .../api/operations/get-all-collection.ts | 45 ++++++++++++++++ framework/vendure/schema.d.ts | 17 ++++++ framework/vendure/utils/normalize.ts | 1 - .../queries/get-all-collections-query.ts | 12 +++++ pages/index.tsx | 48 ++++++++++------- .../common/LabelCommon/LabelCommon.tsx | 1 + .../common/ProductCard/ProductCard.tsx | 6 +-- .../CollectionCarcousel.tsx | 7 +-- .../home/FreshProducts/FreshProducts.tsx | 30 +++++++++-- src/utils/funtion.utils.ts | 7 ++- src/utils/types.utils.ts | 8 ++- 17 files changed, 230 insertions(+), 38 deletions(-) create mode 100644 framework/commerce/types/collection.ts create mode 100644 framework/vendure/api/operations/get-all-collection.ts create mode 100644 framework/vendure/utils/queries/get-all-collections-query.ts diff --git a/commerce.config.json b/commerce.config.json index ad72b58de..6fb526aaf 100644 --- a/commerce.config.json +++ b/commerce.config.json @@ -2,8 +2,8 @@ "features": { "cart": true, "search": true, - "wishlist": false, - "customerAuth": false, - "customCheckout": false + "wishlist": true, + "customerAuth": true, + "customCheckout": true } } diff --git a/framework/commerce/api/operations.ts b/framework/commerce/api/operations.ts index 342d6bbc9..ca9b325c2 100644 --- a/framework/commerce/api/operations.ts +++ b/framework/commerce/api/operations.ts @@ -10,6 +10,7 @@ import type { GetProductOperation, } from '../types/product' import type { APIProvider, CommerceAPI } from '.' +import { GetAllCollectionsOperation } from '@commerce/types/collection'; const noop = () => { throw new Error('Not implemented') @@ -25,6 +26,7 @@ export const OPERATIONS = [ 'getAllProducts', 'getProduct', 'getAllFacets', + 'getAllCollections', ] as const @@ -174,6 +176,22 @@ export type Operations

= { ): Promise } + getAllCollections: { + (opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + } & OperationOptions + ): Promise + } + } diff --git a/framework/commerce/new-provider.md b/framework/commerce/new-provider.md index a48e6961b..3c7f05b7a 100644 --- a/framework/commerce/new-provider.md +++ b/framework/commerce/new-provider.md @@ -16,6 +16,7 @@ Adding a commerce provider means adding a new folder in `framework` with a folde - getProduct - getAllProducts - getAllFacets + - getAllCollections - `wishlist` - useWishlist - useAddItem diff --git a/framework/commerce/types/collection.ts b/framework/commerce/types/collection.ts new file mode 100644 index 000000000..3ccb72ea3 --- /dev/null +++ b/framework/commerce/types/collection.ts @@ -0,0 +1,54 @@ +import { Asset } from '../../vendure/schema.d'; + +export type Collection = { + id: string + name: string + slug: string + description: string + featuredAsse: Asset + asset: Asset[] +} + +export type SearchCollectionsBody = { + search?: string + sort?: string + locale?: string +} + +export type CollectionTypes = { + collection: Collection + searchBody: SearchCollectionsBody +} + +export type SearchCollectionsHook = { + data: { + collections: T['collection'][] + found: boolean + } + body: T['searchBody'] + input: T['searchBody'] + fetcherInput: T['searchBody'] +} + +export type CollectionsSchema = { + endpoint: { + options: {} + handlers: { + getCollections: SearchCollectionsHook + } + } +} + + +export type GetAllCollectionsOperation = { + data: { collections: T['collection'][] } + variables: { + ids?: string[] + first?: number + } +} + +export type GetCollectionOperation = { + data: { collection?: T['collection'] } + variables: { code: string; } | { code?: never; } +} diff --git a/framework/commerce/types/product.ts b/framework/commerce/types/product.ts index ac7025158..e1a8da200 100644 --- a/framework/commerce/types/product.ts +++ b/framework/commerce/types/product.ts @@ -1,4 +1,4 @@ -import { CurrencyCode, FacetValue } from './../../vendure/schema.d'; +import { CurrencyCode } from './../../vendure/schema.d'; import { FacetValueFilterInput, LogicalOperator, SearchResultSortParameter } from "@framework/schema" export type ProductImage = { @@ -59,8 +59,7 @@ export type ProductCard = { weight?: number facetValueIds?: string[], collectionIds?: string[], - // TODO: collection - category?: string, + collection?: string, isNotSell?: boolean } diff --git a/framework/vendure/api/index.ts b/framework/vendure/api/index.ts index 7e49d6c1f..95ec47d66 100644 --- a/framework/vendure/api/index.ts +++ b/framework/vendure/api/index.ts @@ -1,6 +1,7 @@ import type { CommerceAPIConfig } from '@commerce/api' import { CommerceAPI, getCommerceApi as commerceApi } from '@commerce/api' import getAllFacets from './operations/get-all-facets' +import getAllCollections from './operations/get-all-collection' import getAllPages from './operations/get-all-pages' import getAllProductPaths from './operations/get-all-product-paths' import getAllProducts from './operations/get-all-products' @@ -42,6 +43,7 @@ const operations = { getAllProducts, getProduct, getAllFacets, + getAllCollections, } export const provider = { config, operations } diff --git a/framework/vendure/api/operations/get-all-collection.ts b/framework/vendure/api/operations/get-all-collection.ts new file mode 100644 index 000000000..b7cc3a725 --- /dev/null +++ b/framework/vendure/api/operations/get-all-collection.ts @@ -0,0 +1,45 @@ +import { OperationContext } from '@commerce/api/operations' +import { Collection } from '@commerce/types/collection' +import { Provider, VendureConfig } from '..' +import { GetAllCollectionsQuery } from '../../schema' +import { getAllCollectionsQuery } from '../../utils/queries/get-all-collections-query' + +export type CollectionVariables = { first?: number } + +export default function getAllCollectionsOperation({ + commerce, +}: OperationContext) { + async function getAllCollections(opts?: { + variables?: CollectionVariables + config?: Partial + preview?: boolean + }): Promise<{ collections: Collection[] }> + + async function getAllCollections({ + query = getAllCollectionsQuery, + variables: { ...vars } = {}, + config: cfg, + }: { + query?: string + variables?: CollectionVariables + config?: Partial + preview?: boolean + } = {}): Promise<{ collections: Collection[] | any[] }> { + const config = commerce.getConfig(cfg) + const variables = { + input: { + take: vars.first, + groupByCollection: true, + }, + } + const { data } = await config.fetch(query, { + variables, + }) + + return { + collections: data.collections.items, + } + } + + return getAllCollections +} diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index c5c3e8e89..ef667d9f3 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3240,6 +3240,23 @@ export type GetAllFacetsQuery = { __typename?: 'Query' } & { } } +export type GetAllCollectionsQuery = { __typename?: 'Query' } & { + collections: { __typename?: 'CollectionList' } & { + items: Array< + { __typename?: 'Collection' } & Pick< + Collection, + 'id' | 'name' | 'slug' + > & { + parent?: Maybe<{ __typename?: 'Collection' } & Pick> + children?: Maybe< + Array<{ __typename?: 'Collection' } & Pick> + > + } + >, + 'totalItems' + } +} + export type ActiveOrderQueryVariables = Exact<{ [key: string]: never }> export type ActiveOrderQuery = { __typename?: 'Query' } & { diff --git a/framework/vendure/utils/normalize.ts b/framework/vendure/utils/normalize.ts index a790aa8cd..cc76e5f97 100644 --- a/framework/vendure/utils/normalize.ts +++ b/framework/vendure/utils/normalize.ts @@ -18,7 +18,6 @@ export function normalizeSearchResult(item: SearchResultFragment): ProductCard { // discount // isNotSell // weight - // category } } diff --git a/framework/vendure/utils/queries/get-all-collections-query.ts b/framework/vendure/utils/queries/get-all-collections-query.ts new file mode 100644 index 000000000..359c81ea9 --- /dev/null +++ b/framework/vendure/utils/queries/get-all-collections-query.ts @@ -0,0 +1,12 @@ +export const getAllCollectionsQuery = /* GraphQL */ ` +query collections ($options: CollectionListOptions) { + collections (options: $options){ + totalItems, + items { + id + name + slug + } + } +} +` diff --git a/pages/index.tsx b/pages/index.tsx index 589c7ae30..cac464a43 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,28 +1,31 @@ import { ProductCard } from '@commerce/types/product'; import { ProductVariables } from '@framework/api/operations/get-all-products'; -import { FacetValue } from '@framework/schema'; +import { Collection, FacetValue } from '@framework/schema'; import commerce from '@lib/api/commerce'; import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; import { FeaturedProductsCarousel, FreshProducts, HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home'; import HomeSpice from 'src/components/modules/home/HomeSpice/HomeSpice'; import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED } from 'src/utils/constanst.utils'; -import { getAllFacetValueIdsByParentCode, getAllFacetValuesForFeatuedProducts, getFreshFacetId } from 'src/utils/funtion.utils'; +import { getAllFacetValueIdsByParentCode, getAllFacetValuesForFeatuedProducts, getAllPromies, getFreshFacetId } from 'src/utils/funtion.utils'; +import { PromiseWithKey } from 'src/utils/types.utils'; interface Props { featuredAndDiscountFacetsValue: FacetValue[], freshProducts: ProductCard[], featuredProducts: ProductCard[], + collections: Collection[] } export default function Home({ featuredAndDiscountFacetsValue, - freshProducts, featuredProducts }: Props) { + freshProducts, featuredProducts, + collections }: Props) { return ( <> - + @@ -44,13 +47,15 @@ export async function getStaticProps({ locales, }: GetStaticPropsContext) { const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + const { facets } = await commerce.getAllFacets({ variables: {}, config, preview, }) - const featuredAndDiscountFacetsValue = getAllFacetValuesForFeatuedProducts(facets) - + props.featuredAndDiscountFacetsValue = getAllFacetValuesForFeatuedProducts(facets) // fresh products const freshProductvariables: ProductVariables = {} @@ -63,6 +68,8 @@ export async function getStaticProps({ config, preview, }) + promisesWithKey.push({ key: 'freshProducts', promise: freshProductsPromise, keyResult: 'products' }) + // featured products const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED) @@ -75,28 +82,33 @@ export async function getStaticProps({ config, preview, }) + promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' }) + + // collection + const collectionsPromise = commerce.getAllCollections({ + variables: {}, + config, + preview, + }) + promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) try { - const rs = await Promise.all([ - freshProductsPromise, - featuredProductsPromise, - ]) + 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 + }) return { - props: { - featuredAndDiscountFacetsValue, - freshProducts: freshFacetId ? rs[0].products : [], - featuredProducts: facetValueIdsForFeaturedProducts.length > 0 ? rs[1].products : [] - }, + props, revalidate: 60, } } catch (err) { } - - - } diff --git a/src/components/common/LabelCommon/LabelCommon.tsx b/src/components/common/LabelCommon/LabelCommon.tsx index fcb9b7eae..23f2d7d6a 100644 --- a/src/components/common/LabelCommon/LabelCommon.tsx +++ b/src/components/common/LabelCommon/LabelCommon.tsx @@ -2,6 +2,7 @@ import classNames from 'classnames' import React from 'react' import s from './LabelCommon.module.scss' interface LabelCommonProps extends React.HTMLAttributes { + children?: React.ReactNode size?: 'default' | 'large' shape?: 'half' | 'round' | 'default' type?: 'default' | 'discount' | 'waiting' | 'delivering' | 'delivered' diff --git a/src/components/common/ProductCard/ProductCard.tsx b/src/components/common/ProductCard/ProductCard.tsx index b9c351aab..4761aac92 100644 --- a/src/components/common/ProductCard/ProductCard.tsx +++ b/src/components/common/ProductCard/ProductCard.tsx @@ -17,7 +17,7 @@ export interface ProductCardProps extends ProductCard { } const ProductCardComponent = ({ - category, + collection, name, slug, weight, @@ -45,9 +45,9 @@ const ProductCardComponent = ({ { - category && + collection &&

- {category} + {collection}
}
diff --git a/src/components/modules/home/CollectionCarcousel/CollectionCarcousel.tsx b/src/components/modules/home/CollectionCarcousel/CollectionCarcousel.tsx index 3007624db..5dccb1052 100644 --- a/src/components/modules/home/CollectionCarcousel/CollectionCarcousel.tsx +++ b/src/components/modules/home/CollectionCarcousel/CollectionCarcousel.tsx @@ -12,7 +12,7 @@ interface ColectionCarcouselProps extends CollectionHeadingProps { data: ProductCardProps[] itemKey: string viewAllLink?: string, - category:string + category?: string } const ColectionCarcousel = ({ @@ -21,7 +21,8 @@ const ColectionCarcousel = ({ title, subtitle, type, - category + category, + viewAllLink = ROUTE.PRODUCTS, }: ColectionCarcouselProps) => { return (
@@ -34,7 +35,7 @@ const ColectionCarcousel = ({ >
- +
diff --git a/src/components/modules/home/FreshProducts/FreshProducts.tsx b/src/components/modules/home/FreshProducts/FreshProducts.tsx index 33b872032..cf773d495 100644 --- a/src/components/modules/home/FreshProducts/FreshProducts.tsx +++ b/src/components/modules/home/FreshProducts/FreshProducts.tsx @@ -1,12 +1,32 @@ import { ProductCard } from '@commerce/types/product' -import { Product } from '@framework/schema' -import React from 'react' +import { Collection } from '@framework/schema' +import React, { useMemo } from 'react' +import { OPTION_ALL, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' import { CollectionCarcousel } from '..' interface FreshProductsProps { data: ProductCard[] + collections: Collection[] } -const FreshProducts = ({ data }: FreshProductsProps) => { +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 => { + return { + ...item, + collection: getCategoryNameFromCollectionId(collections, item.collectionIds ? item.collectionIds[0] : undefined) + } + }) + }, [data, collections]) + if (data.length === 0) { return null } @@ -14,11 +34,11 @@ const FreshProducts = ({ data }: FreshProductsProps) => {
) diff --git a/src/utils/funtion.utils.ts b/src/utils/funtion.utils.ts index d486ca4d0..853b77108 100644 --- a/src/utils/funtion.utils.ts +++ b/src/utils/funtion.utils.ts @@ -1,6 +1,7 @@ import { Facet } from "@commerce/types/facet"; import { FacetValue } from './../../framework/vendure/schema.d'; import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT } from "./constanst.utils"; +import { PromiseWithKey } from "./types.utils"; export function isMobile() { return window.innerWidth < 768 @@ -55,4 +56,8 @@ export function getFacetNamesFromIds(facets: FacetValue[], ids?: string[]): stri const facetItems = facets.filter((item: FacetValue) => ids.includes(item.id)) const names = facetItems.map((item: FacetValue) => item.name) return names.join(", ") -} \ No newline at end of file +} + +export function getAllPromies (promies: PromiseWithKey[]) { + return promies.map(item => item.promise) +} diff --git a/src/utils/types.utils.ts b/src/utils/types.utils.ts index e2ce516d4..ca21b605f 100644 --- a/src/utils/types.utils.ts +++ b/src/utils/types.utils.ts @@ -50,4 +50,10 @@ export type filterContextType = { visible: boolean; open: () => void; close: () => void; -}; \ No newline at end of file +}; + +export type PromiseWithKey = { + key: string + promise: PromiseLike + keyResult?: string, +} From 2ccccc51deb5f990dd689401f04844b7f63d5315 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Tue, 5 Oct 2021 10:35:53 +0700 Subject: [PATCH 05/20] :recycle: enhan: not get fresh and featured product when don't have facet id :%s --- pages/index.tsx | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/pages/index.tsx b/pages/index.tsx index cac464a43..010094de4 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -62,27 +62,34 @@ export async function getStaticProps({ const freshFacetId = getFreshFacetId(facets) if (freshFacetId) { freshProductvariables.facetValueIds = [freshFacetId] + const freshProductsPromise = commerce.getAllProducts({ + variables: freshProductvariables, + config, + preview, + }) + promisesWithKey.push({ key: 'freshProducts', promise: freshProductsPromise, keyResult: 'products' }) + } else { + props.freshProducts = [] } - const freshProductsPromise = commerce.getAllProducts({ - variables: freshProductvariables, - config, - preview, - }) - promisesWithKey.push({ key: 'freshProducts', promise: freshProductsPromise, keyResult: 'products' }) // featured products const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED) const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT) const facetValueIdsForFeaturedProducts = [...allFeaturedFacetIds, ...allDiscountFacetIds] - const featuredProductsPromise = commerce.getAllProducts({ - variables: { - facetValueIds: facetValueIdsForFeaturedProducts - }, - config, - preview, - }) - promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' }) + + if (facetValueIdsForFeaturedProducts.length > 0) { + const featuredProductsPromise = commerce.getAllProducts({ + variables: { + facetValueIds: facetValueIdsForFeaturedProducts + }, + config, + preview, + }) + promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' }) + } else { + props.featuredProducts = [] + } // collection const collectionsPromise = commerce.getAllCollections({ From c154541948a5a1d647b4f7bc4c638618049a331f Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Tue, 5 Oct 2021 18:10:22 +0700 Subject: [PATCH 06/20] :sparkles: feat: (product list) all products, brands, collection, featured :%s --- .../vendure/api/operations/get-all-facets.ts | 9 +- pages/products.tsx | 85 +++++++++++++++++-- .../common/MenuNavigation/MenuNavigation.tsx | 12 +-- .../common/ProductList/ProductList.tsx | 5 +- .../ProductListFilter/ProductListFilter.tsx | 32 ++++--- src/utils/constanst.utils.ts | 1 + src/utils/types.utils.ts | 19 +++-- 7 files changed, 128 insertions(+), 35 deletions(-) diff --git a/framework/vendure/api/operations/get-all-facets.ts b/framework/vendure/api/operations/get-all-facets.ts index c4b002744..0bde04090 100644 --- a/framework/vendure/api/operations/get-all-facets.ts +++ b/framework/vendure/api/operations/get-all-facets.ts @@ -1,10 +1,10 @@ import { OperationContext } from '@commerce/api/operations' import { Facet } from '@commerce/types/facet' import { Provider, VendureConfig } from '../' -import { GetAllFacetsQuery } from '../../schema' +import { FacetFilterParameter, FacetSortParameter, GetAllFacetsQuery } from '../../schema' import { getAllFacetsQuery } from '../../utils/queries/get-all-facets-query' -export type FacetVariables = { first?: number } +export type FacetVariables = { first?: number, filter?: FacetFilterParameter, sort?: FacetSortParameter } export default function getAllFacetsOperation({ commerce, @@ -27,9 +27,10 @@ export default function getAllFacetsOperation({ } = {}): Promise<{ facets: Facet[] | any[] }> { const config = commerce.getConfig(cfg) const variables = { - input: { + options: { take: vars.first, - groupByFacet: true, + filter: vars.filter, + sort: vars.sort, }, } const { data } = await config.fetch(query, { diff --git a/pages/products.tsx b/pages/products.tsx index 4f9c4eb66..93dda5cdb 100644 --- a/pages/products.tsx +++ b/pages/products.tsx @@ -1,19 +1,94 @@ +import { ProductCard } from '@commerce/types/product'; +import { Collection, Facet } from '@framework/schema'; +import commerce from '@lib/api/commerce'; +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 RecipeListBanner from 'src/components/modules/recipes-list/RecipeListBanner/RecipeListBanner'; -import RecipesList from 'src/components/modules/recipes-list/RecipesList/RecipesList'; +import { CODE_FACET_BRAND, CODE_FACET_FEATURED, DEFAULT_PAGE_SIZE } 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'; +interface Props { + facets: Facet[], + collections: Collection[], + products: ProductCard[], -export default function Products() { +} + +export default function Products({ facets, collections, products }: Props) { + // console.log("facets: ", products) return ( <> - - + + ) } +export async function getStaticProps({ + preview, + locale, + locales, +}: GetStaticPropsContext) { + const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + + const facetsPromise = commerce.getAllFacets({ + variables: { + sort: { + code: SortOrder.Asc + }, + filter: { + code: { + in: [CODE_FACET_FEATURED, CODE_FACET_BRAND] + } + } + }, + config, + preview, + }) + + promisesWithKey.push({ key: 'facets', promise: facetsPromise, keyResult: 'facets' }) + + // collection + const collectionsPromise = commerce.getAllCollections({ + variables: {}, + config, + preview, + }) + promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) + + // products + const productsPromise = commerce.getAllProducts({ + variables: { + first: DEFAULT_PAGE_SIZE, + }, + config, + preview, + }) + promisesWithKey.push({ key: 'products', promise: productsPromise, keyResult: 'products' }) + + + 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 + }) + + return { + props, + revalidate: 60, + } + } catch (err) { + + } +} + Products.Layout = Layout diff --git a/src/components/common/MenuNavigation/MenuNavigation.tsx b/src/components/common/MenuNavigation/MenuNavigation.tsx index 4a8943051..a2554451e 100644 --- a/src/components/common/MenuNavigation/MenuNavigation.tsx +++ b/src/components/common/MenuNavigation/MenuNavigation.tsx @@ -1,15 +1,17 @@ import classNames from 'classnames' import Link from 'next/link' import { useRouter } from 'next/router' +import { ROUTE } from 'src/utils/constanst.utils' import s from './MenuNavigation.module.scss' interface Props { children?: any, - heading:string, - categories:{name:string,link:string}[] + heading: string, + linkPrefix: string, + categories: { name: string, slug?: string, code?: string }[] } -const MenuNavigation = ({heading,categories}:Props)=> { +const MenuNavigation = ({ heading, linkPrefix, categories }: Props) => { const router = useRouter() return ( @@ -19,8 +21,8 @@ const MenuNavigation = ({heading,categories}:Props)=> { { categories.map(item =>
  • - - + + {item.name} diff --git a/src/components/common/ProductList/ProductList.tsx b/src/components/common/ProductList/ProductList.tsx index 7428e3a63..0c5deb91e 100644 --- a/src/components/common/ProductList/ProductList.tsx +++ b/src/components/common/ProductList/ProductList.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react' +import { DEFAULT_PAGE_SIZE } from 'src/utils/constanst.utils' import PaginationCommon from '../PaginationCommon/PaginationCommon' import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard' import s from "./ProductList.module.scss" @@ -15,13 +16,13 @@ const ProductList = ({data}: ProductListProps) => {
    { - data.slice(currentPage*20,(currentPage+1)*20).map((product,index)=>{ + data.slice(currentPage*DEFAULT_PAGE_SIZE,(currentPage+1)* DEFAULT_PAGE_SIZE).map((product,index)=>{ return }) }
    - +
    ) diff --git a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx index 5315283dc..736f3d239 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx +++ b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx @@ -1,12 +1,19 @@ +import { ProductCard } from '@commerce/types/product' +import { Collection, Facet } from '@framework/schema' import React from 'react' import { HeadingCommon, ProductList, SelectCommon } from 'src/components/common' import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon' import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation' -import { BRAND, CATEGORY, FEATURED} from 'src/utils/constanst.utils' -import { PRODUCT_DATA_TEST_PAGE } from 'src/utils/demo-data' +import { QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' +import { PRODUCT_DATA_TEST_PAGE } from 'src/utils/demo-data' import s from './ProductListFilter.module.scss' -interface ProductListFilterProps {} +interface ProductListFilterProps { + facets: Facet[] + collections: Collection[] + products: ProductCard[] + +} const BREADCRUMB = [ { @@ -29,11 +36,7 @@ const OPTIONSLECT = [ }, ] -const onModalClose = () => { - -} - -const ProductListFilter = (props: ProductListFilterProps) => { +const ProductListFilter = ({facets, collections, products}: ProductListFilterProps) => { return (
    @@ -41,9 +44,14 @@ const ProductListFilter = (props: ProductListFilterProps) => {
    - - - + + { + facets.map(item => ) + }
    @@ -56,7 +64,7 @@ const ProductListFilter = (props: ProductListFilterProps) => {
    - +
  • diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index 1db198178..432e9cc78 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -113,6 +113,7 @@ export const BRAND = [ export const CODE_FACET_FEATURED = 'featured' export const CODE_FACET_DISCOUNT = 'discount' +export const CODE_FACET_BRAND = 'brand' export const CODE_FACET_FEATURED_VARIANT = { FRESH: 'fresh', } diff --git a/src/utils/types.utils.ts b/src/utils/types.utils.ts index ca21b605f..59243b80f 100644 --- a/src/utils/types.utils.ts +++ b/src/utils/types.utils.ts @@ -34,18 +34,23 @@ export interface BlogProps { export interface CheckOutForm { name?: string - email?:string + email?: string address?: string - city?:string - state?:string - code?:number - phone?:number - method?:string - shipping_fee?:number + city?: string + state?: string + code?: number + phone?: number + method?: string + shipping_fee?: number } export type MouseAndTouchEvent = MouseEvent | TouchEvent +export enum SortOrder { + Asc = 'ASC', + Desc = 'DESC', +} + export type filterContextType = { visible: boolean; open: () => void; From ef7b490416b19aeace5baa00da68326f116da456 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Wed, 6 Oct 2021 11:14:10 +0700 Subject: [PATCH 07/20] :sparkles: feat: get collections, facets in menu filter :%s --- framework/vendure/schema.d.ts | 5 +- .../HeaderSubMenu/HeaderSubMenu.tsx | 2 +- .../Layout/LayoutContent/LayoutContent.tsx | 9 +- .../common/MenuFilter/MenuFilter.module.scss | 4 +- .../common/MenuFilter/MenuFilter.tsx | 36 +++---- .../MenuFilterItem/MenuFilterItem.module.scss | 16 +++ .../MenuFilterItem/MenuFilterItem.tsx | 28 +++++ .../MenuNavigationProductList.module.scss | 18 +--- .../MenuNavigationProductList.tsx | 100 +++++++++++------- .../MenuSort/MenuSort.module.scss | 7 +- .../hooks/collection/useGetAllCollection.tsx | 4 +- src/components/hooks/facets/useFacets.tsx | 2 +- 12 files changed, 136 insertions(+), 95 deletions(-) create mode 100644 src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.module.scss create mode 100644 src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index ef667d9f3..5faa87b43 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3228,8 +3228,9 @@ export type GetAllFacetsQuery = { __typename?: 'Query' } & { items: Array< { __typename?: 'Facet' } & Pick< Facet, - 'id' | 'name' | 'code' - > & { + 'id' | 'name' | 'code' | 'values' + > + & { parent?: Maybe<{ __typename?: 'Facet' } & Pick> children?: Maybe< Array<{ __typename?: 'Facet' } & Pick> diff --git a/src/components/common/Header/components/HeaderSubMenu/HeaderSubMenu.tsx b/src/components/common/Header/components/HeaderSubMenu/HeaderSubMenu.tsx index 2cd72e5f2..b119a1c8a 100644 --- a/src/components/common/Header/components/HeaderSubMenu/HeaderSubMenu.tsx +++ b/src/components/common/Header/components/HeaderSubMenu/HeaderSubMenu.tsx @@ -42,7 +42,7 @@ const HeaderSubMenu = memo(() => {
      {/* todo: handle active item */}
    • - Categories + Categories
    • { MENU.map(item =>
    • = ({ children }) => { - const { pathname } = useRouter() - const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false }) const router = useRouter() + const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: true }) const {messages, removeMessage} = useMessage() const toggleFilter = () => { @@ -30,6 +29,7 @@ const LayoutContent: FC = ({ children }) => { return ( <>
      + {router.pathname}
      { router.pathname === ROUTE.ACCOUNT ? @@ -38,10 +38,9 @@ const LayoutContent: FC = ({ children }) => { :
      {children}
      } -
      { - FILTER_PAGE.includes(pathname) && (
      ) + FILTER_PAGE.includes(router.pathname) && (
      ) }
      diff --git a/src/components/common/MenuFilter/MenuFilter.module.scss b/src/components/common/MenuFilter/MenuFilter.module.scss index b8b6596ce..e887734a9 100644 --- a/src/components/common/MenuFilter/MenuFilter.module.scss +++ b/src/components/common/MenuFilter/MenuFilter.module.scss @@ -1,9 +1,7 @@ @import "../../../styles/utilities"; .menuFilterWrapper{ - @apply spacing-horizontal; - .menuFilterHeading{ - @apply sub-headline font-bold ; + @apply sub-headline font-bold; color: var(--text-active); font-feature-settings: 'salt' on; margin: 0.8rem 0; diff --git a/src/components/common/MenuFilter/MenuFilter.tsx b/src/components/common/MenuFilter/MenuFilter.tsx index 454942734..a0021da79 100644 --- a/src/components/common/MenuFilter/MenuFilter.tsx +++ b/src/components/common/MenuFilter/MenuFilter.tsx @@ -2,43 +2,31 @@ import classNames from 'classnames' import { useEffect, useState } from 'react'; import s from './MenuFilter.module.scss' +import MenuFilterItem from './MenuFilterItem/MenuFilterItem'; interface Props { children?: any, - heading?:string, - categories:{name:string,link:string}[], - type:string, + heading?: string, + categories: { name: string, slug?: string, code?: string }[], + type: string, onChangeValue?: (value: Object) => void } -const MenuFilter = ({heading,categories,type,onChangeValue}:Props)=> { - const [active, setActive] = useState(''); +const MenuFilter = ({ heading, categories, type, onChangeValue }: Props) => { + function handleClick(value: string) { - function handleClick(link:string){ - setActive(link); - - if(active === link){ - setActive(''); - } } - useEffect(()=>{ - - let href = active?.split("="); - const linkValue = href[1]; - - onChangeValue && onChangeValue({[type]:linkValue}); - },[active]) - return (

      {heading}

        { - categories.map(item =>
      • -
        handleClick(item.link)} className={classNames({ [s.active]: item.link === active? true: false })}> - {item.name} -
        -
      • ) + categories.map(item => ) }
      diff --git a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.module.scss b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.module.scss new file mode 100644 index 000000000..f70a224f2 --- /dev/null +++ b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.module.scss @@ -0,0 +1,16 @@ + +.menuFilterItem { + margin: 1rem 0; + padding: 0; + div { + padding: 0.8rem 1.6rem; + margin-right: 0.8rem; + background-color: var(--gray); + border-radius: 0.8rem; + cursor: pointer; + &.active { + color: var(--white); + background-color: var(--primary); + } + } +} diff --git a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx new file mode 100644 index 000000000..d38d57208 --- /dev/null +++ b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx @@ -0,0 +1,28 @@ +import classNames from 'classnames'; +import { useState } from 'react'; +import s from './MenuFilterItem.module.scss'; + +interface Props { + name: string, + value: string, + onClick: (value: string) => void +} + +const MenuFilterItem = ({ name, value, onClick }: Props) => { + const [isSelected, setIsSelected] = useState(false) + + function handleClick() { + // todo + setIsSelected(!isSelected) + } + + return ( +
    • +
      + {name} +
      +
    • + ) +} + +export default MenuFilterItem diff --git a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.module.scss b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.module.scss index 6c7ee9c17..9fa431ca5 100644 --- a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.module.scss +++ b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.module.scss @@ -1,13 +1,5 @@ @import "../../../styles/utilities"; -.menuNavigationProductListDesktop{ - @screen sm { - @apply hidden; - } - @screen xl { - @apply block; - } -} .menuNavigationProductListMobile{ @apply relative transition-all duration-100; &.isShow{ @@ -37,7 +29,7 @@ transform: translateY(0%) } .content{ - @apply absolute w-full h-full; + @apply absolute w-full h-full spacing-horizontal custom-scroll; margin-top: 3rem; padding-top: 10rem ; padding-bottom: 10rem; @@ -46,6 +38,7 @@ height: 96%; bottom: 0; border-radius: 2.4rem 2.4rem 0 0; + .head{ @apply flex justify-between fixed; top:0; @@ -57,12 +50,11 @@ background-color: white; z-index: 10000; h3{ - @apply heading-3 font-bold; - color:var(--text-base); + @apply heading-3 font-heading; } } .foot{ - @apply fixed; + @apply fixed text-center; bottom: 0; left:0; width: 100%; @@ -70,7 +62,7 @@ padding: 0 1rem 3rem 1rem; } - button{ + button { margin-top: 2rem; width: 100%; } diff --git a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx index 026710bbf..ff4ab2b5a 100644 --- a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx +++ b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx @@ -1,56 +1,78 @@ +import { QueryFacetsArgs } from '@framework/schema'; +import classNames from 'classnames'; import React, { useState } from 'react'; -import {ButtonCommon} from 'src/components/common'; +import { ButtonCommon } from 'src/components/common'; +import { useGetAllCollection } from 'src/components/hooks/collection'; +import { useFacets } from 'src/components/hooks/facets'; +import IconHide from 'src/components/icons/IconHide'; +import { CODE_FACET_BRAND, CODE_FACET_FEATURED, QUERY_KEY } from 'src/utils/constanst.utils'; +import { LANGUAGE } from 'src/utils/language.utils'; +import { SortOrder } from 'src/utils/types.utils'; +import MenuFilter from '../MenuFilter/MenuFilter'; +import SkeletonParagraph from '../SkeletonCommon/SkeletonParagraph/SkeletonParagraph'; import s from './MenuNavigationProductList.module.scss'; import MenuSort from './MenuSort/MenuSort'; -import {LANGUAGE} from 'src/utils/language.utils'; -import classNames from 'classnames' -import MenuFilter from '../MenuFilter/MenuFilter'; -import MenuNavigation from '../MenuNavigation/MenuNavigation'; -import IconHide from 'src/components/icons/IconHide'; -interface Props{ - categories:{name:string,link:string}[], - brands:{name:string,link:string}[], - featured:{name:string,link:string}[], +interface Props { visible: boolean, onClose: () => void } -const MenuNavigationProductList = ({categories,brands,featured,visible,onClose}:Props)=>{ - - const [dataSort,setDataSort] = useState({}); - - function handleValue(value:Object){ - setDataSort({...dataSort,...value}); +const FACET_QUERY = { + options: { + sort: { + code: SortOrder.Asc + }, + filter: { + code: { + in: [CODE_FACET_FEATURED, CODE_FACET_BRAND] + } + } } - function filter(){ +} as QueryFacetsArgs + +const MenuNavigationProductList = ({ visible, onClose }: Props) => { + const { facets, loading: facetsLoading } = useFacets(FACET_QUERY) + const { collections, loading: collectionLoading } = useGetAllCollection() + + const [dataSort, setDataSort] = useState({}); + + function handleValue(value: Object) { + setDataSort({ ...dataSort, ...value }); + } + function filter() { // console.log(dataSort) } - return( - <> -
      - - - -
      -
      -
      -
      -
      -

      FILTER

      -
      -
      - - - - -
      - {LANGUAGE.BUTTON_LABEL.CONFIRM} -
      + + + return ( +
      +
      +
      +
      +

      FILTER

      +
      +
      + {collectionLoading && } + + {facetsLoading && <> + + + } + { + facets?.map(item => ) + } + +
      + {LANGUAGE.BUTTON_LABEL.CONFIRM}
      - +
      ) } diff --git a/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.module.scss b/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.module.scss index 732f0e6eb..8a466ff74 100644 --- a/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.module.scss +++ b/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.module.scss @@ -1,11 +1,8 @@ @import "../../../../styles/utilities"; -.menuSortWrapper{ - @apply spacing-horizontal; +.menuSortWrapper{ .menuSortHeading{ - @apply sub-headline font-bold ; - color: var(--text-active); - font-feature-settings: 'salt' on; + @apply heading-3 font-heading; margin: 0.8rem 0; } .menuSortList{ diff --git a/src/components/hooks/collection/useGetAllCollection.tsx b/src/components/hooks/collection/useGetAllCollection.tsx index 8e7232b2d..7e23cdf40 100644 --- a/src/components/hooks/collection/useGetAllCollection.tsx +++ b/src/components/hooks/collection/useGetAllCollection.tsx @@ -5,8 +5,8 @@ import useSWR from 'swr'; const useGetAllCollection = () => { - const { data, ...rest } = useSWR([getCollectionsNameQuery], gglFetcher) - return { collections: data?.collections, ...rest } + const { data, isValidating, ...rest } = useSWR([getCollectionsNameQuery], gglFetcher) + return { collections: data?.collections.items || [], loading: isValidating, ...rest } } export default useGetAllCollection; \ No newline at end of file diff --git a/src/components/hooks/facets/useFacets.tsx b/src/components/hooks/facets/useFacets.tsx index c9a4e85ab..8966bcedc 100644 --- a/src/components/hooks/facets/useFacets.tsx +++ b/src/components/hooks/facets/useFacets.tsx @@ -5,7 +5,7 @@ import useSWR from 'swr' const useFacets = (options?: QueryFacetsArgs) => { const { data, isValidating, ...rest } = useSWR([getAllFacetsQuery, options], gglFetcher) - return { items: data?.facets.items, totalItems: data?.facets.totalItems, loading: isValidating, ...rest } + return { facets: data?.facets.items, totalItems: data?.facets.totalItems, loading: isValidating, ...rest } } export default useFacets From c49ba5062a20417c8c8e82a4c8cd1ce21db6705c Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Wed, 6 Oct 2021 15:01:23 +0700 Subject: [PATCH 08/20] :sparkles: feat: initial active value for option filter products :%s --- .../utils/queries/get-collections-query.ts | 2 +- .../LayoutContent/LayoutContent.module.scss | 2 +- .../Layout/LayoutContent/LayoutContent.tsx | 3 +- .../common/MenuFilter/MenuFilter.module.scss | 17 +---- .../common/MenuFilter/MenuFilter.tsx | 17 ++--- .../MenuFilterItem/MenuFilterItem.module.scss | 1 - .../MenuFilterItem/MenuFilterItem.tsx | 18 +++-- .../MenuNavigation/MenuNavigation.module.scss | 32 ++------ .../common/MenuNavigation/MenuNavigation.tsx | 25 +++---- .../MenuNavigationItem.module.scss | 15 ++++ .../MenuNavigationItem/MenuNavigationItem.tsx | 57 ++++++++++++++ .../MenuNavigationProductList.tsx | 74 ++++++++++++++++--- .../MenuSort/MenuSort.module.scss | 25 ------- .../MenuSort/MenuSort.tsx | 61 ++++----------- .../MenuSortItem/MenuSortItem.module.scss | 26 +++++++ .../MenuSort/MenuSortItem/MenuSortItem.tsx | 24 ++++++ .../MenuSort/MenuSortItem/check.svg | 3 + .../ModalCreateUserInfo.tsx | 2 +- .../ProductList/ProductList.module.scss | 3 +- .../common/SelectCommon/SelectCommon.tsx | 35 ++++++--- .../SelectOption/SelectOption.tsx | 10 +-- .../EditInfoModal/EditInfoModal.tsx | 2 +- .../ShippingInfoForm/ShippingInfoForm.tsx | 2 +- .../ProductListFilter.module.scss | 26 +------ .../ProductListFilter/ProductListFilter.tsx | 42 ++--------- .../ProductSort/ProductSort.tsx | 40 ++++++++++ .../ProductsMenuNavigationTablet.module.scss | 12 +++ .../ProductsMenuNavigationTablet.tsx | 28 +++++++ .../recipes-list/RecipesList/RecipesList.tsx | 4 +- src/styles/_base.scss | 2 +- src/utils/constanst.utils.ts | 31 +++++++- 31 files changed, 400 insertions(+), 241 deletions(-) create mode 100644 src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.module.scss create mode 100644 src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx create mode 100644 src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/MenuSortItem.module.scss create mode 100644 src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/MenuSortItem.tsx create mode 100644 src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/check.svg create mode 100644 src/components/modules/product-list/ProductListFilter/ProductSort/ProductSort.tsx create mode 100644 src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.module.scss create mode 100644 src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx diff --git a/framework/vendure/utils/queries/get-collections-query.ts b/framework/vendure/utils/queries/get-collections-query.ts index 79e00a292..f07a85249 100644 --- a/framework/vendure/utils/queries/get-collections-query.ts +++ b/framework/vendure/utils/queries/get-collections-query.ts @@ -24,7 +24,7 @@ export const getCollectionsNameQuery = /* GraphQL */ ` collections{ items{ name - link:slug + slug } } } diff --git a/src/components/common/Layout/LayoutContent/LayoutContent.module.scss b/src/components/common/Layout/LayoutContent/LayoutContent.module.scss index 97ed29624..9e687217e 100644 --- a/src/components/common/Layout/LayoutContent/LayoutContent.module.scss +++ b/src/components/common/Layout/LayoutContent/LayoutContent.module.scss @@ -17,7 +17,7 @@ } } .filter { - @screen xl { + @screen md { display: none; } } diff --git a/src/components/common/Layout/LayoutContent/LayoutContent.tsx b/src/components/common/Layout/LayoutContent/LayoutContent.tsx index ce4022dd0..6e82f338a 100644 --- a/src/components/common/Layout/LayoutContent/LayoutContent.tsx +++ b/src/components/common/Layout/LayoutContent/LayoutContent.tsx @@ -15,7 +15,7 @@ interface Props { const LayoutContent: FC = ({ children }) => { const router = useRouter() - const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: true }) + const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false }) const {messages, removeMessage} = useMessage() const toggleFilter = () => { @@ -29,7 +29,6 @@ const LayoutContent: FC = ({ children }) => { return ( <>
      - {router.pathname}
      { router.pathname === ROUTE.ACCOUNT ? diff --git a/src/components/common/MenuFilter/MenuFilter.module.scss b/src/components/common/MenuFilter/MenuFilter.module.scss index e887734a9..ef045ef02 100644 --- a/src/components/common/MenuFilter/MenuFilter.module.scss +++ b/src/components/common/MenuFilter/MenuFilter.module.scss @@ -1,4 +1,5 @@ @import "../../../styles/utilities"; + .menuFilterWrapper{ .menuFilterHeading{ @apply sub-headline font-bold; @@ -17,21 +18,5 @@ width: 100%; border-bottom: 1px solid var(--border-line); } - - li{ - margin: 1rem 0; - padding:0; - div{ - padding: 0.8rem 1.6rem; - margin-right: 0.8rem; - background-color: var(--gray); - border-radius: 0.8rem; - cursor: pointer; - &.active { - color:white; - background-color: var(--primary); - } - } - } } } diff --git a/src/components/common/MenuFilter/MenuFilter.tsx b/src/components/common/MenuFilter/MenuFilter.tsx index a0021da79..b23816aff 100644 --- a/src/components/common/MenuFilter/MenuFilter.tsx +++ b/src/components/common/MenuFilter/MenuFilter.tsx @@ -1,19 +1,17 @@ -import classNames from 'classnames' -import { useEffect, useState } from 'react'; - -import s from './MenuFilter.module.scss' +import s from './MenuFilter.module.scss'; import MenuFilterItem from './MenuFilterItem/MenuFilterItem'; + interface Props { children?: any, heading?: string, categories: { name: string, slug?: string, code?: string }[], type: string, - onChangeValue?: (value: Object) => void + onChange: (value: string, type: string, isSellect?: boolean) => void } -const MenuFilter = ({ heading, categories, type, onChangeValue }: Props) => { - function handleClick(value: string) { - +const MenuFilter = ({ heading, categories, type, onChange }: Props) => { + function handleChange(value: string, isSellect: boolean) { + onChange(value, type, isSellect) } return ( @@ -24,8 +22,9 @@ const MenuFilter = ({ heading, categories, type, onChangeValue }: Props) => { categories.map(item => ) }
    diff --git a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.module.scss b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.module.scss index f70a224f2..3ecab2495 100644 --- a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.module.scss +++ b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.module.scss @@ -1,4 +1,3 @@ - .menuFilterItem { margin: 1rem 0; padding: 0; diff --git a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx index d38d57208..e247bfe93 100644 --- a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx +++ b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx @@ -1,18 +1,26 @@ import classNames from 'classnames'; -import { useState } from 'react'; +import { route } from 'next/dist/server/router'; +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; import s from './MenuFilterItem.module.scss'; interface Props { name: string, value: string, - onClick: (value: string) => void + type: string, + onChange: (value: string, isSellect: boolean) => void } -const MenuFilterItem = ({ name, value, onClick }: Props) => { - const [isSelected, setIsSelected] = useState(false) +const MenuFilterItem = ({ name, value, type, onChange }: Props) => { + const router = useRouter() + const [isSelected, setIsSelected] = useState() + useEffect(() => { + const rs = (router.query[type] || []).includes(value) + setIsSelected(rs) + }, [type, router.query, value]) function handleClick() { - // todo + onChange(value, !isSelected) setIsSelected(!isSelected) } diff --git a/src/components/common/MenuNavigation/MenuNavigation.module.scss b/src/components/common/MenuNavigation/MenuNavigation.module.scss index 6d0a06c3f..49639ce3c 100644 --- a/src/components/common/MenuNavigation/MenuNavigation.module.scss +++ b/src/components/common/MenuNavigation/MenuNavigation.module.scss @@ -3,31 +3,13 @@ @apply hidden; @screen md { @apply block; - } - .menuNavigationHeading{ - @screen md { - @apply sub-headline font-bold ; - color: var(--text-active); - font-feature-settings: 'salt' on; - margin: 1.6rem 0; - } - } - .menuNavigationList{ - @screen md { - li{ - margin: 0.8rem 0; - a{ - display:block; - width:100%; - color:var(--text-base); - &:hover { - @apply text-primary; - } - &.active { - @apply text-primary; - } - } + .menuNavigationHeading{ + @screen md { + @apply sub-headline font-bold ; + color: var(--text-active); + font-feature-settings: 'salt' on; + margin: 1.6rem 0; } } - } + } } diff --git a/src/components/common/MenuNavigation/MenuNavigation.tsx b/src/components/common/MenuNavigation/MenuNavigation.tsx index a2554451e..669eacf59 100644 --- a/src/components/common/MenuNavigation/MenuNavigation.tsx +++ b/src/components/common/MenuNavigation/MenuNavigation.tsx @@ -1,32 +1,25 @@ -import classNames from 'classnames' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { ROUTE } from 'src/utils/constanst.utils' import s from './MenuNavigation.module.scss' +import MenuNavigationItem from './MenuNavigationItem/MenuNavigationItem' interface Props { children?: any, heading: string, - linkPrefix: string, + queryKey: string, categories: { name: string, slug?: string, code?: string }[] } -const MenuNavigation = ({ heading, linkPrefix, categories }: Props) => { - const router = useRouter() - +const MenuNavigation = ({ heading, queryKey, categories }: Props) => { return (

    {heading}({categories.length})

    diff --git a/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.module.scss b/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.module.scss new file mode 100644 index 000000000..97f336443 --- /dev/null +++ b/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.module.scss @@ -0,0 +1,15 @@ +.menuNavigationItem { + @screen md { + display: block; + width: 100%; + margin: 0.8rem 0; + color: var(--text-base); + cursor: pointer; + &:hover { + @apply text-active; + } + &.active { + @apply text-primary; + } + } +} diff --git a/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx b/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx new file mode 100644 index 000000000..c7aeba724 --- /dev/null +++ b/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx @@ -0,0 +1,57 @@ +import classNames from 'classnames' +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' +import { QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils' +import s from './MenuNavigationItem.module.scss' + +interface Props { + name: string + value: string + queryKey: string, +} + +const MenuNavigationItem = ({ name, value, queryKey }: Props) => { + const router = useRouter() + const [isActive, setIsActive] = useState() + + useEffect(() => { + if (!value) { + setIsActive(false) + } + + const queryString = router.query[queryKey] as string || '' + setIsActive(queryString.split(QUERY_SPLIT_SEPERATOR).includes(value)) + }, [router.query, queryKey, value]) + + const handleClick = () => { + const queryString = router.query[queryKey] as string || '' + const prevQuery = queryString.split(QUERY_SPLIT_SEPERATOR) + + let newQuery = [] as string[] + if (isActive) { + newQuery = prevQuery.filter(item => item !== value) + } else { + newQuery = [...prevQuery, value] + } + // setIsActive(!isActive) + + router.push({ + pathname: ROUTE.PRODUCTS, + query: { + ...router.query, + [queryKey]: newQuery.join(QUERY_SPLIT_SEPERATOR) + } + }, + undefined, { shallow: true } + ) + } + + return (
  • + {name} +
  • ) + + +} + +export default MenuNavigationItem diff --git a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx index ff4ab2b5a..af3d4c9f9 100644 --- a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx +++ b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx @@ -1,11 +1,12 @@ import { QueryFacetsArgs } from '@framework/schema'; import classNames from 'classnames'; -import React, { useState } from 'react'; +import { useRouter } from 'next/router'; +import React, { useEffect, useState } from 'react'; import { ButtonCommon } from 'src/components/common'; import { useGetAllCollection } from 'src/components/hooks/collection'; import { useFacets } from 'src/components/hooks/facets'; import IconHide from 'src/components/icons/IconHide'; -import { CODE_FACET_BRAND, CODE_FACET_FEATURED, QUERY_KEY } from 'src/utils/constanst.utils'; +import { CODE_FACET_BRAND, CODE_FACET_FEATURED, OPTIONS_SORT_PRODUCT, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'; import { LANGUAGE } from 'src/utils/language.utils'; import { SortOrder } from 'src/utils/types.utils'; import MenuFilter from '../MenuFilter/MenuFilter'; @@ -32,16 +33,65 @@ const FACET_QUERY = { } as QueryFacetsArgs const MenuNavigationProductList = ({ visible, onClose }: Props) => { + const router = useRouter() const { facets, loading: facetsLoading } = useFacets(FACET_QUERY) const { collections, loading: collectionLoading } = useGetAllCollection() + const [brandQuery, setBrandQuery] = useState([]) + const [featuredQuery, setFeaturedQuery] = useState([]) + const [categoryQuery, setCategoryQuery] = useState([]) + const [sortValue, setSortValue] = useState(); - const [dataSort, setDataSort] = useState({}); + useEffect(() => { + const rs = router.query[QUERY_KEY.SORTBY] as string + if (rs) { + setSortValue(rs) + } + }, [router.query]) - function handleValue(value: Object) { - setDataSort({ ...dataSort, ...value }); + function onSubmit() { + let newURL = `${ROUTE.PRODUCTS}?` + + if (categoryQuery.length > 0) { + newURL += `&${QUERY_KEY.CATEGORY}=${categoryQuery.join(",")}` + } + + if (brandQuery.length > 0) { + newURL += `&${QUERY_KEY.BRAND}=${brandQuery.join(",")}` + } + + if (featuredQuery.length > 0) { + newURL += `&${QUERY_KEY.FEATURED}=${featuredQuery.join(",")}` + } + + if (sortValue) { + newURL += `&${QUERY_KEY.SORTBY}=${sortValue}` + } + router.push(newURL) + onClose() } - function filter() { - // console.log(dataSort) + + const onSortChange = (value: string) => { + setSortValue(value) + } + + const onFilterOptionChange = (value: string, type: string, isSelect: boolean = true) => { + let rs = [...categoryQuery] + let setDataFunction = setCategoryQuery + + if (type === CODE_FACET_BRAND) { + rs = [...brandQuery] + setDataFunction = setBrandQuery + } else if (type === CODE_FACET_FEATURED) { + rs = [...featuredQuery] + setDataFunction = setFeaturedQuery + } + + if (isSelect) { + rs.push(value) + } else { + rs = rs.filter(item => item !== value) + } + setDataFunction(rs) } @@ -54,7 +104,7 @@ const MenuNavigationProductList = ({ visible, onClose }: Props) => {
    {collectionLoading && } - + {facetsLoading && <> @@ -64,11 +114,13 @@ const MenuNavigationProductList = ({ visible, onClose }: Props) => { key={item.id} type={item.code} categories={item.values} - heading={item.name} />) + heading={item.name} + onChange={onFilterOptionChange} + />) } - +
    - {LANGUAGE.BUTTON_LABEL.CONFIRM} + {LANGUAGE.BUTTON_LABEL.CONFIRM}
    diff --git a/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.module.scss b/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.module.scss index 8a466ff74..ac3b0681a 100644 --- a/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.module.scss +++ b/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.module.scss @@ -8,30 +8,5 @@ .menuSortList{ padding-bottom: 1rem; box-sizing: border-box; - - li{ - div{ - height: 4.8rem; - line-height: 4.8rem; - padding: 0 1.6rem; - margin-right: 0.8rem; - border-radius: 0.8rem; - cursor: pointer; - &.active { - @apply font-bold relative; - color:var(--text-active); - background-color: var(--gray); - &::after{ - @apply absolute; - content:""; - background-image: url('/assets/svg/check.svg'); - right: 1.6rem; - top: calc(50% - 24px/2); - width: 2.4rem; - height: 2.4rem; - } - } - } - } } } diff --git a/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.tsx b/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.tsx index 2e66dfc83..cd771fe6e 100644 --- a/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.tsx +++ b/src/components/common/MenuNavigationProductList/MenuSort/MenuSort.tsx @@ -1,63 +1,30 @@ import classNames from 'classnames'; import { useEffect, useState } from 'react'; -import { QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'; +import { PRODUCT_SORT_OPTION_VALUE, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'; import s from './MenuSort.module.scss'; +import MenuSortItem from './MenuSortItem/MenuSortItem'; interface Props { children?: any, - heading:string, - type:string, - onChangeValue?: (value: Object) => void + heading: string, + options: {name: string, value: string}[] + value?: string, + onChange: (value: string) => void } -const SORT = [ - { - name: 'By Name', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.SORTBY}=by-name`, - }, - { - name: 'Price(High to Low)', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.SORTBY}=high-to-low`, - }, - { - name: 'Price (Low to High)', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.SORTBY}=low-to-high`, - }, - { - name: 'On Sale', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.SORTBY}=on-sale`, - }, - ]; - -const MenuSort = ({heading,type,onChangeValue}:Props)=> { - const [active, setActive] = useState(''); - - function handleClick(link:string){ - setActive(link); - - if(active === link){ - setActive(''); - } - } - - useEffect(()=>{ - - let href = active?.split("="); - const linkValue = href[1]; - - onChangeValue && onChangeValue({[type]:linkValue}); - },[active]) - +const MenuSort = ({ heading, value, onChange, options }: Props) => { return (

    {heading}

      { - SORT.map(item =>
    • -
      handleClick(item.link)} className={classNames({ [s.active]: item.link === active? true: false })}> - {item.name} -
      -
    • ) + options.map(item => ) }
    diff --git a/src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/MenuSortItem.module.scss b/src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/MenuSortItem.module.scss new file mode 100644 index 000000000..894cc191a --- /dev/null +++ b/src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/MenuSortItem.module.scss @@ -0,0 +1,26 @@ +.menuSortItem { + div { + height: 4.8rem; + line-height: 4.8rem; + padding: 0 1.6rem; + margin-right: 0.8rem; + border-radius: 0.8rem; + cursor: pointer; + &.active { + @apply font-bold relative; + color: var(--text-active); + background-color: var(--primary-lightest); + &::after { + @apply absolute; + content: ""; + background-image: url("./check.svg"); + background-repeat: no-repeat; + background-position: center; + right: 1.6rem; + top: calc(50% - 24px / 2); + width: 2.4rem; + height: 2.4rem; + } + } + } +} diff --git a/src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/MenuSortItem.tsx b/src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/MenuSortItem.tsx new file mode 100644 index 000000000..f14e9cf24 --- /dev/null +++ b/src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/MenuSortItem.tsx @@ -0,0 +1,24 @@ +import classNames from 'classnames'; +import s from './MenuSortItem.module.scss'; + +interface Props { + name: string, + value: string, + currentValue?: string, + onChange: (value: string) => void +} + +const MenuSortItem = ({ onChange, name, value, currentValue }: Props) => { + const handleChange = () => { + onChange(value) + } + return ( +
  • +
    + {name} +
    +
  • + ) +} + +export default MenuSortItem diff --git a/src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/check.svg b/src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/check.svg new file mode 100644 index 000000000..0962fae2c --- /dev/null +++ b/src/components/common/MenuNavigationProductList/MenuSort/MenuSortItem/check.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/common/ModalCreateUserInfo/ModalCreateUserInfo.tsx b/src/components/common/ModalCreateUserInfo/ModalCreateUserInfo.tsx index c3794bf81..c5625c691 100644 --- a/src/components/common/ModalCreateUserInfo/ModalCreateUserInfo.tsx +++ b/src/components/common/ModalCreateUserInfo/ModalCreateUserInfo.tsx @@ -25,7 +25,7 @@ const ModalCreateUserInfo = ({ demoVisible: visible, demoCloseModal: closeModal
    - +
    diff --git a/src/components/common/ProductList/ProductList.module.scss b/src/components/common/ProductList/ProductList.module.scss index c49696ea5..e58b37f86 100644 --- a/src/components/common/ProductList/ProductList.module.scss +++ b/src/components/common/ProductList/ProductList.module.scss @@ -1,11 +1,10 @@ .wrapper{ + margin-top: 4rem; .list{ - // max-width: 109.4rem; @apply flex flex-wrap justify-around; } .pagination{ padding-top: 4.8rem; - // max-width: 109.4rem; @apply flex justify-center items-center ; } } \ No newline at end of file diff --git a/src/components/common/SelectCommon/SelectCommon.tsx b/src/components/common/SelectCommon/SelectCommon.tsx index 9b8c88e24..bf34ed3f9 100644 --- a/src/components/common/SelectCommon/SelectCommon.tsx +++ b/src/components/common/SelectCommon/SelectCommon.tsx @@ -1,24 +1,33 @@ -import s from './SelectCommon.module.scss' import classNames from 'classnames' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { IconVectorDown } from 'src/components/icons' +import s from './SelectCommon.module.scss' import SelectOption from './SelectOption/SelectOption' interface Props { placeholder? : string, + value?: string, size?: 'base' | 'large', type?: 'default' | 'custom', - option: {name: string, value: string}[], + options: {name: string, value: string}[], onChange?: (value: string) => void, } -const SelectCommon = ({ type = 'default', size = 'base', option, placeholder, onChange}: Props) => { - const [selectedName, setSelectedName] = useState(placeholder) - const [selectedValue, setSelectedValue] = useState('') +const SelectCommon = ({ value, type = 'default', size = 'base', options, placeholder, onChange}: Props) => { + const [selectedName, setSelectedName] = useState() + const [selectedValue, setSelectedValue] = useState('') - const changeSelectedName = (item:string, value: string) => { + useEffect(() => { + setSelectedValue(value || '') + + const name = options.find(item => item.value === value)?.name + setSelectedName(name) + }, [value, options]) + + const changeSelectedName = (value: string) => { setSelectedValue(value) - setSelectedName(item) + const name = options.find(item => item.value === value)?.name + setSelectedName(name) onChange && onChange(value) } return( @@ -33,7 +42,7 @@ const SelectCommon = ({ type = 'default', size = 'base', option, placeholder, on [s.selectTrigger] : true, })} - >{selectedName} + >{selectedName || placeholder}
    { - option.map(item => - + options.map(item => + ) }
    diff --git a/src/components/common/SelectCommon/SelectOption/SelectOption.tsx b/src/components/common/SelectCommon/SelectOption/SelectOption.tsx index 7e1968f9e..2968d6b33 100644 --- a/src/components/common/SelectCommon/SelectOption/SelectOption.tsx +++ b/src/components/common/SelectCommon/SelectOption/SelectOption.tsx @@ -2,16 +2,16 @@ import s from './SelectOption.module.scss' import classNames from 'classnames' interface Props{ - onClick: (name: string, value: string) => void, + onChange: (value: string) => void, itemName: string, size: 'base' | 'large', value: string, selected?: boolean, } -const SelectOption = ({onClick, itemName, size, value, selected} : Props) => { - const changeName = () => { - onClick(itemName, value) +const SelectOption = ({onChange, itemName, size, value, selected} : Props) => { + const handleChange = () => { + onChange(value) } return(
    { [s[size]] : !!size, [s.isChoose] : selected , })} - onClick = {changeName} + onClick = {handleChange} >{itemName}
    ) } diff --git a/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx b/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx index 06e6b2124..8289a3a93 100644 --- a/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx +++ b/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx @@ -43,7 +43,7 @@ const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoMod
    - +
    diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx index 233e9d057..04217d706 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx @@ -46,7 +46,7 @@ const ShippingInfoForm = ({onConfirm,id}: ShippingInfoFormProps) => { />
    - State + State
    { +const ProductListFilter = ({ facets, collections, products }: ProductListFilterProps) => { + return (
    -
    - - { - facets.map(item => ) - } -
    - +
    SPECIAL RECIPES
    -
    -
    - -
    -
    +
    diff --git a/src/components/modules/product-list/ProductListFilter/ProductSort/ProductSort.tsx b/src/components/modules/product-list/ProductListFilter/ProductSort/ProductSort.tsx new file mode 100644 index 000000000..a10cec443 --- /dev/null +++ b/src/components/modules/product-list/ProductListFilter/ProductSort/ProductSort.tsx @@ -0,0 +1,40 @@ +import { useRouter } from 'next/router'; +import React, { useEffect, useState } from 'react'; +import { SelectCommon } from 'src/components/common'; +import { OPTIONS_SORT_PRODUCT, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'; + +const ProductSort = () => { + const router = useRouter() + const [sortValue, setSortValue] = useState(); + + useEffect(() => { + const rs = router.query[QUERY_KEY.SORTBY] as string + if (rs) { + setSortValue(rs) + } + }, [router.query]) + + const onSortChange = (value: string) => { + setSortValue(value) + router.push({ + pathname: ROUTE.PRODUCTS, + query: { + ...router.query, + [QUERY_KEY.SORTBY]: value + } + }, + undefined, { shallow: true } + ) + } + + return ( + + ); +}; + +export default ProductSort; \ No newline at end of file diff --git a/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.module.scss b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.module.scss new file mode 100644 index 000000000..a27b395c3 --- /dev/null +++ b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.module.scss @@ -0,0 +1,12 @@ + +.productsMenuNavigationTablet { + @apply hidden; + @screen md { + @apply block; + padding-right: 2.4rem; + } + @screen xl { + @apply block; + width: 25%; + } +} diff --git a/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx new file mode 100644 index 000000000..47127d53e --- /dev/null +++ b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx @@ -0,0 +1,28 @@ +import { Collection, Facet } from '@framework/schema' +import React from 'react' +import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation' +import { QUERY_KEY } from 'src/utils/constanst.utils' +import s from './ProductsMenuNavigationTablet.module.scss' + +interface Props { + facets: Facet[] + collections: Collection[] + +} + +const ProductsMenuNavigationTablet = ({ facets, collections }: Props) => { + return ( +
    + + { + facets.map(item => ) + } +
    + ) +} + +export default ProductsMenuNavigationTablet diff --git a/src/components/modules/recipes-list/RecipesList/RecipesList.tsx b/src/components/modules/recipes-list/RecipesList/RecipesList.tsx index f636157fa..e91599a2c 100644 --- a/src/components/modules/recipes-list/RecipesList/RecipesList.tsx +++ b/src/components/modules/recipes-list/RecipesList/RecipesList.tsx @@ -189,13 +189,13 @@ const RecipesList = ({ data =recipe}:Props) => {
    - +
    - +
    diff --git a/src/styles/_base.scss b/src/styles/_base.scss index 1eab49e38..e41b6763f 100644 --- a/src/styles/_base.scss +++ b/src/styles/_base.scss @@ -4,7 +4,7 @@ :root { --primary: #5b9a74; --primary-light: #e3f2e9; - --primary-lightest: #effaf4; + --primary-lightest: #F1F8F4; --info-dark: #00317a; --info: #3468B7; diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index 432e9cc78..630054959 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -45,15 +45,24 @@ export const LOCAL_STORAGE_KEY = { TOKEN: 'token' } +export const QUERY_SPLIT_SEPERATOR = ',' export const QUERY_KEY = { TAB: 'tab', CATEGORY: 'category', BRAND: 'brand', - FEATURED: 'feature', + FEATURED: 'featured', SORTBY: 'sortby', RECIPES: 'recipes' } +export const PRODUCT_SORT_OPTION_VALUE = { + NAME_ASC: 'name_asc', + NAME_DESC: 'name_desc', + PRICE_ASC: 'price_asc', + PRICE_DESC: 'price_desc', + +} + export enum ProductFeature { BestSellers = 'Best Sellers', Sales = 'Sales', @@ -118,6 +127,26 @@ export const CODE_FACET_FEATURED_VARIANT = { FRESH: 'fresh', } +export const OPTIONS_SORT_PRODUCT = [ + { + name: 'By Name (A-Z)', + value: PRODUCT_SORT_OPTION_VALUE.NAME_ASC, + }, + { + name: 'By Name (Z-A)', + value: PRODUCT_SORT_OPTION_VALUE.NAME_DESC, + }, + { + name: 'Price (Low to High)', + value: PRODUCT_SORT_OPTION_VALUE.PRICE_ASC, + }, + { + name: 'Price (High to Low)', + value: PRODUCT_SORT_OPTION_VALUE.PRICE_DESC, + }, +]; + + export const FEATURED = [ { name: 'Best Sellers', From a91417eca9614d3c9525f188f8f7e468086a05b7 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Wed, 6 Oct 2021 17:44:36 +0700 Subject: [PATCH 09/20] :sparkles: feat: filter product :%s --- .../api/operations/get-all-products.ts | 5 +- framework/vendure/schema.d.ts | 3 +- .../utils/queries/get-all-products-query.ts | 1 + pages/products.tsx | 13 +-- .../EmptyCommon/EmptyCommon.module.scss | 7 +- .../common/MenuNavigation/MenuNavigation.tsx | 4 +- .../MenuNavigationItem/MenuNavigationItem.tsx | 39 +++++---- .../ProductList/ProductList.module.scss | 19 +++-- .../common/ProductList/ProductList.tsx | 44 +++++++--- src/components/hooks/product/index.ts | 3 + .../hooks/product/useSearchProducts.tsx | 14 ++++ .../ProductListFilter.module.scss | 12 +-- .../ProductListFilter/ProductListFilter.tsx | 82 +++++++++++++++++-- .../ProductsMenuNavigationTablet.tsx | 6 +- src/utils/constanst.utils.ts | 3 +- src/utils/funtion.utils.ts | 25 +++++- 16 files changed, 222 insertions(+), 58 deletions(-) create mode 100644 src/components/hooks/product/index.ts create mode 100644 src/components/hooks/product/useSearchProducts.tsx diff --git a/framework/vendure/api/operations/get-all-products.ts b/framework/vendure/api/operations/get-all-products.ts index 1f558a7cb..5fb458f67 100644 --- a/framework/vendure/api/operations/get-all-products.ts +++ b/framework/vendure/api/operations/get-all-products.ts @@ -14,7 +14,7 @@ export default function getAllProductsOperation({ variables?: ProductVariables config?: Partial preview?: boolean - }): Promise<{ products: Product[] }> + }): Promise<{ products: Product[], totalItems: number }> async function getAllProducts({ query = getAllProductsQuery, @@ -25,7 +25,7 @@ export default function getAllProductsOperation({ variables?: ProductVariables config?: Partial preview?: boolean - } = {}): Promise<{ products: Product[] | any[] }> { + } = {}): Promise<{ products: Product[] | any[], totalItems: number }> { const config = commerce.getConfig(cfg) const variables = { input: { @@ -40,6 +40,7 @@ export default function getAllProductsOperation({ return { products: data.search.items.map((item) => normalizeSearchResult(item)), + totalItems: data.search.totalItems as number, } } diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 5faa87b43..8f49e2d2d 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3219,7 +3219,8 @@ export type GetAllProductsQueryVariables = Exact<{ export type GetAllProductsQuery = { __typename?: 'Query' } & { search: { __typename?: 'SearchResponse' } & { - items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment> + items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment>, + 'totalItems' } } diff --git a/framework/vendure/utils/queries/get-all-products-query.ts b/framework/vendure/utils/queries/get-all-products-query.ts index 1b44b2017..007c6594d 100644 --- a/framework/vendure/utils/queries/get-all-products-query.ts +++ b/framework/vendure/utils/queries/get-all-products-query.ts @@ -3,6 +3,7 @@ import { searchResultFragment } from '../fragments/search-result-fragment' export const getAllProductsQuery = /* GraphQL */ ` query getAllProducts($input: SearchInput!) { search(input: $input) { + totalItems items { ...SearchResult } diff --git a/pages/products.tsx b/pages/products.tsx index 93dda5cdb..9559538df 100644 --- a/pages/products.tsx +++ b/pages/products.tsx @@ -13,16 +13,19 @@ import ProductListBanner from '../src/components/modules/product-list/ProductLis interface Props { facets: Facet[], collections: Collection[], - products: ProductCard[], + productsResult: { products: ProductCard[], totalItems: number }, } -export default function Products({ facets, collections, products }: Props) { - // console.log("facets: ", products) +export default function Products({ facets, collections, productsResult }: Props) { return ( <> - + ) @@ -70,7 +73,7 @@ export async function getStaticProps({ config, preview, }) - promisesWithKey.push({ key: 'products', promise: productsPromise, keyResult: 'products' }) + promisesWithKey.push({ key: 'productsResult', promise: productsPromise }) try { diff --git a/src/components/common/EmptyCommon/EmptyCommon.module.scss b/src/components/common/EmptyCommon/EmptyCommon.module.scss index a31ba4374..4014faeea 100644 --- a/src/components/common/EmptyCommon/EmptyCommon.module.scss +++ b/src/components/common/EmptyCommon/EmptyCommon.module.scss @@ -4,13 +4,16 @@ padding: 1.6rem; margin: auto; .imgWrap { - min-width: 10rem; + min-height: 10rem; text-align: center; + img { + min-height: 10rem; + } } .description { color: var(--disabled); text-align: center; - margin-top: .8rem; + margin-top: 0.8rem; } } diff --git a/src/components/common/MenuNavigation/MenuNavigation.tsx b/src/components/common/MenuNavigation/MenuNavigation.tsx index 669eacf59..a7cc9a3d7 100644 --- a/src/components/common/MenuNavigation/MenuNavigation.tsx +++ b/src/components/common/MenuNavigation/MenuNavigation.tsx @@ -6,9 +6,10 @@ interface Props { heading: string, queryKey: string, categories: { name: string, slug?: string, code?: string }[] + isSingleSelect?: boolean } -const MenuNavigation = ({ heading, queryKey, categories }: Props) => { +const MenuNavigation = ({ heading, queryKey, categories, isSingleSelect }: Props) => { return (

    {heading}({categories.length})

    @@ -19,6 +20,7 @@ const MenuNavigation = ({ heading, queryKey, categories }: Props) => { name={item.name} value={item.slug || item.code || ''} queryKey={queryKey} + isSingleSelect={isSingleSelect} />) } diff --git a/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx b/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx index c7aeba724..e1abee867 100644 --- a/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx +++ b/src/components/common/MenuNavigation/MenuNavigationItem/MenuNavigationItem.tsx @@ -1,16 +1,18 @@ import classNames from 'classnames' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' -import { QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils' +import { QUERY_KEY, QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils' import s from './MenuNavigationItem.module.scss' interface Props { name: string value: string - queryKey: string, + queryKey: string + isSingleSelect?: boolean + } -const MenuNavigationItem = ({ name, value, queryKey }: Props) => { +const MenuNavigationItem = ({ name, value, queryKey, isSingleSelect }: Props) => { const router = useRouter() const [isActive, setIsActive] = useState() @@ -26,21 +28,30 @@ const MenuNavigationItem = ({ name, value, queryKey }: Props) => { const handleClick = () => { const queryString = router.query[queryKey] as string || '' const prevQuery = queryString.split(QUERY_SPLIT_SEPERATOR) - - let newQuery = [] as string[] - if (isActive) { - newQuery = prevQuery.filter(item => item !== value) + + let newQuery = '' + if (isSingleSelect) { + newQuery = isActive ? '' : value } else { - newQuery = [...prevQuery, value] + if (isActive) { + newQuery = prevQuery.filter(item => item !== value).join(QUERY_SPLIT_SEPERATOR) + } else { + newQuery = [...prevQuery, value].join(QUERY_SPLIT_SEPERATOR) + } + } + + const query = { + ...router.query, + [queryKey]: newQuery + } + + if (queryKey === QUERY_KEY.CATEGORY) { + query[QUERY_KEY.PAGE] = "0" } - // setIsActive(!isActive) router.push({ pathname: ROUTE.PRODUCTS, - query: { - ...router.query, - [queryKey]: newQuery.join(QUERY_SPLIT_SEPERATOR) - } + query }, undefined, { shallow: true } ) @@ -50,8 +61,6 @@ const MenuNavigationItem = ({ name, value, queryKey }: Props) => { onClick={handleClick}> {name} ) - - } export default MenuNavigationItem diff --git a/src/components/common/ProductList/ProductList.module.scss b/src/components/common/ProductList/ProductList.module.scss index e58b37f86..b032f409d 100644 --- a/src/components/common/ProductList/ProductList.module.scss +++ b/src/components/common/ProductList/ProductList.module.scss @@ -1,10 +1,19 @@ -.wrapper{ +.wrapper { margin-top: 4rem; - .list{ + .list { @apply flex flex-wrap justify-around; + .empty { + button { + margin-top: 1.6rem; + width: 100%; + } + } } - .pagination{ + .pagination { padding-top: 4.8rem; - @apply flex justify-center items-center ; + @apply flex justify-center items-center; + &.hide { + @apply hidden; + } } -} \ No newline at end of file +} diff --git a/src/components/common/ProductList/ProductList.tsx b/src/components/common/ProductList/ProductList.tsx index 0c5deb91e..c901b4d46 100644 --- a/src/components/common/ProductList/ProductList.tsx +++ b/src/components/common/ProductList/ProductList.tsx @@ -1,28 +1,50 @@ -import React, { useState } from 'react' -import { DEFAULT_PAGE_SIZE } from 'src/utils/constanst.utils' +import classNames from 'classnames' +import { useRouter } from 'next/router' +import React from 'react' +import { DEFAULT_PAGE_SIZE, ROUTE } from 'src/utils/constanst.utils' +import { ButtonCommon, EmptyCommon } from '..' import PaginationCommon from '../PaginationCommon/PaginationCommon' import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard' import s from "./ProductList.module.scss" + interface ProductListProps { - data: ProductCardProps[] + data: ProductCardProps[], + total?: number, + defaultCurrentPage?: number + onPageChange?: (page: number) => void } -const ProductList = ({data}: ProductListProps) => { - const [currentPage, setCurrentPage] = useState(0) - const onPageChange = (page:number) => { - setCurrentPage(page) +const ProductList = ({ data, total = data.length, defaultCurrentPage, onPageChange }: ProductListProps) => { + const router = useRouter() + const handlePageChange = (page: number) => { + onPageChange && onPageChange(page) } + + const handleShowAllProduct = () => { + router.push({ + pathname: ROUTE.PRODUCTS, + }, + undefined, { shallow: true } + ) + } + return (
    { - data.slice(currentPage*DEFAULT_PAGE_SIZE,(currentPage+1)* DEFAULT_PAGE_SIZE).map((product,index)=>{ - return + data.map((product, index) => { + return }) } + { + data.length === 0 &&
    + + Show all products +
    + }
    -
    - +
    +
    ) diff --git a/src/components/hooks/product/index.ts b/src/components/hooks/product/index.ts new file mode 100644 index 000000000..ea2afe03a --- /dev/null +++ b/src/components/hooks/product/index.ts @@ -0,0 +1,3 @@ +export { default as useSearchProducts } from './useSearchProducts' + + diff --git a/src/components/hooks/product/useSearchProducts.tsx b/src/components/hooks/product/useSearchProducts.tsx new file mode 100644 index 000000000..e9d2ca127 --- /dev/null +++ b/src/components/hooks/product/useSearchProducts.tsx @@ -0,0 +1,14 @@ +import { GetAllProductsQuery, QuerySearchArgs } from '@framework/schema' +import { normalizeSearchResult } from '@framework/utils/normalize' +import { getAllProductsQuery } from '@framework/utils/queries/get-all-products-query' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + +const useSearchProducts = (options?: QuerySearchArgs) => { + const { data, isValidating, ...rest } = useSWR([getAllProductsQuery, options], gglFetcher) + console.log("on search ", data?.search.totalItems, options, data?.search.items) + + return { products: data?.search.items.map((item) => normalizeSearchResult(item)), totalItems: data?.search.totalItems, loading: isValidating, ...rest } +} + +export default useSearchProducts diff --git a/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss b/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss index fea3af574..84951b540 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss +++ b/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss @@ -14,12 +14,12 @@ @apply flex; } .list{ - @screen md { - @apply flex justify-between flex-wrap w-full; - margin: 1rem 0; - } - @screen xl { - width:75%; + @apply w-full; + .top { + @screen md { + @apply flex justify-between flex-wrap w-full; + margin: 1rem 0; + } } .inner{ @screen md { diff --git a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx index 827ff966c..e35df74e1 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx +++ b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx @@ -1,8 +1,13 @@ import { ProductCard } from '@commerce/types/product' -import { Collection, Facet } from '@framework/schema' -import React from 'react' +import { Collection, Facet, FacetValue, QuerySearchArgs } from '@framework/schema' +import { useRouter } from 'next/router' +import React, { useEffect, useState } from 'react' import { HeadingCommon, ProductList } from 'src/components/common' import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon' +import SkeletonImage from 'src/components/common/SkeletonCommon/SkeletonImage/SkeletonImage' +import { useSearchProducts } from 'src/components/hooks/product' +import { DEFAULT_PAGE_SIZE, QUERY_KEY, QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils' +import { getFacetIdsFromCodes, getPageFromQuery } from 'src/utils/funtion.utils' import s from './ProductListFilter.module.scss' import ProductsMenuNavigationTablet from './ProductsMenuNavigationTablet/ProductsMenuNavigationTablet' import ProductSort from './ProductSort/ProductSort' @@ -11,6 +16,7 @@ interface ProductListFilterProps { facets: Facet[] collections: Collection[] products: ProductCard[] + total: number } @@ -21,7 +27,64 @@ const BREADCRUMB = [ }, ] -const ProductListFilter = ({ facets, collections, products }: ProductListFilterProps) => { + +const DEFAULT_SEARCH_ARGS = { + groupByProduct: true, take: DEFAULT_PAGE_SIZE +} + +const ProductListFilter = ({ facets, collections, products, total }: ProductListFilterProps) => { + const router = useRouter() + const [initialQueryFlag, setInitialQueryFlag] = useState(true) + const [optionQueryProduct, setOptionQueryProduct] = useState({ input: DEFAULT_SEARCH_ARGS }) + const { products: productSearchResult, totalItems, loading } = useSearchProducts(optionQueryProduct) + const [currentPage, setCurrentPage] = useState(0) + + useEffect(() => { + const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string) + setCurrentPage(page) + }, [router.query]) + + const onPageChange = (page: number) => { + setCurrentPage(page) + + router.push({ + pathname: ROUTE.PRODUCTS, + query: { + ...router.query, + [QUERY_KEY.PAGE]: page + } + }, + undefined, { shallow: true } + ) + } + + useEffect(() => { + const query = { input: { ...DEFAULT_SEARCH_ARGS } } as QuerySearchArgs + + const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string) + query.input.skip = page * DEFAULT_PAGE_SIZE + + // collections + const categoryQuery = router.query[QUERY_KEY.CATEGORY] as string + if (categoryQuery) { + query.input.collectionSlug = categoryQuery + } + + // facets + const facetsQuery = [router.query[QUERY_KEY.FEATURED] as string, router.query[QUERY_KEY.BRAND] as string].join(QUERY_SPLIT_SEPERATOR) + if (facetsQuery) { + const facetsValue = [] as FacetValue[] + facets.map((item: Facet) => { + facetsValue.push(...item.values) + return null + }) + + query.input.facetValueIds = getFacetIdsFromCodes(facetsValue, facetsQuery.split(QUERY_SPLIT_SEPERATOR)) + } + + setOptionQueryProduct(query) + setInitialQueryFlag(false) + }, [router.query, facets]) return (
    @@ -31,12 +94,17 @@ const ProductListFilter = ({ facets, collections, products }: ProductListFilterP
    - SPECIAL RECIPES +
    + SPECIAL RECIPES -
    - +
    + +
    - + { + (!initialQueryFlag && loading && !productSearchResult) && + } +
    diff --git a/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx index 47127d53e..d31609639 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx +++ b/src/components/modules/product-list/ProductListFilter/ProductsMenuNavigationTablet/ProductsMenuNavigationTablet.tsx @@ -13,7 +13,11 @@ interface Props { const ProductsMenuNavigationTablet = ({ facets, collections }: Props) => { return (
    - + { facets.map(item => (arr: Array, value: T): Array { const index = arr.indexOf(value); if (index > -1) { @@ -58,6 +71,16 @@ export function getFacetNamesFromIds(facets: FacetValue[], ids?: string[]): stri return names.join(", ") } -export function getAllPromies (promies: PromiseWithKey[]) { +export function getFacetIdsFromCodes(facets: FacetValue[], codes?: string[]): string[] { + if (!codes || codes?.length === 0) { + return [] + } + + const facetItems = facets.filter((item: FacetValue) => codes.includes(item.code)) + const ids = facetItems.map((item: FacetValue) => item.id) + return ids +} + +export function getAllPromies(promies: PromiseWithKey[]) { return promies.map(item => item.promise) } From fb375ed4e8ac1c66460c68f58b7bd9962a70d404 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Wed, 6 Oct 2021 17:49:41 +0700 Subject: [PATCH 10/20] :sparkles: feat: sort products list :%s --- .../ProductListFilter/ProductListFilter.tsx | 8 +++- src/utils/funtion.utils.ts | 41 +++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx index e35df74e1..17612866c 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx +++ b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx @@ -7,7 +7,7 @@ import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbC import SkeletonImage from 'src/components/common/SkeletonCommon/SkeletonImage/SkeletonImage' import { useSearchProducts } from 'src/components/hooks/product' import { DEFAULT_PAGE_SIZE, QUERY_KEY, QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils' -import { getFacetIdsFromCodes, getPageFromQuery } from 'src/utils/funtion.utils' +import { getFacetIdsFromCodes, getPageFromQuery, getProductSortParamFromQuery } from 'src/utils/funtion.utils' import s from './ProductListFilter.module.scss' import ProductsMenuNavigationTablet from './ProductsMenuNavigationTablet/ProductsMenuNavigationTablet' import ProductSort from './ProductSort/ProductSort' @@ -64,6 +64,12 @@ const ProductListFilter = ({ facets, collections, products, total }: ProductList const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string) query.input.skip = page * DEFAULT_PAGE_SIZE + + const sortQuery = router.query[QUERY_KEY.SORTBY] as string + if (sortQuery) { + query.input.sort = getProductSortParamFromQuery(sortQuery) + } + // collections const categoryQuery = router.query[QUERY_KEY.CATEGORY] as string if (categoryQuery) { diff --git a/src/utils/funtion.utils.ts b/src/utils/funtion.utils.ts index ee7f85c9d..9f712326d 100644 --- a/src/utils/funtion.utils.ts +++ b/src/utils/funtion.utils.ts @@ -1,7 +1,7 @@ import { Facet } from "@commerce/types/facet"; -import { FacetValue } from './../../framework/vendure/schema.d'; -import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT } from "./constanst.utils"; -import { PromiseWithKey } from "./types.utils"; +import { 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"; export function isMobile() { return window.innerWidth < 768 @@ -20,6 +20,41 @@ export function getPageFromQuery(pageQuery: string) { return page } + +export function getProductSortParamFromQuery(query: string) { + let rs = {} as SearchResultSortParameter + switch (query) { + case PRODUCT_SORT_OPTION_VALUE.NAME_ASC: + rs = { + name: SortOrder.Asc + } + break; + + case PRODUCT_SORT_OPTION_VALUE.NAME_DESC: + rs = { + name: SortOrder.Desc + } + break; + + case PRODUCT_SORT_OPTION_VALUE.PRICE_ASC: + rs = { + price: SortOrder.Asc + } + break; + + case PRODUCT_SORT_OPTION_VALUE.PRICE_DESC: + rs = { + price: SortOrder.Desc + } + break; + + default: + break; + } + + return rs +} + export function removeItem(arr: Array, value: T): Array { const index = arr.indexOf(value); if (index > -1) { From 721ae9dedc4ccc4a4fd7c4677e89aa6bfe4cb932 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Wed, 6 Oct 2021 18:07:11 +0700 Subject: [PATCH 11/20] :sparkles: feat: filter products from menu filter (in header) :%s --- .../common/MenuFilter/MenuFilter.tsx | 5 +- .../MenuFilterItem/MenuFilterItem.tsx | 8 ++- .../MenuNavigationProductList.tsx | 64 +++++++++++++------ .../hooks/product/useSearchProducts.tsx | 1 - 4 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/components/common/MenuFilter/MenuFilter.tsx b/src/components/common/MenuFilter/MenuFilter.tsx index b23816aff..72f5a8302 100644 --- a/src/components/common/MenuFilter/MenuFilter.tsx +++ b/src/components/common/MenuFilter/MenuFilter.tsx @@ -7,9 +7,11 @@ interface Props { categories: { name: string, slug?: string, code?: string }[], type: string, onChange: (value: string, type: string, isSellect?: boolean) => void + isSingleSelect?: boolean + singleSelectedValue?: string } -const MenuFilter = ({ heading, categories, type, onChange }: Props) => { +const MenuFilter = ({ heading, categories, type, onChange, singleSelectedValue, isSingleSelect }: Props) => { function handleChange(value: string, isSellect: boolean) { onChange(value, type, isSellect) } @@ -25,6 +27,7 @@ const MenuFilter = ({ heading, categories, type, onChange }: Props) => { type={type} value={item.slug || item.code || ''} onChange={handleChange} + isActive={isSingleSelect && (item.slug || item.code) === singleSelectedValue} />) } diff --git a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx index e247bfe93..eeb96fae1 100644 --- a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx +++ b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx @@ -8,12 +8,18 @@ interface Props { name: string, value: string, type: string, + isActive?: boolean onChange: (value: string, isSellect: boolean) => void } -const MenuFilterItem = ({ name, value, type, onChange }: Props) => { +const MenuFilterItem = ({ name, value, type, isActive, onChange }: Props) => { const router = useRouter() const [isSelected, setIsSelected] = useState() + + useEffect(() => { + setIsSelected(isActive) + }, [isActive]) + useEffect(() => { const rs = (router.query[type] || []).includes(value) setIsSelected(rs) diff --git a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx index af3d4c9f9..122e8e3f8 100644 --- a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx +++ b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx @@ -38,7 +38,7 @@ const MenuNavigationProductList = ({ visible, onClose }: Props) => { const { collections, loading: collectionLoading } = useGetAllCollection() const [brandQuery, setBrandQuery] = useState([]) const [featuredQuery, setFeaturedQuery] = useState([]) - const [categoryQuery, setCategoryQuery] = useState([]) + const [categoryQuery, setCategoryQuery] = useState() const [sortValue, setSortValue] = useState(); useEffect(() => { @@ -48,11 +48,18 @@ const MenuNavigationProductList = ({ visible, onClose }: Props) => { } }, [router.query]) + useEffect(() => { + const rs = router.query[QUERY_KEY.CATEGORY] as string + if (rs) { + setCategoryQuery(rs) + } + }, [router.query]) + function onSubmit() { let newURL = `${ROUTE.PRODUCTS}?` - if (categoryQuery.length > 0) { - newURL += `&${QUERY_KEY.CATEGORY}=${categoryQuery.join(",")}` + if (categoryQuery) { + newURL += `&${QUERY_KEY.CATEGORY}=${categoryQuery}` } if (brandQuery.length > 0) { @@ -74,24 +81,33 @@ const MenuNavigationProductList = ({ visible, onClose }: Props) => { setSortValue(value) } - const onFilterOptionChange = (value: string, type: string, isSelect: boolean = true) => { - let rs = [...categoryQuery] - let setDataFunction = setCategoryQuery - - if (type === CODE_FACET_BRAND) { - rs = [...brandQuery] - setDataFunction = setBrandQuery - } else if (type === CODE_FACET_FEATURED) { - rs = [...featuredQuery] - setDataFunction = setFeaturedQuery - } - + const onCategoryChange = (value: string, isSelect: boolean) => { if (isSelect) { - rs.push(value) + setCategoryQuery(value) } else { - rs = rs.filter(item => item !== value) + setCategoryQuery('') + } + } + + const onFilterOptionChange = (value: string, type: string, isSelect: boolean = true) => { + if (type === QUERY_KEY.CATEGORY) { + onCategoryChange(value, isSelect) + } else { + let rs = [...featuredQuery] + let setDataFunction = setFeaturedQuery + + if (type === CODE_FACET_BRAND) { + rs = [...brandQuery] + setDataFunction = setBrandQuery + } + + if (isSelect) { + rs.push(value) + } else { + rs = rs.filter(item => item !== value) + } + setDataFunction(rs) } - setDataFunction(rs) } @@ -103,8 +119,16 @@ const MenuNavigationProductList = ({ visible, onClose }: Props) => {

    FILTER

    + {collectionLoading && } - + + {facetsLoading && <> @@ -118,7 +142,7 @@ const MenuNavigationProductList = ({ visible, onClose }: Props) => { onChange={onFilterOptionChange} />) } - +
    {LANGUAGE.BUTTON_LABEL.CONFIRM}
    diff --git a/src/components/hooks/product/useSearchProducts.tsx b/src/components/hooks/product/useSearchProducts.tsx index e9d2ca127..78acb7cf3 100644 --- a/src/components/hooks/product/useSearchProducts.tsx +++ b/src/components/hooks/product/useSearchProducts.tsx @@ -6,7 +6,6 @@ import useSWR from 'swr' const useSearchProducts = (options?: QuerySearchArgs) => { const { data, isValidating, ...rest } = useSWR([getAllProductsQuery, options], gglFetcher) - console.log("on search ", data?.search.totalItems, options, data?.search.items) return { products: data?.search.items.map((item) => normalizeSearchResult(item)), totalItems: data?.search.totalItems, loading: isValidating, ...rest } } From 97e3d10bfd9c626e7b391f6483fb8270ac12021f Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Wed, 6 Oct 2021 18:16:25 +0700 Subject: [PATCH 12/20] :art: styles: fix dot in icon filter from header :%s --- .../Header/components/HeaderMenu/HeaderMenu.module.scss | 4 ---- .../common/Header/components/HeaderMenu/HeaderMenu.tsx | 9 +-------- .../HeaderSubMenuMobile/HeaderSubMenuMobile.tsx | 1 + 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/components/common/Header/components/HeaderMenu/HeaderMenu.module.scss b/src/components/common/Header/components/HeaderMenu/HeaderMenu.module.scss index 6a16505bc..7baff45a6 100644 --- a/src/components/common/Header/components/HeaderMenu/HeaderMenu.module.scss +++ b/src/components/common/Header/components/HeaderMenu/HeaderMenu.module.scss @@ -50,10 +50,6 @@ width: 1.2rem; height: 1.2rem; border-radius: 1.2rem; - @apply hidden; - &.isShow { - @apply block; - } } @screen md { display: none; diff --git a/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx b/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx index 4f7e5d21a..803e0b66b 100644 --- a/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx +++ b/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx @@ -27,7 +27,6 @@ interface Props { children?: any isFull?: boolean isStickyHeader?: boolean - visibleFilter?: boolean openModalLogin: () => void openModalRegister: () => void openModalInfo: () => void @@ -38,7 +37,6 @@ const HeaderMenu = memo( ({ isFull, isStickyHeader, - visibleFilter, openModalLogin, openModalRegister, openModalInfo, @@ -105,12 +103,7 @@ const HeaderMenu = memo( {FILTER_PAGE.includes(router.pathname) && ( )}
    From ee06aff83a92b14508c471a853286af538f9fae2 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Thu, 7 Oct 2021 10:27:07 +0700 Subject: [PATCH 16/20] :sparkles: feat: product filter provider :%s --- src/components/common/Header/Header.tsx | 6 ++- src/components/common/Layout/Layout.tsx | 10 +++-- .../Layout/LayoutContent/LayoutContent.tsx | 13 +----- .../MenuNavigationProductList.tsx | 7 +-- src/components/contexts/FilterContext.tsx | 43 ------------------ .../ProductFilter/ProductFilterContext.tsx | 20 +++++++++ .../ProductFilter/ProductFilterProvider.tsx | 30 +++++++++++++ src/components/contexts/index.ts | 3 ++ .../ProductListFilter.module.scss | 45 ++++++++++--------- .../ProductListFilter/ProductListFilter.tsx | 14 ++++-- 10 files changed, 105 insertions(+), 86 deletions(-) delete mode 100644 src/components/contexts/FilterContext.tsx create mode 100644 src/components/contexts/ProductFilter/ProductFilterContext.tsx create mode 100644 src/components/contexts/ProductFilter/ProductFilterProvider.tsx diff --git a/src/components/common/Header/Header.tsx b/src/components/common/Header/Header.tsx index 4bed36757..092bcc8e7 100644 --- a/src/components/common/Header/Header.tsx +++ b/src/components/common/Header/Header.tsx @@ -1,5 +1,6 @@ import classNames from 'classnames' import React, { memo, useEffect, useRef, useState } from 'react' +import { useProductFilter } from 'src/components/contexts' import { useModalCommon } from 'src/components/hooks' import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate' import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo' @@ -9,11 +10,12 @@ import HeaderSubMenu from './components/HeaderSubMenu/HeaderSubMenu' import HeaderSubMenuMobile from './components/HeaderSubMenuMobile/HeaderSubMenuMobile' import s from './Header.module.scss' interface props { - toggleFilter: () => void, + } -const Header = memo(({ toggleFilter }: props) => { +const Header = memo(({ }: props) => { const headeFullRef = useRef(null) + const { toggleProductFilter: toggleFilter } = useProductFilter() const [isFullHeader, setIsFullHeader] = useState(true) const [isModeAuthenRegister, setIsModeAuthenRegister] = useState(false) const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false }) diff --git a/src/components/common/Layout/Layout.tsx b/src/components/common/Layout/Layout.tsx index ec5a1646d..82853117b 100644 --- a/src/components/common/Layout/Layout.tsx +++ b/src/components/common/Layout/Layout.tsx @@ -1,7 +1,7 @@ import { CommerceProvider } from '@framework' import { useRouter } from 'next/router' import { FC } from 'react' -import { CartDrawerProvider, MessageProvider } from 'src/components/contexts' +import { CartDrawerProvider, MessageProvider, ProductFilterProvider } from 'src/components/contexts' import LayoutContent from './LayoutContent/LayoutContent' interface Props { className?: string @@ -13,9 +13,11 @@ const Layout: FC = ({ children }) => { return ( - - {children} - + + + {children} + + ) diff --git a/src/components/common/Layout/LayoutContent/LayoutContent.tsx b/src/components/common/Layout/LayoutContent/LayoutContent.tsx index 58ff83c95..7bfddafe7 100644 --- a/src/components/common/Layout/LayoutContent/LayoutContent.tsx +++ b/src/components/common/Layout/LayoutContent/LayoutContent.tsx @@ -15,21 +15,12 @@ interface Props { const LayoutContent: FC = ({ children }) => { const router = useRouter() - const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false }) const {messages, removeMessage} = useMessage() - const toggleFilter = () => { - if (visibleFilter) { - closeFilter() - } else { - openFilter() - } - } - return ( <>
    -
    +
    { router.pathname === ROUTE.ACCOUNT ?
    @@ -39,7 +30,7 @@ const LayoutContent: FC = ({ children }) => { } { - FILTER_PAGE.includes(router.pathname) && (
    ) + FILTER_PAGE.includes(router.pathname) && (
    ) }
    diff --git a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx index 122e8e3f8..546fbf365 100644 --- a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx +++ b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx @@ -3,6 +3,7 @@ import classNames from 'classnames'; import { useRouter } from 'next/router'; import React, { useEffect, useState } from 'react'; import { ButtonCommon } from 'src/components/common'; +import { useProductFilter } from 'src/components/contexts'; import { useGetAllCollection } from 'src/components/hooks/collection'; import { useFacets } from 'src/components/hooks/facets'; import IconHide from 'src/components/icons/IconHide'; @@ -15,8 +16,7 @@ import s from './MenuNavigationProductList.module.scss'; import MenuSort from './MenuSort/MenuSort'; interface Props { - visible: boolean, - onClose: () => void + } const FACET_QUERY = { @@ -32,8 +32,9 @@ const FACET_QUERY = { } } as QueryFacetsArgs -const MenuNavigationProductList = ({ visible, onClose }: Props) => { +const MenuNavigationProductList = ({}: Props) => { const router = useRouter() + const { productFilterVisible: visible, closeProductFilter: onClose } = useProductFilter() const { facets, loading: facetsLoading } = useFacets(FACET_QUERY) const { collections, loading: collectionLoading } = useGetAllCollection() const [brandQuery, setBrandQuery] = useState([]) diff --git a/src/components/contexts/FilterContext.tsx b/src/components/contexts/FilterContext.tsx deleted file mode 100644 index 36a10ce9f..000000000 --- a/src/components/contexts/FilterContext.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { createContext, ReactNode, useContext, useState } from "react"; -import { filterContextType } from "src/utils/types.utils"; - -const contextDefaultValues: filterContextType = { - visible: false, - open: () => {}, - close: () => {}, -}; - -const FilterContext = createContext(contextDefaultValues); - -export function useAuth() { - return useContext(FilterContext); -} - -type FilterProviderProps = { - children: ReactNode; -}; - -export function FilterProvider({ children }: FilterProviderProps) { - const [visible, setVisible] = useState(false); - - const open = () => { - setVisible(true); - }; - - const close = () => { - setVisible(false); - }; - - const value = { - visible, - open, - close, - }; - return ( - <> - - {children} - - - ); -} \ No newline at end of file diff --git a/src/components/contexts/ProductFilter/ProductFilterContext.tsx b/src/components/contexts/ProductFilter/ProductFilterContext.tsx new file mode 100644 index 000000000..10417f528 --- /dev/null +++ b/src/components/contexts/ProductFilter/ProductFilterContext.tsx @@ -0,0 +1,20 @@ +import { createContext, useContext } from 'react'; + +export type ProductFilterContextType = { + productFilterVisible: boolean; + toggleProductFilter: (visible?: boolean) => void; + openProductFilter: () => void; + closeProductFilter: () => void; +}; +const DEFAULT_VALUE: ProductFilterContextType = { + productFilterVisible: false, + toggleProductFilter: () => { }, + openProductFilter: () => { }, + closeProductFilter: () => { }, +}; + +export const ProductFilterContext = createContext(DEFAULT_VALUE) + +export function useProductFilter() { + return useContext(ProductFilterContext); +} diff --git a/src/components/contexts/ProductFilter/ProductFilterProvider.tsx b/src/components/contexts/ProductFilter/ProductFilterProvider.tsx new file mode 100644 index 000000000..068b9daab --- /dev/null +++ b/src/components/contexts/ProductFilter/ProductFilterProvider.tsx @@ -0,0 +1,30 @@ +import { ReactNode, useState } from "react"; +import { ProductFilterContext } from "./ProductFilterContext"; + +type Props = { + children: ReactNode; +}; + +export function ProductFilterProvider({ children }: Props) { + const [visible, setVisible] = useState(false); + + const closeProductFilter = () => { + setVisible(false); + }; + + const openProductFilter = () => { + setVisible(true); + }; + + const toggleProductFilter = () => { + setVisible(!visible); + }; + + return ( + <> + + {children} + + + ); +} \ No newline at end of file diff --git a/src/components/contexts/index.ts b/src/components/contexts/index.ts index 795c9161c..38e0c0b49 100644 --- a/src/components/contexts/index.ts +++ b/src/components/contexts/index.ts @@ -3,3 +3,6 @@ export * from './CartDrawer/CartDrawerProvider' export * from './Message/MessageContext' export * from './Message/MessageProvider' + +export * from './ProductFilter/ProductFilterContext' +export * from './ProductFilter/ProductFilterProvider' diff --git a/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss b/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss index 84951b540..d3223831a 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss +++ b/src/components/modules/product-list/ProductListFilter/ProductListFilter.module.scss @@ -16,29 +16,34 @@ .list{ @apply w-full; .top { + .left { + @apply flex justify-between items-center; + } + + .iconFilter { + @apply relative; + &:focus { + outline: none; + filter: brightness(1.05); + } + &:focus-visible { + outline: 2px solid var(--text-active); + } + .dot { + @apply absolute; + top: -0.08rem; + right: -0.2rem; + background-color: var(--negative); + width: 1.2rem; + height: 1.2rem; + border-radius: 1.2rem; + } + } @screen md { @apply flex justify-between flex-wrap w-full; margin: 1rem 0; - } - } - .inner{ - @screen md { - @apply flex flex-col items-center justify-center; - } - .boxItem { - @screen md { - @apply flex justify-between flex-wrap; - margin: 1rem 0; - } - .item { - @screen md { - width: calc(97% / 2); - margin-top:1rem; - } - @screen lg{ - width: calc(97% / 3); - margin-top:1rem; - } + .iconFilter { + @apply hidden; } } } diff --git a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx index f99e50c0d..558b9197a 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx +++ b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx @@ -4,8 +4,9 @@ import { useRouter } from 'next/router' import React, { useEffect, useState } from 'react' import { HeadingCommon, ListProductCardSkeleton, ProductList } from 'src/components/common' import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon' -import SkeletonImage from 'src/components/common/SkeletonCommon/SkeletonImage/SkeletonImage' +import { useProductFilter } from 'src/components/contexts' import { useSearchProducts } from 'src/components/hooks/product' +import { IconFilter } from 'src/components/icons' import { DEFAULT_PAGE_SIZE, QUERY_KEY, QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils' import { getFacetIdsFromCodes, getPageFromQuery, getProductSortParamFromQuery } from 'src/utils/funtion.utils' import s from './ProductListFilter.module.scss' @@ -34,6 +35,7 @@ const DEFAULT_SEARCH_ARGS = { const ProductListFilter = ({ facets, collections, products, total }: ProductListFilterProps) => { const router = useRouter() + const { openProductFilter } = useProductFilter() const [initialQueryFlag, setInitialQueryFlag] = useState(true) const [optionQueryProduct, setOptionQueryProduct] = useState({ input: DEFAULT_SEARCH_ARGS }) const { products: productSearchResult, totalItems, loading } = useSearchProducts(optionQueryProduct) @@ -101,14 +103,20 @@ const ProductListFilter = ({ facets, collections, products, total }: ProductList
    - SPECIAL RECIPES +
    + SPECIAL RECIPES + +
    { - (!initialQueryFlag && loading && !productSearchResult) && + (!initialQueryFlag && loading && !productSearchResult) && }
    From a5b10aa85621a27332ce867db9dd36245262ceb5 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Thu, 7 Oct 2021 10:53:19 +0700 Subject: [PATCH 17/20] :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 18/20] :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 19/20] :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 20/20] :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