update on category
This commit is contained in:
@@ -4,7 +4,6 @@ exports.getAllCategory = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const [data, fields] = await db.execute(`SELECT * FROM Category`);
|
const [data, fields] = await db.execute(`SELECT * FROM Category`);
|
||||||
|
|
||||||
// Format as { ID: "", ID: "" }
|
|
||||||
const formattedData = {};
|
const formattedData = {};
|
||||||
data.forEach((row) => {
|
data.forEach((row) => {
|
||||||
formattedData[row.CategoryID] = row.Name;
|
formattedData[row.CategoryID] = row.Name;
|
||||||
|
|||||||
@@ -1,22 +1,34 @@
|
|||||||
const db = require("../utils/database");
|
const db = require("../utils/database");
|
||||||
|
|
||||||
exports.addProduct = async (req, res) => {
|
exports.addProduct = async (req, res) => {
|
||||||
const { userID, name, price, stockQty, Description } = req.body;
|
const { userID, name, price, qty, description, category, images } = req.body;
|
||||||
console.log(userID);
|
|
||||||
try {
|
try {
|
||||||
// Use parameterized query to prevent SQL injection
|
|
||||||
const [result] = await db.execute(
|
const [result] = await db.execute(
|
||||||
`INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)`,
|
`INSERT INTO Product (Name, Price, StockQuantity, UserID, Description, CategoryID) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||||
[userID, productID],
|
[name, price, qty, userID, description, category],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const productID = result.insertId;
|
||||||
|
if (images && images.length > 0) {
|
||||||
|
const imageInsertPromises = images.map((imagePath) =>
|
||||||
|
db.execute(`INSERT INTO Image_URL (URL, ProductID) VALUES (?, ?)`, [
|
||||||
|
imagePath,
|
||||||
|
productID,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(imageInsertPromises); //perallel
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "Product added to favorites successfully",
|
message: "Product and images added successfully",
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error adding favorite product:", error);
|
console.error("Error adding product or images:", error);
|
||||||
return res.json({ error: "Could not add favorite product" });
|
console.log(error);
|
||||||
|
return res.json({ error: "Could not add product or images" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,7 +76,7 @@ exports.myProduct = async (req, res) => {
|
|||||||
const { userID } = req.body;
|
const { userID } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [favorites] = await db.execute(
|
const [result] = await db.execute(
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
p.ProductID,
|
p.ProductID,
|
||||||
@@ -95,7 +107,7 @@ exports.myProduct = async (req, res) => {
|
|||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
favorites: favorites,
|
data: result,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error retrieving favorites:", error);
|
console.error("Error retrieving favorites:", error);
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ const Home = () => {
|
|||||||
<img
|
<img
|
||||||
src="../public/Ucalgary.png"
|
src="../public/Ucalgary.png"
|
||||||
alt="University of Calgary"
|
alt="University of Calgary"
|
||||||
className="w-full h-full object-cover object-bottom opacity-50"
|
className="w-full h-full object-cover object-bottom opacity-45"
|
||||||
/>
|
/>
|
||||||
{/* Dark overlay for better text readability */}
|
{/* Dark overlay for better text readability */}
|
||||||
</div>
|
</div>
|
||||||
@@ -217,7 +217,7 @@ const Home = () => {
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={handleSelling}
|
onClick={handleSelling}
|
||||||
className="bg-emerald-500 hover:bg-emerald-600 text-white font-medium py-2 px-6 focus:outline-none focus:ring-2 focus:ring-emerald-400 transition-colors"
|
className="bg-emerald-600 hover:bg-emerald-700 text-white font-medium py-2 px-6 focus:outline-none focus:ring-2 focus:ring-emerald-400 transition-colors"
|
||||||
>
|
>
|
||||||
Post an Item
|
Post an Item
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useParams, Link } from "react-router-dom";
|
import { useParams, Link } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
Heart,
|
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Tag,
|
Tag,
|
||||||
User,
|
User,
|
||||||
@@ -26,7 +25,6 @@ const ProductDetail = () => {
|
|||||||
reviews: null,
|
reviews: null,
|
||||||
submit: null,
|
submit: null,
|
||||||
});
|
});
|
||||||
const [isFavorite, setIsFavorite] = useState(false);
|
|
||||||
const [showContactOptions, setShowContactOptions] = useState(false);
|
const [showContactOptions, setShowContactOptions] = useState(false);
|
||||||
const [currentImage, setCurrentImage] = useState(0);
|
const [currentImage, setCurrentImage] = useState(0);
|
||||||
const [reviews, setReviews] = useState([]);
|
const [reviews, setReviews] = useState([]);
|
||||||
@@ -52,7 +50,6 @@ const ProductDetail = () => {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
setShowAlert(true);
|
setShowAlert(true);
|
||||||
}
|
}
|
||||||
console.log(`Add Product -> History: ${id}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [reviewForm, setReviewForm] = useState({
|
const [reviewForm, setReviewForm] = useState({
|
||||||
@@ -248,7 +245,7 @@ const ProductDetail = () => {
|
|||||||
if (loading.product) {
|
if (loading.product) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-screen">
|
<div className="flex justify-center items-center h-screen">
|
||||||
<div className="animate-spin h-32 w-32 border-t-2 border-green-500"></div>
|
<div className="animate-spin h-32 w-32 border-t-2 border-emerald-600"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -262,7 +259,7 @@ const ProductDetail = () => {
|
|||||||
<p className="text-gray-600">{error.product}</p>
|
<p className="text-gray-600">{error.product}</p>
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className="mt-4 inline-block bg-green-500 text-white px-4 py-2 hover:bg-green-600"
|
className="mt-4 inline-block bg-emerald-600 text-white px-4 py-2 hover:bg-emerald-700"
|
||||||
>
|
>
|
||||||
Back to Listings
|
Back to Listings
|
||||||
</Link>
|
</Link>
|
||||||
@@ -279,7 +276,7 @@ const ProductDetail = () => {
|
|||||||
<h2 className="text-2xl text-red-500 mb-4">Product Not Found</h2>
|
<h2 className="text-2xl text-red-500 mb-4">Product Not Found</h2>
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className="mt-4 inline-block bg-green-500 text-white px-4 py-2 hover:bg-green-600"
|
className="mt-4 inline-block bg-emerald-600 text-white px-4 py-2 hover:bg-emerald-700"
|
||||||
>
|
>
|
||||||
Back to Listings
|
Back to Listings
|
||||||
</Link>
|
</Link>
|
||||||
@@ -291,15 +288,15 @@ 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
|
||||||
to="/search"
|
to="/search"
|
||||||
className="flex items-center text-green-600 hover:text-green-700"
|
className="flex items-center text-emerald-700 hover:text-emerald-700"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="h-4 w-4 mr-1" />
|
<ArrowLeft className="h-4 w-4 mr-1" />
|
||||||
<span>Back</span>
|
<span>Back</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div> */}
|
||||||
{showAlert && (
|
{showAlert && (
|
||||||
<FloatingAlert
|
<FloatingAlert
|
||||||
message="Product added to favorites!"
|
message="Product added to favorites!"
|
||||||
@@ -351,7 +348,7 @@ const ProductDetail = () => {
|
|||||||
{product.images.map((image, index) => (
|
{product.images.map((image, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`bg-white border ${currentImage === index ? "border-green-500 border-2" : "border-gray-200"} min-w-[100px] cursor-pointer`}
|
className={`bg-white border ${currentImage === index ? "border-emerald-600 border-2" : "border-gray-200"} min-w-[100px] cursor-pointer`}
|
||||||
onClick={() => selectImage(index)}
|
onClick={() => selectImage(index)}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -381,13 +378,13 @@ const ProductDetail = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
toggleFavorite(product.ProductID);
|
toggleFavorite(product.ProductID);
|
||||||
}}
|
}}
|
||||||
className="top-0 p-2 rounded-bl-md bg-emerald-600 hover:bg-emerald-500 transition shadow-sm"
|
className="top-0 p-2 rounded-bl-md bg-emerald-700 hover:bg-emerald-600 transition shadow-sm"
|
||||||
>
|
>
|
||||||
<Bookmark className="text-white w-5 h-5" />
|
<Bookmark className="text-white w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-2xl font-bold text-green-600 mb-4">
|
<div className="text-2xl font-bold text-emerald-700 mb-4">
|
||||||
$
|
$
|
||||||
{typeof product.Price === "number"
|
{typeof product.Price === "number"
|
||||||
? product.Price.toFixed(2)
|
? product.Price.toFixed(2)
|
||||||
@@ -418,7 +415,7 @@ const ProductDetail = () => {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowContactOptions(!showContactOptions)}
|
onClick={() => setShowContactOptions(!showContactOptions)}
|
||||||
className="w-full bg-green-500 hover:bg-green-600 text-white font-medium py-3 px-4 mb-3"
|
className="w-full bg-emerald-700 hover:bg-emerald-700 text-white font-medium py-3 px-4 mb-3"
|
||||||
>
|
>
|
||||||
Contact Seller
|
Contact Seller
|
||||||
</button>
|
</button>
|
||||||
@@ -430,7 +427,7 @@ const ProductDetail = () => {
|
|||||||
href={`tel:${product.SellerPhone}`}
|
href={`tel:${product.SellerPhone}`}
|
||||||
className="flex items-center gap-2 p-3 hover:bg-gray-50 border-b border-gray-100"
|
className="flex items-center gap-2 p-3 hover:bg-gray-50 border-b border-gray-100"
|
||||||
>
|
>
|
||||||
<Phone className="h-5 w-5 text-green-500" />
|
<Phone className="h-5 w-5 text-emerald-600" />
|
||||||
<span>Call Seller</span>
|
<span>Call Seller</span>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
@@ -440,7 +437,7 @@ const ProductDetail = () => {
|
|||||||
href={`mailto:${product.SellerEmail}`}
|
href={`mailto:${product.SellerEmail}`}
|
||||||
className="flex items-center gap-2 p-3 hover:bg-gray-50"
|
className="flex items-center gap-2 p-3 hover:bg-gray-50"
|
||||||
>
|
>
|
||||||
<Mail className="h-5 w-5 text-green-500" />
|
<Mail className="h-5 w-5 text-emerald-600" />
|
||||||
<span>Email Seller</span>
|
<span>Email Seller</span>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
@@ -477,7 +474,7 @@ const ProductDetail = () => {
|
|||||||
<div className="bg-white border border-gray-200 p-6">
|
<div className="bg-white border border-gray-200 p-6">
|
||||||
{loading.reviews ? (
|
{loading.reviews ? (
|
||||||
<div className="flex justify-center py-8">
|
<div className="flex justify-center py-8">
|
||||||
<div className="animate-spin h-8 w-8 border-t-2 border-green-500"></div>
|
<div className="animate-spin h-8 w-8 border-t-2 border-emerald-600"></div>
|
||||||
</div>
|
</div>
|
||||||
) : error.reviews ? (
|
) : error.reviews ? (
|
||||||
<div className="text-red-500 mb-4">
|
<div className="text-red-500 mb-4">
|
||||||
@@ -524,7 +521,7 @@ const ProductDetail = () => {
|
|||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowReviewForm(true)}
|
onClick={() => setShowReviewForm(true)}
|
||||||
className="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4"
|
className="bg-emerald-600 hover:bg-emerald-700 text-white font-medium py-2 px-4"
|
||||||
>
|
>
|
||||||
Write a Review
|
Write a Review
|
||||||
</button>
|
</button>
|
||||||
@@ -582,7 +579,7 @@ const ProductDetail = () => {
|
|||||||
id="comment"
|
id="comment"
|
||||||
value={reviewForm.comment}
|
value={reviewForm.comment}
|
||||||
onChange={handleReviewInputChange}
|
onChange={handleReviewInputChange}
|
||||||
className="w-full p-3 border border-gray-300 focus:outline-none focus:border-green-500"
|
className="w-full p-3 border border-gray-300 focus:outline-none focus:border-emerald-600"
|
||||||
rows="4"
|
rows="4"
|
||||||
required
|
required
|
||||||
></textarea>
|
></textarea>
|
||||||
@@ -598,7 +595,7 @@ const ProductDetail = () => {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-4 py-2 bg-green-500 text-white hover:bg-green-600"
|
className="px-4 py-2 bg-emerald-600 text-white hover:bg-emerald-700"
|
||||||
disabled={loading.submitting}
|
disabled={loading.submitting}
|
||||||
>
|
>
|
||||||
{loading.submitting ? "Submitting..." : "Submit Review"}
|
{loading.submitting ? "Submitting..." : "Submit Review"}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import { useLocation, Link } from "react-router-dom";
|
||||||
|
|
||||||
import ProductForm from "../components/ProductForm";
|
import ProductForm from "../components/ProductForm";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
|
||||||
const Selling = () => {
|
const Selling = () => {
|
||||||
// State to store user's products
|
|
||||||
const [products, setProducts] = useState([]);
|
const [products, setProducts] = useState([]);
|
||||||
// State to control when editing form is shown
|
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
// State to store the product being edited (or empty for new product)
|
const storedUser = JSON.parse(sessionStorage.getItem("user"));
|
||||||
|
|
||||||
const [editingProduct, setEditingProduct] = useState({
|
const [editingProduct, setEditingProduct] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
price: "",
|
price: "",
|
||||||
@@ -17,33 +19,36 @@ const Selling = () => {
|
|||||||
|
|
||||||
// Simulate fetching products from API/database on component mount
|
// Simulate fetching products from API/database on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This would be replaced with a real API call
|
|
||||||
const fetchProducts = async () => {
|
const fetchProducts = async () => {
|
||||||
// Mock data
|
try {
|
||||||
const mockProducts = [
|
// Replace with your actual API endpoint
|
||||||
{
|
const response = await fetch(
|
||||||
id: "1",
|
"http://localhost:3030/api/product/myProduct",
|
||||||
name: "Vintage Camera",
|
{
|
||||||
price: "299.99",
|
method: "POST",
|
||||||
description: "A beautiful vintage film camera in excellent condition",
|
headers: {
|
||||||
categories: ["Electronics", "Art & Collectibles"],
|
"Content-Type": "application/json",
|
||||||
images: ["/public/Pictures/Dell1.jpg"],
|
},
|
||||||
},
|
body: JSON.stringify({
|
||||||
{
|
userID: storedUser.ID, // Assuming you have userId defined elsewhere in your component
|
||||||
id: "2",
|
}),
|
||||||
name: "Leather Jacket",
|
},
|
||||||
price: "149.50",
|
);
|
||||||
description: "Genuine leather jacket, worn only a few times",
|
|
||||||
categories: ["Clothing"],
|
|
||||||
images: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
setProducts(mockProducts);
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
const datajson = await response.json();
|
||||||
|
setProducts(datajson.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching products:", error);
|
||||||
|
// You might want to set an error state here
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchProducts();
|
fetchProducts();
|
||||||
}, []);
|
}, []); // Add userId to dependency array if it might change
|
||||||
|
|
||||||
// Handle creating or updating a product
|
// Handle creating or updating a product
|
||||||
const handleSaveProduct = () => {
|
const handleSaveProduct = () => {
|
||||||
@@ -138,66 +143,71 @@ const Selling = () => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{products.map((product) => (
|
{products.map((product) => (
|
||||||
<div
|
<Link
|
||||||
key={product.id}
|
key={product.ProductID}
|
||||||
className="border-2 border-gray-200 overflow-hidden hover:shadow-md transition-shadow"
|
to={`/product/${product.ProductID}`}
|
||||||
>
|
>
|
||||||
<div className="h-48 bg-gray-200 flex items-center justify-center">
|
<div
|
||||||
{product.images && product.images.length > 0 ? (
|
key={product.ProductID}
|
||||||
<img
|
className="border-2 border-gray-200 overflow-hidden hover:shadow-md transition-shadow"
|
||||||
src={product.images[0] || ""}
|
>
|
||||||
alt={product.name}
|
<div className="h-48 bg-gray-200 flex items-center justify-center">
|
||||||
className="w-full h-full object-cover"
|
{product.image_url && product.image_url.length > 0 ? (
|
||||||
/>
|
<img
|
||||||
) : (
|
src={product.image_url || ""}
|
||||||
<div className="text-gray-400">No image</div>
|
alt={product.Name}
|
||||||
)}
|
className="w-full h-full object-cover"
|
||||||
</div>
|
/>
|
||||||
|
) : (
|
||||||
<div className="p-4">
|
<div className="text-gray-400">No image</div>
|
||||||
<div className="flex justify-between items-start">
|
)}
|
||||||
<h3 className="text-lg font-semibold text-gray-800">
|
|
||||||
{product.name}
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-emerald-600 font-bold mt-1">
|
<div className="p-4">
|
||||||
${product.price}
|
<div className="flex justify-between items-start">
|
||||||
</p>
|
<h3 className="text-lg font-semibold text-gray-800">
|
||||||
|
{product.Name}
|
||||||
{product.categories && product.categories.length > 0 && (
|
</h3>
|
||||||
<div className="mt-2 flex flex-wrap gap-1">
|
|
||||||
{product.categories.map((category) => (
|
|
||||||
<span
|
|
||||||
key={category}
|
|
||||||
className="text-xs bg-gray-100 text-gray-600 px-2 py-1 "
|
|
||||||
>
|
|
||||||
{category}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<p className="text-gray-500 text-sm mt-2 line-clamp-2">
|
<p className="text-emerald-600 font-bold mt-1">
|
||||||
{product.description}
|
${product.Price}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-4 flex justify-end gap-2">
|
{product.categories && product.categories.length > 0 && (
|
||||||
<button
|
<div className="mt-2 flex flex-wrap gap-1">
|
||||||
onClick={() => handleDeleteProduct(product.id)}
|
{product.CategoryID.map((category) => (
|
||||||
className="text-red-600 hover:text-red-800"
|
<span
|
||||||
>
|
key={category}
|
||||||
Delete
|
className="text-xs bg-gray-100 text-gray-600 px-2 py-1 "
|
||||||
</button>
|
>
|
||||||
<button
|
{category}
|
||||||
onClick={() => handleEditProduct(product)}
|
</span>
|
||||||
className="text-emerald-600 hover:text-emerald-800 font-medium"
|
))}
|
||||||
>
|
</div>
|
||||||
Edit
|
)}
|
||||||
</button>
|
|
||||||
|
<p className="text-gray-500 text-sm mt-2 line-clamp-2">
|
||||||
|
{product.Description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-4 flex justify-end gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeleteProduct(product.id)}
|
||||||
|
className="text-red-600 hover:text-red-800"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleEditProduct(product.id)}
|
||||||
|
className="text-emerald-600 hover:text-emerald-800 font-medium"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user