implement user authentication with login functionality and database integration
This commit is contained in:
122
backend/package-lock.json
generated
122
backend/package-lock.json
generated
@@ -12,7 +12,9 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^5.1.0"
|
"express": "^5.1.0",
|
||||||
|
"jose": "^6.0.12",
|
||||||
|
"mysql2": "^3.14.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
@@ -34,6 +36,15 @@
|
|||||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/aws-ssl-profiles": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -176,6 +187,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/denque": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -381,6 +401,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/generate-function": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-property": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
@@ -512,6 +541,12 @@
|
|||||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/is-property": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/jake": {
|
"node_modules/jake": {
|
||||||
"version": "10.9.4",
|
"version": "10.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
|
||||||
@@ -529,6 +564,45 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jose": {
|
||||||
|
"version": "6.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-6.0.12.tgz",
|
||||||
|
"integrity": "sha512-T8xypXs8CpmiIi78k0E+Lk7T2zlK4zDyg+o1CZ4AkOHgDg98ogdP2BeZ61lTFKFyoEwJ9RgAgN+SdM3iPgNonQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/lru-cache": {
|
||||||
|
"version": "7.18.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||||
|
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lru.min": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.0.0",
|
||||||
|
"deno": ">=1.30.0",
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wellwelwel"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -598,6 +672,38 @@
|
|||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/mysql2": {
|
||||||
|
"version": "3.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.3.tgz",
|
||||||
|
"integrity": "sha512-fD6MLV8XJ1KiNFIF0bS7Msl8eZyhlTDCDl75ajU5SJtpdx9ZPEACulJcqJWr1Y8OYyxsFc4j3+nflpmhxCU5aQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"aws-ssl-profiles": "^1.1.1",
|
||||||
|
"denque": "^2.1.0",
|
||||||
|
"generate-function": "^2.3.1",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"long": "^5.2.1",
|
||||||
|
"lru.min": "^1.0.0",
|
||||||
|
"named-placeholders": "^1.1.3",
|
||||||
|
"seq-queue": "^0.0.5",
|
||||||
|
"sqlstring": "^2.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/named-placeholders": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^7.14.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||||
@@ -789,6 +895,11 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/seq-queue": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||||
|
},
|
||||||
"node_modules/serve-static": {
|
"node_modules/serve-static": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||||
@@ -882,6 +993,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sqlstring": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||||
|
@@ -14,6 +14,8 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^5.1.0"
|
"express": "^5.1.0",
|
||||||
|
"jose": "^6.0.12",
|
||||||
|
"mysql2": "^3.14.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
backend/routes/api.js
Normal file
26
backend/routes/api.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import express from "express";
|
||||||
|
import { loginFunc, getItemsFromDatabase } from "../services/database.js";
|
||||||
|
import { authenticate, generateToken } from "../services/tokenService.js";
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Example endpoint
|
||||||
|
router.post("/login", async (req, res) => {
|
||||||
|
const result = await loginFunc(req.body.username, req.body.password);
|
||||||
|
if (result.success) {
|
||||||
|
const token = await generateToken({ username: req.body.username });
|
||||||
|
res.status(200).json({ message: "Login successful", token });
|
||||||
|
} else {
|
||||||
|
res.status(401).json({ message: "Invalid credentials" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/items", authenticate, async (req, res) => {
|
||||||
|
const result = await getItemsFromDatabase();
|
||||||
|
if (result.success) {
|
||||||
|
res.status(200).json(result.data);
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: "Failed to fetch items" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
@@ -11,6 +11,11 @@ app.use(express.urlencoded({ extended: true, limit: "10mb" }));
|
|||||||
app.set("view engine", "ejs");
|
app.set("view engine", "ejs");
|
||||||
app.use(express.json({ limit: "10mb" }));
|
app.use(express.json({ limit: "10mb" }));
|
||||||
|
|
||||||
|
// Import API router
|
||||||
|
import apiRouter from "./routes/api.js";
|
||||||
|
|
||||||
|
app.use("/api", apiRouter);
|
||||||
|
|
||||||
app.get("/", (req, res) => {
|
app.get("/", (req, res) => {
|
||||||
res.render("index.ejs");
|
res.render("index.ejs");
|
||||||
});
|
});
|
||||||
@@ -24,4 +29,4 @@ app.use((err, req, res, next) => {
|
|||||||
// Log the error stack and send a generic error response
|
// Log the error stack and send a generic error response
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
res.status(500).send("Something broke!");
|
res.status(500).send("Something broke!");
|
||||||
});
|
});
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
import mysql from "mysql2";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
// Ein einzelner Pool reicht; der zweite Pool benutzte fälschlich DB_TABLE als Datenbank
|
||||||
|
const pool = mysql
|
||||||
|
.createPool({
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_NAME,
|
||||||
|
})
|
||||||
|
.promise();
|
||||||
|
|
||||||
|
export const loginFunc = async (username, password) => {
|
||||||
|
const [result] = await pool.query(
|
||||||
|
"SELECT * FROM users WHERE username = ? AND password = ?",
|
||||||
|
[username, password]
|
||||||
|
);
|
||||||
|
if (result.length > 0) return { success: true };
|
||||||
|
return { success: false };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getItemsFromDatabase = async () => {
|
||||||
|
const [result] = await pool.query("SELECT * FROM items");
|
||||||
|
if (result.length > 0) {
|
||||||
|
return { success: true, data: result };
|
||||||
|
}
|
||||||
|
return { success: false };
|
||||||
|
};
|
||||||
|
26
backend/services/tokenService.js
Normal file
26
backend/services/tokenService.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { SignJWT, jwtVerify } from "jose";
|
||||||
|
import env from "dotenv";
|
||||||
|
env.config();
|
||||||
|
const secret = new TextEncoder().encode(process.env.SECRET_KEY);
|
||||||
|
|
||||||
|
export async function generateToken(payload) {
|
||||||
|
const newToken = await new SignJWT(payload)
|
||||||
|
.setProtectedHeader({ alg: "HS256" })
|
||||||
|
.setIssuedAt()
|
||||||
|
.setExpirationTime("2h") // Token valid for 2 hours
|
||||||
|
.sign(secret);
|
||||||
|
console.log("Generated token: ", newToken);
|
||||||
|
return newToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function authenticate(req, res, next) {
|
||||||
|
const authHeader = req.headers["authorization"];
|
||||||
|
const token = authHeader && authHeader.split(" ")[1]; // Bearer <token>
|
||||||
|
|
||||||
|
if (token == null) return res.sendStatus(401); // No token present
|
||||||
|
|
||||||
|
const { payload } = await jwtVerify(token, secret);
|
||||||
|
req.user = payload;
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
@@ -1,41 +1,28 @@
|
|||||||
import "./App.css";
|
import "./App.css";
|
||||||
import Layout from "./layout/Layout";
|
import Layout from "./layout/Layout";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Form1 from "./components/Form1";
|
import Form1 from "./components/Form1";
|
||||||
import Form2 from "./components/Form2";
|
import Form2 from "./components/Form2";
|
||||||
import Form3 from "./components/Form3";
|
import Form3 from "./components/Form3";
|
||||||
|
import LoginForm from "./components/LoginForm";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { ToastContainer } from "react-toastify";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const Items = [
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "Mock Book 1",
|
|
||||||
author: "Author 1",
|
|
||||||
description: "Description for Mock Book 1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "Mock Book 2",
|
|
||||||
author: "Author 2",
|
|
||||||
description: "Description for Mock Book 2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "Mock Book 3",
|
|
||||||
author: "Author 3",
|
|
||||||
description: "Description for Mock Book 3",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem("allItems", JSON.stringify(Items));
|
if (Cookies.get("token")) {
|
||||||
localStorage.setItem("borrowableItems", JSON.stringify(Items));
|
setIsLoggedIn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("borrowableItems", JSON.stringify([]));
|
||||||
localStorage.setItem("borrowCode", "123456");
|
localStorage.setItem("borrowCode", "123456");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
// Mock flow without real logic: show the three sections stacked for design preview
|
||||||
|
return isLoggedIn ? (
|
||||||
<Layout>
|
<Layout>
|
||||||
{/* Mock flow without real logic: show the three sections stacked for design preview */}
|
|
||||||
<div className="space-y-10">
|
<div className="space-y-10">
|
||||||
<Form1 />
|
<Form1 />
|
||||||
<div className="h-px bg-blue-100" />
|
<div className="h-px bg-blue-100" />
|
||||||
@@ -44,6 +31,23 @@ function App() {
|
|||||||
<Form3 />
|
<Form3 />
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<LoginForm onLogin={() => setIsLoggedIn(true)} />
|
||||||
|
<ToastContainer
|
||||||
|
position="top-right"
|
||||||
|
autoClose={3000}
|
||||||
|
hideProgressBar={false}
|
||||||
|
newestOnTop
|
||||||
|
closeOnClick
|
||||||
|
rtl={false}
|
||||||
|
pauseOnFocusLoss={false}
|
||||||
|
draggable
|
||||||
|
pauseOnHover
|
||||||
|
theme="light"
|
||||||
|
className="!z-50"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,13 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { myToast } from "../utils/toastify";
|
|
||||||
|
|
||||||
const Form3: React.FC = () => {
|
const Form3: React.FC = () => {
|
||||||
if (localStorage.getItem("borrowCode")) {
|
|
||||||
myToast("Ausleihe erfolgreich!", "success");
|
|
||||||
} else {
|
|
||||||
myToast("Ausleihe fehlgeschlagen!", "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
const code = localStorage.getItem("borrowCode") ?? "—";
|
const code = localStorage.getItem("borrowCode") ?? "—";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
@@ -9,6 +10,15 @@ const Header: React.FC = () => {
|
|||||||
<p className="text-blue-500 mt-1 md:mt-2 text-base md:text-lg font-medium">
|
<p className="text-blue-500 mt-1 md:mt-2 text-base md:text-lg font-medium">
|
||||||
Schnell und unkompliziert Equipment reservieren
|
Schnell und unkompliziert Equipment reservieren
|
||||||
</p>
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
Cookies.remove("token");
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
className="text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
74
frontend/src/components/LoginForm.tsx
Normal file
74
frontend/src/components/LoginForm.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { loginUser } from "../utils/fetchData";
|
||||||
|
import { myToast } from "../utils/toastify";
|
||||||
|
|
||||||
|
type LoginFormProps = {
|
||||||
|
onLogin: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LoginForm: React.FC<LoginFormProps> = ({ onLogin }) => {
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const result = await loginUser(username, password);
|
||||||
|
if (result.success) {
|
||||||
|
myToast("Login successful!", "success");
|
||||||
|
onLogin();
|
||||||
|
} else {
|
||||||
|
myToast("Login failed. Please check your credentials.", "error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-blue-950 min-h-screen">
|
||||||
|
<div className="max-w-sm mx-auto mt-20 bg-white rounded-xl shadow-lg p-8 border border-blue-100">
|
||||||
|
<h2 className="text-2xl font-bold text-blue-700 mb-6 text-center">
|
||||||
|
Login
|
||||||
|
</h2>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="username"
|
||||||
|
className="block text-sm font-medium text-gray-700 mb-1"
|
||||||
|
>
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
id="username"
|
||||||
|
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 px-3 py-2"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="password"
|
||||||
|
className="block text-sm font-medium text-gray-700 mb-1"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 px-3 py-2"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-blue-600 text-white font-bold py-2 px-4 rounded-md shadow hover:bg-blue-700 transition"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginForm;
|
@@ -1,7 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Object from "./Object";
|
import Object from "./Object";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
const Sidebar: React.FC = () => {
|
const Sidebar: React.FC = () => {
|
||||||
|
useEffect(() => {});
|
||||||
|
|
||||||
const items = JSON.parse(localStorage.getItem("allItems") || "[]");
|
const items = JSON.parse(localStorage.getItem("allItems") || "[]");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -20,16 +23,16 @@ const Sidebar: React.FC = () => {
|
|||||||
d="M16.5 7.5V4.75A2.25 2.25 0 0 0 14.25 2.5h-4.5A2.25 2.25 0 0 0 7.5 4.75V7.5m9 0h-9m9 0v11.75A2.25 2.25 0 0 1 14.25 21.5h-4.5A2.25 2.25 0 0 1 7.5 19.25V7.5m9 0h-9"
|
d="M16.5 7.5V4.75A2.25 2.25 0 0 0 14.25 2.5h-4.5A2.25 2.25 0 0 0 7.5 4.75V7.5m9 0h-9m9 0v11.75A2.25 2.25 0 0 1 14.25 21.5h-4.5A2.25 2.25 0 0 1 7.5 19.25V7.5m9 0h-9"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Ausleih-Übersicht
|
Geräte Übersicht
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-4 flex-1 overflow-auto pr-1">
|
<div className="space-y-4 flex-1 overflow-auto pr-1">
|
||||||
{items.map((item: any) => (
|
{items.map((item: any) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.item_name}
|
||||||
className="bg-white/80 rounded-xl p-4 shadow hover:shadow-md transition"
|
className="bg-white/80 rounded-xl p-4 shadow hover:shadow-md transition"
|
||||||
>
|
>
|
||||||
<Object title={item.title} description={item.description} />
|
<Object title={item.item_name} description={"test"} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,53 @@
|
|||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { myToast } from "./toastify";
|
||||||
|
|
||||||
|
export const fetchAllData = async (token: string | undefined) => {
|
||||||
|
if (!token) return;
|
||||||
|
try {
|
||||||
|
// First we fetch all items that are potentially available for borrowing
|
||||||
|
const response = await fetch("http://localhost:8002/api/items", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
myToast("Failed to fetch items", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
localStorage.setItem("allItems", JSON.stringify(data));
|
||||||
|
} catch (error) {
|
||||||
|
myToast("An error occurred", "error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loginUser = async (username: string, password: string) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("http://localhost:8002/api/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return { success: false } as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (data?.token) {
|
||||||
|
Cookies.set("token", data.token);
|
||||||
|
myToast("Login successful!", "success");
|
||||||
|
fetchAllData(Cookies.get("token"));
|
||||||
|
return { success: true, token: data.token } as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false } as const;
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false } as const;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user