diff --git a/backend/controllers/history.js b/backend/controllers/history.js
new file mode 100644
index 0000000..f2af282
--- /dev/null
+++ b/backend/controllers/history.js
@@ -0,0 +1,53 @@
+const db = require("../utils/database");
+
+// TODO: Get the recommondaed product given the userID
+exports.HistoryByUserId = async (req, res) => {
+ const { id } = req.body;
+ try {
+ const [data] = await db.execute(
+ `
+ WITH RankedImages AS (
+ SELECT
+ P.ProductID,
+ P.Name AS ProductName,
+ P.Price,
+ P.Date AS DateUploaded,
+ U.Name AS SellerName,
+ I.URL AS ProductImage,
+ C.Name AS Category,
+ ROW_NUMBER() OVER (PARTITION BY P.ProductID ORDER BY I.URL) AS RowNum
+ FROM Product P
+ JOIN Image_URL I ON P.ProductID = I.ProductID
+ 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 = ?
+ )
+ SELECT
+ ProductID,
+ ProductName,
+ Price,
+ DateUploaded,
+ SellerName,
+ ProductImage,
+ Category
+ FROM RankedImages
+ WHERE RowNum = 1;
+ `,
+ [id],
+ );
+
+ console.log(data);
+ res.json({
+ success: true,
+ message: "Products fetched successfully",
+ data,
+ });
+ } catch (error) {
+ console.error("Error finding products:", error);
+ return res.status(500).json({
+ found: false,
+ error: "Database error occurred",
+ });
+ }
+};
diff --git a/backend/controllers/product.js b/backend/controllers/product.js
index 1fcf06b..c8f9885 100644
--- a/backend/controllers/product.js
+++ b/backend/controllers/product.js
@@ -24,18 +24,31 @@ exports.addToFavorite = async (req, res) => {
exports.getAllProducts = async (req, res) => {
try {
const [data, fields] = await db.execute(`
+ WITH RankedImages AS (
+ SELECT
+ P.ProductID,
+ P.Name AS ProductName,
+ P.Price,
+ P.Date AS DateUploaded,
+ U.Name AS SellerName,
+ I.URL AS ProductImage,
+ C.Name AS Category,
+ ROW_NUMBER() OVER (PARTITION BY P.ProductID ORDER BY I.URL) AS RowNum
+ FROM Product P
+ JOIN Image_URL I ON P.ProductID = I.ProductID
+ JOIN User U ON P.UserID = U.UserID
+ JOIN Category C ON P.CategoryID = C.CategoryID
+ )
SELECT
- P.ProductID,
- P.Name AS ProductName,
- P.Price,
- P.Date AS DateUploaded,
- U.Name AS SellerName,
- I.URL AS ProductImage,
- C.Name AS Category
- FROM Product P
- JOIN Image_URL I ON P.ProductID = I.ProductID
- JOIN User U ON P.UserID = U.UserID
- JOIN Category C ON P.CategoryID = C.CategoryID;
+ ProductID,
+ ProductName,
+ Price,
+ DateUploaded,
+ SellerName,
+ ProductImage,
+ Category
+ FROM RankedImages
+ WHERE RowNum = 1;
`);
console.log(data);
diff --git a/backend/controllers/recommendation.js b/backend/controllers/recommendation.js
index 488b089..5518d46 100644
--- a/backend/controllers/recommendation.js
+++ b/backend/controllers/recommendation.js
@@ -6,20 +6,34 @@ exports.RecommondationByUserId = async (req, res) => {
try {
const [data, fields] = await db.execute(
`
+ WITH RankedImages AS (
+ SELECT
+ P.ProductID,
+ P.Name AS ProductName,
+ P.Price,
+ P.Date AS DateUploaded,
+ U.Name AS SellerName,
+ I.URL AS ProductImage,
+ C.Name AS Category,
+ ROW_NUMBER() OVER (PARTITION BY P.ProductID ORDER BY I.URL) AS RowNum
+ FROM Product P
+ JOIN Image_URL I ON P.ProductID = I.ProductID
+ JOIN User U ON P.UserID = U.UserID
+ JOIN Category C ON P.CategoryID = C.CategoryID
+ JOIN Recommendation R ON P.ProductID = R.RecommendedProductID
+ WHERE R.UserID = ?
+ )
SELECT
- P.ProductID,
- P.Name AS ProductName,
- P.Price,
- P.Date AS DateUploaded,
- U.Name AS SellerName,
- I.URL AS ProductImage,
- C.Name AS Category
- FROM Product P
- JOIN Image_URL I ON P.ProductID = I.ProductID
- JOIN User U ON P.UserID = U.UserID
- JOIN Category C ON P.CategoryID = C.CategoryID
- JOIN Recommendation R ON P.ProductID = R.RecommendedProductID
- Where R.UserID = ?;`,
+ ProductID,
+ ProductName,
+ Price,
+ DateUploaded,
+ SellerName,
+ ProductImage,
+ Category
+ FROM RankedImages
+ WHERE RowNum = 1;
+ `,
[id],
);
@@ -37,3 +51,64 @@ exports.RecommondationByUserId = async (req, res) => {
});
}
};
+
+// Add this to your existing controller file
+exports.submitReview = async (req, res) => {
+ const { productId, reviewerName, rating, comment } = req.body;
+
+ // Validate required fields
+ if (!productId || !reviewerName || !rating || !comment) {
+ return res.status(400).json({
+ success: false,
+ message: "Missing required fields",
+ });
+ }
+
+ try {
+ // Insert the review into the database
+ const [result] = await db.execute(
+ `
+ INSERT INTO Review (
+ ProductID,
+ ReviewerName,
+ Rating,
+ Comment,
+ ReviewDate
+ ) VALUES (?, ?, ?, ?, NOW())
+ `,
+ [productId, reviewerName, rating, comment],
+ );
+
+ // Get the inserted review id
+ const reviewId = result.insertId;
+
+ // Fetch the newly created review to return to client
+ const [newReview] = await db.execute(
+ `
+ SELECT
+ ReviewID as id,
+ ProductID,
+ ReviewerName,
+ Rating,
+ Comment,
+ ReviewDate
+ FROM Review
+ WHERE ReviewID = ?
+ `,
+ [reviewId],
+ );
+
+ res.status(201).json({
+ success: true,
+ message: "Review submitted successfully",
+ data: newReview[0],
+ });
+ } catch (error) {
+ console.error("Error submitting review:", error);
+ return res.status(500).json({
+ success: false,
+ message: "Database error occurred",
+ error: error.message,
+ });
+ }
+};
diff --git a/backend/controllers/review.js b/backend/controllers/review.js
new file mode 100644
index 0000000..5de7d1e
--- /dev/null
+++ b/backend/controllers/review.js
@@ -0,0 +1,133 @@
+const db = require("../utils/database");
+
+exports.getreview = async (req, res) => {
+ const { id } = req.params;
+ console.log("Received Product ID:", id);
+
+ try {
+ const [data] = 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
+ 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],
+ );
+
+ // Log raw data for debugging
+ console.log("Raw Database Result:", data);
+
+ console.log(data);
+ res.json({
+ success: true,
+ message: "Products fetched successfully",
+ data,
+ });
+ } catch (error) {
+ console.error("Full Error Details:", error);
+ return res.status(500).json({
+ success: false,
+ message: "Database error occurred",
+ error: error.message,
+ });
+ }
+};
+
+// Add this to your existing controller file
+exports.submitReview = async (req, res) => {
+ const { productId, userId, rating, comment } = req.body;
+
+ // Validate required fields
+ if (!productId || !userId || !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 {
+ // Insert the review into the database
+ const [result] = await db.execute(
+ `
+ INSERT INTO Review (
+ ProductID,
+ UserID,
+ Rating,
+ Comment
+ ) VALUES (?, ?, ?, ?)
+ `,
+ [productId, userId, rating, comment],
+ );
+
+ // Get the inserted review id
+ const reviewId = result.insertId;
+
+ // 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 = ?
+ `,
+ [reviewId],
+ );
+
+ res.status(201).json({
+ success: false,
+ message: "Review submitted successfully",
+ data: newReview[0],
+ });
+ } catch (error) {
+ console.error("Error submitting 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 c3df135..c7b4328 100644
--- a/backend/index.js
+++ b/backend/index.js
@@ -7,6 +7,9 @@ const userRouter = require("./routes/user");
const productRouter = require("./routes/product");
const searchRouter = require("./routes/search");
const recommendedRouter = require("./routes/recommendation");
+const history = require("./routes/history");
+const review = require("./routes/review");
+
const { generateEmailTransporter } = require("./utils/mail");
const {
cleanupExpiredCodes,
@@ -38,6 +41,8 @@ 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/review", review); //prefix with /api/
// Set up a scheduler to run cleanup every hour
setInterval(cleanupExpiredCodes, 60 * 60 * 1000);
diff --git a/backend/routes/history.js b/backend/routes/history.js
new file mode 100644
index 0000000..8d78efa
--- /dev/null
+++ b/backend/routes/history.js
@@ -0,0 +1,8 @@
+// routes/product.js
+const express = require("express");
+const { HistoryByUserId } = require("../controllers/history");
+const router = express.Router();
+
+router.post("/history", HistoryByUserId);
+
+module.exports = router;
diff --git a/backend/routes/review.js b/backend/routes/review.js
new file mode 100644
index 0000000..de3504f
--- /dev/null
+++ b/backend/routes/review.js
@@ -0,0 +1,9 @@
+// routes/product.js
+const express = require("express");
+const { getreview, submitReview } = require("../controllers/review");
+const router = express.Router();
+
+router.get("/:id", getreview);
+router.post("/add", submitReview);
+
+module.exports = router;
diff --git a/frontend/public/image8.jpg b/frontend/public/image8.jpg
new file mode 100644
index 0000000..eb1e1a1
Binary files /dev/null and b/frontend/public/image8.jpg differ
diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx
index d55af11..92f3f3c 100644
--- a/frontend/src/pages/Home.jsx
+++ b/frontend/src/pages/Home.jsx
@@ -6,6 +6,7 @@ const Home = () => {
const navigate = useNavigate();
const [listings, setListings] = useState([]);
const [recommended, setRecommended] = useState([]);
+ const [history, sethistory] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
@@ -38,7 +39,6 @@ const Home = () => {
price: product.Price,
category: product.Category, // Ensure this gets the category name
image: product.ProductImage, // Use the alias for image URL
- condition: "New", // Modify based on actual data
seller: product.SellerName, // Fetch seller name properly
datePosted: product.DateUploaded, // Use the actual date
isFavorite: false, // Default state
@@ -73,7 +73,6 @@ const Home = () => {
price: product.Price,
category: product.Category, // Ensure this gets the category name
image: product.ProductImage, // Use the alias for image URL
- condition: "New", // Modify based on actual data
seller: product.SellerName, // Fetch seller name properly
datePosted: product.DateUploaded, // Use the actual date
isFavorite: false, // Default state
@@ -90,6 +89,49 @@ const Home = () => {
fetchProducts();
}, []);
+ 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("http://localhost:3030/api/get/history", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ id: storedUser.ID,
+ }),
+ });
+ if (!response.ok) throw new Error("Failed to fetch products");
+
+ const data = await response.json();
+ console.log(data);
+ if (data.success) {
+ sethistory(
+ data.data.map((product) => ({
+ id: product.ProductID,
+ title: product.ProductName, // Use the alias from SQL
+ price: product.Price,
+ category: product.Category, // Ensure this gets the category name
+ 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 {
+ throw new Error(data.message || "Error fetching products");
+ }
+ } catch (error) {
+ console.error("Error fetching products:", error);
+ setError(error.message);
+ }
+ };
+ fetchrecomProducts();
+ }, []);
+
// Toggle favorite status
const toggleFavorite = (id, e) => {
e.preventDefault(); // Prevent navigation when clicking the heart icon
@@ -138,26 +180,6 @@ const Home = () => {
- {/* Categories */}
- {/*
-
Categories
-
- {categories.map((category) => (
-
- ))}
-
-
*/}
-
{/* Recent Listings */}
@@ -219,8 +241,6 @@ const Home = () => {
{recommended.category}
- •
- {recommended.condition}
@@ -311,8 +331,6 @@ const Home = () => {
{listing.category}
- •
- {listing.condition}
@@ -341,6 +359,94 @@ const Home = () => {
+
+ {/* Recent Listings */}
+
+
History
+
+
+ {/* Left Button - Overlaid on products */}
+
+
+ {/* Scrollable Listings Container */}
+
+ {history.map((history) => (
+
+
+

+
+
+
+
+
+ {history.title}
+
+
+ ${history.price}
+
+
+
+
+ {history.category}
+
+
+
+
+ {history.datePosted}
+
+
+ {history.seller}
+
+
+
+
+ ))}
+
+
+ {/* Right Button - Overlaid on products */}
+
+
+
);
};
diff --git a/frontend/src/pages/ProductDetail.jsx b/frontend/src/pages/ProductDetail.jsx
index aca2887..b64a998 100644
--- a/frontend/src/pages/ProductDetail.jsx
+++ b/frontend/src/pages/ProductDetail.jsx
@@ -1,50 +1,145 @@
-import { useState, useEffect } from "react";
-import { useParams, Link } from "react-router-dom";
-import { Heart, ArrowLeft, Tag, User, Calendar } from "lucide-react";
+import { useState, useEffect, setErrors } from "react";
+import { useParams, Link, isSession } from "react-router-dom";
+import { Heart, ArrowLeft, Tag, User, Calendar, Star } from "lucide-react";
const ProductDetail = () => {
const { id } = useParams();
const [product, setProduct] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
+ const [loading, setLoading] = useState({
+ product: true,
+ reviews: true,
+ });
+ const [error, setError] = useState({
+ product: null,
+ reviews: null,
+ });
const [isFavorite, setIsFavorite] = useState(false);
const [showContactForm, setShowContactForm] = useState(false);
- const [message, setMessage] = useState("");
const [currentImage, setCurrentImage] = useState(0);
+ const [reviews, setReviews] = useState([]);
+ const [showReviewForm, setShowReviewForm] = useState(false);
- // Fetch product details
+ const [reviewForm, setReviewForm] = useState({
+ rating: 3,
+ comment: "",
+ name: "",
+ });
+
+ // Add this function to handle review input changes
+ const handleReviewInputChange = (e) => {
+ const { id, value } = e.target;
+ setReviewForm((prev) => ({
+ ...prev,
+ [id]: value,
+ }));
+ };
+
+ // Add this function to handle star rating selection
+ const handleRatingChange = (rating) => {
+ setReviewForm((prev) => ({
+ ...prev,
+ rating,
+ }));
+ };
+
+ const handleSubmitReview = async () => {
+ try {
+ // Ensure userId is present
+ if (!userData.userId) {
+ throw new Error("User ID is missing. Unable to update profile.");
+ }
+
+ setIsLoading(true);
+ setError(null);
+
+ 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),
+ });
+
+ const result = await response.json();
+
+ if (!response.ok) {
+ throw new Error(result.error || "Failed to update profile");
+ }
+
+ console.log("Profile updated successfully:", result);
+ alert("Profile updated successfully!");
+ } catch (error) {
+ console.error("Error updating profile:", error);
+ setError(
+ error.message || "An error occurred while updating your profile.",
+ );
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // Fetch product data
useEffect(() => {
const fetchProduct = async () => {
try {
- setLoading(true);
+ setLoading((prev) => ({ ...prev, product: true }));
const response = await fetch(`http://localhost:3030/api/product/${id}`);
if (!response.ok) {
- throw new Error("Failed to fetch product");
+ throw new Error(`HTTP error! Status: ${response.status}`);
}
const result = await response.json();
- console.log(result);
if (result.success) {
setProduct(result.data);
- setError(null);
+ setError((prev) => ({ ...prev, product: null }));
} else {
throw new Error(result.message || "Error fetching product");
}
} catch (error) {
console.error("Error fetching product:", error);
- setError(error.message);
- setProduct(null);
+ setError((prev) => ({ ...prev, product: error.message }));
} finally {
- setLoading(false);
+ setLoading((prev) => ({ ...prev, product: false }));
}
};
fetchProduct();
}, [id]);
- // Handle favorite toggle
+ // Fetch reviews data
+ useEffect(() => {
+ const fetchReviews = async () => {
+ try {
+ setLoading((prev) => ({ ...prev, reviews: true }));
+ const response = await fetch(`http://localhost:3030/api/review/${id}`);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ const result = await response.json();
+
+ if (result.success) {
+ setReviews(result.data || []);
+ setError((prev) => ({ ...prev, reviews: null }));
+ } else {
+ throw new Error(result.message || "Error fetching reviews");
+ }
+ } catch (error) {
+ console.error("Error fetching reviews:", error);
+ setError((prev) => ({ ...prev, reviews: error.message }));
+ setReviews([]);
+ } finally {
+ setLoading((prev) => ({ ...prev, reviews: false }));
+ }
+ };
+
+ fetchReviews();
+ }, [id]);
+
+ // Handle favorite toggle with error handling
const toggleFavorite = async () => {
try {
const response = await fetch(
@@ -61,28 +156,67 @@ const ProductDetail = () => {
},
);
+ 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}`);
}
};
- // Handle message submission
+ // 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
- console.log("Message sent:", message);
- setMessage("");
- setShowContactForm(false);
- alert("Message sent to seller!");
+ 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 && product.images) {
+ if (product?.images?.length > 0) {
setCurrentImage((prev) =>
prev === product.images.length - 1 ? 0 : prev + 1,
);
@@ -90,7 +224,7 @@ const ProductDetail = () => {
};
const prevImage = () => {
- if (product && product.images) {
+ if (product?.images?.length > 0) {
setCurrentImage((prev) =>
prev === 0 ? product.images.length - 1 : prev - 1,
);
@@ -101,8 +235,22 @@ const ProductDetail = () => {
setCurrentImage(index);
};
- // Render loading state
- if (loading) {
+ // Function to render stars based on rating
+ const renderStars = (rating) => {
+ const stars = [];
+ for (let i = 1; i <= 5; i++) {
+ stars.push(
+ ,
+ );
+ }
+ return stars;
+ };
+
+ // Render loading state for the entire page
+ if (loading.product) {
return (
@@ -110,13 +258,30 @@ const ProductDetail = () => {
);
}
- // Render error state
- if (error) {
+ // Render error state for product
+ if (error.product) {
return (
Error Loading Product
-
{error}
+
{error.product}
+
+ Back to Listings
+
+
+
+ );
+ }
+
+ // Safety check for product
+ if (!product) {
+ return (
+
+
+
Product Not Found
{
{product.images && product.images.length > 0 ? (
-

+ <>
+

{
+ e.target.onerror = null;
+ e.target.src =
+ "https://via.placeholder.com/400x300?text=Image+Not+Available";
+ }}
+ />
+ {product.images.length > 1 && (
+
+
+
+ {currentImage + 1}/{product.images.length}
+
+
+ )}
+ >
) : (
No Image Available
@@ -170,6 +358,11 @@ const ProductDetail = () => {
src={image}
alt={`${product.Name} - view ${index + 1}`}
className="w-full h-auto object-cover"
+ onError={(e) => {
+ e.target.onerror = null;
+ e.target.src =
+ "https://via.placeholder.com/100x100?text=Error";
+ }}
/>
))}
@@ -181,11 +374,14 @@ const ProductDetail = () => {
- {product.Name}
+ {product.Name || "Unnamed Product"}
- ${product.Price}
+ $
+ {typeof product.Price === "number"
+ ? product.Price.toFixed(2)
+ : product.Price}
-
-
- {product.Category}
-
+ {product.Category && (
+
+
+ {product.Category}
+
+ )}
-
-
- Posted on {product.Date}
-
+ {product.Date && (
+
+
+ Posted on {product.Date}
+
+ )}
-
{product.Description}
+
+ {product.Description || "No description available"}
+
)}
@@ -287,14 +503,21 @@ const ProductDetail = () => {
{product.SellerName || "Unknown Seller"}
- Member since {product.SellerName}
+ Member since {product.SellerJoinDate || "N/A"}
Rating:{" "}
- {product.seller ? `${product.seller.rating}/5` : "N/A"}
+ {product.seller?.rating ? (
+
+ {renderStars(product.seller.rating)}
+ {product.seller.rating}/5
+
+ ) : (
+ "N/A"
+ )}
@@ -302,12 +525,158 @@ const ProductDetail = () => {
- {/*
-
Description
+ {/* Reviews Section */}
+
+
Reviews
+
-
{product.Description}
+ {loading.reviews ? (
+
+ ) : error.reviews ? (
+
+ Error loading reviews: {error.reviews}
+
+ ) : reviews.length === 0 ? (
+
+ No reviews yet for this product
+
+ ) : (
+
+ {reviews.map((review) => (
+
+
+
+ {review.ReviewerName || "Anonymous"}
+
+
+ {review.ReviewDate
+ ? new Date(review.ReviewDate).toLocaleDateString()
+ : "Unknown date"}
+
+
+
+
+ {renderStars(review.Rating || 0)}
+
+ {review.Rating || 0}/5
+
+
+
+
+ {review.Comment || "No comment provided"}
+
+
+ ))}
+
+ )}
-
*/}
+
+
+
+
+
+ {/* Review Popup Form */}
+ {showReviewForm && (
+
+
+
+
+ Write a Review
+
+
+
+
+
+
+
+ )}
+
);
};
diff --git a/frontend/src/pages/SearchPage.jsx b/frontend/src/pages/SearchPage.jsx
index 905eb70..2fbcd48 100644
--- a/frontend/src/pages/SearchPage.jsx
+++ b/frontend/src/pages/SearchPage.jsx
@@ -96,12 +96,13 @@ const SearchPage = () => {
return (
+ {/* Filter sidebar */}
@@ -110,9 +111,8 @@ const SearchPage = () => {
-
-
+
Price Range
@@ -146,13 +146,13 @@ const SearchPage = () => {
@@ -160,6 +160,7 @@ const SearchPage = () => {
+ {/* Main content */}
{filteredProducts.length} Results
@@ -170,18 +171,17 @@ const SearchPage = () => {
)}
-
{filteredProducts.map((listing) => (
diff --git a/mysql-code/Init-Data.sql b/mysql-code/Init-Data.sql
index b75516e..a61e04c 100644
--- a/mysql-code/Init-Data.sql
+++ b/mysql-code/Init-Data.sql
@@ -321,6 +321,9 @@ INSERT INTO
Image_URL (URL, ProductID)
VALUES
('/image1.avif', 1),
+ ('/image2.avif', 1),
+ ('/image3.avif', 1),
+ ('/image8.jpg', 1),
('/image1.avif', 2),
('/image1.avif', 3),
('/image1.avif', 4),
@@ -442,3 +445,14 @@ VALUES
(3, 1, 8, '2024-10-14 12:20:00', 'Pending'),
(4, 2, 10, '2024-10-13 17:10:00', 'Completed'),
(5, 2, 4, '2024-10-12 14:30:00', 'Completed');
+
+INSERT INTO
+ Review (UserID, ProductID, Comment, Rating, Date)
+VALUES
+ (
+ 1,
+ 1,
+ 'This is a great fake product! Totally recommend it.',
+ 5,
+ NOW ()
+ );
diff --git a/mysql-code/Schema.sql b/mysql-code/Schema.sql
index df51a58..9764c48 100644
--- a/mysql-code/Schema.sql
+++ b/mysql-code/Schema.sql
@@ -51,7 +51,7 @@ CREATE TABLE Image_URL (
-- Fixed Review Entity (Many-to-One with User, Many-to-One with Product)
CREATE TABLE Review (
- ReviewID INT PRIMARY KEY,
+ ReviewID INT AUTO_INCREMENT PRIMARY KEY,
UserID INT,
ProductID INT,
Comment TEXT,
@@ -61,7 +61,7 @@ CREATE TABLE Review (
),
Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID),
- FOREIGN KEY (ProductID) REFERENCES Pprint(item[0])roduct (ProductID)
+ FOREIGN KEY (ProductID) REFERENCES Product (ProductID)
);
-- Transaction Entity (Many-to-One with User, Many-to-One with Product)
@@ -270,16 +270,16 @@ CREATE TABLE AuthVerification (
-- -- WHERE
-- -- CategoryID = 6;
-- -- -- REVIEW OPERATIONS
--- -- INSERT INTO
--- -- Review (ReviewID, UserID, ProductID, Comment, Rating)
--- -- VALUES
--- -- (
--- -- 1,
--- -- 1,
--- -- 1,
--- -- 'Great product, very satisfied with the purchase!',
--- -- 5
--- -- );
+-- INSERT INTO
+-- Review (ReviewID, UserID, ProductID, Comment, Rating)
+-- VALUES
+-- (
+-- 1,
+-- 1,
+-- 1,
+-- 'Great product, very satisfied with the purchase!',
+-- 5
+-- );
-- -- -- TRANSACTION OPERATIONS
-- -- INSERT INTO
-- -- Transaction (TransactionID, UserID, ProductID, PaymentStatus)