Приглашение в команду с валидацией
Сделай форму приглашения в команду: поле почты с подсказкой под ним, выбор роли из списка, переключатель «отправить уведомление» и кнопка отправки. Пока почта не похожа на адрес, поле показывает ошибку и кнопка заблокирована. После отправки над формой появляется сообщение об успехе, которое можно закрыть.
invite-form__mcp__claude-code__claude-opus-5__1 · 24.09.2026, 13:33:41 мск
Первый вызов модели получил 12 504 токена входа, включая системный промпт Claude Code; вызовов модели 2, каждый отправлял разговор заново, и самый большой получил 18 522 токена. Контекст до задачи — оценка по длине текста, для русского текста она занижает число токенов в полтора-два раза.
Компилятор
Ошибок нет: все компоненты и пропсы существуют.
Линтер и покрытие
- inline-styleСтроки 51, 66, 67, 81, 123Инлайновые стили вместо классов и токенов библиотеки
JxAlert, JxButton, JxInputField, JxSelect, JxSwitch
Ожидались JxInputField, JxSelect, JxSwitch, JxAlert, JxButton · покрытие 100%
- get_rules({"task":"ui"}) → ~1247 токенов
- get_component_api({"name":"JxInputField"}) → ~484 токенов
- get_component_api({"name":"JxSelect"}) → ~563 токенов
- get_component_api({"name":"JxSwitch"}) → ~289 токенов
- get_component_api({"name":"JxButton"}) → ~539 токенов
- get_component_api({"name":"JxAlert"}) → ~303 токенов
Инструменты (search_components,get_component_api,get_component_examples,list_design_tokens,get_docs,get_rules, ~1213)
Вход 4 · выход 2 216 · из кэша 24 517 · в кэш 6 505 · 23 с
Рендер
Код компонента
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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
import { useRef, useState } from 'react';
import { JxAlert, JxButton, JxInputField, JxSelect, JxSwitch } from '@jinx-ui/react';
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
const ROLE_OPTIONS = [
{ value: 'viewer', label: 'Наблюдатель', meta: 'только чтение' },
{ value: 'editor', label: 'Редактор', meta: 'правит материалы' },
{ value: 'admin', label: 'Администратор', meta: 'управляет командой' },
];
export default function TeamInviteForm() {
const [email, setEmail] = useState('');
const [touched, setTouched] = useState(false);
const [role, setRole] = useState('editor');
const [notify, setNotify] = useState(true);
const [sending, setSending] = useState(false);
const [invitedEmail, setInvitedEmail] = useState<string | null>(null);
const emailRef = useRef<HTMLInputElement>(null);
const trimmed = email.trim();
const isValid = EMAIL_PATTERN.test(trimmed);
const errorText = touched && !isValid ? 'Укажите адрес вида name@example.com' : undefined;
const submit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setTouched(true);
if (!isValid || sending) {
emailRef.current?.focus();
return;
}
setSending(true);
try {
setInvitedEmail(trimmed);
setEmail('');
setTouched(false);
} finally {
setSending(false);
}
};
const dismiss = () => {
setInvitedEmail(null);
emailRef.current?.focus();
};
return (
<form
onSubmit={submit}
noValidate
style={{
display: 'flex',
flexDirection: 'column',
gap: 16,
maxWidth: 440,
padding: 20,
background: 'var(--jx-surface)',
border: '1px solid var(--jx-border)',
borderRadius: 'var(--jx-r-lg)',
boxShadow: 'var(--jx-shadow-sm)',
fontFamily: 'var(--jx-font-sans)',
color: 'var(--jx-text)',
}}
>
{invitedEmail !== null && (
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 8 }} role="status">
<div style={{ flex: 1, minWidth: 0 }}>
<JxAlert intent="success" title="Приглашение отправлено">
{invitedEmail} получит доступ с ролью «
{ROLE_OPTIONS.find((option) => option.value === role)?.label}».
{notify ? ' Уведомление на почту отправлено.' : ' Уведомление на почту не отправлялось.'}
</JxAlert>
</div>
<JxButton variant="ghost" size="sm" iconOnly aria-label="Закрыть сообщение" onClick={dismiss}>
×
</JxButton>
</div>
)}
<h2
style={{
margin: 0,
fontFamily: 'var(--jx-font-display)',
fontSize: 18,
color: 'var(--jx-text)',
}}
>
Пригласить в команду
</h2>
<JxInputField
ref={emailRef}
label="Электронная почта"
type="email"
name="email"
required
autoComplete="email"
placeholder="name@example.com"
value={email}
disabled={sending}
onChange={(event) => setEmail(event.target.value)}
onBlur={() => setTouched(true)}
helperText="Приглашение придёт на этот адрес и будет действовать 7 дней"
errorText={errorText}
/>
<JxSelect
label="Роль в команде"
options={ROLE_OPTIONS}
value={role}
onValueChange={setRole}
placeholder="Выберите роль"
/>
<JxSwitch
label="Отправить уведомление на почту"
name="notify"
checked={notify}
disabled={sending}
onChange={(event) => setNotify(event.target.checked)}
/>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<JxButton type="submit" variant="primary" disabled={!isValid || sending}>
{sending ? 'Отправляем…' : 'Отправить приглашение'}
</JxButton>
</div>
</form>
);
}