From 217803ba8fe79b17d048fecf15f4c089cbce1029 Mon Sep 17 00:00:00 2001 From: Theis Gaedigk Date: Sun, 31 Aug 2025 18:07:49 +0200 Subject: [PATCH] implement admin panel with login functionality and dashboard layout --- admin/src/App.tsx | 5 +- admin/src/Layout/Dashboard.tsx | 60 +++++++++++++++++ admin/src/Layout/Layout.tsx | 39 +++++++++++ admin/src/Layout/Login.tsx | 62 +++++++++++++++++ admin/src/Layout/Sidebar.tsx | 81 +++++++++++++++++++++++ admin/src/components/ItemTable.tsx | 7 ++ admin/src/components/LoanTable.tsx | 7 ++ admin/src/components/LockerTable.tsx | 7 ++ admin/src/components/UserTable.tsx | 7 ++ admin/src/components/myChakra/MyAlert.tsx | 21 ++++++ admin/src/utils/loginUser.ts | 43 ++++++++++++ admin/src/utils/toastify.ts | 18 +++++ admin/tsconfig.app.json | 4 +- backend/routes/api.js | 30 +++++++++ backend/scheme.sql | 11 +++ backend/services/database.js | 9 +++ 16 files changed, 409 insertions(+), 2 deletions(-) create mode 100644 admin/src/Layout/Dashboard.tsx create mode 100644 admin/src/Layout/Layout.tsx create mode 100644 admin/src/Layout/Login.tsx create mode 100644 admin/src/Layout/Sidebar.tsx create mode 100644 admin/src/components/ItemTable.tsx create mode 100644 admin/src/components/LoanTable.tsx create mode 100644 admin/src/components/LockerTable.tsx create mode 100644 admin/src/components/UserTable.tsx create mode 100644 admin/src/components/myChakra/MyAlert.tsx create mode 100644 admin/src/utils/loginUser.ts create mode 100644 admin/src/utils/toastify.ts diff --git a/admin/src/App.tsx b/admin/src/App.tsx index 013e1bb..2269c2e 100644 --- a/admin/src/App.tsx +++ b/admin/src/App.tsx @@ -1,9 +1,12 @@ import "./App.css"; +import Layout from "./Layout/Layout"; function App() { return ( <> -

Admin panel

+ +

+
); } diff --git a/admin/src/Layout/Dashboard.tsx b/admin/src/Layout/Dashboard.tsx new file mode 100644 index 0000000..23cdbf6 --- /dev/null +++ b/admin/src/Layout/Dashboard.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { useState } from "react"; +import { Box, Heading, Text, Flex, Button } from "@chakra-ui/react"; +import Sidebar from "./Sidebar"; +import UserTable from "../components/UserTable"; +import ItemTable from "../components/ItemTable"; +import LockerTable from "../components/LockerTable"; +import LoanTable from "../components/LoanTable"; + +type DashboardProps = { + onLogout?: () => void; +}; + +const Dashboard: React.FC = ({ onLogout }) => { + const userName = localStorage.getItem("userName") || "Admin"; + + const [activeView, setActiveView] = useState(""); + + return ( + + setActiveView("Ausleihen")} + viewGegenstaende={() => setActiveView("Gegenstände")} + viewSchliessfaecher={() => setActiveView("Schließfächer")} + viewUser={() => setActiveView("User")} + /> + + + Dashboard + + + Willkommen {userName}, im Admin-Dashboard! + + + + + + {activeView === "" && Bitte wählen Sie eine Ansicht aus.} + {activeView === "User" && } + {activeView === "Ausleihen" && } + {activeView === "Gegenstände" && } + {activeView === "Schließfächer" && } + + + + ); +}; + +export default Dashboard; diff --git a/admin/src/Layout/Layout.tsx b/admin/src/Layout/Layout.tsx new file mode 100644 index 0000000..a329532 --- /dev/null +++ b/admin/src/Layout/Layout.tsx @@ -0,0 +1,39 @@ +import React, { useState } from "react"; +import { useEffect } from "react"; +import Dashboard from "./Dashboard"; +import Login from "./Login"; +import Cookies from "js-cookie"; + +type LayoutProps = { + children: React.ReactNode; +}; + +const Layout: React.FC = ({ children }) => { + const [isLoggedIn, setIsLoggedIn] = useState(false); + + useEffect(() => { + if (Cookies.get("token")) { + setIsLoggedIn(true); + } + }, []); + + const handleLogout = () => { + Cookies.remove("token"); + setIsLoggedIn(false); + }; + + return ( + <> +
+ {isLoggedIn ? ( + handleLogout()} /> + ) : ( + setIsLoggedIn(true)} /> + )} +
+ {children} + + ); +}; + +export default Layout; diff --git a/admin/src/Layout/Login.tsx b/admin/src/Layout/Login.tsx new file mode 100644 index 0000000..934657b --- /dev/null +++ b/admin/src/Layout/Login.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import { useState } from "react"; +import { loginFunc } from "@/utils/loginUser"; +import MyAlert from "@/components/myChakra/MyAlert"; +import { Button, Card, Field, Input, Stack } from "@chakra-ui/react"; + +const Login: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) => { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [isError, setIsError] = useState(false); + const [errorMsg, setErrorMsg] = useState(""); + const [errorDsc, setErrorDsc] = useState(""); + + const handleLogin = async () => { + const result = await loginFunc(username, password); + if (!result.success) { + setErrorMsg(result.message); + setErrorDsc(result.description); + setIsError(true); + return; + } + onSuccess(); + }; + + return ( + + + Login + + Bitte unten Ihre Admin Zugangsdaten eingeben. + + + + + + username + setUsername(e.target.value)} + /> + + + password + setPassword(e.target.value)} + /> + + + + + {isError && } + + + + ); +}; + +export default Login; diff --git a/admin/src/Layout/Sidebar.tsx b/admin/src/Layout/Sidebar.tsx new file mode 100644 index 0000000..92e5699 --- /dev/null +++ b/admin/src/Layout/Sidebar.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { Box, Flex, VStack, Heading, Text, Link } from "@chakra-ui/react"; + +type SidebarProps = { + viewAusleihen: () => void; + viewGegenstaende: () => void; + viewSchliessfaecher: () => void; + viewUser: () => void; +}; + +const Sidebar: React.FC = ({ + viewAusleihen, + viewGegenstaende, + viewSchliessfaecher, + viewUser, +}) => { + return ( + + + + Borrow System + + + + + User + + + Ausleihen + + + Gegenstände + + + Schließfächer + + + + + © Made with ❤️ by Theis Gaedigk + + + + ); +}; + +export default Sidebar; diff --git a/admin/src/components/ItemTable.tsx b/admin/src/components/ItemTable.tsx new file mode 100644 index 0000000..151a05c --- /dev/null +++ b/admin/src/components/ItemTable.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const ItemTable: React.FC = () => { + return <>Item Table; +}; + +export default ItemTable; diff --git a/admin/src/components/LoanTable.tsx b/admin/src/components/LoanTable.tsx new file mode 100644 index 0000000..efcf3fc --- /dev/null +++ b/admin/src/components/LoanTable.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const LoanTable: React.FC = () => { + return <>Loan Table; +}; + +export default LoanTable; diff --git a/admin/src/components/LockerTable.tsx b/admin/src/components/LockerTable.tsx new file mode 100644 index 0000000..9a673c5 --- /dev/null +++ b/admin/src/components/LockerTable.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const LockerTable: React.FC = () => { + return <>Locker Table; +}; + +export default LockerTable; diff --git a/admin/src/components/UserTable.tsx b/admin/src/components/UserTable.tsx new file mode 100644 index 0000000..a011e29 --- /dev/null +++ b/admin/src/components/UserTable.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const UserTable: React.FC = () => { + return <>User Table; +}; + +export default UserTable; diff --git a/admin/src/components/myChakra/MyAlert.tsx b/admin/src/components/myChakra/MyAlert.tsx new file mode 100644 index 0000000..7ec9af5 --- /dev/null +++ b/admin/src/components/myChakra/MyAlert.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { Alert } from "@chakra-ui/react"; + +type MyAlertProps = { + title: string; + description: string; +}; + +const MyAlert: React.FC = ({ title, description }) => { + return ( + + + + {title} + {description} + + + ); +}; + +export default MyAlert; diff --git a/admin/src/utils/loginUser.ts b/admin/src/utils/loginUser.ts new file mode 100644 index 0000000..3a65c2b --- /dev/null +++ b/admin/src/utils/loginUser.ts @@ -0,0 +1,43 @@ +import Cookies from "js-cookie"; + +export type LoginSuccess = { success: true }; +export type LoginFailure = { + success: false; + message: string; + description: string; +}; +export type LoginResult = LoginSuccess | LoginFailure; + +export const loginFunc = async ( + username: string, + password: string +): Promise => { + try { + const response = await fetch("http://localhost:8002/api/loginAdmin", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }); + + if (!response.ok) { + return { + success: false, + message: "Login failed!", + description: "Invalid username or password.", + }; + } + + // Successful login + const data = await response.json(); + Cookies.set("token", data.token); + localStorage.setItem("userName", data.first_name); + return { success: true }; + } catch (error) { + console.error("Error logging in:", error); + return { + success: false, + message: "Login failed!", + description: "Server error.", + }; + } +}; diff --git a/admin/src/utils/toastify.ts b/admin/src/utils/toastify.ts new file mode 100644 index 0000000..51ba790 --- /dev/null +++ b/admin/src/utils/toastify.ts @@ -0,0 +1,18 @@ +import { toast, Flip, type ToastOptions } from "react-toastify"; + +export type ToastType = "success" | "error" | "info" | "warning"; + +export const myToast = (message: string, msgType: ToastType) => { + let config: ToastOptions = { + position: "top-right", + autoClose: 3000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "colored", + transition: Flip, + }; + toast[msgType](message, config); +}; diff --git a/admin/tsconfig.app.json b/admin/tsconfig.app.json index 81137a1..8ad072c 100644 --- a/admin/tsconfig.app.json +++ b/admin/tsconfig.app.json @@ -27,7 +27,9 @@ "baseUrl": ".", "paths": { "@/*": ["./src/*"] - } + }, + + "forceConsistentCasingInFileNames": true }, "include": ["src"] } diff --git a/backend/routes/api.js b/backend/routes/api.js index f0e7a2a..b5f4da1 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -8,6 +8,7 @@ import { getBorrowableItemsFromDatabase, createLoanInDatabase, onTake, + loginAdmin, onReturn, } from "../services/database.js"; import { authenticate, generateToken } from "../services/tokenService.js"; @@ -166,4 +167,33 @@ router.post("/createLoan", authenticate, async (req, res) => { } }); + + +// Admin panel functions + +router.post("/loginAdmin", async (req, res) => { + const { username, password } = req.body || {}; + if (!username || !password) { + return res + .status(400) + .json({ message: "Username and password are required" }); + } + + const result = await loginAdmin(username, password); + if (result.success) { + const token = await generateToken({ + username: result.data.username, + role: result.data.role, + }); + + return res.status(200).json({ + message: "Login successful", + first_name: result.data.first_name, + token, + }); + } + + return res.status(401).json({ message: "Invalid credentials" }); +}); + export default router; diff --git a/backend/scheme.sql b/backend/scheme.sql index 7a1a20f..c7b368b 100644 --- a/backend/scheme.sql +++ b/backend/scheme.sql @@ -12,6 +12,17 @@ CREATE TABLE `users` ( UNIQUE KEY `username` (`username`) ); +CREATE TABLE `admins` ( + `id` int NOT NULL AUTO_INCREMENT, + `username` varchar(100) NOT NULL, + `password` varchar(255) NOT NULL, + `first_name` varchar(255) NOT NULL, + `last_name` varchar(255) NOT NULL, + `entry_created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`) +); + CREATE TABLE `loans` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar(100) NOT NULL, diff --git a/backend/services/database.js b/backend/services/database.js index 5326268..f67c3e2 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -319,3 +319,12 @@ export const onReturn = async (loanId) => { } return { success: false }; }; + +export const loginAdmin = async (username, password) => { + const [result] = await pool.query( + "SELECT * FROM admins WHERE username = ? AND password = ?", + [username, password] + ); + if (result.length > 0) return { success: true, data: result[0] }; + return { success: false }; +};