DEV Community

Ben M
Ben M

Posted on

Fullstack login page

Backend

from flask import Flask, jsonify, request
import os
from flask_cors import CORS
import mysql.connector
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
CORS(app)

conn = mysql.connector.connect(
    host = os.getenv("DB_HOST", "localhost"),
    port = int(os.getenv("DB_PORT", 3306)),
    user = os.getenv("DB_USER"),
    password = os.getenv("DB_PASSWORD"),
    database = os.getenv("DB_NAME", "task_db")
)

cursor = conn.cursor(dictionary = True)

# ---------- REGISTER ----------
@app.post("/api/register")
def register():
    try:
        data = request.json or {}

        firstName = data.get("firstName", "").strip()
        lastName = data.get("lastName", "").strip()
        email = data.get("email", "").strip()
        password = data.get("password", "").strip()
        confirmPassword = data.get("confirmPassword", "").strip()

        if not firstName:
            return jsonify({"success": False, "message": "First name is required."}), 400
        if not lastName:
            return jsonify({"success": False, "message": "Last name is required."}), 400
        if not email:
            return jsonify({"success": False, "message": "Email is required."}), 400
        if not password:
            return jsonify({"success": False, "message": "Password is required."}), 400
        if password != confirmPassword:
            return jsonify({"success": False, "message": "Passwords do not match."}), 400

        # Check duplicate email
        cursor.execute("SELECT id FROM user WHERE email = %s", (email,))
        if cursor.fetchone():
            return jsonify({"success": False, "message": "Email already registered."}), 400

        # Insert new user
        cursor.execute("""
            INSERT INTO user (role_id, firstName, lastName, email, password_hash)
            VALUES (%s, %s, %s, %s, %s)
        """, (2, firstName, lastName, email, password))

        conn.commit()

        # Get new user ID
        cursor.execute("SELECT LAST_INSERT_ID() AS id")
        new_user = cursor.fetchone()

        return jsonify({
            "success": True,
            "message": "User registered successfully!",
            "user": { "id": new_user["id"] }
        }), 200


    except Exception as e:
        print("REGISTER ERROR:", e)
        return jsonify({"success": False, "message": "Server error"}), 500



# ---------- LOGIN ----------
@app.post("/api/login")
def login():
    try:
        data = request.json or {}
        email = data.get("email", "").strip()
        password = data.get("password", "").strip()

        if not email:
            return jsonify({"success": False, "message": "Email is required."}), 400
        if not password:
            return jsonify({"success": False, "message": "Password is required."}), 400

        cursor.execute("SELECT * FROM user WHERE email = %s", (email,))
        user = cursor.fetchone()

        if not user:
            return jsonify({"success": False, "message": "Email not found."}), 400

        if user["password_hash"] != password:
            return jsonify({"success": False, "message": "Incorrect password."}), 400

        return jsonify({
            "success": True,
            "message": "Login successful.",
            "user": {
                "id": user["id"],
                "firstName": user["firstName"],
                "lastName": user["lastName"],
                "email": user["email"],
                "phone": user.get("phone"),
                "userPoints": 0   # You don't have this column yet
            }
        }), 200

    except Exception as e:
        print("LOGIN ERROR:", e)
        return jsonify({"success": False, "message": "Server error"}), 500



# ---------- GET PROFILE ----------
@app.get("/api/user/profile")
def get_profile():
    try:
        user_id = request.args.get("userId")

        if not user_id:
            return jsonify({"success": False, "message": "Missing userId"}), 400

        cursor.execute("""
            SELECT id, firstName, lastName, email, phone
            FROM user
            WHERE id = %s
        """, (user_id,))

        user = cursor.fetchone()

        if not user:
            return jsonify({"success": False, "message": "User not found"}), 404

        # Add userPoints manually (not in DB)
        user["userPoints"] = 0

        return jsonify({"success": True, "user": user}), 200

    except Exception as e:
        print("PROFILE ERROR:", e)
        return jsonify({"success": False, "message": "Server error"}), 500



# ---------- UPDATE PROFILE ----------
@app.post("/api/user/update")
def update_profile():
    try:
        data = request.json or {}
        user_id = data.get("userId")

        if not user_id:
            return jsonify({"success": False, "message": "Missing userId"}), 400

        firstName = data.get("firstName", "").strip()
        lastName = data.get("lastName", "").strip()
        email = data.get("email", "").strip()
        phone = data.get("phone", "").strip()

        cursor.execute("""
            UPDATE user
            SET firstName=%s, lastName=%s, email=%s, phone=%s
            WHERE id=%s
        """, (firstName, lastName, email, phone, user_id))

        conn.commit()

        return jsonify({"success": True, "message": "Profile updated"}), 200

    except Exception as e:
        print("UPDATE ERROR:", e)
        return jsonify({"success": False, "message": "Server error"}), 500

if __name__ == "__main__":
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode

login

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import "./login.css";

import Chicken from "../../assets/chicken.png";

export default function Login() {
    const navigate = useNavigate();
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [error, setError] = useState("");

    const handleLogin = async () => {
        try {
            const res = await fetch("/api/login", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ email, password })
            });

            const data = await res.json();

            if (!data.success) {
                setError(data.message);
                return;
            }

            // ⭐ Save userId
            localStorage.setItem("userId", data.user.id);

            // ⭐ Redirect
            navigate("/profile");

        } catch (err) {
            setError("Something went wrong.");
        }
    };

    return (
        <div className="body">
            <div className="loginContainer">
                <div className="mainLoginContainer">
                    <h1 className="loginHeader">Login</h1>

                    <div className="loginTop">
                        <input
                            className="email"
                            placeholder="Email"
                            value={email}
                            type="email"
                            onChange={(e) => setEmail(e.target.value)}
                        />

                        <input
                            className="password"
                            placeholder="Password"
                            type="password"
                            value={password}
                            onChange={(e) => setPassword(e.target.value)}
                        />
                    </div>

                    {error && <div className="errorPopup">{error}</div>}

                    <div className="loginBottom">
                        <button className="loginSubmit" onClick={handleLogin}>
                            Login
                        </button>
                    </div>
                </div>
            </div>

            <div className="imgContainer">
                <img src={Chicken} alt="chicken" className="chickenImg" />
            </div>
        </div>
    );
}

Enter fullscreen mode Exit fullscreen mode

profile

import { useState, useEffect } from "react";
import { products } from "../../data/products.js";
import "../profile/profile.css";

export default function Profile() {
  const userId = localStorage.getItem("userId");

  // 1️⃣ All hooks at the top (safe)
  const [user, setUser] = useState(null);
  const [orders, setOrders] = useState([]);

  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [email, setEmail] = useState("");
  const [phone, setPhone] = useState("");

  // 2️⃣ Fetch user
  useEffect(() => {
    if (!userId) return;

    fetch(`/api/user/profile?userId=${userId}`)
      .then(res => res.json())
      .then(data => {
        if (data.success) {
          setUser(data.user);
        }
      });

    setOrders([
      { id: 101, status: "delivered", total_price: 25.50, created_at: "2026-03-19" },
      { id: 102, status: "processing", total_price: 14.99, created_at: "2026-03-20" }
    ]);
  }, [userId]);

  // 3️⃣ Sync form fields AFTER user loads
  useEffect(() => {
    if (user) {
      setFirstName(user.firstName);
      setLastName(user.lastName);
      setEmail(user.email);
      setPhone(user.phone || "");
    }
  }, [user]);

  // 4️⃣ Safe loading state (AFTER all hooks)
  if (!user) return <p>Loading...</p>;

  // 5️⃣ Update profile
  const handleUpdateProfile = (e) => {
    e.preventDefault();

    fetch("/api/user/update", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        userId,
        firstName,
        lastName,
        email,
        phone
      })
    })
      .then(res => res.json())
      .then(data => {
        if (data.success) {
          alert("Profile updated!");
        }
      });
  };

  return (
    <div className="profilePage">

      <div className="topSection">

        <div className="welcomeBox">
          <h2>Welcome {user.firstName}!</h2>
          <p className="subtitle">Recommended products for you</p>

          <div className="carouselWrapper">
            {products.map((p) => (
              <div key={p.id} className="carouselItem">
                <img src={p.image} alt={p.name} />
                <h4>{p.name}</h4>
                <p>£{p.price}</p>
                <button>Add to basket</button>
              </div>
            ))}
          </div>
        </div>

        <div className="loyaltyBox">
          <h3>Loyalty Card</h3>
          <p className="points">{user.userPoints} Points</p>
          <p className="info">Points are added when you shop online</p>
          <p className="nextReward">Next Reward 1000 Points: 50% discount</p>

          <div className="progressWrapper">
            <div className="progressBar">
              <div
                className="progressFill"
                style={{ width: `${(user.userPoints / 1000) * 100}%` }}
              ></div>
            </div>
            <p className="progressText">
              {user.userPoints} / 1000 points
            </p>
          </div>

          <button className="continueBtn">Continue shopping</button>
        </div>

      </div>

      <div className="banner">
        <h2>Shop with our best deals</h2>
      </div>

      <div className="recentOrdersSection">
        <h3>Your Recent Orders</h3>

        {orders.length === 0 ? (
          <p>You have no recent orders.</p>
        ) : (
          <div className="ordersList">
            {orders.map((o) => (
              <div key={o.id} className="orderCard">
                <p><strong>Order #{o.id}</strong></p>
                <p>Status: {o.status}</p>
                <p>Total: £{o.total_price}</p>
                <p>Date: {o.created_at}</p>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="editProfileSection">
        <h3>Edit Profile</h3>

        <form className="editForm" onSubmit={handleUpdateProfile}>
          <input
            type="text"
            placeholder="First Name"
            value={firstName}
            onChange={(e) => setFirstName(e.target.value)}
          />

          <input
            type="text"
            placeholder="Last Name"
            value={lastName}
            onChange={(e) => setLastName(e.target.value)}
          />

          <input
            type="email"
            placeholder="Email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />

          <input
            type="text"
            placeholder="Phone"
            value={phone}
            onChange={(e) => setPhone(e.target.value)}
          />

          <button type="submit">Save Changes</button>
        </form>
      </div>

    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

sign up

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import "./signUp.css";

import Bag from "../../assets/bag.png";

export default function SignUp() {
    const navigate = useNavigate();
    const [firstName, setFirstName] = useState("");
    const [lastName, setLastName] = useState("");
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [confirmPassword, setConfirmPassword] = useState("");
    const [error, setError] = useState("");

    const handleRegister = async (e) => {
        e.preventDefault();   // ⭐ IMPORTANT FIX

        try {
            const res = await fetch("/api/register", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    firstName,
                    lastName,
                    email,
                    password,
                    confirmPassword
                }),
            });

            const data = await res.json();

            if (!data.success) {
                setError(data.message);
                return;
            }

            // Save user ID
            localStorage.setItem("userId", data.user.id);

            navigate("/profile");

        } catch (err) {
            setError("Network error. Please try again.");
        }
    };

    return (
        <div className="body">
            <div className="signUpContainer">
                <div className="mainSUContainer">
                    <h1 className="signUpHeader">Sign Up</h1>

                    <form className="signUpTop" onSubmit={handleRegister}>
                        <input
                            className="firstName"
                            placeholder="First Name"
                            value={firstName}
                            onChange={(e) => setFirstName(e.target.value)}
                        />

                        <input
                            className="lastName"
                            placeholder="Last Name"
                            value={lastName}
                            onChange={(e) => setLastName(e.target.value)}
                        />

                        <input
                            className="email"
                            placeholder="Email"
                            type="email"
                            value={email}
                            onChange={(e) => setEmail(e.target.value)}
                        />

                        <input
                            className="password"
                            placeholder="Password"
                            type="password"
                            value={password}
                            onChange={(e) => setPassword(e.target.value)}
                        />

                        <input
                            className="confirmPassword"
                            placeholder="Confirm Password"
                            type="password"
                            value={confirmPassword}
                            onChange={(e) => setConfirmPassword(e.target.value)}
                        />

                        {error && <div className="errorPopup">{error}</div>}

                        <div className="signUpBottom">
                            <button className="signUpSubmit" type="submit">
                                Sign Up
                            </button>
                        </div>
                    </form>
                </div>
            </div>

            <div className="imgContainer">
                <img src={Bag} alt="bag" className="bagImg" />
            </div>
        </div>
    );
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)