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:
18
client/package-lock.json
generated
18
client/package-lock.json
generated
@@ -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",
|
||||
|
@@ -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",
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
108
client/src/components/LoginCard.tsx
Normal file
108
client/src/components/LoginCard.tsx
Normal 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"
|
||||
>
|
||||
×
|
||||
</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;
|
10
client/src/utils/functions.ts
Normal file
10
client/src/utils/functions.ts
Normal 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();
|
||||
};
|
29
client/src/utils/useUsers.ts
Normal file
29
client/src/utils/useUsers.ts
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user