added Login function.

Also added user table but insecure. (In the next time, going to secure the get all users.)

Also noted out the react frontend from the docker compose file - just for development purposes.

I have also created some utils to logout the user and to get all users.
This commit is contained in:
2025-07-22 16:47:44 +02:00
parent cb206c10bb
commit c35838b034
9 changed files with 236 additions and 18 deletions

View File

@@ -24,9 +24,9 @@ app.post("/api/login", async (req, res) => {
loginUser(req.body.username, req.body.password)
.then((result) => {
if (result.success) {
res.status(200).json(result);
res.status(200).json(result, { message: "Login successful" });
} else {
res.status(401).json(result);
res.status(401).json(result, { message: "Invalid credentials" });
}
})
.catch((err) => {
@@ -37,6 +37,17 @@ app.post("/api/login", async (req, res) => {
});
});
app.get("/api/getAllUsers", async (req, res) => {
getAllUsers()
.then((users) => {
res.status(200).json(users);
})
.catch((err) => {
console.error("Error fetching users:", err);
res.status(500).json({ success: false, message: "Internal server error" });
});
});
app.listen(port, () => {
console.log(`Express backend server is running at http://localhost:${port}`);
});

View File

@@ -11,6 +11,7 @@
"@tailwindcss/vite": "^4.1.11",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"js-cookie": "^3.0.5",
"lucide-react": "^0.525.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
@@ -19,6 +20,7 @@
},
"devDependencies": {
"@eslint/js": "^9.30.1",
"@types/js-cookie": "^3.0.6",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
@@ -1892,6 +1894,13 @@
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT"
},
"node_modules/@types/js-cookie": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
"integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -3192,6 +3201,15 @@
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",

View File

@@ -14,6 +14,7 @@
"@tailwindcss/vite": "^4.1.11",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"js-cookie": "^3.0.5",
"lucide-react": "^0.525.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
@@ -22,6 +23,7 @@
},
"devDependencies": {
"@eslint/js": "^9.30.1",
"@types/js-cookie": "^3.0.6",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",

View File

@@ -1,10 +1,38 @@
import "./App.css";
import Layout from "./layout/layout";
import Layout from "./layout/Layout";
import { useUsers } from "./utils/useUsers";
function App() {
const users = useUsers();
return (
<Layout>
<p>Test</p>
<table className="table-auto" style={{ tableLayout: "auto" }}>
<thead>
<tr>
<th>#</th>
<th>Username</th>
<th>First name</th>
<th>Last name</th>
<th>Email</th>
<th>Password</th>
<th>Created</th>
</tr>
</thead>
<tbody>
{users.map((user: any, idx: number) => (
<tr key={user.id}>
<td>{idx + 1}</td>
<td>{user.username}</td>
<td>{user.first_name}</td>
<td>{user.last_name}</td>
<td>{user.email}</td>
<td>{user.password}</td>
<td>{user.created}</td>
</tr>
))}
</tbody>
</table>
</Layout>
);
}

View File

@@ -1,20 +1,32 @@
import { useState } from "react";
import React from "react";
import LoginCard from "./LoginCard";
import { greeting } from "../utils/functions";
const Header: React.FC = () => {
const [loginCardVisible, setLoginCardVisible] = useState(false);
const closeLoginCard = () => {
setLoginCardVisible(false);
};
let loginBtnVal: string = greeting();
return (
<header className="bg-blue-600 text-white p-4 shadow-md">
<div className="container mx-auto flex justify-between items-center">
<h1 className="text-xl font-bold">Bikelane Web</h1>
<h1 className="text-xl font-bold">Bikelane <strong>Admin Panel</strong></h1>
<nav>
<ul className="flex space-x-4">
<li>
<a href="" className="hover:underline">
Login
<a className="hover:underline">
<button onClick={() => setLoginCardVisible(true)}>{loginBtnVal ?? "Login"}</button>
</a>
</li>
</ul>
</nav>
</div>
<div>{loginCardVisible && <LoginCard onClose={closeLoginCard} />}</div>
</header>
);
};

View File

@@ -0,0 +1,108 @@
import React from "react";
import Cookies from "js-cookie";
import { logout } from "../utils/functions";
type LoginCardProps = {
onClose: () => void;
};
const LoginCard: React.FC<LoginCardProps> = ({ onClose }) => {
return (
<div className="fixed inset-0 flex items-center justify-center bg-black/35">
<div className="max-w-sm bg-white rounded-xl shadow-md p-8 relative">
<button
className="absolute top-4 right-4 bg-red-500 text-white w-8 h-8 rounded-full flex items-center justify-center font-bold shadow hover:bg-red-600 transition focus:outline-none focus:ring-2 focus:ring-red-400"
onClick={onClose}
aria-label="Close"
>
&times;
</button>
<h2 className="text-black text-2xl font-bold mb-6 text-center">
Login
</h2>
<form
onSubmit={async (event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const username = formData.get("username");
const password = formData.get("password");
// Example: send login request
await fetch("http://localhost:5002/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
})
.then(async (response) => {
if (response.ok) {
const data = await response.json();
onClose();
Cookies.set("name", data.user.first_name, { expires: 7 });
await fetch("http://localhost:5002/api/getAllUsers")
.then((res) => res.json())
.then((users) => {
localStorage.setItem("users", JSON.stringify(users));
});
} else if (response.status === 401) {
alert("Invalid credentials");
}
})
.catch((error) => {
console.error("Login failed:", error);
});
}}
className="space-y-4 text-black"
>
<div>
<label
htmlFor="username"
className="block text-sm font-medium text-gray-700 mb-1"
>
Username
</label>
<input
type="text"
name="username"
id="username"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700 mb-1"
>
Password
</label>
<input
type="password"
name="password"
id="password"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<input
type="submit"
value="Login"
className="w-full bg-blue-600 text-white font-semibold py-2 rounded-md hover:bg-blue-700 transition"
/>
</form>
{Cookies.get("name") ? (
<button
className="w-full bg-blue-600 text-white font-semibold py-2 rounded-md hover:bg-blue-700 transition"
onClick={logout}
>
Logout
</button>
) : (
<p className="text-center text-gray-500 mt-4">
Don't have an account?{" "}
<a href="/register" className="text-blue-600 hover:underline">
Register here
</a>
</p>
)}
</div>
</div>
);
};
export default LoginCard;

View File

@@ -0,0 +1,10 @@
import Cookies from "js-cookie";
export const greeting = () => {
return Cookies.get("name") ?? "Login";
};
export const logout = () => {
Cookies.remove("name");
window.location.reload();
};

View File

@@ -0,0 +1,29 @@
import { useState, useEffect } from "react";
export interface User {
id: number;
username: string;
first_name: string;
last_name: string;
email: string;
password: string;
created: string;
}
export function useUsers(): User[] {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
const data = localStorage.getItem("users");
if (data) {
try {
const parsed = JSON.parse(data);
setUsers(parsed.result || []);
} catch {
setUsers([]);
}
}
}, []);
return users;
}

View File

@@ -1,15 +1,15 @@
services:
react-frontend:
container_name: bikelane-frontend_react
build: ./client
ports:
- "5001:5001"
environment:
- CHOKIDAR_USEPOLLING=true
volumes:
- ./client:/app
- /app/node_modules
restart: unless-stopped
# react-frontend:
# container_name: bikelane-frontend_react
# build: ./client
# ports:
# - "5001:5001"
# environment:
# - CHOKIDAR_USEPOLLING=true
# volumes:
# - ./client:/app
# - /app/node_modules
# restart: unless-stopped
bikelane-backend:
container_name: bikelane-backend_express