diff --git a/backend/controllers/history.js b/backend/controllers/history.js index 684bb35..1c85042 100644 --- a/backend/controllers/history.js +++ b/backend/controllers/history.js @@ -1,6 +1,5 @@ const db = require("../utils/database"); -// TODO: Get the recommondaed product given the userID exports.HistoryByUserId = async (req, res) => { const { id } = req.body; try { @@ -21,7 +20,7 @@ exports.HistoryByUserId = async (req, res) => { JOIN User U ON P.UserID = U.UserID JOIN Category C ON P.CategoryID = C.CategoryID JOIN History H ON H.ProductID = P.ProductID - WHERE U.UserID = ? + WHERE H.UserID = ? ) SELECT ProductID, @@ -50,3 +49,42 @@ exports.HistoryByUserId = async (req, res) => { }); } }; + +exports.AddHistory = async (req, res) => { + const { userID, productID } = req.body; + console.log(userID); + try { + // Use parameterized query to prevent SQL injection + const [result] = await db.execute( + `INSERT INTO History (UserID, ProductID) VALUES (?, ?)`, + [userID, productID], + ); + + res.json({ + success: true, + message: "Product added to history successfully", + }); + } catch (error) { + console.error("Error adding favorite product:", error); + return res.json({ error: "Could not add favorite product" }); + } +}; + +exports.DelHistory = async (req, res) => { + const { userID, productID } = req.body; + console.log(userID); + try { + // Use parameterized query to prevent SQL injection + const [result] = await db.execute(`DELETE FROM History WHERE UserID=?`, [ + userID, + ]); + + res.json({ + success: true, + message: "Product deleted from History successfully", + }); + } catch (error) { + console.error("Error adding favorite product:", error); + return res.json({ error: "Could not add favorite product" }); + } +}; diff --git a/backend/controllers/product.js b/backend/controllers/product.js index 357867a..c5a790c 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -20,6 +20,26 @@ exports.addFavorite = async (req, res) => { } }; +exports.removeFavorite = async (req, res) => { + const { userID, productID } = req.body; + console.log(userID); + try { + // Use parameterized query to prevent SQL injection + const [result] = await db.execute( + `DELETE FROM Favorites WHERE UserID = ? AND ProductID = ?`, + [userID, productID], + ); + + res.json({ + success: true, + message: "Product removed from favorites successfully", + }); + } catch (error) { + console.error("Error removing favorite product:", error); + return res.json({ error: "Could not remove favorite product" }); + } +}; + exports.getFavorites = async (req, res) => { const { userID } = req.body; diff --git a/backend/routes/history.js b/backend/routes/history.js index 50cd826..6713233 100644 --- a/backend/routes/history.js +++ b/backend/routes/history.js @@ -1,8 +1,14 @@ // routes/product.js const express = require("express"); -const { HistoryByUserId } = require("../controllers/history"); +const { + HistoryByUserId, + DelHistory, + AddHistory, +} = require("../controllers/history"); const router = express.Router(); router.post("/getHistory", HistoryByUserId); +router.post("/delHistory", DelHistory); +router.post("/addHistory", AddHistory); module.exports = router; diff --git a/backend/routes/product.js b/backend/routes/product.js index 7158fbf..24c5705 100644 --- a/backend/routes/product.js +++ b/backend/routes/product.js @@ -3,6 +3,7 @@ const express = require("express"); const { addFavorite, getFavorites, + removeFavorite, getAllProducts, getProductById, } = require("../controllers/product"); @@ -16,6 +17,7 @@ router.use((req, res, next) => { router.post("/addFavorite", addFavorite); router.post("/getFavorites", getFavorites); +router.post("/delFavorite", removeFavorite); router.get("/getProduct", getAllProducts); router.get("/:id", getProductById); // Simplified route diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6703948..4499afa 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -14,7 +14,6 @@ import Favorites from "./pages/Favorites"; import ProductDetail from "./pages/ProductDetail"; import ItemForm from "./pages/MyListings"; import SearchPage from "./pages/SearchPage"; // Make sure to import the SearchPage -import axios from "axios"; function App() { // Authentication state - initialize from localStorage if available diff --git a/frontend/src/components/FloatingAlert.jsx b/frontend/src/components/FloatingAlert.jsx new file mode 100644 index 0000000..6c6bf69 --- /dev/null +++ b/frontend/src/components/FloatingAlert.jsx @@ -0,0 +1,19 @@ +// components/FloatingAlert.jsx +import { useEffect } from "react"; + +const FloatingAlert = ({ message, onClose, duration = 3000 }) => { + useEffect(() => { + const timer = setTimeout(() => { + onClose(); + }, duration); + + return () => clearTimeout(timer); + }, [onClose, duration]); + + return ( +
+ {message} +
+ ); +}; +export default FloatingAlert; diff --git a/frontend/src/pages/Favorites.jsx b/frontend/src/pages/Favorites.jsx index a42486e..59219fb 100644 --- a/frontend/src/pages/Favorites.jsx +++ b/frontend/src/pages/Favorites.jsx @@ -7,6 +7,7 @@ const Favorites = () => { const [showFilters, setShowFilters] = useState(false); const [sortBy, setSortBy] = useState("dateAdded"); const [filterCategory, setFilterCategory] = useState("All"); + const storedUser = JSON.parse(sessionStorage.getItem("user")); const mapCategory = (id) => { const categories = { @@ -20,10 +21,41 @@ const Favorites = () => { return categories[id] || "Other"; }; + function reloadPage() { + var doctTimestamp = new Date(performance.timing.domLoading).getTime(); + var now = Date.now(); + if (now > doctTimestamp) { + location.reload(); + } + } + + const removeFromFavorites = async (itemID) => { + const response = await fetch( + "http://localhost:3030/api/product/delFavorite", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userID: storedUser.ID, + productID: itemID, + }), + }, + ); + const data = await response.json(); + if (data.success) { + reloadPage(); + } + + if (!response.ok) throw new Error("Failed to fetch products"); + console.log(response); + console.log(`Add Product -> History: ${itemID}`); + }; + useEffect(() => { const fetchFavorites = async () => { try { - const user = JSON.parse(sessionStorage.getItem("user")); const response = await fetch( "http://localhost:3030/api/product/getFavorites", { @@ -31,13 +63,11 @@ const Favorites = () => { headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ userID: user.ID }), + body: JSON.stringify({ userID: storedUser.ID }), }, ); const data = await response.json(); - console.log(user.ID); - console.log(data); const favoritesData = data.favorites; @@ -75,11 +105,6 @@ const Favorites = () => { return `${diffInDays}d ago`; }; - const removeFromFavorites = (id) => { - setFavorites(favorites.filter((item) => item.id !== id)); - // Optional: Send DELETE request to backend here - }; - const sortedFavorites = [...favorites].sort((a, b) => { if (sortBy === "dateAdded") return new Date(b.dateAdded) - new Date(a.dateAdded); diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index c07c032..315bf3b 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -1,6 +1,8 @@ import { useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; -import { Tag, Heart } from "lucide-react"; +import { Tag } from "lucide-react"; + +import FloatingAlert from "../components/FloatingAlert"; // adjust path if needed const Home = () => { const navigate = useNavigate(); @@ -9,10 +11,11 @@ const Home = () => { const [history, sethistory] = useState([]); const [error, setError] = useState(null); const storedUser = JSON.parse(sessionStorage.getItem("user")); + const [showAlert, setShowAlert] = useState(false); - const handleLinkClick = async (id) => { + const toggleFavorite = async (id) => { const response = await fetch( - "http://localhost:3030/api/product/addFavorites", + "http://localhost:3030/api/product/addFavorite", { method: "POST", headers: { @@ -20,17 +23,34 @@ const Home = () => { }, body: JSON.stringify({ userID: storedUser.ID, - productsID: id, + productID: id, }), }, ); - - if (!response.ok) throw new Error("Failed to fetch products"); - + const data = await response.json(); + if (data.success) { + setShowAlert(true); + } console.log(response); console.log(`Add Product -> History: ${id}`); }; + const addHistory = async (id) => { + const response = await fetch( + "http://localhost:3030/api/history/addHistory", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userID: storedUser.ID, + productID: id, + }), + }, + ); + }; + function reloadPage() { var doctTimestamp = new Date(performance.timing.domLoading).getTime(); var now = Date.now(); @@ -44,7 +64,6 @@ const Home = () => { useEffect(() => { const fetchrecomProducts = async () => { // Get the user's data from localStorage - const storedUser = JSON.parse(sessionStorage.getItem("user")); console.log(storedUser); try { const response = await fetch( @@ -126,7 +145,6 @@ const Home = () => { useEffect(() => { const fetchrecomProducts = async () => { // Get the user's data from localStorage - const storedUser = JSON.parse(sessionStorage.getItem("user")); console.log(storedUser); try { const response = await fetch( @@ -155,7 +173,6 @@ const Home = () => { image: product.ProductImage, // Use the alias for image URL seller: product.SellerName, // Fetch seller name properly datePosted: product.DateUploaded, // Use the actual date - isFavorite: false, // Default state })), ); } else { @@ -169,18 +186,6 @@ const Home = () => { fetchrecomProducts(); }, []); - // Toggle favorite status - const toggleFavorite = (id, e) => { - e.preventDefault(); // Prevent navigation when clicking the heart icon - setListings((prevListings) => - prevListings.map((listing) => - listing.id === id - ? { ...listing, isFavorite: !listing.isFavorite } - : listing, - ), - ); - }; - const handleSelling = () => { navigate("/selling"); }; @@ -218,6 +223,12 @@ const Home = () => { {/* Recent Listings */} + {showAlert && ( + setShowAlert(false)} + /> + )}

Recommendation @@ -245,6 +256,7 @@ const Home = () => { addHistory(recommended.id)} className="bg-white border border-gray-200 hover:shadow-md transition-shadow w-70 flex-shrink-0 relative" >
@@ -254,16 +266,14 @@ const Home = () => { className="w-full h-48 object-cover" />
@@ -308,6 +318,12 @@ const Home = () => {

{/* Recent Listings */} + {showAlert && ( + setShowAlert(false)} + /> + )}

Recent Listings @@ -335,26 +351,24 @@ const Home = () => { handleLinkClick(listing.id)} className="bg-white border border-gray-200 hover:shadow-md transition-shadow w-70 flex-shrink-0 relative" >
{listing.title} addHistory(listing.id)} className="w-full h-48 object-cover" />
@@ -399,6 +413,12 @@ const Home = () => {

{/* Recent Listings */} + {showAlert && ( + setShowAlert(false)} + /> + )}

History

@@ -433,16 +453,14 @@ const Home = () => { className="w-full h-48 object-cover" />
diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index dfc967e..4f758e6 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -1,5 +1,6 @@ import { useState, useEffect } from "react"; import { User, Lock, Trash2, History, Search, Shield } from "lucide-react"; +import FloatingAlert from "../components/FloatingAlert"; // adjust path if needed const Settings = () => { const [userData, setUserData] = useState({ @@ -16,6 +17,8 @@ const Settings = () => { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); + const [showAlert, setShowAlert] = useState(false); + const storedUser = JSON.parse(sessionStorage.getItem("user")); // Fetch user data when component mounts useEffect(() => { @@ -88,6 +91,25 @@ const Settings = () => { })); }; + const removeHistory = async () => { + const response = await fetch( + "http://localhost:3030/api/history/delHistory", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userID: storedUser.ID, + }), + }, + ); + + if (response.ok) { + setShowAlert(true); + } + }; + const handleUpdateProfile = async () => { try { // Ensure userId is present @@ -159,12 +181,6 @@ const Settings = () => { } }; - const handleDeleteHistory = (type) => { - // TODO: Delete the specified history - console.log(`Deleting ${type} history`); - alert(`${type} history deleted successfully!`); - }; - const handleDeleteAccount = async () => { if ( window.confirm( @@ -416,6 +432,12 @@ const Settings = () => { {/* Privacy Section */} + {showAlert && ( + setShowAlert(false)} + /> + )}
@@ -432,33 +454,12 @@ const Settings = () => {

Search History

- Delete all your search history on StudentMarket + Delete all your history on Market

-
- -
-
- -
-

- Browsing History -

-

- Delete all your browsing history on StudentMarket -

-
-
-