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<
|
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'>>
|
||||||
|
@@ -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}
|
||||||
|
@@ -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,9 +14,8 @@ 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 { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: true })
|
||||||
const {messages, removeMessage} = useMessage()
|
const {messages, removeMessage} = useMessage()
|
||||||
|
|
||||||
const toggleFilter = () => {
|
const toggleFilter = () => {
|
||||||
@@ -30,6 +29,7 @@ const LayoutContent: FC<Props> = ({ children }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={s.mainLayout}>
|
<div className={s.mainLayout}>
|
||||||
|
{router.pathname}
|
||||||
<Header toggleFilter={toggleFilter} visibleFilter={visibleFilter} />
|
<Header toggleFilter={toggleFilter} visibleFilter={visibleFilter} />
|
||||||
{
|
{
|
||||||
router.pathname === ROUTE.ACCOUNT ?
|
router.pathname === ROUTE.ACCOUNT ?
|
||||||
@@ -38,10 +38,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 visible={visibleFilter} onClose={closeFilter} /> </div>)
|
||||||
}
|
}
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
@import "../../../styles/utilities";
|
@import "../../../styles/utilities";
|
||||||
.menuFilterWrapper{
|
.menuFilterWrapper{
|
||||||
@apply spacing-horizontal;
|
|
||||||
|
|
||||||
.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;
|
||||||
|
@@ -2,43 +2,31 @@ import classNames from 'classnames'
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import s from './MenuFilter.module.scss'
|
import s from './MenuFilter.module.scss'
|
||||||
|
import MenuFilterItem from './MenuFilterItem/MenuFilterItem';
|
||||||
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
|
onChangeValue?: (value: Object) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuFilter = ({heading,categories,type,onChangeValue}:Props)=> {
|
const MenuFilter = ({ heading, categories, type, onChangeValue }: Props) => {
|
||||||
const [active, setActive] = useState<string>('');
|
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 (
|
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>
|
value={item.slug || item.code || ''}
|
||||||
</li>)
|
onClick={handleClick}
|
||||||
|
/>)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</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";
|
@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,78 @@
|
|||||||
|
import { QueryFacetsArgs } from '@framework/schema';
|
||||||
|
import classNames from 'classnames';
|
||||||
import React, { useState } from 'react';
|
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 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,
|
visible: boolean,
|
||||||
onClose: () => void
|
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
|
||||||
|
|
||||||
|
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)
|
// console.log(dataSort)
|
||||||
}
|
}
|
||||||
return(
|
|
||||||
<>
|
|
||||||
<div className={s.menuNavigationProductListDesktop}>
|
return (
|
||||||
<MenuNavigation categories={categories} heading="Categories"/>
|
<div className={classNames({ [s.menuNavigationProductListMobile]: true, [s.isShow]: visible })}>
|
||||||
<MenuNavigation categories={brands} heading="Brands"/>
|
<div className={classNames({ [s.menuNavigationProductModal]: true, [s.animation]: visible })}>
|
||||||
<MenuNavigation categories={featured} heading="Featured"/>
|
<div className={s.content}>
|
||||||
</div>
|
<div className={s.head}>
|
||||||
<div className={classNames({ [s.menuNavigationProductListMobile] :true,[s.isShow]: visible})}>
|
<h3>FILTER</h3>
|
||||||
<div className={classNames({ [s.menuNavigationProductModal] :true,[s.animation]: visible})}>
|
<div onClick={onClose}><IconHide /></div>
|
||||||
<div className={s.content}>
|
</div>
|
||||||
<div className={s.head}>
|
{collectionLoading && <SkeletonParagraph rows={5} />}
|
||||||
<h3>FILTER</h3>
|
<MenuFilter categories={collections} heading="Categories" type={QUERY_KEY.CATEGORY} onChangeValue={handleValue} />
|
||||||
<div onClick={onClose}><IconHide/></div>
|
{facetsLoading && <>
|
||||||
</div>
|
<SkeletonParagraph rows={5} />
|
||||||
<MenuFilter categories={categories} heading="Categories" type="category" onChangeValue={handleValue}/>
|
<SkeletonParagraph rows={5} />
|
||||||
<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}/>
|
facets?.map(item => <MenuFilter
|
||||||
<div className={s.foot}>
|
key={item.id}
|
||||||
<ButtonCommon size="large" onClick={filter}>{LANGUAGE.BUTTON_LABEL.CONFIRM}</ButtonCommon>
|
type={item.code}
|
||||||
</div>
|
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>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,11 +1,8 @@
|
|||||||
@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{
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user