diff --git a/backend/src/controllers/products.ts b/backend/src/controllers/products.ts index 3c00ae0..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(); + 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" }); } @@ -191,6 +194,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..f0652fc 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,33 @@ export function IndividualProductPage() { setIsSubmitting(false); } }; + + const handleMarkSold = async () => { + 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 +262,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 && (