SQL CODE BUGS FIXES

Login Auth now implemented
Persistance login page
AI app.jsx formating
This commit is contained in:
Mann Patel
2025-03-12 16:13:03 -06:00
parent 864a386ba2
commit 6798a5c6a6
8 changed files with 756 additions and 319 deletions

View File

@@ -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

107
SQL_code/Schema.sql Normal file
View File

@@ -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
);

View File

@@ -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
);

View File

@@ -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)
);

View File

@@ -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: `<p>Your verification code is: <strong>${verificationCode}</strong></p><p>This code will expire in 15 minutes.</p>`,
});
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`);
});

View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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 && (
<div className="w-1/2 relative">
<img
src="../market.png"
alt="auth illustration"
<img
src="../market.png"
alt="auth illustration"
className="w-full h-full object-cover opacity-75"
/>
<div className="absolute inset-0"></div>
@@ -158,14 +177,18 @@ function App() {
)}
{/* Auth Form Section */}
<div className={`${showImage ? 'w-1/2' : 'w-full'} bg-white p-8 flex items-center justify-center`}>
<div
className={`${
showImage ? "w-1/2" : "w-full"
} bg-white p-8 flex items-center justify-center`}
>
<div className="w-full max-w-md">
<div className="mb-8 text-center">
<h2 className="text-2xl font-bold text-gray-800">
{isSignUp ? 'Create Account' : 'Welcome Back'}
{isSignUp ? "Create Account" : "Welcome Back"}
</h2>
<p className="mt-2 text-gray-600">
{isSignUp ? 'Set up your new account' : 'Sign in to your account'}
{isSignUp ? "Set up your new account" : "Sign in to your account"}
</p>
</div>
@@ -175,19 +198,21 @@ function App() {
{error}
</div>
)}
<form onSubmit={handleLogin} className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-4">
{/* Name field - only for signup */}
{isSignUp && (
<div>
<label htmlFor="name" className="block mb-1 text-sm font-medium text-gray-800">
<label
htmlFor="name"
className="block mb-1 text-sm font-medium text-gray-800"
>
Full Name
</label>
<input
type="text"
id="name"
value={formData.name}
onChange={handleInputChange}
name="name"
placeholder="Enter your name"
className="w-full px-4 py-2 border border-gray-300 bg-white text-gray-800 focus:outline-none focus:border-green-500 focus:ring-1 focus:ring-green-500"
required={isSignUp}
@@ -197,14 +222,16 @@ function App() {
{isSignUp && (
<div>
<label htmlFor="ucid" className="block mb-1 text-sm font-medium text-gray-800">
<label
htmlFor="ucid"
className="block mb-1 text-sm font-medium text-gray-800"
>
UCID
</label>
<input
type="text"
id="ucid"
value={formData.ucid}
onChange={handleInputChange}
name="ucid"
placeholder="1234567"
className="w-full px-4 py-2 border border-gray-300 bg-white text-gray-800 focus:outline-none focus:border-green-500 focus:ring-1 focus:ring-green-500"
required={isSignUp}
@@ -213,30 +240,34 @@ function App() {
)}
<div>
<label htmlFor="email" className="block mb-1 text-sm font-medium text-gray-800">
<label
htmlFor="email"
className="block mb-1 text-sm font-medium text-gray-800"
>
Email
</label>
<input
type="email"
id="email"
value={formData.email}
onChange={handleInputChange}
name="email"
placeholder="your.email@ucalgary.ca"
className="w-full px-4 py-2 border border-gray-300 bg-white text-gray-800 focus:outline-none focus:border-green-500 focus:ring-1 focus:ring-green-500"
required
/>
</div>
{isSignUp && (
<div>
<label htmlFor="phone" className="block mb-1 text-sm font-medium text-gray-800">
<label
htmlFor="phone"
className="block mb-1 text-sm font-medium text-gray-800"
>
Phone Number
</label>
<input
type="tel"
id="phone"
value={formData.phone}
onChange={handleInputChange}
name="phone"
placeholder="+1(123)456 7890"
className="w-full px-4 py-2 border border-gray-300 bg-white text-gray-800 focus:outline-none focus:border-green-500 focus:ring-1 focus:ring-green-500"
required={isSignUp}
@@ -245,48 +276,52 @@ function App() {
)}
<div>
<label htmlFor="password" className="block mb-1 text-sm font-medium text-gray-800">
<label
htmlFor="password"
className="block mb-1 text-sm font-medium text-gray-800"
>
Password
</label>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
id="password"
value={formData.password}
onChange={handleInputChange}
placeholder={isSignUp ? "Create a secure password" : "Enter your password"}
className="w-full px-4 py-2 border border-gray-300 bg-white text-gray-800 focus:outline-none focus:border-green-500 focus:ring-1 focus:ring-green-500"
required
/>
<button
type="button"
className="absolute inset-y-0 right-0 pr-3 flex items-center text-sm text-gray-500 hover:text-green-500"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? 'Hide' : 'Show'}
</button>
</div>
<input
type="password"
id="password"
name="password"
placeholder={
isSignUp
? "Create a secure password"
: "Enter your password"
}
className="w-full px-4 py-2 border border-gray-300 bg-white text-gray-800 focus:outline-none focus:border-green-500 focus:ring-1 focus:ring-green-500"
required
/>
</div>
<div className="pt-4">
<button
type="submit"
className="w-full px-6 py-2 text-base font-medium text-white bg-green-500 hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-offset-2 transition-colors"
disabled={isLoading}
className="w-full px-6 py-2 text-base font-medium text-white bg-green-500 hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-offset-2 transition-colors disabled:bg-green-300"
>
{isSignUp ? 'Create Account' : 'Sign In'}
{isLoading
? "Please wait..."
: isSignUp
? "Create Account"
: "Sign In"}
</button>
</div>
</form>
<div className="mt-6 text-center text-sm text-gray-500">
<p>
{isSignUp ? 'Already have an account?' : "Don't have an account?"}
{' '}
<button
{isSignUp
? "Already have an account?"
: "Don't have an account?"}{" "}
<button
onClick={toggleAuthMode}
type="button"
className="text-green-500 font-medium hover:text-green-700"
>
{isSignUp ? 'Sign in' : 'Sign up'}
{isSignUp ? "Sign in" : "Sign up"}
</button>
</p>
</div>
@@ -301,7 +336,7 @@ function App() {
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return children;
};
@@ -309,90 +344,91 @@ function App() {
<Router>
<div className="min-h-screen bg-gray-50">
{/* Only show navbar when authenticated */}
{isAuthenticated && <Navbar onLogout={handleLogout} userName={user?.name} />}
{isAuthenticated && (
<Navbar onLogout={handleLogout} userName={user?.name} />
)}
<Routes>
{/* Public routes */}
<Route
path="/login"
element={
isAuthenticated ?
<Navigate to="/" /> :
<LoginComponent />
}
<Route
path="/login"
element={isAuthenticated ? <Navigate to="/" /> : <LoginComponent />}
/>
{/* Protected routes */}
<Route
path="/"
<Route
path="/"
element={
<ProtectedRoute>
<div className="container mx-auto px-4 py-6">
<Home />
</div>
</ProtectedRoute>
}
}
/>
<Route
path="/product/:id"
<Route
path="/product/:id"
element={
<ProtectedRoute>
<ProductDetail />
</ProtectedRoute>
}
}
/>
<Route
path="/settings"
<Route
path="/settings"
element={
<ProtectedRoute>
<div className="container mx-auto px-4 py-6">
<Settings />
</div>
</ProtectedRoute>
}
}
/>
<Route
path="/selling"
<Route
path="/selling"
element={
<ProtectedRoute>
<div className="container mx-auto px-4 py-6">
<Selling />
</div>
</ProtectedRoute>
}
}
/>
<Route
path="/transactions"
<Route
path="/transactions"
element={
<ProtectedRoute>
<div className="container mx-auto px-4 py-6">
<Transactions />
</div>
</ProtectedRoute>
}
}
/>
<Route
path="/favorites"
<Route
path="/favorites"
element={
<ProtectedRoute>
<div className="container mx-auto px-4 py-6">
<Favorites />
</div>
</ProtectedRoute>
}
}
/>
{/* Redirect to login for any unmatched routes */}
<Route path="*" element={<Navigate to={isAuthenticated ? "/" : "/login"} />} />
<Route
path="*"
element={<Navigate to={isAuthenticated ? "/" : "/login"} />}
/>
</Routes>
</div>
</Router>
);
}
export default App;
export default App;