From 0d4866d2ed2b70f43d2bf7a9b9f4a110fb4750fa Mon Sep 17 00:00:00 2001 From: Riadh mouamnia Date: Tue, 14 Nov 2023 13:52:03 +0100 Subject: [PATCH] fix(products page): refactor code and fix search bar fix search bar text disappears when refresh and infinite loop when click on clear button fixes #87 --- .../Items/Filters/CategoryFilter.jsx | 74 ++++++++++++ .../Items/Filters/ClearFilterButton.jsx | 16 +++ .../Items/Filters/ListingTypeFilter.jsx | 72 +++++++++++ .../Items/Filters/LocationFilter.jsx | 75 ++++++++++++ src/components/Items/Filters/Pagination.jsx | 57 +++++++++ .../Filters/__test__/CategoryFilter.test.js | 12 ++ .../__test__/ClearFilterButton.test.js | 9 ++ .../__test__/ListingTypeFilter.test.js | 12 ++ .../Filters/__test__/LocationFilter.test.js | 12 ++ .../Items/Filters/__test__/Pagination.test.js | 21 ++++ .../__snapshots__/CategoryFilter.test.js.snap | 63 ++++++++++ .../ClearFilterButton.test.js.snap | 30 +++++ .../ListingTypeFilter.test.js.snap | 84 +++++++++++++ .../__snapshots__/LocationFilter.test.js.snap | 71 +++++++++++ .../__snapshots__/Pagination.test.js.snap | 72 +++++++++++ src/components/Items/index.jsx | 114 ++++++++++++++++++ .../ProductFiltering/ClearFilterButton.jsx | 9 +- .../__snapshots__/SearchBar.test.js.snap | 1 - src/components/SearchBar/index.jsx | 45 +++---- src/pages/products/index.jsx | 83 ++----------- 20 files changed, 836 insertions(+), 96 deletions(-) create mode 100644 src/components/Items/Filters/CategoryFilter.jsx create mode 100644 src/components/Items/Filters/ClearFilterButton.jsx create mode 100644 src/components/Items/Filters/ListingTypeFilter.jsx create mode 100644 src/components/Items/Filters/LocationFilter.jsx create mode 100644 src/components/Items/Filters/Pagination.jsx create mode 100644 src/components/Items/Filters/__test__/CategoryFilter.test.js create mode 100644 src/components/Items/Filters/__test__/ClearFilterButton.test.js create mode 100644 src/components/Items/Filters/__test__/ListingTypeFilter.test.js create mode 100644 src/components/Items/Filters/__test__/LocationFilter.test.js create mode 100644 src/components/Items/Filters/__test__/Pagination.test.js create mode 100644 src/components/Items/Filters/__test__/__snapshots__/CategoryFilter.test.js.snap create mode 100644 src/components/Items/Filters/__test__/__snapshots__/ClearFilterButton.test.js.snap create mode 100644 src/components/Items/Filters/__test__/__snapshots__/ListingTypeFilter.test.js.snap create mode 100644 src/components/Items/Filters/__test__/__snapshots__/LocationFilter.test.js.snap create mode 100644 src/components/Items/Filters/__test__/__snapshots__/Pagination.test.js.snap create mode 100644 src/components/Items/index.jsx diff --git a/src/components/Items/Filters/CategoryFilter.jsx b/src/components/Items/Filters/CategoryFilter.jsx new file mode 100644 index 0000000..79520eb --- /dev/null +++ b/src/components/Items/Filters/CategoryFilter.jsx @@ -0,0 +1,74 @@ +import { Menu, Transition } from "@headlessui/react"; +import Link from "next/link"; +import { Fragment } from "react"; +import { BiCategory } from "react-icons/bi"; +import { IoMdArrowDropdown } from "react-icons/io"; + +import getAllCategories from "@/lib/getAllCategories"; + +function CategoryFilter({ t, queryParams }) { + function classNames(...classes) { + return classes.filter(Boolean).join(" "); + } + const categories = getAllCategories(t); + + return ( + <> + + +
+ + + {queryParams.category + ? t(`categories:${queryParams.category}`) + : t("productsPage:all")} + +
+ +
+ + + +
+ {categories.map(({ name, dataKey }) => ( + + + {({ active }) => ( +

+ {name} +

+ )} +
+ + ))} +
+
+
+
+ + ); +} + +export default CategoryFilter; diff --git a/src/components/Items/Filters/ClearFilterButton.jsx b/src/components/Items/Filters/ClearFilterButton.jsx new file mode 100644 index 0000000..4d1e720 --- /dev/null +++ b/src/components/Items/Filters/ClearFilterButton.jsx @@ -0,0 +1,16 @@ +import React from "react"; +import { MdFilterListOff } from "react-icons/md"; + +function ClearFilterButton({ t, handleClearFilters }) { + return ( + + ); +} + +export default ClearFilterButton; diff --git a/src/components/Items/Filters/ListingTypeFilter.jsx b/src/components/Items/Filters/ListingTypeFilter.jsx new file mode 100644 index 0000000..c5ea0fe --- /dev/null +++ b/src/components/Items/Filters/ListingTypeFilter.jsx @@ -0,0 +1,72 @@ +import { Menu, Transition } from "@headlessui/react"; +import Link from "next/link"; +import { Fragment } from "react"; +import { IoMdArrowDropdown } from "react-icons/io"; +import { TbSitemap } from "react-icons/tb"; + +function ListingTypeFilter({ t, queryParams }) { + function classNames(...classes) { + return classes.filter(Boolean).join(" "); + } + const listingTypes = ["exchangeButton", "requestButton", "donationButton"]; + + return ( + <> + + +
+ + + {queryParams.listingType + ? t(`addItem:${queryParams.listingType}`) + : "Listing type"} + +
+ +
+ + + +
+ {listingTypes.map((type) => ( + + + {({ active }) => ( +

+ {t(`addItem:${type}`)} +

+ )} +
+ + ))} +
+
+
+
+ + ); +} + +export default ListingTypeFilter; diff --git a/src/components/Items/Filters/LocationFilter.jsx b/src/components/Items/Filters/LocationFilter.jsx new file mode 100644 index 0000000..de7705f --- /dev/null +++ b/src/components/Items/Filters/LocationFilter.jsx @@ -0,0 +1,75 @@ +import { Menu, Transition } from "@headlessui/react"; +import Link from "next/link"; +import { Fragment } from "react"; +import { HiOutlineLocationMarker } from "react-icons/hi"; +import { IoMdArrowDropdown } from "react-icons/io"; + +import getAllStates from "@/lib/getAllStates"; + +function LocationFilter({ t, queryParams }) { + function classNames(...classes) { + return classes.filter(Boolean).join(" "); + } + //This is to test the LocationFilter + const states = getAllStates(t); + + return ( + <> + + +
+ + + {queryParams.location + ? t(`states:${queryParams.location}`) + : t("productsPage:location")} + +
+ +
+ + + +
+ {states.map(({ name, dataKey }) => ( + + + {({ active }) => ( +

+ {name} +

+ )} +
+ + ))} +
+
+
+
+ + ); +} + +export default LocationFilter; diff --git a/src/components/Items/Filters/Pagination.jsx b/src/components/Items/Filters/Pagination.jsx new file mode 100644 index 0000000..224bf0d --- /dev/null +++ b/src/components/Items/Filters/Pagination.jsx @@ -0,0 +1,57 @@ +import { useRouter } from "next/router"; +import { BiLeftArrow, BiRightArrow } from "react-icons/bi"; + +function Pagination({ page, pageSize, totalItems, queryParams, totalPages }) { + const router = useRouter(); + + const hasPrev = pageSize * (parseInt(page) - 1) > 0; + const hasNext = pageSize * (parseInt(page) - 1) + pageSize < totalItems; + + const handleChangePage = (type) => { + type === "prev" + ? router.push( + { + pathname: `/products`, + query: { ...queryParams, page: page - 1 }, + }, + undefined, + { scroll: false } + ) + : router.push( + { + pathname: `/products`, + query: { ...queryParams, page: page + 1 }, + }, + undefined, + { scroll: false } + ); + }; + + return ( +
+
+ + +
+
+

+ page: {page} of {totalPages} +

+
+
+ ); +} + +export default Pagination; diff --git a/src/components/Items/Filters/__test__/CategoryFilter.test.js b/src/components/Items/Filters/__test__/CategoryFilter.test.js new file mode 100644 index 0000000..9f4227e --- /dev/null +++ b/src/components/Items/Filters/__test__/CategoryFilter.test.js @@ -0,0 +1,12 @@ +import renderer from "react-test-renderer"; + +import CategoryFilter from "../CategoryFilter"; + +it("renders correctly", () => { + const queryParams = {}; + const mockT = jest.fn(); + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); +}); diff --git a/src/components/Items/Filters/__test__/ClearFilterButton.test.js b/src/components/Items/Filters/__test__/ClearFilterButton.test.js new file mode 100644 index 0000000..0255d53 --- /dev/null +++ b/src/components/Items/Filters/__test__/ClearFilterButton.test.js @@ -0,0 +1,9 @@ +import renderer from "react-test-renderer"; + +import ClearFilterButton from "../ClearFilterButton"; + +it("renders correctly", () => { + const mockT = jest.fn(); + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); +}); diff --git a/src/components/Items/Filters/__test__/ListingTypeFilter.test.js b/src/components/Items/Filters/__test__/ListingTypeFilter.test.js new file mode 100644 index 0000000..9bd244c --- /dev/null +++ b/src/components/Items/Filters/__test__/ListingTypeFilter.test.js @@ -0,0 +1,12 @@ +import renderer from "react-test-renderer"; + +import ListingTypeFilter from "../ListingTypeFilter"; + +it("renders correctly", () => { + const queryParams = {}; + const mockT = jest.fn(); + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); +}); diff --git a/src/components/Items/Filters/__test__/LocationFilter.test.js b/src/components/Items/Filters/__test__/LocationFilter.test.js new file mode 100644 index 0000000..1f02f97 --- /dev/null +++ b/src/components/Items/Filters/__test__/LocationFilter.test.js @@ -0,0 +1,12 @@ +import renderer from "react-test-renderer"; + +import LocationFilter from "../LocationFilter"; + +it("renders correctly", () => { + const queryParams = {}; + const mockT = jest.fn(); + const tree = renderer + .create() + .toJSON(); + expect(tree).toMatchSnapshot(); +}); diff --git a/src/components/Items/Filters/__test__/Pagination.test.js b/src/components/Items/Filters/__test__/Pagination.test.js new file mode 100644 index 0000000..f1237fe --- /dev/null +++ b/src/components/Items/Filters/__test__/Pagination.test.js @@ -0,0 +1,21 @@ +import renderer from "react-test-renderer"; + +import Pagination from "../Pagination"; + +it("renders correctly", () => { + const queryParams = {}; + const mockT = jest.fn(); + const tree = renderer + .create( + + ) + .toJSON(); + expect(tree).toMatchSnapshot(); +}); diff --git a/src/components/Items/Filters/__test__/__snapshots__/CategoryFilter.test.js.snap b/src/components/Items/Filters/__test__/__snapshots__/CategoryFilter.test.js.snap new file mode 100644 index 0000000..4d36c33 --- /dev/null +++ b/src/components/Items/Filters/__test__/__snapshots__/CategoryFilter.test.js.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+ +
+`; diff --git a/src/components/Items/Filters/__test__/__snapshots__/ClearFilterButton.test.js.snap b/src/components/Items/Filters/__test__/__snapshots__/ClearFilterButton.test.js.snap new file mode 100644 index 0000000..54ffc20 --- /dev/null +++ b/src/components/Items/Filters/__test__/__snapshots__/ClearFilterButton.test.js.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + +`; diff --git a/src/components/Items/Filters/__test__/__snapshots__/ListingTypeFilter.test.js.snap b/src/components/Items/Filters/__test__/__snapshots__/ListingTypeFilter.test.js.snap new file mode 100644 index 0000000..df209ca --- /dev/null +++ b/src/components/Items/Filters/__test__/__snapshots__/ListingTypeFilter.test.js.snap @@ -0,0 +1,84 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+ +
+`; diff --git a/src/components/Items/Filters/__test__/__snapshots__/LocationFilter.test.js.snap b/src/components/Items/Filters/__test__/__snapshots__/LocationFilter.test.js.snap new file mode 100644 index 0000000..e34437c --- /dev/null +++ b/src/components/Items/Filters/__test__/__snapshots__/LocationFilter.test.js.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+ +
+`; diff --git a/src/components/Items/Filters/__test__/__snapshots__/Pagination.test.js.snap b/src/components/Items/Filters/__test__/__snapshots__/Pagination.test.js.snap new file mode 100644 index 0000000..c116ba6 --- /dev/null +++ b/src/components/Items/Filters/__test__/__snapshots__/Pagination.test.js.snap @@ -0,0 +1,72 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+
+ + +
+
+

+ page: + 1 + of + 3 +

+
+
+`; diff --git a/src/components/Items/index.jsx b/src/components/Items/index.jsx new file mode 100644 index 0000000..0d5ddec --- /dev/null +++ b/src/components/Items/index.jsx @@ -0,0 +1,114 @@ +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import { BsBoxSeam } from "react-icons/bs"; +import { IoAddCircleOutline } from "react-icons/io5"; +import { MdFilterList } from "react-icons/md"; + +import CategoryFilter from "./Filters/CategoryFilter"; +import ClearFilterButton from "./Filters/ClearFilterButton"; +import ListingTypeFilter from "./Filters/ListingTypeFilter"; +import LocationFilter from "./Filters/LocationFilter"; +import Pagination from "./Filters/Pagination"; +import ProductCard from "../ProductCard"; +import SearchBar from "../SearchBar"; + +function Items({ + t, + queryParams, + totalItems, + items, + page, + totalPages, + pageSize, +}) { + const router = useRouter(); + const [searchText, setSearchText] = useState(queryParams.search || ""); + + useEffect(() => { + if (!queryParams.search) { + setSearchText(""); + } + }, [queryParams.search]); + + const handleClearFilters = async () => { + router.push( + { + pathname: "/products", + query: undefined, + }, + undefined, + { scroll: false } + ); + }; + return ( +
+
+

+ Filters +

+ + + + + +
+
+
+

+ + {t("productsPage:productsList")} + | {totalItems} +

+ + + + {t("common:buttons:addItem")} + +
+
+
+ {items.map((item) => ( + + + + ))} + {!items.length &&

No items found...

} +
+ +
+
+
+ ); +} + +export default Items; diff --git a/src/components/ProductFiltering/ClearFilterButton.jsx b/src/components/ProductFiltering/ClearFilterButton.jsx index 08a9c5c..c7209ad 100644 --- a/src/components/ProductFiltering/ClearFilterButton.jsx +++ b/src/components/ProductFiltering/ClearFilterButton.jsx @@ -7,7 +7,14 @@ function ClearFilterButton({ t }) { return (