Приглашение в команду с валидацией

Сделай форму приглашения в команду: поле почты с подсказкой под ним, выбор роли из списка, переключатель «отправить уведомление» и кнопка отправки. Пока почта не похожа на адрес, поле показывает ошибку и кнопка заблокирована. После отправки над формой появляется сообщение об успехе, которое можно закрыть.

invite-form__readme__claude-code__claude-opus-5__1 · 23.09.2026, 20:13:35 мск

Есть проблемы
README · claude-opus-5 · Claude Code
Контекст до задачи~798
Токенов на прогон11 743
Цена$0.067
Ошибок компилятора4

Первый вызов модели получил 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; }'.
Выдуманные компоненты
JxInput
Выдуманные пропсы
JxAlert.variantJxSelect.nameJxSwitch.onCheckedChange

Линтер и покрытие

  • 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 с

Рендер

Код компонента

tsx98 строк
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>
  );
}