admin stuff

This commit is contained in:
Jake Kasper
2025-08-29 08:59:10 -04:00
parent 0cc5372e3d
commit 8c728d42d4
6 changed files with 485 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ import {
UserPlusIcon,
TrashIcon,
CogIcon,
PencilIcon,
EnvelopeIcon,
KeyIcon,
ExclamationTriangleIcon
@@ -24,6 +25,15 @@ const AdminUsers = () => {
const [showRoleModal, setShowRoleModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showInviteModal, setShowInviteModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [editFormData, setEditFormData] = useState({
firstName: '',
lastName: '',
email: '',
role: '',
newPassword: '',
confirmPassword: ''
});
const fetchUsers = async (page = 1) => {
try {
@@ -77,6 +87,49 @@ const AdminUsers = () => {
}
};
const handleEditUser = async (e) => {
e.preventDefault();
// Validate password match if password is being changed
if (editFormData.newPassword || editFormData.confirmPassword) {
if (editFormData.newPassword !== editFormData.confirmPassword) {
toast.error('Passwords do not match');
return;
}
if (editFormData.newPassword.length < 6) {
toast.error('Password must be at least 6 characters long');
return;
}
}
try {
const updateData = {
firstName: editFormData.firstName,
lastName: editFormData.lastName,
email: editFormData.email,
role: editFormData.role,
...(editFormData.newPassword && { password: editFormData.newPassword })
};
await adminAPI.updateUser(selectedUser.id, updateData);
toast.success('User updated successfully');
setShowEditModal(false);
setSelectedUser(null);
setEditFormData({
firstName: '',
lastName: '',
email: '',
role: '',
newPassword: '',
confirmPassword: ''
});
fetchUsers(currentPage);
} catch (error) {
console.error('Failed to update user:', error);
toast.error('Failed to update user');
}
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
@@ -199,6 +252,24 @@ const AdminUsers = () => {
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div className="flex items-center space-x-2">
<button
onClick={() => {
setSelectedUser(user);
setEditFormData({
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
role: user.role,
newPassword: '',
confirmPassword: ''
});
setShowEditModal(true);
}}
className="text-green-600 hover:text-green-900"
title="Edit User"
>
<PencilIcon className="h-4 w-4" />
</button>
<button
onClick={() => {
setSelectedUser(user);
@@ -330,6 +401,135 @@ const AdminUsers = () => {
</div>
)}
{/* Edit User Modal */}
{showEditModal && selectedUser && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div className="mt-3">
<h3 className="text-lg font-medium text-gray-900 mb-4">
Edit User: {selectedUser.firstName} {selectedUser.lastName}
</h3>
<form onSubmit={handleEditUser}>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
First Name
</label>
<input
type="text"
value={editFormData.firstName}
onChange={(e) => setEditFormData({ ...editFormData, firstName: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Last Name
</label>
<input
type="text"
value={editFormData.lastName}
onChange={(e) => setEditFormData({ ...editFormData, lastName: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Email
</label>
<input
type="email"
value={editFormData.email}
onChange={(e) => setEditFormData({ ...editFormData, email: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Role
</label>
<select
value={editFormData.role}
onChange={(e) => setEditFormData({ ...editFormData, role: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
<div className="border-t pt-4">
<h4 className="text-sm font-medium text-gray-900 mb-3">
Password Reset (Optional)
</h4>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
New Password
</label>
<input
type="password"
value={editFormData.newPassword}
onChange={(e) => setEditFormData({ ...editFormData, newPassword: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Leave blank to keep current password"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Confirm New Password
</label>
<input
type="password"
value={editFormData.confirmPassword}
onChange={(e) => setEditFormData({ ...editFormData, confirmPassword: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Confirm new password"
/>
</div>
</div>
</div>
</div>
<div className="flex justify-end space-x-3 mt-6">
<button
type="button"
onClick={() => {
setShowEditModal(false);
setEditFormData({
firstName: '',
lastName: '',
email: '',
role: '',
newPassword: '',
confirmPassword: ''
});
}}
className="px-4 py-2 text-sm text-gray-700 bg-gray-200 rounded hover:bg-gray-300"
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 text-sm text-white bg-green-600 rounded hover:bg-green-700"
>
Update User
</button>
</div>
</form>
</div>
</div>
</div>
)}
{/* Invite User Modal */}
{showInviteModal && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">