From a70182aff7028c4f852a26f4b4635882e1507e18 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Mon, 24 Oct 2022 16:57:18 +0300 Subject: [PATCH] feat: add syncing filters state with URL --- package-lock.json | 83 +++++++++++++++++-- package.json | 1 + .../pages/ExplorePage/ExplorePage.tsx | 15 ++-- .../ExplorePage/Filters/FiltersModal.tsx | 14 ++-- .../Projects/pages/ExplorePage/helpers.ts | 50 +++++++++++ src/utils/hooks/useQueryState.ts | 30 +++++++ 6 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 src/features/Projects/pages/ExplorePage/helpers.ts create mode 100644 src/utils/hooks/useQueryState.ts diff --git a/package-lock.json b/package-lock.json index e6772cd..849b067 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "makers-bolt-fun", + "name": "lightning-landscape", "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "makers-bolt-fun", + "name": "lightning-landscape", "version": "0.1.0", "dependencies": { "@apollo/client": "^3.6.9", @@ -70,6 +70,7 @@ "passport": "^0.6.0", "passport-lnurl-auth": "^1.5.0", "qrcode.react": "^3.0.2", + "qs": "^6.11.0", "react": "^18.0.0", "react-accessible-accordion": "^5.0.0", "react-confetti": "^6.0.1", @@ -19310,6 +19311,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/body-scroll-lock": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz", @@ -24983,6 +24998,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/express/node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -31758,6 +31787,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/lnurl/node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/lnurl/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -61957,9 +62000,9 @@ } }, "node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dependencies": { "side-channel": "^1.0.4" }, @@ -85911,6 +85954,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } } } }, @@ -90334,6 +90385,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -95490,6 +95549,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -118434,9 +118501,9 @@ "requires": {} }, "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "requires": { "side-channel": "^1.0.4" } diff --git a/package.json b/package.json index c13d38a..9181760 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "passport": "^0.6.0", "passport-lnurl-auth": "^1.5.0", "qrcode.react": "^3.0.2", + "qs": "^6.11.0", "react": "^18.0.0", "react-accessible-accordion": "^5.0.0", "react-confetti": "^6.0.1", diff --git a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx index c1d22aa..98964d3 100644 --- a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx +++ b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx @@ -4,7 +4,7 @@ import { useExplorePageQuery } from 'src/graphql'; import ProjectsGrid from './ProjectsGrid/ProjectsGrid'; import { Helmet } from "react-helmet"; import Categories, { Category } from '../../Components/Categories/Categories'; -import { useCallback, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import Header from './Header/Header'; import Button from 'src/Components/Button/Button'; import { useAppDispatch } from 'src/utils/hooks'; @@ -15,6 +15,7 @@ import { NetworkStatus } from '@apollo/client'; import { FiSliders } from 'react-icons/fi'; import { HiOutlineChevronDoubleDown } from 'react-icons/hi' import { ProjectsFilters } from './Filters/FiltersModal'; +import { getFiltersFromUrl, useUpdateUrlWithFilters } from './helpers'; const UPDATE_FILTERS_ACTION = createAction>('PROJECTS_FILTERS_UPDATED')({}) @@ -32,11 +33,15 @@ const PAGE_SIZE = 20; export default function ExplorePage() { const dispatch = useAppDispatch(); - const [filters, setFilters] = useState | null>(null) + const [filters, setFilters] = useState | null>(getFiltersFromUrl) const [selectedCategory, setSelectedCategory] = useState(null) const projectsLength = useRef(0); const [canFetchMore, setCanFetchMore] = useState(true); + useUpdateUrlWithFilters(filters) + + // useQueryState(removeEmptyFitlers(filters ?? {})); + const { queryFilters, hasSearchFilters } = useMemo(() => { let filter: QueryFilter = {} let hasSearchFilters = false; @@ -155,8 +160,8 @@ export default function ExplorePage() {
-
-
selectCategoryTab(v)} />
+
+
selectCategoryTab(v)} />
-
+
- +
- - + +
diff --git a/src/features/Projects/pages/ExplorePage/helpers.ts b/src/features/Projects/pages/ExplorePage/helpers.ts new file mode 100644 index 0000000..2ee7b14 --- /dev/null +++ b/src/features/Projects/pages/ExplorePage/helpers.ts @@ -0,0 +1,50 @@ + +import { useQueryState } from 'src/utils/hooks/useQueryState'; +import qs from "qs" +import { useLocation, useNavigate } from 'react-router-dom'; +import { ProjectsFilters } from './Filters/FiltersModal'; +import { useEffect } from 'react'; + +export function getFiltersFromUrl(): Partial { + const qsValues = qs.parse(window.location.search, { ignoreQueryPrefix: true }) as Partial; + return removeEmptyFitlers(qsValues) +} + +export function removeEmptyFitlers(filters: Partial): Partial { + let res: Partial = {} + + if (filters.yearFounded !== 'any') + res.yearFounded = filters.yearFounded; + + if (filters.projectLicense !== 'any') + res.projectLicense = filters.projectLicense; + + if (filters.projectStatus !== 'any') + res.projectStatus = filters.projectStatus; + + if (filters.categoriesIds && filters.categoriesIds?.length > 0) + res.categoriesIds = filters.categoriesIds; + + if (filters.tagsIds && filters.tagsIds?.length > 0) + res.tagsIds = filters.tagsIds; + + return res; +} + + +export const useUpdateUrlWithFilters = (state?: Partial | null) => { + const location = useLocation() + const navigate = useNavigate() + + useEffect(() => { + + const queryString = qs.stringify( + removeEmptyFitlers(state ?? {}), + { skipNulls: true } + ) + + navigate(`${location.pathname}?${queryString}`, { replace: true }); + + }, [location.pathname, location.search, navigate, state]); + +} \ No newline at end of file diff --git a/src/utils/hooks/useQueryState.ts b/src/utils/hooks/useQueryState.ts new file mode 100644 index 0000000..d510b58 --- /dev/null +++ b/src/utils/hooks/useQueryState.ts @@ -0,0 +1,30 @@ +import { useCallback, useEffect } from "react" +import { useNavigate, useLocation } from "react-router-dom" + +import qs from "qs" + + + + +export const useQueryState = (state?: Record | null) => { + const location = useLocation() + const navigate = useNavigate() + + useEffect(() => { + + const existingQueries = qs.parse(location.search, { + ignoreQueryPrefix: true, + }) + + const queryString = qs.stringify( + { ...existingQueries, ...(state ?? {}) }, + { skipNulls: true } + ) + + navigate(`${location.pathname}?${queryString}`, { replace: true }); + + }, [location.pathname, location.search, navigate, state]); + + + return qs.parse(location.search, { ignoreQueryPrefix: true }) +} \ No newline at end of file