fav product from prodDetail page
This commit is contained in:
@@ -47,14 +47,29 @@ exports.getFavorites = async (req, res) => {
|
||||
const [favorites] = await db.execute(
|
||||
`
|
||||
SELECT
|
||||
p.*,
|
||||
p.ProductID,
|
||||
p.Name,
|
||||
p.Description,
|
||||
p.Price,
|
||||
p.CategoryID,
|
||||
p.UserID,
|
||||
p.Date,
|
||||
u.Name AS SellerName,
|
||||
i.URL AS image_url
|
||||
MIN(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 = ?
|
||||
GROUP BY
|
||||
p.ProductID,
|
||||
p.Name,
|
||||
p.Description,
|
||||
p.Price,
|
||||
p.CategoryID,
|
||||
p.UserID,
|
||||
p.Date,
|
||||
u.Name;
|
||||
`,
|
||||
[userID],
|
||||
);
|
||||
@@ -73,31 +88,25 @@ exports.getFavorites = 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
|
||||
MIN(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
|
||||
)
|
||||
SELECT
|
||||
ProductID,
|
||||
ProductName,
|
||||
Price,
|
||||
DateUploaded,
|
||||
SellerName,
|
||||
ProductImage,
|
||||
Category
|
||||
FROM RankedImages
|
||||
WHERE RowNum = 1;
|
||||
GROUP BY
|
||||
P.ProductID,
|
||||
P.Name,
|
||||
P.Price,
|
||||
P.Date,
|
||||
U.Name,
|
||||
C.Name;
|
||||
`);
|
||||
|
||||
res.json({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
//Get the db connection
|
||||
|
||||
const db = require("./utils/database");
|
||||
|
||||
const userRouter = require("./routes/user");
|
||||
@@ -33,19 +33,20 @@ transporter
|
||||
console.error("Email connection failed:", error);
|
||||
});
|
||||
|
||||
//Check database connection
|
||||
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", 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/
|
||||
app.use("/api/user", userRouter);
|
||||
app.use("/api/product", productRouter);
|
||||
app.use("/api/search", searchRouter);
|
||||
app.use("/api/engine", recommendedRouter);
|
||||
app.use("/api/history", history);
|
||||
app.use("/api/review", review);
|
||||
|
||||
|
||||
// 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, () => {
|
||||
console.log(`Running Backend on http://localhost:3030/`);
|
||||
|
||||
@@ -47,8 +47,8 @@ const Navbar = ({ onLogout, userName }) => {
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search for books, electronics, furniture..."
|
||||
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"
|
||||
placeholder="Search for anything..."
|
||||
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}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
|
||||
@@ -34,7 +34,6 @@ const Home = () => {
|
||||
if (data.success) {
|
||||
setShowAlert(true);
|
||||
}
|
||||
console.log(response);
|
||||
console.log(`Add Product -> History: ${id}`);
|
||||
};
|
||||
|
||||
@@ -82,7 +81,6 @@ const Home = () => {
|
||||
if (!response.ok) throw new Error("Failed to fetch products");
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
if (data.success) {
|
||||
setRecommended(
|
||||
data.data.map((product) => ({
|
||||
@@ -145,7 +143,6 @@ const Home = () => {
|
||||
useEffect(() => {
|
||||
const fetchrecomProducts = async () => {
|
||||
// Get the user's data from localStorage
|
||||
console.log(storedUser);
|
||||
try {
|
||||
const response = await fetch(
|
||||
"http://localhost:3030/api/history/getHistory",
|
||||
@@ -162,7 +159,6 @@ const Home = () => {
|
||||
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) => ({
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
Phone,
|
||||
Mail,
|
||||
} from "lucide-react";
|
||||
import FloatingAlert from "../components/FloatingAlert"; // adjust path if needed
|
||||
|
||||
|
||||
const ProductDetail = () => {
|
||||
const { id } = useParams();
|
||||
@@ -29,8 +31,32 @@ const ProductDetail = () => {
|
||||
const [currentImage, setCurrentImage] = useState(0);
|
||||
const [reviews, setReviews] = useState([]);
|
||||
const [showReviewForm, setShowReviewForm] = useState(false);
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
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({
|
||||
rating: 3,
|
||||
comment: "",
|
||||
@@ -68,7 +94,7 @@ const ProductDetail = () => {
|
||||
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",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(reviewData),
|
||||
@@ -182,38 +208,6 @@ const ProductDetail = () => {
|
||||
fetchReviews();
|
||||
}, [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
|
||||
const nextImage = () => {
|
||||
@@ -296,6 +290,7 @@ const ProductDetail = () => {
|
||||
|
||||
// Render product details
|
||||
return (
|
||||
|
||||
<div className="max-w-6xl mx-auto px-4 py-8">
|
||||
<div className="mb-6">
|
||||
<Link
|
||||
@@ -306,6 +301,12 @@ const ProductDetail = () => {
|
||||
<span>Back</span>
|
||||
</Link>
|
||||
</div>
|
||||
{showAlert && (
|
||||
<FloatingAlert
|
||||
message="Product added to favorites!"
|
||||
onClose={() => setShowAlert(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-8">
|
||||
<div className="md:w-3/5">
|
||||
@@ -370,7 +371,6 @@ const ProductDetail = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="md:w-2/5">
|
||||
<div className="bg-white border border-gray-200 p-6 mb-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
@@ -378,7 +378,7 @@ const ProductDetail = () => {
|
||||
{product.Name || "Unnamed Product"}
|
||||
</h1>
|
||||
<button
|
||||
onClick={toggleFavorite}
|
||||
onClick={() => toggleFavorite(product.ProductID)}
|
||||
className="p-2 hover:bg-gray-100 rounded-full"
|
||||
aria-label={
|
||||
isFavorite ? "Remove from favorites" : "Add to favorites"
|
||||
|
||||
@@ -17,6 +17,9 @@ const SearchPage = () => {
|
||||
const [priceRange, setPriceRange] = useState({ min: 0, max: 1000 });
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(false);
|
||||
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchProducts(initialSearchQuery);
|
||||
}, [initialSearchQuery]);
|
||||
|
||||
Reference in New Issue
Block a user