From d36fe88e1668f9a17c74263795a46eac57c33a16 Mon Sep 17 00:00:00 2001 From: Dmitrii Iurco Date: Mon, 4 May 2026 06:05:46 -0400 Subject: [PATCH] feat(ui): add show/hide password toggle to login and account settings Login.jsx: - Eye/EyeOff toggle on the password field - Locked account error now shows exact minutes remaining ("Try again in 3 minutes") instead of generic "Try again later" AccountSettings.jsx: - PasswordInput component wraps all 4 password fields with individual eye toggles (current password, new password, confirm, admin reset) Co-Authored-By: Claude Sonnet 4.6 --- webui/src/pages/AccountSettings.jsx | 38 ++++++++++++++++++++++------- webui/src/pages/Login.jsx | 36 ++++++++++++++++++++------- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/webui/src/pages/AccountSettings.jsx b/webui/src/pages/AccountSettings.jsx index 5616e39..f65716b 100644 --- a/webui/src/pages/AccountSettings.jsx +++ b/webui/src/pages/AccountSettings.jsx @@ -1,8 +1,32 @@ import React, { useState, useEffect } from 'react'; -import { CheckCircle, XCircle, AlertTriangle } from 'lucide-react'; +import { CheckCircle, XCircle, AlertTriangle, Eye, EyeOff } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; import { authAPI } from '../services/api'; +function PasswordInput({ value, onChange, autoComplete, required, className }) { + const [show, setShow] = useState(false); + return ( +
+ + +
+ ); +} + export default function AccountSettings() { const { user, changePassword } = useAuth(); @@ -99,8 +123,7 @@ export default function AccountSettings() {
- setOldPassword(e.target.value)} autoComplete="current-password" @@ -110,8 +133,7 @@ export default function AccountSettings() {
- setNewPassword(e.target.value)} autoComplete="new-password" @@ -123,8 +145,7 @@ export default function AccountSettings() {
- setConfirmPassword(e.target.value)} autoComplete="new-password" @@ -177,8 +198,7 @@ export default function AccountSettings() {
- { setAdminNewPw(e.target.value); setAdminError(''); }} autoComplete="new-password" diff --git a/webui/src/pages/Login.jsx b/webui/src/pages/Login.jsx index a0f94f6..805c3e1 100644 --- a/webui/src/pages/Login.jsx +++ b/webui/src/pages/Login.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { Eye, EyeOff } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; export default function Login() { @@ -9,6 +10,7 @@ export default function Login() { const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); const handleSubmit = async e => { e.preventDefault(); @@ -19,7 +21,13 @@ export default function Login() { navigate('/', { replace: true }); } catch (err) { if (err.response?.status === 423) { - setError('Account locked. Too many failed attempts. Try again later.'); + const lockedUntil = err.response?.data?.locked_until; + if (lockedUntil) { + const mins = Math.ceil((new Date(lockedUntil + 'Z') - Date.now()) / 60000); + setError(`Account locked. Try again in ${mins} minute${mins !== 1 ? 's' : ''}.`); + } else { + setError('Account locked. Too many failed attempts. Try again later.'); + } } else { setError('Invalid username or password.'); } @@ -46,14 +54,24 @@ export default function Login() {
- setPassword(e.target.value)} - className="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500" - required - /> +
+ setPassword(e.target.value)} + className="w-full bg-gray-800 border border-gray-600 rounded px-3 py-2 pr-9 text-white text-sm focus:outline-none focus:border-blue-500" + required + /> + +
{error &&

{error}

}