Приглашение в команду с валидацией
Сделай форму приглашения в команду: поле почты с подсказкой под ним, выбор роли из списка, переключатель «отправить уведомление» и кнопка отправки. Пока почта не похожа на адрес, поле показывает ошибку и кнопка заблокирована. После отправки над формой появляется сообщение об успехе, которое можно закрыть.
invite-form__readme__claude-code__claude-opus-5__1 · 23.09.2026, 20:13:35 мск
Первый вызов модели получил 9 553 токена входа, включая системный промпт Claude Code. Контекст до задачи — оценка по длине текста, для русского текста она занижает число токенов в полтора-два раза.
Компилятор
- Строка 5 · TS2305
Module '"@jinx-ui/react"' has no exported member 'JxInput'. - Строка 48 · TS2322
Type '{ children: string[]; variant: string; title: string; dismissible: true; onClose: () => void; }' is not assignable to type 'IntrinsicAttributes & HTMLAttributes<HTMLDivElement> & { ref?: Ref<HTMLDivElement> | undefined; intent?: JxAlertIntent | undefined; title: ReactNode; icon?: ReactNode; children?: ReactNode; }'. - Строка 78 · TS2322
Type '{ name: string; label: string; value: Role; onValueChange: (next: string) => void; options: { value: Role; label: string; }[]; }' is not assignable to type 'IntrinsicAttributes & JxSelectProps'. - Строка 89 · TS2322
Type '{ name: string; label: string; checked: boolean; onCheckedChange: Dispatch<SetStateAction<boolean>>; }' is not assignable to type 'IntrinsicAttributes & Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "size"> & { ref?: Ref<HTMLInputElement> | undefined; label?: ReactNode; wrapClassName?: string | undefined; }'.
Линтер и покрытие
- inline-styleСтроки 44, 59Инлайновые стили вместо классов и токенов библиотеки
JxAlert, JxButton, JxInput, JxSelect, JxSwitch
Ожидались JxInputField, JxSelect, JxSwitch, JxAlert, JxButton · покрытие 80%
README (README.md, ~644)
Вход 2 · выход 2 190 · из кэша 8 233 · в кэш 1 318 · 26 с
Рендер
Код компонента
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
import { useState, useId, type FormEvent } from 'react';
import {
JxAlert,
JxButton,
JxInput,
JxSelect,
JxSwitch,
} from '@jinx-ui/react';
type Role = 'viewer' | 'editor' | 'admin';
const ROLE_OPTIONS: { value: Role; label: string }[] = [
{ value: 'viewer', label: 'Наблюдатель' },
{ value: 'editor', label: 'Редактор' },
{ value: 'admin', label: 'Администратор' },
];
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
export default function TeamInviteForm() {
const hintId = useId();
const [email, setEmail] = useState('');
const [touched, setTouched] = useState(false);
const [role, setRole] = useState<Role>('editor');
const [notify, setNotify] = useState(true);
const [invitedEmail, setInvitedEmail] = useState<string | null>(null);
const emailIsValid = EMAIL_PATTERN.test(email.trim());
const showError = touched && !emailIsValid;
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setTouched(true);
if (!emailIsValid) return;
setInvitedEmail(email.trim());
setEmail('');
setTouched(false);
}
return (
<div
className="jx-stack"
style={{ gap: 'var(--jx-space-4)', maxWidth: 'var(--jx-size-md)' }}
>
{invitedEmail && (
<JxAlert
variant="success"
title="Приглашение отправлено"
dismissible
onClose={() => setInvitedEmail(null)}
>
{invitedEmail} получит доступ к команде.
</JxAlert>
)}
<form
className="jx-stack"
style={{ gap: 'var(--jx-space-4)' }}
onSubmit={handleSubmit}
noValidate
>
<JxInput
type="email"
name="email"
label="Рабочая почта"
placeholder="name@company.com"
value={email}
onValueChange={setEmail}
onBlur={() => setTouched(true)}
invalid={showError}
aria-describedby={hintId}
hint="Приглашение придёт на этот адрес и будет действовать 7 дней."
error={showError ? 'Введите адрес в формате name@company.com' : undefined}
/>
<JxSelect
name="role"
label="Роль в команде"
value={role}
onValueChange={(next: string) => setRole(next as Role)}
options={ROLE_OPTIONS}
/>
<JxSwitch
name="notify"
label="Отправить уведомление"
checked={notify}
onCheckedChange={setNotify}
/>
<JxButton type="submit" variant="primary" disabled={!emailIsValid}>
Отправить приглашение
</JxButton>
</form>
</div>
);
}