An eye on the login form, to see what is being typed
A guest password is read off a message and typed by hand, and from the login form a mistyped character and a wrong password look identical. Revealed, the field is a text field, so it also turns off capitalisation and autocorrect, which a phone keyboard would otherwise offer for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
179f342a2c
commit
ed64aca63b
+27
-2
@@ -2,6 +2,19 @@ import { FormEvent, useState } from 'react'
|
|||||||
import { api } from './api'
|
import { api } from './api'
|
||||||
import { Spinner } from './ui'
|
import { Spinner } from './ui'
|
||||||
|
|
||||||
|
// Drawn, not typed: 👁 is an emoji on phones, full colour and its own size. Outline strokes
|
||||||
|
// match the glyphs in the rail and the power button.
|
||||||
|
const EyeIcon = (
|
||||||
|
<svg viewBox="0 0 16 16" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||||
|
<path d="M1.2 8S3.9 3.6 8 3.6 14.8 8 14.8 8 12.1 12.4 8 12.4 1.2 8 1.2 8z" /><circle cx="8" cy="8" r="2.1" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
const EyeOffIcon = (
|
||||||
|
<svg viewBox="0 0 16 16" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||||
|
<path d="M1.2 8S3.9 3.6 8 3.6 14.8 8 14.8 8 12.1 12.4 8 12.4 1.2 8 1.2 8z" /><circle cx="8" cy="8" r="2.1" /><path d="M2.4 13.6 13.6 2.4" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
const USER_KEY = 'hamlab.user' // the last account signed in on this device, offered again
|
const USER_KEY = 'hamlab.user' // the last account signed in on this device, offered again
|
||||||
|
|
||||||
export default function Login({ onLogin }: { onLogin: () => void }) {
|
export default function Login({ onLogin }: { onLogin: () => void }) {
|
||||||
@@ -9,6 +22,9 @@ export default function Login({ onLogin }: { onLogin: () => void }) {
|
|||||||
const [pw, setPw] = useState('')
|
const [pw, setPw] = useState('')
|
||||||
const [err, setErr] = useState('')
|
const [err, setErr] = useState('')
|
||||||
const [busy, setBusy] = useState(false)
|
const [busy, setBusy] = useState(false)
|
||||||
|
// Shown on request: a generated password is typed by hand off a message, and a wrong
|
||||||
|
// character looks exactly like a wrong password from here.
|
||||||
|
const [show, setShow] = useState(false)
|
||||||
|
|
||||||
async function submit(e: FormEvent) {
|
async function submit(e: FormEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -26,8 +42,17 @@ export default function Login({ onLogin }: { onLogin: () => void }) {
|
|||||||
<img className="login-logo" src="/icons/logo.png" alt="Ham Lab" width={176} height={176} />
|
<img className="login-logo" src="/icons/logo.png" alt="Ham Lab" width={176} height={176} />
|
||||||
<input type="text" autoFocus={!user} autoComplete="username" placeholder="user" spellCheck={false} autoCapitalize="off" autoCorrect="off"
|
<input type="text" autoFocus={!user} autoComplete="username" placeholder="user" spellCheck={false} autoCapitalize="off" autoCorrect="off"
|
||||||
value={user} onChange={(e) => setUser(e.target.value)} disabled={busy} />
|
value={user} onChange={(e) => setUser(e.target.value)} disabled={busy} />
|
||||||
<input type="password" autoFocus={!!user} autoComplete="current-password" placeholder="password"
|
<div className="pwfield">
|
||||||
value={pw} onChange={(e) => setPw(e.target.value)} disabled={busy} />
|
{/* revealed, the field is a text field, so the keyboard would otherwise offer to
|
||||||
|
capitalise and correct what is typed into it */}
|
||||||
|
<input type={show ? 'text' : 'password'} autoFocus={!!user} autoComplete="current-password" placeholder="password"
|
||||||
|
spellCheck={false} autoCapitalize="off" autoCorrect="off"
|
||||||
|
value={pw} onChange={(e) => setPw(e.target.value)} disabled={busy} />
|
||||||
|
<button type="button" className="pweye" onClick={() => setShow(!show)} disabled={busy}
|
||||||
|
aria-label={show ? 'hide the password' : 'show the password'} aria-pressed={show} title={show ? 'hide' : 'show'}>
|
||||||
|
{show ? EyeOffIcon : EyeIcon}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button disabled={busy || !pw}>{busy && <Spinner />}{busy ? 'Signing in…' : 'Sign in'}</button>
|
<button disabled={busy || !pw}>{busy && <Spinner />}{busy ? 'Signing in…' : 'Sign in'}</button>
|
||||||
{err && <div className="error">{err}</div>}
|
{err && <div className="error">{err}</div>}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|||||||
.login input { padding: 10px 12px; background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 16px; }
|
.login input { padding: 10px 12px; background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 16px; }
|
||||||
.login button, .primary { padding: 10px 12px; background: var(--accent); color: var(--btn-fg); border-radius: 6px; font-weight: 600; }
|
.login button, .primary { padding: 10px 12px; background: var(--accent); color: var(--btn-fg); border-radius: 6px; font-weight: 600; }
|
||||||
.login button:disabled { opacity: .5; }
|
.login button:disabled { opacity: .5; }
|
||||||
|
/* the password field and its show/hide eye, which sits inside the field's right edge */
|
||||||
|
.login .pwfield { position: relative; display: flex; }
|
||||||
|
.login .pwfield input { flex: 1; min-width: 0; padding-right: 44px; }
|
||||||
|
.login button.pweye { position: absolute; right: 1px; top: 1px; bottom: 1px; width: 42px; padding: 0; background: none; color: var(--muted); font-weight: 400; border-radius: 0 6px 6px 0; }
|
||||||
|
.login button.pweye:hover, .login button.pweye[aria-pressed="true"] { color: var(--fg); }
|
||||||
|
.login button.pweye:disabled { opacity: .4; }
|
||||||
.error { color: var(--bad); font-size: 13px; }
|
.error { color: var(--bad); font-size: 13px; }
|
||||||
|
|
||||||
/* layout */
|
/* layout */
|
||||||
|
|||||||
Reference in New Issue
Block a user