diff --git a/admin/src/components/AddForm.tsx b/admin/src/components/AddForm.tsx index e40c80e..1666105 100644 --- a/admin/src/components/AddForm.tsx +++ b/admin/src/components/AddForm.tsx @@ -65,6 +65,13 @@ const AddForm: React.FC = ({ onClose, alert }) => { "Der Nutzer wurde erfolgreich erstellt." ); onClose(); + } else { + alert( + "error", + "Fehler beim Erstellen des Nutzers", + "Es gab einen Fehler beim Erstellen des Nutzers. Vielleicht gibt es bereits einen Nutzer mit diesem Benutzernamen." + ); + onClose(); } }} > diff --git a/admin/src/components/ChangePWform.tsx b/admin/src/components/ChangePWform.tsx new file mode 100644 index 0000000..aeddefe --- /dev/null +++ b/admin/src/components/ChangePWform.tsx @@ -0,0 +1,115 @@ +import React from "react"; +import { Button, Card, Field, Input, Stack, Alert } from "@chakra-ui/react"; +import { changePW } from "@/utils/userActions"; +import { useState } from "react"; + +type ChangePWformProps = { + onClose: () => void; + alert: ( + status: "success" | "error", + message: string, + description: string + ) => void; + username: string; +}; + +const ChangePWform: React.FC = ({ + onClose, + alert, + username, +}) => { + const [showSubAlert, setShowSubAlert] = useState(false); + const [subAlertMessage, setSubAlertMessage] = useState(""); + + const subAlert = (message: string) => { + setSubAlertMessage(message); + setShowSubAlert(true); + }; + + return ( +
+ + + Passwort ändern + + Füllen Sie das folgende Formular aus, um das Passwort zu ändern. + + + + + + Neues Passwort + + + + Neues Passwort widerholen + + + + + + + + {showSubAlert && ( + + + + {subAlertMessage} + + + )} + + +
+ ); +}; + +export default ChangePWform; diff --git a/admin/src/components/ItemTable.tsx b/admin/src/components/ItemTable.tsx index 8c196ed..6c415bb 100644 --- a/admin/src/components/ItemTable.tsx +++ b/admin/src/components/ItemTable.tsx @@ -24,6 +24,7 @@ import Cookies from "js-cookie"; import { useState, useEffect } from "react"; import { deleteItem } from "@/utils/userActions"; import AddItemForm from "./AddItemForm"; +import { formatDateTime } from "@/utils/userFuncs"; type Items = { id: number; @@ -232,7 +233,7 @@ const ItemTable: React.FC = () => { )} - {item.entry_created_at} + {formatDateTime(item.entry_created_at)} { user.id, user.username, user.role, - user.password ).then((response) => { if (response.success) { setError( diff --git a/admin/src/utils/userActions.ts b/admin/src/utils/userActions.ts index 7cdcaef..7d812b4 100644 --- a/admin/src/utils/userActions.ts +++ b/admin/src/utils/userActions.ts @@ -24,19 +24,18 @@ export const handleDelete = async (userId: number) => { export const handleEdit = async ( userId: number, username: string, - role: string, - password: string + role: string ) => { try { const response = await fetch( `https://backend.insta.the1s.de/api/editUser/${userId}`, { - method: "PUT", + method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${Cookies.get("token")}`, }, - body: JSON.stringify({ username, role, password }), + body: JSON.stringify({ username, role }), } ); if (!response.ok) { @@ -73,6 +72,26 @@ export const createUser = async ( } }; +export const changePW = async (newPassword: string, username: string) => { + try { + const response = await fetch(`http://localhost:8002/api/changePWadmin`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${Cookies.get("token")}`, + }, + body: JSON.stringify({ newPassword, username }), + }); + if (!response.ok) { + throw new Error("Failed to change password"); + } + return { success: true }; + } catch (error) { + console.error("Error changing password:", error); + return { success: false }; + } +}; + export const deleteLoan = async (loanId: number) => { try { const response = await fetch( diff --git a/admin/src/utils/userFuncs.ts b/admin/src/utils/userFuncs.ts index 0aa7587..c9ded8d 100644 --- a/admin/src/utils/userFuncs.ts +++ b/admin/src/utils/userFuncs.ts @@ -1,14 +1,7 @@ export const formatDateTime = (value: string | null | undefined) => { if (!value) return "N/A"; - const inpDate = new Date(value); - if (isNaN(inpDate.getTime())) return "N/A"; - return ( - inpDate.toLocaleString(undefined, { - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - }) + " Uhr" - ); + const m = value.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/); + if (!m) return "N/A"; + const [, y, M, d, h, min] = m; + return `${d}.${M}.${y} ${h}:${min} Uhr`; }; diff --git a/backend/routes/api.js b/backend/routes/api.js index a4b12c1..1863029 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -18,6 +18,8 @@ import { getAllItems, deleteItemID, createItem, + changeUserPassword, + changeUserPasswordFRONTEND, } from "../services/database.js"; import { authenticate, generateToken } from "../services/tokenService.js"; const router = express.Router(); @@ -175,6 +177,21 @@ router.post("/createLoan", authenticate, async (req, res) => { } }); +router.post("/changePassword", authenticate, async (req, res) => { + const { oldPassword, newPassword } = req.body || {}; + const username = req.user.username; + const result = await changeUserPasswordFRONTEND( + username, + oldPassword, + newPassword + ); + if (result.success) { + res.status(200).json({ message: "Password changed successfully" }); + } else { + res.status(500).json({ message: "Failed to change password" }); + } +}); + // Admin panel functions router.post("/loginAdmin", async (req, res) => { @@ -223,10 +240,10 @@ router.get("/verifyToken", authenticate, async (req, res) => { res.status(200).json({ message: "Token is valid" }); }); -router.put("/editUser/:id", authenticate, async (req, res) => { +router.post("/editUser/:id", authenticate, async (req, res) => { const userId = req.params.id; - const { username, role, password } = req.body || {}; - const result = await handleEdit(userId, username, role, password); + const { username, role } = req.body || {}; + const result = await handleEdit(userId, username, role); if (result.success) { return res.status(200).json({ message: "User edited successfully" }); } @@ -276,4 +293,17 @@ router.post("/createItem", authenticate, async (req, res) => { return res.status(500).json({ message: "Failed to create item" }); }); +router.post("/changePWadmin", authenticate, async (req, res) => { + const newPassword = req.body.newPassword; + if (!newPassword) { + return res.status(400).json({ message: "New password is required" }); + } + + const result = await changeUserPassword(req.body.username, newPassword); + if (result.success) { + return res.status(200).json({ message: "Password changed successfully" }); + } + return res.status(500).json({ message: "Failed to change password" }); +}); + export default router; diff --git a/backend/services/database.js b/backend/services/database.js index bcdd5e7..e5c2c22 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -88,11 +88,8 @@ export const getItemsFromDatabase = async (role) => { }; export const getLoansFromDatabase = async () => { - const [result] = await pool.query("SELECT * FROM loans;"); - if (result.length > 0) { - return { success: true, data: result }; - } - return { success: false }; + const [rows] = await pool.query("SELECT * FROM loans;"); + return { success: true, data: rows.length > 0 ? rows : null }; }; export const getUserLoansFromDatabase = async (username) => { @@ -298,24 +295,44 @@ export const createLoanInDatabase = async ( // These functions are only temporary, and will be deleted when the full bin is set up. export const onTake = async (loanId) => { + const [items] = await pool.query( + "SELECT loaned_items_id FROM loans WHERE id = ?", + [loanId] + ); + + const [setItemStates] = await pool.query( + "UPDATE items SET inSafe = 0 WHERE id IN (?)", + [items.map((item) => item.loaned_items_id)] + ); + const [result] = await pool.query( "UPDATE loans SET take_date = NOW() WHERE id = ?", [loanId] ); - if (result.affectedRows > 0) { + if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { return { success: true }; } return { success: false }; }; export const onReturn = async (loanId) => { + const [items] = await pool.query( + "SELECT loaned_items_id FROM loans WHERE id = ?", + [loanId] + ); + + const [setItemStates] = await pool.query( + "UPDATE items SET inSafe = 1 WHERE id IN (?)", + [items.map((item) => item.loaned_items_id)] + ); + const [result] = await pool.query( "UPDATE loans SET returned_date = NOW() WHERE id = ?", [loanId] ); - if (result.affectedRows > 0) { + if (result.affectedRows > 0 && setItemStates.affectedRows > 0) { return { success: true }; } return { success: false }; @@ -331,7 +348,9 @@ export const loginAdmin = async (username, password) => { }; export const getAllUsers = async () => { - const [result] = await pool.query("SELECT * FROM users"); + const [result] = await pool.query( + "SELECT id, username, role, entry_created_at FROM users" + ); if (result.length > 0) return { success: true, data: result }; return { success: false }; }; @@ -342,10 +361,10 @@ export const deleteUserID = async (userId) => { return { success: false }; }; -export const handleEdit = async (userId, username, role, password) => { +export const handleEdit = async (userId, username, role) => { const [result] = await pool.query( - "UPDATE users SET username = ?, role = ?, password = ? WHERE id = ?", - [username, role, password, userId] + "UPDATE users SET username = ?, role = ? WHERE id = ?", + [username, role, userId] ); if (result.affectedRows > 0) return { success: true }; return { success: false }; @@ -386,3 +405,25 @@ export const createItem = async (item_name, can_borrow_role) => { if (result.affectedRows > 0) return { success: true }; return { success: false }; }; + +export const changeUserPassword = async (username, newPassword) => { + const [result] = await pool.query( + "UPDATE users SET password = ? WHERE username = ?", + [newPassword, username] + ); + if (result.affectedRows > 0) return { success: true }; + return { success: false }; +}; + +export const changeUserPasswordFRONTEND = async ( + username, + oldPassword, + newPassword +) => { + const [result] = await pool.query( + "UPDATE users SET password = ? WHERE username = ? AND password = ?", + [newPassword, username, oldPassword] + ); + if (result.affectedRows > 0) return { success: true }; + return { success: false }; +}; diff --git a/frontend/src/components/Form4.tsx b/frontend/src/components/Form4.tsx index c78aa22..f3e47de 100644 --- a/frontend/src/components/Form4.tsx +++ b/frontend/src/components/Form4.tsx @@ -21,9 +21,10 @@ type Loan = { const formatDate = (iso: string | null) => { if (!iso) return "-"; - const d = new Date(iso); - if (Number.isNaN(d.getTime())) return iso; - return d.toLocaleString("de-DE", { dateStyle: "short", timeStyle: "short" }); + const m = iso.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/); + if (!m) return iso; + const [, y, M, d, h, min] = m; + return `${d}.${M}.${y} ${h}:${min}`; }; async function fetchUserLoans(): Promise { diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index b04bd4c..903786c 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,13 +1,33 @@ import React from "react"; +import { changePW } from "../utils/userHandler"; +import { myToast } from "../utils/toastify"; type HeaderProps = { onLogout: () => void; }; const Header: React.FC = ({ onLogout }) => { + const passwordForm = () => { + const oldPW = window.prompt("Altes Passwort"); + const newPW = window.prompt("Neues Passwort"); + const repeatNewPW = window.prompt("Neues Passwort wiederholen"); + if (oldPW && newPW && repeatNewPW) { + if (newPW === repeatNewPW) { + changePW(oldPW, newPW); + } else { + myToast("Die neuen Passwörter stimmen nicht überein.", "error"); + } + } else { + myToast("Bitte alle Felder ausfüllen.", "error"); + } + }; + + const btn = + "inline-flex items-center h-9 px-3 rounded-md text-sm font-medium border border-slate-300 bg-white text-slate-700 hover:bg-slate-100 active:bg-slate-200 transition focus:outline-none focus:ring-2 focus:ring-slate-400/50"; + return (
-
+

Gegenstand ausleihen @@ -16,23 +36,38 @@ const Header: React.FC = ({ onLogout }) => { Schnell und unkompliziert Equipment reservieren

- - - - - - - + +
); diff --git a/frontend/src/utils/userHandler.ts b/frontend/src/utils/userHandler.ts index 89793ba..6bf92b0 100644 --- a/frontend/src/utils/userHandler.ts +++ b/frontend/src/utils/userHandler.ts @@ -137,3 +137,22 @@ export const onTake = async (loanID: number) => { myToast("Ausleihe erfolgreich ausgeliehen!", "success"); return true; }; + +export const changePW = async (oldPassword: string, newPassword: string) => { + const response = await fetch("http://localhost:8002/api/changePassword", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${Cookies.get("token") || ""}`, + }, + body: JSON.stringify({ oldPassword, newPassword }), + }); + + if (!response.ok) { + myToast("Fehler beim Ändern des Passworts", "error"); + return false; + } + + myToast("Passwort erfolgreich geändert!", "success"); + return true; +};