From c35838b0344b9b4acb38f59d119982c320d0fdd5 Mon Sep 17 00:00:00 2001 From: "theis.gaedigk" Date: Tue, 22 Jul 2025 16:47:44 +0200 Subject: [PATCH] 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. --- backend/server.js | 15 +++- client/package-lock.json | 18 +++++ client/package.json | 2 + client/src/App.tsx | 32 ++++++++- client/src/components/Header.tsx | 18 ++++- client/src/components/LoginCard.tsx | 108 ++++++++++++++++++++++++++++ client/src/utils/functions.ts | 10 +++ client/src/utils/useUsers.ts | 29 ++++++++ docker-compose.yml | 22 +++--- 9 files changed, 236 insertions(+), 18 deletions(-) create mode 100644 client/src/components/LoginCard.tsx create mode 100644 client/src/utils/functions.ts create mode 100644 client/src/utils/useUsers.ts diff --git a/backend/server.js b/backend/server.js index 3c22743..2c756aa 100644 --- a/backend/server.js +++ b/backend/server.js @@ -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}`); }); diff --git a/client/package-lock.json b/client/package-lock.json index 940c328..72f0496 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -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", diff --git a/client/package.json b/client/package.json index 0d30e82..4660edc 100644 --- a/client/package.json +++ b/client/package.json @@ -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", diff --git a/client/src/App.tsx b/client/src/App.tsx index b081ddc..4263df3 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -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 ( -

Test

+ + + + + + + + + + + + + + {users.map((user: any, idx: number) => ( + + + + + + + + + + ))} + +
#UsernameFirst nameLast nameEmailPasswordCreated
{idx + 1}{user.username}{user.first_name}{user.last_name}{user.email}{user.password}{user.created}
); } diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 6fd7985..5a73c1b 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -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 (
-

Bikelane Web

+

Bikelane Admin Panel

+
{loginCardVisible && }
); }; diff --git a/client/src/components/LoginCard.tsx b/client/src/components/LoginCard.tsx new file mode 100644 index 0000000..d81f989 --- /dev/null +++ b/client/src/components/LoginCard.tsx @@ -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 = ({ onClose }) => { + return ( +
+
+ +

+ Login +

+
{ + 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" + > +
+ + +
+
+ + +
+ +
+ {Cookies.get("name") ? ( + + ) : ( +

+ Don't have an account?{" "} + + Register here + +

+ )} +
+
+ ); +}; + +export default LoginCard; diff --git a/client/src/utils/functions.ts b/client/src/utils/functions.ts new file mode 100644 index 0000000..a932be6 --- /dev/null +++ b/client/src/utils/functions.ts @@ -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(); +}; diff --git a/client/src/utils/useUsers.ts b/client/src/utils/useUsers.ts new file mode 100644 index 0000000..f6006e0 --- /dev/null +++ b/client/src/utils/useUsers.ts @@ -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([]); + + useEffect(() => { + const data = localStorage.getItem("users"); + if (data) { + try { + const parsed = JSON.parse(data); + setUsers(parsed.result || []); + } catch { + setUsers([]); + } + } + }, []); + + return users; +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ccdecfa..7bf477a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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