Храните шесть символов в состоянии, нормализуйте допустимый ввод и отправляйте код только после заполнения всех полей. Проверка действительности кода остаётся на сервере.
Компонент кода авторизации
Храните шесть символов в состоянии, нормализуйте допустимый ввод и отправляйте код только после заполнения всех полей. Проверка действительности кода остаётся на сервере.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Компонент кода авторизации
Резюме AuthCodeInput — это поле для ввода 6-значного кода авторизации, используемое для потоков в стиле 2FA. Оно отображает шесть однобуквенных полей и интегрируется с бэкендом для проверки введенного кода.
История пользователя Как пользователь, я хочу ввести 6-значный код авторизации и отправить его, чтобы подтвердить свою личность в знакомом потоке 2FA.
Анатомия
- 6 полей ввода, расположенных в ряд (по одному символу в каждом)
- В центре полей отображается дефис (-)
Основное поведение Вводы
- Каждое из 6 полей принимает один символ.
- Разрешены только цифры 0–9 и буквы A-Z.
- При вводе допустимого символа: o символ сохраняется в текущем поле. o если введенный символ в нижнем регистре, он автоматически преобразуется в верхний. o автоматически фокусируется на следующее поле.
- Полная OTP: o компонент считает значение "завершенным" только когда все 6 полей заполнены.
Ответ
Храните шесть символов в состоянии, нормализуйте допустимый ввод и отправляйте код только после заполнения всех полей. Проверка действительности кода остаётся на сервере.
Минимальный React-пример использует переданный async-callback onVerify для интеграции с API. Дефис отображается после третьего поля.
import { Fragment, useRef, useState } from 'react';
function AuthCodeInput({ onVerify }) {
const [digits, setDigits] = useState(Array(6).fill(''));
const [pending, setPending] = useState(false);
const [error, setError] = useState('');
const inputs = useRef([]);
const busy = useRef(false);
const complete = digits.every(value => /^[A-Z0-9]$/.test(value));
function change(index, raw) {
if (raw !== '' && !/^[a-z0-9]$/i.test(raw)) return;
setDigits(old => old.map((value, i) => i === index ? raw.toUpperCase() : value));
setError('');
if (raw && index < 5) inputs.current[index + 1]?.focus();
}
async function submit(event) {
event.preventDefault();
if (!complete || busy.current) return;
busy.current = true; setPending(true); setError('');
try { await onVerify(digits.join('')); }
catch { setError('Не удалось подтвердить код. Попробуйте ещё раз.'); }
finally { busy.current = false; setPending(false); }
}
return <form onSubmit={submit}>
<fieldset disabled={pending} style={{ display: 'flex', gap: 8 }}>
<legend>Код авторизации</legend>
{digits.map((value, index) => <Fragment key={index}>
{index === 3 && <span aria-hidden='true'>-</span>}
<input value={value} maxLength={1} autoCapitalize='characters'
style={{ width: '2em' }} aria-label={`Символ ${index + 1} из 6`}
ref={element => { inputs.current[index] = element; }}
onChange={event => change(index, event.target.value)}
onKeyDown={event => {
if (event.key === 'Backspace' && !value && index > 0) {
event.preventDefault(); inputs.current[index - 1]?.focus();
}
}} />
</Fragment>)}
</fieldset>
<button disabled={!complete || pending}>Подтвердить</button>
{error && <p role='alert'>{error}</p>}
</form>;
}
Backend проверяет срок, одноразовость и число попыток; заполненные поля не доказывают успешную авторизацию. Полная вставка OTP и платформенное автозаполнение требуют отдельной доработки сверх базового условия.