fav product from prodDetail page
This commit is contained in:
@@ -47,14 +47,29 @@ exports.getFavorites = async (req, res) => {
|
|||||||
const [favorites] = await db.execute(
|
const [favorites] = await db.execute(
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
p.*,
|
p.ProductID,
|
||||||
u.Name AS SellerName,
|
p.Name,
|
||||||
i.URL AS image_url
|
p.Description,
|
||||||
FROM Favorites f
|
p.Price,
|
||||||
JOIN Product p ON f.ProductID = p.ProductID
|
p.CategoryID,
|
||||||
JOIN User u ON p.UserID = u.UserID
|
p.UserID,
|
||||||
LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
|
p.Date,
|
||||||
WHERE f.UserID = ?
|
u.Name AS SellerName,
|
||||||
|
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],
|
[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
|
|
||||||
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
|
SELECT
|
||||||
ProductID,
|
P.ProductID,
|
||||||
ProductName,
|
P.Name AS ProductName,
|
||||||
Price,
|
P.Price,
|
||||||
DateUploaded,
|
P.Date AS DateUploaded,
|
||||||
SellerName,
|
U.Name AS SellerName,
|
||||||
ProductImage,
|
MIN(I.URL) AS ProductImage,
|
||||||
Category
|
C.Name AS Category
|
||||||
FROM RankedImages
|
FROM Product P
|
||||||
WHERE RowNum = 1;
|
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
|
||||||
|
GROUP BY
|
||||||
|
P.ProductID,
|
||||||
|
P.Name,
|
||||||
|
P.Price,
|
||||||
|
P.Date,
|
||||||
|
U.Name,
|
||||||
|
C.Name;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -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/`);
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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) => ({
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
Reference in New Issue
Block a user