LoVisual/frontend/src/features/auth/forms/LoginForm.tsx
loki5512344 e00ea8eb11
feat: frontend perf and a11y pass, plus mod visuals, hero main menu, Simple Voice Chat and vanilla chat autocomplete
Frontend:
- replace the i18next stack with a small typed i18n module
- hero clouds removed, llms.txt, HUD widget ARIA fixes and tests

Mod:
- TargetESP constellation mode; tracker and colors extracted
- JumpCircles: 7 new shapes, shared animation and color pipeline
- KillEffect blood mode with Burst/Fountain/Ring/Spray styles
- main menu: menuLayout setting (orbs/hero) and hero screen
- Simple Voice Chat soft integration: VoiceChat module and HUD panel
- renderer/quick: static Draw facade with glass outline
- vanilla chat: Baritone-style autocomplete popup for the % command prefix
2026-10-01 21:29:54 +02:00

57 lines
1.8 KiB
TypeScript

import { useState, type FormEvent } from 'react'
import { useTranslation } from '@shared/i18n/react'
import { useLocation, useNavigate } from 'react-router'
import { describeError } from '@shared/api/errors'
import { Button } from '@shared/ui/Button'
import { Notice } from '@shared/ui/Notice'
import { TextField } from '@shared/ui/text-field/TextField'
import { useSession } from '../session'
export function LoginForm() {
const { t } = useTranslation('auth')
const { t: tc } = useTranslation('common')
const { signIn } = useSession()
const navigate = useNavigate()
const from = (useLocation().state as { from?: string } | null)?.from ?? '/configs'
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState<string>()
const [busy, setBusy] = useState(false)
async function submit(e: FormEvent) {
e.preventDefault()
setBusy(true)
setError(undefined)
try {
await signIn(email.trim(), password)
navigate(from, { replace: true })
} catch (err) {
setError(describeError(tc, err, { 401: t('login.wrongCredentials') }))
} finally {
setBusy(false)
}
}
return (
<form onSubmit={submit} className="flex w-full max-w-sm flex-col gap-4" noValidate>
<TextField
label={t('login.emailLabel')}
type="email"
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<TextField
label={t('login.passwordLabel')}
type="password"
autoComplete="current-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{error ? <Notice tone="error">{error}</Notice> : null}
<Button type="submit" busy={busy}>
{t('login.submit')}
</Button>
</form>
)
}