diff --git a/backend/server.js b/backend/server.js index 0317118..0c07ffc 100644 --- a/backend/server.js +++ b/backend/server.js @@ -23,7 +23,7 @@ app.use(cookieParser()); app.post("/api/login", async (req, res) => { try { const result = await loginUser(req.body.username, req.body.password); - if (result.success && result.role === "admin") { + if (result.success && result.user.role === "admin") { const userToken = await generateToken({ role: result.user.role, username: result.user.username, @@ -35,8 +35,10 @@ app.post("/api/login", async (req, res) => { token: userToken, ...result, }); - } else if (result.success && result.role === "user") { - + } else if (result.success && result.user.role === "user") { + // PROBLEM BELOW DOESNT WORK + // FIX LATER + res.redirect("http://localhost:5003"); } else { res.status(401).json(result, { message: "Invalid credentials" }); } @@ -68,6 +70,28 @@ app.get("/api/getAllUsers", authenticate, async (req, res) => { } }); +app.post("/api/deleteUser", authenticate, async (req, res) => { + if (req.user.role === "admin") { + deleteUser(req.body.id) + .then((result) => { + if (result.success) { + res.status(200).json(result); + } else { + throw new Error("Failed to delete user"); + } + }) + .catch((err) => { + console.error("Error deleting user:", err); + res + .status(500) + .json({ success: false, message: "Internal server error" }); + }); + console.log("User deleted successfully"); + } else { + console.log("Access denied for user role"); + } +}); + app.listen(port, () => { console.log(`Express backend server is running at http://localhost:${port}`); }); diff --git a/backend/services/database.js b/backend/services/database.js index d9e7167..160a7b4 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -22,7 +22,7 @@ export async function loginUser(username, password) { ); // If a user is found, return success and user data - if (result.length > 0) { + if (result.length > 0 && result[0].role === "admin") { console.log("User found: ", result[0].username, " ", result[0].id); return { success: true, user: result[0] }; } else { @@ -95,18 +95,12 @@ export async function updateUser( } // Function to delete a user from the database -export async function deleteUser( - username, - first_name, - last_name, - password, - email -) { +export async function deleteUser(id) { try { // Delete user based on username and password const [result] = await pool.query( - "DELETE FROM users WHERE username = ? AND password = ?", - [username, password] + "DELETE FROM users WHERE id = ?", + [id] ); const resultOfquery = result.affectedRows; diff --git a/docker-compose.yml b/docker-compose.yml index 40cc54a..8c08b5b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -52,4 +52,4 @@ services: - "3307:3306" volumes: - mysql-data: + mysql-data: \ No newline at end of file diff --git a/frontend_admin/package-lock.json b/frontend_admin/package-lock.json index 72f0496..d1f1a9f 100644 --- a/frontend_admin/package-lock.json +++ b/frontend_admin/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@tailwindcss/vite": "^4.1.11", + "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "js-cookie": "^3.0.5", @@ -16,6 +17,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "tailwind-merge": "^3.3.1", + "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.3.5" }, "devDependencies": { @@ -1843,6 +1845,39 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4167,6 +4202,15 @@ "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", "license": "MIT" }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/tapable": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", diff --git a/frontend_admin/package.json b/frontend_admin/package.json index 4660edc..df42f4b 100644 --- a/frontend_admin/package.json +++ b/frontend_admin/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@tailwindcss/vite": "^4.1.11", + "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "js-cookie": "^3.0.5", @@ -19,6 +20,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "tailwind-merge": "^3.3.1", + "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.3.5" }, "devDependencies": { diff --git a/frontend_admin/src/App.tsx b/frontend_admin/src/App.tsx index 0ca6ccd..e73148b 100644 --- a/frontend_admin/src/App.tsx +++ b/frontend_admin/src/App.tsx @@ -1,56 +1,14 @@ import "./App.css"; import Layout from "./layout/Layout"; import { useUsers } from "./utils/useUsers"; +import UserTable from "./components/UserTable"; function App() { const users = useUsers(); return ( - - - - - - - - - - - - - - - {users.map((user: any, idx: number) => ( - - - - - - - - - - - ))} - -
- # - - Username - - First name - - Last name - - Email - - Password - - Created - - Role -
{idx + 1}{user.username}{user.first_name}{user.last_name}{user.email}{user.password}{user.created}{user.role}
+
); } diff --git a/frontend_admin/src/components/UserTable.tsx b/frontend_admin/src/components/UserTable.tsx new file mode 100644 index 0000000..caeb30d --- /dev/null +++ b/frontend_admin/src/components/UserTable.tsx @@ -0,0 +1,128 @@ +import { MoreVertical } from "lucide-react"; +import React from "react"; +import { useEffect, useState } from "react"; +import { deleteUser } from "../utils/functions.ts"; + +const selectedUsers: Record = {}; + +interface User { + id: number; + username: string; + first_name: string; + last_name: string; + email: string; + password: string; + created: string; + role: string; +} + +interface UserTableProps { + users: User[]; +} + +const UserTable: React.FC = ({ users }) => { + const [openMenu, setOpenMenu] = useState(null); + + const handleMenuClick = (userId: number) => { + setOpenMenu(openMenu === userId ? null : userId); + }; + + const handleMenuClose = () => setOpenMenu(null); + + return ( + + + + + + + + + + + + + + + + + {users.map((user, idx) => ( + + + + + + + + + + + + + ))} + +
+ + + # + + Username + + First name + + Last name + + Email + + Password + + Created + + Role + + Actions +
+ + {user.id} + + + + + + + + + + {user.created} + + + + {openMenu === user.id && ( +
+ + +
+ )} +
+ ); +}; + +export default UserTable; diff --git a/frontend_admin/src/utils/functions.ts b/frontend_admin/src/utils/functions.ts index 8af4362..cc9fef8 100644 --- a/frontend_admin/src/utils/functions.ts +++ b/frontend_admin/src/utils/functions.ts @@ -10,3 +10,38 @@ export const logout = () => { localStorage.removeItem("users"); window.location.reload(); }; + +export const deleteUser = (id: number) => { + fetch("http://localhost:5002/api/deleteUser", { + method: "POST", + body: JSON.stringify({ id: id }), + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${Cookies.get("token")}`, + }, + }) + .then((response) => { + if (response.ok) { + localStorage.removeItem("users"); + document.location.reload(); + } else { + alert("Failed to delete user"); + } + }) + .catch((error) => { + console.log("Error deleting user: ", error); + }); +}; + +export const fetchUsers = async () => { + 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)); + }); +}; diff --git a/frontend_admin/src/utils/useUsers.ts b/frontend_admin/src/utils/useUsers.ts index 4b04f30..0845562 100644 --- a/frontend_admin/src/utils/useUsers.ts +++ b/frontend_admin/src/utils/useUsers.ts @@ -8,6 +8,7 @@ export interface User { email: string; password: string; created: string; + role: string; } export function useUsers(): User[] { diff --git a/frontend_user/src/App.tsx b/frontend_user/src/App.tsx index 3d7ded3..7f3232d 100644 --- a/frontend_user/src/App.tsx +++ b/frontend_user/src/App.tsx @@ -1,35 +1,11 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' +import "./App.css"; function App() { - const [count, setCount] = useState(0) - return ( <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

+

Hello world

- ) + ); } -export default App +export default App;