Docs: sync booking expired (Back#60) + Front#31 UX polish. Fixes EventHub/EventHubSpec#13
This commit is contained in:
+28
-14
@@ -95,13 +95,19 @@ pending владельца, **не** меняет `calendar.type`. Legacy `downg
|
|||||||
- `POST /v1/events/:id/bookings` только если календарь события commercial, `booking_open=true`,
|
- `POST /v1/events/:id/bookings` только если календарь события commercial, `booking_open=true`,
|
||||||
событие `active`, есть свободная вместимость.
|
событие `active`, есть свободная вместимость.
|
||||||
- **Pending занимает capacity** наравне с confirmed (защита от overbook при auto/timeout).
|
- **Pending занимает capacity** наравне с confirmed (защита от overbook при auto/timeout).
|
||||||
- Capacity: число booking со статусом `pending` | `confirmed`; `cancelled` не считаются.
|
- Capacity: число booking со статусом `pending` | `confirmed`; `cancelled` и `expired` не считаются.
|
||||||
- Политика `confirmation` календаря при создании booking:
|
- Политика `confirmation` календаря при создании booking:
|
||||||
- `auto` → сразу `confirmed` + `confirmed_at`;
|
- `auto` → сразу `confirmed` + `confirmed_at`;
|
||||||
- `manual` → `pending`; confirm/decline — владелец или specialist (см. ниже);
|
- `manual` → `pending`; confirm/decline — владелец или specialist (см. ниже);
|
||||||
- `{timeout, N}` → `pending`; через N секунд без решения: auto-confirm, если ещё есть
|
- `{timeout, N}` → `pending`; через N секунд без решения: auto-confirm, если ещё есть
|
||||||
capacity, иначе `cancelled` (`timeout_full`).
|
capacity, иначе `cancelled` (`timeout_full`).
|
||||||
- Участник: `DELETE /v1/bookings/:id` — отмена своей pending/confirmed.
|
- **Past-pending → `expired`:** если событие уже началось (`now >= event.start_time`), а booking
|
||||||
|
ещё `pending`, статус переводится в `expired` (lazy при чтении списков/`GET` booking и в
|
||||||
|
`process_timeout_bookings`). `expired` не actionable: Confirm/Decline → `409` (`Booking expired`).
|
||||||
|
- **Inbox:** `GET /v1/user/booking-requests` возвращает только ещё actionable pending
|
||||||
|
(до старта события); past-pending помечает `expired` и **не** включает в ответ.
|
||||||
|
- Участник: `DELETE /v1/bookings/:id` — отмена своей pending/confirmed; для уже `expired` —
|
||||||
|
no-op успех (как для `cancelled`).
|
||||||
- WS: `booking_update` участнику и владельцу (и specialist при confirm на «своём» событии).
|
- WS: `booking_update` участнику и владельцу (и specialist при confirm на «своём» событии).
|
||||||
|
|
||||||
#### Специалисты
|
#### Специалисты
|
||||||
@@ -171,8 +177,9 @@ Legacy: прямой `POST /v1/calendars/:id/specialists` **не использ
|
|||||||
(иначе `400`).
|
(иначе `400`).
|
||||||
- **Confirm/decline booking:** владелец — любые booking календаря; `active` specialist — только
|
- **Confirm/decline booking:** владелец — любые booking календаря; `active` specialist — только
|
||||||
booking на событиях, где `event.specialist_id` = его `user_id`.
|
booking на событиях, где `event.specialist_id` = его `user_id`.
|
||||||
- **Inbox к подтверждению:** `GET /v1/user/booking-requests` агрегирует pending по тем же
|
- **Inbox к подтверждению:** `GET /v1/user/booking-requests` агрегирует **actionable** pending
|
||||||
правилам (owner — все события своих календарей; specialist — только свои слоты).
|
по тем же правилам (owner — все события своих календарей; specialist — только свои слоты);
|
||||||
|
past-pending → `expired` и из ответа исключается.
|
||||||
|
|
||||||
#### Фаза 2 (вне текущего контракта реализации)
|
#### Фаза 2 (вне текущего контракта реализации)
|
||||||
- `calendar_share` (`read` | `write` | `admin`): не путать с follow, specialist_invite и платной subscription.
|
- `calendar_share` (`read` | `write` | `admin`): не путать с follow, specialist_invite и платной subscription.
|
||||||
@@ -247,12 +254,17 @@ Legacy: прямой `POST /v1/calendars/:id/specialists` **не использ
|
|||||||
- Запись доступна только на событиях **commercial**-календаря с `booking_open=true` (§2.1.2).
|
- Запись доступна только на событиях **commercial**-календаря с `booking_open=true` (§2.1.2).
|
||||||
- На personal → `403` (`personal_calendar`); при restricted commercial → `403` (`subscription_inactive`).
|
- На personal → `403` (`personal_calendar`); при restricted commercial → `403` (`subscription_inactive`).
|
||||||
- В зависимости от `confirmation` календаря: auto / manual / timeout (§2.1.2).
|
- В зависимости от `confirmation` календаря: auto / manual / timeout (§2.1.2).
|
||||||
- Статусы: `pending`, `confirmed`, `cancelled`.
|
- Статусы: `pending`, `confirmed`, `cancelled`, `expired`.
|
||||||
- **Capacity:** лимит слотов считают `pending` + `confirmed` (pending резервирует место).
|
- **Capacity:** лимит слотов считают `pending` + `confirmed` (pending резервирует место;
|
||||||
- Участник может отменить свою запись (`DELETE`).
|
`cancelled` / `expired` не занимают).
|
||||||
|
- **Истечение:** past-pending (событие уже началось) → `expired` (lazy + timeout job); не
|
||||||
|
путать с `specialist_invite.status=expired`.
|
||||||
|
- Участник может отменить свою запись (`DELETE`) для pending/confirmed; `expired`/`cancelled` —
|
||||||
|
идемпотентный успех.
|
||||||
- Confirm/decline: владелец календаря — любые заявки; active specialist — только на событиях
|
- Confirm/decline: владелец календаря — любые заявки; active specialist — только на событиях
|
||||||
со своим `specialist_id` (§2.1.2).
|
со своим `specialist_id` (§2.1.2). На `expired` (и после lazy-mark) → `409`.
|
||||||
- При подтверждении фиксируется `confirmed_at`.
|
- При подтверждении фиксируется `confirmed_at`.
|
||||||
|
- Inbox `GET /v1/user/booking-requests` — только pending до старта события (§2.1.2).
|
||||||
|
|
||||||
### 2.4. Отзывы и рейтинги
|
### 2.4. Отзывы и рейтинги
|
||||||
- Пользователи могут оставлять отзывы (рейтинг 1–5 и комментарий) на события или календари.
|
- Пользователи могут оставлять отзывы (рейтинг 1–5 и комментарий) на события или календари.
|
||||||
@@ -456,11 +468,11 @@ src/
|
|||||||
пара `current_password` + `password` (неверный текущий → `403`). Нельзя менять
|
пара `current_password` + `password` (неверный текущий → `403`). Нельзя менять
|
||||||
email/role/status; неизвестные поля → `400`. Ответ — полный профиль как GET.
|
email/role/status; неизвестные поля → `400`. Ответ — полный профиль как GET.
|
||||||
- `GET /v1/user/bookings` — бронирования пользователя **как участника**.
|
- `GET /v1/user/bookings` — бронирования пользователя **как участника**.
|
||||||
- `GET /v1/user/booking-requests` — pending-заявки к подтверждению, где текущий
|
- `GET /v1/user/booking-requests` — **actionable** pending-заявки к подтверждению, где
|
||||||
пользователь — **owner** календаря события или **assigned specialist**
|
текущий пользователь — **owner** календаря события или **assigned specialist**
|
||||||
(`event.specialist_id` = user и specialist active). Ответ — массив объектов
|
(`event.specialist_id` = user и specialist active). Past-pending помечается `expired` и
|
||||||
booking + `role` (`owner`|`specialist`) + вложенный `event`
|
**не** попадает в ответ. Ответ — массив объектов booking + `role` (`owner`|`specialist`) +
|
||||||
(`id`, `calendar_id`, `calendar_title`, `title`, `start_time`, `duration`,
|
вложенный `event` (`id`, `calendar_id`, `calendar_title`, `title`, `start_time`, `duration`,
|
||||||
`specialist_id`). Confirm/decline — существующий `PUT /v1/bookings/:id`.
|
`specialist_id`). Confirm/decline — существующий `PUT /v1/bookings/:id`.
|
||||||
- `GET /v1/user/reviews` — отзывы пользователя.
|
- `GET /v1/user/reviews` — отзывы пользователя.
|
||||||
- `GET /v1/user/following` — календари, которые пользователь отслеживает (follow).
|
- `GET /v1/user/following` — календари, которые пользователь отслеживает (follow).
|
||||||
@@ -494,7 +506,9 @@ src/
|
|||||||
- `GET /v1/bookings/:id` — статус бронирования.
|
- `GET /v1/bookings/:id` — статус бронирования.
|
||||||
- `PUT /v1/bookings/:id` — подтвердить/отклонить (`confirm`|`decline`); владелец —
|
- `PUT /v1/bookings/:id` — подтвердить/отклонить (`confirm`|`decline`); владелец —
|
||||||
любые booking календаря; active specialist — только события со своим `specialist_id`.
|
любые booking календаря; active specialist — только события со своим `specialist_id`.
|
||||||
- `DELETE /v1/bookings/:id` — отменить бронирование (участник).
|
На past-pending / уже `expired` → `409` (`Booking expired`); при полной вместимости на
|
||||||
|
confirm → `409` (`Event is full`).
|
||||||
|
- `DELETE /v1/bookings/:id` — отменить бронирование (участник; `expired`/`cancelled` — no-op `200`).
|
||||||
- `POST /v1/reviews` — создать отзыв.
|
- `POST /v1/reviews` — создать отзыв.
|
||||||
- `GET /v1/reviews` — список отзывов (поле `my_vote` для текущего пользователя).
|
- `GET /v1/reviews` — список отзывов (поле `my_vote` для текущего пользователя).
|
||||||
- `GET /v1/reviews/:id` — отзыв по ID (`my_vote`).
|
- `GET /v1/reviews/:id` — отзыв по ID (`my_vote`).
|
||||||
|
|||||||
+25
-10
@@ -53,10 +53,15 @@ Deep-link на календарь, владельцем которого явл
|
|||||||
- Главные вкладки: **Календарь** (`/`, `/c/:id`), **Найти** (`/search`), **Записи** (`/bookings`), **Ещё** (`/more`).
|
- Главные вкладки: **Календарь** (`/`, `/c/:id`), **Найти** (`/search`), **Записи** (`/bookings`), **Ещё** (`/more`).
|
||||||
- **Записи** (`/bookings`) — grouped inbox: «К подтверждению» (owner/specialist pending + Confirm/Decline)
|
- **Записи** (`/bookings`) — grouped inbox: «К подтверждению» (owner/specialist pending + Confirm/Decline)
|
||||||
и «Мои записи» (participant); пустые группы скрывать. Confirm на карточке события остаётся.
|
и «Мои записи» (participant); пустые группы скрывать. Confirm на карточке события остаётся.
|
||||||
- В **Ещё**: пункт входящих specialist invites скрыт при `pending=0` (deep-link `/invites` работает).
|
Past/expired: бейдж `expired` («истекла»), Cancel скрыт; empty-state с CTA Discover / календари.
|
||||||
|
Время на list-карточках — вторичное к title (`.eh-book-list-time`).
|
||||||
|
- В **Ещё**: пункт входящих specialist invites скрыт при `pending=0`; при `pending > 0` —
|
||||||
|
badge со счётчиком (deep-link `/invites` работает).
|
||||||
- Контекст виджета: свой календарь (селектор) или чужой (browse после поиска). Чужой `personal` —
|
- Контекст виджета: свой календарь (селектор) или чужой (browse после поиска). Чужой `personal` —
|
||||||
только просмотр; чужой `commercial` с `booking_open=true` — запись; при `booking_open=false`
|
только просмотр; чужой `commercial` с `booking_open=true` — запись; при `booking_open=false`
|
||||||
(restricted / нет подписки владельца) — просмотр + сообщение «Запись временно недоступна».
|
(restricted / нет подписки владельца) — просмотр + сообщение «Запись временно недоступна».
|
||||||
|
- Agenda rail: empty copy зависит от типа календаря — personal «Нет событий», commercial
|
||||||
|
«Нет слотов».
|
||||||
- Выбор события открывает карточку действий: mobile — bottom sheet; desktop — боковая панель. Действия зависят от роли (owner / participant).
|
- Выбор события открывает карточку действий: mobile — bottom sheet; desktop — боковая панель. Действия зависят от роли (owner / participant).
|
||||||
- Без выбранного события — панель «О календаре» (описание, title/meta, рейтинг, отзывы).
|
- Без выбранного события — панель «О календаре» (описание, title/meta, рейтинг, отзывы).
|
||||||
Форма отзыва на календарь/событие — только при confirmed booking; жалоба доступна без записи.
|
Форма отзыва на календарь/событие — только при confirmed booking; жалоба доступна без записи.
|
||||||
@@ -69,7 +74,9 @@ Deep-link на календарь, владельцем которого явл
|
|||||||
- Поиск (`/search`): query/filters в URL params (восстановление при возврате); chip type фильтрует
|
- Поиск (`/search`): query/filters в URL params (восстановление при возврате); chip type фильтрует
|
||||||
discovery tops без ухода из Popular; в строке результата — id snippet и `calendar_title` для event.
|
discovery tops без ухода из Popular; в строке результата — id snippet и `calendar_title` для event.
|
||||||
- CRUD своих календарей — `/calendars` (из «Ещё»). Legacy `/discover`, `/calendars/:id` → redirects.
|
- CRUD своих календарей — `/calendars` (из «Ещё»). Legacy `/discover`, `/calendars/:id` → redirects.
|
||||||
- На мобиле — bottom tab bar (+ safe-area); на desktop — sticky header + табы.
|
- На мобиле — bottom tab bar (+ safe-area); на desktop — **одна строка** chrome:
|
||||||
|
logo | nav tabs | nickname + logout (не два ряда header+nav). Сырой email в chrome
|
||||||
|
не показывать — primary identity = `nickname` (fallback без `@`).
|
||||||
Nav `aria-current` синхронизирован с `useLocation` (без remount `Outlet` по pathname).
|
Nav `aria-current` синхронизирован с `useLocation` (без remount `Outlet` по pathname).
|
||||||
- Mood themes — см. §3.2.
|
- Mood themes — см. §3.2.
|
||||||
|
|
||||||
@@ -85,7 +92,7 @@ Deep-link на календарь, владельцем которого явл
|
|||||||
| MOMENTUM | `energetic` | тёмный ink + coral `#FF6A3D` |
|
| MOMENTUM | `energetic` | тёмный ink + coral `#FF6A3D` |
|
||||||
| CONTROL | `business` | charcoal + steel `#324A5F`, более жёсткие радиусы |
|
| CONTROL | `business` | charcoal + steel `#324A5F`, более жёсткие радиусы |
|
||||||
|
|
||||||
Переключатели mood и языка: на экранах auth (`/login`, `/register`, `/verify`) и в `/more`. В шапке workspace — wordmark CalenTIQ (Time Arc), email и выход (без mood/lang).
|
Переключатели mood и языка: на экранах auth (`/login`, `/register`, `/verify`) и в `/more`. В шапке workspace — wordmark CalenTIQ (Time Arc), **nickname** (не raw email) и выход (без mood/lang). Desktop chrome — одна строка: logo | nav | logout.
|
||||||
|
|
||||||
Язык UI (`ru`/`en`): до логина — `navigator.language`; после входа — поле `language` профиля (`PATCH /v1/user/me`). Если в профиле язык пуст — при логине записывается текущий (браузерный/выбранный на экране входа). Язык также можно сменить в форме `/profile`.
|
Язык UI (`ru`/`en`): до логина — `navigator.language`; после входа — поле `language` профиля (`PATCH /v1/user/me`). Если в профиле язык пуст — при логине записывается текущий (браузерный/выбранный на экране входа). Язык также можно сменить в форме `/profile`.
|
||||||
|
|
||||||
@@ -103,7 +110,8 @@ Deep-link на календарь, владельцем которого явл
|
|||||||
(B) мои записи участника; deep-link в `/c/.../e/...`
|
(B) мои записи участника; deep-link в `/c/.../e/...`
|
||||||
- `/calendars` — управление своими календарями (CRUD)
|
- `/calendars` — управление своими календарями (CRUD)
|
||||||
- `/more` — вторичное меню + mood switcher; пункт specialist invites **скрыт**,
|
- `/more` — вторичное меню + mood switcher; пункт specialist invites **скрыт**,
|
||||||
если входящих pending = 0 (deep-link `/invites?token=` работает)
|
если входящих pending = 0; при pending > 0 — badge со счётчиком
|
||||||
|
(deep-link `/invites?token=` работает)
|
||||||
- `/invites` — inbox входящих specialist invites (не смешивать с `/bookings`)
|
- `/invites` — inbox входящих specialist invites (не смешивать с `/bookings`)
|
||||||
- `/following` — отслеживаемые чужие календари (follow; не платная Подписка)
|
- `/following` — отслеживаемые чужие календари (follow; не платная Подписка)
|
||||||
- `/reviews`, `/subscription`, `/tickets`, `/profile`
|
- `/reviews`, `/subscription`, `/tickets`, `/profile`
|
||||||
@@ -122,7 +130,9 @@ Redirects: `/discover` → `/search`; `/calendars/:id` → `/c/:id`; `/calendars
|
|||||||
|
|
||||||
### 5.3. Поиск (участник)
|
### 5.3. Поиск (участник)
|
||||||
`GET /v1/search` — фильтры (`type`, `q`, `tags`, даты, `lat`/`lon`/`radius`, sort, order, пагинация). Без `q`/дат/tags/geo/sort — топы календарей и событий; иначе — поиск. В списке бейдж типа — `calendar`|`event` (не `personal`/`commercial`). Даты, теги, geo и сортировка свёрнуты по умолчанию. Переход к `/c/:id` или `/c/:id/e/:eventId`.
|
`GET /v1/search` — фильтры (`type`, `q`, `tags`, даты, `lat`/`lon`/`radius`, sort, order, пагинация). Без `q`/дат/tags/geo/sort — топы календарей и событий; иначе — поиск. В списке бейдж типа — `calendar`|`event` (не `personal`/`commercial`). Даты, теги, geo и сортировка свёрнуты по умолчанию. Переход к `/c/:id` или `/c/:id/e/:eventId`.
|
||||||
В результатах calendar (и event, если Back отдаёт) может быть `image_url` — Discover показывает cover; иначе mood wash + initials. **Upload API нет** — поле URL (seed / ручная установка).
|
В результатах calendar (и event, если Back отдаёт) может быть `image_url` — Discover показывает
|
||||||
|
cover (`.eh-discover-media--photo`); иначе mood wash + initials (`.eh-discover-media--fallback`,
|
||||||
|
не fake photo-hero). Битый `image_url` → fallback. **Upload API нет** — поле URL (seed / ручная установка).
|
||||||
|
|
||||||
### 5.4. Календари
|
### 5.4. Календари
|
||||||
- Список своих: `GET /v1/calendars`
|
- Список своих: `GET /v1/calendars`
|
||||||
@@ -141,8 +151,8 @@ Redirects: `/discover` → `/search`; `/calendars/:id` → `/c/:id`; `/calendars
|
|||||||
Баннер «подписка истекла / истекает» при `booking_open=false`.
|
Баннер «подписка истекла / истекает» при `booking_open=false`.
|
||||||
- Invitee: inbox входящих `GET /v1/user/specialist-invites` на `/invites` —
|
- Invitee: inbox входящих `GET /v1/user/specialist-invites` на `/invites` —
|
||||||
принять / отклонить; deep-link из email → accept по `token` после логина.
|
принять / отклонить; deep-link из email → accept по `token` после логина.
|
||||||
В More пункт «Приглашения» показывается **только** при `pending > 0`
|
В More пункт «Приглашения» показывается **только** при `pending > 0` с badge
|
||||||
(не путать с booking-заявками на `/bookings`).
|
счётчика (не путать с booking-заявками на `/bookings`).
|
||||||
|
|
||||||
### 5.5. События
|
### 5.5. События
|
||||||
- Список: `GET /v1/calendars/:calendar_id/events`
|
- Список: `GET /v1/calendars/:calendar_id/events`
|
||||||
@@ -157,11 +167,16 @@ Redirects: `/discover` → `/search`; `/calendars/:id` → `/c/:id`; `/calendars
|
|||||||
- Владелец и specialist:
|
- Владелец и specialist:
|
||||||
- на карточке события: `GET /v1/events/:id/bookings` + Confirm/Decline (**обязательно**)
|
- на карточке события: `GET /v1/events/:id/bookings` + Confirm/Decline (**обязательно**)
|
||||||
- inbox `/bookings` группа «К подтверждению»: `GET /v1/user/booking-requests`
|
- inbox `/bookings` группа «К подтверждению»: `GET /v1/user/booking-requests`
|
||||||
(pending, где user — owner календаря или `event.specialist_id`); те же
|
(actionable pending, где user — owner календаря или `event.specialist_id`; past-pending
|
||||||
`PUT /v1/bookings/:id` `{ action: confirm | decline }`
|
Back отдаёт как `expired` и **не** включает в inbox); те же
|
||||||
|
`PUT /v1/bookings/:id` `{ action: confirm | decline }` (на `expired` → `409`)
|
||||||
- UI `/bookings`: две группы (пустые скрывать): **К подтверждению** + **Мои записи**
|
- UI `/bookings`: две группы (пустые скрывать): **К подтверждению** + **Мои записи**
|
||||||
(upcoming/past participant). `specialist_invite` сюда **не** попадает.
|
(upcoming/past participant). `specialist_invite` сюда **не** попадает.
|
||||||
- Статусы: `pending` | `confirmed` | `cancelled`; confirmation: `auto` | `manual` | timeout
|
Empty state: title + hint + CTA Discover / «Мои календари».
|
||||||
|
Время слота на list-карточках — soft (secondary to title).
|
||||||
|
- Статусы: `pending` | `confirmed` | `cancelled` | `expired`; confirmation: `auto` | `manual` | timeout.
|
||||||
|
Past+pending / `expired` в «Мои записи» — бейдж «истекла», Cancel скрыт (в т.ч. на
|
||||||
|
`EventActionCard`). Defensive: если API ещё отдал past `pending` — UI мапит в `expired`.
|
||||||
- Pending резервирует capacity (как на бэке)
|
- Pending резервирует capacity (как на бэке)
|
||||||
|
|
||||||
### 5.7. Отзывы
|
### 5.7. Отзывы
|
||||||
|
|||||||
@@ -378,6 +378,7 @@ error rate > 1% **или** mean request > 500 ms (hold ≥2 мин в фазе)
|
|||||||
|
|
||||||
| Дата | Изменение |
|
| Дата | Изменение |
|
||||||
|------|-----------|
|
|------|-----------|
|
||||||
|
| 2026-07-27 | Spec sync: booking `expired` (Back#60) + Front#31 UX (desktop chrome, empty bookings, agenda copy, Discover covers, nickname, invites badge) |
|
||||||
| 2026-07-27 | Spec#12: booking-requests + Bookings IA + image_url; правило one-repo-per-agent |
|
| 2026-07-27 | Spec#12: booking-requests + Bookings IA + image_url; правило one-repo-per-agent |
|
||||||
| 2026-07-27 | §6 / STAGE-BROWSER: канон — прямой HTTPS `*.calentiq.com`; proxy `:8787` только DNS-fallback |
|
| 2026-07-27 | §6 / STAGE-BROWSER: канон — прямой HTTPS `*.calentiq.com`; proxy `:8787` только DNS-fallback |
|
||||||
| 2026-07-22 | §6: Stage UI в Cursor Browser — `STAGE-BROWSER.md` + alias proxy |
|
| 2026-07-22 | §6: Stage UI в Cursor Browser — `STAGE-BROWSER.md` + alias proxy |
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ SoT: [`boards/calentiq-ui-board-final.png`](boards/calentiq-ui-board-final.png).
|
|||||||
| Bookings list §D | список заявок с Confirm/Decline на `/bookings` | `/bookings` = **grouped inbox**: (A) К подтверждению — pending owner/specialist + Confirm/Decline; (B) Мои записи — participant upcoming/past + Cancel; empty groups hidden. Event card Confirm/Decline **остаётся** обязательным |
|
| Bookings list §D | список заявок с Confirm/Decline на `/bookings` | `/bookings` = **grouped inbox**: (A) К подтверждению — pending owner/specialist + Confirm/Decline; (B) Мои записи — participant upcoming/past + Cancel; empty groups hidden. Event card Confirm/Decline **остаётся** обязательным |
|
||||||
| Login §D phone chrome | три phone frames + (на борде) Google CTA | brand-first hero + mood `--auth-*`; email/password only; mood switcher на login (иконки); taglines mood cards — на `/more`; **без** Google OAuth |
|
| Login §D phone chrome | три phone frames + (на борде) Google CTA | brand-first hero + mood `--auth-*`; email/password only; mood switcher на login (иконки); taglines mood cards — на `/more`; **без** Google OAuth |
|
||||||
| Favicon set | 3 отдельных favicon файла на борде | mood appicon через `applyMood` |
|
| Favicon set | 3 отдельных favicon файла на борде | mood appicon через `applyMood` |
|
||||||
| Desktop nav | phone bottom tabs на борде | desktop: top tabs (Календарь / Найти / Записи / Ещё); bottom nav — mobile |
|
| Desktop nav | phone bottom tabs на борде | desktop: **одна строка** chrome — logo \| nav (Календарь / Найти / Записи / Ещё) \| nickname + logout; bottom nav — mobile |
|
||||||
|
| Header identity | — | nickname primary (не raw email); email только в `title` tooltip |
|
||||||
|
| Bookings past/expired | — | `expired` бейдж + Cancel скрыт; empty-state с CTA Discover / календари; soft list time |
|
||||||
|
| Agenda empty copy | — | personal «Нет событий»; commercial «Нет слотов» |
|
||||||
|
| More invites | пункт меню | скрыт при pending=0; badge счётчика при pending > 0 |
|
||||||
| Stage | полный smoke на stage | **done** 2026-07-27: Login×3 / Calendar×3 + agenda / Discover / CONTROL commercial + Confirm·Decline / More moods |
|
| Stage | полный smoke на stage | **done** 2026-07-27: Login×3 / Calendar×3 + agenda / Discover / CONTROL commercial + Confirm·Decline / More moods |
|
||||||
| Домены calentiq.* | — | публичный stage: `stage.calentiq.com` / `admin.stage.calentiq.com` (канон STAGE-BROWSER.md) |
|
| Домены calentiq.* | — | публичный stage: `stage.calentiq.com` / `admin.stage.calentiq.com` (канон STAGE-BROWSER.md) |
|
||||||
|
|
||||||
@@ -32,7 +36,9 @@ IA без sidebar / без Creator mood — совпадает с non-goals (п
|
|||||||
|
|
||||||
**Polish in this pass:** agenda copy/casing/counts + time-first rows; CONTROL steel rail emphasis; neutral Discover cover in mock (не flow-appicon во всех moods).
|
**Polish in this pass:** agenda copy/casing/counts + time-first rows; CONTROL steel rail emphasis; neutral Discover cover in mock (не flow-appicon во всех moods).
|
||||||
|
|
||||||
**Remaining intentional:** no pixel-perfect phone chrome; Discover без photo-hero grid (cover via `image_url` when API returns it); §E specialist schedule card vs panel — не pixel-perfect board mocks (follow-up).
|
**Front#31 (2026-07-27):** desktop one-row chrome (logo|nav|logout); header nickname; bookings `expired` + soft list time + empty CTA; agenda personal vs commercial empty copy; Discover cover photo/fallback; More invites badge.
|
||||||
|
|
||||||
|
**Remaining intentional:** no pixel-perfect phone chrome; Discover без photo-hero grid (cover via `image_url` when API returns it; broken URL → wash+initials); §E specialist schedule card vs panel — не pixel-perfect board mocks (follow-up).
|
||||||
|
|
||||||
### Bookings inbox + More invites (locked 2026-07-27)
|
### Bookings inbox + More invites (locked 2026-07-27)
|
||||||
|
|
||||||
@@ -44,18 +50,25 @@ IA без sidebar / без Creator mood — совпадает с non-goals (п
|
|||||||
- **Группа B — Мои записи:** participant bookings (upcoming/past + Cancel) как раньше.
|
- **Группа B — Мои записи:** participant bookings (upcoming/past + Cancel) как раньше.
|
||||||
- Пустые группы скрывать (`count=0`).
|
- Пустые группы скрывать (`count=0`).
|
||||||
- Опционально подпись owner vs specialist внутри A; иначе одна секция «К подтверждению».
|
- Опционально подпись owner vs specialist внутри A; иначе одна секция «К подтверждению».
|
||||||
3. **`specialist_invite` в More:** скрывать пункт меню, если **0 pending** входящих. Deep-link `/invites?token=` работает. Исходящие invite остаются в `SpecialistsPanel`.
|
3. **`specialist_invite` в More:** скрывать пункт меню, если **0 pending** входящих; при
|
||||||
|
pending > 0 — **badge** со счётчиком. Deep-link `/invites?token=` работает. Исходящие
|
||||||
|
invite остаются в `SpecialistsPanel`.
|
||||||
4. **Не** класть строки `specialist_invite` в `/bookings`.
|
4. **Не** класть строки `specialist_invite` в `/bookings`.
|
||||||
|
5. **Past-pending / `expired`:** Back#60 помечает past-pending → `expired` и исключает из
|
||||||
|
`booking-requests`; Confirm/Decline → `409`. UI: бейдж «истекла», Cancel скрыт
|
||||||
|
(Bookings + EventActionCard); defensive map past+pending → expired badge.
|
||||||
|
|
||||||
API: `GET /v1/user/booking-requests` (Back) + существующий `PUT /v1/bookings/:id`.
|
API: `GET /v1/user/booking-requests` (Back) + существующий `PUT /v1/bookings/:id`
|
||||||
|
(`409` на expired / full).
|
||||||
|
|
||||||
### Follow-ups (вне этого fix-pass или частично)
|
### Follow-ups (вне этого fix-pass или частично)
|
||||||
|
|
||||||
| Тема | Статус |
|
| Тема | Статус |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| Discover `image_url` в search serialization | Back отдаёт поле; upload API **нет** — только URL field / seed |
|
| Discover `image_url` в search serialization | Back отдаёт поле; upload API **нет** — только URL field / seed; Front#31: photo vs fallback media classes + onError fallback |
|
||||||
| Stage Popular clutter (e2e calendars) | filter notes: см. `EventHubSpec/STAGE-POPULAR-CLEANUP.md`; не трогать реальные smoke данные без фильтра по e2e-паттернам |
|
| Stage Popular clutter (e2e calendars) | filter notes: см. `EventHubSpec/STAGE-POPULAR-CLEANUP.md`; не трогать реальные smoke данные без фильтра по e2e-паттернам |
|
||||||
| §E specialist schedule card vs panel | deviation: EventActionCard + SpecialistsPanel; отдельный pixel mock — Future |
|
| §E specialist schedule card vs panel | deviation: EventActionCard + SpecialistsPanel; отдельный pixel mock — Future |
|
||||||
|
| Desktop one-row chrome | **done** Front#31 — logo \| nav \| nickname+logout |
|
||||||
|
|
||||||
## Mood concept = product surface
|
## Mood concept = product surface
|
||||||
|
|
||||||
@@ -180,6 +193,8 @@ CSS-токены и chrome на mood beyond primary hex: surfaces, gradients, ev
|
|||||||
| 3 | Discover mood variants | [Front#27](https://git.sabilin.com/EventHub/EventHubFront/issues/27) |
|
| 3 | Discover mood variants | [Front#27](https://git.sabilin.com/EventHub/EventHubFront/issues/27) |
|
||||||
| 4 | CONTROL booking surfaces | [Front#28](https://git.sabilin.com/EventHub/EventHubFront/issues/28) |
|
| 4 | CONTROL booking surfaces | [Front#28](https://git.sabilin.com/EventHub/EventHubFront/issues/28) |
|
||||||
| 5 | Polish + stage smoke | [Front#29](https://git.sabilin.com/EventHub/EventHubFront/issues/29) |
|
| 5 | Polish + stage smoke | [Front#29](https://git.sabilin.com/EventHub/EventHubFront/issues/29) |
|
||||||
|
| — | P1/P2 UX polish + expired + desktop chrome | [Front#31](https://git.sabilin.com/EventHub/EventHubFront/issues/31) |
|
||||||
|
| — | Past-pending → `expired` | [Back#60](https://git.sabilin.com/EventHub/EventHubBack/issues/60) |
|
||||||
|
|
||||||
Эпик: [Spec#10](https://git.sabilin.com/EventHub/EventHubSpec/issues/10).
|
Эпик: [Spec#10](https://git.sabilin.com/EventHub/EventHubSpec/issues/10).
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user