From 444a5b1e1ca1b178cc7862d7d759f0b66744eb22 Mon Sep 17 00:00:00 2001 From: Aleksey Sabilin Date: Mon, 20 Jul 2026 01:52:13 +0300 Subject: [PATCH] docs: add EventHubFrontSpec (language/mood) and README link. Refs EventHub/EventHubDevOps#7 --- EventHubFrontSpec.md | 197 +++++++++++++++++++++++++++++++++++++++++++ README.md | 4 +- 2 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 EventHubFrontSpec.md diff --git a/EventHubFrontSpec.md b/EventHubFrontSpec.md new file mode 100644 index 0000000..3300e54 --- /dev/null +++ b/EventHubFrontSpec.md @@ -0,0 +1,197 @@ +# 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 }`. +- `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 `; при 401 — single-flight refresh; при неудаче — редирект на `/login`. +- При старте приложения — `GET /v1/user/me` при наличии токена. +- Logout — только клиентский (очистка storage); серверного revoke нет. +- WebSocket: `ws://…:8081/ws?token=`; 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, пагинация). Переход к календарю/событию. + +### 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`) diff --git a/README.md b/README.md index 7952f4f..b9a9155 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # **Техническое задание EventHub** ## 1. [EventHub Backend — Техническое задание](EventHubBackSpec.md) ## 2. [EventHub Admin UI — Техническое задание](EventHubFrontAdminSpec.md) -## 3. [Правила работы (workflow)](WORKFLOW.md) +## 3. [EventHub Client UI — Техническое задание](EventHubFrontSpec.md) +## 4. [Правила работы (workflow)](WORKFLOW.md) # **Репозитории разработки EventHub** ## 1. [EventHub Backend](https://git.sabilin.com/EventHub/EventHubBack) ## 2. [EventHub Admin UI](https://git.sabilin.com/EventHub/EventHubFrontAdmin) +## 3. [EventHub Client UI](https://git.sabilin.com/EventHub/EventHubFront) --- **Поиск по репозиториям производится запросом , пример https://git.sabilin.com/EventHub/EventHubBack/search?q=mnesia* \ No newline at end of file