page.tsx•5.63 kB
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
interface User {
id: string;
name: string;
email: string;
role: string;
createdAt: string;
}
export default function UsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const limit = 10;
useEffect(() => {
const fetchUsers = async () => {
try {
setLoading(true);
const res = await fetch(`/api/users?page=${page}&limit=${limit}`);
if (!res.ok) {
throw new Error('Failed to fetch users');
}
const data = await res.json();
setUsers(data.data.users);
setTotal(data.data.total);
} catch (err) {
setError('Error fetching users. Please try again.');
console.error(err);
} finally {
setLoading(false);
}
};
fetchUsers();
}, [page]);
const totalPages = Math.ceil(total / limit);
return (
<div>
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">Users</h1>
<button className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition">
Add New User
</button>
</div>
{loading ? (
<div className="flex justify-center my-12">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-600"></div>
</div>
) : error ? (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{error}
</div>
) : (
<>
<div className="overflow-x-auto bg-white rounded-lg shadow">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Name
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Email
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Role
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Joined
</th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{users.length === 0 ? (
<tr>
<td colSpan={5} className="px-6 py-4 text-center text-gray-500">
No users found.
</td>
</tr>
) : (
users.map((user) => (
<tr key={user.id}>
<td className="px-6 py-4 whitespace-nowrap">
<div className="font-medium text-gray-900">{user.name}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-gray-500">{user.email}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
{user.role}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{new Date(user.createdAt).toLocaleDateString()}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">
<Link href={`/users/${user.id}`} className="text-blue-600 hover:text-blue-900 mr-4">
View
</Link>
<button className="text-red-600 hover:text-red-900">
Delete
</button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
{totalPages > 1 && (
<div className="flex justify-center mt-8">
<nav className="flex items-center space-x-2">
<button
onClick={() => setPage(page > 1 ? page - 1 : 1)}
disabled={page === 1}
className="px-3 py-1 rounded border bg-gray-100 disabled:opacity-50"
>
Previous
</button>
<span className="px-3 py-1">
Page {page} of {totalPages}
</span>
<button
onClick={() => setPage(page < totalPages ? page + 1 : totalPages)}
disabled={page === totalPages}
className="px-3 py-1 rounded border bg-gray-100 disabled:opacity-50"
>
Next
</button>
</nav>
</div>
)}
</>
)}
</div>
);
}