diff --git a/backend/index.js b/backend/index.js index bf23680..17ee0be 100644 --- a/backend/index.js +++ b/backend/index.js @@ -2,6 +2,7 @@ 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"; @@ -33,7 +34,7 @@ const transporter = nodemailer.createTransport({ port: 465, auth: { user: "campusplug@zohomailcloud.ca", //Zoho email - pass: "NzaZ7FFKNh18", //Zoho password + pass: "e0YRrNSeJZQd", //Zoho password }, }); @@ -74,7 +75,8 @@ app.post("/send-verification", async (req, res) => { if (results.length > 0) { // Update existing record db_con.query( - "UPDATE AuthVerification SET VerificationCode = ?, Authenticated = FALSE, Date = CURRENT_TIMESTAMP WHERE Email = ?", + `UPDATE AuthVerification SET VerificationCode = ?, Authenticated = FALSE, Date = CURRENT_TIMESTAMP + WHERE Email = ?`, [verificationCode, email], async (err) => { if (err) { @@ -138,7 +140,7 @@ app.post("/verify-code", (req, res) => { // Check verification code db_con.query( - "SELECT * FROM AuthVerification WHERE Email = ? AND VerificationCode = ? AND Authenticated = FALSE AND Date > DATE_SUB(NOW(), INTERVAL 15 MINUTE)", + "SELECT * FROM AuthVerification WHERE Email = ? AND VerificationCode = ? AND Authenticated = 0 AND Date > DATE_SUB(NOW(), INTERVAL 15 MINUTE)", [email, code], (err, results) => { if (err) { @@ -179,11 +181,11 @@ app.post("/verify-code", (req, res) => { }); // Create a users and Complete user registration after verification -app.post("/complete-registration", (req, res) => { +app.post("/complete-signup", (req, res) => { const data = req.body; db_con.query( - `SELECT * FROM AuthVerification WHERE Email = ${data.email} AND Authenticated = 1`, + `SELECT * FROM AuthVerification WHERE Email = '${data.email}' AND Authenticated = 1;`, (err, results) => { if (err) { console.error("Database error:", err); @@ -196,7 +198,7 @@ app.post("/complete-registration", (req, res) => { // 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})`, + VALUES ('${data.name}', '${data.email}', '${data.UCID}', '${data.password}', '${data.phone}', '${data.address}')`, (err, result) => { if (err) { console.error("Error creating user:", err); @@ -215,7 +217,7 @@ app.post("/complete-registration", (req, res) => { // Delete verification record db_con.query( - `DELETE FROM AuthVerification WHERE Email = ${data.email}`, + `DELETE FROM AuthVerification WHERE Email = '${data.email}'`, (deleteErr) => { if (deleteErr) { console.error("Error deleting verification:", deleteErr); @@ -223,7 +225,10 @@ app.post("/complete-registration", (req, res) => { res.json({ success: true, message: "User registration completed successfully", - userId: result.insertId, + name: data.name, + email: data.email, + UCID: data.UCID, + phone: data.phone, }); }, ); @@ -243,9 +248,7 @@ function cleanupExpiredCodes() { if (err) { console.error("Error cleaning up expired codes:", err); } else { - console.log( - `Cleaned up ${result.affectedRows} expired verification codes`, - ); + console.log(`Cleaned up ${results} expired verification codes`); } }, ); @@ -253,7 +256,7 @@ function cleanupExpiredCodes() { // Set up a scheduler to run cleanup every hour setInterval(cleanupExpiredCodes, 60 * 60 * 1000); -//TODO: Fetch all users data: +//Fetch all users data: app.get("/fetch_all_users", (req, res) => { db_con.query("SELECT * FROM User;", (err, data) => { if (err) { @@ -264,7 +267,7 @@ app.get("/fetch_all_users", (req, res) => { }); }); -//TODO: Fetch One user Data: +//Fetch One user Data: app.post("/find_user", (req, res) => { const { email, password } = req.body; @@ -310,7 +313,82 @@ app.post("/find_user", (req, res) => { }); //TODO: Update A uses Data: -//TODO: Delete A uses Data: +app.post("/update", (req, res) => { + const { userId, ...updateData } = req.body; + + if (!userId) { + return res.status(400).json({ error: "User ID is required" }); + } + + // Create query dynamically based on provided fields + const updateFields = []; + const values = []; + + Object.entries(updateData).forEach(([key, value]) => { + // Only include fields that are actually in the User table + if (["Name", "Email", "Password", "Phone", "UCID"].includes(key)) { + updateFields.push(`${key} = ?`); + values.push(value); + } + }); + + if (updateFields.length === 0) { + return res.status(400).json({ error: "No valid fields to update" }); + } + + // Add userId to values array + values.push(userId); + + const query = `UPDATE User SET ${updateFields.join(", ")} WHERE UserID = ?`; + + db_con.query(query, values, (err, result) => { + if (err) { + console.error("Error updating user:", err); + return res.status(500).json({ error: "Could not update user" }); + } + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "User not found" }); + } + + res.json({ success: true, message: "User updated successfully" }); + }); +}); + +//Delete A uses Data: +app.post("/delete", (req, res) => { + const { userId } = req.body; + + if (!userId) { + return res.status(400).json({ error: "User ID is required" }); + } + + // Delete from UserRole first (assuming foreign key constraint) + db_con.query("DELETE FROM UserRole WHERE UserID = ?", [userId], (err) => { + if (err) { + console.error("Error deleting user role:", err); + return res.status(500).json({ error: "Could not delete user role" }); + } + + // Then delete from User table + db_con.query( + "DELETE FROM User WHERE UserID = ?", + [userId], + (err, result) => { + if (err) { + console.error("Error deleting user:", err); + return res.status(500).json({ error: "Could not delete user" }); + } + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "User not found" }); + } + + res.json({ success: true, message: "User deleted successfully" }); + }, + ); + }); +}); app.listen(3030, () => { console.log(`Running Backend on http://localhost:3030/`); diff --git a/frontend/index.html b/frontend/index.html index a9abed9..ce26f74 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,13 +1,17 @@ - - - - - Campus Plug - - -
- - + + + + + Campus + + +
+ + diff --git a/frontend/public/20191227_012601_0000.png b/frontend/public/20191227_012601_0000.png new file mode 100644 index 0000000..e4b0445 Binary files /dev/null and b/frontend/public/20191227_012601_0000.png differ diff --git a/frontend/public/Ucalgary.png b/frontend/public/Ucalgary.png new file mode 100644 index 0000000..f5d5fe9 Binary files /dev/null and b/frontend/public/Ucalgary.png differ diff --git a/frontend/public/icon.png b/frontend/public/icon.png deleted file mode 100644 index 31164be..0000000 Binary files a/frontend/public/icon.png and /dev/null differ diff --git a/frontend/public/icon/apple-touch-icon.png b/frontend/public/icon/apple-touch-icon.png new file mode 100644 index 0000000..b69ea23 Binary files /dev/null and b/frontend/public/icon/apple-touch-icon.png differ diff --git a/frontend/public/icon/favicon.ico b/frontend/public/icon/favicon.ico new file mode 100644 index 0000000..3c826fc Binary files /dev/null and b/frontend/public/icon/favicon.ico differ diff --git a/frontend/public/icon/icon-192-maskable.png b/frontend/public/icon/icon-192-maskable.png new file mode 100644 index 0000000..5913259 Binary files /dev/null and b/frontend/public/icon/icon-192-maskable.png differ diff --git a/frontend/public/icon/icon-192.png b/frontend/public/icon/icon-192.png new file mode 100644 index 0000000..97f48c7 Binary files /dev/null and b/frontend/public/icon/icon-192.png differ diff --git a/frontend/public/icon/icon-512-maskable.png b/frontend/public/icon/icon-512-maskable.png new file mode 100644 index 0000000..4066f48 Binary files /dev/null and b/frontend/public/icon/icon-512-maskable.png differ diff --git a/frontend/public/icon/icon-512.png b/frontend/public/icon/icon-512.png new file mode 100644 index 0000000..c4bd39c Binary files /dev/null and b/frontend/public/icon/icon-512.png differ diff --git a/frontend/public/market.jpg b/frontend/public/market.jpg new file mode 100644 index 0000000..9b7fe14 Binary files /dev/null and b/frontend/public/market.jpg differ diff --git a/frontend/public/university-of-calgary-logo.png b/frontend/public/university-of-calgary-logo.png new file mode 100644 index 0000000..875b098 Binary files /dev/null and b/frontend/public/university-of-calgary-logo.png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ede7618..e89937a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -29,6 +29,11 @@ function App() { const [error, setError] = useState(""); const [isLoading, setIsLoading] = useState(false); + // New verification states + const [verificationStep, setVerificationStep] = useState("initial"); // 'initial', 'code-sent', 'verifying' + const [tempUserData, setTempUserData] = useState(null); + const [verificationCode, setVerificationCode] = useState(""); + // Auto-hide image on smaller screens useEffect(() => { const handleResize = () => { @@ -38,17 +43,162 @@ function App() { setShowImage(true); } }; - - // Initial check handleResize(); - // Listen for window resize window.addEventListener("resize", handleResize); - // Cleanup return () => window.removeEventListener("resize", handleResize); }, []); + // Send verification code + const sendVerificationCode = async (userData) => { + try { + setIsLoading(true); + setError(""); + + console.log("Sending verification code to:", userData.email); + + // Make API call to send verification code + const response = await fetch("http://localhost:3030/send-verification", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: userData.email, + // Add any other required fields + }), + }); + + if (!response.ok) { + throw new Error("Failed to send verification code"); + } + + const result = await response.json(); + + if (result.success) { + // Store temporary user data + setTempUserData(userData); + // Move to verification step + setVerificationStep("code-sent"); + console.log("Verification code sent successfully"); + return true; + } else { + setError(result.message || "Failed to send verification code"); + return false; + } + } catch (err) { + console.error("Error sending verification code:", err); + setError("Failed to send verification code. Please try again."); + return false; + } finally { + setIsLoading(false); + } + }; + + // Verify code + const verifyCode = async (code) => { + try { + setIsLoading(true); + setError(""); + + console.log("Verifying code:", code); + + // Make API call to verify code + const response = await fetch("http://localhost:3030/verify-code", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: tempUserData.email, + code: code, + }), + }); + + if (!response.ok) { + throw new Error("Failed to verify code"); + } + + const result = await response.json(); + + if (result.success) { + console.log("Code verified successfully"); + // Proceed to complete signup + return await completeSignUp(tempUserData); + } else { + setError(result.message || "Invalid verification code"); + return false; + } + } catch (err) { + console.error("Error verifying code:", err); + setError("Failed to verify code. Please try again."); + return false; + } finally { + setIsLoading(false); + } + }; + + // Complete signup + const completeSignUp = async (userData) => { + try { + setIsLoading(true); + setError(""); + + console.log("Completing signup for:", userData.email); + console.log(userData); + + // Make API call to complete signup + const response = await fetch("http://localhost:3030/complete-signup", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }); + + if (!response.ok) { + throw new Error("Failed to complete signup"); + } + + const result = await response.json(); + + if (result.success) { + // Create user object from API response + const newUser = { + name: result.name || userData.name, + email: result.email || userData.email, + UCID: result.UCID || userData.ucid, + phone: result.phone || userData.phone, + }; + + // Set authenticated user + setUser(newUser); + setIsAuthenticated(true); + + // Save to localStorage to persist across refreshes + localStorage.setItem("isAuthenticated", "true"); + localStorage.setItem("user", JSON.stringify(newUser)); + + // Reset verification steps + setVerificationStep("initial"); + setTempUserData(null); + + console.log("Signup completed successfully"); + return true; + } else { + setError(result.message || "Failed to complete signup"); + return false; + } + } catch (err) { + console.error("Error completing signup:", err); + setError("Failed to complete signup. Please try again."); + return false; + } finally { + setIsLoading(false); + } + }; + const handleSubmit = async (e) => { e.preventDefault(); setIsLoading(true); @@ -63,31 +213,30 @@ function App() { setError("Email and password are required"); setIsLoading(false); return; + } else if (!formValues.email.endsWith("@ucalgary.ca")) { + setError("Please use your UCalgary email address (@ucalgary.ca)"); + setIsLoading(false); + return; } - try { if (isSignUp) { - // Handle Sign Up + // Handle Sign Up with verification console.log("Sign Up Form Data:", formValues); - // You could add API endpoint for registration here - // For now, we'll just simulate a successful registration + // Create a user object with the form data const newUser = { name: formValues.name, email: formValues.email, - ucid: formValues.ucid, + UCID: formValues.ucid, phone: formValues.phone, + password: formValues.password, // This will be needed for the final signup + address: "NOT_GIVEN", + client: 1, + admin: 0, }; - // Set authenticated user - setUser(newUser); - setIsAuthenticated(true); - - // Save to localStorage to persist across refreshes - localStorage.setItem("isAuthenticated", "true"); - localStorage.setItem("user", JSON.stringify(newUser)); - - console.log("New user registered:", newUser); + // Send verification code + await sendVerificationCode(newUser); } else { // Handle Login with API console.log("Login Attempt:", { @@ -144,10 +293,25 @@ function App() { } }; + // Handle code verification submission + const handleVerifySubmit = async (e) => { + e.preventDefault(); + const code = e.target.verificationCode.value; + + if (!code) { + setError("Verification code is required"); + return; + } + + await verifyCode(code); + }; + const handleLogout = () => { // Clear authentication state setIsAuthenticated(false); setUser(null); + setVerificationStep("initial"); + setTempUserData(null); // Clear localStorage localStorage.removeItem("isAuthenticated"); @@ -159,6 +323,20 @@ function App() { const toggleAuthMode = () => { setIsSignUp(!isSignUp); setError(""); // Clear errors when switching modes + setVerificationStep("initial"); // Reset verification step + }; + + // Resend verification code + const handleResendCode = async () => { + if (tempUserData) { + await sendVerificationCode(tempUserData); + } + }; + + // Back to signup form + const handleBackToSignup = () => { + setVerificationStep("initial"); + setError(""); }; // Login component @@ -168,9 +346,9 @@ function App() { {showImage && (
auth illustration
@@ -185,10 +363,18 @@ function App() {

- {isSignUp ? "Create Account" : "Welcome Back"} + {isSignUp + ? verificationStep === "initial" + ? "Create Account" + : "Verify Your Email" + : "Welcome Back"}

- {isSignUp ? "Set up your new account" : "Sign in to your account"} + {isSignUp + ? verificationStep === "initial" + ? "Set up your new account" + : "Enter the verification code sent to your email" + : "Sign in to your account"}

@@ -199,132 +385,191 @@ function App() {
)} -
- {/* Name field - only for signup */} - {isSignUp && ( + {/* Signup or Login Form */} + {(!isSignUp || (isSignUp && verificationStep === "initial")) && ( + + {/* Name field - only for signup */} + {isSignUp && ( +
+ + +
+ )} + + {isSignUp && ( +
+ + +
+ )} +
+ +
+ + {isSignUp && ( +
+ + +
+ )} + +
+ + +
+ +
+ +
+
+ )} + + {/* Verification Code Form */} + {isSignUp && verificationStep === "code-sent" && ( +
+
+ +

+ We've sent a verification code to {tempUserData?.email} +

- )} - {isSignUp && ( -
-
- )} -
- - -
- - {isSignUp && ( -
-
- )} +
+ )} -
- - + {/* Auth mode toggle */} + {verificationStep === "initial" && ( +
+

+ {isSignUp + ? "Already have an account?" + : "Don't have an account?"}{" "} + +

- -
- -
- - -
-

- {isSignUp - ? "Already have an account?" - : "Don't have an account?"}{" "} - -

-
+ )}
diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 48832f4..036a137 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -1,18 +1,18 @@ -import { useState } from 'react'; -import { Link } from 'react-router-dom'; -import UserDropdown from './UserDropdown'; -import { Search, Heart } from 'lucide-react'; +import { useState } from "react"; +import { Link } from "react-router-dom"; +import UserDropdown from "./UserDropdown"; +import { Search, Heart } from "lucide-react"; const Navbar = ({ onLogout, userName }) => { - const [searchQuery, setSearchQuery] = useState(''); - + const [searchQuery, setSearchQuery] = useState(""); + const handleSearchChange = (e) => { setSearchQuery(e.target.value); }; - + const handleSearchSubmit = (e) => { e.preventDefault(); - console.log('Searching for:', searchQuery); + console.log("Searching for:", searchQuery); // TODO: Implement search functionality }; @@ -23,10 +23,17 @@ const Navbar = ({ onLogout, userName }) => { {/* Logo */}
- Campus Plug + Campus Plug + + Campus Plug +
- + {/* Search Bar */}
@@ -44,11 +51,14 @@ const Navbar = ({ onLogout, userName }) => {
- + {/* User Navigation */}
{/* Favorites Button */} - + {/* User Profile */} @@ -60,4 +70,4 @@ const Navbar = ({ onLogout, userName }) => { ); }; -export default Navbar; \ No newline at end of file +export default Navbar; diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 75f6c80..d98a998 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -1,74 +1,86 @@ -import { useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; -import { Tag, Book, Laptop, Sofa, Utensils, Gift, Heart } from 'lucide-react'; +import { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { Tag, Book, Laptop, Sofa, Utensils, Gift, Heart } from "lucide-react"; const Home = () => { const navigate = useNavigate(); // Same categories const categories = [ - { id: 1, name: 'Textbooks', icon: }, - { id: 2, name: 'Electronics', icon: }, - { id: 3, name: 'Furniture', icon: }, - { id: 4, name: 'Kitchen', icon: }, - { id: 5, name: 'Other', icon: }, + { id: 1, name: "Textbooks", icon: }, + { id: 2, name: "Electronics", icon: }, + { id: 3, name: "Furniture", icon: }, + { id: 4, name: "Kitchen", icon: }, + { id: 5, name: "Other", icon: }, ]; - + // Same listings data const [listings, setListings] = useState([ { id: 0, - title: 'Dell XPS 16 Laptop', + title: "Dell XPS 16 Laptop", price: 850, - category: 'Electronics', - image: 'image1.avif', - condition: 'Good', - seller: 'Michael T.', - datePosted: '5d ago', + category: "Electronics", + image: "image1.avif", + condition: "Good", + seller: "Michael T.", + datePosted: "5d ago", isFavorite: true, }, ]); - + // Toggle favorite status const toggleFavorite = (id, e) => { e.preventDefault(); // Prevent navigation when clicking the heart icon setListings( listings.map((listing) => - listing.id === id ? { ...listing, isFavorite: !listing.isFavorite } : listing - ) + listing.id === id + ? { ...listing, isFavorite: !listing.isFavorite } + : listing, + ), ); }; const handleSelling = () => { - navigate('/selling'); + navigate("/selling"); + }; - } - return (
- {/* Hero Section */} -
-
-

+ {/* Hero Section with School Background */} +
+ {/* Background Image - Positioned at bottom */} +
+ University of Calgary + {/* Dark overlay for better text readability */} +
+ + {/* Content */} +
+

Buy and Sell on Campus

-

- The marketplace exclusively for university students. Find everything you need or sell what you don't. +

+ The marketplace exclusively for university students. Find everything + you need or sell what you don't.

- - -
- + {/* Categories */} -
+ {/*

Categories

{categories.map((category) => ( - ))}
-
- +
*/} + {/* Recent Listings */}
-

Recent Listings

-
+

+ Recent Listings +

+
{listings.map((listing) => ( { className="bg-white border border-gray-200 hover:shadow-md transition-shadow" >
- {listing.title} + {listing.title}
- +

{listing.title}

- ${listing.price} + + ${listing.price} +
- +
{listing.category} {listing.condition}
- +
- {listing.datePosted} - {listing.seller} + + {listing.datePosted} + + + {listing.seller} +
@@ -134,4 +162,4 @@ const Home = () => { ); }; -export default Home; \ No newline at end of file +export default Home;