feat(tradein/ui): окно чата поддержки на сайте вместо ухода в Telegram #2533

Merged
lekss361 merged 3 commits from feat/tradein-web-chat-ui into main 2026-07-26 20:52:31 +00:00
Owner

Summary

Клиент теперь общается с поддержкой в окне на странице и не уходит в Telegram. Кнопка «Поддержка» из внешней ссылки на @MERAsupport_bot превратилась в открывающуюся панель 360×520; пункт «Помощь» в верхнем меню открывает её же.

Под капотом ничего не меняется для оператора: сообщения уходят в тот же топик супергруппы, ответ реплаем возвращается в окно.

⚠️ Порядок выката

Мержить и деплоить строго ПОСЛЕ #2532 (серверная часть). Иначе окно откроется, а запросы уйдут в 404 — не падение, но фича будет мёртвой.

Что внутри

  • Лента сообщений с различением своих и операторских, автопрокруткой, временем.
  • Ввод: Enter отправляет, Shift+Enter переносит строку; блокировка пустого и превышения 4000 символов.
  • Опрос с двумя предохранителями: сообщения опрашиваются только при открытой панели (6 с), счётчик непрочитанного — только при закрытой (20 с); хуки взаимоисключающие, одновременно активными не бывают. В неактивной вкладке TanStack Query по умолчанию не рефетчит (refetchIntervalInBackground: false — проверено по исходникам queryObserver, не по памяти).
  • Бейдж непрочитанного на закрытой кнопке, отметка прочитанным при открытии.
  • Честные состояния: загрузка, ошибка с кнопкой «Повторить», пустая история с приветствием — три разные ветки, а не одна пустота. Текст ошибки берётся с сервера (429 «слишком часто», 503 «поддержка недоступна»), черновик при ошибке отправки не теряется.
  • Доступность: ловушка фокуса и Esc портированы из LocationDrawer, начальный фокус в поле ввода, aria-label на кнопках.

Осознанные решения

Ссылка на Telegram не удалена, а осталась запасным путём внутри окна. Причина: push-уведомлений у веб-чата нет (решение владельца продукта — брали самый простой вариант). Если оператор ответит через час, клиент, ушедший со страницы, ответа не увидит — и у него должен остаться способ получить его в телефон.

createPortal в document.body для кнопки и панели вместе. /v2 рисует интерфейс внутри артборда с transform: scale(), а position: fixed внутри трансформированного предка позиционируется относительно него, а не вьюпорта.

z-index 45 — выше кнопки (25) и всех оверлеев v2, но ниже верхнего меню (50) и меню пользователя (200), чтобы не воровать у них клики. Значения сверены с реальными литералами в коде, не по памяти.

Сообщения оператора рендерятся текстовым узлом с white-space: pre-wrap, без dangerouslySetInnerHTML и без автолинковки — это недоверенный ввод, приходящий из Telegram.

Test plan

  • npx tsc --noEmit — чисто
  • next lint по изменённым файлам — чисто
  • Ревью: XSS, поллинг, ловушка фокуса, z-index, портал, обработка ошибок — подтверждены построчно
  • Реальный round-trip с бэкендом — обе ветки вместе не разворачивались
  • После деплоя: отправить сообщение из окна, ответить реплаем в топике, проверить доставку и бейдж
## Summary Клиент теперь общается с поддержкой в окне на странице и не уходит в Telegram. Кнопка «Поддержка» из внешней ссылки на `@MERAsupport_bot` превратилась в открывающуюся панель 360×520; пункт «Помощь» в верхнем меню открывает её же. Под капотом ничего не меняется для оператора: сообщения уходят в тот же топик супергруппы, ответ реплаем возвращается в окно. ## ⚠️ Порядок выката **Мержить и деплоить строго ПОСЛЕ #2532** (серверная часть). Иначе окно откроется, а запросы уйдут в 404 — не падение, но фича будет мёртвой. ## Что внутри - Лента сообщений с различением своих и операторских, автопрокруткой, временем. - Ввод: Enter отправляет, Shift+Enter переносит строку; блокировка пустого и превышения 4000 символов. - **Опрос с двумя предохранителями:** сообщения опрашиваются только при открытой панели (6 с), счётчик непрочитанного — только при закрытой (20 с); хуки взаимоисключающие, одновременно активными не бывают. В неактивной вкладке TanStack Query по умолчанию не рефетчит (`refetchIntervalInBackground: false` — проверено по исходникам `queryObserver`, не по памяти). - Бейдж непрочитанного на закрытой кнопке, отметка прочитанным при открытии. - **Честные состояния:** загрузка, ошибка с кнопкой «Повторить», пустая история с приветствием — три разные ветки, а не одна пустота. Текст ошибки берётся с сервера (429 «слишком часто», 503 «поддержка недоступна»), черновик при ошибке отправки не теряется. - Доступность: ловушка фокуса и Esc портированы из `LocationDrawer`, начальный фокус в поле ввода, `aria-label` на кнопках. ## Осознанные решения **Ссылка на Telegram не удалена**, а осталась запасным путём внутри окна. Причина: push-уведомлений у веб-чата нет (решение владельца продукта — брали самый простой вариант). Если оператор ответит через час, клиент, ушедший со страницы, ответа не увидит — и у него должен остаться способ получить его в телефон. **`createPortal` в `document.body` для кнопки и панели вместе.** `/v2` рисует интерфейс внутри артборда с `transform: scale()`, а `position: fixed` внутри трансформированного предка позиционируется относительно него, а не вьюпорта. **z-index 45** — выше кнопки (25) и всех оверлеев v2, но ниже верхнего меню (50) и меню пользователя (200), чтобы не воровать у них клики. Значения сверены с реальными литералами в коде, не по памяти. **Сообщения оператора рендерятся текстовым узлом** с `white-space: pre-wrap`, без `dangerouslySetInnerHTML` и без автолинковки — это недоверенный ввод, приходящий из Telegram. ## Test plan - [x] `npx tsc --noEmit` — чисто - [x] `next lint` по изменённым файлам — чисто - [x] Ревью: XSS, поллинг, ловушка фокуса, z-index, портал, обработка ошибок — подтверждены построчно - [ ] Реальный round-trip с бэкендом — обе ветки вместе не разворачивались - [ ] После деплоя: отправить сообщение из окна, ответить реплаем в топике, проверить доставку и бейдж
lekss361 added 3 commits 2026-07-26 20:38:25 +00:00
Клиент теперь общается с поддержкой прямо на странице /v2 — SupportButton
открывает панель чата (SupportChatPanel) вместо перехода на t.me. Под капотом
сообщения по-прежнему зеркалятся в тот же Telegram-топик (backend contract:
feat/tradein-web-chat-backend, /api/v1/trade-in/support/*), оператор отвечает
как раньше, клиент этого не видит.

- useSupportChat.ts (src/lib): TanStack Query polling — сообщения только пока
  панель открыта (5-10с), unread-бейдж только пока закрыта (20с); pause на
  неактивной вкладке — встроенное поведение refetchInterval
  (refetchIntervalInBackground по умолчанию false), без ручной
  visibilitychange-логики.
- SupportChatContext.tsx: общее open/close состояние поверх React Context —
  TopNav («Помощь») и SupportButton не являются родителем/потомком друг
  друга, оба открывают ОДНО и то же окно.
- SupportChatPanel.tsx: лента + композер (Enter отправляет, Shift+Enter —
  перенос, лимит 4000 символов со счётчиком), честные состояния (loading /
  error+retry / empty-welcome / 429 / 503 — тексты уже человекочитаемы на
  бэкенде), focus-trap + Esc (мирроринг LocationDrawer), plain-text рендер
  сообщений оператора (untrusted input, без dangerouslySetInnerHTML).
  Telegram-ссылка остаётся маленьким запасным вариантом внутри окна — у
  веб-чата нет push, поэтому это осознанный fallback, не удаление.
- z-index 45 для панели: выше кнопки (25) и LocationDrawer (40/41), ниже
  TopNav (50) и UserMenu (200) — не ворует клики у верхней навигации.
Merge remote-tracking branch 'forgejo/main' into feat/tradein-web-chat-ui
All checks were successful
CI / changes (pull_request) Successful in 15s
CI Trade-In / changes (pull_request) Successful in 15s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m13s
46e87c1ba2
lekss361 merged commit a0647a53a9 into main 2026-07-26 20:52:31 +00:00
lekss361 deleted branch feat/tradein-web-chat-ui 2026-07-26 20:52:31 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2533
No description provided.