Add edit user details on admin users page
- Backend: extend PUT /api/users/:id with email and accountStatus; admin-only for role/email/accountStatus; return isClaimed, rucNumber, accountStatus in user responses - Frontend: add Edit button and modal on /admin/users to edit name, email, phone, role, language preference, account status Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,7 +5,8 @@ import { useLanguage } from '@/context/LanguageContext';
|
||||
import { usersApi, User } from '@/lib/api';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { TrashIcon } from '@heroicons/react/24/outline';
|
||||
import Input from '@/components/ui/Input';
|
||||
import { TrashIcon, PencilSquareIcon } from '@heroicons/react/24/outline';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
export default function AdminUsersPage() {
|
||||
@@ -13,6 +14,16 @@ export default function AdminUsersPage() {
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [roleFilter, setRoleFilter] = useState<string>('');
|
||||
const [editingUser, setEditingUser] = useState<User | null>(null);
|
||||
const [editForm, setEditForm] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
role: '' as User['role'],
|
||||
languagePreference: '' as string,
|
||||
accountStatus: '' as string,
|
||||
});
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers();
|
||||
@@ -51,6 +62,51 @@ export default function AdminUsersPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const openEditModal = (user: User) => {
|
||||
setEditingUser(user);
|
||||
setEditForm({
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
phone: user.phone || '',
|
||||
role: user.role,
|
||||
languagePreference: user.languagePreference || '',
|
||||
accountStatus: user.accountStatus || 'active',
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!editingUser) return;
|
||||
|
||||
if (!editForm.name.trim() || editForm.name.trim().length < 2) {
|
||||
toast.error('Name must be at least 2 characters');
|
||||
return;
|
||||
}
|
||||
if (!editForm.email.trim()) {
|
||||
toast.error('Email is required');
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
await usersApi.update(editingUser.id, {
|
||||
name: editForm.name.trim(),
|
||||
email: editForm.email.trim(),
|
||||
phone: editForm.phone.trim() || undefined,
|
||||
role: editForm.role,
|
||||
languagePreference: editForm.languagePreference || undefined,
|
||||
accountStatus: editForm.accountStatus || undefined,
|
||||
} as Partial<User>);
|
||||
toast.success('User updated successfully');
|
||||
setEditingUser(null);
|
||||
loadUsers();
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || 'Failed to update user');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', {
|
||||
year: 'numeric',
|
||||
@@ -162,6 +218,13 @@ export default function AdminUsersPage() {
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => openEditModal(user)}
|
||||
className="p-2 hover:bg-blue-100 text-blue-600 rounded-btn"
|
||||
title="Edit"
|
||||
>
|
||||
<PencilSquareIcon className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(user.id)}
|
||||
className="p-2 hover:bg-red-100 text-red-600 rounded-btn"
|
||||
@@ -178,6 +241,94 @@ export default function AdminUsersPage() {
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Edit User Modal */}
|
||||
{editingUser && (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-lg max-h-[90vh] overflow-y-auto p-6">
|
||||
<h2 className="text-xl font-bold text-primary-dark mb-6">Edit User</h2>
|
||||
|
||||
<form onSubmit={handleEditSubmit} className="space-y-4">
|
||||
<Input
|
||||
label="Name"
|
||||
value={editForm.name}
|
||||
onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
||||
required
|
||||
minLength={2}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Email"
|
||||
type="email"
|
||||
value={editForm.email}
|
||||
onChange={(e) => setEditForm({ ...editForm, email: e.target.value })}
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Phone"
|
||||
value={editForm.phone}
|
||||
onChange={(e) => setEditForm({ ...editForm, phone: e.target.value })}
|
||||
placeholder="Optional"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-primary-dark mb-1.5">Role</label>
|
||||
<select
|
||||
value={editForm.role}
|
||||
onChange={(e) => setEditForm({ ...editForm, role: e.target.value as User['role'] })}
|
||||
className="w-full px-4 py-3 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow focus:border-transparent"
|
||||
>
|
||||
<option value="user">{t('admin.users.roles.user')}</option>
|
||||
<option value="staff">{t('admin.users.roles.staff')}</option>
|
||||
<option value="marketing">{t('admin.users.roles.marketing')}</option>
|
||||
<option value="organizer">{t('admin.users.roles.organizer')}</option>
|
||||
<option value="admin">{t('admin.users.roles.admin')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-primary-dark mb-1.5">Language Preference</label>
|
||||
<select
|
||||
value={editForm.languagePreference}
|
||||
onChange={(e) => setEditForm({ ...editForm, languagePreference: e.target.value })}
|
||||
className="w-full px-4 py-3 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow focus:border-transparent"
|
||||
>
|
||||
<option value="">Not set</option>
|
||||
<option value="en">English</option>
|
||||
<option value="es">Español</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-primary-dark mb-1.5">Account Status</label>
|
||||
<select
|
||||
value={editForm.accountStatus}
|
||||
onChange={(e) => setEditForm({ ...editForm, accountStatus: e.target.value })}
|
||||
className="w-full px-4 py-3 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow focus:border-transparent"
|
||||
>
|
||||
<option value="active">Active</option>
|
||||
<option value="unclaimed">Unclaimed</option>
|
||||
<option value="suspended">Suspended</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 justify-end mt-6 pt-4 border-t border-secondary-light-gray">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setEditingUser(null)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" isLoading={saving}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user