From 61087da2d30220b4a4e2be45be52b6c595d304b6 Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Mon, 21 Apr 2025 20:16:31 -0700 Subject: [PATCH 01/17] add tag attribute --- backend/src/controllers/products.ts | 3 ++- backend/src/models/product.ts | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/src/controllers/products.ts b/backend/src/controllers/products.ts index 16723d9..00304ac 100644 --- a/backend/src/controllers/products.ts +++ b/backend/src/controllers/products.ts @@ -65,7 +65,7 @@ export const addProduct = [ upload.single("image"), async (req: AuthenticatedRequest, res: Response) => { try { - const { name, price, description } = req.body; + const { name, price, description, tags } = req.body; const userId = req.user.id; const userEmail = req.user.userEmail; if (!name || !price || !userEmail) { @@ -92,6 +92,7 @@ export const addProduct = [ description, userEmail, image, + tags, timeCreated: new Date(), timeUpdated: new Date(), }); diff --git a/backend/src/models/product.ts b/backend/src/models/product.ts index a8cc9a9..c96c795 100644 --- a/backend/src/models/product.ts +++ b/backend/src/models/product.ts @@ -25,6 +25,11 @@ const productSchema = new Schema({ required: true, }, image: { type: String }, + tags: { + type: String, + enum: ['Electronics', 'School Supplies', 'Dorm Essentials', 'Furniture', 'Clothes', 'Miscellaneous'], + required: false + } }); type Product = InferSchemaType; From 8335db1c0301863b358863bd46b7b549262c52de Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Mon, 21 Apr 2025 21:09:49 -0700 Subject: [PATCH 02/17] select tags in add product form --- backend/src/models/product.ts | 2 +- frontend/src/pages/AddProduct.tsx | 61 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/backend/src/models/product.ts b/backend/src/models/product.ts index c96c795..4fe72b8 100644 --- a/backend/src/models/product.ts +++ b/backend/src/models/product.ts @@ -26,7 +26,7 @@ const productSchema = new Schema({ }, image: { type: String }, tags: { - type: String, + type: [String], enum: ['Electronics', 'School Supplies', 'Dorm Essentials', 'Furniture', 'Clothes', 'Miscellaneous'], required: false } diff --git a/frontend/src/pages/AddProduct.tsx b/frontend/src/pages/AddProduct.tsx index 8f34157..9b27857 100644 --- a/frontend/src/pages/AddProduct.tsx +++ b/frontend/src/pages/AddProduct.tsx @@ -3,11 +3,21 @@ import { Helmet } from "react-helmet-async"; import { post } from "src/api/requests"; import { FirebaseContext } from "src/utils/FirebaseProvider"; +const tags = [ + "Electronics", + "School Supplies", + "Dorm Essentials", + "Furniture", + "Clothes", + "Miscellaneous", +]; + export function AddProduct() { const productName = useRef(null); const productPrice = useRef(null); const productDescription = useRef(null); const productImages = useRef(null); + const [productTags, setProductTags] = useState>([]); const [error, setError] = useState(false); const { user } = useContext(FirebaseContext); @@ -24,6 +34,9 @@ export function AddProduct() { body.append("name", productName.current.value); body.append("price", productPrice.current.value); body.append("description", productDescription.current.value); + productTags.forEach((tag) => { + body.append("tags[]", tag); + }); if (user.email) body.append("userEmail", user.email); if (image) body.append("image", image); @@ -109,6 +122,54 @@ export function AddProduct() { className="border border-gray-300 text-black text-sm rounded-md w-full p-2.5 y-600" /> + {/* Product Tags */} +
+ +
+ {productTags.map((tag) => ( +
+ {tag} + +
+ ))} +
+ {productTags.length !== tags.length && ( +
+
+ {tags.map((tag) => { + if (!productTags.includes(tag)) { + return ( +

{ + setProductTags([...productTags, tag]); + }} + key={tag} + className="px-4 py-2 text-sm text-gray-700 hover:cursor-pointer" + > + {tag} +

+ ); + } + })} +
+
+ )} +
{/* error message */} {error &&

Error adding product. Try again.

} From 2f7eab82731d3f40aa0e8cd57b84b1d90d773abe Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Mon, 21 Apr 2025 21:40:22 -0700 Subject: [PATCH 03/17] show and hide tags dropdown --- frontend/src/pages/AddProduct.tsx | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/AddProduct.tsx b/frontend/src/pages/AddProduct.tsx index 9b27857..9654410 100644 --- a/frontend/src/pages/AddProduct.tsx +++ b/frontend/src/pages/AddProduct.tsx @@ -1,4 +1,4 @@ -import { FormEvent, useRef, useContext, useState } from "react"; +import { FormEvent, useRef, useContext, useState, useEffect } from "react"; import { Helmet } from "react-helmet-async"; import { post } from "src/api/requests"; import { FirebaseContext } from "src/utils/FirebaseProvider"; @@ -21,6 +21,23 @@ export function AddProduct() { const [error, setError] = useState(false); const { user } = useContext(FirebaseContext); + // handle dropdown display + const dropdownRef = useRef(null); + const buttonRef = useRef(null); + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { + dropdownRef.current.hidden = true; + } + }; + document.addEventListener("mousedown", handleClickOutside); + }, []); + const handleSubmit = async (e: FormEvent) => { e.preventDefault(); try { @@ -130,6 +147,10 @@ export function AddProduct() {
{ + if (dropdownRef.current) dropdownRef.current.hidden = false; + }} + ref={buttonRef} > {productTags.map((tag) => (
))}
+ {/* Product Tags Dropdown */} {productTags.length !== tags.length && ( -
+
{tags.map((tag) => { if (!productTags.includes(tag)) { From 94921a33d25826ef0a450594016b1f102b0282f7 Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Tue, 22 Apr 2025 11:47:45 -0700 Subject: [PATCH 04/17] create dropdown for search bar --- frontend/src/components/SearchBar.tsx | 60 +++++++++++++++++++++++---- frontend/src/pages/AddProduct.tsx | 10 +---- frontend/src/utils/constants.tsx | 8 ++++ 3 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 frontend/src/utils/constants.tsx diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx index aa40a0f..a828aef 100644 --- a/frontend/src/components/SearchBar.tsx +++ b/frontend/src/components/SearchBar.tsx @@ -1,5 +1,7 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { get } from "src/api/requests"; +import { FaFilter } from "react-icons/fa6"; +import { tags } from "../utils/constants.tsx"; interface Props { setProducts: (products: []) => void; @@ -41,12 +43,56 @@ export default function SearchBar({ setProducts, setError }: Props) { search(); }, [query]); + // handle dropdown display + const dropdownRef = useRef(null); + const buttonRef = useRef(null); + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { + dropdownRef.current.hidden = true; + } + }; + document.addEventListener("mousedown", handleClickOutside); + }, []); + return ( - setQuery(e.target.value)} - placeholder="Search for a product..." - className="w-full bg-[#F8F8F8] shadow-md p-3 px-6 mx-auto my-2 rounded-3xl" - /> + <> +
+ setQuery(e.target.value)} + placeholder="Search for a product..." + className="w-full bg-[#F8F8F8] shadow-md p-3 pr-12 pl-6 rounded-3xl" + /> +
+ { + if (dropdownRef.current) dropdownRef.current.hidden = !dropdownRef.current?.hidden; + }} + className="absolute right-6 top-1/2 transform -translate-y-1/2 text-[#00629B] text-[1.2rem] cursor-pointer" + /> +
+
+
+
+ Category + {tags.map((tag, index) => ( +
+ + +
+
+ ))} +
+
+ ); } diff --git a/frontend/src/pages/AddProduct.tsx b/frontend/src/pages/AddProduct.tsx index 9654410..76187b1 100644 --- a/frontend/src/pages/AddProduct.tsx +++ b/frontend/src/pages/AddProduct.tsx @@ -2,15 +2,7 @@ import { FormEvent, useRef, useContext, useState, useEffect } from "react"; import { Helmet } from "react-helmet-async"; import { post } from "src/api/requests"; import { FirebaseContext } from "src/utils/FirebaseProvider"; - -const tags = [ - "Electronics", - "School Supplies", - "Dorm Essentials", - "Furniture", - "Clothes", - "Miscellaneous", -]; +import { tags } from "../utils/constants.tsx"; export function AddProduct() { const productName = useRef(null); diff --git a/frontend/src/utils/constants.tsx b/frontend/src/utils/constants.tsx new file mode 100644 index 0000000..d1185ee --- /dev/null +++ b/frontend/src/utils/constants.tsx @@ -0,0 +1,8 @@ +export const tags = [ + "Electronics", + "School Supplies", + "Dorm Essentials", + "Furniture", + "Clothes", + "Miscellaneous", +]; From a456c7688c1d4fd5528b00dae56d63e69883c930 Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Wed, 23 Apr 2025 17:47:32 -0700 Subject: [PATCH 05/17] query by keyword and tags --- backend/src/controllers/products.ts | 20 ++++++++++++++--- backend/src/routes/product.ts | 4 ++-- frontend/src/components/SearchBar.tsx | 31 +++++++++++++++++++++------ 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/backend/src/controllers/products.ts b/backend/src/controllers/products.ts index c12fad4..9077bc6 100644 --- a/backend/src/controllers/products.ts +++ b/backend/src/controllers/products.ts @@ -45,15 +45,29 @@ export const getProductById = async (req: AuthenticatedRequest, res: Response) = /* * search for product by name */ -export const getProductsByName = async (req: AuthenticatedRequest, res: Response) => { +export const getProductsByQuery = async (req: AuthenticatedRequest, res: Response) => { try { - const query = req.params.query; - const products = await ProductModel.find({ name: { $regex: query, $options: "i" } }); + const keyword = req.query.keyword; + let tags: string[] = []; + if (typeof req.query.tags === "string" && req.query.tags.length > 0) { + tags = req.query.tags.split(","); + } + + let query: any = {} + if (typeof keyword === "string" && keyword.length > 0){ + query.name = { $regex: keyword || "", $options: "i" } + } + if (tags.length > 0) { + query.tags = { $in: tags }; + } + + const products = await ProductModel.find(query); if (!products) { return res.status(404).json({ message: "Product not found" }); } res.status(200).json(products); } catch (error) { + console.error(error); res.status(500).json({ message: "Error getting product", error }); } }; diff --git a/backend/src/routes/product.ts b/backend/src/routes/product.ts index 581fb50..fc92e53 100644 --- a/backend/src/routes/product.ts +++ b/backend/src/routes/product.ts @@ -5,14 +5,14 @@ import { addProduct, deleteProductById, updateProductById, - getProductsByName, + getProductsByQuery, } from "src/controllers/products"; import { authenticateUser } from "src/validators/authUserMiddleware"; const router = express.Router(); router.get("/", authenticateUser, getProducts); +router.get("/search", authenticateUser, getProductsByQuery); router.get("/:id", authenticateUser, getProductById); -router.get("/search/:query", authenticateUser, getProductsByName); router.post("/", authenticateUser, addProduct); router.delete("/:id", authenticateUser, deleteProductById); router.patch("/:id", authenticateUser, updateProductById); diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx index a828aef..dccf0d9 100644 --- a/frontend/src/components/SearchBar.tsx +++ b/frontend/src/components/SearchBar.tsx @@ -10,16 +10,23 @@ interface Props { export default function SearchBar({ setProducts, setError }: Props) { const [query, setQuery] = useState(null); + const [tagFilters, setTagFilters] = useState([]); useEffect(() => { /* - * if query is null, get all products - * otherwise get products that match the query + * if query and tags are null, get all products + * otherwise get products that match the query/tags */ const search = async () => { try { - if (query && query.trim().length > 0) { - await get(`/api/products/search/${query}`).then((res) => { + if ((query && query.trim().length > 0) || tagFilters.length > 0) { + const selectedTags = tagFilters.length > 0 ? tagFilters.join(",") : ""; + let keyword = ""; + if (query) { + keyword = query.trim().length > 0 ? query.trim() : ""; + } + + await get(`/api/products/search?keyword=${keyword}&tags=${selectedTags}`).then((res) => { if (res.ok) { res.json().then((data) => { setProducts(data); @@ -41,7 +48,7 @@ export default function SearchBar({ setProducts, setError }: Props) { } }; search(); - }, [query]); + }, [query, tagFilters]); // handle dropdown display const dropdownRef = useRef(null); @@ -86,7 +93,19 @@ export default function SearchBar({ setProducts, setError }: Props) { Category {tags.map((tag, index) => (
- + { + if (e.target.checked) { + setTagFilters([...tagFilters, tag]); + } else { + setTagFilters(tagFilters.filter((t) => t !== tag)); + } + }} + />
From 3d43200aba130f559445e6de3e1cc933c66aa157 Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Wed, 23 Apr 2025 17:54:31 -0700 Subject: [PATCH 06/17] display tags on individual product page --- frontend/src/pages/Individual-product-page.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frontend/src/pages/Individual-product-page.tsx b/frontend/src/pages/Individual-product-page.tsx index c4294c1..d71350d 100644 --- a/frontend/src/pages/Individual-product-page.tsx +++ b/frontend/src/pages/Individual-product-page.tsx @@ -16,6 +16,7 @@ export function IndividualProductPage() { image: string; userEmail: string; description: string; + tags: string[]; }>(); const [error, setError] = useState(); const [hasPermissions, setHasPermissions] = useState(false); @@ -100,6 +101,18 @@ export function IndividualProductPage() {

)} + {product?.tags && ( +
+ {product.tags.map((tag) => ( +
+ {tag} +
+ ))} +
+ )}

Interested? Contact them here: From dd4b0be8ae35c656b469bfdc6b316ef9dd13b2b9 Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Wed, 23 Apr 2025 18:08:06 -0700 Subject: [PATCH 07/17] improve responsiveness --- frontend/src/components/SearchBar.tsx | 53 ++++++++++++++------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx index dccf0d9..2fedd86 100644 --- a/frontend/src/components/SearchBar.tsx +++ b/frontend/src/components/SearchBar.tsx @@ -84,32 +84,33 @@ export default function SearchBar({ setProducts, setError }: Props) { className="absolute right-6 top-1/2 transform -translate-y-1/2 text-[#00629B] text-[1.2rem] cursor-pointer" />

-
-
-
- Category - {tags.map((tag, index) => ( -
- { - if (e.target.checked) { - setTagFilters([...tagFilters, tag]); - } else { - setTagFilters(tagFilters.filter((t) => t !== tag)); - } - }} - /> - -
-
- ))} + +
+
+ Category + {tags.map((tag, index) => ( +
+ { + if (e.target.checked) { + setTagFilters([...tagFilters, tag]); + } else { + setTagFilters(tagFilters.filter((t) => t !== tag)); + } + }} + /> + +
+
+ ))} +
From 5903e43d27889bae1360d549b596a83105e62821 Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Thu, 1 May 2025 17:18:56 -0700 Subject: [PATCH 08/17] edit tags functionality --- backend/src/controllers/products.ts | 1 + frontend/src/pages/EditProduct.tsx | 81 ++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/backend/src/controllers/products.ts b/backend/src/controllers/products.ts index 9077bc6..9e89d6f 100644 --- a/backend/src/controllers/products.ts +++ b/backend/src/controllers/products.ts @@ -204,6 +204,7 @@ export const updateProductById = [ description: req.body.description, timeUpdated: new Date(), image: newImage, + tags: req.body.tags ?? [] }; console.log("Done..."); diff --git a/frontend/src/pages/EditProduct.tsx b/frontend/src/pages/EditProduct.tsx index 1e4df09..c13403f 100644 --- a/frontend/src/pages/EditProduct.tsx +++ b/frontend/src/pages/EditProduct.tsx @@ -3,6 +3,7 @@ import { Helmet } from "react-helmet-async"; import { useNavigate, useParams } from "react-router-dom"; import { DELETE, get, patch } from "src/api/requests"; import { FirebaseContext } from "src/utils/FirebaseProvider"; +import { tags } from "src/utils/constants"; export function EditProduct() { const { id } = useParams(); @@ -13,6 +14,7 @@ export function EditProduct() { image: string; userEmail: string; description: string; + tags: string[]; }>(); const productName = useRef(null); @@ -20,6 +22,7 @@ export function EditProduct() { const productDescription = useRef(null); const productImages = useRef(null); const [currentImage, setCurrentImage] = useState(null); + const [productTags, setProductTags] = useState>([]); const [error, setError] = useState(false); @@ -32,6 +35,7 @@ export function EditProduct() { .then(async (res) => { const productData = await res.json(); setProduct(productData); + setProductTags(productData.tags); setCurrentImage(productData.image); }) .catch(() => setError(true)); @@ -52,9 +56,11 @@ export function EditProduct() { body.append("name", productName.current.value); body.append("price", productPrice.current.value); body.append("description", productDescription.current.value); + productTags.forEach((tag) => { + body.append("tags[]", tag); + }); if (user.email) body.append("userEmail", user.email); if (image) body.append("image", image); - console.log(body.get("price")); const res = await patch(`/api/products/${id}`, body); @@ -90,6 +96,23 @@ export function EditProduct() { } }; + // handle dropdown display + const dropdownRef = useRef(null); + const buttonRef = useRef(null); + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { + dropdownRef.current.hidden = true; + } + }; + document.addEventListener("mousedown", handleClickOutside); + }, []); + return ( <> @@ -166,6 +189,62 @@ export function EditProduct() { Product )}
+ {/* Product Tags */} +
+ +
{ + if (dropdownRef.current) dropdownRef.current.hidden = false; + }} + ref={buttonRef} + > + {productTags.map((tag) => ( +
+ {tag} + +
+ ))} +
+ {/* Product Tags Dropdown */} + {productTags.length !== tags.length && ( +
+
+ {tags.map((tag) => { + if (!productTags.includes(tag)) { + return ( +

{ + setProductTags([...productTags, tag]); + }} + key={tag} + className="px-4 py-2 text-sm text-gray-700 hover:cursor-pointer" + > + {tag} +

+ ); + } + })} +
+
+ )} +
- + {error &&

{error}

} {!error && products?.length === 0 && (

No products available

@@ -89,7 +90,7 @@ export function Marketplace() {
- +
diff --git a/frontend/src/utils/constants.tsx b/frontend/src/utils/constants.tsx index 9a547aa..2a52e75 100644 --- a/frontend/src/utils/constants.tsx +++ b/frontend/src/utils/constants.tsx @@ -12,3 +12,5 @@ export const orderMethods = { "Price (low-high)": "price", "Alphabetical (A-Z)": "name", } as { [key: string]: string }; + +export const pageSize = 12; From 34167478a13fc0721cd5fe1320016834d3192d7e Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Thu, 15 May 2025 17:26:22 -0700 Subject: [PATCH 14/17] change page count for queries --- backend/src/controllers/products.ts | 10 +++++----- frontend/src/components/Pagination.tsx | 5 +++-- frontend/src/components/SearchBar.tsx | 13 +++++++++---- frontend/src/pages/Marketplace.tsx | 12 +++++++++--- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/backend/src/controllers/products.ts b/backend/src/controllers/products.ts index 8f4603b..59f6f2b 100644 --- a/backend/src/controllers/products.ts +++ b/backend/src/controllers/products.ts @@ -24,7 +24,8 @@ export const getProducts = async (req: AuthenticatedRequest, res: Response) => { try { const page = Number(req.query.page) - 1; const products = await ProductModel.find().skip(pageSize * page).limit(pageSize).sort({"timeUpdated": -1}); - res.status(200).json(products); + const totalCount = await ProductModel.countDocuments().then((count) => Math.ceil(count / pageSize)) + res.status(200).json({products, totalCount}); } catch (error) { res.status(500).json({ message: "Error fetching products", error }); } @@ -75,10 +76,9 @@ export const getProductsByQuery = async (req: AuthenticatedRequest, res: Respons if (price) query.price = {$lte: price}; const products = await ProductModel.find(query).skip(pageSize * page).limit(pageSize).sort({[sortField]: sortOrder === 1 ? 'asc' : 'desc'}); - if (!products) { - return res.status(404).json({ message: "Product not found" }); - } - res.status(200).json(products); + const totalCount = await ProductModel.countDocuments(query).then((count) => Math.ceil(count / pageSize)) + + res.status(200).json({products, totalCount}); } catch (error) { console.error(error); res.status(500).json({ message: "Error getting product", error }); diff --git a/frontend/src/components/Pagination.tsx b/frontend/src/components/Pagination.tsx index dce0d56..429f28a 100644 --- a/frontend/src/components/Pagination.tsx +++ b/frontend/src/components/Pagination.tsx @@ -1,10 +1,11 @@ import { useMediaQuery, useTheme, Pagination } from "@mui/material"; interface Props { + totalPages: number; page: number; setPage: (page: number) => void; } -export default function PaginationBar({ page, setPage }: Props) { +export default function PaginationBar({ totalPages, page, setPage }: Props) { const theme = useTheme(); const isSm = useMediaQuery(theme.breakpoints.down("sm")); const isMd = useMediaQuery(theme.breakpoints.between("sm", "md")); @@ -26,7 +27,7 @@ export default function PaginationBar({ page, setPage }: Props) { return ( void; + setTotalPages: (totalPages: number) => void; setError: (error: string) => void; page: number; } -export default function SearchBar({ setProducts, setError, page }: Props) { +export default function SearchBar({ setProducts, setTotalPages, setError, page }: Props) { const [dropdownHidden, setDropdownHidden] = useState(true); const [query, setQuery] = useState(null); const [tagFilters, setTagFilters] = useState([]); @@ -42,7 +43,9 @@ export default function SearchBar({ setProducts, setError, page }: Props) { ).then((res) => { if (res.ok) { res.json().then((data) => { - setProducts(data); + setProducts(data.products); + console.log(data.totalCount); + setTotalPages(data.totalCount); }); } }); @@ -50,7 +53,9 @@ export default function SearchBar({ setProducts, setError, page }: Props) { await get(`/api/products?page=${page}`).then((res) => { if (res.ok) { res.json().then((data) => { - setProducts(data); + setProducts(data.products); + console.log(data.totalCount); + setTotalPages(data.totalCount); }); } }); @@ -101,7 +106,7 @@ export default function SearchBar({ setProducts, setError, page }: Props) {
-
- +
+
From 1747f06612b2d1d03712a4e41353a08bca9ddcba Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Wed, 21 May 2025 13:06:16 -0700 Subject: [PATCH 15/17] fix icon and filter selection, add high to low sort --- backend/src/controllers/products.ts | 5 +++-- frontend/src/components/SearchBar.tsx | 11 ++++++++--- frontend/src/utils/constants.tsx | 3 ++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/src/controllers/products.ts b/backend/src/controllers/products.ts index 5f05010..76f6c11 100644 --- a/backend/src/controllers/products.ts +++ b/backend/src/controllers/products.ts @@ -57,9 +57,10 @@ export const getProductsByQuery = async (req: AuthenticatedRequest, res: Respons tags = req.query.tags.split(","); } const price = req.query.price; - const sortField : string = String(req.query.order) ?? ""; + let sortField : string = String(req.query.order) ?? ""; let sortOrder = 1 - if (sortField === 'timeUpdated') sortOrder = -1 + if (sortField === 'timeUpdated' || sortField === 'priceDesc') sortOrder = -1 + if (sortField === 'priceAsc' || sortField === 'priceDesc') sortField = 'price' let query: any = {} if (typeof keyword === "string" && keyword.length > 0){ diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx index 279e106..cadd28e 100644 --- a/frontend/src/components/SearchBar.tsx +++ b/frontend/src/components/SearchBar.tsx @@ -101,7 +101,7 @@ export default function SearchBar({ setProducts, setError }: Props) { ))} diff --git a/frontend/src/utils/constants.tsx b/frontend/src/utils/constants.tsx index 9a547aa..0105f12 100644 --- a/frontend/src/utils/constants.tsx +++ b/frontend/src/utils/constants.tsx @@ -9,6 +9,7 @@ export const tags = [ export const orderMethods = { "Most Recent": "timeUpdated", - "Price (low-high)": "price", + "Price (low-high)": "priceAsc", + "Price (high-low)": "priceDesc", "Alphabetical (A-Z)": "name", } as { [key: string]: string }; From 5510ee1a3545acf6b804223c5d0f3ef827424896 Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Wed, 21 May 2025 13:12:50 -0700 Subject: [PATCH 16/17] change page size to 24 --- backend/src/controllers/products.ts | 2 +- frontend/src/utils/constants.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/controllers/products.ts b/backend/src/controllers/products.ts index 59f6f2b..f6c53a2 100644 --- a/backend/src/controllers/products.ts +++ b/backend/src/controllers/products.ts @@ -15,7 +15,7 @@ const upload = multer({ limits: { fileSize: 5 * 1024 * 1024 }, // 5 MB limit }).array("images", 10); -const pageSize = 12; +const pageSize = 24; /** * get all the products in database diff --git a/frontend/src/utils/constants.tsx b/frontend/src/utils/constants.tsx index 2a52e75..9203c7a 100644 --- a/frontend/src/utils/constants.tsx +++ b/frontend/src/utils/constants.tsx @@ -13,4 +13,4 @@ export const orderMethods = { "Alphabetical (A-Z)": "name", } as { [key: string]: string }; -export const pageSize = 12; +export const pageSize = 24; From 9fc6fe85c902041e343d0b6a51274621b68bae90 Mon Sep 17 00:00:00 2001 From: n1sh1thaS Date: Fri, 23 May 2025 11:19:05 -0700 Subject: [PATCH 17/17] fix dropdown visibility --- frontend/src/components/SearchBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx index cadd28e..5536197 100644 --- a/frontend/src/components/SearchBar.tsx +++ b/frontend/src/components/SearchBar.tsx @@ -74,7 +74,7 @@ export default function SearchBar({ setProducts, setError }: Props) { buttonRef.current && !buttonRef.current.contains(event.target as Node) ) { - setDropdownHidden(false); + setDropdownHidden(true); } }; document.addEventListener("mousedown", handleClickOutside); @@ -92,7 +92,7 @@ export default function SearchBar({ setProducts, setError }: Props) {
{ - if (dropdownRef.current) dropdownRef.current.hidden = !dropdownRef.current?.hidden; + setDropdownHidden((prev) => !prev); }} className="absolute right-6 top-1/2 transform -translate-y-1/2 text-[#00629B] text-[1.2rem] cursor-pointer" />