Перейти к содержанию

P2-019 Frontend: Процесс регистрации

Метаданные

Поле Значение
Фаза Phase 2: Core
Статус done
Приоритет high
Связь с roadmap Roadmap - Frontend

Контекст

Бизнес-контекст

После заполнения формы регистрации участник видит подтверждение и получает билет в Telegram. Процесс должен быть простым и давать уверенность в успешной регистрации.

Технический контекст

  • Форма на публичной странице события
  • Mutation через TanStack Query
  • Redirect на success page
  • Интеграция с Notification Service для отправки билета

Связанные документы: - User Journeys - Journey 2 - Registrations API

Цель

Реализовать полный пользовательский путь регистрации от заполнения формы до получения подтверждения.

Definition of Ready (DoR)

  • [x] Контекст понятен и описан
  • [x] Цель сформулирована
  • [x] Acceptance Criteria определены
  • [x] Технические детали проработаны
  • [x] Зависимости определены
  • [x] Нет блокеров

Acceptance Criteria

Форма регистрации

  • [x] Выбор типа билета (если несколько)
  • [x] Поля: firstName, lastName, email
  • [x] Валидация в реальном времени
  • [x] Custom fields из настроек события (JSONB)
  • [x] Если авторизован — автозаполнение
  • [x] Loading состояние кнопки

Подтверждение

  • [x] После успешной регистрации — redirect на success page
  • [x] Показать: confirmation code, детали события, тип билета
  • [x] Сообщение «Билет отправлен в Telegram»
  • [x] Если нет Telegram — предложение привязать
  • [x] Кнопка «Добавить в календарь» (ics файл)

Ошибки

  • [x] Билеты закончились — понятное сообщение
  • [x] Уже зарегистрирован — сообщение со ссылкой на билет
  • [x] Событие отменено — сообщение
  • [x] Network error — retry option

Без авторизации

  • [x] Разрешить регистрацию без аккаунта (решение: требуется авторизация)
  • [x] Предложить создать аккаунт после регистрации (показываем форму входа/регистрации)
  • [x] Email для отправки билета (через Telegram невозможно без привязки)

С авторизацией

  • [x] Автозаполнение из профиля
  • [x] Регистрация привязана к user_id
  • [x] Билет отправляется в Telegram если привязан

Definition of Done (DoD)

  • [x] Все Acceptance Criteria выполнены
  • [x] UX: < 3 клика до регистрации
  • [x] Loading states
  • [x] Error handling
  • [x] Тесты успешного пути
  • [x] Code review пройден
  • [ ] CI/CD pipeline проходит

Технические детали

Структура

frontend/
├── app/(public)/events/[slug]/
│   └── success/page.tsx        — страница подтверждения
├── components/features/registration/
│   ├── registration-form.tsx
│   ├── ticket-selector.tsx
│   ├── registration-success.tsx
│   └── add-to-calendar.tsx
└── lib/hooks/
    └── use-registration.ts

Registration Flow

// Упрощённый flow

// 1. Пользователь заполняет форму
const form = useForm<RegistrationInput>({
  defaultValues: {
    ticketTypeId: ticketTypes[0].id,
    firstName: user?.firstName,
    lastName: user?.lastName,
    email: user?.email,
  },
});

// 2. Submit
const mutation = useMutation({
  mutationFn: (data) => eventApi.createRegistration(eventId, data),
  onSuccess: (registration) => {
    router.push(`/events/${slug}/success?code=${registration.confirmationCode}`);
  },
  onError: (error) => {
    if (error.response?.data?.code === 'already_registered') {
      toast.error('Вы уже зарегистрированы на это событие');
    } else if (error.response?.data?.code === 'sold_out') {
      toast.error('Билеты закончились');
    } else {
      toast.error('Ошибка регистрации. Попробуйте ещё раз.');
    }
  },
});

// 3. Success page показывает confirmation code

Success Page

// app/(public)/events/[slug]/success/page.tsx

export default async function SuccessPage({ params, searchParams }) {
  const event = await eventApi.getBySlug(params.slug);
  const code = searchParams.code;

  return (
    <div className="container py-12">
      <Card className="max-w-md mx-auto">
        <CardHeader>
          <CheckCircle className="h-12 w-12 text-green-500 mx-auto" />
          <CardTitle className="text-center">Регистрация успешна!</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          <div className="text-center">
            <p className="text-muted-foreground">Код вашего билета:</p>
            <p className="text-2xl font-mono font-bold">{code}</p>
          </div>

          <Separator />

          <div>
            <h3 className="font-semibold">{event.title}</h3>
            <p className="text-sm text-muted-foreground">
              {formatDate(event.startsAt)}
            </p>
            <p className="text-sm text-muted-foreground">
              {event.locationAddress || event.locationUrl}
            </p>
          </div>

          <Alert>
            <MessageSquare className="h-4 w-4" />
            <AlertDescription>
              Билет с QR-кодом отправлен в Telegram
            </AlertDescription>
          </Alert>

          <AddToCalendarButton event={event} />
        </CardContent>
      </Card>
    </div>
  );
}

Add to Calendar

function AddToCalendarButton({ event }) {
  const generateIcs = () => {
    const icsContent = [
      'BEGIN:VCALENDAR',
      'VERSION:2.0',
      'BEGIN:VEVENT',
      `DTSTART:${formatIcsDate(event.startsAt)}`,
      event.endsAt ? `DTEND:${formatIcsDate(event.endsAt)}` : '',
      `SUMMARY:${event.title}`,
      `DESCRIPTION:${event.description?.slice(0, 200)}`,
      `LOCATION:${event.locationAddress || event.locationUrl || ''}`,
      'END:VEVENT',
      'END:VCALENDAR',
    ].filter(Boolean).join('\r\n');

    const blob = new Blob([icsContent], { type: 'text/calendar' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${event.slug}.ics`;
    a.click();
  };

  return (
    <Button variant="outline" onClick={generateIcs}>
      <Calendar className="mr-2 h-4 w-4" />
      Добавить в календарь
    </Button>
  );
}

Зависимости

Блокирует

  • Нет

Зависит от

Out of Scope

  • Payment flow (Phase 3)
  • Waitlist (Phase 3)
  • Групповая регистрация (Phase 4)
  • Apple/Google Wallet интеграция

Заметки

  • Confirmation code показывается на success page и отправляется в Telegram
  • Если пользователь не привязал Telegram — показать инструкцию
  • Рассмотреть добавление share buttons на success page
  • Analytics: track registration conversion