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 };
+};