From 9f55fa2748474358ea85c9e47db8f6ad7c6de82a Mon Sep 17 00:00:00 2001 From: mraysu Date: Tue, 18 Nov 2025 21:41:15 -0800 Subject: [PATCH 1/3] add mark as sold button --- backend/src/controllers/products.ts | 3 +- backend/src/models/product.ts | 5 ++ frontend/src/pages/EditProduct.tsx | 2 + .../src/pages/Individual-product-page.tsx | 59 ++++++++++++++++++- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/backend/src/controllers/products.ts b/backend/src/controllers/products.ts index 3c00ae0..334190a 100644 --- a/backend/src/controllers/products.ts +++ b/backend/src/controllers/products.ts @@ -20,7 +20,7 @@ const upload = multer({ */ export const getProducts = async (req: AuthenticatedRequest, res: Response) => { try { - const products = await ProductModel.find(); + const products = await ProductModel.find({ isMarkedSold: {$in: [false, null]} }); res.status(200).json(products); } catch (error) { res.status(500).json({ message: "Error fetching products", error }); @@ -191,6 +191,7 @@ export const updateProductById = [ description: req.body.description, images: finalImages, timeUpdated: new Date(), + isMarkedSold: req.body.isMarkedSold ?? false, }, { new: true }, ); diff --git a/backend/src/models/product.ts b/backend/src/models/product.ts index ca7386a..67ac4e9 100644 --- a/backend/src/models/product.ts +++ b/backend/src/models/product.ts @@ -24,6 +24,11 @@ const productSchema = new Schema({ type: String, required: true, }, + isMarkedSold: { + type: Boolean, + required: true, + default: false, + }, images: [{ type: String }], }); diff --git a/frontend/src/pages/EditProduct.tsx b/frontend/src/pages/EditProduct.tsx index 67f3297..17782a3 100644 --- a/frontend/src/pages/EditProduct.tsx +++ b/frontend/src/pages/EditProduct.tsx @@ -14,6 +14,7 @@ export function EditProduct() { images: string[]; userEmail: string; description: string; + isMarkedSold: boolean; }>(); const productName = useRef(null); @@ -84,6 +85,7 @@ export function EditProduct() { body.append("price", productPrice.current.value); body.append("description", productDescription.current.value); body.append("userEmail", user.email || ""); + body.append("isMarkedSold", String(product?.isMarkedSold)); // append existing image URLs existingImages.forEach((url) => body.append("existingImages", url)); diff --git a/frontend/src/pages/Individual-product-page.tsx b/frontend/src/pages/Individual-product-page.tsx index a54eb83..a0f3c6c 100644 --- a/frontend/src/pages/Individual-product-page.tsx +++ b/frontend/src/pages/Individual-product-page.tsx @@ -1,11 +1,11 @@ -import { faPenToSquare } from "@fortawesome/free-solid-svg-icons"; +import { faPenToSquare, faCheck, faArrowUp } from "@fortawesome/free-solid-svg-icons"; import { faHeart as faHeartSolid } from "@fortawesome/free-solid-svg-icons"; import { faHeart as faHeartRegular } from "@fortawesome/free-regular-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useContext, useEffect, useState } from "react"; import { Helmet } from "react-helmet-async"; import { useLocation, useNavigate, useParams } from "react-router-dom"; -import { get, post } from "src/api/requests"; +import { get, patch, post } from "src/api/requests"; import { FirebaseContext } from "src/utils/FirebaseProvider"; import EmblaCarousel from "src/components/EmblaCarousel"; import { EmblaOptionsType } from "embla-carousel"; @@ -20,6 +20,7 @@ export function IndividualProductPage() { images: string[]; userEmail: string; description: string; + isMarkedSold: boolean; }>(); const [error, setError] = useState(); const [hasPermissions, setHasPermissions] = useState(false); @@ -122,6 +123,34 @@ export function IndividualProductPage() { setIsSubmitting(false); } }; + + const handleMarkSold = async () => { + console.log(product); + if (!product) return; + const body = new FormData(); + body.append("name", product.name); + body.append("price", product.price.toString()); + body.append("description", product.description); + body.append("userEmail", product.userEmail); + product.images.forEach((url) => body.append("existingImages", url)); + body.append("isMarkedSold", String(!product.isMarkedSold)); + + await patch(`/api/products/${id}`, body) + .then(async (res) => { + const response = await res.json(); + if (res.ok) { + setProduct(response.updatedProduct); + navigate(`/products/${id}`); + } else { + alert("Failed to update product"); + console.log(response); + } + }) + .catch((e) => { + console.log(e); + }); + }; + const isCooling = Boolean(cooldownEnd && Date.now() < cooldownEnd); // const secondsLeft = isCooling ? Math.ceil((cooldownEnd! - Date.now()) / 1000) : 0; const msLeft = isCooling ? cooldownEnd! - Date.now() : 0; @@ -234,9 +263,35 @@ export function IndividualProductPage() {
+ {hasPermissions && + (product?.isMarkedSold ? ( + + ) : ( + + ))} +

USD ${product?.price?.toFixed(2)}

+ {product?.isMarkedSold && ( +
+

+ {hasPermissions + ? "This product has been marked as sold. It will not appear on the marketplace, but others can still be find it under your profile." + : "This product is no longer available."} +

+
+ )} {product?.description && (

From 1c48e6c5da47dfeb5efcda1e63a613f7d407110a Mon Sep 17 00:00:00 2001 From: mraysu Date: Wed, 19 Nov 2025 21:16:26 -0800 Subject: [PATCH 2/3] remove debug code --- frontend/src/pages/Individual-product-page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/pages/Individual-product-page.tsx b/frontend/src/pages/Individual-product-page.tsx index a0f3c6c..f0652fc 100644 --- a/frontend/src/pages/Individual-product-page.tsx +++ b/frontend/src/pages/Individual-product-page.tsx @@ -125,7 +125,6 @@ export function IndividualProductPage() { }; const handleMarkSold = async () => { - console.log(product); if (!product) return; const body = new FormData(); body.append("name", product.name); From 6c183955e22c0295d4d27711829152ec28e2049e Mon Sep 17 00:00:00 2001 From: mraysu Date: Fri, 21 Nov 2025 18:30:52 -0800 Subject: [PATCH 3/3] disable search for products marked as sold --- backend/src/controllers/products.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/controllers/products.ts b/backend/src/controllers/products.ts index 334190a..cf78ac8 100644 --- a/backend/src/controllers/products.ts +++ b/backend/src/controllers/products.ts @@ -20,7 +20,7 @@ const upload = multer({ */ export const getProducts = async (req: AuthenticatedRequest, res: Response) => { try { - const products = await ProductModel.find({ isMarkedSold: {$in: [false, null]} }); + const products = await ProductModel.find({ isMarkedSold: { $in: [false, null] } }); res.status(200).json(products); } catch (error) { res.status(500).json({ message: "Error fetching products", error }); @@ -52,7 +52,10 @@ export const getProductById = async (req: AuthenticatedRequest, res: Response) = export const getProductsByName = async (req: AuthenticatedRequest, res: Response) => { try { const query = req.params.query; - const products = await ProductModel.find({ name: { $regex: query, $options: "i" } }); + const products = await ProductModel.find({ + name: { $regex: query, $options: "i" }, + isMarkedSold: { $in: [false, null] }, + }); if (!products) { return res.status(404).json({ message: "Product not found" }); }