commit
fd79d99444
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
import DropdownCategories from './dropdown-categories';
|
||||||
|
|
||||||
|
describe('DropdownCategories', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<DropdownCategories />);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import styles from './dropdown-categories.module.scss';
|
||||||
|
import {PropsWithChildren, useState} from "react";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faChevronDown, faChevronUp} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
export interface DropdownCategoriesProps extends PropsWithChildren {
|
||||||
|
title?: string;
|
||||||
|
collapse?: boolean;
|
||||||
|
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function DropdownCategories({children, title = '', count = null, collapse = false}: DropdownCategoriesProps) {
|
||||||
|
const [visible, setVisible] = useState(!collapse);
|
||||||
|
return (
|
||||||
|
<div className={styles['container']}>
|
||||||
|
<header className="flex justify-between grow px-6 py-2 align-middle items-center cursor-pointer"
|
||||||
|
onClick={() => setVisible(!visible)}>
|
||||||
|
<h2 className="overflow-y-auto text-md text-gray-400 flex items-center">
|
||||||
|
{title}
|
||||||
|
{count && (
|
||||||
|
<span className="inline-flex items-center justify-center w-6 h-6 ml-2 text-xs font-semibold text-blue-800 bg-blue-200 rounded-full">
|
||||||
|
{count}
|
||||||
|
</span>)}
|
||||||
|
</h2>
|
||||||
|
{visible && (<FontAwesomeIcon icon={faChevronDown}
|
||||||
|
size={'sm'}
|
||||||
|
className="text-gray-400" />)}
|
||||||
|
{!visible && (<FontAwesomeIcon icon={faChevronUp}
|
||||||
|
size={'sm'}
|
||||||
|
className="text-gray-400" />)}
|
||||||
|
</header>
|
||||||
|
<main className={visible ? 'block' : 'hidden'}>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DropdownCategories;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
import DropdownSection from './dropdown-section';
|
||||||
|
|
||||||
|
describe('DropdownComponent', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<DropdownSection />);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import {PropsWithChildren, useState} from "react";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faChevronDown, faChevronUp} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
import styles from './dropdown-section.module.scss';
|
||||||
|
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
export interface DropdownComponentProps extends PropsWithChildren {
|
||||||
|
title?: string;
|
||||||
|
collapse?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownSection({children, title = '', collapse = false}: DropdownComponentProps) {
|
||||||
|
const [visible, setVisible] = useState(!collapse);
|
||||||
|
return (
|
||||||
|
<div className={styles['container']}>
|
||||||
|
<header className="flex justify-between grow px-6 py-4 align-middle items-center cursor-pointer"
|
||||||
|
onClick={() => setVisible(!visible)}>
|
||||||
|
<h1 className="overflow-y-auto text-lg text-gray-600">{title}</h1>
|
||||||
|
{visible && (<FontAwesomeIcon icon={faChevronDown} className="text-gray-600"/>)}
|
||||||
|
{!visible && (<FontAwesomeIcon icon={faChevronUp} className="text-gray-600"/>)}
|
||||||
|
</header>
|
||||||
|
<main className={visible ? 'block' : 'hidden'}>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DropdownSection;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
const DynamicComponent = ({children}) => <>{children}</>
|
||||||
|
|
||||||
|
export default dynamic(() => Promise.resolve(DynamicComponent), {ssr: false})
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
import ShopAmountRange from './shop-amount-range';
|
||||||
|
|
||||||
|
describe('ShopAmountRange', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<ShopAmountRange />);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import InputRange from "react-input-range";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {PriceRange} from "../../libs/utils";
|
||||||
|
import {isEmpty} from "radash";
|
||||||
|
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
export interface ShopAmountRangeProps {
|
||||||
|
range?: PriceRange;
|
||||||
|
onChange?: (range: PriceRange) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShopAmountRange({range = {min: 10, max: 599}, onChange}: ShopAmountRangeProps) {
|
||||||
|
const [value, setValue] = useState(!isEmpty(range) ? range : {min: 10, max: 599});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(!isEmpty(range) ? range : {min: 10, max: 599});
|
||||||
|
}, [range]);
|
||||||
|
const inputRangeChange = (value) => setValue(value);
|
||||||
|
const inputRangeChangeComplete = (value) => {
|
||||||
|
if (onChange) {
|
||||||
|
onChange(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-5 pb-4">
|
||||||
|
<section className="my-3 p-3 h-[40px]">
|
||||||
|
<InputRange
|
||||||
|
onChange={inputRangeChange}
|
||||||
|
onChangeComplete={inputRangeChangeComplete}
|
||||||
|
value={value}
|
||||||
|
minValue={10}
|
||||||
|
step={15}
|
||||||
|
formatLabel={value => `${value} €`}
|
||||||
|
maxValue={600} />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShopAmountRange;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
import ShopAside from './shop-aside';
|
||||||
|
|
||||||
|
describe('ShopAside', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<ShopAside />);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
import {buildShopParamsGET, findQueryParamsValue, PriceRange} from "../../libs/utils";
|
||||||
|
|
||||||
|
import DropdownSection from "../dropdown-component/dropdown-section";
|
||||||
|
import ShopCategories from "../shop-categories/shop-categories";
|
||||||
|
import ShopAmountRange from "../shop-amount-range/shop-amount-range";
|
||||||
|
import ShopFilters from "../shop-filters/shop-filters";
|
||||||
|
import ShopSearch from "../shop-search/shop-search";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {useRouter} from "next/router";
|
||||||
|
import {isEqual} from "radash";
|
||||||
|
|
||||||
|
export function ShopAside({sections = [], queries = [], filters = []}) {
|
||||||
|
const [filterMap, setFilterMap] = useState(null);
|
||||||
|
const [filtersSelected, setFiltersSelected] = useState(findQueryParamsValue(queries, 'filters') || []);
|
||||||
|
const [priceRange, setPriceRange] = useState(findQueryParamsValue(queries, 'facets') as PriceRange);
|
||||||
|
const [hasFilters, setHasFilters] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
const validFilters = () => {
|
||||||
|
const list = [];
|
||||||
|
if (filterMap) {
|
||||||
|
Array.from(filterMap).reduce((array: number[], map: [number, Map<number, boolean>]) => {
|
||||||
|
Array.from(map[1].entries()).forEach((value: [number, boolean]) => array.push(value[0]));
|
||||||
|
return array;
|
||||||
|
}, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push(`${router.pathname}${buildShopParamsGET(
|
||||||
|
findQueryParamsValue(queries, 'sort'),
|
||||||
|
findQueryParamsValue(queries, 'page'),
|
||||||
|
priceRange,
|
||||||
|
findQueryParamsValue(queries, 'cat'),
|
||||||
|
list,
|
||||||
|
findQueryParamsValue(queries, 'search'),
|
||||||
|
)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!findQueryParamsValue(queries, 'filters')) {
|
||||||
|
setFilterMap(new Map());
|
||||||
|
setFiltersSelected([]);
|
||||||
|
} else {
|
||||||
|
setFiltersSelected(findQueryParamsValue(queries, 'filters') || []);
|
||||||
|
}
|
||||||
|
if (!findQueryParamsValue(queries, 'facets')) {
|
||||||
|
setPriceRange(null);
|
||||||
|
}
|
||||||
|
}, [queries]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHasFilters((!isEqual(filterMap, null) && filterMap.size !== 0) || !isEqual(priceRange, null));
|
||||||
|
}, [filterMap]);
|
||||||
|
|
||||||
|
const updatePriceRange = (range: PriceRange) => {
|
||||||
|
setPriceRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFilterMap = (map: Map<number, Map<number, boolean>>) => {
|
||||||
|
setFilterMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetFilters = () => {
|
||||||
|
router.push(`${router.pathname}${buildShopParamsGET(
|
||||||
|
findQueryParamsValue(queries, 'sort'),
|
||||||
|
findQueryParamsValue(queries, 'page'),
|
||||||
|
null,
|
||||||
|
findQueryParamsValue(queries, 'cat'),
|
||||||
|
null,
|
||||||
|
findQueryParamsValue(queries, 'search'),
|
||||||
|
)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ShopSearch queries={queries} />
|
||||||
|
<div className="rounded-lg mx-2 md:mx-0 mt-5 md:mt-0 mb-5 shadow-sm
|
||||||
|
bg-white
|
||||||
|
dark:bg-gray-800">
|
||||||
|
<DropdownSection title="Catégogies">
|
||||||
|
<ShopCategories queries={queries}
|
||||||
|
sections={sections} />
|
||||||
|
</DropdownSection>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-lg mx-2 md:mx-0 mt-5 md:mt-0 mb-5 shadow-sm
|
||||||
|
bg-white
|
||||||
|
dark:bg-gray-800">
|
||||||
|
|
||||||
|
{hasFilters && (
|
||||||
|
<div className="pt-5 px-5 flex justify-end">
|
||||||
|
<button onClick={() => resetFilters()}
|
||||||
|
className="px-3 py-2 text-xs font-medium text-center rounded-lg border
|
||||||
|
text-gray-900 bg-white border-gray-200
|
||||||
|
focus:outline-none hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-200
|
||||||
|
dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">Réinitialiser les filtres
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<DropdownSection title="Prix"
|
||||||
|
collapse={true}>
|
||||||
|
<ShopAmountRange range={priceRange}
|
||||||
|
onChange={(value: PriceRange) => updatePriceRange(value)} />
|
||||||
|
</DropdownSection>
|
||||||
|
<hr className="border-gray-100 dark:border-gray-700 w-full" />
|
||||||
|
<DropdownSection title="Filtres">
|
||||||
|
<ShopFilters filters={filters}
|
||||||
|
filtersSelected={filtersSelected}
|
||||||
|
onChange={(map: Map<number, Map<number, boolean>>) => updateFilterMap(map)} />
|
||||||
|
</DropdownSection>
|
||||||
|
<hr className="border-gray-100 dark:border-gray-700 w-full mb-5" />
|
||||||
|
<div className="px-5">
|
||||||
|
<button onClick={() => validFilters()}
|
||||||
|
className="font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-5 w-full
|
||||||
|
text-white bg-blue-700
|
||||||
|
hover:bg-blue-800 focus:ring-4 focus:ring-blue-300
|
||||||
|
dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
|
||||||
|
Appliquer les filtres
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShopAside;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
import ShopCatalog from './shop-catalog';
|
||||||
|
|
||||||
|
describe('ShopCatalog', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<ShopCatalog />);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import {buildShopParamsGET, findQueryParamsValue, getShopSortList, SortType} from "../../libs/utils";
|
||||||
|
import BlogPagination from "../blog-pagination/blog-pagination";
|
||||||
|
import {useRouter} from "next/router";
|
||||||
|
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
export interface ShopCatalogProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShopCatalog({
|
||||||
|
queries = [],
|
||||||
|
products = [],
|
||||||
|
paginator = {pagination: {}}
|
||||||
|
}) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const sortCatalog = (event) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
router.push(`${router.pathname}${buildShopParamsGET(
|
||||||
|
value as SortType,
|
||||||
|
findQueryParamsValue(queries, 'page'),
|
||||||
|
findQueryParamsValue(queries, 'range'),
|
||||||
|
findQueryParamsValue(queries, 'cat'),
|
||||||
|
findQueryParamsValue(queries, 'filters'),
|
||||||
|
findQueryParamsValue(queries, 'search')
|
||||||
|
)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header className="flex flex-col items-end w-full">
|
||||||
|
<div className="max-w-[300px] text-right">
|
||||||
|
<select id="countries"
|
||||||
|
value={findQueryParamsValue(queries, 'sort')}
|
||||||
|
onChange={sortCatalog}
|
||||||
|
className="border text-sm rounded-lg block w-full p-2.5
|
||||||
|
bg-white border-gray-300 text-gray-900
|
||||||
|
focus:ring-blue-500 focus:border-blue-500
|
||||||
|
dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
|
||||||
|
|
||||||
|
{getShopSortList().map((val, index: number) => (
|
||||||
|
<option key={'sort-' + index}
|
||||||
|
value={val.value}>{val.label}</option>))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<hr className="border-gray-200 dark:border-gray-700 w-full mt-3 mb-5" />
|
||||||
|
<main>
|
||||||
|
{products && products.length === 0 && (<p>Il n'y aucun produit pour l'instant</p>)}
|
||||||
|
{products && products.length > 0 && (<p>Produit</p>)}
|
||||||
|
</main>
|
||||||
|
<footer className="mt-10">
|
||||||
|
<BlogPagination paginator={paginator} />
|
||||||
|
</footer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShopCatalog;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
import ShopCategories from './shop-categories';
|
||||||
|
|
||||||
|
describe('ShopCategories', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<ShopCategories />);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
/* eslint-disable-next-line */
|
||||||
|
import {Fragment, useEffect, useState} from "react";
|
||||||
|
import delve from "dlv";
|
||||||
|
import {useRouter} from "next/router";
|
||||||
|
|
||||||
|
import {buildShopParamsGET, findQueryParamsValue} from "../../libs/utils";
|
||||||
|
|
||||||
|
export interface ShopCategoriesProps {
|
||||||
|
sections?: object[];
|
||||||
|
onChange?: (map: Map<number, boolean>) => void,
|
||||||
|
queries: { param: string, value: any }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShopCategories({sections = [], queries = [], onChange = () => null}: ShopCategoriesProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [category, setCategory] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCategory(findQueryParamsValue(queries, 'cat'));
|
||||||
|
}, [queries]);
|
||||||
|
const navigateTo = (slug) => router.push(`${router.pathname}${buildShopParamsGET(
|
||||||
|
findQueryParamsValue(queries, 'sort'),
|
||||||
|
findQueryParamsValue(queries, 'page'),
|
||||||
|
findQueryParamsValue(queries, 'range'),
|
||||||
|
slug,
|
||||||
|
findQueryParamsValue(queries, 'filters'),
|
||||||
|
findQueryParamsValue(queries, 'search')
|
||||||
|
)}`);
|
||||||
|
|
||||||
|
const isActive = (slug) => slug === category;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="h-full px-3 pb-4 overflow-y-auto">
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{sections.map((section: object, sectionIndex: number) => (
|
||||||
|
<Fragment key={'accordion-' + sectionIndex}>
|
||||||
|
<li>
|
||||||
|
<div onClick={() => navigateTo(delve(section, 'attributes.slug', null))}
|
||||||
|
className={"flex items-center py-2 text-base font-light rounded-lg cursor-pointer " +
|
||||||
|
(isActive(delve(section, 'attributes.slug', null)) ? "bg-gray-100 text-blue-600 hover:bg-gray-100 " : "hover:bg-gray-50 ") +
|
||||||
|
"text-gray-900 " +
|
||||||
|
"dark:text-white dark:hover:bg-gray-700"}>
|
||||||
|
<span className="ml-3">
|
||||||
|
{delve(section, 'attributes.label', 'N/A')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShopCategories;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
import ShopFilters from './shop-filters';
|
||||||
|
|
||||||
|
describe('ShopFilters', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<ShopFilters />);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
import delve from "dlv";
|
||||||
|
|
||||||
|
import DropdownCategories from "../dropdown-categories/dropdown-categories";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {isEmpty} from "radash";
|
||||||
|
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
export interface ShopFiltersProps {
|
||||||
|
filters?: object[];
|
||||||
|
filtersSelected?: number[];
|
||||||
|
|
||||||
|
onChange?: (map: Map<number, Map<number, boolean>>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShopFilters({filters = [], filtersSelected = [], onChange = null}: ShopFiltersProps) {
|
||||||
|
const [map, setMap] = useState(new Map());
|
||||||
|
const [count, setCount] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMap(new Map());
|
||||||
|
setCount({});
|
||||||
|
}, [filters]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
filters.forEach((filter: object) => {
|
||||||
|
const filterItems: number[] = delve(filter, 'attributes.filter_items.data', []).map((item) => item.id);
|
||||||
|
if (filterItems.some((item: number) => filtersSelected.includes(item))) {
|
||||||
|
const itemIds: number[] = filterItems.filter((item: number) => filtersSelected.includes(item));
|
||||||
|
itemIds.forEach((itemId) => populateMap(delve(filter, 'id', null), itemId.toString(), true))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onChange(map);
|
||||||
|
}, [filtersSelected]);
|
||||||
|
|
||||||
|
const setMapValue = (sectionId, event) => {
|
||||||
|
const {checked, value} = event.target || event.detail;
|
||||||
|
populateMap(sectionId, value, checked);
|
||||||
|
|
||||||
|
if (onChange) {
|
||||||
|
onChange(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultChecked = (sectionId, itemId) => map.has(sectionId) && map.get(sectionId).has(itemId) ? true : false;
|
||||||
|
|
||||||
|
const populateMap = (sectionId: string, itemId: string, checked: boolean): void => {
|
||||||
|
if (!map.has(sectionId)) {
|
||||||
|
map.set(sectionId, new Map());
|
||||||
|
}
|
||||||
|
if (checked) {
|
||||||
|
map.get(sectionId).set(parseInt(itemId, 10), checked);
|
||||||
|
} else {
|
||||||
|
map.get(sectionId).delete(parseInt(itemId, 10));
|
||||||
|
}
|
||||||
|
setMap(map);
|
||||||
|
count[sectionId] = map.get(sectionId).size;
|
||||||
|
setCount({...count});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pb-4">
|
||||||
|
{filters.map((filter: object, index: number) => (
|
||||||
|
<DropdownCategories key={'category-' + index}
|
||||||
|
collapse={true}
|
||||||
|
count={!isEmpty(count[delve(filter, 'id', null)]) || count[delve(filter, 'id', null)] !== 0 ? count[delve(filter, 'id', null)] : null}
|
||||||
|
title={delve(filter, 'attributes.label', 'N/A')}>
|
||||||
|
{delve(filter, 'attributes.filter_items.data', []).map((item, itemIndex: number) => (
|
||||||
|
<div key={'category-item-' + index + '-' + itemIndex}
|
||||||
|
className="flex items-center align-middle h-[40px] px-6 rounded-l grow
|
||||||
|
hover:bg-gray-100">
|
||||||
|
<input id={delve(item, 'attributes.slug', 'N/A')}
|
||||||
|
type="checkbox"
|
||||||
|
value={delve(item, 'id', null)}
|
||||||
|
checked={defaultChecked(delve(filter, 'id', null), delve(item, 'id', null))}
|
||||||
|
onChange={(event) => setMapValue(delve(filter, 'id', null), event)}
|
||||||
|
className="w-4 h-4 rounded
|
||||||
|
text-blue-600 bg-gray-100 border-gray-300
|
||||||
|
focus:ring-blue-500
|
||||||
|
dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
|
||||||
|
<label htmlFor={delve(item, 'attributes.slug', 'N/A')}
|
||||||
|
className="ml-2 text-sm font-light text-gray-800 dark:text-gray-300 flex flex-1 cursor-pointer">
|
||||||
|
{delve(item, 'attributes.label', 'N/A')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</DropdownCategories>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShopFilters;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
import ShopSearch from './shop-search';
|
||||||
|
|
||||||
|
describe('ShopSearch', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<ShopSearch />);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
import {useRouter} from "next/router";
|
||||||
|
import {useState} from "react";
|
||||||
|
|
||||||
|
import styles from './shop-search.module.scss';
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faMagnifyingGlass} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import {buildShopParamsGET, findQueryParamsValue} from "../../libs/utils";
|
||||||
|
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
export interface ShopSearchProps {
|
||||||
|
queries: { param: string, value: any }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShopSearch({queries = []}: ShopSearchProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [search, setSearch] = useState(findQueryParamsValue(queries, 'search') || '');
|
||||||
|
|
||||||
|
/*useEffect(() => {
|
||||||
|
if (subject === null) {
|
||||||
|
const sub = new BehaviorSubject('');
|
||||||
|
const client = new MeiliSearch({
|
||||||
|
host: environment.meiliUrl,
|
||||||
|
apiKey: environment.meiliApiKey
|
||||||
|
})
|
||||||
|
setSubject(sub);
|
||||||
|
setClient(client);
|
||||||
|
} else {
|
||||||
|
subject.pipe(
|
||||||
|
map((s: string) => s.trim()),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
filter((s: string) => s.length >= 2),
|
||||||
|
debounceTime(200),
|
||||||
|
map((value) => {
|
||||||
|
setLoading(true);
|
||||||
|
return value;
|
||||||
|
}),
|
||||||
|
delay(2000),
|
||||||
|
switchMap(async (value: string) => {
|
||||||
|
const index = await client.getIndex('product');
|
||||||
|
const products = await index.search(value);
|
||||||
|
setLoading(false);
|
||||||
|
await router.push('/search');
|
||||||
|
dispatch(setShopSearchState(products.hits));
|
||||||
|
}),
|
||||||
|
catchError((err) => of(false))
|
||||||
|
).subscribe(() => setLoading(false));
|
||||||
|
}
|
||||||
|
}, [subject]);*/
|
||||||
|
|
||||||
|
const onSearchChange = (e) => setSearch(e.target.value);
|
||||||
|
|
||||||
|
const validSearch = (event) => {
|
||||||
|
router.push(`${router.pathname}${buildShopParamsGET(
|
||||||
|
findQueryParamsValue(queries, 'sort'),
|
||||||
|
findQueryParamsValue(queries, 'page'),
|
||||||
|
findQueryParamsValue(queries, 'range'),
|
||||||
|
findQueryParamsValue(queries, 'cat'),
|
||||||
|
findQueryParamsValue(queries, 'filters'),
|
||||||
|
search,
|
||||||
|
)}`);
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles['container'] + " rounded-lg mx-2 md:mx-0 mt-5 md:mt-0 p-4 mb-5 shadow-sm" +
|
||||||
|
" bg-white" +
|
||||||
|
" dark:bg-gray-800"}>
|
||||||
|
<label htmlFor="default-search"
|
||||||
|
className="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Rechercher</label>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<svg aria-hidden="true"
|
||||||
|
className="w-5 h-5 text-gray-500 dark:text-gray-400"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<input type="search"
|
||||||
|
id="default-search"
|
||||||
|
value={search}
|
||||||
|
onChange={onSearchChange}
|
||||||
|
className="block w-full p-4 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="Rechercher un produit ..." />
|
||||||
|
<button type="button"
|
||||||
|
onClick={(event) => validSearch(event)}
|
||||||
|
className="text-white absolute right-2.5 bottom-2.5 font-medium rounded-lg text-sm px-4 py-2
|
||||||
|
bg-blue-700
|
||||||
|
hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
||||||
|
<FontAwesomeIcon icon={faMagnifyingGlass} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShopSearch;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import delve from "dlv";
|
||||||
|
|
||||||
|
import {environment} from "../../../environments/environment";
|
||||||
|
import SeoConfig from "../../../components/seo-config/seo-config";
|
||||||
|
import Layout from "../../../components/layout/layout";
|
||||||
|
import ShopSearch from "../../../components/shop-search/shop-search";
|
||||||
|
import Categories from "../../../components/categories/categories";
|
||||||
|
import BlogPagination from "../../../components/blog-pagination/blog-pagination";
|
||||||
|
|
||||||
|
import styles from "./index.module.scss";
|
||||||
|
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
export interface ResultProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServerSideProps(context) {
|
||||||
|
const {query} = context;
|
||||||
|
|
||||||
|
let postsUrl = `${environment.strapiApiUrl}/articles?populate=deep&sort=publishedAt:DESC`;
|
||||||
|
if (query && query.page) {
|
||||||
|
postsUrl += `$pagination[page]=${query.page}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const categories = await axios.get(`${environment.strapiApiUrl}/categories?populate=deep`);
|
||||||
|
const posts = await axios.get(postsUrl);
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
categories: delve(categories, 'data.data', []),
|
||||||
|
lastPublished: delve(posts, 'data.data', []),
|
||||||
|
paginator: delve(posts, 'data.meta', {pagination: {}}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Filter({menuHeader, menuFooter, seo, categories, lastPublished, paginator}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SeoConfig {...seo} />
|
||||||
|
<Layout menuHeader={menuHeader}
|
||||||
|
menuFooter={menuFooter}
|
||||||
|
headerTransparent={true}>
|
||||||
|
<main className={styles['blog-container'] + " w-full my-4 px-2 lg:px-0"}>
|
||||||
|
<section className="container max-w-screen-xl mx-auto relative flex flex-col md:flex-row">
|
||||||
|
<aside className="grow md:basis-2/6 md:mx-0 md:pr-5">
|
||||||
|
<ShopSearch />
|
||||||
|
<Categories items={categories} />
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section className="grow md:basis-4/6 mx-2">
|
||||||
|
<footer className="mt-10">
|
||||||
|
<p>Filter page!</p>
|
||||||
|
<BlogPagination paginator={paginator} />
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Filter;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import delve from 'dlv';
|
||||||
|
|
||||||
|
import {environment} from "../../environments/environment";
|
||||||
|
|
||||||
|
import SeoConfig from "../../components/seo-config/seo-config";
|
||||||
|
import Layout from "../../components/layout/layout";
|
||||||
|
import ShopAside from "../../components/shop-aside/shop-aside";
|
||||||
|
import ShopCatalog from "../../components/shop-catalog/shop-catalog";
|
||||||
|
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
import {buildQueriesListFromQueryParams, SortType} from "../../libs/utils";
|
||||||
|
|
||||||
|
export async function getServerSideProps(context) {
|
||||||
|
const {query} = context;
|
||||||
|
|
||||||
|
const queryValues = buildQueriesListFromQueryParams(query);
|
||||||
|
|
||||||
|
let productsUrl = `${environment.strapiApiUrl}/products?populate=deep`;
|
||||||
|
|
||||||
|
if (query && query.page) {
|
||||||
|
productsUrl += `$pagination[page]=${query.page}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query && query.search) {
|
||||||
|
productsUrl += `&filters[$or][0][title][$contains]=${query.search}&filters[$or][1][content][$contains]=${query.search}&filters[$or][2][description][$contains]=${query.search}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query && query.sort) {
|
||||||
|
if ((query.sort as SortType) === 'pas') {
|
||||||
|
productsUrl += `&sort=[amount]:ASC`;
|
||||||
|
} else if ((query.sort as SortType) === 'pde') {
|
||||||
|
productsUrl += `&sort=[amount]:DESC`;
|
||||||
|
} else if ((query.sort as SortType) === 'alp') {
|
||||||
|
productsUrl += `&sort=[title]:ASC`;
|
||||||
|
} else {
|
||||||
|
productsUrl += `&sort=[title]:ASC`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query && query.facets) {
|
||||||
|
const facets: string[] = query.facets.split(':');
|
||||||
|
productsUrl += `&filters[amount][$between]=${(parseInt(facets[0], 10) * 100)}&filters[amount][$between]=${(parseInt(facets[1], 10) * 100)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query && query.filters) {
|
||||||
|
query.filters.split(',').forEach((filterId, index) => productsUrl += `&filters[filter_items][id][$in][${index}]=${filterId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let filtersUrl = `${environment.strapiApiUrl}/filter-categories?populate=deep&sort=[filter_section][slug]:DESC`;
|
||||||
|
|
||||||
|
if (query && query.cat) {
|
||||||
|
productsUrl += `&filters[filter_items][filter_category][filter_section][slug][$eq]=${query.cat}`;
|
||||||
|
filtersUrl += `&filters[filter_section][slug][$eq]=${query.cat}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(productsUrl);
|
||||||
|
|
||||||
|
const sections = await axios.get(`${environment.strapiApiUrl}/filter-sections?populate=deep`);
|
||||||
|
const products = await axios.get(productsUrl);
|
||||||
|
const filters = await axios.get(filtersUrl);
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
sections: delve(sections, 'data.data', []),
|
||||||
|
products: delve(products, 'data.data', []),
|
||||||
|
filters: delve(filters, 'data.data', []),
|
||||||
|
paginator: delve(products, 'data.meta', {pagination: {}}),
|
||||||
|
queries: queryValues
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Shop({menuHeader, menuFooter, seo, sections, products, paginator, queries = [], filters = []}) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SeoConfig {...seo} />
|
||||||
|
<Layout menuHeader={menuHeader}
|
||||||
|
menuFooter={menuFooter}
|
||||||
|
headerTransparent={true}>
|
||||||
|
<main className={styles['blog-container'] + " w-full my-4 lg:px-0"}>
|
||||||
|
<section className="container max-w-screen-xl mx-auto relative flex flex-col md:flex-row">
|
||||||
|
<aside className="grow md:basis-3/12 md:mx-0 md:pr-5">
|
||||||
|
<ShopAside queries={queries}
|
||||||
|
filters={filters}
|
||||||
|
sections={sections} />
|
||||||
|
</aside>
|
||||||
|
<section className="grow md:basis-9/12 mx-2">
|
||||||
|
<ShopCatalog products={products}
|
||||||
|
queries={queries}
|
||||||
|
paginator={paginator} />
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Shop;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Replace this with your own classes
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* .container {
|
||||||
|
* }
|
||||||
|
*/
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import delve from 'dlv';
|
||||||
|
|
||||||
|
import SeoConfig from "../../../components/seo-config/seo-config";
|
||||||
|
import Layout from "../../../components/layout/layout";
|
||||||
|
import ShopSearch from "../../../components/shop-search/shop-search";
|
||||||
|
import Categories from "../../../components/categories/categories";
|
||||||
|
import BlogPagination from "../../../components/blog-pagination/blog-pagination";
|
||||||
|
import {environment} from "../../../environments/environment";
|
||||||
|
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
export interface ResultProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServerSideProps(context) {
|
||||||
|
const {query} = context;
|
||||||
|
|
||||||
|
let postsUrl = `${environment.strapiApiUrl}/articles?populate=deep&sort=publishedAt:DESC`;
|
||||||
|
if (query && query.page) {
|
||||||
|
postsUrl += `$pagination[page]=${query.page}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const categories = await axios.get(`${environment.strapiApiUrl}/categories?populate=deep`);
|
||||||
|
const posts = await axios.get(postsUrl);
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
categories: delve(categories, 'data.data', []),
|
||||||
|
lastPublished: delve(posts, 'data.data', []),
|
||||||
|
paginator: delve(posts, 'data.meta', {pagination: {}}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Result({menuHeader, menuFooter, seo, categories, lastPublished, paginator}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SeoConfig {...seo} />
|
||||||
|
<Layout menuHeader={menuHeader}
|
||||||
|
menuFooter={menuFooter}
|
||||||
|
headerTransparent={true}>
|
||||||
|
<main className={styles['blog-container'] + " w-full my-4 px-2 lg:px-0"}>
|
||||||
|
<section className="container max-w-screen-xl mx-auto relative flex flex-col md:flex-row">
|
||||||
|
<aside className="grow md:basis-2/6 md:mx-0 md:pr-5">
|
||||||
|
<ShopSearch />
|
||||||
|
<Categories items={categories} />
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section className="grow md:basis-4/6 mx-2">
|
||||||
|
<footer className="mt-10">
|
||||||
|
<p>Result page !</p>
|
||||||
|
<BlogPagination paginator={paginator} />
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Result;
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import {createSlice} from "@reduxjs/toolkit";
|
||||||
|
import {HYDRATE} from "next-redux-wrapper";
|
||||||
|
|
||||||
|
import {AppState} from "./store";
|
||||||
|
|
||||||
|
export interface ShopSearchState {
|
||||||
|
results: object[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
const initialState: ShopSearchState = {
|
||||||
|
results: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Actual Slice
|
||||||
|
// @ts-ignore
|
||||||
|
export const shopSearchSlice = createSlice({
|
||||||
|
name: "shopSearch",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
|
||||||
|
// Action to set the authentication status
|
||||||
|
setShopSearchState(state, action) {
|
||||||
|
// @ts-ignore
|
||||||
|
state.results = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Special reducer for hydrating the state. Special case for next-redux-wrapper
|
||||||
|
extraReducers: {
|
||||||
|
// @ts-ignore
|
||||||
|
[HYDRATE]: (state, action) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
// @ts-ignore
|
||||||
|
...action.payload.shopSearch,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {setShopSearchState} = shopSearchSlice.actions;
|
||||||
|
|
||||||
|
export const selectShopSearchState = (state: AppState) => state.blogSearch;
|
||||||
|
|
||||||
|
export default shopSearchSlice.reducer;
|
||||||
Loading…
Reference in new issue