Add password reset for users in admin panel

This commit is contained in:
2026-01-28 18:55:03 +00:00
parent 621559ee22
commit ed7fe15126
3 changed files with 84 additions and 11 deletions

4
dist/index.html vendored
View File

@@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Todo App - Task management made simple" />
<title>Todo App</title>
<script type="module" crossorigin src="/assets/index-BbNHrKUH.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-oe9qB6TG.css">
<script type="module" crossorigin src="/assets/index-WUccXayL.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CR9gBHOO.css">
</head>
<body>
<div id="root"></div>

View File

@@ -246,6 +246,13 @@ class ApiClient {
});
}
async resetUserPassword(id: string, newPassword: string): Promise<void> {
await this.fetch(`/admin/users/${id}/reset-password`, {
method: 'POST',
body: JSON.stringify({ newPassword }),
});
}
async deleteUser(id: string): Promise<void> {
await this.fetch(`/admin/users/${id}`, { method: 'DELETE' });
}

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import { Users, Mail, Plus, Trash2, Copy, Check } from 'lucide-react';
import { Users, Mail, Plus, Trash2, Copy, Check, KeyRound } from 'lucide-react';
import { api } from '@/lib/api';
import { useAuthStore } from '@/stores/auth';
import type { User, Invite } from '@/types';
@@ -70,6 +70,22 @@ export function AdminPage() {
}
};
const [resetUserId, setResetUserId] = useState<string | null>(null);
const [newPassword, setNewPassword] = useState('');
const [resetSuccess, setResetSuccess] = useState('');
const handleResetPassword = async (userId: string) => {
if (!newPassword || newPassword.length < 8) return;
try {
await api.resetUserPassword(userId, newPassword);
setResetSuccess(userId);
setNewPassword('');
setTimeout(() => { setResetSuccess(''); setResetUserId(null); }, 2000);
} catch (error) {
console.error('Failed to reset password:', error);
}
};
const handleDeleteUser = async (userId: string) => {
if (!confirm('Are you sure you want to delete this user?')) return;
@@ -188,14 +204,64 @@ export function AdminPage() {
{new Date(user.createdAt).toLocaleDateString()}
</td>
<td className="px-4 py-3">
{user.id !== currentUser?.id && user.role !== 'service' && (
<button
onClick={() => handleDeleteUser(user.id)}
className="p-1 text-gray-400 hover:text-red-500"
>
<Trash2 className="w-4 h-4" />
</button>
)}
<div className="flex items-center gap-1">
{user.id !== currentUser?.id && (
<>
{resetUserId === user.id ? (
<div className="flex items-center gap-1">
{resetSuccess === user.id ? (
<span className="text-xs text-green-600"> Reset!</span>
) : (
<>
<input
type="text"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
placeholder="New password (8+ chars)"
className="text-xs border border-gray-200 rounded px-2 py-1 w-40"
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter') handleResetPassword(user.id);
if (e.key === 'Escape') { setResetUserId(null); setNewPassword(''); }
}}
/>
<button
onClick={() => handleResetPassword(user.id)}
disabled={newPassword.length < 8}
className="text-xs px-2 py-1 bg-blue-600 text-white rounded disabled:opacity-50"
>
Set
</button>
<button
onClick={() => { setResetUserId(null); setNewPassword(''); }}
className="text-xs px-1 py-1 text-gray-400 hover:text-gray-600"
>
</button>
</>
)}
</div>
) : (
<button
onClick={() => setResetUserId(user.id)}
className="p-1 text-gray-400 hover:text-blue-500"
title="Reset password"
>
<KeyRound className="w-4 h-4" />
</button>
)}
{user.role !== 'service' && resetUserId !== user.id && (
<button
onClick={() => handleDeleteUser(user.id)}
className="p-1 text-gray-400 hover:text-red-500"
title="Delete user"
>
<Trash2 className="w-4 h-4" />
</button>
)}
</>
)}
</div>
</td>
</tr>
))}