Skip to content

Commit

Permalink
Merge pull request #88 from 202306-NEA-DZ-FEW/87-products-page-enhanc…
Browse files Browse the repository at this point in the history
…ement

87 products page enhancement
  • Loading branch information
Bushra369 committed Nov 14, 2023
2 parents 258708b + 0d4866d commit c635e42
Show file tree
Hide file tree
Showing 38 changed files with 1,520 additions and 128 deletions.
1 change: 1 addition & 0 deletions src/components/AddItemForm/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function AddItemForm({ t, initialLocale, categories, states }) {
const imagesUrl = await uploadImages(imageRef, images);
const valuesWithImages = {
...values,
title: values.title.toLowerCase(),
id,
uid: user.uid,
images: imagesUrl,
Expand Down
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 c635e42

Please sign in to comment.