mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
Merge pull request #77 from KieIO/feature/m3-product-list
Feature/m3 product list
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import { OperationContext } from '@commerce/api/operations'
|
import { OperationContext } from '@commerce/api/operations'
|
||||||
import { Facet } from '@commerce/types/facet'
|
import { Facet } from '@commerce/types/facet'
|
||||||
import { Provider, VendureConfig } from '../'
|
import { Provider, VendureConfig } from '../'
|
||||||
import { GetAllFacetsQuery } from '../../schema'
|
import { FacetFilterParameter, FacetSortParameter, GetAllFacetsQuery } from '../../schema'
|
||||||
import { getAllFacetsQuery } from '../../utils/queries/get-all-facets-query'
|
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({
|
export default function getAllFacetsOperation({
|
||||||
commerce,
|
commerce,
|
||||||
@@ -27,9 +27,10 @@ export default function getAllFacetsOperation({
|
|||||||
} = {}): Promise<{ facets: Facet[] | any[] }> {
|
} = {}): Promise<{ facets: Facet[] | any[] }> {
|
||||||
const config = commerce.getConfig(cfg)
|
const config = commerce.getConfig(cfg)
|
||||||
const variables = {
|
const variables = {
|
||||||
input: {
|
options: {
|
||||||
take: vars.first,
|
take: vars.first,
|
||||||
groupByFacet: true,
|
filter: vars.filter,
|
||||||
|
sort: vars.sort,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const { data } = await config.fetch<GetAllFacetsQuery>(query, {
|
const { data } = await config.fetch<GetAllFacetsQuery>(query, {
|
||||||
|
@@ -14,7 +14,7 @@ export default function getAllProductsOperation({
|
|||||||
variables?: ProductVariables
|
variables?: ProductVariables
|
||||||
config?: Partial<VendureConfig>
|
config?: Partial<VendureConfig>
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
}): Promise<{ products: Product[] }>
|
}): Promise<{ products: Product[], totalItems: number }>
|
||||||
|
|
||||||
async function getAllProducts({
|
async function getAllProducts({
|
||||||
query = getAllProductsQuery,
|
query = getAllProductsQuery,
|
||||||
@@ -25,7 +25,7 @@ export default function getAllProductsOperation({
|
|||||||
variables?: ProductVariables
|
variables?: ProductVariables
|
||||||
config?: Partial<VendureConfig>
|
config?: Partial<VendureConfig>
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
} = {}): Promise<{ products: Product[] | any[] }> {
|
} = {}): Promise<{ products: Product[] | any[], totalItems: number }> {
|
||||||
const config = commerce.getConfig(cfg)
|
const config = commerce.getConfig(cfg)
|
||||||
const variables = {
|
const variables = {
|
||||||
input: {
|
input: {
|
||||||
@@ -40,6 +40,7 @@ export default function getAllProductsOperation({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
products: data.search.items.map((item) => normalizeSearchResult(item)),
|
products: data.search.items.map((item) => normalizeSearchResult(item)),
|
||||||
|
totalItems: data.search.totalItems as number,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
framework/vendure/schema.d.ts
vendored
8
framework/vendure/schema.d.ts
vendored
@@ -3219,7 +3219,8 @@ export type GetAllProductsQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type GetAllProductsQuery = { __typename?: 'Query' } & {
|
export type GetAllProductsQuery = { __typename?: 'Query' } & {
|
||||||
search: { __typename?: 'SearchResponse' } & {
|
search: { __typename?: 'SearchResponse' } & {
|
||||||
items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment>
|
items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment>,
|
||||||
|
'totalItems'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3228,8 +3229,9 @@ export type GetAllFacetsQuery = { __typename?: 'Query' } & {
|
|||||||
items: Array<
|
items: Array<
|
||||||
{ __typename?: 'Facet' } & Pick<
|
{ __typename?: 'Facet' } & Pick<
|
||||||
Facet,
|
Facet,
|
||||||
'id' | 'name' | 'code'
|
'id' | 'name' | 'code' | 'values'
|
||||||
> & {
|
>
|
||||||
|
& {
|
||||||
parent?: Maybe<{ __typename?: 'Facet' } & Pick<Facet, 'id'>>
|
parent?: Maybe<{ __typename?: 'Facet' } & Pick<Facet, 'id'>>
|
||||||
children?: Maybe<
|
children?: Maybe<
|
||||||
Array<{ __typename?: 'Facet' } & Pick<Facet, 'id'>>
|
Array<{ __typename?: 'Facet' } & Pick<Facet, 'id'>>
|
||||||
|
@@ -3,6 +3,7 @@ import { searchResultFragment } from '../fragments/search-result-fragment'
|
|||||||
export const getAllProductsQuery = /* GraphQL */ `
|
export const getAllProductsQuery = /* GraphQL */ `
|
||||||
query getAllProducts($input: SearchInput!) {
|
query getAllProducts($input: SearchInput!) {
|
||||||
search(input: $input) {
|
search(input: $input) {
|
||||||
|
totalItems
|
||||||
items {
|
items {
|
||||||
...SearchResult
|
...SearchResult
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ export const getCollectionsNameQuery = /* GraphQL */ `
|
|||||||
collections{
|
collections{
|
||||||
items{
|
items{
|
||||||
name
|
name
|
||||||
link:slug
|
slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -62,27 +62,34 @@ export async function getStaticProps({
|
|||||||
const freshFacetId = getFreshFacetId(facets)
|
const freshFacetId = getFreshFacetId(facets)
|
||||||
if (freshFacetId) {
|
if (freshFacetId) {
|
||||||
freshProductvariables.facetValueIds = [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
|
// featured products
|
||||||
const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED)
|
const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED)
|
||||||
const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT)
|
const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT)
|
||||||
const facetValueIdsForFeaturedProducts = [...allFeaturedFacetIds, ...allDiscountFacetIds]
|
const facetValueIdsForFeaturedProducts = [...allFeaturedFacetIds, ...allDiscountFacetIds]
|
||||||
const featuredProductsPromise = commerce.getAllProducts({
|
|
||||||
variables: {
|
if (facetValueIdsForFeaturedProducts.length > 0) {
|
||||||
facetValueIds: facetValueIdsForFeaturedProducts
|
const featuredProductsPromise = commerce.getAllProducts({
|
||||||
},
|
variables: {
|
||||||
config,
|
facetValueIds: facetValueIdsForFeaturedProducts
|
||||||
preview,
|
},
|
||||||
})
|
config,
|
||||||
promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' })
|
preview,
|
||||||
|
})
|
||||||
|
promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' })
|
||||||
|
} else {
|
||||||
|
props.featuredProducts = []
|
||||||
|
}
|
||||||
|
|
||||||
// collection
|
// collection
|
||||||
const collectionsPromise = commerce.getAllCollections({
|
const collectionsPromise = commerce.getAllCollections({
|
||||||
|
@@ -1,19 +1,97 @@
|
|||||||
|
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 { Layout } from 'src/components/common';
|
||||||
import { ViewedProducts } from 'src/components/modules/product-detail';
|
import { ViewedProducts } from 'src/components/modules/product-detail';
|
||||||
import ProductListFilter from 'src/components/modules/product-list/ProductListFilter/ProductListFilter';
|
import ProductListFilter from 'src/components/modules/product-list/ProductListFilter/ProductListFilter';
|
||||||
import RecipeListBanner from 'src/components/modules/recipes-list/RecipeListBanner/RecipeListBanner';
|
import { CODE_FACET_BRAND, CODE_FACET_FEATURED, DEFAULT_PAGE_SIZE } from 'src/utils/constanst.utils';
|
||||||
import RecipesList from 'src/components/modules/recipes-list/RecipesList/RecipesList';
|
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';
|
import ProductListBanner from '../src/components/modules/product-list/ProductListBanner/ProductListBanner';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
facets: Facet[],
|
||||||
|
collections: Collection[],
|
||||||
|
productsResult: { products: ProductCard[], totalItems: number },
|
||||||
|
|
||||||
export default function Products() {
|
}
|
||||||
|
|
||||||
|
export default function Products({ facets, collections, productsResult }: Props) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProductListBanner />
|
<ProductListBanner />
|
||||||
<ProductListFilter/>
|
<ProductListFilter
|
||||||
<ViewedProducts/>
|
collections={collections}
|
||||||
|
facets={facets}
|
||||||
|
products={productsResult.products}
|
||||||
|
total={productsResult.totalItems} />
|
||||||
|
<ViewedProducts />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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: 'productsResult', promise: productsPromise })
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Products.Layout = Layout
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import commerce from '@lib/api/commerce';
|
import commerce from '@lib/api/commerce';
|
||||||
import { GetStaticPropsContext } from 'next';
|
import { GetStaticPropsContext } from 'next';
|
||||||
import { Layout } from 'src/components/common';
|
import { Layout, ListProductCardSkeleton } from 'src/components/common';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
products: any
|
products: any
|
||||||
@@ -8,10 +8,11 @@ interface Props {
|
|||||||
export default function Home({ products }: Props) {
|
export default function Home({ products }: Props) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>
|
{/* <ListProductCardSkeleton /> */}
|
||||||
TOTAL: {products?.length}
|
{/* <ListProductCardSkeleton count={1} /> */}
|
||||||
</p>
|
<ListProductCardSkeleton count={10} />
|
||||||
{JSON.stringify(products[0])}
|
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ab qui magnam debitis ex laborum laboriosam suscipit! Totam excepturi eum libero.
|
||||||
|
<ListProductCardSkeleton count={10} isWrap/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -22,28 +23,9 @@ export async function getServerSideProps({
|
|||||||
locale,
|
locale,
|
||||||
locales,
|
locales,
|
||||||
}: GetStaticPropsContext) {
|
}: GetStaticPropsContext) {
|
||||||
const config = { locale, locales }
|
|
||||||
const productsPromise = commerce.getAllProducts({
|
|
||||||
// const productsPromise = commerce.getAllFacets({
|
|
||||||
variables: {
|
|
||||||
first: 70,
|
|
||||||
// filter: {
|
|
||||||
// name: {
|
|
||||||
// contains: 'ca'
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
config,
|
|
||||||
preview,
|
|
||||||
// Saleor provider only
|
|
||||||
...({ featured: true } as any),
|
|
||||||
})
|
|
||||||
|
|
||||||
const { products } = await productsPromise
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: { products },
|
props: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,13 +4,16 @@
|
|||||||
padding: 1.6rem;
|
padding: 1.6rem;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
.imgWrap {
|
.imgWrap {
|
||||||
min-width: 10rem;
|
min-height: 10rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
img {
|
||||||
|
min-height: 10rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
color: var(--disabled);
|
color: var(--disabled);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: .8rem;
|
margin-top: 0.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import React, { memo, useEffect, useRef, useState } from 'react'
|
import React, { memo, useEffect, useRef, useState } from 'react'
|
||||||
|
import { useProductFilter } from 'src/components/contexts'
|
||||||
import { useModalCommon } from 'src/components/hooks'
|
import { useModalCommon } from 'src/components/hooks'
|
||||||
import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate'
|
import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate'
|
||||||
import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo'
|
import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo'
|
||||||
@@ -9,12 +10,12 @@ import HeaderSubMenu from './components/HeaderSubMenu/HeaderSubMenu'
|
|||||||
import HeaderSubMenuMobile from './components/HeaderSubMenuMobile/HeaderSubMenuMobile'
|
import HeaderSubMenuMobile from './components/HeaderSubMenuMobile/HeaderSubMenuMobile'
|
||||||
import s from './Header.module.scss'
|
import s from './Header.module.scss'
|
||||||
interface props {
|
interface props {
|
||||||
toggleFilter: () => void,
|
|
||||||
visibleFilter: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = memo(({ toggleFilter, visibleFilter }: props) => {
|
const Header = memo(({ }: props) => {
|
||||||
const headeFullRef = useRef<HTMLDivElement>(null)
|
const headeFullRef = useRef<HTMLDivElement>(null)
|
||||||
|
const { toggleProductFilter: toggleFilter } = useProductFilter()
|
||||||
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
|
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
|
||||||
const [isModeAuthenRegister, setIsModeAuthenRegister] = useState<boolean>(false)
|
const [isModeAuthenRegister, setIsModeAuthenRegister] = useState<boolean>(false)
|
||||||
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false })
|
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false })
|
||||||
@@ -63,7 +64,6 @@ const Header = memo(({ toggleFilter, visibleFilter }: props) => {
|
|||||||
<div className={s.menu}>
|
<div className={s.menu}>
|
||||||
<HeaderMenu
|
<HeaderMenu
|
||||||
isFull={isFullHeader}
|
isFull={isFullHeader}
|
||||||
visibleFilter={visibleFilter}
|
|
||||||
toggleFilter={toggleFilter}
|
toggleFilter={toggleFilter}
|
||||||
openModalLogin={openModalLogin}
|
openModalLogin={openModalLogin}
|
||||||
openModalRegister = {openModalRegister}
|
openModalRegister = {openModalRegister}
|
||||||
|
@@ -50,10 +50,6 @@
|
|||||||
width: 1.2rem;
|
width: 1.2rem;
|
||||||
height: 1.2rem;
|
height: 1.2rem;
|
||||||
border-radius: 1.2rem;
|
border-radius: 1.2rem;
|
||||||
@apply hidden;
|
|
||||||
&.isShow {
|
|
||||||
@apply block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@screen md {
|
@screen md {
|
||||||
display: none;
|
display: none;
|
||||||
|
@@ -27,7 +27,6 @@ interface Props {
|
|||||||
children?: any
|
children?: any
|
||||||
isFull?: boolean
|
isFull?: boolean
|
||||||
isStickyHeader?: boolean
|
isStickyHeader?: boolean
|
||||||
visibleFilter?: boolean
|
|
||||||
openModalLogin: () => void
|
openModalLogin: () => void
|
||||||
openModalRegister: () => void
|
openModalRegister: () => void
|
||||||
openModalInfo: () => void
|
openModalInfo: () => void
|
||||||
@@ -38,7 +37,6 @@ const HeaderMenu = memo(
|
|||||||
({
|
({
|
||||||
isFull,
|
isFull,
|
||||||
isStickyHeader,
|
isStickyHeader,
|
||||||
visibleFilter,
|
|
||||||
openModalLogin,
|
openModalLogin,
|
||||||
openModalRegister,
|
openModalRegister,
|
||||||
openModalInfo,
|
openModalInfo,
|
||||||
@@ -90,6 +88,7 @@ const HeaderMenu = memo(
|
|||||||
],
|
],
|
||||||
[logout]
|
[logout]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={classNames({
|
className={classNames({
|
||||||
@@ -105,12 +104,7 @@ const HeaderMenu = memo(
|
|||||||
{FILTER_PAGE.includes(router.pathname) && (
|
{FILTER_PAGE.includes(router.pathname) && (
|
||||||
<button className={s.iconFilter} onClick={toggleFilter}>
|
<button className={s.iconFilter} onClick={toggleFilter}>
|
||||||
<IconFilter />
|
<IconFilter />
|
||||||
<div
|
<div className={s.dot}></div>
|
||||||
className={classNames({
|
|
||||||
[s.dot]: true,
|
|
||||||
[s.isShow]: visibleFilter,
|
|
||||||
})}
|
|
||||||
></div>
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
@@ -42,7 +42,7 @@ const HeaderSubMenu = memo(() => {
|
|||||||
<ul className={s.menu}>
|
<ul className={s.menu}>
|
||||||
{/* todo: handle active item */}
|
{/* todo: handle active item */}
|
||||||
<li>
|
<li>
|
||||||
<MenuDropdown options={collections?.items ?? []} align="left">Categories</MenuDropdown>
|
<MenuDropdown options={collections || []} align="left">Categories</MenuDropdown>
|
||||||
</li>
|
</li>
|
||||||
{
|
{
|
||||||
MENU.map(item => <li key={item.name}
|
MENU.map(item => <li key={item.name}
|
||||||
|
@@ -68,4 +68,5 @@ const HeaderSubMenuMobile = memo(({ }: Props) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
HeaderSubMenuMobile.displayName = 'HeaderSubMenuMobile'
|
||||||
export default HeaderSubMenuMobile
|
export default HeaderSubMenuMobile
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { CommerceProvider } from '@framework'
|
import { CommerceProvider } from '@framework'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { CartDrawerProvider, MessageProvider } from 'src/components/contexts'
|
import { CartDrawerProvider, MessageProvider, ProductFilterProvider } from 'src/components/contexts'
|
||||||
import LayoutContent from './LayoutContent/LayoutContent'
|
import LayoutContent from './LayoutContent/LayoutContent'
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
@@ -13,9 +13,11 @@ const Layout: FC<Props> = ({ children }) => {
|
|||||||
return (
|
return (
|
||||||
<CommerceProvider locale={locale}>
|
<CommerceProvider locale={locale}>
|
||||||
<CartDrawerProvider>
|
<CartDrawerProvider>
|
||||||
<MessageProvider>
|
<ProductFilterProvider>
|
||||||
<LayoutContent>{children}</LayoutContent>
|
<MessageProvider>
|
||||||
</MessageProvider>
|
<LayoutContent>{children}</LayoutContent>
|
||||||
|
</MessageProvider>
|
||||||
|
</ProductFilterProvider>
|
||||||
</CartDrawerProvider>
|
</CartDrawerProvider>
|
||||||
</CommerceProvider>
|
</CommerceProvider>
|
||||||
)
|
)
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.filter {
|
.filter {
|
||||||
@screen xl {
|
@screen md {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import { useRouter } from 'next/router'
|
|||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useMessage } from 'src/components/contexts'
|
import { useMessage } from 'src/components/contexts'
|
||||||
import { useModalCommon } from 'src/components/hooks'
|
import { useModalCommon } from 'src/components/hooks'
|
||||||
import { BRAND, CATEGORY, FEATURED, FILTER_PAGE, ROUTE } from 'src/utils/constanst.utils'
|
import { FILTER_PAGE, ROUTE } from 'src/utils/constanst.utils'
|
||||||
import { CartDrawer, Footer, MessageCommon, ScrollToTop } from '../..'
|
import { CartDrawer, Footer, MessageCommon, ScrollToTop } from '../..'
|
||||||
import Header from '../../Header/Header'
|
import Header from '../../Header/Header'
|
||||||
import MenuNavigationProductList from '../../MenuNavigationProductList/MenuNavigationProductList'
|
import MenuNavigationProductList from '../../MenuNavigationProductList/MenuNavigationProductList'
|
||||||
@@ -14,23 +14,13 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LayoutContent: FC<Props> = ({ children }) => {
|
const LayoutContent: FC<Props> = ({ children }) => {
|
||||||
const { pathname } = useRouter()
|
|
||||||
const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false })
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const {messages, removeMessage} = useMessage()
|
const {messages, removeMessage} = useMessage()
|
||||||
|
|
||||||
const toggleFilter = () => {
|
|
||||||
if (visibleFilter) {
|
|
||||||
closeFilter()
|
|
||||||
} else {
|
|
||||||
openFilter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={s.mainLayout}>
|
<div className={s.mainLayout}>
|
||||||
<Header toggleFilter={toggleFilter} visibleFilter={visibleFilter} />
|
<Header/>
|
||||||
{
|
{
|
||||||
router.pathname === ROUTE.ACCOUNT ?
|
router.pathname === ROUTE.ACCOUNT ?
|
||||||
<section className={s.wrapperWithBg}>
|
<section className={s.wrapperWithBg}>
|
||||||
@@ -38,10 +28,9 @@ const LayoutContent: FC<Props> = ({ children }) => {
|
|||||||
</section> :
|
</section> :
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
}
|
}
|
||||||
<div className={s.filter}><MenuNavigationProductList categories={CATEGORY} brands={BRAND} featured={FEATURED} visible={visibleFilter} onClose={closeFilter} /> </div>
|
|
||||||
<ScrollToTop visibilityHeight={1500} />
|
<ScrollToTop visibilityHeight={1500} />
|
||||||
{
|
{
|
||||||
FILTER_PAGE.includes(pathname) && (<div className={s.filter}><MenuNavigationProductList categories={CATEGORY} brands={BRAND} featured={FEATURED} visible={visibleFilter} onClose={closeFilter} /> </div>)
|
FILTER_PAGE.includes(router.pathname) && (<div className={s.filter}><MenuNavigationProductList /> </div>)
|
||||||
}
|
}
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
.listProductCardSkeleton {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
overflow: unset;
|
||||||
|
> div {
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import { ProductCardSkeleton } from '..'
|
||||||
|
import s from './ListProductCardSkeleton.module.scss'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
count?: number
|
||||||
|
isWrap?: boolean
|
||||||
|
}
|
||||||
|
const ListProductCardSkeleton = ({ count = 5, isWrap }: Props) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(s.listProductCardSkeleton, { [s.wrap]: isWrap })}>
|
||||||
|
{
|
||||||
|
Array.from(Array(count).keys()).map(item => <ProductCardSkeleton key={item} />)
|
||||||
|
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListProductCardSkeleton
|
@@ -1,9 +1,8 @@
|
|||||||
@import "../../../styles/utilities";
|
@import "../../../styles/utilities";
|
||||||
.menuFilterWrapper{
|
|
||||||
@apply spacing-horizontal;
|
|
||||||
|
|
||||||
|
.menuFilterWrapper{
|
||||||
.menuFilterHeading{
|
.menuFilterHeading{
|
||||||
@apply sub-headline font-bold ;
|
@apply sub-headline font-bold;
|
||||||
color: var(--text-active);
|
color: var(--text-active);
|
||||||
font-feature-settings: 'salt' on;
|
font-feature-settings: 'salt' on;
|
||||||
margin: 0.8rem 0;
|
margin: 0.8rem 0;
|
||||||
@@ -19,21 +18,5 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid var(--border-line);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,44 +1,34 @@
|
|||||||
import classNames from 'classnames'
|
import s from './MenuFilter.module.scss';
|
||||||
import { useEffect, useState } from 'react';
|
import MenuFilterItem from './MenuFilterItem/MenuFilterItem';
|
||||||
|
|
||||||
import s from './MenuFilter.module.scss'
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: any,
|
children?: any,
|
||||||
heading?:string,
|
heading?: string,
|
||||||
categories:{name:string,link:string}[],
|
categories: { name: string, slug?: string, code?: string }[],
|
||||||
type:string,
|
type: string,
|
||||||
onChangeValue?: (value: Object) => void
|
onChange: (value: string, type: string, isSellect?: boolean) => void
|
||||||
|
isSingleSelect?: boolean
|
||||||
|
singleSelectedValue?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuFilter = ({heading,categories,type,onChangeValue}:Props)=> {
|
const MenuFilter = ({ heading, categories, type, onChange, singleSelectedValue, isSingleSelect }: Props) => {
|
||||||
const [active, setActive] = useState<string>('');
|
function handleChange(value: string, isSellect: boolean) {
|
||||||
|
onChange(value, type, isSellect)
|
||||||
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 (
|
return (
|
||||||
<section className={s.menuFilterWrapper}>
|
<section className={s.menuFilterWrapper}>
|
||||||
<h2 className={s.menuFilterHeading}>{heading}</h2>
|
<h2 className={s.menuFilterHeading}>{heading}</h2>
|
||||||
<ul className={s.menuFilterList}>
|
<ul className={s.menuFilterList}>
|
||||||
{
|
{
|
||||||
categories.map(item => <li key={item.name}>
|
categories.map(item => <MenuFilterItem
|
||||||
<div onClick={()=> handleClick(item.link)} className={classNames({ [s.active]: item.link === active? true: false })}>
|
key={item.slug || item.code}
|
||||||
{item.name}
|
name={item.name}
|
||||||
</div>
|
type={type}
|
||||||
</li>)
|
value={item.slug || item.code || ''}
|
||||||
|
onChange={handleChange}
|
||||||
|
isActive={isSingleSelect && (item.slug || item.code) === singleSelectedValue}
|
||||||
|
/>)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
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,
|
||||||
|
type: string,
|
||||||
|
isActive?: boolean
|
||||||
|
onChange: (value: string, isSellect: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const MenuFilterItem = ({ name, value, type, isActive, onChange }: Props) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const [isSelected, setIsSelected] = useState<boolean>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsSelected(isActive)
|
||||||
|
}, [isActive])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const rs = (router.query[type] || []).includes(value)
|
||||||
|
setIsSelected(rs)
|
||||||
|
}, [type, router.query, value])
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
onChange(value, !isSelected)
|
||||||
|
setIsSelected(!isSelected)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={s.menuFilterItem}>
|
||||||
|
<div onClick={handleClick} className={classNames({ [s.active]: isSelected })}>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuFilterItem
|
@@ -3,31 +3,13 @@
|
|||||||
@apply hidden;
|
@apply hidden;
|
||||||
@screen md {
|
@screen md {
|
||||||
@apply block;
|
@apply block;
|
||||||
}
|
.menuNavigationHeading{
|
||||||
.menuNavigationHeading{
|
@screen md {
|
||||||
@screen md {
|
@apply sub-headline font-bold ;
|
||||||
@apply sub-headline font-bold ;
|
color: var(--text-active);
|
||||||
color: var(--text-active);
|
font-feature-settings: 'salt' on;
|
||||||
font-feature-settings: 'salt' on;
|
margin: 1.6rem 0;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,30 +1,27 @@
|
|||||||
import classNames from 'classnames'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import s from './MenuNavigation.module.scss'
|
import s from './MenuNavigation.module.scss'
|
||||||
|
import MenuNavigationItem from './MenuNavigationItem/MenuNavigationItem'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: any,
|
children?: any,
|
||||||
heading:string,
|
heading: string,
|
||||||
categories:{name:string,link:string}[]
|
queryKey: string,
|
||||||
|
categories: { name: string, slug?: string, code?: string }[]
|
||||||
|
isSingleSelect?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuNavigation = ({heading,categories}:Props)=> {
|
const MenuNavigation = ({ heading, queryKey, categories, isSingleSelect }: Props) => {
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={s.menuNavigationWrapper}>
|
<section className={s.menuNavigationWrapper}>
|
||||||
<h2 className={s.menuNavigationHeading}>{heading}({categories.length})</h2>
|
<h2 className={s.menuNavigationHeading}>{heading}({categories.length})</h2>
|
||||||
<ul className={s.menuNavigationList}>
|
<ul className={s.menuNavigationList}>
|
||||||
{
|
{
|
||||||
categories.map(item => <li key={item.name}
|
categories.map(item => <MenuNavigationItem
|
||||||
>
|
key={item.name}
|
||||||
<Link href={item.link}>
|
name={item.name}
|
||||||
<a className={classNames({ [s.active]: router.asPath === item.link})}>
|
value={item.slug || item.code || ''}
|
||||||
{item.name}
|
queryKey={queryKey}
|
||||||
</a>
|
isSingleSelect={isSingleSelect}
|
||||||
</Link>
|
/>)
|
||||||
</li>)
|
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
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
|
||||||
|
isSingleSelect?: boolean
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const MenuNavigationItem = ({ name, value, queryKey, isSingleSelect }: Props) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const [isActive, setIsActive] = useState<boolean>()
|
||||||
|
|
||||||
|
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 = ''
|
||||||
|
if (isSingleSelect) {
|
||||||
|
newQuery = isActive ? '' : value
|
||||||
|
} else {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
pathname: ROUTE.PRODUCTS,
|
||||||
|
query
|
||||||
|
},
|
||||||
|
undefined, { shallow: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<li className={classNames(s.menuNavigationItem, { [s.active]: isActive })}
|
||||||
|
onClick={handleClick}>
|
||||||
|
{name}
|
||||||
|
</li>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuNavigationItem
|
@@ -1,13 +1,5 @@
|
|||||||
@import "../../../styles/utilities";
|
@import "../../../styles/utilities";
|
||||||
.menuNavigationProductListDesktop{
|
|
||||||
@screen sm {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
@screen xl {
|
|
||||||
@apply block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.menuNavigationProductListMobile{
|
.menuNavigationProductListMobile{
|
||||||
@apply relative transition-all duration-100;
|
@apply relative transition-all duration-100;
|
||||||
&.isShow{
|
&.isShow{
|
||||||
@@ -37,7 +29,7 @@
|
|||||||
transform: translateY(0%)
|
transform: translateY(0%)
|
||||||
}
|
}
|
||||||
.content{
|
.content{
|
||||||
@apply absolute w-full h-full;
|
@apply absolute w-full h-full spacing-horizontal custom-scroll;
|
||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
padding-top: 10rem ;
|
padding-top: 10rem ;
|
||||||
padding-bottom: 10rem;
|
padding-bottom: 10rem;
|
||||||
@@ -46,6 +38,7 @@
|
|||||||
height: 96%;
|
height: 96%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
border-radius: 2.4rem 2.4rem 0 0;
|
border-radius: 2.4rem 2.4rem 0 0;
|
||||||
|
|
||||||
.head{
|
.head{
|
||||||
@apply flex justify-between fixed;
|
@apply flex justify-between fixed;
|
||||||
top:0;
|
top:0;
|
||||||
@@ -57,12 +50,11 @@
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
h3{
|
h3{
|
||||||
@apply heading-3 font-bold;
|
@apply heading-3 font-heading;
|
||||||
color:var(--text-base);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foot{
|
.foot{
|
||||||
@apply fixed;
|
@apply fixed text-center;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left:0;
|
left:0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -70,7 +62,7 @@
|
|||||||
padding: 0 1rem 3rem 1rem;
|
padding: 0 1rem 3rem 1rem;
|
||||||
|
|
||||||
}
|
}
|
||||||
button{
|
button {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@@ -1,56 +1,155 @@
|
|||||||
import React, { useState } from 'react';
|
import { QueryFacetsArgs } from '@framework/schema';
|
||||||
import {ButtonCommon} from 'src/components/common';
|
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';
|
||||||
|
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';
|
||||||
|
import SkeletonParagraph from '../SkeletonCommon/SkeletonParagraph/SkeletonParagraph';
|
||||||
import s from './MenuNavigationProductList.module.scss';
|
import s from './MenuNavigationProductList.module.scss';
|
||||||
import MenuSort from './MenuSort/MenuSort';
|
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{
|
interface Props {
|
||||||
categories:{name:string,link:string}[],
|
|
||||||
brands:{name:string,link:string}[],
|
|
||||||
featured:{name:string,link:string}[],
|
|
||||||
visible: boolean,
|
|
||||||
onClose: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuNavigationProductList = ({categories,brands,featured,visible,onClose}:Props)=>{
|
const FACET_QUERY = {
|
||||||
|
options: {
|
||||||
const [dataSort,setDataSort] = useState({});
|
sort: {
|
||||||
|
code: SortOrder.Asc
|
||||||
function handleValue(value:Object){
|
},
|
||||||
setDataSort({...dataSort,...value});
|
filter: {
|
||||||
|
code: {
|
||||||
|
in: [CODE_FACET_FEATURED, CODE_FACET_BRAND]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function filter(){
|
} as QueryFacetsArgs
|
||||||
// console.log(dataSort)
|
|
||||||
|
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<string[]>([])
|
||||||
|
const [featuredQuery, setFeaturedQuery] = useState<string[]>([])
|
||||||
|
const [categoryQuery, setCategoryQuery] = useState<string>()
|
||||||
|
const [sortValue, setSortValue] = useState<string>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const rs = router.query[QUERY_KEY.SORTBY] as string
|
||||||
|
if (rs) {
|
||||||
|
setSortValue(rs)
|
||||||
|
}
|
||||||
|
}, [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) {
|
||||||
|
newURL += `&${QUERY_KEY.CATEGORY}=${categoryQuery}`
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
return(
|
|
||||||
<>
|
const onSortChange = (value: string) => {
|
||||||
<div className={s.menuNavigationProductListDesktop}>
|
setSortValue(value)
|
||||||
<MenuNavigation categories={categories} heading="Categories"/>
|
}
|
||||||
<MenuNavigation categories={brands} heading="Brands"/>
|
|
||||||
<MenuNavigation categories={featured} heading="Featured"/>
|
const onCategoryChange = (value: string, isSelect: boolean) => {
|
||||||
</div>
|
if (isSelect) {
|
||||||
<div className={classNames({ [s.menuNavigationProductListMobile] :true,[s.isShow]: visible})}>
|
setCategoryQuery(value)
|
||||||
<div className={classNames({ [s.menuNavigationProductModal] :true,[s.animation]: visible})}>
|
} else {
|
||||||
<div className={s.content}>
|
setCategoryQuery('')
|
||||||
<div className={s.head}>
|
}
|
||||||
<h3>FILTER</h3>
|
}
|
||||||
<div onClick={onClose}><IconHide/></div>
|
|
||||||
</div>
|
const onFilterOptionChange = (value: string, type: string, isSelect: boolean = true) => {
|
||||||
<MenuFilter categories={categories} heading="Categories" type="category" onChangeValue={handleValue}/>
|
if (type === QUERY_KEY.CATEGORY) {
|
||||||
<MenuFilter categories={brands} heading="Brand" type="brand" onChangeValue={handleValue}/>
|
onCategoryChange(value, isSelect)
|
||||||
<MenuFilter categories={featured} heading="Featured" type="featured" onChangeValue={handleValue}/>
|
} else {
|
||||||
<MenuSort heading="SORT BY" type="sort" onChangeValue={handleValue}/>
|
let rs = [...featuredQuery]
|
||||||
<div className={s.foot}>
|
let setDataFunction = setFeaturedQuery
|
||||||
<ButtonCommon size="large" onClick={filter}>{LANGUAGE.BUTTON_LABEL.CONFIRM}</ButtonCommon>
|
|
||||||
</div>
|
if (type === CODE_FACET_BRAND) {
|
||||||
|
rs = [...brandQuery]
|
||||||
|
setDataFunction = setBrandQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelect) {
|
||||||
|
rs.push(value)
|
||||||
|
} else {
|
||||||
|
rs = rs.filter(item => item !== value)
|
||||||
|
}
|
||||||
|
setDataFunction(rs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames({ [s.menuNavigationProductListMobile]: true, [s.isShow]: visible })}>
|
||||||
|
<div className={classNames({ [s.menuNavigationProductModal]: true, [s.animation]: visible })}>
|
||||||
|
<div className={s.content}>
|
||||||
|
<div className={s.head}>
|
||||||
|
<h3>FILTER</h3>
|
||||||
|
<div onClick={onClose}><IconHide /></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{collectionLoading && <SkeletonParagraph rows={5} />}
|
||||||
|
<MenuFilter categories={collections}
|
||||||
|
heading="Categories"
|
||||||
|
type={QUERY_KEY.CATEGORY}
|
||||||
|
onChange={onFilterOptionChange}
|
||||||
|
singleSelectedValue={categoryQuery}
|
||||||
|
isSingleSelect={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{facetsLoading && <>
|
||||||
|
<SkeletonParagraph rows={5} />
|
||||||
|
<SkeletonParagraph rows={5} />
|
||||||
|
</>}
|
||||||
|
{
|
||||||
|
facets?.map(item => <MenuFilter
|
||||||
|
key={item.id}
|
||||||
|
type={item.code}
|
||||||
|
categories={item.values}
|
||||||
|
heading={item.name}
|
||||||
|
onChange={onFilterOptionChange}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
<MenuSort heading="SORT BY" onChange={onSortChange} value={sortValue} options={OPTIONS_SORT_PRODUCT} />
|
||||||
|
<div className={s.foot}>
|
||||||
|
<ButtonCommon size="large" onClick={onSubmit}>{LANGUAGE.BUTTON_LABEL.CONFIRM}</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,40 +1,12 @@
|
|||||||
@import "../../../../styles/utilities";
|
@import "../../../../styles/utilities";
|
||||||
.menuSortWrapper{
|
|
||||||
@apply spacing-horizontal;
|
|
||||||
|
|
||||||
|
.menuSortWrapper{
|
||||||
.menuSortHeading{
|
.menuSortHeading{
|
||||||
@apply sub-headline font-bold ;
|
@apply heading-3 font-heading;
|
||||||
color: var(--text-active);
|
|
||||||
font-feature-settings: 'salt' on;
|
|
||||||
margin: 0.8rem 0;
|
margin: 0.8rem 0;
|
||||||
}
|
}
|
||||||
.menuSortList{
|
.menuSortList{
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
box-sizing: border-box;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,63 +1,30 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useEffect, useState } from 'react';
|
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 s from './MenuSort.module.scss';
|
||||||
|
import MenuSortItem from './MenuSortItem/MenuSortItem';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: any,
|
children?: any,
|
||||||
heading:string,
|
heading: string,
|
||||||
type:string,
|
options: {name: string, value: string}[]
|
||||||
onChangeValue?: (value: Object) => void
|
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, value, onChange, options }: Props) => {
|
||||||
const MenuSort = ({heading,type,onChangeValue}:Props)=> {
|
|
||||||
const [active, setActive] = useState<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 (
|
return (
|
||||||
<section className={classNames(s.menuSortWrapper)}>
|
<section className={classNames(s.menuSortWrapper)}>
|
||||||
<h2 className={classNames(s.menuSortHeading)}>{heading}</h2>
|
<h2 className={classNames(s.menuSortHeading)}>{heading}</h2>
|
||||||
<ul className={s.menuSortList}>
|
<ul className={s.menuSortList}>
|
||||||
{
|
{
|
||||||
SORT.map(item => <li key={item.name}>
|
options.map(item => <MenuSortItem
|
||||||
<div onClick={()=> handleClick(item.link)} className={classNames({ [s.active]: item.link === active? true: false })}>
|
key={item.value}
|
||||||
{item.name}
|
name={item.name}
|
||||||
</div>
|
value={item.value}
|
||||||
</li>)
|
currentValue={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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 (
|
||||||
|
<li className={s.menuSortItem}>
|
||||||
|
<div onClick={handleChange} className={classNames({ [s.active]: value === currentValue })}>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuSortItem
|
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.7104 1.20986C14.6175 1.11613 14.5069 1.04174 14.385 0.990969C14.2632 0.940201 14.1324 0.914062 14.0004 0.914062C13.8684 0.914062 13.7377 0.940201 13.6159 0.990969C13.494 1.04174 13.3834 1.11613 13.2904 1.20986L5.84044 8.66986L2.71044 5.52986C2.61392 5.43662 2.49998 5.36331 2.37512 5.3141C2.25026 5.2649 2.11694 5.24077 1.98276 5.24309C1.84858 5.24541 1.71617 5.27414 1.59309 5.32763C1.47001 5.38113 1.35868 5.45834 1.26544 5.55486C1.1722 5.65138 1.09889 5.76532 1.04968 5.89018C1.00048 6.01503 0.976347 6.14836 0.978669 6.28254C0.98099 6.41672 1.00972 6.54913 1.06321 6.67221C1.1167 6.79529 1.19392 6.90662 1.29044 6.99986L5.13044 10.8399C5.2234 10.9336 5.334 11.008 5.45586 11.0588C5.57772 11.1095 5.70843 11.1357 5.84044 11.1357C5.97245 11.1357 6.10316 11.1095 6.22502 11.0588C6.34687 11.008 6.45748 10.9336 6.55044 10.8399L14.7104 2.67986C14.8119 2.58622 14.893 2.47257 14.9484 2.34607C15.0038 2.21957 15.0324 2.08296 15.0324 1.94486C15.0324 1.80676 15.0038 1.67015 14.9484 1.54365C14.893 1.41715 14.8119 1.3035 14.7104 1.20986Z" fill="#5b9a74" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@@ -25,7 +25,7 @@ const ModalCreateUserInfo = ({ demoVisible: visible, demoCloseModal: closeModal
|
|||||||
<Inputcommon placeholder='Street Address' ref={firstInputRef} />
|
<Inputcommon placeholder='Street Address' ref={firstInputRef} />
|
||||||
<Inputcommon placeholder='City' />
|
<Inputcommon placeholder='City' />
|
||||||
<div className={s.line}>
|
<div className={s.line}>
|
||||||
<SelectCommon option={STATE_OPTIONS} type="custom" size="large" placeholder='State'/>
|
<SelectCommon options={STATE_OPTIONS} type="custom" size="large" placeholder='State'/>
|
||||||
<Inputcommon placeholder='Zip code' />
|
<Inputcommon placeholder='Zip code' />
|
||||||
</div>
|
</div>
|
||||||
<Inputcommon placeholder='Phone (delivery contact)' />
|
<Inputcommon placeholder='Phone (delivery contact)' />
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
.productCardSkeleton {
|
||||||
|
max-width: 22.4rem;
|
||||||
|
min-width: 20rem;
|
||||||
|
margin: 0 0.8rem;
|
||||||
|
> div {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
margin-top: 1.6rem;
|
||||||
|
> div {
|
||||||
|
margin: 0;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
import SkeletonImage from "../SkeletonCommon/SkeletonImage/SkeletonImage"
|
||||||
|
import SkeletonParagraph from "../SkeletonCommon/SkeletonParagraph/SkeletonParagraph"
|
||||||
|
import s from './ProductCardSkeleton.module.scss'
|
||||||
|
|
||||||
|
const ProductCardSkeleton = ({ }) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={s.productCardSkeleton}>
|
||||||
|
<SkeletonImage />
|
||||||
|
<div className={s.content}>
|
||||||
|
<SkeletonParagraph rows={3} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductCardSkeleton
|
@@ -1,11 +1,28 @@
|
|||||||
.wrapper{
|
.wrapper {
|
||||||
.list{
|
margin: 4rem auto;
|
||||||
// max-width: 109.4rem;
|
.list {
|
||||||
@apply flex flex-wrap justify-around;
|
@apply grid grid-cols-2;
|
||||||
|
@screen md {
|
||||||
|
@apply grid-cols-3;
|
||||||
|
}
|
||||||
|
@screen lg {
|
||||||
|
@apply grid-cols-4;
|
||||||
|
}
|
||||||
|
@screen xl {
|
||||||
|
@apply grid-cols-6;
|
||||||
|
}
|
||||||
|
.empty {
|
||||||
|
button {
|
||||||
|
margin-top: 1.6rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.pagination{
|
.pagination {
|
||||||
padding-top: 4.8rem;
|
padding-top: 4.8rem;
|
||||||
// max-width: 109.4rem;
|
@apply flex justify-center items-center;
|
||||||
@apply flex justify-center items-center ;
|
&.hide {
|
||||||
|
@apply hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +1,50 @@
|
|||||||
import React, { useState } from 'react'
|
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 PaginationCommon from '../PaginationCommon/PaginationCommon'
|
||||||
import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard'
|
import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard'
|
||||||
import s from "./ProductList.module.scss"
|
import s from "./ProductList.module.scss"
|
||||||
|
|
||||||
interface ProductListProps {
|
interface ProductListProps {
|
||||||
data: ProductCardProps[]
|
data: ProductCardProps[],
|
||||||
|
total?: number,
|
||||||
|
defaultCurrentPage?: number
|
||||||
|
onPageChange?: (page: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductList = ({data}: ProductListProps) => {
|
const ProductList = ({ data, total = data.length, defaultCurrentPage, onPageChange }: ProductListProps) => {
|
||||||
const [currentPage, setCurrentPage] = useState(0)
|
const router = useRouter()
|
||||||
const onPageChange = (page:number) => {
|
const handlePageChange = (page: number) => {
|
||||||
setCurrentPage(page)
|
onPageChange && onPageChange(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleShowAllProduct = () => {
|
||||||
|
router.push({
|
||||||
|
pathname: ROUTE.PRODUCTS,
|
||||||
|
},
|
||||||
|
undefined, { shallow: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={s.wrapper}>
|
<div className={s.wrapper}>
|
||||||
<div className={s.list}>
|
<div className={s.list}>
|
||||||
{
|
{
|
||||||
data.slice(currentPage*20,(currentPage+1)*20).map((product,index)=>{
|
data.map((product, index) => {
|
||||||
return <ProductCard {...product} key={index}/>
|
return <ProductCard {...product} key={index} />
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
data.length === 0 && <div className={s.empty}>
|
||||||
|
<EmptyCommon />
|
||||||
|
<ButtonCommon onClick={handleShowAllProduct}>Show all products</ButtonCommon>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={s.pagination}>
|
<div className={classNames(s.pagination, { [s.hide]: data.length === 0 })}>
|
||||||
<PaginationCommon total={data.length} pageSize={20} onChange={onPageChange}/>
|
<PaginationCommon defaultCurrent={defaultCurrentPage} total={total} pageSize={DEFAULT_PAGE_SIZE} onChange={handlePageChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@@ -1,24 +1,33 @@
|
|||||||
import s from './SelectCommon.module.scss'
|
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { IconVectorDown } from 'src/components/icons'
|
import { IconVectorDown } from 'src/components/icons'
|
||||||
|
import s from './SelectCommon.module.scss'
|
||||||
import SelectOption from './SelectOption/SelectOption'
|
import SelectOption from './SelectOption/SelectOption'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
placeholder? : string,
|
placeholder? : string,
|
||||||
|
value?: string,
|
||||||
size?: 'base' | 'large',
|
size?: 'base' | 'large',
|
||||||
type?: 'default' | 'custom',
|
type?: 'default' | 'custom',
|
||||||
option: {name: string, value: string}[],
|
options: {name: string, value: string}[],
|
||||||
onChange?: (value: string) => void,
|
onChange?: (value: string) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectCommon = ({ type = 'default', size = 'base', option, placeholder, onChange}: Props) => {
|
const SelectCommon = ({ value, type = 'default', size = 'base', options, placeholder, onChange}: Props) => {
|
||||||
const [selectedName, setSelectedName] = useState(placeholder)
|
const [selectedName, setSelectedName] = useState<string>()
|
||||||
const [selectedValue, setSelectedValue] = useState('')
|
const [selectedValue, setSelectedValue] = useState<string>('')
|
||||||
|
|
||||||
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)
|
setSelectedValue(value)
|
||||||
setSelectedName(item)
|
const name = options.find(item => item.value === value)?.name
|
||||||
|
setSelectedName(name)
|
||||||
onChange && onChange(value)
|
onChange && onChange(value)
|
||||||
}
|
}
|
||||||
return(
|
return(
|
||||||
@@ -33,7 +42,7 @@ const SelectCommon = ({ type = 'default', size = 'base', option, placeholder, on
|
|||||||
[s.selectTrigger] : true,
|
[s.selectTrigger] : true,
|
||||||
|
|
||||||
})}
|
})}
|
||||||
>{selectedName}<IconVectorDown /></div>
|
>{selectedName || placeholder}<IconVectorDown /></div>
|
||||||
|
|
||||||
<div className={s.hoverWrapper}>
|
<div className={s.hoverWrapper}>
|
||||||
<div className={classNames({
|
<div className={classNames({
|
||||||
@@ -43,8 +52,12 @@ const SelectCommon = ({ type = 'default', size = 'base', option, placeholder, on
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
option.map(item =>
|
options.map(item =>
|
||||||
<SelectOption key={item.value} itemName={item.name} value={item.value} onClick={changeSelectedName} size={size} selected={(selectedValue === item.value)} />
|
<SelectOption key={item.value}
|
||||||
|
itemName={item.name}
|
||||||
|
value={item.value}
|
||||||
|
onChange={changeSelectedName}
|
||||||
|
size={size} selected={(selectedValue === item.value)} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,16 +2,16 @@ import s from './SelectOption.module.scss'
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
interface Props{
|
interface Props{
|
||||||
onClick: (name: string, value: string) => void,
|
onChange: (value: string) => void,
|
||||||
itemName: string,
|
itemName: string,
|
||||||
size: 'base' | 'large',
|
size: 'base' | 'large',
|
||||||
value: string,
|
value: string,
|
||||||
selected?: boolean,
|
selected?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectOption = ({onClick, itemName, size, value, selected} : Props) => {
|
const SelectOption = ({onChange, itemName, size, value, selected} : Props) => {
|
||||||
const changeName = () => {
|
const handleChange = () => {
|
||||||
onClick(itemName, value)
|
onChange(value)
|
||||||
}
|
}
|
||||||
return(
|
return(
|
||||||
<div className={classNames({
|
<div className={classNames({
|
||||||
@@ -19,7 +19,7 @@ const SelectOption = ({onClick, itemName, size, value, selected} : Props) => {
|
|||||||
[s[size]] : !!size,
|
[s[size]] : !!size,
|
||||||
[s.isChoose] : selected ,
|
[s.isChoose] : selected ,
|
||||||
})}
|
})}
|
||||||
onClick = {changeName}
|
onClick = {handleChange}
|
||||||
>{itemName}</div>
|
>{itemName}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
@import '../../../../styles/utilities';
|
@import '../../../../styles/utilities';
|
||||||
|
|
||||||
.skeletonImage {
|
.skeletonImage {
|
||||||
@apply relative;
|
@apply relative overflow-hidden;
|
||||||
background: #DDDBDD;
|
background: #DDDBDD;
|
||||||
|
|
||||||
&.small {
|
&.small {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
@import '../../../../styles/utilities';
|
@import '../../../../styles/utilities';
|
||||||
|
|
||||||
.skeletonParagraph {
|
.skeletonParagraph {
|
||||||
|
@apply overflow-hidden;
|
||||||
margin: 0 1.6rem;
|
margin: 0 1.6rem;
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
|
@@ -51,5 +51,6 @@ export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout'
|
|||||||
export { default as InputPasswordFiledInForm} from './InputPasswordFiledInForm/InputPasswordFiledInForm'
|
export { default as InputPasswordFiledInForm} from './InputPasswordFiledInForm/InputPasswordFiledInForm'
|
||||||
export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm'
|
export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm'
|
||||||
export { default as MessageCommon} from './MessageCommon/MessageCommon'
|
export { default as MessageCommon} from './MessageCommon/MessageCommon'
|
||||||
|
export { default as ProductCardSkeleton} from './ProductCardSkeleton/ProductCardSkeleton'
|
||||||
|
export { default as ListProductCardSkeleton} from './ListProductCardSkeleton/ListProductCardSkeleton'
|
||||||
|
|
||||||
|
@@ -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<filterContextType>(contextDefaultValues);
|
|
||||||
|
|
||||||
export function useAuth() {
|
|
||||||
return useContext(FilterContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
type FilterProviderProps = {
|
|
||||||
children: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function FilterProvider({ children }: FilterProviderProps) {
|
|
||||||
const [visible, setVisible] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const open = () => {
|
|
||||||
setVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
setVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const value = {
|
|
||||||
visible,
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FilterContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</FilterContext.Provider>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -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<ProductFilterContextType>(DEFAULT_VALUE)
|
||||||
|
|
||||||
|
export function useProductFilter() {
|
||||||
|
return useContext(ProductFilterContext);
|
||||||
|
}
|
@@ -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<boolean>(false);
|
||||||
|
|
||||||
|
const closeProductFilter = () => {
|
||||||
|
setVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openProductFilter = () => {
|
||||||
|
setVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleProductFilter = () => {
|
||||||
|
setVisible(!visible);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProductFilterContext.Provider value={{productFilterVisible: visible, closeProductFilter, openProductFilter, toggleProductFilter}}>
|
||||||
|
{children}
|
||||||
|
</ProductFilterContext.Provider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@@ -3,3 +3,6 @@ export * from './CartDrawer/CartDrawerProvider'
|
|||||||
|
|
||||||
export * from './Message/MessageContext'
|
export * from './Message/MessageContext'
|
||||||
export * from './Message/MessageProvider'
|
export * from './Message/MessageProvider'
|
||||||
|
|
||||||
|
export * from './ProductFilter/ProductFilterContext'
|
||||||
|
export * from './ProductFilter/ProductFilterProvider'
|
||||||
|
@@ -5,8 +5,8 @@ import useSWR from 'swr';
|
|||||||
|
|
||||||
|
|
||||||
const useGetAllCollection = () => {
|
const useGetAllCollection = () => {
|
||||||
const { data, ...rest } = useSWR<GetCollectionsQuery>([getCollectionsNameQuery], gglFetcher)
|
const { data, isValidating, ...rest } = useSWR<GetCollectionsQuery>([getCollectionsNameQuery], gglFetcher)
|
||||||
return { collections: data?.collections, ...rest }
|
return { collections: data?.collections.items || [], loading: isValidating, ...rest }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useGetAllCollection;
|
export default useGetAllCollection;
|
@@ -5,7 +5,7 @@ import useSWR from 'swr'
|
|||||||
|
|
||||||
const useFacets = (options?: QueryFacetsArgs) => {
|
const useFacets = (options?: QueryFacetsArgs) => {
|
||||||
const { data, isValidating, ...rest } = useSWR<GetAllFacetsQuery>([getAllFacetsQuery, options], gglFetcher)
|
const { data, isValidating, ...rest } = useSWR<GetAllFacetsQuery>([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
|
export default useFacets
|
||||||
|
3
src/components/hooks/product/index.ts
Normal file
3
src/components/hooks/product/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as useSearchProducts } from './useSearchProducts'
|
||||||
|
|
||||||
|
|
13
src/components/hooks/product/useSearchProducts.tsx
Normal file
13
src/components/hooks/product/useSearchProducts.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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>([getAllProductsQuery, options], gglFetcher)
|
||||||
|
|
||||||
|
return { products: data?.search.items.map((item) => normalizeSearchResult(item)), totalItems: data?.search.totalItems, loading: isValidating, ...rest }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSearchProducts
|
@@ -43,7 +43,7 @@ const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoMod
|
|||||||
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className={s.inputState}>
|
<div className={s.inputState}>
|
||||||
<SelectCommon type="custom" placeholder="State" option={states} />
|
<SelectCommon type="custom" placeholder="State" options={states} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={s.inputPostalCode}>
|
<div className={s.inputPostalCode}>
|
||||||
|
@@ -46,7 +46,7 @@ const ShippingInfoForm = ({onConfirm,id}: ShippingInfoFormProps) => {
|
|||||||
/>
|
/>
|
||||||
<Inputcommon type="text" placeholder="City" ref={cityRef} />
|
<Inputcommon type="text" placeholder="City" ref={cityRef} />
|
||||||
<div className={s.line}>
|
<div className={s.line}>
|
||||||
<SelectCommon option={option} type="custom" size="large">State</SelectCommon>
|
<SelectCommon options={option} type="custom" size="large">State</SelectCommon>
|
||||||
<Inputcommon type="text" placeholder="Zip Code" ref={codeRef} />
|
<Inputcommon type="text" placeholder="Zip Code" ref={codeRef} />
|
||||||
</div>
|
</div>
|
||||||
<Inputcommon
|
<Inputcommon
|
||||||
|
@@ -13,72 +13,59 @@
|
|||||||
@screen md {
|
@screen md {
|
||||||
@apply flex;
|
@apply flex;
|
||||||
}
|
}
|
||||||
.categories{
|
|
||||||
padding-right: 2.4rem;
|
|
||||||
@apply hidden;
|
|
||||||
@screen md {
|
|
||||||
@apply block;
|
|
||||||
}
|
|
||||||
@screen xl{
|
|
||||||
@apply block;
|
|
||||||
width:25%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.list{
|
.list{
|
||||||
@screen md {
|
@apply w-full;
|
||||||
@apply flex justify-between flex-wrap w-full;
|
.top {
|
||||||
margin: 1rem 0;
|
.left {
|
||||||
}
|
@apply flex justify-between items-center;
|
||||||
@screen xl {
|
|
||||||
width:75%;
|
|
||||||
}
|
|
||||||
.inner{
|
|
||||||
@screen md {
|
|
||||||
@apply flex flex-col items-center justify-center;
|
|
||||||
}
|
}
|
||||||
.boxItem {
|
|
||||||
@screen md {
|
.iconFilter {
|
||||||
@apply flex justify-between flex-wrap;
|
@apply relative;
|
||||||
margin: 1rem 0;
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
filter: brightness(1.05);
|
||||||
}
|
}
|
||||||
.item {
|
&:focus-visible {
|
||||||
@screen md {
|
outline: 2px solid var(--text-active);
|
||||||
width: calc(97% / 2);
|
}
|
||||||
margin-top:1rem;
|
.dot {
|
||||||
}
|
@apply absolute;
|
||||||
@screen lg{
|
top: -0.08rem;
|
||||||
width: calc(97% / 3);
|
right: -0.2rem;
|
||||||
margin-top:1rem;
|
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;
|
||||||
|
.iconFilter {
|
||||||
|
@apply hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.boxSelect{
|
.boxSelect{
|
||||||
@apply w-auto;
|
@apply w-auto;
|
||||||
// padding: 2.5rem 0;
|
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
@screen xl {
|
@screen md {
|
||||||
@apply block;
|
@apply block;
|
||||||
width: auto;
|
width: auto;
|
||||||
padding:0;
|
padding:0;
|
||||||
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
.categorySelectCate{
|
|
||||||
@screen xl {
|
label {
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
label{
|
|
||||||
@apply font-bold topline ;
|
@apply font-bold topline ;
|
||||||
color:var(--text-active);
|
color:var(--text-active);
|
||||||
@screen xl {
|
@screen xl {
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.select{
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,25 @@
|
|||||||
import React from 'react'
|
import { ProductCard } from '@commerce/types/product'
|
||||||
import { HeadingCommon, ProductList, SelectCommon } from 'src/components/common'
|
import { Collection, Facet, FacetValue, QuerySearchArgs } from '@framework/schema'
|
||||||
|
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 BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon'
|
||||||
import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation'
|
import { useProductFilter } from 'src/components/contexts'
|
||||||
import { BRAND, CATEGORY, FEATURED} from 'src/utils/constanst.utils'
|
import { useSearchProducts } from 'src/components/hooks/product'
|
||||||
import { PRODUCT_DATA_TEST_PAGE } from 'src/utils/demo-data'
|
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'
|
import s from './ProductListFilter.module.scss'
|
||||||
|
import ProductsMenuNavigationTablet from './ProductsMenuNavigationTablet/ProductsMenuNavigationTablet'
|
||||||
|
import ProductSort from './ProductSort/ProductSort'
|
||||||
|
|
||||||
interface ProductListFilterProps {}
|
interface ProductListFilterProps {
|
||||||
|
facets: Facet[]
|
||||||
|
collections: Collection[]
|
||||||
|
products: ProductCard[]
|
||||||
|
total: number
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const BREADCRUMB = [
|
const BREADCRUMB = [
|
||||||
{
|
{
|
||||||
@@ -14,49 +27,98 @@ const BREADCRUMB = [
|
|||||||
link: `#`,
|
link: `#`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const OPTIONSLECT = [
|
|
||||||
{
|
|
||||||
name: 'Most Viewed',
|
|
||||||
value: 'most-viewed',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Lastest Products',
|
|
||||||
value: 'lastest-products',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Recent Products',
|
|
||||||
value: 'recent-products',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const onModalClose = () => {
|
|
||||||
|
const DEFAULT_SEARCH_ARGS = {
|
||||||
|
groupByProduct: true, take: DEFAULT_PAGE_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductListFilter = (props: ProductListFilterProps) => {
|
const ProductListFilter = ({ facets, collections, products, total }: ProductListFilterProps) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const { openProductFilter } = useProductFilter()
|
||||||
|
const [initialQueryFlag, setInitialQueryFlag] = useState<boolean>(true)
|
||||||
|
const [optionQueryProduct, setOptionQueryProduct] = useState<QuerySearchArgs>({ 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
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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 (
|
return (
|
||||||
<div className={s.warpper}>
|
<div className={s.warpper}>
|
||||||
<div className={s.breadcrumb}>
|
<div className={s.breadcrumb}>
|
||||||
<BreadcrumbCommon crumbs={BREADCRUMB} />
|
<BreadcrumbCommon crumbs={BREADCRUMB} />
|
||||||
</div>
|
</div>
|
||||||
<div className={s.main}>
|
<div className={s.main}>
|
||||||
<div className={s.categories}>
|
<ProductsMenuNavigationTablet facets={facets} collections={collections} />
|
||||||
<MenuNavigation categories={CATEGORY} heading="Categories" />
|
|
||||||
<MenuNavigation categories={BRAND} heading="Brands" />
|
|
||||||
<MenuNavigation categories={FEATURED} heading="featured" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={s.list}>
|
<div className={s.list}>
|
||||||
<HeadingCommon align="left">SPECIAL RECIPES</HeadingCommon>
|
<div className={s.top}>
|
||||||
|
<div className={s.left}>
|
||||||
|
<HeadingCommon align="left">SPECIAL RECIPES</HeadingCommon>
|
||||||
|
<button className={s.iconFilter} onClick={openProductFilter}>
|
||||||
|
<IconFilter />
|
||||||
|
<div className={s.dot}></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={s.boxSelect}>
|
<div className={s.boxSelect}>
|
||||||
<div className={s.categorySelectSort}>
|
<ProductSort />
|
||||||
<div className={s.select}>
|
|
||||||
<SelectCommon option={OPTIONSLECT} placeholder="Sort By" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProductList data={PRODUCT_DATA_TEST_PAGE} />
|
{
|
||||||
|
(!initialQueryFlag && loading && !productSearchResult) && <ListProductCardSkeleton count={DEFAULT_PAGE_SIZE} isWrap />
|
||||||
|
}
|
||||||
|
<ProductList data={initialQueryFlag ? products : (productSearchResult || [])} total={totalItems !== undefined ? totalItems : total} onPageChange={onPageChange} defaultCurrentPage={currentPage} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -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<string>();
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<SelectCommon
|
||||||
|
options={OPTIONS_SORT_PRODUCT}
|
||||||
|
placeholder="Sort By"
|
||||||
|
value={sortValue}
|
||||||
|
onChange={onSortChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductSort;
|
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
.productsMenuNavigationTablet {
|
||||||
|
@apply hidden;
|
||||||
|
@screen md {
|
||||||
|
@apply block;
|
||||||
|
padding-right: 2.4rem;
|
||||||
|
}
|
||||||
|
@screen xl {
|
||||||
|
@apply block;
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,32 @@
|
|||||||
|
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 (
|
||||||
|
<div className={s.productsMenuNavigationTablet}>
|
||||||
|
<MenuNavigation
|
||||||
|
heading="Categories"
|
||||||
|
categories={collections}
|
||||||
|
queryKey={QUERY_KEY.CATEGORY}
|
||||||
|
isSingleSelect={true} />
|
||||||
|
{
|
||||||
|
facets.map(item => <MenuNavigation
|
||||||
|
key={item.id}
|
||||||
|
queryKey={item.code}
|
||||||
|
categories={item.values}
|
||||||
|
heading={item.name} />)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductsMenuNavigationTablet
|
@@ -189,13 +189,13 @@ const RecipesList = ({ data =recipe}:Props) => {
|
|||||||
<div className={s.categorySelectCate}>
|
<div className={s.categorySelectCate}>
|
||||||
<label htmlFor="">Categories</label>
|
<label htmlFor="">Categories</label>
|
||||||
<div className={s.select}>
|
<div className={s.select}>
|
||||||
<SelectCommon option={CATEGORYSELECT} placeholder="Categories"/>
|
<SelectCommon options={CATEGORYSELECT} placeholder="Categories"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.categorySelectSort}>
|
<div className={s.categorySelectSort}>
|
||||||
<label htmlFor="" >Sort By</label>
|
<label htmlFor="" >Sort By</label>
|
||||||
<div className={s.select}>
|
<div className={s.select}>
|
||||||
<SelectCommon option={OPTIONSLECT} placeholder="Sort By" />
|
<SelectCommon options={OPTIONSLECT} placeholder="Sort By" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
:root {
|
:root {
|
||||||
--primary: #5b9a74;
|
--primary: #5b9a74;
|
||||||
--primary-light: #e3f2e9;
|
--primary-light: #e3f2e9;
|
||||||
--primary-lightest: #effaf4;
|
--primary-lightest: #F1F8F4;
|
||||||
|
|
||||||
--info-dark: #00317a;
|
--info-dark: #00317a;
|
||||||
--info: #3468B7;
|
--info: #3468B7;
|
||||||
|
@@ -45,13 +45,23 @@ export const LOCAL_STORAGE_KEY = {
|
|||||||
TOKEN: 'token'
|
TOKEN: 'token'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const QUERY_SPLIT_SEPERATOR = ','
|
||||||
export const QUERY_KEY = {
|
export const QUERY_KEY = {
|
||||||
TAB: 'tab',
|
TAB: 'tab',
|
||||||
CATEGORY: 'category',
|
CATEGORY: 'category',
|
||||||
BRAND: 'brand',
|
BRAND: 'brand',
|
||||||
FEATURED: 'feature',
|
FEATURED: 'featured',
|
||||||
SORTBY: 'sortby',
|
SORTBY: 'sortby',
|
||||||
RECIPES: 'recipes'
|
RECIPES: 'recipes',
|
||||||
|
PAGE: 'page',
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
export enum ProductFeature {
|
||||||
@@ -113,10 +123,31 @@ export const BRAND = [
|
|||||||
|
|
||||||
export const CODE_FACET_FEATURED = 'featured'
|
export const CODE_FACET_FEATURED = 'featured'
|
||||||
export const CODE_FACET_DISCOUNT = 'discount'
|
export const CODE_FACET_DISCOUNT = 'discount'
|
||||||
|
export const CODE_FACET_BRAND = 'brand'
|
||||||
export const CODE_FACET_FEATURED_VARIANT = {
|
export const CODE_FACET_FEATURED_VARIANT = {
|
||||||
FRESH: 'fresh',
|
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 = [
|
export const FEATURED = [
|
||||||
{
|
{
|
||||||
name: 'Best Sellers',
|
name: 'Best Sellers',
|
||||||
|
@@ -1,12 +1,60 @@
|
|||||||
import { Facet } from "@commerce/types/facet";
|
import { Facet } from "@commerce/types/facet";
|
||||||
import { FacetValue } from './../../framework/vendure/schema.d';
|
import { FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d';
|
||||||
import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT } from "./constanst.utils";
|
import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils";
|
||||||
import { PromiseWithKey } from "./types.utils";
|
import { PromiseWithKey, SortOrder } from "./types.utils";
|
||||||
|
|
||||||
export function isMobile() {
|
export function isMobile() {
|
||||||
return window.innerWidth < 768
|
return window.innerWidth < 768
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPageFromQuery(pageQuery: string) {
|
||||||
|
let page = 0
|
||||||
|
try {
|
||||||
|
page = +pageQuery
|
||||||
|
if (isNaN(page)) {
|
||||||
|
page = 0
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
page = 0
|
||||||
|
}
|
||||||
|
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<T>(arr: Array<T>, value: T): Array<T> {
|
export function removeItem<T>(arr: Array<T>, value: T): Array<T> {
|
||||||
const index = arr.indexOf(value);
|
const index = arr.indexOf(value);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@@ -58,6 +106,16 @@ export function getFacetNamesFromIds(facets: FacetValue[], ids?: string[]): stri
|
|||||||
return names.join(", ")
|
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)
|
return promies.map(item => item.promise)
|
||||||
}
|
}
|
||||||
|
@@ -34,18 +34,23 @@ export interface BlogProps {
|
|||||||
|
|
||||||
export interface CheckOutForm {
|
export interface CheckOutForm {
|
||||||
name?: string
|
name?: string
|
||||||
email?:string
|
email?: string
|
||||||
address?: string
|
address?: string
|
||||||
city?:string
|
city?: string
|
||||||
state?:string
|
state?: string
|
||||||
code?:number
|
code?: number
|
||||||
phone?:number
|
phone?: number
|
||||||
method?:string
|
method?: string
|
||||||
shipping_fee?:number
|
shipping_fee?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MouseAndTouchEvent = MouseEvent | TouchEvent
|
export type MouseAndTouchEvent = MouseEvent | TouchEvent
|
||||||
|
|
||||||
|
export enum SortOrder {
|
||||||
|
Asc = 'ASC',
|
||||||
|
Desc = 'DESC',
|
||||||
|
}
|
||||||
|
|
||||||
export type filterContextType = {
|
export type filterContextType = {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
open: () => void;
|
open: () => void;
|
||||||
|
Reference in New Issue
Block a user