Files
khalijpay-issuer/src/pages/Users.jsx

271 lines
8.9 KiB
JavaScript

import React, { useEffect, useMemo, useState } from 'react';
import DataTable from '../components/DataTable';
import { usersAPI } from '../services/api';
import { Plus, Trash2, Search, Pencil, ShieldOff, RefreshCcw } from 'lucide-react';
const Users = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [filter, setFilter] = useState('');
const [isModalOpen, setIsModalOpen] = useState(false);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [editingUser, setEditingUser] = useState(null);
const fetchUsers = async (q = '') => {
try {
setLoading(true);
const list = await usersAPI.list(q);
setUsers(Array.isArray(list) ? list : []);
} catch (e) {
setError('Failed to load users');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchUsers();
}, []);
const onAddUser = async (e) => {
e.preventDefault();
if (!firstName.trim() || !lastName.trim() || !email.trim()) return;
try {
await usersAPI.create({ firstName, lastName, email });
setFirstName('');
setLastName('');
setEmail('');
await fetchUsers(filter);
setIsModalOpen(false);
} catch (_) {
setError('Failed to create user');
}
};
const onDelete = async (id) => {
if (!window.confirm('Are you sure you want to delete this user?')) return;
await usersAPI.remove(id);
await fetchUsers(filter);
};
const openEdit = (user) => {
setEditingUser(user);
setFirstName(user.firstName || '');
setLastName(user.lastName || '');
setEmail(user.email || '');
setIsEditModalOpen(true);
};
const onUpdateUser = async (e) => {
e.preventDefault();
if (!editingUser) return;
try {
await usersAPI.update(editingUser.id, { firstName, lastName, email });
await fetchUsers(filter);
setIsEditModalOpen(false);
setEditingUser(null);
setFirstName('');
setLastName('');
setEmail('');
} catch (_) {
setError('Failed to update user');
}
};
const onToggleActivation = async (id) => {
await usersAPI.toggleActivation(id);
await fetchUsers(filter);
};
const onResetPassword = async (id) => {
await usersAPI.resetPassword(id);
alert('Password reset successfully.');
};
const columns = useMemo(() => [
{ key: 'firstName', header: 'First Name' },
{ key: 'lastName', header: 'Last Name' },
{ key: 'email', header: 'Email' },
{ key: 'isActive', header: 'Active', render: (val) => val ? '✅' : '❌' },
{
key: 'actions',
header: 'Actions',
render: (_val, row) => (
<div className="flex items-center gap-x-2">
<button
onClick={() => openEdit(row)}
className="inline-flex items-center px-3 py-1 text-sm rounded-md bg-blue-100 text-blue-700 hover:opacity-90"
>
<Pencil className="h-4 w-4 mr-1" /> Edit
</button>
<button
onClick={() => onDelete(row.id)}
className="inline-flex items-center px-3 py-1 text-sm rounded-md bg-red-100 text-red-700 hover:opacity-90"
>
<Trash2 className="h-4 w-4 mr-1" /> Delete
</button>
<button
onClick={() => onToggleActivation(row.id)}
className="inline-flex items-center px-3 py-1 text-sm rounded-md bg-gray-100 text-gray-700 hover:opacity-90"
>
<ShieldOff className="h-4 w-4 mr-1" /> Toggle
</button>
<button
onClick={() => onResetPassword(row.id)}
className="inline-flex items-center px-3 py-1 text-sm rounded-md bg-green-100 text-green-700 hover:opacity-90"
>
<RefreshCcw className="h-4 w-4 mr-1" /> Reset
</button>
</div>
),
},
], []);
return (
<div className="p-6">
<div className="mb-6 flex items-start justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Users</h1>
<p className="text-gray-600 dark:text-gray-400">
Manage users: add, edit, activate/deactivate and reset password
</p>
</div>
<button
onClick={() => setIsModalOpen(true)}
className="btn-primary inline-flex items-center"
>
<Plus className="h-4 w-4 mr-2" /> Add User
</button>
</div>
{/* Add Modal */}
{isModalOpen && (
<>
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40"
onClick={() => setIsModalOpen(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="w-full max-w-md card p-6 relative">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Add User</h3>
<form className="space-y-4" onSubmit={onAddUser}>
<input
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
className="w-full p-2 border rounded-lg"
placeholder="First Name"
/>
<input
value={lastName}
onChange={(e) => setLastName(e.target.value)}
className="w-full p-2 border rounded-lg"
placeholder="Last Name"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-2 border rounded-lg"
placeholder="Email"
/>
<div className="flex justify-end gap-x-2">
<button
type="button"
onClick={() => setIsModalOpen(false)}
className="px-4 py-2 border rounded-lg"
>
Cancel
</button>
<button type="submit" className="btn-primary px-4 py-2 rounded-lg">
Add
</button>
</div>
</form>
</div>
</div>
</>
)}
{/* Edit Modal */}
{isEditModalOpen && (
<>
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40"
onClick={() => setIsEditModalOpen(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="w-full max-w-md card p-6 relative">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Edit User</h3>
<form className="space-y-4" onSubmit={onUpdateUser}>
<input
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
className="w-full p-2 border rounded-lg"
placeholder="First Name"
/>
<input
value={lastName}
onChange={(e) => setLastName(e.target.value)}
className="w-full p-2 border rounded-lg"
placeholder="Last Name"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-2 border rounded-lg"
placeholder="Email"
/>
<div className="flex justify-end gap-x-2">
<button
type="button"
onClick={() => setIsEditModalOpen(false)}
className="px-4 py-2 border rounded-lg"
>
Cancel
</button>
<button type="submit" className="btn-primary px-4 py-2 rounded-lg">
Update
</button>
</div>
</form>
</div>
</div>
</>
)}
{/* Filter */}
<div className="card p-4 mb-4">
<div className="relative max-w-md">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<input
value={filter}
onChange={async (e) => {
const v = e.target.value;
setFilter(v);
await fetchUsers(v);
}}
placeholder="Filter by name or email"
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
/>
</div>
</div>
<DataTable
data={users}
columns={columns}
loading={loading}
searchable={false}
/>
{error && (
<div className="mt-4 text-sm text-red-600">{error}</div>
)}
</div>
);
};
export default Users;