diff --git a/README.md b/README.md
index 47f82f1..b506b12 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,42 @@
### Some ground rules
-1. Add both node_modules from Slient and Server to your `gitignore` file
-2. Make a brach with the following naming conventionp, refix it with your name `name-some branch name`.
+1. Add both node_modules from Slient and Server to your ```gitignore``` file
+2. Make a brach with the following naming conventionp, prefix it with your name ```Your-Name Branch-Name```.
+---
-### `frontend`
-- Use React Js and vite as the node manger
+### Frontend
+1. `cd frontend` into the dir and then type command
```Bash
- npm install
+ 1. npm install #Installs the needed packages
+ 2. npm run dev #Start The Server
```
-2. **Start The Server**, `cd frontend` into the dir and then type command
+---
+
+### Backend
+1. `cd backend` into the dir and then type command
```Bash
- npm run dev
+ 1. npm install #Installs the needed packages
+ 2. npm run dev #Start The Server
```
-### `backend`
-1. Install the needed lib with the command bellow
+---
+
+### Recommendation
+1. `cd recommendation-engine` into the dir and then type command
```Bash
- npm install
+ 1. python3 server.py #Start The Server
```
-2. **Start The Server**, `cd backend` into the dir and then type command
+---
+### Recommendation system
+1. Install the dependencies
```Bash
- npm run dev
+ pip install mysql.connector
```
+
### Database
-1. To Create the DB use the command bellow
+1. MySql Version 9.2.0
+2. To Create the DataBase use the command bellow:
```Bash
- python3 ./SQL_code/init-db.py
+ 1. mysql -u root
+ 2. \. PathToYour/Schema.sql
+ 3. \. PathToYour/Init-Data.sql
```
-- MySql Version 9.2.0
diff --git a/backend/controllers/history.js b/backend/controllers/history.js
new file mode 100644
index 0000000..1c85042
--- /dev/null
+++ b/backend/controllers/history.js
@@ -0,0 +1,90 @@
+const db = require("../utils/database");
+
+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 H.UserID = ?
+ )
+ SELECT
+ ProductID,
+ ProductName,
+ Price,
+ DateUploaded,
+ SellerName,
+ ProductImage,
+ Category
+ FROM RankedImages
+ WHERE RowNum = 1;
+ `,
+ [id],
+ );
+
+ 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",
+ });
+ }
+};
+
+exports.AddHistory = async (req, res) => {
+ const { userID, productID } = req.body;
+ console.log(userID);
+ try {
+ // Use parameterized query to prevent SQL injection
+ const [result] = await db.execute(
+ `INSERT INTO History (UserID, ProductID) VALUES (?, ?)`,
+ [userID, productID],
+ );
+
+ res.json({
+ success: true,
+ message: "Product added to history successfully",
+ });
+ } catch (error) {
+ console.error("Error adding favorite product:", error);
+ return res.json({ error: "Could not add favorite product" });
+ }
+};
+
+exports.DelHistory = async (req, res) => {
+ const { userID, productID } = req.body;
+ console.log(userID);
+ try {
+ // Use parameterized query to prevent SQL injection
+ const [result] = await db.execute(`DELETE FROM History WHERE UserID=?`, [
+ userID,
+ ]);
+
+ res.json({
+ success: true,
+ message: "Product deleted from History successfully",
+ });
+ } catch (error) {
+ console.error("Error adding favorite product:", error);
+ return res.json({ error: "Could not add favorite product" });
+ }
+};
diff --git a/backend/controllers/product.js b/backend/controllers/product.js
index bd40907..569e4c6 100644
--- a/backend/controllers/product.js
+++ b/backend/controllers/product.js
@@ -1,13 +1,45 @@
const db = require("../utils/database");
-exports.addToFavorite = async (req, res) => {
- const { userID, productsID } = req.body;
+exports.addProduct = async (req, res) => {
+ const { userID, name, price, qty, description, category, images } = req.body;
+ try {
+ const [result] = await db.execute(
+ `INSERT INTO Product (Name, Price, StockQuantity, UserID, Description, CategoryID) VALUES (?, ?, ?, ?, ?, ?)`,
+ [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({
+ success: true,
+ message: "Product and images added successfully",
+ });
+ } catch (error) {
+ console.error("Error adding product or images:", error);
+ console.log(error);
+ return res.json({ error: "Could not add product or images" });
+ }
+};
+
+exports.addFavorite = async (req, res) => {
+ const { userID, productID } = req.body;
+ console.log(userID);
try {
// Use parameterized query to prevent SQL injection
const [result] = await db.execute(
- "INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)",
- [userID, productsID],
+ `INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)`,
+ [userID, productID],
);
res.json({
@@ -20,13 +52,93 @@ exports.addToFavorite = async (req, res) => {
}
};
+exports.removeFavorite = async (req, res) => {
+ const { userID, productID } = req.body;
+ console.log(userID);
+ try {
+ // Use parameterized query to prevent SQL injection
+ const [result] = await db.execute(
+ `DELETE FROM Favorites WHERE UserID = ? AND ProductID = ?`,
+ [userID, productID],
+ );
+
+ res.json({
+ success: true,
+ message: "Product removed from favorites successfully",
+ });
+ } catch (error) {
+ console.error("Error removing favorite product:", error);
+ return res.json({ error: "Could not remove favorite product" });
+ }
+};
+
+exports.getFavorites = async (req, res) => {
+ const { userID } = req.body;
+
+ try {
+ const [favorites] = await db.execute(
+ `
+ SELECT
+ p.ProductID,
+ p.Name,
+ p.Description,
+ p.Price,
+ p.CategoryID,
+ p.UserID,
+ p.Date,
+ 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],
+ );
+
+ 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 {
const [data, fields] = await db.execute(`
- SELECT p.*, i.URL
- FROM Product p
- LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
+ SELECT
+ P.ProductID,
+ P.Name AS ProductName,
+ P.Price,
+ P.Date AS DateUploaded,
+ U.Name AS SellerName,
+ 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
+GROUP BY
+ P.ProductID,
+ P.Name,
+ P.Price,
+ P.Date,
+ U.Name,
+ C.Name;
`);
res.json({
@@ -43,44 +155,58 @@ exports.getAllProducts = async (req, res) => {
}
};
-// Get a single product by ID along with image URLs
exports.getProductById = async (req, res) => {
const { id } = req.params;
- console.log(id);
+ console.log("Received Product ID:", id);
+
try {
const [data] = await db.execute(
`
- SELECT p.*, 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
WHERE p.ProductID = ?
`,
[id],
);
+ // Log raw data for debugging
+ console.log("Raw Database Result:", data);
+
if (data.length === 0) {
+ console.log("No product found with ID:", id);
return res.status(404).json({
success: false,
message: "Product not found",
});
}
- // Assuming that `data` contains product information and the image URLs
+ // Collect all image URLs
+ const images = data
+ .map((row) => row.image_url)
+ .filter((url) => url !== null);
+
+ // Create product object with all details from first row and collected images
const product = {
- ...data[0], // First product found in the query
- images: data.map((image) => image.image_url), // Collect all image URLs into an array
+ ...data[0], // Base product details
+ images: images, // Collected image URLs
};
+ // Log processed product for debugging
+ console.log("Processed Product:", product);
+
res.json({
success: true,
message: "Product fetched successfully",
data: product,
});
} catch (error) {
- console.error("Error fetching product:", error);
+ console.error("Full Error Details:", error);
return res.status(500).json({
success: false,
- error: "Database error occurred",
+ message: "Database error occurred",
+ error: error.message,
});
}
};
diff --git a/backend/controllers/recommendation.js b/backend/controllers/recommendation.js
new file mode 100644
index 0000000..63c9516
--- /dev/null
+++ b/backend/controllers/recommendation.js
@@ -0,0 +1,53 @@
+const db = require("../utils/database");
+
+// TODO: Get the recommondaed product given the userID
+exports.RecommondationByUserId = async (req, res) => {
+ const { id } = req.body;
+ 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
+ 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/review.js b/backend/controllers/review.js
new file mode 100644
index 0000000..e792409
--- /dev/null
+++ b/backend/controllers/review.js
@@ -0,0 +1,302 @@
+const db = require("../utils/database");
+
+/**
+ * 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 {
+ // First query: Get reviews for this specific product
+ const [productReviews] = 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,
+ '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 = ?`,
+ [id],
+ );
+
+ // // 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);
+
+ res.json({
+ success: true,
+ message: "Reviews fetched successfully",
+ data: combinedReviews,
+ });
+ } catch (error) {
+ console.error("Full Error Details:", error);
+ return res.status(500).json({
+ success: false,
+ message: "Database error occurred",
+ error: error.message,
+ });
+ }
+};
+
+/**
+ * Submit a new review for a product
+ */
+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 {
+ // 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 (
+ ProductID,
+ UserID,
+ Rating,
+ Comment,
+ Date
+ ) VALUES (?, ?, ?, ?, NOW())`,
+ [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
+ 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: true, // Fixed from false to 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,
+ });
+ }
+};
+
+// /**
+// * 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/controllers/search.js b/backend/controllers/search.js
new file mode 100644
index 0000000..ba6700c
--- /dev/null
+++ b/backend/controllers/search.js
@@ -0,0 +1,164 @@
+const db = require("../utils/database");
+
+exports.searchProductsByName = async (req, res) => {
+ const { name } = req.query;
+
+ if (name.length === 0) {
+ console.log("Searching for products with no name", name);
+ }
+
+ console.log("Searching for products with name:", name);
+
+ try {
+ // Modify SQL to return all products when no search term is provided
+ const sql = `
+ SELECT p.*, i.URL as image
+ FROM Product p
+ LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
+ ${name ? "WHERE p.Name LIKE ?" : ""}
+ ORDER BY p.ProductID
+ `;
+
+ const params = name ? [`%${name}%`] : [];
+ console.log("Executing SQL:", sql);
+ console.log("With parameters:", params);
+
+ const [data] = await db.execute(sql, params);
+
+ console.log("Raw Database Result:", data);
+
+ if (data.length === 0) {
+ console.log("No products found matching:", name);
+ return res.status(404).json({
+ success: false,
+ message: "No products found matching your search",
+ });
+ }
+
+ // Group products by ProductID to handle multiple images per product
+ const productsMap = new Map();
+
+ data.forEach((row) => {
+ if (!productsMap.has(row.ProductID)) {
+ const product = {
+ ProductID: row.ProductID,
+ Name: row.Name,
+ Description: row.Description,
+ Price: row.Price,
+ images: row.image,
+ };
+ productsMap.set(row.ProductID, product);
+ } else if (row.image_url) {
+ productsMap.get(row.ProductID).images.push(row.image_url);
+ }
+ });
+
+ const products = Array.from(productsMap.values());
+
+ console.log("Processed Products:", products);
+
+ res.json({
+ success: true,
+ message: "Products fetched successfully",
+ data: products,
+ count: products.length,
+ });
+ } catch (error) {
+ console.error("Database Error:", error);
+ return res.status(500).json({
+ success: false,
+ message: "Database error occurred",
+ error: error.message || "Unknown database error",
+ });
+ }
+};
+
+// exports.searchProductsByName = async (req, res) => {
+// const { name } = req.query;
+
+// // Add better validation and error handling
+// if (!name || typeof name !== "string") {
+// return res.status(400).json({
+// success: false,
+// message: "Valid search term is required",
+// });
+// }
+
+// console.log("Searching for products with name:", name);
+
+// try {
+// // Log the SQL query and parameters for debugging
+// const sql = `
+// SELECT p.*, i.URL AS image_url
+// FROM Product p
+// LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
+// WHERE p.Name LIKE ?
+// `;
+// const params = [`%${name}%`];
+// console.log("Executing SQL:", sql);
+// console.log("With parameters:", params);
+
+// const [data] = await db.execute(sql, params);
+
+// // Log raw data for debugging
+// console.log("Raw Database Result:", data);
+
+// if (data.length === 0) {
+// console.log("No products found matching:", name);
+// return res.status(404).json({
+// success: false,
+// message: "No products found matching your search",
+// });
+// }
+
+// // Group products by ProductID to handle multiple images per product
+// const productsMap = new Map();
+
+// data.forEach((row) => {
+// if (!productsMap.has(row.ProductID)) {
+// // Create a clean object without circular references
+// const product = {
+// ProductID: row.ProductID,
+// Name: row.Name,
+// Description: row.Description,
+// Price: row.Price,
+// // Add any other product fields you need
+// images: row.image_url ? [row.image_url] : [],
+// };
+// productsMap.set(row.ProductID, product);
+// } else if (row.image_url) {
+// // Add additional image to existing product
+// productsMap.get(row.ProductID).images.push(row.image_url);
+// }
+// });
+
+// // Convert map to array of products
+// const products = Array.from(productsMap.values());
+
+// // Log processed products for debugging
+// console.log("Processed Products:", products);
+
+// res.json({
+// success: true,
+// message: "Products fetched successfully",
+// data: products,
+// count: products.length,
+// });
+// } catch (error) {
+// // Enhanced error logging
+// console.error("Database Error Details:", {
+// message: error.message,
+// code: error.code,
+// errno: error.errno,
+// sqlState: error.sqlState,
+// sqlMessage: error.sqlMessage,
+// sql: error.sql,
+// });
+
+// return res.status(500).json({
+// success: false,
+// message: "Database error occurred",
+// error: error.message || "Unknown database error",
+// });
+// }
+// };
diff --git a/backend/controllers/user.js b/backend/controllers/user.js
index 27a0dc3..32aef17 100644
--- a/backend/controllers/user.js
+++ b/backend/controllers/user.js
@@ -134,6 +134,62 @@ exports.completeSignUp = async (req, res) => {
}
};
+exports.doLogin = async (req, res) => {
+ const { email, password } = req.body;
+
+ // Input validation
+ if (!email || !password) {
+ return res.status(400).json({
+ found: false,
+ error: "Email and password are required",
+ });
+ }
+
+ try {
+ // Query to find user with matching email
+ const query = "SELECT * FROM User WHERE email = ?";
+ const [data, fields] = await db.execute(query, [email]);
+
+ // Check if user was found
+ if (data && data.length > 0) {
+ const user = data[0];
+
+ // Verify password match
+ if (user.Password === password) {
+ // Consider using bcrypt for secure password comparison
+ // Return user data without password
+ return res.json({
+ found: true,
+ userID: user.UserID,
+ name: user.Name,
+ email: user.Email,
+ UCID: user.UCID,
+ phone: user.Phone,
+ address: user.Address,
+ });
+ } else {
+ // Password doesn't match
+ return res.json({
+ found: false,
+ error: "Invalid email or password",
+ });
+ }
+ } else {
+ // User not found
+ return res.json({
+ found: false,
+ error: "Invalid email or password",
+ });
+ }
+ } catch (error) {
+ console.error("Error logging in:", error);
+ return res.status(500).json({
+ found: false,
+ error: "Database error occurred",
+ });
+ }
+};
+
exports.getAllUser = async (req, res) => {
try {
const [users, fields] = await db.execute("SELECT * FROM User;");
@@ -174,6 +230,7 @@ exports.findUserByEmail = async (req, res) => {
UCID: user.UCID,
phone: user.Phone,
address: user.Address,
+ password: user.Password,
// Include any other fields your user might have
// Make sure the field names match exactly with your database column names
});
@@ -201,7 +258,7 @@ exports.updateUser = async (req, res) => {
const phone = req.body?.phone;
const UCID = req.body?.UCID;
const address = req.body?.address;
-
+ const password = req.body?.password;
if (!userId) {
return res.status(400).json({ error: "User ID is required" });
}
@@ -213,7 +270,7 @@ exports.updateUser = async (req, res) => {
if (phone) updateData.phone = phone;
if (UCID) updateData.UCID = UCID;
if (address) updateData.address = address;
-
+ if (password) updateData.password = password;
if (Object.keys(updateData).length === 0) {
return res.status(400).json({ error: "No valid fields to update" });
}
diff --git a/backend/index.js b/backend/index.js
index 01fc880..7afa3fe 100644
--- a/backend/index.js
+++ b/backend/index.js
@@ -1,10 +1,15 @@
const express = require("express");
const cors = require("cors");
-//Get the db connection
+
const db = require("./utils/database");
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,
@@ -28,15 +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/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/`);
diff --git a/backend/routes/history.js b/backend/routes/history.js
new file mode 100644
index 0000000..6713233
--- /dev/null
+++ b/backend/routes/history.js
@@ -0,0 +1,14 @@
+// routes/product.js
+const express = require("express");
+const {
+ HistoryByUserId,
+ DelHistory,
+ AddHistory,
+} = require("../controllers/history");
+const router = express.Router();
+
+router.post("/getHistory", HistoryByUserId);
+router.post("/delHistory", DelHistory);
+router.post("/addHistory", AddHistory);
+
+module.exports = router;
diff --git a/backend/routes/product.js b/backend/routes/product.js
index aeea0d9..944e63b 100644
--- a/backend/routes/product.js
+++ b/backend/routes/product.js
@@ -1,16 +1,27 @@
+// routes/product.js
const express = require("express");
const {
- addToFavorite,
+ addFavorite,
+ getFavorites,
+ removeFavorite,
getAllProducts,
getProductById,
+ addProduct,
} = require("../controllers/product");
-
const router = express.Router();
-router.post("/add_fav_product", addToFavorite);
+// Add detailed logging middleware
+router.use((req, res, next) => {
+ console.log(`Incoming ${req.method} request to ${req.path}`);
+ next();
+});
-router.get("/get_product", getAllProducts);
+router.post("/addFavorite", addFavorite);
+router.post("/getFavorites", getFavorites);
+router.post("/delFavorite", removeFavorite);
-router.post("/get_productID", getProductById);
+router.post("/addProduct", addProduct);
+router.get("/getProduct", getAllProducts);
+router.get("/:id", getProductById); // Simplified route
module.exports = router;
diff --git a/backend/routes/recommendation.js b/backend/routes/recommendation.js
new file mode 100644
index 0000000..e252a34
--- /dev/null
+++ b/backend/routes/recommendation.js
@@ -0,0 +1,8 @@
+// routes/product.js
+const express = require("express");
+const { RecommondationByUserId } = require("../controllers/recommendation");
+const router = express.Router();
+
+router.post("/recommended", RecommondationByUserId);
+
+module.exports = router;
diff --git a/backend/routes/review.js b/backend/routes/review.js
new file mode 100644
index 0000000..b39c8f6
--- /dev/null
+++ b/backend/routes/review.js
@@ -0,0 +1,9 @@
+// routes/product.js
+const express = require("express");
+const { getReviews, submitReview } = require("../controllers/review");
+const router = express.Router();
+
+router.get("/:id", getReviews);
+router.post("/addReview", submitReview);
+
+module.exports = router;
diff --git a/backend/routes/search.js b/backend/routes/search.js
new file mode 100644
index 0000000..d59f785
--- /dev/null
+++ b/backend/routes/search.js
@@ -0,0 +1,14 @@
+// routes/product.js
+const express = require("express");
+const { searchProductsByName } = require("../controllers/search");
+const router = express.Router();
+
+// Add detailed logging middleware
+router.use((req, res, next) => {
+ console.log(`Incoming ${req.method} request to ${req.path}`);
+ next();
+});
+
+router.get("/getProduct", searchProductsByName);
+
+module.exports = router;
diff --git a/backend/routes/user.js b/backend/routes/user.js
index 3d11102..1ccbc88 100644
--- a/backend/routes/user.js
+++ b/backend/routes/user.js
@@ -7,6 +7,7 @@ const {
findUserByEmail,
updateUser,
deleteUser,
+ doLogin,
} = require("../controllers/user");
const router = express.Router();
@@ -26,6 +27,9 @@ router.get("/fetch_all_users", getAllUser);
//Fetch One user Data with all fields:
router.post("/find_user", findUserByEmail);
+//Fetch One user Data with all fields:
+router.post("/do_login", doLogin);
+
//Update A uses Data:
router.post("/update", updateUser);
diff --git a/backend/utils/database.js b/backend/utils/database.js
index 0bd82e5..6e75c3a 100644
--- a/backend/utils/database.js
+++ b/backend/utils/database.js
@@ -1,11 +1,9 @@
const mysql = require("mysql2");
-//Create a pool of connections to allow multiple query happen at the same time
const pool = mysql.createPool({
host: "localhost",
user: "root",
- database: "marketplace",
+ database: "Marketplace",
});
-//Export a promise for promise-based query
module.exports = pool.promise();
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 2d81364..7b44684 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@tailwindcss/vite": "^4.0.9",
+ "axios": "^1.8.4",
"lucide-react": "^0.477.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -1770,6 +1771,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
"node_modules/autoprefixer": {
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
@@ -1824,6 +1831,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/axios": {
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
+ "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1898,7 +1916,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -1993,6 +2010,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2153,6 +2182,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@@ -2182,7 +2220,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -2283,7 +2320,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2293,7 +2329,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2331,7 +2366,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -2344,7 +2378,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -2732,6 +2765,26 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -2748,6 +2801,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -2780,7 +2848,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -2831,7 +2898,6 @@
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -2856,7 +2922,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -2931,7 +2996,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3002,7 +3066,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3015,7 +3078,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -3031,7 +3093,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -3895,12 +3956,32 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4251,6 +4332,12 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index aa89b14..8a323b6 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,6 +11,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.0.9",
+ "axios": "^1.8.4",
"lucide-react": "^0.477.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/frontend/public/Pictures/Acoustic-Guitar.jpg b/frontend/public/Pictures/Acoustic-Guitar.jpg
new file mode 100644
index 0000000..143f359
Binary files /dev/null and b/frontend/public/Pictures/Acoustic-Guitar.jpg differ
diff --git a/frontend/public/Pictures/Backpack.jpg b/frontend/public/Pictures/Backpack.jpg
new file mode 100644
index 0000000..1abff9d
Binary files /dev/null and b/frontend/public/Pictures/Backpack.jpg differ
diff --git a/frontend/public/Pictures/Basketball.jpg b/frontend/public/Pictures/Basketball.jpg
new file mode 100644
index 0000000..0d41950
Binary files /dev/null and b/frontend/public/Pictures/Basketball.jpg differ
diff --git a/frontend/public/Pictures/Bluetooth-Speaker.jpg b/frontend/public/Pictures/Bluetooth-Speaker.jpg
new file mode 100644
index 0000000..e85097e
Binary files /dev/null and b/frontend/public/Pictures/Bluetooth-Speaker.jpg differ
diff --git a/frontend/public/Pictures/CS-Textbook.jpg b/frontend/public/Pictures/CS-Textbook.jpg
new file mode 100644
index 0000000..4603981
Binary files /dev/null and b/frontend/public/Pictures/CS-Textbook.jpg differ
diff --git a/frontend/public/Pictures/Calculator.jpg b/frontend/public/Pictures/Calculator.jpg
new file mode 100644
index 0000000..710dbd9
Binary files /dev/null and b/frontend/public/Pictures/Calculator.jpg differ
diff --git a/frontend/public/Pictures/Calculus-Textbook.jpg b/frontend/public/Pictures/Calculus-Textbook.jpg
new file mode 100644
index 0000000..b4849d4
Binary files /dev/null and b/frontend/public/Pictures/Calculus-Textbook.jpg differ
diff --git a/frontend/public/Pictures/Calculus-Textbook2.jpg b/frontend/public/Pictures/Calculus-Textbook2.jpg
new file mode 100644
index 0000000..439c512
Binary files /dev/null and b/frontend/public/Pictures/Calculus-Textbook2.jpg differ
diff --git a/frontend/public/Pictures/Calculus-Textbook3.jpg b/frontend/public/Pictures/Calculus-Textbook3.jpg
new file mode 100644
index 0000000..557c095
Binary files /dev/null and b/frontend/public/Pictures/Calculus-Textbook3.jpg differ
diff --git a/frontend/public/Pictures/Controller.jpg b/frontend/public/Pictures/Controller.jpg
new file mode 100644
index 0000000..9056402
Binary files /dev/null and b/frontend/public/Pictures/Controller.jpg differ
diff --git a/frontend/public/image1.avif b/frontend/public/Pictures/Dell1.jpg
similarity index 100%
rename from frontend/public/image1.avif
rename to frontend/public/Pictures/Dell1.jpg
diff --git a/frontend/public/image2.avif b/frontend/public/Pictures/Dell2.jpg
similarity index 100%
rename from frontend/public/image2.avif
rename to frontend/public/Pictures/Dell2.jpg
diff --git a/frontend/public/image3.avif b/frontend/public/Pictures/Dell3.jpg
similarity index 100%
rename from frontend/public/image3.avif
rename to frontend/public/Pictures/Dell3.jpg
diff --git a/frontend/public/Pictures/Desk-Lamp.jpg b/frontend/public/Pictures/Desk-Lamp.jpg
new file mode 100644
index 0000000..80bcbee
Binary files /dev/null and b/frontend/public/Pictures/Desk-Lamp.jpg differ
diff --git a/frontend/public/Pictures/Dorm-Desk.jpg b/frontend/public/Pictures/Dorm-Desk.jpg
new file mode 100644
index 0000000..8838fd8
Binary files /dev/null and b/frontend/public/Pictures/Dorm-Desk.jpg differ
diff --git a/frontend/public/Pictures/HP-Calculator.jpg b/frontend/public/Pictures/HP-Calculator.jpg
new file mode 100644
index 0000000..8f1f94a
Binary files /dev/null and b/frontend/public/Pictures/HP-Calculator.jpg differ
diff --git a/frontend/public/Pictures/HP-Laptop1.jpg b/frontend/public/Pictures/HP-Laptop1.jpg
new file mode 100644
index 0000000..ab8457d
Binary files /dev/null and b/frontend/public/Pictures/HP-Laptop1.jpg differ
diff --git a/frontend/public/Pictures/HP-Laptop2.jpg b/frontend/public/Pictures/HP-Laptop2.jpg
new file mode 100644
index 0000000..b65d2ce
Binary files /dev/null and b/frontend/public/Pictures/HP-Laptop2.jpg differ
diff --git a/frontend/public/Pictures/Lab-Coat.jpg b/frontend/public/Pictures/Lab-Coat.jpg
new file mode 100644
index 0000000..5a6729b
Binary files /dev/null and b/frontend/public/Pictures/Lab-Coat.jpg differ
diff --git a/frontend/public/Pictures/Mini-Fridge.jpg b/frontend/public/Pictures/Mini-Fridge.jpg
new file mode 100644
index 0000000..ad6eedf
Binary files /dev/null and b/frontend/public/Pictures/Mini-Fridge.jpg differ
diff --git a/frontend/public/Pictures/Mountain-Bike.jpg b/frontend/public/Pictures/Mountain-Bike.jpg
new file mode 100644
index 0000000..1664e79
Binary files /dev/null and b/frontend/public/Pictures/Mountain-Bike.jpg differ
diff --git a/frontend/public/Pictures/Physics-Textbook.jpg b/frontend/public/Pictures/Physics-Textbook.jpg
new file mode 100644
index 0000000..bff3ab2
Binary files /dev/null and b/frontend/public/Pictures/Physics-Textbook.jpg differ
diff --git a/frontend/public/Pictures/University-Hoodie.jpg b/frontend/public/Pictures/University-Hoodie.jpg
new file mode 100644
index 0000000..f00b434
Binary files /dev/null and b/frontend/public/Pictures/University-Hoodie.jpg differ
diff --git a/frontend/public/Pictures/Winter-Jacket.jpg b/frontend/public/Pictures/Winter-Jacket.jpg
new file mode 100644
index 0000000..0496261
Binary files /dev/null and b/frontend/public/Pictures/Winter-Jacket.jpg differ
diff --git a/frontend/public/Pictures/Wireless-Mouse.jpg b/frontend/public/Pictures/Wireless-Mouse.jpg
new file mode 100644
index 0000000..b1d4490
Binary files /dev/null and b/frontend/public/Pictures/Wireless-Mouse.jpg differ
diff --git a/frontend/public/Pictures/Yoga-Mat.jpg b/frontend/public/Pictures/Yoga-Mat.jpg
new file mode 100644
index 0000000..83d81a4
Binary files /dev/null and b/frontend/public/Pictures/Yoga-Mat.jpg differ
diff --git a/frontend/public/icon/apple-touch-icon.png b/frontend/public/icon/apple-touch-icon.png
deleted file mode 100644
index b69ea23..0000000
Binary files a/frontend/public/icon/apple-touch-icon.png and /dev/null differ
diff --git a/frontend/public/icon/favicon.ico b/frontend/public/icon/favicon.ico
index 3c826fc..84fef13 100644
Binary files a/frontend/public/icon/favicon.ico and b/frontend/public/icon/favicon.ico differ
diff --git a/frontend/public/icon/icon-192-maskable.png b/frontend/public/icon/icon-192-maskable.png
deleted file mode 100644
index 5913259..0000000
Binary files a/frontend/public/icon/icon-192-maskable.png and /dev/null differ
diff --git a/frontend/public/icon/icon-192.png b/frontend/public/icon/icon-192.png
deleted file mode 100644
index 97f48c7..0000000
Binary files a/frontend/public/icon/icon-192.png and /dev/null differ
diff --git a/frontend/public/icon/icon-512-maskable.png b/frontend/public/icon/icon-512-maskable.png
deleted file mode 100644
index 4066f48..0000000
Binary files a/frontend/public/icon/icon-512-maskable.png and /dev/null differ
diff --git a/frontend/public/icon/icon-512.png b/frontend/public/icon/icon-512.png
index c4bd39c..77f6209 100644
Binary files a/frontend/public/icon/icon-512.png and b/frontend/public/icon/icon-512.png differ
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index f6f42cd..4b88b70 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -12,7 +12,7 @@ import Selling from "./pages/Selling";
import Transactions from "./pages/Transactions";
import Favorites from "./pages/Favorites";
import ProductDetail from "./pages/ProductDetail";
-import ItemForm from "./pages/MyListings";
+import SearchPage from "./pages/SearchPage"; // Make sure to import the SearchPage
function App() {
// Authentication state - initialize from localStorage if available
@@ -30,6 +30,8 @@ function App() {
const [error, setError] = useState("");
const [isLoading, setIsLoading] = useState(false);
+ const [userId, setUserId] = useState(null);
+
// New verification states
const [verificationStep, setVerificationStep] = useState("initial"); // 'initial', 'code-sent', 'verifying'
const [tempUserData, setTempUserData] = useState(null);
@@ -50,6 +52,10 @@ function App() {
return () => window.removeEventListener("resize", handleResize);
}, []);
+ useEffect(() => {
+ sendSessionDataToServer();
+ }, []);
+
// Send verification code
const sendVerificationCode = async (userData) => {
try {
@@ -189,6 +195,9 @@ function App() {
sessionStorage.setItem("isAuthenticated", "true");
sessionStorage.setItem("user", JSON.stringify(newUser));
+ // After successful signup, send session data to server
+ sendSessionDataToServer(); // Call it after signup
+
// Reset verification steps
setVerificationStep("initial");
setTempUserData(null);
@@ -240,7 +249,7 @@ function App() {
UCID: formValues.ucid,
phone: formValues.phone,
password: formValues.password, // This will be needed for the final signup
- address: "NOT_GIVEN",
+ address: formValues.address, // Add this line
client: 1,
admin: 0,
};
@@ -256,7 +265,7 @@ function App() {
// Make API call to localhost:3030/find_user
const response = await fetch(
- "http://localhost:3030/api/user/find_user",
+ "http://localhost:3030/api/user/do_login",
{
method: "POST",
headers: {
@@ -356,6 +365,48 @@ function App() {
setError("");
};
+ const sendSessionDataToServer = async () => {
+ try {
+ // Retrieve data from sessionStorage
+ const user = JSON.parse(sessionStorage.getItem("user"));
+ // const isAuthenticated =
+ // sessionStorage.getItem("isAuthenticated") === "true";
+
+ if (!user || !isAuthenticated) {
+ console.log("User is not authenticated");
+ return;
+ }
+
+ // Prepare the data to send
+ const requestData = {
+ userId: user.ID, // or user.ID depending on your user structure
+ email: user.email,
+ isAuthenticated,
+ };
+
+ console.log("Sending user data to the server:", requestData);
+
+ // Send data to Python server (replace with your actual server URL)
+ const response = await fetch("http://0.0.0.0:5000/api/user/session", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(requestData),
+ });
+
+ // Check the response
+ if (response.ok) {
+ const result = await response.json();
+ console.log("Server response:", result);
+ } else {
+ console.error("Failed to send session data to the server");
+ }
+ } catch (error) {
+ console.error("Error sending session data:", error);
+ }
+ };
+
// Login component
const LoginComponent = () => (
@@ -480,6 +531,25 @@ function App() {
)}
+ {isSignUp && (
+
+
+
+
+ )}
+
@@ -57,10 +70,11 @@ const Navbar = ({ onLogout, userName }) => {
{/* Favorites Button */}
+
{/* User Profile */}
diff --git a/frontend/src/components/ProductForm.jsx b/frontend/src/components/ProductForm.jsx
new file mode 100644
index 0000000..32ed01b
--- /dev/null
+++ b/frontend/src/components/ProductForm.jsx
@@ -0,0 +1,410 @@
+import React, { useState } from "react";
+import { X, ChevronLeft, Plus, Trash2, Check } from "lucide-react";
+
+const ProductForm = ({
+ editingProduct,
+ setEditingProduct,
+ onSave,
+ onCancel,
+}) => {
+ const [selectedCategory, setSelectedCategory] = useState("");
+ const storedUser = JSON.parse(sessionStorage.getItem("user"));
+
+ const categories = [
+ "Electronics",
+ "Clothing",
+ "Home & Garden",
+ "Toys & Games",
+ "Books",
+ "Sports & Outdoors",
+ "Automotive",
+ "Beauty & Personal Care",
+ "Health & Wellness",
+ "Jewelry",
+ "Art & Collectibles",
+ "Food & Beverages",
+ "Office Supplies",
+ "Pet Supplies",
+ "Music & Instruments",
+ "Other",
+ ];
+
+ // Map category names to their respective IDs
+ const categoryMapping = {
+ Electronics: 1,
+ Clothing: 2,
+ "Home & Garden": 3,
+ "Toys & Games": 4,
+ Books: 5,
+ "Sports & Outdoors": 6,
+ Automotive: 7,
+ "Beauty & Personal Care": 8,
+ "Health & Wellness": 9,
+ Jewelry: 10,
+ "Art & Collectibles": 11,
+ "Food & Beverages": 12,
+ "Office Supplies": 13,
+ "Pet Supplies": 14,
+ "Music & Instruments": 15,
+ Other: 16,
+ };
+
+ const handleSave = async () => {
+ // Check if the user has selected at least one category
+ if (!(editingProduct.categories || []).length) {
+ alert("Please select at least one category");
+ return;
+ }
+
+ try {
+ // First, upload images if there are any
+ const imagePaths = [];
+
+ // If we have files to upload, we'd handle the image upload here
+ // This is a placeholder for where you'd implement image uploads
+ // For now, we'll simulate the API expecting paths:
+ if (editingProduct.images && editingProduct.images.length > 0) {
+ // Simulating image paths for demo purposes
+ // In a real implementation, you would upload these files first
+ // and then use the returned paths
+ editingProduct.images.forEach((file, index) => {
+ const simulatedPath = `/public/uploads/${file.name}`;
+ imagePaths.push(simulatedPath);
+ });
+ }
+
+ // Get the category ID from the first selected category
+ const categoryName = (editingProduct.categories || [])[0];
+ const categoryID = categoryMapping[categoryName] || 3; // Default to 3 if not found
+
+ // Prepare payload according to API expectations
+ const payload = {
+ name: editingProduct.name || "",
+ price: parseFloat(editingProduct.price) || 0,
+ qty: 1, // Hardcoded as per your requirement
+ userID: storedUser.ID,
+ description: editingProduct.description || "",
+ category: categoryID,
+ images: imagePaths,
+ };
+
+ console.log("Sending payload:", payload);
+
+ const response = await fetch(
+ "http://localhost:3030/api/product/addProduct",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(payload),
+ },
+ );
+
+ if (!response.ok) {
+ const errorData = await response.text();
+ throw new Error(`Failed to add product: ${errorData}`);
+ }
+
+ const data = await response.json();
+ console.log("Product added:", data);
+ if (onSave) onSave(data);
+ } catch (error) {
+ console.error("Error saving product:", error);
+ alert(`Error saving product: ${error.message}`);
+ }
+ };
+
+ const addCategory = () => {
+ if (
+ selectedCategory &&
+ !(editingProduct.categories || []).includes(selectedCategory)
+ ) {
+ setEditingProduct((prev) => ({
+ ...prev,
+ categories: [...(prev.categories || []), selectedCategory],
+ }));
+ setSelectedCategory("");
+ }
+ };
+
+ const removeCategory = (categoryToRemove) => {
+ setEditingProduct((prev) => ({
+ ...prev,
+ categories: (prev.categories || []).filter(
+ (cat) => cat !== categoryToRemove,
+ ),
+ }));
+ };
+
+ const toggleSoldStatus = () => {
+ setEditingProduct((prev) => ({
+ ...prev,
+ isSold: !prev.isSold,
+ }));
+ };
+
+ return (
+
+ {/* Back Button */}
+
+
+
+ {editingProduct?.id ? "Edit Your Product" : "List a New Product"}
+
+
+
+ {/* Product Name */}
+
+
+
+ setEditingProduct({ ...editingProduct, name: e.target.value })
+ }
+ className="w-full px-3 py-2 border border-gray-300 focus:border-emerald-500 focus:outline-none"
+ />
+
+
+ {/* Price */}
+
+
+
+ setEditingProduct({
+ ...editingProduct,
+ price: e.target.value,
+ })
+ }
+ className="w-full px-3 py-2 border border-gray-300 focus:border-emerald-500 focus:outline-none"
+ />
+
+
+ {/* Sold Status */}
+
+
+
+
+ {editingProduct.isSold && (
+
+ Sold
+
+ )}
+
+
+
+ {/* Categories */}
+
+
+
+
+
+
+
+ {/* Selected Categories */}
+ {(editingProduct.categories || []).length > 0 ? (
+
+ {(editingProduct.categories || []).map((category) => (
+
+ {category}
+
+
+ ))}
+
+ ) : (
+
+ Please select at least one category
+
+ )}
+
+
+ {/* Description */}
+
+
+
+
+
+ {/* Image Upload */}
+
+
+
+
{
+ const files = Array.from(e.target.files).slice(0, 5);
+ setEditingProduct((prev) => ({
+ ...prev,
+ images: [...(prev.images || []), ...files].slice(0, 5),
+ }));
+ }}
+ className="hidden"
+ id="image-upload"
+ />
+
+
+ {/* Image previews */}
+ {(editingProduct.images || []).length > 0 && (
+
+
+
+ {editingProduct.images.length}{" "}
+ {editingProduct.images.length === 1 ? "image" : "images"}{" "}
+ selected
+
+
+
+
+ {editingProduct.images.map((img, idx) => (
+
+
})
+
+
+ ))}
+
+
+ )}
+
+
+
+ {/* Actions */}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ProductForm;
diff --git a/frontend/src/components/UserDropdown.jsx b/frontend/src/components/UserDropdown.jsx
index 76870e7..e8eed00 100644
--- a/frontend/src/components/UserDropdown.jsx
+++ b/frontend/src/components/UserDropdown.jsx
@@ -1,19 +1,19 @@
-import { useState, useRef, useEffect } from 'react';
-import { Link, useNavigate } from 'react-router-dom';
-import { User, Settings, ShoppingBag, DollarSign, LogOut } from 'lucide-react';
+import { useState, useRef, useEffect } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import { User, Settings, ShoppingBag, DollarSign, LogOut } from "lucide-react";
const UserDropdown = ({ onLogout, userName }) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);
const navigate = useNavigate();
-
+
// Use passed userName or fallback to default
- const displayName = userName || 'User';
-
+ const displayName = userName || "User";
+
const toggleDropdown = () => {
setIsOpen(!isOpen);
};
-
+
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event) => {
@@ -21,45 +21,45 @@ const UserDropdown = ({ onLogout, userName }) => {
setIsOpen(false);
}
};
-
- document.addEventListener('mousedown', handleClickOutside);
+
+ document.addEventListener("mousedown", handleClickOutside);
return () => {
- document.removeEventListener('mousedown', handleClickOutside);
+ document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
-
+
const handleLogout = () => {
// Close the dropdown
setIsOpen(false);
-
+
// Call the onLogout function from props
if (onLogout) {
onLogout();
console.log("Logout successful");
}
-
+
// Navigate to login page (this may be redundant as App.jsx should handle redirection)
- navigate('/login');
+ navigate("/login");
};
-
+
return (