SQL CODE BUGS FIXES
Login Auth now implemented Persistance login page AI app.jsx formating
This commit is contained in:
@@ -5,15 +5,15 @@
|
|||||||
## `frontend`
|
## `frontend`
|
||||||
- Use React Js
|
- Use React Js
|
||||||
- Use vite as the node manger
|
- Use vite as the node manger
|
||||||
```
|
```bash
|
||||||
npm install express cors react-router-dom lucid-react
|
npm install
|
||||||
```
|
```
|
||||||
3. To start the server, cd into the dir and then type command `npm run dev`
|
3. To start the server, cd into the dir and then type command `npm run dev`
|
||||||
|
|
||||||
## `backend`
|
## `backend`
|
||||||
1. Install the needed lib with the command bellow
|
1. Install the needed lib with the command bellow
|
||||||
``` bash
|
``` 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!
|
2. make sure in the `package.json` file type is set to module, if it's not there, add it!
|
||||||
```json
|
```json
|
||||||
@@ -22,8 +22,7 @@
|
|||||||
"type": "module"
|
"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
|
### Database
|
||||||
- Use only mySQL database
|
- Use only mySQL database
|
||||||
|
|
||||||
|
|||||||
107
SQL_code/Schema.sql
Normal file
107
SQL_code/Schema.sql
Normal 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
|
||||||
|
);
|
||||||
@@ -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
|
|
||||||
);
|
|
||||||
@@ -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)
|
|
||||||
);
|
|
||||||
293
backend/index.js
293
backend/index.js
@@ -1,10 +1,13 @@
|
|||||||
import express, { json } from "express";
|
import express, { json } from "express";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import mysql from "mysql2";
|
import mysql from "mysql2";
|
||||||
|
import nodemailer from "nodemailer";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(json());
|
app.use(express.json());
|
||||||
|
|
||||||
//TODO: Connect with the database:
|
//TODO: Connect with the database:
|
||||||
const db_con = mysql.createConnection({
|
const db_con = mysql.createConnection({
|
||||||
@@ -23,24 +26,236 @@ db_con.connect((err) => {
|
|||||||
console.log("Connected to MySQL database.");
|
console.log("Connected to MySQL database.");
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Create a users:
|
// Configure email transporter for Zoho
|
||||||
app.post("/create_users", (req, res) => {
|
const transporter = nodemailer.createTransport({
|
||||||
const data = req.body;
|
host: "smtp.zohocloud.ca",
|
||||||
db_con.query(
|
secure: true,
|
||||||
`INSERT INTO Users (Name, Email, UCID, Password, Phone, Address)
|
port: 465,
|
||||||
VALUES ('${data.name}', '${data.email}', '${data.UCID}', '${data.password}', '${data.phone}', '${data.address}');`,
|
auth: {
|
||||||
);
|
user: "campusplug@zohomailcloud.ca", //Zoho email
|
||||||
db_con.query(
|
pass: "NzaZ7FFKNh18", //Zoho password
|
||||||
`INSERT INTO UserRole (Role)
|
},
|
||||||
VALUES ('${data.role}');`,
|
|
||||||
);
|
|
||||||
console.log(data);
|
|
||||||
res.send();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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:
|
//TODO: Fetch all users data:
|
||||||
app.get("/fetch_all_users", (req, res) => {
|
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) {
|
if (err) {
|
||||||
console.error("Errors: ", err);
|
console.error("Errors: ", err);
|
||||||
return res.status(500).json({ error: "\nCould not fetch users!" });
|
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:
|
//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: Update A uses Data:
|
||||||
//TODO: Delete A uses Data:
|
//TODO: Delete A uses Data:
|
||||||
|
|
||||||
app.listen(3030, () => {
|
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`);
|
||||||
});
|
});
|
||||||
|
|||||||
140
backend/package-lock.json
generated
140
backend/package-lock.json
generated
@@ -9,11 +9,15 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"mysql2": "^3.12.0"
|
"mysql2": "^3.12.0",
|
||||||
|
"nodemailer": "^6.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
@@ -77,6 +81,12 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"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": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -170,6 +180,13 @@
|
|||||||
"node": ">= 0.10"
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
@@ -233,6 +250,15 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -521,6 +547,97 @@
|
|||||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/long": {
|
||||||
"version": "5.2.4",
|
"version": "5.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz",
|
||||||
@@ -709,6 +826,15 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -862,6 +988,18 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/send": {
|
||||||
"version": "0.19.0",
|
"version": "0.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||||
|
|||||||
@@ -5,17 +5,21 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "node index.js"
|
"dev": "node index.js"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"mysql2": "^3.12.0"
|
"mysql2": "^3.12.0",
|
||||||
|
"nodemailer": "^6.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from "react";
|
||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
import {
|
||||||
import Navbar from './components/Navbar';
|
BrowserRouter as Router,
|
||||||
import Home from './pages/Home';
|
Routes,
|
||||||
import Settings from './pages/Settings';
|
Route,
|
||||||
import Selling from './pages/Selling';
|
Navigate,
|
||||||
import Transactions from './pages/Transactions';
|
} from "react-router-dom";
|
||||||
import Favorites from './pages/Favorites';
|
import Navbar from "./components/Navbar";
|
||||||
import ProductDetail from './pages/ProductDetail';
|
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() {
|
function App() {
|
||||||
// Authentication state
|
// Authentication state - initialize from localStorage if available
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
const [isAuthenticated, setIsAuthenticated] = useState(() => {
|
||||||
const [user, setUser] = useState(null);
|
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
|
// UI state for login/signup form
|
||||||
const [isSignUp, setIsSignUp] = useState(false);
|
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 [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
|
// Auto-hide image on smaller screens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -46,100 +43,122 @@ function App() {
|
|||||||
handleResize();
|
handleResize();
|
||||||
|
|
||||||
// Listen for window resize
|
// Listen for window resize
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleSubmit = async (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) => {
|
|
||||||
e.preventDefault();
|
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
|
// Validate email and password
|
||||||
if (!formData.email || !formData.password) {
|
if (!formValues.email || !formValues.password) {
|
||||||
setError('Email and password are required');
|
setError("Email and password are required");
|
||||||
|
setIsLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
if (isSignUp) {
|
if (isSignUp) {
|
||||||
// Handle Sign Up
|
// Handle Sign Up
|
||||||
console.log('Sign Up Form Data:', formData);
|
console.log("Sign Up Form Data:", formValues);
|
||||||
|
|
||||||
// Simulate saving new user to database
|
// You could add API endpoint for registration here
|
||||||
|
// For now, we'll just simulate a successful registration
|
||||||
const newUser = {
|
const newUser = {
|
||||||
name: formData.name,
|
name: formValues.name,
|
||||||
email: formData.email,
|
email: formValues.email,
|
||||||
ucid: formData.ucid,
|
ucid: formValues.ucid,
|
||||||
phone: formData.phone
|
phone: formValues.phone,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set authenticated user
|
// Set authenticated user
|
||||||
setUser(newUser);
|
setUser(newUser);
|
||||||
setIsAuthenticated(true);
|
setIsAuthenticated(true);
|
||||||
|
|
||||||
console.log('New user registered:', newUser);
|
// Save to localStorage to persist across refreshes
|
||||||
|
localStorage.setItem("isAuthenticated", "true");
|
||||||
|
localStorage.setItem("user", JSON.stringify(newUser));
|
||||||
|
|
||||||
|
console.log("New user registered:", newUser);
|
||||||
} else {
|
} else {
|
||||||
// Handle Login
|
// Handle Login with API
|
||||||
console.log('Login Attempt:', { email: formData.email, password: formData.password });
|
console.log("Login Attempt:", {
|
||||||
|
email: formValues.email,
|
||||||
// Check against fake user database
|
password: formValues.password,
|
||||||
const foundUser = fakeUsers.find(
|
|
||||||
user => user.email === formData.email && user.password === formData.password
|
|
||||||
);
|
|
||||||
|
|
||||||
if (foundUser) {
|
|
||||||
// Set authenticated user
|
|
||||||
setUser({
|
|
||||||
name: foundUser.name,
|
|
||||||
email: foundUser.email
|
|
||||||
});
|
});
|
||||||
setIsAuthenticated(true);
|
|
||||||
console.log('Login successful for:', foundUser.name);
|
// Make API call to localhost:3030/find_user
|
||||||
} else {
|
const response = await fetch("http://localhost:3030/find_user", {
|
||||||
setError('Invalid email or password');
|
method: "POST",
|
||||||
console.log('Login failed: Invalid credentials');
|
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 = () => {
|
const handleLogout = () => {
|
||||||
|
// Clear authentication state
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
console.log('User logged out');
|
|
||||||
|
|
||||||
// Reset form data
|
// Clear localStorage
|
||||||
setFormData({
|
localStorage.removeItem("isAuthenticated");
|
||||||
name: '',
|
localStorage.removeItem("user");
|
||||||
ucid: '',
|
|
||||||
email: '',
|
console.log("User logged out");
|
||||||
phone: '',
|
|
||||||
password: '',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleAuthMode = () => {
|
const toggleAuthMode = () => {
|
||||||
setIsSignUp(!isSignUp);
|
setIsSignUp(!isSignUp);
|
||||||
setError(''); // Clear errors when switching modes
|
setError(""); // Clear errors when switching modes
|
||||||
|
|
||||||
// Reset form data when switching modes
|
|
||||||
setFormData({
|
|
||||||
name: '',
|
|
||||||
ucid: '',
|
|
||||||
email: '',
|
|
||||||
phone: '',
|
|
||||||
password: '',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Login component
|
// Login component
|
||||||
@@ -158,14 +177,18 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Auth Form Section */}
|
{/* 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="w-full max-w-md">
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<h2 className="text-2xl font-bold text-gray-800">
|
<h2 className="text-2xl font-bold text-gray-800">
|
||||||
{isSignUp ? 'Create Account' : 'Welcome Back'}
|
{isSignUp ? "Create Account" : "Welcome Back"}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-2 text-gray-600">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -176,18 +199,20 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={handleLogin} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
{/* Name field - only for signup */}
|
{/* Name field - only for signup */}
|
||||||
{isSignUp && (
|
{isSignUp && (
|
||||||
<div>
|
<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
|
Full Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
value={formData.name}
|
name="name"
|
||||||
onChange={handleInputChange}
|
|
||||||
placeholder="Enter your 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"
|
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}
|
required={isSignUp}
|
||||||
@@ -197,14 +222,16 @@ function App() {
|
|||||||
|
|
||||||
{isSignUp && (
|
{isSignUp && (
|
||||||
<div>
|
<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
|
UCID
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="ucid"
|
id="ucid"
|
||||||
value={formData.ucid}
|
name="ucid"
|
||||||
onChange={handleInputChange}
|
|
||||||
placeholder="1234567"
|
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"
|
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}
|
required={isSignUp}
|
||||||
@@ -213,14 +240,16 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<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
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
id="email"
|
id="email"
|
||||||
value={formData.email}
|
name="email"
|
||||||
onChange={handleInputChange}
|
|
||||||
placeholder="your.email@ucalgary.ca"
|
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"
|
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
|
required
|
||||||
@@ -229,14 +258,16 @@ function App() {
|
|||||||
|
|
||||||
{isSignUp && (
|
{isSignUp && (
|
||||||
<div>
|
<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
|
Phone Number
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
id="phone"
|
id="phone"
|
||||||
value={formData.phone}
|
name="phone"
|
||||||
onChange={handleInputChange}
|
|
||||||
placeholder="+1(123)456 7890"
|
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"
|
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}
|
required={isSignUp}
|
||||||
@@ -245,48 +276,52 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<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
|
Password
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
|
||||||
<input
|
<input
|
||||||
type={showPassword ? "text" : "password"}
|
type="password"
|
||||||
id="password"
|
id="password"
|
||||||
value={formData.password}
|
name="password"
|
||||||
onChange={handleInputChange}
|
placeholder={
|
||||||
placeholder={isSignUp ? "Create a secure password" : "Enter your password"}
|
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"
|
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
|
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="mt-6 text-center text-sm text-gray-500">
|
<div className="mt-6 text-center text-sm text-gray-500">
|
||||||
<p>
|
<p>
|
||||||
{isSignUp ? 'Already have an account?' : "Don't have an account?"}
|
{isSignUp
|
||||||
{' '}
|
? "Already have an account?"
|
||||||
|
: "Don't have an account?"}{" "}
|
||||||
<button
|
<button
|
||||||
onClick={toggleAuthMode}
|
onClick={toggleAuthMode}
|
||||||
|
type="button"
|
||||||
className="text-green-500 font-medium hover:text-green-700"
|
className="text-green-500 font-medium hover:text-green-700"
|
||||||
>
|
>
|
||||||
{isSignUp ? 'Sign in' : 'Sign up'}
|
{isSignUp ? "Sign in" : "Sign up"}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -309,17 +344,15 @@ function App() {
|
|||||||
<Router>
|
<Router>
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
{/* Only show navbar when authenticated */}
|
{/* Only show navbar when authenticated */}
|
||||||
{isAuthenticated && <Navbar onLogout={handleLogout} userName={user?.name} />}
|
{isAuthenticated && (
|
||||||
|
<Navbar onLogout={handleLogout} userName={user?.name} />
|
||||||
|
)}
|
||||||
|
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Public routes */}
|
{/* Public routes */}
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
path="/login"
|
||||||
element={
|
element={isAuthenticated ? <Navigate to="/" /> : <LoginComponent />}
|
||||||
isAuthenticated ?
|
|
||||||
<Navigate to="/" /> :
|
|
||||||
<LoginComponent />
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Protected routes */}
|
{/* Protected routes */}
|
||||||
@@ -388,7 +421,10 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Redirect to login for any unmatched routes */}
|
{/* Redirect to login for any unmatched routes */}
|
||||||
<Route path="*" element={<Navigate to={isAuthenticated ? "/" : "/login"} />} />
|
<Route
|
||||||
|
path="*"
|
||||||
|
element={<Navigate to={isAuthenticated ? "/" : "/login"} />}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
Reference in New Issue
Block a user