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