mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
✨ feat: product filter provider
:%s
This commit is contained in:
@@ -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,11 +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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = memo(({ toggleFilter }: 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 })
|
||||||
|
@@ -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>
|
||||||
)
|
)
|
||||||
|
@@ -15,21 +15,12 @@ interface Props {
|
|||||||
|
|
||||||
const LayoutContent: FC<Props> = ({ children }) => {
|
const LayoutContent: FC<Props> = ({ children }) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false })
|
|
||||||
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}/>
|
<Header/>
|
||||||
{
|
{
|
||||||
router.pathname === ROUTE.ACCOUNT ?
|
router.pathname === ROUTE.ACCOUNT ?
|
||||||
<section className={s.wrapperWithBg}>
|
<section className={s.wrapperWithBg}>
|
||||||
@@ -39,7 +30,7 @@ const LayoutContent: FC<Props> = ({ children }) => {
|
|||||||
}
|
}
|
||||||
<ScrollToTop visibilityHeight={1500} />
|
<ScrollToTop visibilityHeight={1500} />
|
||||||
{
|
{
|
||||||
FILTER_PAGE.includes(router.pathname) && (<div className={s.filter}><MenuNavigationProductList visible={visibleFilter} onClose={closeFilter} /> </div>)
|
FILTER_PAGE.includes(router.pathname) && (<div className={s.filter}><MenuNavigationProductList /> </div>)
|
||||||
}
|
}
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,6 +3,7 @@ import classNames from 'classnames';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { ButtonCommon } from 'src/components/common';
|
import { ButtonCommon } from 'src/components/common';
|
||||||
|
import { useProductFilter } from 'src/components/contexts';
|
||||||
import { useGetAllCollection } from 'src/components/hooks/collection';
|
import { useGetAllCollection } from 'src/components/hooks/collection';
|
||||||
import { useFacets } from 'src/components/hooks/facets';
|
import { useFacets } from 'src/components/hooks/facets';
|
||||||
import IconHide from 'src/components/icons/IconHide';
|
import IconHide from 'src/components/icons/IconHide';
|
||||||
@@ -15,8 +16,7 @@ import s from './MenuNavigationProductList.module.scss';
|
|||||||
import MenuSort from './MenuSort/MenuSort';
|
import MenuSort from './MenuSort/MenuSort';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean,
|
|
||||||
onClose: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FACET_QUERY = {
|
const FACET_QUERY = {
|
||||||
@@ -32,8 +32,9 @@ const FACET_QUERY = {
|
|||||||
}
|
}
|
||||||
} as QueryFacetsArgs
|
} as QueryFacetsArgs
|
||||||
|
|
||||||
const MenuNavigationProductList = ({ visible, onClose }: Props) => {
|
const MenuNavigationProductList = ({}: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { productFilterVisible: visible, closeProductFilter: onClose } = useProductFilter()
|
||||||
const { facets, loading: facetsLoading } = useFacets(FACET_QUERY)
|
const { facets, loading: facetsLoading } = useFacets(FACET_QUERY)
|
||||||
const { collections, loading: collectionLoading } = useGetAllCollection()
|
const { collections, loading: collectionLoading } = useGetAllCollection()
|
||||||
const [brandQuery, setBrandQuery] = useState<string[]>([])
|
const [brandQuery, setBrandQuery] = useState<string[]>([])
|
||||||
|
@@ -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'
|
||||||
|
@@ -16,29 +16,34 @@
|
|||||||
.list{
|
.list{
|
||||||
@apply w-full;
|
@apply w-full;
|
||||||
.top {
|
.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 {
|
@screen md {
|
||||||
@apply flex justify-between flex-wrap w-full;
|
@apply flex justify-between flex-wrap w-full;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
.iconFilter {
|
||||||
}
|
@apply hidden;
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,9 @@ import { useRouter } from 'next/router'
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { HeadingCommon, ListProductCardSkeleton, ProductList } from 'src/components/common'
|
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 SkeletonImage from 'src/components/common/SkeletonCommon/SkeletonImage/SkeletonImage'
|
import { useProductFilter } from 'src/components/contexts'
|
||||||
import { useSearchProducts } from 'src/components/hooks/product'
|
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 { DEFAULT_PAGE_SIZE, QUERY_KEY, QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils'
|
||||||
import { getFacetIdsFromCodes, getPageFromQuery, getProductSortParamFromQuery } from 'src/utils/funtion.utils'
|
import { getFacetIdsFromCodes, getPageFromQuery, getProductSortParamFromQuery } from 'src/utils/funtion.utils'
|
||||||
import s from './ProductListFilter.module.scss'
|
import s from './ProductListFilter.module.scss'
|
||||||
@@ -34,6 +35,7 @@ const DEFAULT_SEARCH_ARGS = {
|
|||||||
|
|
||||||
const ProductListFilter = ({ facets, collections, products, total }: ProductListFilterProps) => {
|
const ProductListFilter = ({ facets, collections, products, total }: ProductListFilterProps) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { openProductFilter } = useProductFilter()
|
||||||
const [initialQueryFlag, setInitialQueryFlag] = useState<boolean>(true)
|
const [initialQueryFlag, setInitialQueryFlag] = useState<boolean>(true)
|
||||||
const [optionQueryProduct, setOptionQueryProduct] = useState<QuerySearchArgs>({ input: DEFAULT_SEARCH_ARGS })
|
const [optionQueryProduct, setOptionQueryProduct] = useState<QuerySearchArgs>({ input: DEFAULT_SEARCH_ARGS })
|
||||||
const { products: productSearchResult, totalItems, loading } = useSearchProducts(optionQueryProduct)
|
const { products: productSearchResult, totalItems, loading } = useSearchProducts(optionQueryProduct)
|
||||||
@@ -101,14 +103,20 @@ const ProductListFilter = ({ facets, collections, products, total }: ProductList
|
|||||||
<ProductsMenuNavigationTablet facets={facets} collections={collections} />
|
<ProductsMenuNavigationTablet facets={facets} collections={collections} />
|
||||||
<div className={s.list}>
|
<div className={s.list}>
|
||||||
<div className={s.top}>
|
<div className={s.top}>
|
||||||
<HeadingCommon align="left">SPECIAL RECIPES</HeadingCommon>
|
<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}>
|
||||||
<ProductSort />
|
<ProductSort />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
(!initialQueryFlag && loading && !productSearchResult) && <ListProductCardSkeleton count={DEFAULT_PAGE_SIZE} isWrap/>
|
(!initialQueryFlag && loading && !productSearchResult) && <ListProductCardSkeleton count={DEFAULT_PAGE_SIZE} isWrap />
|
||||||
}
|
}
|
||||||
<ProductList data={initialQueryFlag ? products : (productSearchResult || [])} total={totalItems !== undefined ? totalItems : total} onPageChange={onPageChange} defaultCurrentPage={currentPage} />
|
<ProductList data={initialQueryFlag ? products : (productSearchResult || [])} total={totalItems !== undefined ? totalItems : total} onPageChange={onPageChange} defaultCurrentPage={currentPage} />
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user