Made everything pretty
Fix registration
This commit is contained in:
parent
963240ceb1
commit
d9c4a2caf9
@ -3,11 +3,15 @@ const jwt = require("jsonwebtoken");
|
||||
const User = require("../models/user.model");
|
||||
|
||||
exports.register = async (req, res) => {
|
||||
const { email, password, role } = req.body;
|
||||
let { username, password, name } = req.body;
|
||||
username = username.toLowerCase();
|
||||
console.log(`🆕 Registration attempt for ${name} => username:${username}, password:${password}`);
|
||||
|
||||
try {
|
||||
const hash = await bcrypt.hash(password, 10);
|
||||
const user = await User.createUser(email, hash, role);
|
||||
const user = await User.createUser(username, hash, name);
|
||||
console.log(`✅ User registered: ${username}`);
|
||||
|
||||
res.json({ message: "User registered", user });
|
||||
} catch (err) {
|
||||
res.status(400).json({ message: "Registration failed", error: err });
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
const User = require("../models/user.model");
|
||||
|
||||
exports.test = async (req, res) => {
|
||||
console.log("User route is working");
|
||||
res.json({ message: "User route is working" });
|
||||
};
|
||||
|
||||
exports.getAllUsers = async (req, res) => {
|
||||
console.log(req);
|
||||
const users = await User.getAllUsers();
|
||||
console.log(users);
|
||||
res.json(users);
|
||||
};
|
||||
|
||||
@ -39,3 +44,12 @@ exports.deleteUser = async (req, res) => {
|
||||
res.status(500).json({ error: "Failed to delete user" });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
exports.checkIfUserExists = async (req, res) => {
|
||||
const { username } = req.query;
|
||||
const users = await User.checkIfUserExists(username);
|
||||
res.json(users);
|
||||
};
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
const pool = require("../db/pool");
|
||||
|
||||
exports.ROLES = {
|
||||
VIEWER: "viewer",
|
||||
EDITOR: "editor",
|
||||
ADMIN: "admin",
|
||||
}
|
||||
|
||||
exports.findByUsername = async (username) => {
|
||||
query = `SELECT * FROM users WHERE username = ${username}`;
|
||||
const result = await pool.query("SELECT * FROM users WHERE username = $1", [username]);
|
||||
@ -7,12 +13,11 @@ exports.findByUsername = async (username) => {
|
||||
return result.rows[0];
|
||||
};
|
||||
|
||||
exports.createUser = async (username, hashedPassword, name, role = "viewer") => {
|
||||
exports.createUser = async (username, hashedPassword, name) => {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO users (username, password, name, role)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, username, role`,
|
||||
[username, hashedPassword, name, role]
|
||||
VALUES ($1, $2, $3, $4)`,
|
||||
[username, hashedPassword, name, this.ROLES.EDITOR]
|
||||
);
|
||||
return result.rows[0];
|
||||
};
|
||||
@ -45,8 +50,12 @@ exports.deleteUser = async (id) => {
|
||||
};
|
||||
|
||||
|
||||
exports.ROLES = {
|
||||
VIEWER: "viewer",
|
||||
EDITOR: "editor",
|
||||
ADMIN: "admin",
|
||||
exports.checkIfUserExists = async (username) => {
|
||||
const result = await pool.query(
|
||||
"SELECT COUNT(*) FROM users WHERE username = $1",
|
||||
[username]
|
||||
);
|
||||
return result.rows[0].count > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ const requireRole = require("../middleware/rbac");
|
||||
const usersController = require("../controllers/users.controller");
|
||||
const { ROLES } = require("../models/user.model");
|
||||
|
||||
router.get("/", auth, requireRole(ROLES.ADMIN), usersController.getAllUsers);
|
||||
router.get("/exists", usersController.checkIfUserExists);
|
||||
router.get("/test", usersController.test);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@ -5,6 +5,7 @@ import { AuthProvider } from "./context/AuthContext.jsx";
|
||||
import AdminPanel from "./pages/AdminPanel.jsx";
|
||||
import GroceryList from "./pages/GroceryList.jsx";
|
||||
import Login from "./pages/Login.jsx";
|
||||
import Register from "./pages/Register.jsx";
|
||||
|
||||
import AppLayout from "./components/AppLayout.jsx";
|
||||
import PrivateRoute from "./utils/PrivateRoute.jsx";
|
||||
@ -20,6 +21,7 @@ function App() {
|
||||
|
||||
{/* Public route */}
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
|
||||
{/* Private routes with layout */}
|
||||
<Route
|
||||
|
||||
@ -5,7 +5,7 @@ export const loginRequest = async (username, password) => {
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const registerRequest = async (data) => {
|
||||
const res = await api.post("/auth/register", data);
|
||||
export const registerRequest = async (username, password, name) => {
|
||||
const res = await api.post("/auth/register", { username, password, name });
|
||||
return res.data;
|
||||
};
|
||||
@ -3,3 +3,4 @@ import api from "./axios";
|
||||
export const getAllUsers = () => api.get("/admin/users");
|
||||
export const updateRole = (id, role) => api.put(`/admin/users/${id}/role`, { role });
|
||||
export const deleteUser = (id) => api.delete(`/admin/users/${id}`);
|
||||
export const checkIfUserExists = (username) => api.get(`/users/exists`, { params: { username: username } });
|
||||
@ -5,11 +5,14 @@ import { ROLES } from "../constants/roles";
|
||||
export default function AdminPanel() {
|
||||
const [users, setUsers] = useState([]);
|
||||
|
||||
async function loadUsers() {
|
||||
const allUsers = await getAllUsers();
|
||||
console.log(allUsers);
|
||||
setUsers(allUsers.data);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
const allUsers = await getAllUsers();
|
||||
setUsers(allUsers);
|
||||
}
|
||||
loadUsers();
|
||||
}, []);
|
||||
|
||||
const changeRole = async (id, role) => {
|
||||
@ -20,7 +23,6 @@ export default function AdminPanel() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Admin Panel</h1>
|
||||
|
||||
{users.map((user) => (
|
||||
<div key={user.id}>
|
||||
<strong>{user.username}</strong> - {user.role}
|
||||
|
||||
@ -109,7 +109,7 @@ export default function GroceryList() {
|
||||
</select>
|
||||
|
||||
{/* Add Item form (editor/admin only) */}
|
||||
{[ROLES.ADMIN, ROLES.VIEWER].includes(role) && showAddForm && (
|
||||
{[ROLES.ADMIN, ROLES.EDITOR].includes(role) && showAddForm && (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
@ -157,7 +157,7 @@ export default function GroceryList() {
|
||||
key={item.id}
|
||||
className="glist-li"
|
||||
onClick={() =>
|
||||
(role === "editor" || role === "admin") && handleBought(item.id)
|
||||
[ROLES.ADMIN, ROLES.EDITOR].includes(role) && handleBought(item.id)
|
||||
}
|
||||
>
|
||||
{item.item_name} ({item.quantity})
|
||||
@ -167,7 +167,7 @@ export default function GroceryList() {
|
||||
</div>
|
||||
|
||||
{/* Floating Button (editor/admin only) */}
|
||||
{[ROLES.ADMIN, ROLES.VIEWER].includes(role) && (
|
||||
{[ROLES.ADMIN, ROLES.EDITOR].includes(role) && (
|
||||
<button
|
||||
className="glist-fab"
|
||||
onClick={() => setShowAddForm(!showAddForm)}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useContext, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { loginRequest } from "../api/auth";
|
||||
import { AuthContext } from "../context/AuthContext";
|
||||
import "../styles/Login.css";
|
||||
|
||||
export default function Login() {
|
||||
const { login } = useContext(AuthContext);
|
||||
@ -11,6 +13,7 @@ export default function Login() {
|
||||
const submit = async (e) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
|
||||
try {
|
||||
const data = await loginRequest(username, password);
|
||||
login(data);
|
||||
@ -20,25 +23,35 @@ export default function Login() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
{error && <p style={{ color: "red" }}>{error}</p>}
|
||||
<div className="login-wrapper">
|
||||
<div className="login-box">
|
||||
<h1 className="login-title">Login</h1>
|
||||
|
||||
<form onSubmit={submit}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
{error && <p className="login-error">{error}</p>}
|
||||
|
||||
<form onSubmit={submit}>
|
||||
<input
|
||||
type="text"
|
||||
className="login-input"
|
||||
placeholder="Username"
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
className="login-input"
|
||||
placeholder="Password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
|
||||
<button type="submit" className="login-button">Login</button>
|
||||
</form>
|
||||
|
||||
<p className="login-register">
|
||||
Need an account? <Link to="/register">Register here</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { loginRequest, registerRequest } from "../api/auth";
|
||||
import { checkIfUserExists } from "../api/users";
|
||||
import { AuthContext } from "../context/AuthContext";
|
||||
|
||||
import "../styles/Register.css";
|
||||
|
||||
export default function Register() {
|
||||
const navigate = useNavigate();
|
||||
const { login } = useContext(AuthContext);
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [userExists, setUserExists] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordMatches, setPasswordMatches] = useState(true);
|
||||
const [confirm, setConfirm] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
|
||||
|
||||
useEffect(() => { checkIfUserExistsHandler(); }, [username]);
|
||||
async function checkIfUserExistsHandler() {
|
||||
setUserExists((await checkIfUserExists(username)).data);
|
||||
}
|
||||
|
||||
useEffect(() => { setError(userExists ? `Username '${username}' already taken` : ""); }, [userExists]);
|
||||
|
||||
useEffect(() => {
|
||||
setPasswordMatches(
|
||||
!password ||
|
||||
!confirm ||
|
||||
password === confirm
|
||||
);
|
||||
}, [password, confirm]);
|
||||
|
||||
useEffect(() => { setError(passwordMatches ? "" : "Passwords are not matching"); }, [passwordMatches]);
|
||||
|
||||
|
||||
|
||||
const submit = async (e) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
setSuccess("");
|
||||
|
||||
try {
|
||||
await registerRequest(username, password, name);
|
||||
console.log("Registered user:", username);
|
||||
const data = await loginRequest(username, password);
|
||||
console.log(data);
|
||||
login(data);
|
||||
setSuccess("Account created! Redirecting the grocery list...");
|
||||
setTimeout(() => navigate("/"), 2000);
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.message || "Registration failed");
|
||||
setTimeout(() => {
|
||||
setError("");
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="register-container">
|
||||
<h1>Register</h1>
|
||||
|
||||
{<p className="error-message">{error}</p>}
|
||||
{success && <p className="success-message">{success}</p>}
|
||||
|
||||
<form className="register-form" onSubmit={submit}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
onKeyUp={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Confirm Password"
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<button disabled={error !== ""} type="submit">
|
||||
Create Account
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="register-link">
|
||||
Already have an account? <Link to="/login">Login here</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
70
frontend/src/styles/Login.css
Normal file
70
frontend/src/styles/Login.css
Normal file
@ -0,0 +1,70 @@
|
||||
.login-wrapper {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 1em;
|
||||
background: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
background: white;
|
||||
padding: 1.5em;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.login-title {
|
||||
text-align: center;
|
||||
font-size: 1.6em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.login-input {
|
||||
width: 100%;
|
||||
padding: 0.6em;
|
||||
margin: 0.4em 0;
|
||||
font-size: 1em;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
padding: 0.7em;
|
||||
margin-top: 0.6em;
|
||||
background: #007bff;
|
||||
border: none;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.login-button:hover {
|
||||
background: #0068d1;
|
||||
}
|
||||
|
||||
.login-error {
|
||||
color: red;
|
||||
text-align: center;
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
|
||||
.login-register {
|
||||
text-align: center;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.login-register a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.login-register a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
84
frontend/src/styles/Register.css
Normal file
84
frontend/src/styles/Register.css
Normal file
@ -0,0 +1,84 @@
|
||||
.register-container {
|
||||
max-width: 400px;
|
||||
margin: 50px auto;
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.register-container h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.register-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.register-form input {
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.register-form input:focus {
|
||||
border-color: #0077ff;
|
||||
}
|
||||
|
||||
.register-form button {
|
||||
padding: 12px;
|
||||
border: none;
|
||||
background: #0077ff;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.register-form button:hover:not(:disabled) {
|
||||
background: #005fcc;
|
||||
}
|
||||
|
||||
.register-form button:disabled {
|
||||
background: #a8a8a8;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
height: 15px;
|
||||
color: red;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
color: green;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.register-link {
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.register-link a {
|
||||
color: #0077ff;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.register-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user