feat: implement user management features including user deletion and role-based access
This commit is contained in:
@@ -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 (
|
||||
<Layout>
|
||||
<table className="min-w-full divide-y divide-gray-200 shadow rounded-lg overflow-hidden">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
#
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Username
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
First name
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Last name
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Email
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Password
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Created
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Role
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-100">
|
||||
{users.map((user: any, idx: number) => (
|
||||
<tr key={user.id} className="hover:bg-blue-50 transition">
|
||||
<td className="px-4 py-2 whitespace-nowrap">{idx + 1}</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">{user.username}</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">{user.first_name}</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">{user.last_name}</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">{user.email}</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">{user.password}</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">{user.created}</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">{user.role}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<UserTable users={users} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
128
frontend_admin/src/components/UserTable.tsx
Normal file
128
frontend_admin/src/components/UserTable.tsx
Normal file
@@ -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<number, boolean> = {};
|
||||
|
||||
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<UserTableProps> = ({ users }) => {
|
||||
const [openMenu, setOpenMenu] = useState<number | null>(null);
|
||||
|
||||
const handleMenuClick = (userId: number) => {
|
||||
setOpenMenu(openMenu === userId ? null : userId);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => setOpenMenu(null);
|
||||
|
||||
return (
|
||||
<table className="min-w-full divide-y divide-gray-200 shadow rounded-lg overflow-hidden">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
<input type="checkbox" name="checkAll" id="checkAll" />
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
#
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Username
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
First name
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Last name
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Email
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Password
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Created
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Role
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-100">
|
||||
{users.map((user, idx) => (
|
||||
<tr key={user.id} className="hover:bg-blue-50 transition">
|
||||
<td className="px-4 py-2 whitespace-nowrap">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="checkUser"
|
||||
id={`checkUser-${user.id}`}
|
||||
checked={!!selectedUsers[user.id]}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">{user.id}</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">
|
||||
<input type="text" className="w-25" value={user.username} />
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">
|
||||
<input type="text" className="w-20" value={user.first_name} />
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">
|
||||
<input type="text" className="w-20" value={user.last_name} />
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">
|
||||
<input type="text" className="w-50" value={user.email} />
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">
|
||||
<input type="text" className="w-25" value={user.password} />
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">{user.created}</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap">
|
||||
<input type="text" className="w-15" value={user.role} />
|
||||
</td>
|
||||
<td className="px-4 py-2 whitespace-nowrap relative">
|
||||
<button
|
||||
onClick={() => handleMenuClick(user.id)}
|
||||
className="p-1 rounded hover:bg-gray-200"
|
||||
aria-label="Open actions menu"
|
||||
>
|
||||
<MoreVertical size={18} />
|
||||
</button>
|
||||
{openMenu === user.id && (
|
||||
<div
|
||||
className="absolute right-0 mt-2 w-32 bg-white border rounded shadow-lg z-10"
|
||||
onMouseLeave={handleMenuClose}
|
||||
>
|
||||
<button className="block w-full text-left px-4 py-2 hover:bg-gray-100">
|
||||
Edit
|
||||
</button>
|
||||
<button onClick={() => deleteUser(user.id)} className="block w-full text-left px-4 py-2 hover:bg-gray-100">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTable;
|
@@ -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));
|
||||
});
|
||||
};
|
||||
|
@@ -8,6 +8,7 @@ export interface User {
|
||||
email: string;
|
||||
password: string;
|
||||
created: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export function useUsers(): User[] {
|
||||
|
Reference in New Issue
Block a user