diff --git a/README.md b/README.md index 795fd0c..a1816b7 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,15 @@ ## `frontend` - Use React Js - Use vite as the node manger -``` - npm install express cors react-router-dom lucid-react +```bash + npm install ``` 3. To start the server, cd into the dir and then type command `npm run dev` ## `backend` 1. Install the needed lib with the command bellow ``` bash - npm install express mysql2 dotenv cors + npm install ``` 2. make sure in the `package.json` file type is set to module, if it's not there, add it! ```json @@ -22,8 +22,7 @@ "type": "module" } ``` -3. To start the server, cd into the dir and then type command `npm run start` +3. To start the server, cd into the dir and then type command `npm run dev` ### Database - Use only mySQL database - diff --git a/SQL_code/Schema.sql b/SQL_code/Schema.sql new file mode 100644 index 0000000..9bce3a2 --- /dev/null +++ b/SQL_code/Schema.sql @@ -0,0 +1,107 @@ +-- User Entity +CREATE TABLE User ( + UserID INT AUTO_INCREMENT PRIMARY KEY, + Name VARCHAR(100) NOT NULL, + Email VARCHAR(100) UNIQUE NOT NULL, + UCID VARCHAR(20) UNIQUE NOT NULL, + Password VARCHAR(255) NOT NULL, + Phone VARCHAR(20), + Address VARCHAR(255) +); + +CREATE TABLE UserRole ( + UserID INT, + Client BOOLEAN DEFAULT True, + Admin BOOLEAN DEFAULT FALSE, + PRIMARY KEY (UserID), + FOREIGN KEY (UserID) REFERENCES Users (UserID) ON DELETE CASCADE +); + +-- Product Entity +CREATE TABLE Product ( + ProductID INT PRIMARY KEY, + Name VARCHAR(255) NOT NULL, + Price DECIMAL(10, 2) NOT NULL, + StockQuantity INT, + ImageURL VARCHAR(255), + UserID INT, + Description TEXT, + CategoryID INT NOT NULL, + FOREIGN KEY (UserID) REFERENCES User (UserID), + FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) +); + +-- Category Entity +CREATE TABLE Category (CategoryID INT PRIMARY KEY, Name VARCHAR(255)); + +-- Review Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE Review ( + ReviewID INT PRIMARY KEY, + UserID INT, + ProductID INT, + Comment TEXT, + Rating INT CHECK ( + Rating >= 1 + AND Rating <= 5 + ), + Date DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (UserID) REFERENCES User (UserID), + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) +); + +-- Transaction Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE Transaction ( + TransactionID INT PRIMARY KEY, + UserID INT, + ProductID INT, + Date DATETIME DEFAULT CURRENT_TIMESTAMP, + PaymentStatus VARCHAR(50), + FOREIGN KEY (UserID) REFERENCES User (UserID), + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) +); + +-- Recommendation Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE Recommendation ( + RecommendationID_PK INT PRIMARY KEY, + UserID INT, + RecommendedProductID INT, + FOREIGN KEY (UserID) REFERENCES User (UserID), + FOREIGN KEY (RecommendedProductID) REFERENCES Product (ProductID) +); + +-- History Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE History ( + HistoryID INT PRIMARY KEY, + UserID INT, + ProductID INT, + Date DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (UserID) REFERENCES User (UserID), + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) +); + +-- Favorites Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE Favorites ( + FavoriteID INT PRIMARY KEY, + UserID INT, + ProductID INT, + FOREIGN KEY (UserID) REFERENCES User (UserID), + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) +); + +-- Product-Category Junction Table (Many-to-Many) +CREATE TABLE Product_Category ( + ProductID INT, + CategoryID INT, + PRIMARY KEY (ProductID, CategoryID), + FOREIGN KEY (ProductID) REFERENCES Product (ProductID), + FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) +); + +-- Login Authentication table, Store the userID,and a emailed code of user who have not authenticated, +CREATE TABLE AuthVerification ( + UserID INT AUTO_INCREMENT PRIMARY KEY, + Email VARCHAR(100) UNIQUE NOT NULL, + VerificationCode VARCHAR(6) NOT NULL, + Authenticated BOOLEAN DEFAULT FALSE, + Date DATETIME DEFAULT CURRENT_TIMESTAMP +); diff --git a/SQL_code/Users.sql b/SQL_code/Users.sql deleted file mode 100644 index 9e98b13..0000000 --- a/SQL_code/Users.sql +++ /dev/null @@ -1,19 +0,0 @@ --- Create the main User table with auto-incrementing ID -CREATE TABLE Users ( - ID INT AUTO_INCREMENT PRIMARY KEY, - Name VARCHAR(100) NOT NULL, - Email VARCHAR(100) UNIQUE NOT NULL, - UCID VARCHAR(20) UNIQUE NOT NULL, - Password VARCHAR(255) NOT NULL, - Phone VARCHAR(20), - Address VARCHAR(255) -); - --- Create a separate table for the multi-valued Role attribute -CREATE TABLE UserRole ( - UserID INT, - Client BOOLEAN DEFAULT FALSE, - Admin BOOLEAN DEFAULT FALSE, - PRIMARY KEY (UserID), - FOREIGN KEY (UserID) REFERENCES Users (ID) ON DELETE CASCADE -); diff --git a/SQL_code/schemas.sql b/SQL_code/schemas.sql deleted file mode 100644 index 63891bf..0000000 --- a/SQL_code/schemas.sql +++ /dev/null @@ -1,89 +0,0 @@ --- User Entity -CREATE TABLE User ( - UserID_PK INT PRIMARY KEY, - Password VARCHAR(255), - IP VARCHAR(15), - Name VARCHAR(100), - Email VARCHAR(255), - UCID VARCHAR(20), - Phone VARCHAR(20), - Role VARCHAR(50), - Address VARCHAR(255) -); - --- Buyer, Seller, Admin are roles within User (handled by Role attribute) - --- Product Entity -CREATE TABLE Product ( - ProductID_PK INT PRIMARY KEY, - ProductID VARCHAR(50), - Price DECIMAL(10, 2), - Description TEXT, - StockQuantity INT -); - --- Category Entity -CREATE TABLE Category ( - CategoryID_PK INT PRIMARY KEY, - Name VARCHAR(100) -); - --- Review Entity (Many-to-One with User, Many-to-One with Product) -CREATE TABLE Review ( - ReviewID INT PRIMARY KEY, - UserID_FK INT, - ProductID_FK INT, - Comment TEXT, - Rating INT CHECK (Rating >= 1 AND Rating <= 5), - Date DATE, - FOREIGN KEY (UserID_FK) REFERENCES User(UserID_PK), - FOREIGN KEY (ProductID_FK) REFERENCES Product(ProductID_PK) -); - --- Transaction Entity (Many-to-One with User, Many-to-One with Product) -CREATE TABLE Transaction ( - TransactionID_PK INT PRIMARY KEY, - UserID_FK INT, - ProductID_FK INT, - Date DATE, - PaymentStatus VARCHAR(50), - FOREIGN KEY (UserID_FK) REFERENCES User(UserID_PK), - FOREIGN KEY (ProductID_FK) REFERENCES Product(ProductID_PK) -); - --- Recommendation Entity (Many-to-One with User, Many-to-One with Product) -CREATE TABLE Recommendation ( - RecommendationID_PK INT PRIMARY KEY, - UserID_FK INT, - RecommendedProductID_FK INT, - FOREIGN KEY (UserID_FK) REFERENCES User(UserID_PK), - FOREIGN KEY (RecommendedProductID_FK) REFERENCES Product(ProductID_PK) -); - --- History Entity (Many-to-One with User, Many-to-One with Product) -CREATE TABLE History ( - HistoryID INT PRIMARY KEY, - UserID_FK INT, - ProductID_FK INT, - TimeStamp DATETIME, - FOREIGN KEY (UserID_FK) REFERENCES User(UserID_PK), - FOREIGN KEY (ProductID_FK) REFERENCES Product(ProductID_PK) -); - --- Favorites Entity (Many-to-One with User, Many-to-One with Product) -CREATE TABLE Favorites ( - FavoriteID INT PRIMARY KEY, - UserID_FK INT, - ProductID_FK INT, - FOREIGN KEY (UserID_FK) REFERENCES User(UserID_PK), - FOREIGN KEY (ProductID_FK) REFERENCES Product(ProductID_PK) -); - --- Product-Category Junction Table (Many-to-Many) -CREATE TABLE Product_Category ( - ProductID_FK INT, - CategoryID_FK INT, - PRIMARY KEY (ProductID_FK, CategoryID_FK), - FOREIGN KEY (ProductID_FK) REFERENCES Product(ProductID_PK), - FOREIGN KEY (CategoryID_FK) REFERENCES Category(CategoryID_PK) -); \ No newline at end of file diff --git a/backend/index.js b/backend/index.js index e3c0bca..bf23680 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,10 +1,13 @@ import express, { json } from "express"; import cors from "cors"; import mysql from "mysql2"; +import nodemailer from "nodemailer"; +import crypto from "crypto"; +import jwt from "jsonwebtoken"; const app = express(); app.use(cors()); -app.use(json()); +app.use(express.json()); //TODO: Connect with the database: const db_con = mysql.createConnection({ @@ -23,24 +26,236 @@ db_con.connect((err) => { console.log("Connected to MySQL database."); }); -//TODO: Create a users: -app.post("/create_users", (req, res) => { - const data = req.body; - db_con.query( - `INSERT INTO Users (Name, Email, UCID, Password, Phone, Address) - VALUES ('${data.name}', '${data.email}', '${data.UCID}', '${data.password}', '${data.phone}', '${data.address}');`, - ); - db_con.query( - `INSERT INTO UserRole (Role) - VALUES ('${data.role}');`, - ); - console.log(data); - res.send(); +// Configure email transporter for Zoho +const transporter = nodemailer.createTransport({ + host: "smtp.zohocloud.ca", + secure: true, + port: 465, + auth: { + user: "campusplug@zohomailcloud.ca", //Zoho email + pass: "NzaZ7FFKNh18", //Zoho password + }, }); +// Test the email connection +transporter + .verify() + .then(() => { + console.log("Email connection successful!"); + }) + .catch((error) => { + console.error("Email connection failed:", error); + }); +// Generate and send verification code for signup +app.post("/send-verification", async (req, res) => { + const { email } = req.body; + + if (!email) { + return res.status(400).json({ error: "Email is required" }); + } + + try { + // Generate a random 6-digit code + const verificationCode = crypto.randomInt(100000, 999999).toString(); + console.log( + `Generated verification code for ${email}: ${verificationCode}`, + ); + + // Check if email already exists in verification table + db_con.query( + "SELECT * FROM AuthVerification WHERE Email = ?", + [email], + async (err, results) => { + if (err) { + console.error("Database error:", err); + return res.status(500).json({ error: "Database error" }); + } + + if (results.length > 0) { + // Update existing record + db_con.query( + "UPDATE AuthVerification SET VerificationCode = ?, Authenticated = FALSE, Date = CURRENT_TIMESTAMP WHERE Email = ?", + [verificationCode, email], + async (err) => { + if (err) { + console.error("Database error:", err); + return res.status(500).json({ error: "Database error" }); + } + + // Send email and respond + await sendVerificationEmail(email, verificationCode); + res.json({ success: true, message: "Verification code sent" }); + }, + ); + } else { + // Insert new record + db_con.query( + "INSERT INTO AuthVerification (Email, VerificationCode, Authenticated) VALUES (?, ?, FALSE)", + [email, verificationCode], + async (err) => { + if (err) { + console.error("Database error:", err); + return res.status(500).json({ error: "Database error" }); + } + + // Send email and respond + await sendVerificationEmail(email, verificationCode); + res.json({ success: true, message: "Verification code sent" }); + }, + ); + } + }, + ); + } catch (error) { + console.error("Error:", error); + res.status(500).json({ error: "Server error" }); + } +}); + +// Helper function to send email +async function sendVerificationEmail(email, verificationCode) { + // Send the email with Zoho + await transporter.sendMail({ + from: "campusplug@zohomailcloud.ca", + to: email, + subject: "Your Verification Code", + text: `Your verification code is: ${verificationCode}. This code will expire in 15 minutes.`, + html: `
Your verification code is: ${verificationCode}
This code will expire in 15 minutes.
`, + }); + + console.log(`Verification code sent to ${email}`); +} + +// Verify the code +app.post("/verify-code", (req, res) => { + const { email, code } = req.body; + + if (!email || !code) { + return res.status(400).json({ error: "Email and code are required" }); + } + + console.log(`Attempting to verify code for ${email}: ${code}`); + + // Check verification code + db_con.query( + "SELECT * FROM AuthVerification WHERE Email = ? AND VerificationCode = ? AND Authenticated = FALSE AND Date > DATE_SUB(NOW(), INTERVAL 15 MINUTE)", + [email, code], + (err, results) => { + if (err) { + console.error("Database error:", err); + return res.status(500).json({ error: "Database error" }); + } + + if (results.length === 0) { + console.log(`Invalid or expired verification code for email ${email}`); + return res + .status(400) + .json({ error: "Invalid or expired verification code" }); + } + + const userId = results[0].UserID; + + // Mark as authenticated + db_con.query( + "UPDATE AuthVerification SET Authenticated = TRUE WHERE Email = ?", + [email], + (err) => { + if (err) { + console.error("Database error:", err); + return res.status(500).json({ error: "Database error" }); + } + + console.log(`Email ${email} successfully verified`); + + res.json({ + success: true, + message: "Verification successful", + userId, + }); + }, + ); + }, + ); +}); + +// Create a users and Complete user registration after verification +app.post("/complete-registration", (req, res) => { + const data = req.body; + + db_con.query( + `SELECT * FROM AuthVerification WHERE Email = ${data.email} AND Authenticated = 1`, + (err, results) => { + if (err) { + console.error("Database error:", err); + return res.status(500).json({ error: "Database error" }); + } + if (results.length === 0) { + return res.status(400).json({ error: "Email not verified" }); + } + + // Create the user + db_con.query( + `INSERT INTO User (Name, Email, UCID, Password, Phone, Address) + VALUES (${data.name}, ${data.email}, ${data.UCID}, ${data.password}, ${data.phone}, ${data.address})`, + (err, result) => { + if (err) { + console.error("Error creating user:", err); + return res.status(500).json({ error: "Could not create user" }); + } + + // Insert role using the user's ID + db_con.query( + `INSERT INTO UserRole (UserID, Client, Admin) + VALUES (LAST_INSERT_ID(), ${data.client || true}, ${data.admin || false})`, + (roleErr) => { + if (roleErr) { + console.error("Error creating role:", roleErr); + return res.status(500).json({ error: "Could not create role" }); + } + + // Delete verification record + db_con.query( + `DELETE FROM AuthVerification WHERE Email = ${data.email}`, + (deleteErr) => { + if (deleteErr) { + console.error("Error deleting verification:", deleteErr); + } + res.json({ + success: true, + message: "User registration completed successfully", + userId: result.insertId, + }); + }, + ); + }, + ); + }, + ); + }, + ); +}); + +// Clean up expired verification codes (run this periodically) +function cleanupExpiredCodes() { + db_con.query( + "DELETE FROM AuthVerification WHERE Date < DATE_SUB(NOW(), INTERVAL 15 MINUTE) AND Authenticated = 0", + (err, result) => { + if (err) { + console.error("Error cleaning up expired codes:", err); + } else { + console.log( + `Cleaned up ${result.affectedRows} expired verification codes`, + ); + } + }, + ); +} + +// Set up a scheduler to run cleanup every hour +setInterval(cleanupExpiredCodes, 60 * 60 * 1000); //TODO: Fetch all users data: app.get("/fetch_all_users", (req, res) => { - db_con.query("SELECT * FROM Users;", (err, data) => { + db_con.query("SELECT * FROM User;", (err, data) => { if (err) { console.error("Errors: ", err); return res.status(500).json({ error: "\nCould not fetch users!" }); @@ -50,9 +265,55 @@ app.get("/fetch_all_users", (req, res) => { }); //TODO: Fetch One user Data: +app.post("/find_user", (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", + }); + } + + // Query to find user with matching email and password + const query = "SELECT * FROM User WHERE email = ? AND password = ?"; + + db_con.query(query, [email, password], (err, data) => { + if (err) { + console.error("Error finding user:", err); + return res.status(500).json({ + found: false, + error: "Database error occurred", + }); + } + + // Check if user was found + if (data && data.length > 0) { + console.log(data); + const user = data[0]; + + // Return user data without sensitive information + return res.json({ + found: true, + name: user.Name, + email: user.Email, + }); + } else { + // User not found or invalid credentials + return res.json({ + found: false, + error: "Invalid email or password", + }); + } + }); +}); + //TODO: Update A uses Data: //TODO: Delete A uses Data: app.listen(3030, () => { - console.log("Running Backend on http://localhost:3030/"); + console.log(`Running Backend on http://localhost:3030/`); + console.log(`Send verification code: POST /send-verification`); + console.log(`Verify code: POST /verify-code`); }); diff --git a/backend/package-lock.json b/backend/package-lock.json index 9dad131..c497432 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,11 +9,15 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "body-parser": "^1.20.3", "cors": "^2.8.5", + "crypto": "^1.0.1", "dotenv": "^16.4.7", "express": "^4.21.2", + "jsonwebtoken": "^9.0.2", "mysql": "^2.18.1", - "mysql2": "^3.12.0" + "mysql2": "^3.12.0", + "nodemailer": "^6.10.0" } }, "node_modules/accepts": { @@ -77,6 +81,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -170,6 +180,13 @@ "node": ">= 0.10" } }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", + "license": "ISC" + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -233,6 +250,15 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -521,6 +547,97 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/long": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", @@ -709,6 +826,15 @@ "node": ">= 0.6" } }, + "node_modules/nodemailer": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", + "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -862,6 +988,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", diff --git a/backend/package.json b/backend/package.json index 2c6ff0e..3e2f635 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,17 +5,21 @@ "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node index.js" + "dev": "node index.js" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { + "body-parser": "^1.20.3", "cors": "^2.8.5", + "crypto": "^1.0.1", "dotenv": "^16.4.7", "express": "^4.21.2", + "jsonwebtoken": "^9.0.2", "mysql": "^2.18.1", - "mysql2": "^3.12.0" + "mysql2": "^3.12.0", + "nodemailer": "^6.10.0" } } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 05a1560..ede7618 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,37 +1,34 @@ -import { useState, useEffect } from 'react'; -import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; -import Navbar from './components/Navbar'; -import Home from './pages/Home'; -import Settings from './pages/Settings'; -import Selling from './pages/Selling'; -import Transactions from './pages/Transactions'; -import Favorites from './pages/Favorites'; -import ProductDetail from './pages/ProductDetail'; +import { useState, useEffect } from "react"; +import { + BrowserRouter as Router, + Routes, + Route, + Navigate, +} from "react-router-dom"; +import Navbar from "./components/Navbar"; +import Home from "./pages/Home"; +import Settings from "./pages/Settings"; +import Selling from "./pages/Selling"; +import Transactions from "./pages/Transactions"; +import Favorites from "./pages/Favorites"; +import ProductDetail from "./pages/ProductDetail"; function App() { - // Authentication state - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [user, setUser] = useState(null); - + // Authentication state - initialize from localStorage if available + const [isAuthenticated, setIsAuthenticated] = useState(() => { + return localStorage.getItem("isAuthenticated") === "true"; + }); + const [user, setUser] = useState(() => { + const savedUser = localStorage.getItem("user"); + return savedUser ? JSON.parse(savedUser) : null; + }); + // UI state for login/signup form const [isSignUp, setIsSignUp] = useState(false); - const [formData, setFormData] = useState({ - name: '', - ucid: '', - email: '', - phone: '', - password: '', - }); - const [showPassword, setShowPassword] = useState(false); const [showImage, setShowImage] = useState(true); - const [error, setError] = useState(''); + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(false); - // Fake users database - const fakeUsers = [ - { email: 'john1@ucalgary.ca', password: 'password123', name: 'John Doe' }, - { email: 'jane@ucalgary.ca', password: 'password456', name: 'Jane Smith' } - ]; - // Auto-hide image on smaller screens useEffect(() => { const handleResize = () => { @@ -41,105 +38,127 @@ function App() { setShowImage(true); } }; - + // Initial check handleResize(); - + // Listen for window resize - window.addEventListener('resize', handleResize); - + window.addEventListener("resize", handleResize); + // Cleanup - return () => window.removeEventListener('resize', handleResize); + return () => window.removeEventListener("resize", handleResize); }, []); - const handleInputChange = (e) => { - const { id, value } = e.target; - setFormData(prev => ({ - ...prev, - [id]: value - })); - - // Clear any error when user starts typing again - if (error) setError(''); - }; - - const handleLogin = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); - + setIsLoading(true); + setError(""); + + // Get form data directly from the form + const formData = new FormData(e.target); + const formValues = Object.fromEntries(formData.entries()); + // Validate email and password - if (!formData.email || !formData.password) { - setError('Email and password are required'); + if (!formValues.email || !formValues.password) { + setError("Email and password are required"); + setIsLoading(false); return; } - - if (isSignUp) { - // Handle Sign Up - console.log('Sign Up Form Data:', formData); - - // Simulate saving new user to database - const newUser = { - name: formData.name, - email: formData.email, - ucid: formData.ucid, - phone: formData.phone - }; - - // Set authenticated user - setUser(newUser); - setIsAuthenticated(true); - - console.log('New user registered:', newUser); - } else { - // Handle Login - console.log('Login Attempt:', { email: formData.email, password: formData.password }); - - // Check against fake user database - const foundUser = fakeUsers.find( - user => user.email === formData.email && user.password === formData.password - ); - - if (foundUser) { + + try { + if (isSignUp) { + // Handle Sign Up + console.log("Sign Up Form Data:", formValues); + + // You could add API endpoint for registration here + // For now, we'll just simulate a successful registration + const newUser = { + name: formValues.name, + email: formValues.email, + ucid: formValues.ucid, + phone: formValues.phone, + }; + // Set authenticated user - setUser({ - name: foundUser.name, - email: foundUser.email - }); + setUser(newUser); setIsAuthenticated(true); - console.log('Login successful for:', foundUser.name); + + // Save to localStorage to persist across refreshes + localStorage.setItem("isAuthenticated", "true"); + localStorage.setItem("user", JSON.stringify(newUser)); + + console.log("New user registered:", newUser); } else { - setError('Invalid email or password'); - console.log('Login failed: Invalid credentials'); + // Handle Login with API + console.log("Login Attempt:", { + email: formValues.email, + password: formValues.password, + }); + + // Make API call to localhost:3030/find_user + const response = await fetch("http://localhost:3030/find_user", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: formValues.email, + password: formValues.password, + }), + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const userData = await response.json(); + + if (userData && userData.found) { + // Create user object + const userObj = { + name: userData.name || userData.email, + email: userData.email, + // Add any other user data returned from the API + }; + + // Set authenticated user with data from API + setUser(userObj); + setIsAuthenticated(true); + + // Save to localStorage to persist across refreshes + localStorage.setItem("isAuthenticated", "true"); + localStorage.setItem("user", JSON.stringify(userObj)); + + console.log("Login successful for:", userData.email); + } else { + // Show error message for invalid credentials + setError("Invalid email or password"); + console.log("Login failed: Invalid credentials"); + } } + } catch (err) { + console.error("Authentication error:", err); + setError("Authentication failed. Please try again later."); + } finally { + setIsLoading(false); } }; - + const handleLogout = () => { + // Clear authentication state setIsAuthenticated(false); setUser(null); - console.log('User logged out'); - - // Reset form data - setFormData({ - name: '', - ucid: '', - email: '', - phone: '', - password: '', - }); + + // Clear localStorage + localStorage.removeItem("isAuthenticated"); + localStorage.removeItem("user"); + + console.log("User logged out"); }; const toggleAuthMode = () => { setIsSignUp(!isSignUp); - setError(''); // Clear errors when switching modes - - // Reset form data when switching modes - setFormData({ - name: '', - ucid: '', - email: '', - phone: '', - password: '', - }); + setError(""); // Clear errors when switching modes }; // Login component @@ -148,9 +167,9 @@ function App() { {/* Image Section - Automatically hidden on mobile */} {showImage && (
@@ -158,14 +177,18 @@ function App() {
)}
{/* Auth Form Section */}
- - {isSignUp ? 'Set up your new account' : 'Sign in to your account'} + {isSignUp ? "Set up your new account" : "Sign in to your account"}
- {isSignUp ? 'Already have an account?' : "Don't have an account?"} - {' '} -