diff --git a/backend/server.js b/backend/server.js index 8b4a3ed..f1aed34 100644 --- a/backend/server.js +++ b/backend/server.js @@ -50,7 +50,7 @@ app.get("/api/getAllUsers", authenticate, async (req, res) => { if (req.user.role === "admin") { getAllUsers() .then((users) => { - res.status(200).json(users).reload(); + res.status(200).json(users); }) .catch((err) => { console.error("Error fetching users:", err); diff --git a/frontend_admin/package-lock.json b/frontend_admin/package-lock.json index d1f1a9f..a559a16 100644 --- a/frontend_admin/package-lock.json +++ b/frontend_admin/package-lock.json @@ -16,6 +16,7 @@ "lucide-react": "^0.525.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-toastify": "^11.0.5", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.3.5" @@ -4010,6 +4011,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", diff --git a/frontend_admin/package.json b/frontend_admin/package.json index df42f4b..8a1f283 100644 --- a/frontend_admin/package.json +++ b/frontend_admin/package.json @@ -19,6 +19,7 @@ "lucide-react": "^0.525.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-toastify": "^11.0.5", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.3.5" diff --git a/frontend_admin/src/App.tsx b/frontend_admin/src/App.tsx index 0b49c69..3d10364 100644 --- a/frontend_admin/src/App.tsx +++ b/frontend_admin/src/App.tsx @@ -3,7 +3,9 @@ import Layout from "./layout/Layout"; import { useUsers } from "./utils/useUsers"; import UserTable from "./components/UserTable"; import { useEffect } from "react"; -import { loadTheme } from "./utils/functions"; +import { loadTheme } from "./utils/frontendService"; +import { ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; function App() { const users = useUsers(); @@ -15,6 +17,7 @@ function App() { return ( + ); } diff --git a/frontend_admin/src/components/Header.tsx b/frontend_admin/src/components/Header.tsx index b83b48e..1ac2921 100644 --- a/frontend_admin/src/components/Header.tsx +++ b/frontend_admin/src/components/Header.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import React from "react"; import LoginCard from "./LoginCard"; -import { greeting } from "../utils/functions"; -import { changeTheme } from "../utils/functions"; +import { greeting } from "../utils/frontendService"; +import { changeTheme } from "../utils/frontendService"; const Header: React.FC = () => { const [loginCardVisible, setLoginCardVisible] = useState(false); diff --git a/frontend_admin/src/components/LoginCard.tsx b/frontend_admin/src/components/LoginCard.tsx index 8c1060c..8bf3634 100644 --- a/frontend_admin/src/components/LoginCard.tsx +++ b/frontend_admin/src/components/LoginCard.tsx @@ -1,6 +1,6 @@ import React from "react"; import Cookies from "js-cookie"; -import { logout } from "../utils/functions"; +import { logout, loginUser } from "../utils/userHandler"; type LoginCardProps = { onClose: () => void; }; @@ -25,38 +25,7 @@ const LoginCard: React.FC = ({ onClose }) => { const formData = new FormData(event.currentTarget); const username = formData.get("username"); const password = formData.get("password"); - // Example: send login request - await fetch("http://localhost:5002/api/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ username, password }), - }) - .then(async (response) => { - if (response.ok) { - const data = await response.json(); - Cookies.set("token", data.token, { expires: 7 }); - onClose(); - Cookies.set("name", data.user.first_name, { expires: 7 }); - await fetch("http://localhost:5002/api/getAllUsers", { - method: "GET", - headers: { - Authorization: `Bearer ${Cookies.get("token")}`, - }, - }) - .then((res) => res.json()) - .then((users) => { - localStorage.setItem("users", JSON.stringify(users)); - }); - document.location.reload(); - } else if (response.status === 401) { - alert("Invalid credentials"); - } else if (response.status === 403) { - alert("You are not an Admin!"); - } - }) - .catch((error) => { - console.log("Login failed: ", error); - }); + loginUser(username as string, password as string); }} className="space-y-4 text-black dark:text-white" > diff --git a/frontend_admin/src/components/UserTable.tsx b/frontend_admin/src/components/UserTable.tsx index d2a14fe..1a2809a 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/functions.ts"; +import { deleteUser, updateUserFunc } from "../utils/userHandler.ts"; interface User { id: number; @@ -70,100 +70,105 @@ const UserTable: React.FC = ({ users }) => { - - {userList.map((user) => ( - - {user.id} - - - handleInputChange(user.id, "username", e.target.value) - } - /> - - - - handleInputChange(user.id, "first_name", e.target.value) - } - /> - - - - handleInputChange(user.id, "last_name", e.target.value) - } - /> - - - - handleInputChange(user.id, "email", e.target.value) - } - /> - - - - handleInputChange(user.id, "password", e.target.value) - } - /> - - {user.created} - {user.role} - - - {openMenu === user.id && ( -
+ {userList.map((user, idx) => { + // If this is one of the last 2 rows, open menu upwards + const openUp = idx >= userList.length - 2; + return ( + + {user.id} + + + handleInputChange(user.id, "username", e.target.value) + } + /> + + + + handleInputChange(user.id, "first_name", e.target.value) + } + /> + + + + handleInputChange(user.id, "last_name", e.target.value) + } + /> + + + + handleInputChange(user.id, "email", e.target.value) + } + /> + + + + handleInputChange(user.id, "password", e.target.value) + } + /> + + {user.created} + {user.role} + + + {openMenu === user.id && ( +
- Save - - -
- )} - - - ))} + + +
+ )} + + + ); + })} ); diff --git a/frontend_admin/src/utils/frontendService.ts b/frontend_admin/src/utils/frontendService.ts new file mode 100644 index 0000000..35114e4 --- /dev/null +++ b/frontend_admin/src/utils/frontendService.ts @@ -0,0 +1,52 @@ +import Cookies from "js-cookie"; + +export const greeting = () => { + return Cookies.get("name") ?? "Login"; +}; + +export const loadTheme = () => { + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + // Switch to dark theme + console.log("dark"); + document.documentElement.classList.add("dark"); + document.body.classList.add("dark"); + Cookies.set("theme", "dark", { expires: 365 }); + } else { + // Switch to light theme + console.log("light"); + document.documentElement.classList.remove("dark"); + document.body.classList.remove("dark"); + Cookies.set("theme", "light", { expires: 365 }); + } +}; + +export const changeTheme = () => { + if (Cookies.get("theme") === "dark") { + // Switch to light theme + console.log("light"); + removeDarkTheme(); + } else if (Cookies.get("theme") === "light") { + // Switch to dark theme + console.log("dark"); + setDarkTheme(); + } else { + console.error("Theme not set or recognized"); + } +}; + +export const removeDarkTheme = () => { + console.log("Removing dark theme"); + document.documentElement.classList.remove("dark"); + document.body.classList.remove("dark"); + Cookies.set("theme", "light", { expires: 365 }); +}; + +export const setDarkTheme = () => { + console.log("Setting dark theme"); + document.documentElement.classList.add("dark"); + document.body.classList.add("dark"); + Cookies.set("theme", "dark", { expires: 365 }); +}; diff --git a/frontend_admin/src/utils/functions.ts b/frontend_admin/src/utils/userHandler.ts similarity index 67% rename from frontend_admin/src/utils/functions.ts rename to frontend_admin/src/utils/userHandler.ts index 91f2d2c..76a37a4 100644 --- a/frontend_admin/src/utils/functions.ts +++ b/frontend_admin/src/utils/userHandler.ts @@ -1,54 +1,38 @@ import Cookies from "js-cookie"; +import { ToastContainer, toast } from "react-toastify"; -export const greeting = () => { - return Cookies.get("name") ?? "Login"; -}; - -export const loadTheme = () => { - if ( - window.matchMedia && - window.matchMedia("(prefers-color-scheme: dark)").matches - ) { - // Switch to dark theme - console.log("dark"); - document.documentElement.classList.add("dark"); - document.body.classList.add("dark"); - Cookies.set("theme", "dark", { expires: 365 }); - } else { - // Switch to light theme - console.log("light"); - document.documentElement.classList.remove("dark"); - document.body.classList.remove("dark"); - Cookies.set("theme", "light", { expires: 365 }); - } -}; - -export const changeTheme = () => { - if (Cookies.get("theme") === "dark") { - // Switch to light theme - console.log("light"); - removeDarkTheme(); - } else if (Cookies.get("theme") === "light") { - // Switch to dark theme - console.log("dark"); - setDarkTheme(); - } else { - console.error("Theme not set or recognized"); - } -}; - -export const removeDarkTheme = () => { - console.log("Removing dark theme"); - document.documentElement.classList.remove("dark"); - document.body.classList.remove("dark"); - Cookies.set("theme", "light", { expires: 365 }); -}; - -export const setDarkTheme = () => { - console.log("Setting dark theme"); - document.documentElement.classList.add("dark"); - document.body.classList.add("dark"); - Cookies.set("theme", "dark", { expires: 365 }); +export const loginUser = (username: string, password: string) => { + fetch("http://localhost:5002/api/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }) + .then(async (response) => { + if (response.ok) { + const data = await response.json(); + Cookies.set("token", data.token, { expires: 7 }); + Cookies.set("name", data.user.first_name, { expires: 7 }); + await fetch("http://localhost:5002/api/getAllUsers", { + method: "GET", + headers: { + Authorization: `Bearer ${Cookies.get("token")}`, + }, + }) + .then((res) => res.json()) + .then((users) => { + localStorage.setItem("users", JSON.stringify(users)); + }); + document.location.reload(); + toast("Login successful!"); + } else if (response.status === 401) { + toast("Invalid credentials"); + } else if (response.status === 403) { + toast("You are not an Admin!"); + } + }) + .catch((error) => { + console.log("Login failed: ", error); + }); }; export const logout = () => {