Skip to content

Commit

Permalink
fix(products page): refactor code and fix search bar
Browse files Browse the repository at this point in the history
fix search bar text disappears when refresh and infinite loop when click on clear button

fixes #87
  • Loading branch information
riadhmouamnia committed Nov 14, 2023
1 parent 1c0d974 commit 0d4866d
Show file tree
Hide file tree
Showing 20 changed files with 836 additions and 96 deletions.
74 changes: 74 additions & 0 deletions src/components/Items/Filters/CategoryFilter.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Menu as='div' className='relative w-full flex justify-center text-left'>
<Menu.Button className='flex items-center gap-2 w-full justify-between border bg-white input-sm rounded-full tracking-wider'>
<div className='flex gap-2 items-center'>
<BiCategory className='w-5 h-5' />
<span>
{queryParams.category
? t(`categories:${queryParams.category}`)
: t("productsPage:all")}
</span>
</div>
<IoMdArrowDropdown />
</Menu.Button>

<Transition
as={Fragment}
enter='transition ease-out duration-100'
enterFrom='transform opacity-0 scale-95'
enterTo='transform opacity-100 scale-100'
leave='transition ease-in duration-75'
leaveFrom='transform opacity-100 scale-100'
leaveTo='transform opacity-0 scale-95'
>
<Menu.Items className='absolute max-h-52 overflow-y-scroll no-scrollbar bg-white z-10 rounded-lg top-10 w-full ring-1 ring-black ring-opacity-5 drop-shadow-xl'>
<div className='py-1'>
{categories.map(({ name, dataKey }) => (
<Link
scroll={false}
key={dataKey}
href={{
pathname: "/products",
query: { ...queryParams, category: dataKey },
}}
>
<Menu.Item>
{({ active }) => (
<p
className={classNames(
active
? "bg-gray-100 text-gray-900"
: "text-gray-700",
"block px-4 py-2 tracking-wider my-2"
)}
>
{name}
</p>
)}
</Menu.Item>
</Link>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
</>
);
}

export default CategoryFilter;
16 changes: 16 additions & 0 deletions src/components/Items/Filters/ClearFilterButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";
import { MdFilterListOff } from "react-icons/md";

function ClearFilterButton({ t, handleClearFilters }) {
return (
<button
onClick={handleClearFilters}
className='btn btn-sm rounded-full btn-neutral normal-case font-light tracking-wider'
>
<MdFilterListOff />
{t("productsPage:clearFilter")}
</button>
);
}

export default ClearFilterButton;
72 changes: 72 additions & 0 deletions src/components/Items/Filters/ListingTypeFilter.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Menu as='div' className='relative w-full flex justify-center text-left'>
<Menu.Button className='flex items-center gap-2 w-full justify-between border bg-white input-sm rounded-full tracking-wider'>
<div className='flex gap-2 items-center'>
<TbSitemap className='w-5 h-5' />
<span>
{queryParams.listingType
? t(`addItem:${queryParams.listingType}`)
: "Listing type"}
</span>
</div>
<IoMdArrowDropdown />
</Menu.Button>

<Transition
as={Fragment}
enter='transition ease-out duration-100'
enterFrom='transform opacity-0 scale-95'
enterTo='transform opacity-100 scale-100'
leave='transition ease-in duration-75'
leaveFrom='transform opacity-100 scale-100'
leaveTo='transform opacity-0 scale-95'
>
<Menu.Items className='absolute max-h-52 overflow-y-scroll no-scrollbar bg-white z-10 rounded-lg top-10 w-full ring-1 ring-black ring-opacity-5 drop-shadow-xl'>
<div className='py-1'>
{listingTypes.map((type) => (
<Link
scroll={false}
key={type}
href={{
pathname: "/products",
query: { ...queryParams, listingType: type },
}}
>
<Menu.Item>
{({ active }) => (
<p
className={classNames(
active
? "bg-gray-100 text-gray-900"
: "text-gray-700",
"block px-4 py-2 tracking-wider my-2"
)}
>
{t(`addItem:${type}`)}
</p>
)}
</Menu.Item>
</Link>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
</>
);
}

export default ListingTypeFilter;
75 changes: 75 additions & 0 deletions src/components/Items/Filters/LocationFilter.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Menu as='div' className='relative w-full flex justify-center text-left'>
<Menu.Button className='flex items-center gap-2 w-full justify-between border bg-white input-sm rounded-full tracking-wider'>
<div className='flex gap-2 items-center'>
<HiOutlineLocationMarker className='w-5 h-5' />
<span>
{queryParams.location
? t(`states:${queryParams.location}`)
: t("productsPage:location")}
</span>
</div>
<IoMdArrowDropdown />
</Menu.Button>

<Transition
as={Fragment}
enter='transition ease-out duration-100'
enterFrom='transform opacity-0 scale-95'
enterTo='transform opacity-100 scale-100'
leave='transition ease-in duration-75'
leaveFrom='transform opacity-100 scale-100'
leaveTo='transform opacity-0 scale-95'
>
<Menu.Items className='absolute max-h-52 no-scrollbar overflow-y-scroll bg-white z-10 rounded-lg top-10 w-full ring-1 ring-black ring-opacity-5 drop-shadow-xl'>
<div className='py-1'>
{states.map(({ name, dataKey }) => (
<Link
scroll={false}
key={dataKey}
href={{
pathname: "/products",
query: { ...queryParams, location: dataKey },
}}
>
<Menu.Item>
{({ active }) => (
<p
className={classNames(
active
? "bg-gray-100 text-gray-900"
: "text-gray-700",
"block px-4 py-2 tracking-wider my-2"
)}
>
{name}
</p>
)}
</Menu.Item>
</Link>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
</>
);
}

export default LocationFilter;
57 changes: 57 additions & 0 deletions src/components/Items/Filters/Pagination.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='flex items-center justify-center md:justify-between flex-wrap gap-4'>
<div className='flex gap-4'>
<button
className='btn btn-sm normal-case font-normal tracking-wide'
disabled={!hasPrev}
onClick={() => handleChangePage("prev")}
>
<BiLeftArrow /> Previous
</button>
<button
className='btn btn-sm normal-case font-normal tracking-wide'
disabled={!hasNext}
onClick={() => handleChangePage("next")}
>
Next <BiRightArrow />
</button>
</div>
<div>
<p className='font-normal opacity-50 text-sm tracking-wide'>
page: {page} of {totalPages}
</p>
</div>
</div>
);
}

export default Pagination;
12 changes: 12 additions & 0 deletions src/components/Items/Filters/__test__/CategoryFilter.test.js
Original file line number Diff line number Diff line change
@@ -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(<CategoryFilter queryParams={queryParams} t={mockT} />)
.toJSON();
expect(tree).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -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(<ClearFilterButton t={mockT} />).toJSON();
expect(tree).toMatchSnapshot();
});
12 changes: 12 additions & 0 deletions src/components/Items/Filters/__test__/ListingTypeFilter.test.js
Original file line number Diff line number Diff line change
@@ -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(<ListingTypeFilter queryParams={queryParams} t={mockT} />)
.toJSON();
expect(tree).toMatchSnapshot();
});
12 changes: 12 additions & 0 deletions src/components/Items/Filters/__test__/LocationFilter.test.js
Original file line number Diff line number Diff line change
@@ -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(<LocationFilter queryParams={queryParams} t={mockT} />)
.toJSON();
expect(tree).toMatchSnapshot();
});
21 changes: 21 additions & 0 deletions src/components/Items/Filters/__test__/Pagination.test.js
Original file line number Diff line number Diff line change
@@ -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(
<Pagination
page={1}
totalItems={4}
totalPages={3}
pageSize={4}
queryParams={queryParams}
t={mockT}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
Loading

0 comments on commit 0d4866d

Please sign in to comment.