Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions frontend/app/hooks/useUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { getUsers, updateUserRole } from '../lib/api/usersApi';

export function useUsers() {
return useQuery({ queryKey: ['users'], queryFn: getUsers });
}

export function useUpdateUserRole() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, role }: { id: string; role: string }) =>
updateUserRole(id, role),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}
108 changes: 108 additions & 0 deletions frontend/app/users/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
'use client';

import React, { useState } from 'react';
import { useUsers, useUpdateUserRole } from '../hooks/useUsers';
import { Avatar } from '../../components/ui/Avatar';

export default function UsersPage() {
const { data: users = [], isLoading } = useUsers();
const updateRole = useUpdateUserRole();

const [search, setSearch] = useState('');
const [roleFilter, setRoleFilter] = useState('');

if (isLoading) return <div>Loading...</div>;

const filteredUsers = users.filter((u: any) =>
(u.name.toLowerCase().includes(search.toLowerCase()) ||
u.email.toLowerCase().includes(search.toLowerCase())) &&
(roleFilter ? u.role === roleFilter : true)
);

return (
<div className="p-6">
<h1 className="text-xl font-bold mb-4">Users Management</h1>

<div className="flex gap-4 mb-4">
<input
type="text"
placeholder="Search by name or email"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="border p-2"
/>
<select
value={roleFilter}
onChange={(e) => setRoleFilter(e.target.value)}
className="border p-2"
>
<option value="">All Roles</option>
<option value="admin">Admin</option>
<option value="manager">Manager</option>
<option value="staff">Staff</option>
</select>
</div>

<table className="w-full border">
<thead>
<tr>
<th>Avatar + Name</th>
<th>Email</th>
<th>Role</th>
<th>Joined</th>
</tr>
</thead>
<tbody>
{filteredUsers.map((user: any) => (
<tr key={user.id}>
<td className="flex items-center gap-2">
<Avatar
firstName={user.firstName}
lastName={user.lastName}
isCurrentUser={user.isCurrentUser}
/>
{user.name}{' '}
{user.isCurrentUser && (
<span className="ml-2 text-xs bg-gray-200 px-2 py-1 rounded">
You
</span>
)}
</td>
<td>{user.email}</td>
<td>
<select
value={user.role}
disabled={user.isCurrentUser}
onChange={(e) =>
updateRole.mutate({ id: user.id, role: e.target.value })
}
className="border p-1"
>
<option value="admin">Admin</option>
<option value="manager">Manager</option>
<option value="staff">Staff</option>
</select>
</td>
<td>{new Date(user.joinedAt).toLocaleDateString()}</td>
</tr>
))}
</tbody>
</table>

<div className="mt-4">
<h2 className="font-semibold">Role Legend</h2>
<ul>
<li>
<span className="text-red-600">Admin</span> — full access
</li>
<li>
<span className="text-blue-600">Manager</span> — manage teams
</li>
<li>
<span className="text-green-600">Staff</span> — standard user
</li>
</ul>
</div>
</div>
);
}
22 changes: 22 additions & 0 deletions frontend/components/ui/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';

export function Avatar({
firstName,
lastName,
isCurrentUser,
}: {
firstName: string;
lastName: string;
isCurrentUser?: boolean;
}) {
const initials = `${firstName[0]}${lastName[0]}`.toUpperCase();
const bgColor = isCurrentUser ? 'bg-gray-800' : 'bg-gray-400';

return (
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-white ${bgColor}`}
>
{initials}
</div>
);
}
11 changes: 11 additions & 0 deletions frontend/lib/api/usersApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import api from './client';

export async function getUsers() {
const res = await api.get('/api/users');
return res.data;
}

export async function updateUserRole(id: string, role: string) {
const res = await api.patch(`/api/users/${id}/role`, { role });
return res.data;
}
Loading