Add reset password to admin panel
This commit is contained in:
@@ -4,7 +4,7 @@ import type { AdminUser, UserRole } from '@/types'
|
||||
import { useToast } from '@/store/toast'
|
||||
import { useConfirm } from '@/store/confirm'
|
||||
import { NeonButton } from '@/components/ui'
|
||||
import { Search, Ban, UserCheck, Shield, ShieldOff, ChevronLeft, ChevronRight, Users, X } from 'lucide-react'
|
||||
import { Search, Ban, UserCheck, Shield, ShieldOff, ChevronLeft, ChevronRight, Users, X, KeyRound } from 'lucide-react'
|
||||
|
||||
export function AdminUsersPage() {
|
||||
const [users, setUsers] = useState<AdminUser[]>([])
|
||||
@@ -17,6 +17,9 @@ export function AdminUsersPage() {
|
||||
const [banDuration, setBanDuration] = useState<string>('permanent')
|
||||
const [banCustomDate, setBanCustomDate] = useState('')
|
||||
const [banning, setBanning] = useState(false)
|
||||
const [resetPasswordUser, setResetPasswordUser] = useState<AdminUser | null>(null)
|
||||
const [newPassword, setNewPassword] = useState('')
|
||||
const [resettingPassword, setResettingPassword] = useState(false)
|
||||
|
||||
const toast = useToast()
|
||||
const confirm = useConfirm()
|
||||
@@ -120,6 +123,24 @@ export function AdminUsersPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleResetPassword = async () => {
|
||||
if (!resetPasswordUser || !newPassword.trim() || newPassword.length < 6) return
|
||||
|
||||
setResettingPassword(true)
|
||||
try {
|
||||
const updated = await adminApi.resetUserPassword(resetPasswordUser.id, newPassword)
|
||||
setUsers(users.map(u => u.id === updated.id ? updated : u))
|
||||
toast.success(`Пароль ${updated.nickname} сброшен`)
|
||||
setResetPasswordUser(null)
|
||||
setNewPassword('')
|
||||
} catch (err) {
|
||||
console.error('Failed to reset password:', err)
|
||||
toast.error('Ошибка сброса пароля')
|
||||
} finally {
|
||||
setResettingPassword(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
@@ -265,6 +286,14 @@ export function AdminUsersPage() {
|
||||
<Shield className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => setResetPasswordUser(user)}
|
||||
className="p-2 text-yellow-400 hover:bg-yellow-500/20 rounded-lg transition-colors"
|
||||
title="Сбросить пароль"
|
||||
>
|
||||
<KeyRound className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -393,6 +422,71 @@ export function AdminUsersPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Reset Password Modal */}
|
||||
{resetPasswordUser && (
|
||||
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50">
|
||||
<div className="glass rounded-2xl p-6 max-w-md w-full mx-4 border border-dark-600 shadow-2xl">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-semibold text-white flex items-center gap-2">
|
||||
<KeyRound className="w-5 h-5 text-yellow-400" />
|
||||
Сбросить пароль {resetPasswordUser.nickname}
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
setResetPasswordUser(null)
|
||||
setNewPassword('')
|
||||
}}
|
||||
className="p-1.5 text-gray-400 hover:text-white rounded-lg hover:bg-dark-600/50 transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Новый пароль
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
placeholder="Минимум 6 символов"
|
||||
className="w-full bg-dark-700/50 border border-dark-600 rounded-xl px-4 py-2.5 text-white placeholder:text-gray-500 focus:outline-none focus:border-accent-500/50 transition-colors"
|
||||
/>
|
||||
{newPassword && newPassword.length < 6 && (
|
||||
<p className="mt-2 text-sm text-red-400">Пароль должен быть минимум 6 символов</p>
|
||||
)}
|
||||
{resetPasswordUser.telegram_id && (
|
||||
<p className="mt-2 text-sm text-gray-400">
|
||||
Пользователь получит уведомление в Telegram о смене пароля
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 justify-end">
|
||||
<NeonButton
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setResetPasswordUser(null)
|
||||
setNewPassword('')
|
||||
}}
|
||||
>
|
||||
Отмена
|
||||
</NeonButton>
|
||||
<NeonButton
|
||||
color="neon"
|
||||
onClick={handleResetPassword}
|
||||
disabled={!newPassword.trim() || newPassword.length < 6 || resettingPassword}
|
||||
isLoading={resettingPassword}
|
||||
icon={<KeyRound className="w-4 h-4" />}
|
||||
>
|
||||
Сбросить
|
||||
</NeonButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user