Co-authored-by: Cursor <cursoragent@cursor.com>
13 KiB
EventHub Client UI — Техническое задание
Клиентское Web SPA для end-user: владельцы календарей и участники. Администраторы работают в EventHubFrontAdmin. Контракт API — EventHubBackSpec.md и маршруты EventHubBack (порт HTTP 8080, WS 8081).
1. Роли и режимы
Один пользовательский аккаунт; UI разделяется по контексту (не по JWT-роли админки):
| Режим | Описание | Ключевые возможности |
|---|---|---|
| Участник | Поиск и запись на события коммерческих календарей | Поиск, просмотр календаря/события, запись, отмена своей записи, отзывы, жалобы, тикеты |
| Владелец | Управление своими календарями | CRUD календарей и событий, список заявок, confirm/decline, подписка |
Deep-link на календарь, владельцем которого является текущий пользователь, показывает owner-действия (редактирование, заявки).
Права на операции проверяет бэкенд; фронт скрывает недоступные действия по owner_id / ответу API.
2. Аутентификация и авторизация
Эндпоинты:
POST /v1/register— регистрация (email,password); статус пользователяpendingдо верификации.POST /v1/verify— подтверждение email{ token }; после успеха у пользователя есть дефолтный personal-календарь.POST /v1/login— вход; ответ{ token, refresh_token, user }. Сессияauth_sessionсclient_type=web.POST /v1/refresh— ротация пары токенов по{ refresh_token }.GET /v1/user/me— профиль текущего пользователя.
Реализация:
- JWT в
localStorageпод ключами, отличными от Admin UI (избежать коллизии при общем origin). - Axios:
Authorization: Bearer <access>; при 401 — single-flight refresh; при неудаче — редирект на/login. - При старте приложения —
GET /v1/user/meпри наличии токена. - Logout — только клиентский (очистка storage); серверного revoke нет.
- WebSocket:
ws://…:8081/ws?token=<access_jwt>; subscribe/unsubscribe поcalendar_id.
3. Технологический стек
- Язык: TypeScript
- Сборщик: Vite
- UI: React 19 + Tailwind CSS 4 + shadcn/Radix
- Типографика: Syne (display) + Manrope (UI)
- Состояние: Zustand (auth + mood + calendar context), TanStack React Query v5 (сервер)
- HTTP: Axios с интерсепторами
- Формы: React Hook Form + Zod
- Дата/время: dayjs (ru locale)
- i18n: react-i18next (ru, en)
- Роутинг: React Router 7
- WebSocket: нативный API с переподключением
- Контейнеризация: Docker, Nginx (прокси
/v1→ user API:8080,/ws→:8081)
3.1. UX / IA (calendar workspace, mobile-first)
- Главная сущность — календарь. Центр UI — виджет с видами месяц (default) / неделя / день.
- Главные вкладки: Календарь (
/,/c/:id), Найти (/search), Записи (/bookings), Ещё (/more). - Контекст виджета: свой календарь (селектор) или чужой (browse после поиска).
personalчужой — только просмотр;commercial— запись на событие. - Выбор события открывает карточку действий: mobile — bottom sheet; desktop — боковая панель. Действия зависят от роли (owner / participant).
- Без выбранного события — панель «О календаре» (описание, рейтинг, отзывы; у browse — отзыв/жалоба на календарь).
- Owner: WS subscribe на активный календарь;
PUTсобытия из карточки;PUTкалендаря на/calendars. - Поиск — список календарей/событий; выбор подставляет календарь в виджет (
/c/:idили/c/:id/e/:eventId). - CRUD своих календарей —
/calendars(из «Ещё»). Legacy/discover,/calendars/:id→ redirects. - На мобиле — bottom tab bar (+ safe-area); на desktop — sticky header + табы.
- Mood themes — см. §3.2.
3.2. Настроения (mood themes)
Пользователь переключает визуальное настроение; атрибут html[data-mood]. Для гостя и при отсутствии preferences.mood — дефолт calm (в профиль при логине не пишется). После входа смена mood сохраняется в preferences.mood через PATCH /v1/user/me.
| Mood | Идентификатор | Характер |
|---|---|---|
| Спокойный | calm |
sage/mist, мягкий градиент (default) |
| Энергичный | energetic |
тёмный ink + coral/amber |
| Деловой | business |
charcoal + steel, более жёсткие радиусы |
Переключатель доступен на login и в /more (на desktop — также в header).
Язык UI (ru/en): до логина — navigator.language; после входа — поле language профиля (PATCH /v1/user/me). Если в профиле язык пуст — при логине записывается текущий (браузерный/выбранный на экране входа).
4. Маршруты
Публичные:
/login,/register,/verify
Защищённые (ProtectedRoute):
/— Calendar Workspace (автовыбор своего календаря или empty-state)/c/:calendarId— workspace с календарём/c/:calendarId/e/:eventId— workspace + карточка события/search— поиск календарей/событий/bookings— мои бронирования (deep-link в/c/.../e/...)/calendars— управление своими календарями (CRUD)/more— вторичное меню + mood switcher/reviews,/subscription,/tickets,/profile
Redirects: /discover → /search; /calendars/:id → /c/:id; /calendars/:id/events/:eventId → /c/:id/e/:eventId.
Состояние вида: Zustand calendarContextStore (view, cursorDate, last own calendarId в localStorage).
5. Модули
5.1. Auth
Регистрация, верификация, вход, восстановление сессии.
5.2. Профиль
GET /v1/user/me — отображение полей. PATCH /v1/user/me — язык (language), mood в preferences.mood, прочие поля профиля. Язык при первом визите — из браузера; при первом логине без language в профиле — записывается в профиль. Mood по умолчанию calm, в профиль при логине не сидится.
5.3. Поиск (участник)
GET /v1/search — фильтры (type, q, даты, теги, geo, sort, пагинация). Без фильтров (страница «Найти») — топ календарей и событий по рейтингу; с q/фильтрами — полнотекстовый поиск. Переход к календарю/событию.
5.4. Календари
- Список своих:
GET /v1/calendars - CRUD:
POST/PUT/DELETE /v1/calendars,GET /v1/calendars/:id - Просмотр коммерческого чужого календаря по id (доступ по правилам бэка)
- HTML month view владельца:
GET /v1/calendars/:calendar_id/view?month=YYYY-MM— при переключении виджета на прошедший месяц (owner + вид «Месяц») автоматически подменяет клиентский grid серверным HTML (live/архив, задача #15). Текущий и будущие месяцы — React-виджет поGET …/events. - Создание
commercialтребует активной подписки (ответ 402 → экран подписки)
5.5. События
- Список:
GET /v1/calendars/:calendar_id/events - CRUD владельца:
POST/PUT/DELETE - Детали:
GET /v1/events/:id - Вхождения:
GET /v1/events/:id/occurrences - Отмена вхождения:
DELETE /v1/events/:id/occurrences/:start_time
5.6. Запись (bookings)
- Участник:
POST /v1/events/:id/bookings,GET /v1/user/bookings,DELETE /v1/bookings/:id,GET /v1/bookings/:id - Владелец:
GET /v1/events/:id/bookings,PUT /v1/bookings/:idс{ action: confirm | decline } - Статусы:
pending|confirmed|cancelled(режим confirmation календаря:auto|manual| timeout)
5.7. Отзывы
GET/POST /v1/reviews, GET/PUT/DELETE /v1/reviews/:id, GET /v1/user/reviews. Цели: event | calendar.
Голос: PUT /v1/reviews/:id/vote { "value": "like"|"dislike" }, DELETE /v1/reviews/:id/vote; в ответах — my_vote.
5.8. Жалобы
POST /v1/reports — target_type: event | calendar | review.
5.9. Подписка
GET /v1/subscription, POST /v1/subscription (start_trial | activate).
5.10. Тикеты
- ErrorBoundary / необработанные ошибки →
POST /v1/ticketsсsource=frontend - Форма «Сообщить о баге» →
source=manual - Список:
GET /v1/tickets, деталиGET /v1/tickets/:id
5.11. Realtime
Подписка WS на открытые календари; сообщения calendar_update, event_update, booking_update → инвалидация React Query.
5.12. Версии сборки
Как в Admin UI / Back (EventHubSpec/WORKFLOW.md §6):
- Product
MAJOR.MINORизEventHubSpec/VERSION(CI:scripts/resolve-product-version.sh; локальныйVERSION— fallback). - Bake:
VITE_APP_VERSION,VITE_APP_BUILD,VITE_GIT_SHA,VITE_BUILT_AT. - UI: экран «Ещё» —
UI {version}.{build} (sha); API —GET /health→API ….
6. Структура проекта
src/
├── api/ — Axios client + *Api.ts
├── hooks/ — React Query hooks
├── store/ — Zustand (auth)
├── pages/ — auth, discover, calendars, events, bookings, reviews, subscription, tickets, profile
├── layouts/ — consumer shell (не admin Control Center)
├── components/ui — shadcn primitives
├── i18n/ — locales ru/en
├── schemas/ — Zod
└── types/
Репозиторий: EventHubFront.
6.1. Стенды и балансировщик
| Stand | URL | Swarm |
|---|---|---|
| IFT | https://ui.ift.eventhub.local |
alias client-ui |
| stage | https://ui.stage.eventhub.local |
alias client-ui |
| dev | https://ui.dev.eventhub.local |
alias client-ui |
Образ registry: git.sabilin.com/eventhub/eventhub-front. Traefik host-based (без path prefix). Nginx в контейнере: / SPA, /v1/ → eventhub:8080, /ws → eventhub:8081.
CI: lint → build → Playwright mock → push image → deploy IFT → e2e-ift → stage → e2e-stage (зеркало FrontAdmin). Деплой зависит от EventHubDevOps#7 (deploy-service.sh … client).
E2E: e2e/TESTIDS.md; mock npm run test:e2e; стенд SMOKE_USER_* + npm run test:e2e:ift|stage.
7. Ограничения MVP (Future)
Не реализуется в UI, пока нет user HTTP API:
- приглашения / шаринг календаря (
calendar_share) - CRUD специалистов (
calendar_specialist) - серверный logout / revoke session
- загрузка файлов (вложения)
- явный
client_type=mobile(фаза 3 бэка) - push-уведомления (таблица
notificationбез полноценной доставки)
Лайки/дизлайки отзывов: PUT/DELETE /v1/reviews/:id/vote, поле my_vote в ответах отзывов (EventHubBack#47).
8. Источники истины
- Маршруты и поведение:
EventHubBack/src/eventhub_app.erl+ handlers - Продукт и auth:
EventHubBackSpec.md§1–2, §6.1 - Swagger
client-swagger.json— вторичный (возможен drift, например/v1/auth/refreshvs/v1/refresh)