0d74ad33e3
Mood/lang only on auth and More; profile editable fields; search badge and collapsed filters. Refs EventHub/EventHubBack#48
198 lines
14 KiB
Markdown
198 lines
14 KiB
Markdown
# EventHub Client UI — Техническое задание
|
||
|
||
Клиентское Web SPA для end-user: владельцы календарей и участники. Администраторы работают в [EventHubFrontAdmin](EventHubFrontAdminSpec.md). Контракт API — [EventHubBackSpec.md](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, более жёсткие радиусы |
|
||
|
||
Переключатели mood и языка: на экранах auth (`/login`, `/register`, `/verify`) и в `/more`. В шапке workspace — только бренд, email и выход (без mood/lang).
|
||
|
||
Язык UI (`ru`/`en`): до логина — `navigator.language`; после входа — поле `language` профиля (`PATCH /v1/user/me`). Если в профиле язык пуст — при логине записывается текущий (браузерный/выбранный на экране входа). Язык также можно сменить в форме `/profile`.
|
||
|
||
## 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` + форма на `/profile`. Редактируемые через `PATCH /v1/user/me`: `nickname`, `phone`, `timezone`, `language`. Read-only в UI: `id`, `email`, `role`, `status`. Mood — отдельно в `/more` (`preferences.mood`). Смена пароля и `avatar_url` на бэке есть, в клиентском UI пока нет. Язык при первом визите — из браузера; при первом логине без `language` — пишется в профиль. Mood по умолчанию `calm`, при логине не сидится.
|
||
|
||
### 5.3. Поиск (участник)
|
||
`GET /v1/search` — фильтры (`type`, `q`, даты, sort, order, пагинация; теги/geo в API есть, в UI пока нет). Без `q`/дат/sort — топы календарей и событий; иначе — поиск. В списке бейдж типа — `calendar`|`event` (не `personal`/`commercial`). Даты и сортировка свёрнуты по умолчанию. Переход к `/c/:id` или `/c/:id/e/:eventId`.
|
||
|
||
### 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](https://git.sabilin.com/EventHub/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/refresh` vs `/v1/refresh`)
|