From 814c24c83fb3e7a7eb50c1a9c029d1bff3c1f976 Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Sat, 12 Apr 2025 18:33:13 -0600 Subject: [PATCH] selling Pg UI and Fav is done --- backend/controllers/product.js | 8 +- frontend/src/components/ProductForm.jsx | 148 ++++++++++++++++++++ frontend/src/pages/Favorites.jsx | 179 ++++++++++++++++-------- frontend/src/pages/Selling.jsx | 154 +++++++++++++++++++- mysql-code/Schema.sql | 2 +- 5 files changed, 425 insertions(+), 66 deletions(-) create mode 100644 frontend/src/components/ProductForm.jsx diff --git a/backend/controllers/product.js b/backend/controllers/product.js index b3e52e0..357867a 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -1,13 +1,13 @@ const db = require("../utils/database"); exports.addFavorite = async (req, res) => { - const { userID, productsID } = req.body; - + const { userID, productID } = req.body; + console.log(userID); try { // Use parameterized query to prevent SQL injection const [result] = await db.execute( - "INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)", - [userID, productsID], + `INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)`, + [userID, productID], ); res.json({ diff --git a/frontend/src/components/ProductForm.jsx b/frontend/src/components/ProductForm.jsx new file mode 100644 index 0000000..33c0107 --- /dev/null +++ b/frontend/src/components/ProductForm.jsx @@ -0,0 +1,148 @@ +import React from "react"; + +const ProductForm = ({ + editingProduct, + setEditingProduct, + onSave, + onCancel, +}) => { + return ( +
+ {/* Back Button */} + + +

+ {editingProduct?.id ? "Edit Your Product" : "List a New Product"} +

+ +
+ {/* Product Name */} +
+ + + setEditingProduct({ ...editingProduct, name: e.target.value }) + } + className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500" + /> +
+ + {/* Price */} +
+ + + setEditingProduct({ + ...editingProduct, + price: e.target.value, + }) + } + className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500" + /> +
+ + {/* Status */} +
+ + +
+ + {/* Images */} +
+ + { + const files = Array.from(e.target.files).slice(0, 5); + setEditingProduct((prev) => ({ + ...prev, + images: [...prev.images, ...files].slice(0, 5), + })); + }} + className="w-full px-4 py-2 border border-gray-300 rounded-md" + /> + +
+ {editingProduct.images.length > 0 && + editingProduct.images.map((img, idx) => ( +
+ {`Preview + +
+ ))} +
+
+
+ + {/* Actions */} +
+ + +
+
+ ); +}; + +export default ProductForm; diff --git a/frontend/src/pages/Favorites.jsx b/frontend/src/pages/Favorites.jsx index 86e9e1f..a42486e 100644 --- a/frontend/src/pages/Favorites.jsx +++ b/frontend/src/pages/Favorites.jsx @@ -1,63 +1,113 @@ -import { useState } from 'react'; -import { Link } from 'react-router-dom'; -import { Heart, Tag, Trash2, Filter, ChevronDown } from 'lucide-react'; +import { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; +import { Heart, Tag, Trash2, Filter, ChevronDown } from "lucide-react"; const Favorites = () => { - const [favorites, setFavorites] = useState([ - { - id: 0, - title: 'Dell XPS 16 Laptop', - price: 850, - category: 'Electronics', - image: '/image1.avif', - condition: 'Like New', - seller: 'Michael T.', - datePosted: '5d ago', - dateAdded: '2023-03-08', - }, - - ]); - + const [favorites, setFavorites] = useState([]); const [showFilters, setShowFilters] = useState(false); - const [sortBy, setSortBy] = useState('dateAdded'); - const [filterCategory, setFilterCategory] = useState('All'); + const [sortBy, setSortBy] = useState("dateAdded"); + const [filterCategory, setFilterCategory] = useState("All"); - // Function to remove item from favorites - const removeFromFavorites = (id) => { - setFavorites(favorites.filter(item => item.id !== id)); + const mapCategory = (id) => { + const categories = { + 1: "Electronics", + 2: "Textbooks", + 3: "Furniture", + 4: "Clothing", + 5: "Kitchen", + 6: "Other", + }; + return categories[id] || "Other"; }; - // Available categories for filtering - const categories = ['All', 'Electronics', 'Textbooks', 'Furniture', 'Kitchen', 'Other']; + useEffect(() => { + const fetchFavorites = async () => { + try { + const user = JSON.parse(sessionStorage.getItem("user")); + const response = await fetch( + "http://localhost:3030/api/product/getFavorites", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ userID: user.ID }), + }, + ); + + const data = await response.json(); + console.log(user.ID); + console.log(data); + + const favoritesData = data.favorites; + + if (!Array.isArray(favoritesData)) { + console.error("Expected an array but got:", favoritesData); + return; + } + + const transformed = favoritesData.map((item) => ({ + id: item.ProductID, + title: item.Name, + price: parseFloat(item.Price), + category: mapCategory(item.CategoryID), // 👈 map numeric category to a string + image: item.image_url || "/default-image.jpg", + condition: "Used", // or another field if you add `Condition` to your DB + seller: item.SellerName, + datePosted: formatDatePosted(item.Date), + dateAdded: item.Date || new Date().toISOString(), + })); + + setFavorites(transformed); + } catch (error) { + console.error("Failed to fetch favorites:", error); + } + }; + + fetchFavorites(); + }, []); + + const formatDatePosted = (dateString) => { + const postedDate = new Date(dateString); + const today = new Date(); + const diffInMs = today - postedDate; + const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24)); + return `${diffInDays}d ago`; + }; + + const removeFromFavorites = (id) => { + setFavorites(favorites.filter((item) => item.id !== id)); + // Optional: Send DELETE request to backend here + }; - // Sort favorites based on selected sort option const sortedFavorites = [...favorites].sort((a, b) => { - if (sortBy === 'dateAdded') { + if (sortBy === "dateAdded") return new Date(b.dateAdded) - new Date(a.dateAdded); - } else if (sortBy === 'priceHigh') { - return b.price - a.price; - } else if (sortBy === 'priceLow') { - return a.price - b.price; - } + if (sortBy === "priceHigh") return b.price - a.price; + if (sortBy === "priceLow") return a.price - b.price; return 0; }); - // Filter favorites based on selected category - const filteredFavorites = filterCategory === 'All' - ? sortedFavorites - : sortedFavorites.filter(item => item.category === filterCategory); + const filteredFavorites = + filterCategory === "All" + ? sortedFavorites + : sortedFavorites.filter((item) => item.category === filterCategory); + + // rest of the JSX remains unchanged... return (

My Favorites

-
@@ -89,7 +139,9 @@ const Favorites = () => { className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500" > {categories.map((category) => ( - + ))}
@@ -101,12 +153,15 @@ const Favorites = () => { {filteredFavorites.length === 0 ? (
-

No favorites yet

+

+ No favorites yet +

- Items you save will appear here. Start browsing to add items to your favorites. + Items you save will appear here. Start browsing to add items to your + favorites.

- Browse Listings @@ -115,7 +170,10 @@ const Favorites = () => { ) : (
{filteredFavorites.map((item) => ( -
+
- + - {item.title} - + {item.title} +

{item.title}

- ${item.price} + + ${item.price} +
- +
{item.category} {item.condition}
- +
- Listed {item.datePosted} - {item.seller} + + Listed {item.datePosted} + + + {item.seller} +
@@ -156,12 +224,13 @@ const Favorites = () => { {/* Show count if there are favorites */} {filteredFavorites.length > 0 && (
- Showing {filteredFavorites.length} {filteredFavorites.length === 1 ? 'item' : 'items'} - {filterCategory !== 'All' && ` in ${filterCategory}`} + Showing {filteredFavorites.length}{" "} + {filteredFavorites.length === 1 ? "item" : "items"} + {filterCategory !== "All" && ` in ${filterCategory}`}
)}
); }; -export default Favorites; \ No newline at end of file +export default Favorites; diff --git a/frontend/src/pages/Selling.jsx b/frontend/src/pages/Selling.jsx index 1ed5fdf..3495698 100644 --- a/frontend/src/pages/Selling.jsx +++ b/frontend/src/pages/Selling.jsx @@ -1,13 +1,155 @@ -import { useState } from 'react'; -import { Link } from 'react-router-dom'; -import { Tag, Book, Laptop, Sofa, Utensils, Gift, Heart } from 'lucide-react'; +import { useState } from "react"; +import { Pencil, Trash2, Plus } from "lucide-react"; +import ProductForm from "../components/ProductForm"; const Selling = () => { + const [products, setProducts] = useState([ + { + id: 1, + name: "Green Sofa", + price: 299, + status: "Active", + images: [], + }, + { + id: 2, + name: "Wooden Table", + price: 150, + status: "Inactive", + images: [], + }, + ]); + + const [editingProduct, setEditingProduct] = useState(null); + const [view, setView] = useState("list"); // "list" or "form" + + const handleEdit = (product) => { + setEditingProduct({ ...product }); + setView("form"); + }; + + const handleAddNew = () => { + setEditingProduct({ + id: null, + name: "", + price: "", + status: "Active", + images: [], + }); + setView("form"); + }; + + const handleDelete = (id) => { + setProducts((prev) => prev.filter((p) => p.id !== id)); + }; + + const handleSave = () => { + if (!editingProduct.name || !editingProduct.price) { + alert("Please enter a name and price."); + return; + } + if (editingProduct.images.length < 1) { + alert("Please upload at least one image."); + return; + } + + if (editingProduct.id === null) { + const newProduct = { + ...editingProduct, + id: Date.now(), + }; + setProducts((prev) => [newProduct, ...prev]); + } else { + setProducts((prev) => + prev.map((p) => (p.id === editingProduct.id ? editingProduct : p)), + ); + } + + setEditingProduct(null); + setView("list"); + }; + + const handleCancel = () => { + setEditingProduct(null); + setView("list"); + }; + return ( -
- +
+ {view === "list" && ( + <> +
+

My Listings

+ +
+ +
    + {products.map((product) => ( +
  • +
    +
    + {product.images.length > 0 ? ( + Product + ) : ( + No Image + )} +
    +
    +

    {product.name}

    +

    ${product.price}

    +

    + {product.status} +

    +
    +
    +
    + + +
    +
  • + ))} +
+ + )} + + {view === "form" && ( + + )}
); }; -export default Selling; \ No newline at end of file +export default Selling; diff --git a/mysql-code/Schema.sql b/mysql-code/Schema.sql index 7f94d78..8ac0497 100644 --- a/mysql-code/Schema.sql +++ b/mysql-code/Schema.sql @@ -102,7 +102,7 @@ CREATE TABLE Favorites ( ProductID INT, FOREIGN KEY (UserID) REFERENCES User (UserID), FOREIGN KEY (ProductID) REFERENCES Product (ProductID), - UNIQUE (UserID, ProductID) -- Prevents duplicate favorites + UNIQUE (UserID, ProductID) ); -- Product-Category Junction Table (Many-to-Many)