fav product from prodDetail page

This commit is contained in:
Mann Patel
2025-04-15 00:18:19 -06:00
parent fdf63f4e6a
commit 06e045fbff
6 changed files with 91 additions and 82 deletions

View File

@@ -47,14 +47,29 @@ exports.getFavorites = async (req, res) => {
const [favorites] = await db.execute( const [favorites] = await db.execute(
` `
SELECT SELECT
p.*, p.ProductID,
p.Name,
p.Description,
p.Price,
p.CategoryID,
p.UserID,
p.Date,
u.Name AS SellerName, u.Name AS SellerName,
i.URL AS image_url MIN(i.URL) AS image_url
FROM Favorites f FROM Favorites f
JOIN Product p ON f.ProductID = p.ProductID JOIN Product p ON f.ProductID = p.ProductID
JOIN User u ON p.UserID = u.UserID JOIN User u ON p.UserID = u.UserID
LEFT JOIN Image_URL i ON p.ProductID = i.ProductID LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
WHERE f.UserID = ? WHERE f.UserID = ?
GROUP BY
p.ProductID,
p.Name,
p.Description,
p.Price,
p.CategoryID,
p.UserID,
p.Date,
u.Name;
`, `,
[userID], [userID],
); );
@@ -73,31 +88,25 @@ exports.getFavorites = async (req, res) => {
exports.getAllProducts = async (req, res) => { exports.getAllProducts = async (req, res) => {
try { try {
const [data, fields] = await db.execute(` const [data, fields] = await db.execute(`
WITH RankedImages AS (
SELECT SELECT
P.ProductID, P.ProductID,
P.Name AS ProductName, P.Name AS ProductName,
P.Price, P.Price,
P.Date AS DateUploaded, P.Date AS DateUploaded,
U.Name AS SellerName, U.Name AS SellerName,
I.URL AS ProductImage, MIN(I.URL) AS ProductImage,
C.Name AS Category, C.Name AS Category
ROW_NUMBER() OVER (PARTITION BY P.ProductID ORDER BY I.URL) AS RowNum
FROM Product P FROM Product P
JOIN Image_URL I ON P.ProductID = I.ProductID JOIN Image_URL I ON P.ProductID = I.ProductID
JOIN User U ON P.UserID = U.UserID JOIN User U ON P.UserID = U.UserID
JOIN Category C ON P.CategoryID = C.CategoryID JOIN Category C ON P.CategoryID = C.CategoryID
) GROUP BY
SELECT P.ProductID,
ProductID, P.Name,
ProductName, P.Price,
Price, P.Date,
DateUploaded, U.Name,
SellerName, C.Name;
ProductImage,
Category
FROM RankedImages
WHERE RowNum = 1;
`); `);
res.json({ res.json({

View File

@@ -1,6 +1,6 @@
const express = require("express"); const express = require("express");
const cors = require("cors"); const cors = require("cors");
//Get the db connection
const db = require("./utils/database"); const db = require("./utils/database");
const userRouter = require("./routes/user"); const userRouter = require("./routes/user");
@@ -33,19 +33,20 @@ transporter
console.error("Email connection failed:", error); console.error("Email connection failed:", error);
}); });
//Check database connection
checkDatabaseConnection(db); checkDatabaseConnection(db);
//Routes //Routes
app.use("/api/user", userRouter); //prefix with /api/user app.use("/api/user", userRouter);
app.use("/api/product", productRouter); //prefix with /api/product app.use("/api/product", productRouter);
app.use("/api/search", searchRouter); //prefix with /api/product app.use("/api/search", searchRouter);
app.use("/api/engine", recommendedRouter); //prefix with /api/ app.use("/api/engine", recommendedRouter);
app.use("/api/history", history); //prefix with /api/ app.use("/api/history", history);
app.use("/api/review", review); //prefix with /api/ app.use("/api/review", review);
// Set up a scheduler to run cleanup every hour // Set up a scheduler to run cleanup every hour
setInterval(cleanupExpiredCodes, 60 * 60 * 1000); clean_up_time = 30*60*1000;
setInterval(cleanupExpiredCodes, clean_up_time);
app.listen(3030, () => { app.listen(3030, () => {
console.log(`Running Backend on http://localhost:3030/`); console.log(`Running Backend on http://localhost:3030/`);

View File

@@ -47,8 +47,8 @@ const Navbar = ({ onLogout, userName }) => {
<div className="relative"> <div className="relative">
<input <input
type="text" type="text"
placeholder="Search for books, electronics, furniture..." placeholder="Search for anything..."
className="w-full p-2 pl-10 pr-4 border border-gray-300 rounded-md focus:outline-none focus:border-green-500 focus:ring-1 focus:ring-green-500" className="w-full p-2 pl-10 pr-4 border border-gray-300 focus:outline-none focus:border-green-500 focus:ring-1 focus:ring-green-500"
value={searchQuery} value={searchQuery}
onChange={handleSearchChange} onChange={handleSearchChange}
/> />

View File

@@ -34,7 +34,6 @@ const Home = () => {
if (data.success) { if (data.success) {
setShowAlert(true); setShowAlert(true);
} }
console.log(response);
console.log(`Add Product -> History: ${id}`); console.log(`Add Product -> History: ${id}`);
}; };
@@ -82,7 +81,6 @@ const Home = () => {
if (!response.ok) throw new Error("Failed to fetch products"); if (!response.ok) throw new Error("Failed to fetch products");
const data = await response.json(); const data = await response.json();
console.log(data);
if (data.success) { if (data.success) {
setRecommended( setRecommended(
data.data.map((product) => ({ data.data.map((product) => ({
@@ -145,7 +143,6 @@ const Home = () => {
useEffect(() => { useEffect(() => {
const fetchrecomProducts = async () => { const fetchrecomProducts = async () => {
// Get the user's data from localStorage // Get the user's data from localStorage
console.log(storedUser);
try { try {
const response = await fetch( const response = await fetch(
"http://localhost:3030/api/history/getHistory", "http://localhost:3030/api/history/getHistory",
@@ -162,7 +159,6 @@ const Home = () => {
if (!response.ok) throw new Error("Failed to fetch products"); if (!response.ok) throw new Error("Failed to fetch products");
const data = await response.json(); const data = await response.json();
console.log(data);
if (data.success) { if (data.success) {
sethistory( sethistory(
data.data.map((product) => ({ data.data.map((product) => ({

View File

@@ -10,6 +10,8 @@ import {
Phone, Phone,
Mail, Mail,
} from "lucide-react"; } from "lucide-react";
import FloatingAlert from "../components/FloatingAlert"; // adjust path if needed
const ProductDetail = () => { const ProductDetail = () => {
const { id } = useParams(); const { id } = useParams();
@@ -29,8 +31,32 @@ const ProductDetail = () => {
const [currentImage, setCurrentImage] = useState(0); const [currentImage, setCurrentImage] = useState(0);
const [reviews, setReviews] = useState([]); const [reviews, setReviews] = useState([]);
const [showReviewForm, setShowReviewForm] = useState(false); const [showReviewForm, setShowReviewForm] = useState(false);
const [showAlert, setShowAlert] = useState(false);
const storedUser = JSON.parse(sessionStorage.getItem("user")); const storedUser = JSON.parse(sessionStorage.getItem("user"));
const toggleFavorite = async (id) => {
const response = await fetch(
"http://localhost:3030/api/product/addFavorite",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userID: storedUser.ID,
productID: id,
}),
},
);
const data = await response.json();
if (data.success) {
setShowAlert(true);
}
console.log(`Add Product -> History: ${id}`);
};
const [reviewForm, setReviewForm] = useState({ const [reviewForm, setReviewForm] = useState({
rating: 3, rating: 3,
comment: "", comment: "",
@@ -68,7 +94,7 @@ const ProductDetail = () => {
userId: storedUser.ID, userId: storedUser.ID,
}; };
const response = await fetch(`http://localhost:3030/api/review/add`, { const response = await fetch(`http://localhost:3030/api/review/addReview`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(reviewData), body: JSON.stringify(reviewData),
@@ -182,38 +208,6 @@ const ProductDetail = () => {
fetchReviews(); fetchReviews();
}, [id]); }, [id]);
// Handle favorite toggle with error handling
const toggleFavorite = async () => {
try {
const response = await fetch(
"http://localhost:3030/api/product/add_to_favorite",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userID: 1, // Replace with actual user ID
productsID: id,
}),
},
);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
setIsFavorite(!isFavorite);
} else {
throw new Error(result.message || "Failed to toggle favorite");
}
} catch (error) {
console.error("Error toggling favorite:", error);
alert(`Failed to add to favorites: ${error.message}`);
}
};
// Image navigation // Image navigation
const nextImage = () => { const nextImage = () => {
@@ -296,6 +290,7 @@ const ProductDetail = () => {
// Render product details // Render product details
return ( return (
<div className="max-w-6xl mx-auto px-4 py-8"> <div className="max-w-6xl mx-auto px-4 py-8">
<div className="mb-6"> <div className="mb-6">
<Link <Link
@@ -306,6 +301,12 @@ const ProductDetail = () => {
<span>Back</span> <span>Back</span>
</Link> </Link>
</div> </div>
{showAlert && (
<FloatingAlert
message="Product added to favorites!"
onClose={() => setShowAlert(false)}
/>
)}
<div className="flex flex-col md:flex-row gap-8"> <div className="flex flex-col md:flex-row gap-8">
<div className="md:w-3/5"> <div className="md:w-3/5">
@@ -370,7 +371,6 @@ const ProductDetail = () => {
</div> </div>
)} )}
</div> </div>
<div className="md:w-2/5"> <div className="md:w-2/5">
<div className="bg-white border border-gray-200 p-6 mb-6"> <div className="bg-white border border-gray-200 p-6 mb-6">
<div className="flex justify-between items-start mb-4"> <div className="flex justify-between items-start mb-4">
@@ -378,7 +378,7 @@ const ProductDetail = () => {
{product.Name || "Unnamed Product"} {product.Name || "Unnamed Product"}
</h1> </h1>
<button <button
onClick={toggleFavorite} onClick={() => toggleFavorite(product.ProductID)}
className="p-2 hover:bg-gray-100 rounded-full" className="p-2 hover:bg-gray-100 rounded-full"
aria-label={ aria-label={
isFavorite ? "Remove from favorites" : "Add to favorites" isFavorite ? "Remove from favorites" : "Add to favorites"

View File

@@ -17,6 +17,9 @@ const SearchPage = () => {
const [priceRange, setPriceRange] = useState({ min: 0, max: 1000 }); const [priceRange, setPriceRange] = useState({ min: 0, max: 1000 });
const [isFilterOpen, setIsFilterOpen] = useState(false); const [isFilterOpen, setIsFilterOpen] = useState(false);
useEffect(() => { useEffect(() => {
fetchProducts(initialSearchQuery); fetchProducts(initialSearchQuery);
}, [initialSearchQuery]); }, [initialSearchQuery]);