mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
✨ feat: get collections, facets in menu filter
:%s
This commit is contained in:
5
framework/vendure/schema.d.ts
vendored
5
framework/vendure/schema.d.ts
vendored
@@ -3228,8 +3228,9 @@ export type GetAllFacetsQuery = { __typename?: 'Query' } & {
|
||||
items: Array<
|
||||
{ __typename?: 'Facet' } & Pick<
|
||||
Facet,
|
||||
'id' | 'name' | 'code'
|
||||
> & {
|
||||
'id' | 'name' | 'code' | 'values'
|
||||
>
|
||||
& {
|
||||
parent?: Maybe<{ __typename?: 'Facet' } & Pick<Facet, 'id'>>
|
||||
children?: Maybe<
|
||||
Array<{ __typename?: 'Facet' } & Pick<Facet, 'id'>>
|
||||
|
@@ -42,7 +42,7 @@ const HeaderSubMenu = memo(() => {
|
||||
<ul className={s.menu}>
|
||||
{/* todo: handle active item */}
|
||||
<li>
|
||||
<MenuDropdown options={collections?.items ?? []} align="left">Categories</MenuDropdown>
|
||||
<MenuDropdown options={collections || []} align="left">Categories</MenuDropdown>
|
||||
</li>
|
||||
{
|
||||
MENU.map(item => <li key={item.name}
|
||||
|
@@ -2,7 +2,7 @@ import { useRouter } from 'next/router'
|
||||
import { FC } from 'react'
|
||||
import { useMessage } from 'src/components/contexts'
|
||||
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 Header from '../../Header/Header'
|
||||
import MenuNavigationProductList from '../../MenuNavigationProductList/MenuNavigationProductList'
|
||||
@@ -14,9 +14,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const LayoutContent: FC<Props> = ({ children }) => {
|
||||
const { pathname } = useRouter()
|
||||
const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false })
|
||||
const router = useRouter()
|
||||
const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: true })
|
||||
const {messages, removeMessage} = useMessage()
|
||||
|
||||
const toggleFilter = () => {
|
||||
@@ -30,6 +29,7 @@ const LayoutContent: FC<Props> = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<div className={s.mainLayout}>
|
||||
{router.pathname}
|
||||
<Header toggleFilter={toggleFilter} visibleFilter={visibleFilter} />
|
||||
{
|
||||
router.pathname === ROUTE.ACCOUNT ?
|
||||
@@ -38,10 +38,9 @@ const LayoutContent: FC<Props> = ({ children }) => {
|
||||
</section> :
|
||||
<main>{children}</main>
|
||||
}
|
||||
<div className={s.filter}><MenuNavigationProductList categories={CATEGORY} brands={BRAND} featured={FEATURED} visible={visibleFilter} onClose={closeFilter} /> </div>
|
||||
<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 visible={visibleFilter} onClose={closeFilter} /> </div>)
|
||||
}
|
||||
<Footer />
|
||||
</div>
|
||||
|
@@ -1,9 +1,7 @@
|
||||
@import "../../../styles/utilities";
|
||||
.menuFilterWrapper{
|
||||
@apply spacing-horizontal;
|
||||
|
||||
.menuFilterHeading{
|
||||
@apply sub-headline font-bold ;
|
||||
@apply sub-headline font-bold;
|
||||
color: var(--text-active);
|
||||
font-feature-settings: 'salt' on;
|
||||
margin: 0.8rem 0;
|
||||
|
@@ -2,43 +2,31 @@ import classNames from 'classnames'
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import s from './MenuFilter.module.scss'
|
||||
import MenuFilterItem from './MenuFilterItem/MenuFilterItem';
|
||||
interface Props {
|
||||
children?: any,
|
||||
heading?:string,
|
||||
categories:{name:string,link:string}[],
|
||||
type:string,
|
||||
heading?: string,
|
||||
categories: { name: string, slug?: string, code?: string }[],
|
||||
type: string,
|
||||
onChangeValue?: (value: Object) => void
|
||||
}
|
||||
|
||||
const MenuFilter = ({heading,categories,type,onChangeValue}:Props)=> {
|
||||
const [active, setActive] = useState<string>('');
|
||||
const MenuFilter = ({ heading, categories, type, onChangeValue }: Props) => {
|
||||
function handleClick(value: string) {
|
||||
|
||||
function handleClick(link:string){
|
||||
setActive(link);
|
||||
|
||||
if(active === link){
|
||||
setActive('');
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
|
||||
let href = active?.split("=");
|
||||
const linkValue = href[1];
|
||||
|
||||
onChangeValue && onChangeValue({[type]:linkValue});
|
||||
},[active])
|
||||
|
||||
return (
|
||||
<section className={s.menuFilterWrapper}>
|
||||
<h2 className={s.menuFilterHeading}>{heading}</h2>
|
||||
<ul className={s.menuFilterList}>
|
||||
{
|
||||
categories.map(item => <li key={item.name}>
|
||||
<div onClick={()=> handleClick(item.link)} className={classNames({ [s.active]: item.link === active? true: false })}>
|
||||
{item.name}
|
||||
</div>
|
||||
</li>)
|
||||
categories.map(item => <MenuFilterItem
|
||||
key={item.slug || item.code}
|
||||
name={item.name}
|
||||
value={item.slug || item.code || ''}
|
||||
onClick={handleClick}
|
||||
/>)
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
|
@@ -0,0 +1,16 @@
|
||||
|
||||
.menuFilterItem {
|
||||
margin: 1rem 0;
|
||||
padding: 0;
|
||||
div {
|
||||
padding: 0.8rem 1.6rem;
|
||||
margin-right: 0.8rem;
|
||||
background-color: var(--gray);
|
||||
border-radius: 0.8rem;
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
color: var(--white);
|
||||
background-color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import s from './MenuFilterItem.module.scss';
|
||||
|
||||
interface Props {
|
||||
name: string,
|
||||
value: string,
|
||||
onClick: (value: string) => void
|
||||
}
|
||||
|
||||
const MenuFilterItem = ({ name, value, onClick }: Props) => {
|
||||
const [isSelected, setIsSelected] = useState(false)
|
||||
|
||||
function handleClick() {
|
||||
// todo
|
||||
setIsSelected(!isSelected)
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={s.menuFilterItem}>
|
||||
<div onClick={handleClick} className={classNames({ [s.active]: isSelected })}>
|
||||
{name}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
export default MenuFilterItem
|
@@ -1,13 +1,5 @@
|
||||
@import "../../../styles/utilities";
|
||||
.menuNavigationProductListDesktop{
|
||||
@screen sm {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
@screen xl {
|
||||
@apply block;
|
||||
}
|
||||
}
|
||||
.menuNavigationProductListMobile{
|
||||
@apply relative transition-all duration-100;
|
||||
&.isShow{
|
||||
@@ -37,7 +29,7 @@
|
||||
transform: translateY(0%)
|
||||
}
|
||||
.content{
|
||||
@apply absolute w-full h-full;
|
||||
@apply absolute w-full h-full spacing-horizontal custom-scroll;
|
||||
margin-top: 3rem;
|
||||
padding-top: 10rem ;
|
||||
padding-bottom: 10rem;
|
||||
@@ -46,6 +38,7 @@
|
||||
height: 96%;
|
||||
bottom: 0;
|
||||
border-radius: 2.4rem 2.4rem 0 0;
|
||||
|
||||
.head{
|
||||
@apply flex justify-between fixed;
|
||||
top:0;
|
||||
@@ -57,12 +50,11 @@
|
||||
background-color: white;
|
||||
z-index: 10000;
|
||||
h3{
|
||||
@apply heading-3 font-bold;
|
||||
color:var(--text-base);
|
||||
@apply heading-3 font-heading;
|
||||
}
|
||||
}
|
||||
.foot{
|
||||
@apply fixed;
|
||||
@apply fixed text-center;
|
||||
bottom: 0;
|
||||
left:0;
|
||||
width: 100%;
|
||||
@@ -70,7 +62,7 @@
|
||||
padding: 0 1rem 3rem 1rem;
|
||||
|
||||
}
|
||||
button{
|
||||
button {
|
||||
margin-top: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -1,56 +1,78 @@
|
||||
import { QueryFacetsArgs } from '@framework/schema';
|
||||
import classNames from 'classnames';
|
||||
import React, { useState } from 'react';
|
||||
import {ButtonCommon} from 'src/components/common';
|
||||
import { ButtonCommon } from 'src/components/common';
|
||||
import { useGetAllCollection } from 'src/components/hooks/collection';
|
||||
import { useFacets } from 'src/components/hooks/facets';
|
||||
import IconHide from 'src/components/icons/IconHide';
|
||||
import { CODE_FACET_BRAND, CODE_FACET_FEATURED, QUERY_KEY } from 'src/utils/constanst.utils';
|
||||
import { LANGUAGE } from 'src/utils/language.utils';
|
||||
import { SortOrder } from 'src/utils/types.utils';
|
||||
import MenuFilter from '../MenuFilter/MenuFilter';
|
||||
import SkeletonParagraph from '../SkeletonCommon/SkeletonParagraph/SkeletonParagraph';
|
||||
import s from './MenuNavigationProductList.module.scss';
|
||||
import MenuSort from './MenuSort/MenuSort';
|
||||
import {LANGUAGE} from 'src/utils/language.utils';
|
||||
import classNames from 'classnames'
|
||||
import MenuFilter from '../MenuFilter/MenuFilter';
|
||||
import MenuNavigation from '../MenuNavigation/MenuNavigation';
|
||||
import IconHide from 'src/components/icons/IconHide';
|
||||
|
||||
interface Props{
|
||||
categories:{name:string,link:string}[],
|
||||
brands:{name:string,link:string}[],
|
||||
featured:{name:string,link:string}[],
|
||||
interface Props {
|
||||
visible: boolean,
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const MenuNavigationProductList = ({categories,brands,featured,visible,onClose}:Props)=>{
|
||||
|
||||
const [dataSort,setDataSort] = useState({});
|
||||
|
||||
function handleValue(value:Object){
|
||||
setDataSort({...dataSort,...value});
|
||||
const FACET_QUERY = {
|
||||
options: {
|
||||
sort: {
|
||||
code: SortOrder.Asc
|
||||
},
|
||||
filter: {
|
||||
code: {
|
||||
in: [CODE_FACET_FEATURED, CODE_FACET_BRAND]
|
||||
}
|
||||
function filter(){
|
||||
}
|
||||
}
|
||||
} as QueryFacetsArgs
|
||||
|
||||
const MenuNavigationProductList = ({ visible, onClose }: Props) => {
|
||||
const { facets, loading: facetsLoading } = useFacets(FACET_QUERY)
|
||||
const { collections, loading: collectionLoading } = useGetAllCollection()
|
||||
|
||||
const [dataSort, setDataSort] = useState({});
|
||||
|
||||
function handleValue(value: Object) {
|
||||
setDataSort({ ...dataSort, ...value });
|
||||
}
|
||||
function filter() {
|
||||
// console.log(dataSort)
|
||||
}
|
||||
return(
|
||||
<>
|
||||
<div className={s.menuNavigationProductListDesktop}>
|
||||
<MenuNavigation categories={categories} heading="Categories"/>
|
||||
<MenuNavigation categories={brands} heading="Brands"/>
|
||||
<MenuNavigation categories={featured} heading="Featured"/>
|
||||
</div>
|
||||
<div className={classNames({ [s.menuNavigationProductListMobile] :true,[s.isShow]: visible})}>
|
||||
<div className={classNames({ [s.menuNavigationProductModal] :true,[s.animation]: visible})}>
|
||||
|
||||
|
||||
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 onClick={onClose}><IconHide /></div>
|
||||
</div>
|
||||
<MenuFilter categories={categories} heading="Categories" type="category" onChangeValue={handleValue}/>
|
||||
<MenuFilter categories={brands} heading="Brand" type="brand" onChangeValue={handleValue}/>
|
||||
<MenuFilter categories={featured} heading="Featured" type="featured" onChangeValue={handleValue}/>
|
||||
<MenuSort heading="SORT BY" type="sort" onChangeValue={handleValue}/>
|
||||
{collectionLoading && <SkeletonParagraph rows={5} />}
|
||||
<MenuFilter categories={collections} heading="Categories" type={QUERY_KEY.CATEGORY} onChangeValue={handleValue} />
|
||||
{facetsLoading && <>
|
||||
<SkeletonParagraph rows={5} />
|
||||
<SkeletonParagraph rows={5} />
|
||||
</>}
|
||||
{
|
||||
facets?.map(item => <MenuFilter
|
||||
key={item.id}
|
||||
type={item.code}
|
||||
categories={item.values}
|
||||
heading={item.name} />)
|
||||
}
|
||||
<MenuSort heading="SORT BY" type="sort" onChangeValue={handleValue} />
|
||||
<div className={s.foot}>
|
||||
<ButtonCommon size="large" onClick={filter}>{LANGUAGE.BUTTON_LABEL.CONFIRM}</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,8 @@
|
||||
@import "../../../../styles/utilities";
|
||||
.menuSortWrapper{
|
||||
@apply spacing-horizontal;
|
||||
|
||||
.menuSortWrapper{
|
||||
.menuSortHeading{
|
||||
@apply sub-headline font-bold ;
|
||||
color: var(--text-active);
|
||||
font-feature-settings: 'salt' on;
|
||||
@apply heading-3 font-heading;
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
.menuSortList{
|
||||
|
@@ -5,8 +5,8 @@ import useSWR from 'swr';
|
||||
|
||||
|
||||
const useGetAllCollection = () => {
|
||||
const { data, ...rest } = useSWR<GetCollectionsQuery>([getCollectionsNameQuery], gglFetcher)
|
||||
return { collections: data?.collections, ...rest }
|
||||
const { data, isValidating, ...rest } = useSWR<GetCollectionsQuery>([getCollectionsNameQuery], gglFetcher)
|
||||
return { collections: data?.collections.items || [], loading: isValidating, ...rest }
|
||||
}
|
||||
|
||||
export default useGetAllCollection;
|
@@ -5,7 +5,7 @@ import useSWR from 'swr'
|
||||
|
||||
const useFacets = (options?: QueryFacetsArgs) => {
|
||||
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
|
||||
|
Reference in New Issue
Block a user