diff --git a/frontend_admin/src/App.tsx b/frontend_admin/src/App.tsx index c748668..94e5464 100644 --- a/frontend_admin/src/App.tsx +++ b/frontend_admin/src/App.tsx @@ -1,16 +1,15 @@ import "./App.css"; import Layout from "./layout/Layout"; -import { useUsers } from "./utils/useUsers"; import UserTable from "./components/UserTable"; -import { useEffect } from "react"; +import LoginCard from "./components/LoginCard"; +import { useEffect, useState } from "react"; import { loadTheme } from "./utils/frontendService"; import { myToast } from "./utils/frontendService"; import "react-toastify/dist/ReactToastify.css"; import Cookies from "js-cookie"; +import { AuthContext } from "./utils/context"; function App() { - const users = useUsers(); - useEffect(() => { loadTheme(); if (Cookies.get("token")) { @@ -18,10 +17,31 @@ function App() { } }, []); + const [isAuthenticated, setIsAuthenticated] = useState( + !!Cookies.get("token") + ); + const [showLogin, setShowLogin] = useState(false); + return ( - - - + + + {isAuthenticated ? ( + + ) : ( + <> + setShowLogin(false)} + changeAuth={(loggedIn) => setIsAuthenticated(loggedIn)} + /> + + + Please log in to view the user table. + + + > + )} + + ); } diff --git a/frontend_admin/src/components/Header.tsx b/frontend_admin/src/components/Header.tsx index 1ac2921..4c94146 100644 --- a/frontend_admin/src/components/Header.tsx +++ b/frontend_admin/src/components/Header.tsx @@ -1,46 +1,39 @@ -import { useState } from "react"; -import React from "react"; -import LoginCard from "./LoginCard"; -import { greeting } from "../utils/frontendService"; -import { changeTheme } from "../utils/frontendService"; +import React, { useContext } from "react"; +import Cookies from "js-cookie"; +import { AuthContext } from "../utils/context"; +import { myToast } from "../utils/frontendService"; const Header: React.FC = () => { - const [loginCardVisible, setLoginCardVisible] = useState(false); + const { isAuthenticated, setIsAuthenticated } = useContext(AuthContext); + const firstName = Cookies.get("firstName"); - const closeLoginCard = () => { - setLoginCardVisible(false); + const handleLogout = () => { + Cookies.remove("token"); + Cookies.remove("firstName"); + setIsAuthenticated(false); + myToast("Logged out successfully!", "info"); }; - let loginBtnVal: string = "Hello, " + greeting() + "!"; - return ( - - - - Bikelane Admin Panel - - - - - - changeTheme()} - className="bg-blue-700 dark:bg-gray-800 shadow-md hover:bg-blue-800 dark:hover:bg-gray-700 transition padding px-4 py-2 rounded-md text-white font-semibold" - > - Change Theme - - setLoginCardVisible(true)} - className="bg-blue-700 dark:bg-gray-800 shadow-md hover:bg-blue-800 dark:hover:bg-gray-700 transition padding px-4 py-2 rounded-md text-white font-semibold" - > - {loginBtnVal ?? "Login"} - - - - - + + + 🚲 Bikelane Dashboard + + + {isAuthenticated && ( + <> + + Hello, {firstName || "User"} + + + Logout + + > + )} - {loginCardVisible && } ); }; diff --git a/frontend_admin/src/components/LoginCard.tsx b/frontend_admin/src/components/LoginCard.tsx index 8bf3634..f4a72d7 100644 --- a/frontend_admin/src/components/LoginCard.tsx +++ b/frontend_admin/src/components/LoginCard.tsx @@ -1,93 +1,85 @@ -import React from "react"; +import React, { useState, useContext } from "react"; import Cookies from "js-cookie"; -import { logout, loginUser } from "../utils/userHandler"; -type LoginCardProps = { - onClose: () => void; -}; +import { AuthContext } from "../utils/context"; +import { myToast } from "../utils/frontendService"; + +interface LoginCardProps { + onClose: () => void; + changeAuth?: (loggedIn: boolean) => void; +} + +const LoginCard: React.FC = ({ onClose, changeAuth }) => { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const { setIsAuthenticated } = useContext(AuthContext); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + try { + const response = await fetch("http://localhost:5002/api/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }); + const data = await response.json(); + if (response.ok && data.token) { + Cookies.set("token", data.token); + Cookies.set("firstName", data.user.first_name); + setIsAuthenticated(true); + if (changeAuth) changeAuth(true); + onClose(); + myToast("Login successful!", "success"); + } else { + myToast("Login failed!", "error"); + setError(data.message || "Login fehlgeschlagen"); + } + } catch (err) { + setError("Netzwerkfehler"); + } + }; -const LoginCard: React.FC = ({ onClose }) => { return ( - - - - × - - + + {/* Overlay */} + + {/* Card oben am Dashboard */} + + Login - { - event.preventDefault(); - const formData = new FormData(event.currentTarget); - const username = formData.get("username"); - const password = formData.get("password"); - loginUser(username as string, password as string); - }} - className="space-y-4 text-black dark:text-white" - > - - - Username - - - - - - Password - - - - {Cookies.get("name") ? ( - - ) : ( - - )} - - {Cookies.get("name") ? ( - - Logout - - ) : ( - - Don't have an account?{" "} - - Register here - - + {error && ( + {error} )} - + setUsername(e.target.value)} + className="border border-blue-300 dark:border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400 dark:bg-gray-800 dark:text-white" + required + /> + setPassword(e.target.value)} + className="border border-blue-300 dark:border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400 dark:bg-gray-800 dark:text-white" + required + /> + + + Login + + + ); }; diff --git a/frontend_admin/src/components/UserTable.tsx b/frontend_admin/src/components/UserTable.tsx index 1a2809a..78cc609 100644 --- a/frontend_admin/src/components/UserTable.tsx +++ b/frontend_admin/src/components/UserTable.tsx @@ -1,6 +1,6 @@ import { MoreVertical } from "lucide-react"; -import React, { useEffect, useState } from "react"; -import { deleteUser, updateUserFunc } from "../utils/userHandler.ts"; +import { useEffect, useState } from "react"; +import { useUsers } from "../utils/useUsers.ts"; interface User { id: number; @@ -14,16 +14,25 @@ interface User { } interface UserTableProps { - users: User[]; + isAuthenticated: boolean; } -const UserTable: React.FC = ({ users }) => { +const UserTable: React.FC = ({ isAuthenticated }) => { const [openMenu, setOpenMenu] = useState(null); - const [userList, setUserList] = useState(users); + + const { + users, + refresh: refreshUsers, + setUsers, + deleteUser, + updateUser: updateUserFunc, + } = useUsers(); useEffect(() => { - setUserList(users); - }, [users]); + if (isAuthenticated) { + refreshUsers(); + } + }, [isAuthenticated]); const handleMenuClick = (userId: number) => { setOpenMenu(openMenu === userId ? null : userId); @@ -32,8 +41,10 @@ const UserTable: React.FC = ({ users }) => { const handleMenuClose = () => setOpenMenu(null); const handleInputChange = (id: number, field: keyof User, value: string) => { - setUserList((prev) => - prev.map((user) => (user.id === id ? { ...user, [field]: value } : user)) + setUsers((prevUsers) => + prevUsers.map((user) => + user.id === id ? { ...user, [field]: value } : user + ) ); }; @@ -71,9 +82,9 @@ const UserTable: React.FC = ({ users }) => { - {userList.map((user, idx) => { + {(users ?? []).map((user, idx) => { // If this is one of the last 2 rows, open menu upwards - const openUp = idx >= userList.length - 2; + const openUp = idx >= users.length - 2; return ( = ({ children }) => { - const isLoggedIn = !!Cookies.get("name"); - return ( - {isLoggedIn && ( - <> - {/* Sidebar */} - - {/* Main content */} - - {children} - - > - )} + + + {children} +
- Don't have an account?{" "} - - Register here - -