implement admin panel with login functionality and dashboard layout
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import "./App.css";
|
||||
import Layout from "./Layout/Layout";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<p>Admin panel</p>
|
||||
<Layout>
|
||||
<p></p>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
60
admin/src/Layout/Dashboard.tsx
Normal file
60
admin/src/Layout/Dashboard.tsx
Normal file
@@ -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<DashboardProps> = ({ onLogout }) => {
|
||||
const userName = localStorage.getItem("userName") || "Admin";
|
||||
|
||||
const [activeView, setActiveView] = useState("");
|
||||
|
||||
return (
|
||||
<Flex h="100vh">
|
||||
<Sidebar
|
||||
viewAusleihen={() => setActiveView("Ausleihen")}
|
||||
viewGegenstaende={() => setActiveView("Gegenstände")}
|
||||
viewSchliessfaecher={() => setActiveView("Schließfächer")}
|
||||
viewUser={() => setActiveView("User")}
|
||||
/>
|
||||
<Box flex="1" display="flex" flexDirection="column">
|
||||
<Flex
|
||||
as="header"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
px={6}
|
||||
py={4}
|
||||
borderBottom="1px"
|
||||
borderColor="gray.200"
|
||||
bg="gray.900"
|
||||
>
|
||||
<Heading size="md">Dashboard</Heading>
|
||||
<Flex align="center" gap={6}>
|
||||
<Text fontSize="sm" color="white">
|
||||
Willkommen {userName}, im Admin-Dashboard!
|
||||
</Text>
|
||||
<Button variant="solid" onClick={onLogout}>
|
||||
Logout
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box as="main" flex="1" p={6}>
|
||||
{activeView === "" && <Text>Bitte wählen Sie eine Ansicht aus.</Text>}
|
||||
{activeView === "User" && <UserTable />}
|
||||
{activeView === "Ausleihen" && <LoanTable />}
|
||||
{activeView === "Gegenstände" && <ItemTable />}
|
||||
{activeView === "Schließfächer" && <LockerTable />}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
39
admin/src/Layout/Layout.tsx
Normal file
39
admin/src/Layout/Layout.tsx
Normal file
@@ -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<LayoutProps> = ({ children }) => {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (Cookies.get("token")) {
|
||||
setIsLoggedIn(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleLogout = () => {
|
||||
Cookies.remove("token");
|
||||
setIsLoggedIn(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<main>
|
||||
{isLoggedIn ? (
|
||||
<Dashboard onLogout={() => handleLogout()} />
|
||||
) : (
|
||||
<Login onSuccess={() => setIsLoggedIn(true)} />
|
||||
)}
|
||||
</main>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
62
admin/src/Layout/Login.tsx
Normal file
62
admin/src/Layout/Login.tsx
Normal file
@@ -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 (
|
||||
<Card.Root maxW="sm">
|
||||
<Card.Header>
|
||||
<Card.Title>Login</Card.Title>
|
||||
<Card.Description>
|
||||
Bitte unten Ihre Admin Zugangsdaten eingeben.
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Stack gap="4" w="full">
|
||||
<Field.Root>
|
||||
<Field.Label>username</Field.Label>
|
||||
<Input
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
<Field.Root>
|
||||
<Field.Label>password</Field.Label>
|
||||
<Input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</Field.Root>
|
||||
</Stack>
|
||||
</Card.Body>
|
||||
<Card.Footer justifyContent="flex-end">
|
||||
{isError && <MyAlert title={errorMsg} description={errorDsc} />}
|
||||
<Button onClick={() => handleLogin()} variant="solid">
|
||||
Login
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
81
admin/src/Layout/Sidebar.tsx
Normal file
81
admin/src/Layout/Sidebar.tsx
Normal file
@@ -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<SidebarProps> = ({
|
||||
viewAusleihen,
|
||||
viewGegenstaende,
|
||||
viewSchliessfaecher,
|
||||
viewUser,
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
as="aside"
|
||||
w="260px"
|
||||
minH="100vh"
|
||||
bg="gray.800"
|
||||
color="gray.100"
|
||||
px={6}
|
||||
py={8}
|
||||
borderRight="1px solid"
|
||||
borderColor="gray.700"
|
||||
>
|
||||
<Flex direction="column" h="full">
|
||||
<Heading size="md" mb={8} letterSpacing="wide">
|
||||
Borrow System
|
||||
</Heading>
|
||||
|
||||
<VStack align="stretch" gap={4} fontSize="sm">
|
||||
<Link
|
||||
px={3}
|
||||
py={2}
|
||||
rounded="md"
|
||||
_hover={{ bg: "gray.700", textDecoration: "none" }}
|
||||
onClick={viewUser}
|
||||
>
|
||||
User
|
||||
</Link>
|
||||
<Link
|
||||
px={3}
|
||||
py={2}
|
||||
rounded="md"
|
||||
_hover={{ bg: "gray.700", textDecoration: "none" }}
|
||||
onClick={viewAusleihen}
|
||||
>
|
||||
Ausleihen
|
||||
</Link>
|
||||
<Link
|
||||
px={3}
|
||||
py={2}
|
||||
rounded="md"
|
||||
_hover={{ bg: "gray.700", textDecoration: "none" }}
|
||||
onClick={viewGegenstaende}
|
||||
>
|
||||
Gegenstände
|
||||
</Link>
|
||||
<Link
|
||||
px={3}
|
||||
py={2}
|
||||
rounded="md"
|
||||
_hover={{ bg: "gray.700", textDecoration: "none" }}
|
||||
onClick={viewSchliessfaecher}
|
||||
>
|
||||
Schließfächer
|
||||
</Link>
|
||||
</VStack>
|
||||
|
||||
<Box mt="auto" pt={8} fontSize="xs" color="gray.500">
|
||||
<Text>© Made with ❤️ by Theis Gaedigk</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
7
admin/src/components/ItemTable.tsx
Normal file
7
admin/src/components/ItemTable.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const ItemTable: React.FC = () => {
|
||||
return <>Item Table</>;
|
||||
};
|
||||
|
||||
export default ItemTable;
|
7
admin/src/components/LoanTable.tsx
Normal file
7
admin/src/components/LoanTable.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const LoanTable: React.FC = () => {
|
||||
return <>Loan Table</>;
|
||||
};
|
||||
|
||||
export default LoanTable;
|
7
admin/src/components/LockerTable.tsx
Normal file
7
admin/src/components/LockerTable.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const LockerTable: React.FC = () => {
|
||||
return <>Locker Table</>;
|
||||
};
|
||||
|
||||
export default LockerTable;
|
7
admin/src/components/UserTable.tsx
Normal file
7
admin/src/components/UserTable.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const UserTable: React.FC = () => {
|
||||
return <>User Table</>;
|
||||
};
|
||||
|
||||
export default UserTable;
|
21
admin/src/components/myChakra/MyAlert.tsx
Normal file
21
admin/src/components/myChakra/MyAlert.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { Alert } from "@chakra-ui/react";
|
||||
|
||||
type MyAlertProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const MyAlert: React.FC<MyAlertProps> = ({ title, description }) => {
|
||||
return (
|
||||
<Alert.Root status="error">
|
||||
<Alert.Indicator />
|
||||
<Alert.Content>
|
||||
<Alert.Title>{title}</Alert.Title>
|
||||
<Alert.Description>{description}</Alert.Description>
|
||||
</Alert.Content>
|
||||
</Alert.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyAlert;
|
43
admin/src/utils/loginUser.ts
Normal file
43
admin/src/utils/loginUser.ts
Normal file
@@ -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<LoginResult> => {
|
||||
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.",
|
||||
};
|
||||
}
|
||||
};
|
18
admin/src/utils/toastify.ts
Normal file
18
admin/src/utils/toastify.ts
Normal file
@@ -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);
|
||||
};
|
Reference in New Issue
Block a user