diff --git a/backend/controllers/history.js b/backend/controllers/history.js index f2af282..684bb35 100644 --- a/backend/controllers/history.js +++ b/backend/controllers/history.js @@ -37,7 +37,6 @@ exports.HistoryByUserId = async (req, res) => { [id], ); - console.log(data); res.json({ success: true, message: "Products fetched successfully", diff --git a/backend/controllers/product.js b/backend/controllers/product.js index e482854..b3e52e0 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -1,12 +1,12 @@ const db = require("../utils/database"); -exports.addToFavorite = async (req, res) => { +exports.addFavorite = async (req, res) => { const { userID, productsID } = req.body; try { // Use parameterized query to prevent SQL injection const [result] = await db.execute( - "INSERT INTO Favorites (UserID, ProductID) VALUES unique(?, ?)", + "INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)", [userID, productsID], ); @@ -14,13 +14,41 @@ exports.addToFavorite = async (req, res) => { success: true, message: "Product added to favorites successfully", }); - console.log(result); } catch (error) { console.error("Error adding favorite product:", error); return res.json({ error: "Could not add favorite product" }); } }; +exports.getFavorites = async (req, res) => { + const { userID } = req.body; + + try { + const [favorites] = await db.execute( + ` + SELECT + p.*, + u.Name AS SellerName, + i.URL AS image_url + FROM Favorites f + JOIN Product p ON f.ProductID = p.ProductID + JOIN User u ON p.UserID = u.UserID + LEFT JOIN Image_URL i ON p.ProductID = i.ProductID + WHERE f.UserID = ? + `, + [userID], + ); + + res.json({ + success: true, + favorites: favorites, + }); + } catch (error) { + console.error("Error retrieving favorites:", error); + res.status(500).json({ error: "Could not retrieve favorite products" }); + } +}; + // Get all products along with their image URLs exports.getAllProducts = async (req, res) => { try { @@ -52,7 +80,6 @@ exports.getAllProducts = async (req, res) => { WHERE RowNum = 1; `); - console.log(data); res.json({ success: true, message: "Products fetched successfully", @@ -74,7 +101,7 @@ exports.getProductById = async (req, res) => { try { const [data] = await db.execute( ` - SELECT p.*,U.Name AS SellerName, i.URL AS image_url + SELECT p.*,U.Name AS SellerName,U.Email as SellerEmail,U.Phone as SellerPhone, i.URL AS image_url FROM Product p LEFT JOIN Image_URL i ON p.ProductID = i.ProductID JOIN User U ON p.UserID = U.UserID diff --git a/backend/controllers/review.js b/backend/controllers/review.js index 5de7d1e..e792409 100644 --- a/backend/controllers/review.js +++ b/backend/controllers/review.js @@ -1,13 +1,17 @@ const db = require("../utils/database"); -exports.getreview = async (req, res) => { +/** + * Get reviews for a specific product + * Returns both reviews for the product and reviews by the product owner for other products + */ +exports.getReviews = async (req, res) => { const { id } = req.params; console.log("Received Product ID:", id); try { - const [data] = await db.execute( - ` - SELECT + // First query: Get reviews for this specific product + const [productReviews] = await db.execute( + `SELECT R.ReviewID, R.UserID, R.ProductID, @@ -15,44 +19,49 @@ exports.getreview = async (req, res) => { R.Rating, R.Date AS ReviewDate, U.Name AS ReviewerName, - P.Name AS ProductName + P.Name AS ProductName, + 'product' AS ReviewType FROM Review R JOIN User U ON R.UserID = U.UserID JOIN Product P ON R.ProductID = P.ProductID - WHERE R.ProductID = ? - - UNION - - SELECT - R.ReviewID, - R.UserID, - R.ProductID, - R.Comment, - R.Rating, - R.Date AS ReviewDate, - U.Name AS ReviewerName, - P.Name AS ProductName - FROM Review R - JOIN User U ON R.UserID = U.UserID - JOIN Product P ON R.ProductID = P.ProductID - WHERE P.UserID = ( - SELECT UserID - FROM Product - WHERE ProductID = ? - ) - AND R.UserID != P.UserID; - `, - [id, id], + WHERE R.ProductID = ?`, + [id], ); - // Log raw data for debugging - console.log("Raw Database Result:", data); + // // Second query: Get reviews written by the product owner for other products + // const [sellerReviews] = await db.execute( + // `SELECT + // R.ReviewID, + // R.UserID, + // R.ProductID, + // R.Comment, + // R.Rating, + // R.Date AS ReviewDate, + // U.Name AS ReviewerName, + // P.Name AS ProductName, + // 'seller' AS ReviewType + // FROM Review R + // JOIN User U ON R.UserID = U.UserID + // JOIN Product P ON R.ProductID = P.ProductID + // WHERE R.UserID = ( + // SELECT UserID + // FROM Product + // WHERE ProductID = ? + // ) + // AND R.ProductID != ?`, + // [id, id], + // ); + + // Combine the results + const combinedReviews = [...productReviews]; + + // Log data for debugging + console.log("Combined Reviews:", combinedReviews); - console.log(data); res.json({ success: true, - message: "Products fetched successfully", - data, + message: "Reviews fetched successfully", + data: combinedReviews, }); } catch (error) { console.error("Full Error Details:", error); @@ -64,7 +73,9 @@ exports.getreview = async (req, res) => { } }; -// Add this to your existing controller file +/** + * Submit a new review for a product + */ exports.submitReview = async (req, res) => { const { productId, userId, rating, comment } = req.body; @@ -85,16 +96,41 @@ exports.submitReview = async (req, res) => { } try { + // Check if user has already reviewed this product + const [existingReview] = await db.execute( + `SELECT ReviewID FROM Review WHERE ProductID = ? AND UserID = ?`, + [productId, userId], + ); + + if (existingReview.length > 0) { + return res.status(400).json({ + success: false, + message: "You have already reviewed this product", + }); + } + + // Check if user is trying to review their own product + const [productOwner] = await db.execute( + `SELECT UserID FROM Product WHERE ProductID = ?`, + [productId], + ); + + if (productOwner.length > 0 && productOwner[0].UserID === userId) { + return res.status(400).json({ + success: false, + message: "You cannot review your own product", + }); + } + // Insert the review into the database const [result] = await db.execute( - ` - INSERT INTO Review ( + `INSERT INTO Review ( ProductID, UserID, Rating, - Comment - ) VALUES (?, ?, ?, ?) - `, + Comment, + Date + ) VALUES (?, ?, ?, ?, NOW())`, [productId, userId, rating, comment], ); @@ -103,22 +139,24 @@ exports.submitReview = async (req, res) => { // Fetch the newly created review to return to client const [newReview] = await db.execute( - ` - SELECT - ReviewID as id, - ProductID, - UserID, - Rating, - Comment, - Date as ReviewDate - FROM Review - WHERE ReviewID = ? - `, + `SELECT + R.ReviewID, + R.ProductID, + R.UserID, + R.Rating, + R.Comment, + R.Date AS ReviewDate, + U.Name AS ReviewerName, + P.Name AS ProductName + FROM Review R + JOIN User U ON R.UserID = U.UserID + JOIN Product P ON R.ProductID = P.ProductID + WHERE R.ReviewID = ?`, [reviewId], ); res.status(201).json({ - success: false, + success: true, // Fixed from false to true message: "Review submitted successfully", data: newReview[0], }); @@ -131,3 +169,134 @@ exports.submitReview = async (req, res) => { }); } }; + +// /** +// * Update an existing review +// */ +// exports.updateReview = async (req, res) => { +// const { reviewId } = req.params; +// const { rating, comment } = req.body; +// const userId = req.body.userId; // Assuming you have middleware that validates the user + +// // Validate required fields +// if (!reviewId || !rating || !comment) { +// return res.status(400).json({ +// success: false, +// message: "Missing required fields", +// }); +// } + +// // Validate rating is between 1 and 5 +// if (rating < 1 || rating > 5) { +// return res.status(400).json({ +// success: false, +// message: "Rating must be between 1 and 5", +// }); +// } + +// try { +// // Check if review exists and belongs to the user +// const [existingReview] = await db.execute( +// `SELECT ReviewID, UserID FROM Review WHERE ReviewID = ?`, +// [reviewId], +// ); + +// if (existingReview.length === 0) { +// return res.status(404).json({ +// success: false, +// message: "Review not found", +// }); +// } + +// if (existingReview[0].UserID !== userId) { +// return res.status(403).json({ +// success: false, +// message: "You can only update your own reviews", +// }); +// } + +// // Update the review +// await db.execute( +// `UPDATE Review +// SET Rating = ?, Comment = ?, Date = NOW() +// WHERE ReviewID = ?`, +// [rating, comment, reviewId], +// ); + +// // Fetch the updated review +// const [updatedReview] = await db.execute( +// `SELECT +// R.ReviewID, +// R.ProductID, +// R.UserID, +// R.Rating, +// R.Comment, +// R.Date AS ReviewDate, +// U.Name AS ReviewerName, +// P.Name AS ProductName +// FROM Review R +// JOIN User U ON R.UserID = U.UserID +// JOIN Product P ON R.ProductID = P.ProductID +// WHERE R.ReviewID = ?`, +// [reviewId], +// ); + +// res.json({ +// success: true, +// message: "Review updated successfully", +// data: updatedReview[0], +// }); +// } catch (error) { +// console.error("Error updating review:", error); +// return res.status(500).json({ +// success: false, +// message: "Database error occurred", +// error: error.message, +// }); +// } +// }; + +// /** +// * Delete a review +// */ +// exports.deleteReview = async (req, res) => { +// const { reviewId } = req.params; +// const userId = req.body.userId; // Assuming you have middleware that validates the user + +// try { +// // Check if review exists and belongs to the user +// const [existingReview] = await db.execute( +// `SELECT ReviewID, UserID FROM Review WHERE ReviewID = ?`, +// [reviewId], +// ); + +// if (existingReview.length === 0) { +// return res.status(404).json({ +// success: false, +// message: "Review not found", +// }); +// } + +// if (existingReview[0].UserID !== userId) { +// return res.status(403).json({ +// success: false, +// message: "You can only delete your own reviews", +// }); +// } + +// // Delete the review +// await db.execute(`DELETE FROM Review WHERE ReviewID = ?`, [reviewId]); + +// res.json({ +// success: true, +// message: "Review deleted successfully", +// }); +// } catch (error) { +// console.error("Error deleting review:", error); +// return res.status(500).json({ +// success: false, +// message: "Database error occurred", +// error: error.message, +// }); +// } +// }; diff --git a/backend/index.js b/backend/index.js index c7b4328..0d0dbcb 100644 --- a/backend/index.js +++ b/backend/index.js @@ -39,9 +39,9 @@ checkDatabaseConnection(db); //Routes app.use("/api/user", userRouter); //prefix with /api/user app.use("/api/product", productRouter); //prefix with /api/product -app.use("/api/search_products", searchRouter); //prefix with /api/product -app.use("/api/Engine", recommendedRouter); //prefix with /api/ -app.use("/api/get", history); //prefix with /api/ +app.use("/api/search", searchRouter); //prefix with /api/product +app.use("/api/engine", recommendedRouter); //prefix with /api/ +app.use("/api/history", history); //prefix with /api/ app.use("/api/review", review); //prefix with /api/ // Set up a scheduler to run cleanup every hour diff --git a/backend/routes/history.js b/backend/routes/history.js index 8d78efa..50cd826 100644 --- a/backend/routes/history.js +++ b/backend/routes/history.js @@ -3,6 +3,6 @@ const express = require("express"); const { HistoryByUserId } = require("../controllers/history"); const router = express.Router(); -router.post("/history", HistoryByUserId); +router.post("/getHistory", HistoryByUserId); module.exports = router; diff --git a/backend/routes/product.js b/backend/routes/product.js index 90c7914..7158fbf 100644 --- a/backend/routes/product.js +++ b/backend/routes/product.js @@ -1,7 +1,8 @@ // routes/product.js const express = require("express"); const { - addToFavorite, + addFavorite, + getFavorites, getAllProducts, getProductById, } = require("../controllers/product"); @@ -13,8 +14,10 @@ router.use((req, res, next) => { next(); }); -router.post("/add_fav_product", addToFavorite); -router.get("/get_product", getAllProducts); +router.post("/addFavorite", addFavorite); +router.post("/getFavorites", getFavorites); + +router.get("/getProduct", getAllProducts); router.get("/:id", getProductById); // Simplified route module.exports = router; diff --git a/backend/routes/review.js b/backend/routes/review.js index de3504f..5b26a87 100644 --- a/backend/routes/review.js +++ b/backend/routes/review.js @@ -1,9 +1,9 @@ // routes/product.js const express = require("express"); -const { getreview, submitReview } = require("../controllers/review"); +const { getReviews, submitReview } = require("../controllers/review"); const router = express.Router(); -router.get("/:id", getreview); +router.get("/:id", getReviews); router.post("/add", submitReview); module.exports = router; diff --git a/backend/routes/search.js b/backend/routes/search.js index 2871eba..d59f785 100644 --- a/backend/routes/search.js +++ b/backend/routes/search.js @@ -9,6 +9,6 @@ router.use((req, res, next) => { next(); }); -router.get("/search", searchProductsByName); +router.get("/getProduct", searchProductsByName); module.exports = router; diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 555744e..c07c032 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -11,9 +11,8 @@ const Home = () => { const storedUser = JSON.parse(sessionStorage.getItem("user")); const handleLinkClick = async (id) => { - // Example: append to localStorage or call analytics const response = await fetch( - "http://localhost:3030/api/product/add_fav_product", + "http://localhost:3030/api/product/addFavorites", { method: "POST", headers: { @@ -32,6 +31,16 @@ const Home = () => { console.log(`Add Product -> History: ${id}`); }; + function reloadPage() { + var doctTimestamp = new Date(performance.timing.domLoading).getTime(); + var now = Date.now(); + var tenSec = 10 * 1000; + if (now > doctTimestamp + tenSec) { + location.reload(); + } + } + reloadPage(); + useEffect(() => { const fetchrecomProducts = async () => { // Get the user's data from localStorage @@ -76,13 +85,15 @@ const Home = () => { } }; fetchrecomProducts(); + //reloadPage(); }, []); + reloadPage(); useEffect(() => { const fetchProducts = async () => { try { const response = await fetch( - "http://localhost:3030/api/product/get_product", + "http://localhost:3030/api/product/getProduct", ); if (!response.ok) throw new Error("Failed to fetch products"); @@ -118,15 +129,18 @@ const Home = () => { const storedUser = JSON.parse(sessionStorage.getItem("user")); console.log(storedUser); try { - const response = await fetch("http://localhost:3030/api/get/history", { - method: "POST", - headers: { - "Content-Type": "application/json", + const response = await fetch( + "http://localhost:3030/api/history/getHistory", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + id: storedUser.ID, + }), }, - body: JSON.stringify({ - id: storedUser.ID, - }), - }); + ); if (!response.ok) throw new Error("Failed to fetch products"); const data = await response.json(); diff --git a/frontend/src/pages/ProductDetail.jsx b/frontend/src/pages/ProductDetail.jsx index b8dbc47..711d1f3 100644 --- a/frontend/src/pages/ProductDetail.jsx +++ b/frontend/src/pages/ProductDetail.jsx @@ -1,6 +1,15 @@ -import { useState, useEffect, setErrors } from "react"; -import { useParams, Link, isSession } from "react-router-dom"; -import { Heart, ArrowLeft, Tag, User, Calendar, Star } from "lucide-react"; +import { useState, useEffect } from "react"; +import { useParams, Link } from "react-router-dom"; +import { + Heart, + ArrowLeft, + Tag, + User, + Calendar, + Star, + Phone, + Mail, +} from "lucide-react"; const ProductDetail = () => { const { id } = useParams(); @@ -8,16 +17,19 @@ const ProductDetail = () => { const [loading, setLoading] = useState({ product: true, reviews: true, + submitting: false, }); const [error, setError] = useState({ product: null, reviews: null, + submit: null, }); const [isFavorite, setIsFavorite] = useState(false); - const [contactForm, showContactForm, setShowContactForm] = useState(false); + const [showContactOptions, setShowContactOptions] = useState(false); const [currentImage, setCurrentImage] = useState(0); const [reviews, setReviews] = useState([]); const [showReviewForm, setShowReviewForm] = useState(false); + const storedUser = JSON.parse(sessionStorage.getItem("user")); const [reviewForm, setReviewForm] = useState({ rating: 3, @@ -42,39 +54,70 @@ const ProductDetail = () => { })); }; - const handleSubmitReview = async () => { - try { - // Ensure userId is present - if (!userData.userId) { - throw new Error("User ID is missing. Unable to update profile."); - } + const handleSubmitReview = async (e) => { + e.preventDefault(); // Prevent form default behavior - setIsLoading(true); - setError(null); + try { + setLoading((prev) => ({ ...prev, submitting: true })); + setError((prev) => ({ ...prev, submit: null })); + + const reviewData = { + productId: id, + rating: reviewForm.rating, + comment: reviewForm.comment, + userId: storedUser.ID, + }; const response = await fetch(`http://localhost:3030/api/review/add`, { - method: "POST", // or "PUT" if your backend supports it - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(reviewData), }); const result = await response.json(); - if (!response.ok) { - throw new Error(result.error || "Failed to update profile"); + // Check if API returned an error message even with 200 status + if (!result.success) { + throw new Error(result.message || "Failed to submit review"); } - console.log("Profile updated successfully:", result); - alert("Profile updated successfully!"); + alert("Review submitted successfully!"); + + setReviewForm({ + rating: 3, + comment: "", + name: "", + }); + setShowReviewForm(false); + + try { + setLoading((prev) => ({ ...prev, reviews: true })); + const reviewsResponse = await fetch( + `http://localhost:3030/api/review/${id}`, + ); + const reviewsResult = await reviewsResponse.json(); + + if (reviewsResult.success) { + setReviews(reviewsResult.data || []); + setError((prev) => ({ ...prev, reviews: null })); + } else { + throw new Error(reviewsResult.message || "Error fetching reviews"); + } + } catch (reviewsError) { + console.error("Error fetching reviews:", reviewsError); + setError((prev) => ({ ...prev, reviews: reviewsError.message })); + } finally { + setLoading((prev) => ({ ...prev, reviews: false })); + } } catch (error) { - console.error("Error updating profile:", error); - setError( - error.message || "An error occurred while updating your profile.", - ); + console.error("Error submitting review:", error); + alert(`Error: ${error.message}`); + setError((prev) => ({ + ...prev, + submit: error.message, + })); } finally { - setIsLoading(false); + setLoading((prev) => ({ ...prev, submitting: false })); } }; @@ -172,48 +215,6 @@ const ProductDetail = () => { } }; - // Handle form input changes - const handleContactInputChange = (e) => { - const { id, value } = e.target; - setContactForm((prev) => ({ - ...prev, - [id]: value, - })); - }; - - // Handle message submission with improved validation - const handleSendMessage = (e) => { - e.preventDefault(); - - // Basic validation - if (!contactForm.email || !contactForm.phone) { - alert("Please fill in all required fields"); - return; - } - - // Email validation - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(contactForm.email)) { - alert("Please enter a valid email address"); - return; - } - - // TODO: Implement actual message sending logic - try { - // Mock API call - console.log("Message sent:", contactForm); - setContactForm({ - email: "", - phone: "", - message: "Hi, is this item still available?", - }); - setShowContactForm(false); - alert("Message sent to seller!"); - } catch (error) { - alert(`Failed to send message: ${error.message}`); - } - }; - // Image navigation const nextImage = () => { if (product?.images?.length > 0) { @@ -417,12 +418,38 @@ const ProductDetail = () => {
- {/* */} +