feat: improve account lockout UX with severity-aware error styling

Login errors now distinguish between wrong-password (red), progressive
lockout warnings (amber, Lock icon), and temporary lockout (amber, Lock
icon) based on the backend detail string. Removes the dead 423 branch
from handleCredentialSubmit — account lockout is now returned as 401
with a descriptive detail message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-18 01:00:42 +08:00
parent 1b868ba503
commit 863e9e2c45

View File

@ -155,11 +155,10 @@ export default function LockScreen() {
// mfaSetupRequired / mfaRequired handled by hook state → activeMode switches automatically
} catch (error: any) {
const status = error?.response?.status;
if (status === 423) {
setLoginError(error.response.data?.detail || 'Account locked. Try again later.');
} else if (status === 403) {
if (status === 403) {
setLoginError(error.response.data?.detail || 'Account is disabled. Contact an administrator.');
} else {
// 401 covers both wrong password and account lockout (backend embeds detail string)
setLoginError(getErrorMessage(error, 'Invalid username or password'));
}
}
@ -519,18 +518,28 @@ export default function LockScreen() {
</div>
</CardHeader>
<CardContent>
{loginError && (
{loginError && (() => {
const isLockWarning =
loginError.includes('remaining') || loginError.includes('temporarily locked');
return (
<div
role="alert"
className={cn(
'flex items-center gap-2 rounded-md border border-red-500/30',
'bg-red-500/10 px-3 py-2 mb-4'
'flex items-center gap-2 rounded-md border px-3 py-2 mb-4',
isLockWarning
? 'bg-amber-500/10 border-amber-500/30'
: 'bg-red-500/10 border-red-500/30'
)}
>
<AlertTriangle className="h-4 w-4 text-red-400 shrink-0" aria-hidden="true" />
<p className="text-xs text-red-400">{loginError}</p>
{isLockWarning
? <Lock className="h-4 w-4 text-amber-400 shrink-0" aria-hidden="true" />
: <AlertTriangle className="h-4 w-4 text-red-400 shrink-0" aria-hidden="true" />}
<p className={cn('text-xs', isLockWarning ? 'text-amber-400' : 'text-red-400')}>
{loginError}
</p>
</div>
)}
);
})()}
<form onSubmit={handleCredentialSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="username" required>Username</Label>