diff --git a/.gitignore b/.gitignore index 9bfebba..ef9b284 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,4 @@ testem.log Thumbs.db # Next.js -.next +.next \ No newline at end of file diff --git a/apps/website/components/blog-search/blog-search.tsx b/apps/website/components/blog-search/blog-search.tsx index 9737e04..3e13564 100644 --- a/apps/website/components/blog-search/blog-search.tsx +++ b/apps/website/components/blog-search/blog-search.tsx @@ -7,7 +7,7 @@ import {useDispatch} from "react-redux"; import {environment} from "../../environments/environment"; import styles from './blog-search.module.scss'; -import {setSearchState} from "../../store/searchSlice"; +import {setBlogSearchState} from "../../store/blogSearchSlice"; /* eslint-disable-next-line */ export interface BlogSearchProps { @@ -44,8 +44,8 @@ export function BlogSearch(props: BlogSearchProps) { const index = await client.getIndex('article'); const articles = await index.search(value); setLoading(false); - await router.push('/search'); - dispatch(setSearchState(articles.hits)); + await router.push('/shop/results'); + dispatch(setBlogSearchState(articles.hits)); }), catchError((err) => of(false)) ).subscribe(() => setLoading(false)); diff --git a/apps/website/components/dropdown-categories/dropdown-categories.module.scss b/apps/website/components/dropdown-categories/dropdown-categories.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/dropdown-categories/dropdown-categories.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/dropdown-categories/dropdown-categories.spec.tsx b/apps/website/components/dropdown-categories/dropdown-categories.spec.tsx new file mode 100644 index 0000000..2b7ee48 --- /dev/null +++ b/apps/website/components/dropdown-categories/dropdown-categories.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import DropdownCategories from './dropdown-categories'; + +describe('DropdownCategories', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/dropdown-categories/dropdown-categories.tsx b/apps/website/components/dropdown-categories/dropdown-categories.tsx new file mode 100644 index 0000000..a70857c --- /dev/null +++ b/apps/website/components/dropdown-categories/dropdown-categories.tsx @@ -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 ( +
+
setVisible(!visible)}> +

+ {title} + {count && ( + + {count} + )} +

+ {visible && ()} + {!visible && ()} +
+
+ {children} +
+
+ ); +} + +export default DropdownCategories; diff --git a/apps/website/components/dropdown-component/dropdown-section.module.scss b/apps/website/components/dropdown-component/dropdown-section.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/dropdown-component/dropdown-section.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/dropdown-component/dropdown-section.spec.tsx b/apps/website/components/dropdown-component/dropdown-section.spec.tsx new file mode 100644 index 0000000..ffae2db --- /dev/null +++ b/apps/website/components/dropdown-component/dropdown-section.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import DropdownSection from './dropdown-section'; + +describe('DropdownComponent', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/dropdown-component/dropdown-section.tsx b/apps/website/components/dropdown-component/dropdown-section.tsx new file mode 100644 index 0000000..50c269d --- /dev/null +++ b/apps/website/components/dropdown-component/dropdown-section.tsx @@ -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 ( +
+
setVisible(!visible)}> +

{title}

+ {visible && ()} + {!visible && ()} +
+
+ {children} +
+
+ ); +} + +export default DropdownSection; diff --git a/apps/website/components/dynamic-component/dynamic-component.module.scss b/apps/website/components/dynamic-component/dynamic-component.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/dynamic-component/dynamic-component.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/dynamic-component/dynamic-component.tsx b/apps/website/components/dynamic-component/dynamic-component.tsx new file mode 100644 index 0000000..aa65817 --- /dev/null +++ b/apps/website/components/dynamic-component/dynamic-component.tsx @@ -0,0 +1,5 @@ +import dynamic from "next/dynamic"; + +const DynamicComponent = ({children}) => <>{children} + +export default dynamic(() => Promise.resolve(DynamicComponent), {ssr: false}) diff --git a/apps/website/components/header/header.tsx b/apps/website/components/header/header.tsx index 2a9d163..58285dc 100644 --- a/apps/website/components/header/header.tsx +++ b/apps/website/components/header/header.tsx @@ -3,13 +3,16 @@ import {useEffect} from "react"; import delve from "dlv"; import {signOut, useSession} from "next-auth/react"; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' +import {useRouter} from "next/router"; +import {faBagShopping, faRightToBracket, faUserPlus} from "@fortawesome/free-solid-svg-icons"; import {getBackendImg, hasAvatar} from "../../libs/api"; -import {faBagShopping, faRightToBracket, faUserPlus} from "@fortawesome/free-solid-svg-icons"; import {siteConfig} from "../../config"; +import {buildShopParamsGET} from "../../libs/utils"; export function Header({items = []}) { const {data: session} = useSession(); + const router = useRouter(); useEffect(() => { if (session == null) { @@ -37,6 +40,8 @@ export function Header({items = []}) { } }); + const isActive = (item) => router.route.includes(item.attributes.url); + const renderGenerateItem = (item, index, isActive = false) => { const value = delve(item, 'attributes', {}); return ( @@ -47,8 +52,10 @@ export function Header({items = []}) { } const renderRegularItem = (item, isActive = false) => { + const url = item.url.includes('/shop') ? `${item.url}${buildShopParamsGET()}` : item.url; + return ( -
    - {items.map((item, index: number) => renderGenerateItem(item, index))} + {items.map((item, index: number) => renderGenerateItem(item, index, isActive(item)))}
diff --git a/apps/website/components/shop-amount-range/shop-amount-range.module.scss b/apps/website/components/shop-amount-range/shop-amount-range.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/shop-amount-range/shop-amount-range.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/shop-amount-range/shop-amount-range.spec.tsx b/apps/website/components/shop-amount-range/shop-amount-range.spec.tsx new file mode 100644 index 0000000..9d3b4c1 --- /dev/null +++ b/apps/website/components/shop-amount-range/shop-amount-range.spec.tsx @@ -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(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/shop-amount-range/shop-amount-range.tsx b/apps/website/components/shop-amount-range/shop-amount-range.tsx new file mode 100644 index 0000000..bb5a9b3 --- /dev/null +++ b/apps/website/components/shop-amount-range/shop-amount-range.tsx @@ -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 ( +
+
+ `${value} €`} + maxValue={600} /> +
+
+ ); +} + +export default ShopAmountRange; diff --git a/apps/website/components/shop-aside/shop-aside.module.scss b/apps/website/components/shop-aside/shop-aside.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/shop-aside/shop-aside.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/shop-aside/shop-aside.spec.tsx b/apps/website/components/shop-aside/shop-aside.spec.tsx new file mode 100644 index 0000000..a230e10 --- /dev/null +++ b/apps/website/components/shop-aside/shop-aside.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import ShopAside from './shop-aside'; + +describe('ShopAside', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/shop-aside/shop-aside.tsx b/apps/website/components/shop-aside/shop-aside.tsx new file mode 100644 index 0000000..5f19f5d --- /dev/null +++ b/apps/website/components/shop-aside/shop-aside.tsx @@ -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]) => { + 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>) => { + setFilterMap(map); + } + + const resetFilters = () => { + router.push(`${router.pathname}${buildShopParamsGET( + findQueryParamsValue(queries, 'sort'), + findQueryParamsValue(queries, 'page'), + null, + findQueryParamsValue(queries, 'cat'), + null, + findQueryParamsValue(queries, 'search'), + )}`); + } + + return ( + <> + +
+ + + +
+
+ + {hasFilters && ( +
+ +
+ )} + + updatePriceRange(value)} /> + +
+ + >) => updateFilterMap(map)} /> + +
+
+ +
+
+ + ); +} + +export default ShopAside; diff --git a/apps/website/components/shop-catalog/shop-catalog.module.scss b/apps/website/components/shop-catalog/shop-catalog.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/shop-catalog/shop-catalog.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/shop-catalog/shop-catalog.spec.tsx b/apps/website/components/shop-catalog/shop-catalog.spec.tsx new file mode 100644 index 0000000..35c7041 --- /dev/null +++ b/apps/website/components/shop-catalog/shop-catalog.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import ShopCatalog from './shop-catalog'; + +describe('ShopCatalog', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/shop-catalog/shop-catalog.tsx b/apps/website/components/shop-catalog/shop-catalog.tsx new file mode 100644 index 0000000..02f23b5 --- /dev/null +++ b/apps/website/components/shop-catalog/shop-catalog.tsx @@ -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 ( + <> +
+
+ +
+
+
+
+ {products && products.length === 0 && (

Il n'y aucun produit pour l'instant

)} + {products && products.length > 0 && (

Produit

)} +
+
+ +
+ + ); +} + +export default ShopCatalog; diff --git a/apps/website/components/shop-categories/shop-categories.module.scss b/apps/website/components/shop-categories/shop-categories.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/shop-categories/shop-categories.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/shop-categories/shop-categories.spec.tsx b/apps/website/components/shop-categories/shop-categories.spec.tsx new file mode 100644 index 0000000..5824b8d --- /dev/null +++ b/apps/website/components/shop-categories/shop-categories.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import ShopCategories from './shop-categories'; + +describe('ShopCategories', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/shop-categories/shop-categories.tsx b/apps/website/components/shop-categories/shop-categories.tsx new file mode 100644 index 0000000..2dfed8b --- /dev/null +++ b/apps/website/components/shop-categories/shop-categories.tsx @@ -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) => 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 ( + <> +
+
    + {sections.map((section: object, sectionIndex: number) => ( + +
  • +
    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"}> + + {delve(section, 'attributes.label', 'N/A')} + +
    +
  • +
    + ))} +
+
+ + ); +} + +export default ShopCategories; diff --git a/apps/website/components/shop-filters/shop-filters.module.scss b/apps/website/components/shop-filters/shop-filters.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/shop-filters/shop-filters.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/shop-filters/shop-filters.spec.tsx b/apps/website/components/shop-filters/shop-filters.spec.tsx new file mode 100644 index 0000000..5159a67 --- /dev/null +++ b/apps/website/components/shop-filters/shop-filters.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import ShopFilters from './shop-filters'; + +describe('ShopFilters', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/shop-filters/shop-filters.tsx b/apps/website/components/shop-filters/shop-filters.tsx new file mode 100644 index 0000000..e6e96dc --- /dev/null +++ b/apps/website/components/shop-filters/shop-filters.tsx @@ -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>) => 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 ( +
+ {filters.map((filter: object, index: number) => ( + + {delve(filter, 'attributes.filter_items.data', []).map((item, itemIndex: number) => ( +
+ 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" /> + +
+ ))} +
+ ))} +
+ ); +} + +export default ShopFilters; diff --git a/apps/website/components/shop-search/shop-search.module.scss b/apps/website/components/shop-search/shop-search.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/components/shop-search/shop-search.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/components/shop-search/shop-search.spec.tsx b/apps/website/components/shop-search/shop-search.spec.tsx new file mode 100644 index 0000000..6bc3ac7 --- /dev/null +++ b/apps/website/components/shop-search/shop-search.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import ShopSearch from './shop-search'; + +describe('ShopSearch', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/website/components/shop-search/shop-search.tsx b/apps/website/components/shop-search/shop-search.tsx new file mode 100644 index 0000000..6aaa15d --- /dev/null +++ b/apps/website/components/shop-search/shop-search.tsx @@ -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 ( +
+ +
+
+ +
+ + +
+
+ ); +} + +export default ShopSearch; diff --git a/apps/website/libs/utils.ts b/apps/website/libs/utils.ts index 8cc8886..57fcf34 100644 --- a/apps/website/libs/utils.ts +++ b/apps/website/libs/utils.ts @@ -1,17 +1,98 @@ import delve from 'dlv'; import {ImageLoader, ImageLoaderProps} from "next/image"; +import {isEmpty, isEqual} from "radash"; import {environment} from "../environments/environment"; export type ImageFormatType = "default" | "thumbnail" | "medium" | "small"; -export const contentfulImageLoader: ImageLoader = ({ src, width }: ImageLoaderProps) => { +export interface PriceRange { + min: number; + max: number; +} + +export type SortType = 'pas' | 'pde' | 'alp'; + +export interface QueryParam { + param: string; + value: PriceRange | string | number[]; +} + +export const buildQueriesListFromQueryParams = (query): QueryParam[] => { + const queryMap = ['sort', 'cat', 'page', 'facets', 'filters', 'search']; + + return queryMap.map((param: string) => { + if (!isEmpty(query[param])) { + let value = !isEmpty(query[param]) ? query[param] : null; + + if (param === 'filters') { + value = query[param].split(',').map((value: string) => parseInt(value, 10)); + } + + if (param === 'facets') { + const values = query[param].split(':'); + value = {min: parseInt(values[0], 10), max: parseInt(values[1], 10)}; + } + + return ({param, value}); + } else { + return null; + } + }).filter((val) => !isEmpty(val)); +} + +export const findQueryParamsValue = (queryList: Array<{ param: string, value: T }>, params: string): T | null => { + const paramGET = queryList.find((val: { param: string, value: T }) => val.param === params); + return !isEmpty(paramGET) ? paramGET.value : null; +} + +export const getShopSortList = (): Array<{ value: SortType, label: string }> => { + return [ + {value: "pas", label: 'Prix croissant'}, + {value: "pde", label: 'Prix décroissant'}, + {value: "alp", label: 'Ordre alphabetique'}, + ]; +} + +export const buildShopParamsGET = (sort: SortType = 'pas', + page: number = null, + range: PriceRange = { + min: 0, + max: 0 + }, + cat: string = null, + filters: number[] = [], + search: string) => { + let url = `?sort=${sort}`; + + if (!isEmpty(range) && !isEqual(range, {min: 0, max: 0})) { + url += `&facets=${range.min}:${range.max}`; + } + if (!isEmpty(filters)) { + url += `&filters=${filters.join(',')}`; + } + if (!isEmpty(cat)) { + url += `&cat=${cat}`; + } + if (!isEmpty(page)) { + url += `&page=${page}`; + } + if (!isEmpty(search)) { + url += `&search=${search}`; + } + + return url; +} + +export const contentfulImageLoader: ImageLoader = ({src, width}: ImageLoaderProps) => { return `${src}?w=${width}` } + export const getCategoryUrl = (item): string => { const categorySlug = !delve(item, 'attributes', null) ? delve(item, 'category.data.attributes.slug', '') : delve(item, 'attributes.category.data.attributes.slug', ''); return '/blog/' + categorySlug; } + export const getPostUrl = (item): string => { const categorySlug = !delve(item, 'attributes', null) ? delve(item, 'category.data.attributes.slug', '') : delve(item, 'attributes.category.data.attributes.slug', ''); const postSlug = !delve(item, 'attributes', null) ? delve(item, 'slug', '') : delve(item, 'attributes.slug', ''); @@ -33,6 +114,7 @@ export const getStrapiImage = (item, format: ImageFormatType = 'default') => { return environment.strapiUrl + delve(image, "url", "/images/default.png"); } } + export const getStrapiImageSize = (item, format: ImageFormatType = 'default'): [number, number] => { const image = delve(item, 'attributes.image.data.attributes', {}); switch (format) { diff --git a/apps/website/next.config.js b/apps/website/next.config.js index 1de947f..deb1758 100644 --- a/apps/website/next.config.js +++ b/apps/website/next.config.js @@ -10,6 +10,9 @@ const nextConfig = { reactStrictMode: true, poweredByHeader: false, output: 'standalone', + compiler: { + styledComponents: true + }, nx: { // Set this to true if you would like to to use SVGR // See: https://github.com/gregberge/svgr diff --git a/apps/website/pages/_app.tsx b/apps/website/pages/_app.tsx index 8c56f07..ed0638c 100644 --- a/apps/website/pages/_app.tsx +++ b/apps/website/pages/_app.tsx @@ -5,9 +5,12 @@ import '@fortawesome/fontawesome-svg-core/styles.css'; import { faBagShopping, faChalkboardUser, + faChevronDown, + faChevronUp, faGraduationCap, faGuitar, faLinesLeaning, + faMagnifyingGlass, faRightToBracket, faScrewdriverWrench, faTabletScreenButton, @@ -33,6 +36,9 @@ library.add(faChalkboardUser); library.add(faLinesLeaning); library.add(faGuitar); library.add(faScrewdriverWrench); +library.add(faChevronDown); +library.add(faChevronUp); +library.add(faMagnifyingGlass); interface CustomAppProps extends AppProps { menuHeader: object[], @@ -43,10 +49,14 @@ interface CustomAppProps extends AppProps { const getPageMetadata = async (path) => { try { + if (path === '/_error') { + throw 'page error'; + } + console.log(path); return await axios.get(`${environment.strapiApiUrl}/pages?filters[slug]=${path === '/' ? 'home' : path.slice(1, path.length)}&populate=deep`); } catch (e) { console.error(e); - return Promise.resolve({}) + return Promise.resolve({}); } } @@ -88,17 +98,14 @@ CustomApp.getInitialProps = async (context) => { const menuFooter = await axios.get(`${environment.strapiApiUrl}/menus/2?nested&populate=deep`); let page = null; - console.log(context.ctx.query); if (context.ctx.query.category_slug && !context.ctx.query.post_slug) { page = await getCategoryMetadata(context.ctx.query.category_slug); } else if (context.ctx.query.category_slug && context.ctx.query.post_slug) { page = await getPostMetadata(context.ctx.query.post_slug); - } else if (Object.keys(context.ctx.query).length === 0) { + } else if (!context.ctx.query.category_slug && !context.ctx.query.post_slug) { page = await getPageMetadata(path); } - console.log(page); - return { ...appProps, menuHeader: delve(menuHeader, 'data.data.attributes.items.data', []), diff --git a/apps/website/pages/search/index.tsx b/apps/website/pages/search/index.tsx index d6e3bbf..6b0812f 100644 --- a/apps/website/pages/search/index.tsx +++ b/apps/website/pages/search/index.tsx @@ -9,7 +9,7 @@ import BlogSearch from "../../components/blog-search/blog-search"; import Categories from "../../components/categories/categories"; import styles from './index.module.scss'; import {useSelector} from "react-redux"; -import {selectSearchState} from "../../store/searchSlice"; +import {selectBlogSearchState} from "../../store/blogSearchSlice"; export async function getServerSideProps(context) { @@ -24,7 +24,7 @@ export async function getServerSideProps(context) { } export function Search({menuHeader, menuFooter, seo, categories, lastPublished}) { - const {results} = useSelector(selectSearchState); + const {results} = useSelector(selectBlogSearchState); return ( <> diff --git a/apps/website/pages/shop/[filter_slug]/[product_slug].module.scss b/apps/website/pages/shop/[filter_slug]/[product_slug].module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/pages/shop/[filter_slug]/[product_slug].module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/pages/shop/[filter_slug]/index.module.scss b/apps/website/pages/shop/[filter_slug]/index.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/pages/shop/[filter_slug]/index.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/pages/shop/[filter_slug]/index.tsx b/apps/website/pages/shop/[filter_slug]/index.tsx index e69de29..ee9b270 100644 --- a/apps/website/pages/shop/[filter_slug]/index.tsx +++ b/apps/website/pages/shop/[filter_slug]/index.tsx @@ -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 ( + <> + + +
+
+ + +
+
+

Filter page!

+ +
+
+
+
+
+ + ); +} + +export default Filter; diff --git a/apps/website/pages/shop/index.tsx b/apps/website/pages/shop/index.tsx index cf35386..ccb5a89 100644 --- a/apps/website/pages/shop/index.tsx +++ b/apps/website/pages/shop/index.tsx @@ -1,13 +1,100 @@ +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 + } + } +} -/* eslint-disable-next-line */ -export interface ShopProps {} +export function Shop({menuHeader, menuFooter, seo, sections, products, paginator, queries = [], filters = []}) { -export function Shop(props: ShopProps) { return ( -
-

Welcome to Shop!

-
+ <> + + +
+
+ +
+ +
+
+
+
+ ); } diff --git a/apps/website/pages/shop/results/index.module.scss b/apps/website/pages/shop/results/index.module.scss new file mode 100644 index 0000000..45c2aa4 --- /dev/null +++ b/apps/website/pages/shop/results/index.module.scss @@ -0,0 +1,7 @@ +/* + * Replace this with your own classes + * + * e.g. + * .container { + * } +*/ diff --git a/apps/website/pages/shop/results/index.tsx b/apps/website/pages/shop/results/index.tsx new file mode 100644 index 0000000..00c8201 --- /dev/null +++ b/apps/website/pages/shop/results/index.tsx @@ -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 ( + <> + + +
+
+ + +
+ +
+
+
+
+ + ); +} + +export default Result; diff --git a/apps/website/pages/styles.css b/apps/website/pages/styles.css index 9218dad..938afe8 100644 --- a/apps/website/pages/styles.css +++ b/apps/website/pages/styles.css @@ -1,6 +1,7 @@ @import 'tailwindcss/base.css'; @import 'tailwindcss/components.css'; @import 'tailwindcss/utilities.css'; +@import "react-input-range/lib/css/index.css"; html, body { position: relative; diff --git a/apps/website/store/searchSlice.ts b/apps/website/store/blogSearchSlice.ts similarity index 60% rename from apps/website/store/searchSlice.ts rename to apps/website/store/blogSearchSlice.ts index bae559e..35a6808 100644 --- a/apps/website/store/searchSlice.ts +++ b/apps/website/store/blogSearchSlice.ts @@ -3,24 +3,24 @@ import {HYDRATE} from "next-redux-wrapper"; import {AppState} from "./store"; -export interface SearchState { +export interface BlogSearchState { results: object[]; } // Initial state -const initialState: SearchState = { +const initialState: BlogSearchState = { results: [], }; // Actual Slice // @ts-ignore -export const searchSlice = createSlice({ - name: "search", +export const blogSearchSlice = createSlice({ + name: "blogSearch", initialState, reducers: { // Action to set the authentication status - setSearchState(state, action) { + setBlogSearchState(state, action) { // @ts-ignore state.results = action.payload; }, @@ -32,7 +32,7 @@ export const searchSlice = createSlice({ return { ...state, // @ts-ignore - ...action.payload.search, + ...action.payload.blogSearch, }; }, }, @@ -40,8 +40,8 @@ export const searchSlice = createSlice({ }, }); -export const {setSearchState} = searchSlice.actions; +export const {setBlogSearchState} = blogSearchSlice.actions; -export const selectSearchState = (state: AppState) => state.search; +export const selectBlogSearchState = (state: AppState) => state.blogSearch; -export default searchSlice.reducer; +export default blogSearchSlice.reducer; diff --git a/apps/website/store/store.ts b/apps/website/store/store.ts index c952af3..d3815a4 100644 --- a/apps/website/store/store.ts +++ b/apps/website/store/store.ts @@ -1,11 +1,14 @@ import {Action, configureStore, ThunkAction} from "@reduxjs/toolkit"; -import {searchSlice} from "./searchSlice"; import {createWrapper} from "next-redux-wrapper"; +import {blogSearchSlice} from "./blogSearchSlice"; +import {shopSearchSlice} from "./storeSearchSlice"; + const makeStore = () => configureStore({ reducer: { - [searchSlice.name]: searchSlice.reducer, + [blogSearchSlice.name]: blogSearchSlice.reducer, + [shopSearchSlice.name]: shopSearchSlice.reducer, }, devTools: true, }); diff --git a/apps/website/store/storeSearchSlice.ts b/apps/website/store/storeSearchSlice.ts new file mode 100644 index 0000000..c5f8731 --- /dev/null +++ b/apps/website/store/storeSearchSlice.ts @@ -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; diff --git a/apps/website/tailwind.config.js b/apps/website/tailwind.config.js index 364d5db..070124d 100644 --- a/apps/website/tailwind.config.js +++ b/apps/website/tailwind.config.js @@ -4,6 +4,10 @@ const {join} = require('path'); /** @type {import('tailwindcss').Config} */ module.exports = { content: [ + join( + __dirname, + "../../node_modules/flowbite-react/**/*.js" + ), join( __dirname, '{src,pages,components}/**/*!(*.stories|*.spec).{ts,tsx,html}' @@ -13,6 +17,8 @@ module.exports = { theme: { extend: {}, }, - plugins: [], + plugins: [ + require('flowbite/plugin') + ], darkMode: 'class' }; diff --git a/package-lock.json b/package-lock.json index 90236c9..eaea6c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1416,6 +1416,37 @@ } } }, + "@floating-ui/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.1.tgz", + "integrity": "sha512-LSqwPZkK3rYfD7GKoIeExXOyYx6Q1O4iqZWwIehDNuv3Dv425FIAE8PRwtAx1imEolFTHgBEcoFHm9MDnYgPCg==" + }, + "@floating-ui/dom": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.1.tgz", + "integrity": "sha512-Rt45SmRiV8eU+xXSB9t0uMYiQ/ZWGE/jumse2o3i5RGlyvcbqOF4q+1qBnzLE2kZ5JGhq0iMkcGXUKbFe7MpTA==", + "requires": { + "@floating-ui/core": "^1.2.1" + } + }, + "@floating-ui/react": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.16.0.tgz", + "integrity": "sha512-h+69TJSAY2R/k5rw+az56RzzDFc/Tg7EHn/qEgwkIVz56Zg9LlaRMMUvxkcvd+iN3CNFDLtEnDlsXnpshjsRsQ==", + "requires": { + "@floating-ui/react-dom": "^1.1.2", + "aria-hidden": "^1.1.3", + "tabbable": "^6.0.1" + } + }, + "@floating-ui/react-dom": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-1.3.0.tgz", + "integrity": "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==", + "requires": { + "@floating-ui/dom": "^1.2.1" + } + }, "@fortawesome/fontawesome-common-types": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.1.tgz", @@ -4492,6 +4523,14 @@ "sprintf-js": "~1.0.2" } }, + "aria-hidden": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.2.tgz", + "integrity": "sha512-6y/ogyDTk/7YAe91T3E2PR1ALVKyM2QbTio5HwM+N1Q6CMlCKhvClyIjkckBswa0f2xJhjsfzIGa1yVSe1UMVA==", + "requires": { + "tslib": "^2.0.0" + } + }, "aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", @@ -4603,6 +4642,11 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "autobind-decorator": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-1.4.3.tgz", + "integrity": "sha512-FRzT10Vc0lzgDOhMTpm9a2kZF6Q+MMGwd6Y7OGgHigMZwGz7vpN4qH9ifiPTum8mhJQV9UqLPperHxc9yalAAA==" + }, "autoprefixer": { "version": "10.4.13", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", @@ -5914,6 +5958,11 @@ "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==", "dev": true }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -6153,6 +6202,11 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "easy-bem": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/easy-bem/-/easy-bem-1.1.1.tgz", + "integrity": "sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A==" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -7440,6 +7494,17 @@ "mini-svg-data-uri": "^1.4.3" } }, + "flowbite-react": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/flowbite-react/-/flowbite-react-0.3.8.tgz", + "integrity": "sha512-IzbpvnUBDXsdf3HflbYv2W1lmTXITizMaX4G0SYoh/GxSp+25E97yNuwdBItwtCacUU1MJLwqIYXeicAxScRfA==", + "requires": { + "@floating-ui/react": "^0.16.0", + "classnames": "^2.3.2", + "react-icons": "^4.6.0", + "react-indiana-drag-scroll": "^2.2.0" + } + }, "follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -11292,6 +11357,11 @@ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" }, + "radash": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/radash/-/radash-10.7.0.tgz", + "integrity": "sha512-dz5NUcGnvn080kKJnyPtqVnP4MWoiwp5qQhEQFK/qMJxCjffQd8tMR4cybxy6y5hupYa5UkSlJRSRY2F7GPBxQ==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11369,6 +11439,30 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.42.1.tgz", "integrity": "sha512-2UIGqwMZksd5HS55crTT1ATLTr0rAI4jS7yVuqTaoRVDhY2Qc4IyjskCmpnmdYqUNOYFy04vW253tb2JRVh+IQ==" }, + "react-icons": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.7.1.tgz", + "integrity": "sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw==" + }, + "react-indiana-drag-scroll": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-indiana-drag-scroll/-/react-indiana-drag-scroll-2.2.0.tgz", + "integrity": "sha512-+W/3B2OQV0FrbdnsoIo4dww/xpH0MUQJz6ziQb7H+oBko3OCbXuzDFYnho6v6yhGrYDNWYPuFUewb89IONEl/A==", + "requires": { + "classnames": "^2.2.6", + "debounce": "^1.2.0", + "easy-bem": "^1.1.1" + } + }, + "react-input-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-input-range/-/react-input-range-1.3.0.tgz", + "integrity": "sha512-G//kJqUHo7zQA5PuGZNKhuzhGcj83FJsv62tcP4Eo61DUC/0usHPYxFfIZ3zOfdMWuWEaduD6N4lNsZMmaOJgw==", + "requires": { + "autobind-decorator": "^1.3.4", + "prop-types": "^15.5.8" + } + }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -12580,6 +12674,11 @@ } } }, + "tabbable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.1.tgz", + "integrity": "sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg==" + }, "tailwindcss": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz", diff --git a/package.json b/package.json index d4c91ac..fd0834c 100644 --- a/package.json +++ b/package.json @@ -23,15 +23,18 @@ "date-fns": "^2.29.3", "dlv": "1.1.3", "flowbite": "1.6.2", + "flowbite-react": "^0.3.8", "meilisearch": "^0.31.1", "next": "13.1.1", "next-auth": "4.18.8", "next-redux-wrapper": "^8.1.0", "next-themes": "^0.2.1", + "radash": "^10.7.0", "react": "18.2.0", "react-dom": "18.2.0", "react-helmet": "6.0.0", "react-hook-form": "7.42.1", + "react-input-range": "^1.3.0", "react-player": "^2.11.2", "react-redux": "^8.0.5", "react-share": "^4.4.1",