fix(pwa): код двухфакторки уходит сам, а не ждёт кнопки

Второй шаг входа сотрудника не отправлялся вовсе: у поля с кодом не было ни
обработки Enter, ни автоотправки, а «Готово» на клавиатуре телефона — это как
раз Enter. Со стороны выглядело как «код не принят», хотя запрос на /auth/2fa
не уходил ни разу (в логах бэкенда — только login с ответом «нужен второй шаг»).

Теперь шесть цифр отправляются сразу, Enter тоже работает, поле получает фокус и
autocomplete=one-time-code (подсказка кода из СМС/менеджера паролей).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 15:19:11 +10:00
co-authored by Claude Opus 5
parent a0ada7de67
commit 39ee2b7a49
2 changed files with 17 additions and 6 deletions
@@ -59,8 +59,9 @@ describe("DevLoginPage", () => {
await userEvent.click(screen.getByRole("button", { name: "Войти" }));
expect(useStaff.getState().token).toBeNull();
// Шестая цифра отправляет код сама: раньше форма ждала нажатия, а «Готово» на
// клавиатуре телефона ничего не делало — выглядело как «код не принят».
await userEvent.type(await screen.findByPlaceholderText("000000"), "123456");
await userEvent.click(screen.getByRole("button", { name: "Подтвердить" }));
expect(staffApi.staffLogin2fa).toHaveBeenCalledWith("partial", "123456");
expect(useStaff.getState().token).toBe("staff-tok");
});
+15 -5
View File
@@ -42,11 +42,11 @@ export function DevLoginPage() {
}
}
async function submit2fa() {
if (!partial || code.length < 6 || busy) return;
async function submit2fa(value = code) {
if (!partial || value.length < 6 || busy) return;
setBusy(true);
try {
const r = await staffLogin2fa(partial, code);
const r = await staffLogin2fa(partial, value);
if (!r.access_token) throw new Error("no token");
setStaff(r.access_token, username);
setPartial(null);
@@ -80,13 +80,23 @@ export function DevLoginPage() {
<>
<p className="text-sm mb-2">Код из приложения-аутентификатора.</p>
<input
autoFocus
inputMode="numeric"
autoComplete="one-time-code"
value={code}
onChange={(e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
onChange={(e) => {
const next = e.target.value.replace(/\D/g, "").slice(0, 6);
setCode(next);
// Шесть цифр — код собран, ждать нажатия нечего: у TOTP всё равно
// остаётся секунд тридцать. Раньше отправки не было ни по Enter, ни
// по «Готово» на клавиатуре телефона, и это выглядело как «код не принят».
if (next.length === 6) void submit2fa(next);
}}
onKeyDown={(e) => { if (e.key === "Enter") void submit2fa(); }}
placeholder="000000"
className="card w-full px-4 py-3 mb-3 text-base tracking-widest bg-white"
/>
<button className="btn-primary" disabled={busy || code.length < 6} onClick={submit2fa}>
<button className="btn-primary" disabled={busy || code.length < 6} onClick={() => submit2fa()}>
{busy ? <Spinner /> : "Подтвердить"}
</button>
<button className="btn-ghost mt-2" onClick={() => { setPartial(null); setCode(""); }}>