Перейти к содержанию

Веб-клиент

Стек

  • Next.js 15 App Router
  • TypeScript
  • Tailwind CSS + shadcn/ui
  • WebSocket native client
  • Telegram Login Widget (optional)

Экраны

Route Описание FR
/ Landing, CTA create/join FR-047
/join Ввод кода приглашения FR-002
/session/[id]/lobby Участники, выбор игры FR-004
/session/[id]/game/[gameId] Игровое поле FR-048
/profile Профиль, история FR-049
/auth/login Login / register FR-007
/admin/login Admin OTP login FR-058
/admin/games Список игр (логи) FR-057
/admin/games/[id] Timeline лога игры FR-057

Session flow

sequenceDiagram
    participant U as User
    participant W as web-app
    participant API as API
    participant WS as realtime

    U->>W: Open /join
    W->>API: POST /auth/guest or JWT
    W->>API: POST /sessions/join
    Note over W: сохраняет participant_id в localStorage
    W->>WS: Connect + subscribe session topic
    WS-->>W: session events
    U->>W: Host starts game
    W->>API: POST /sessions/{id}/games
    Note over W: 2-й игрок: WS game_started или poll active_game_id → /game/{id}
    W->>WS: subscribe game topic

Игровой экран (/session/[id]/game/[gameId])

Общее для всех плагинов:

  • GameNav — ссылки «В лобби» и «На главную» (всегда видны)
  • Заголовок и фаза по plugin_id из GET /games/{id}/state
  • Кнопки/поля по view.actions_available (сервер учитывает viewer_id из auth)

dice_board

  • BoardGrid 6×6 — см. ui-assets
  • «Бросить кубики» → roll_dice
  • Клик клетки → choose_cell

kontakt

  • Фаза Association: поле + «Задать ассоциацию» → set_association
  • Фаза Contacting: два игрока отправляют слово (contact); до второго ответа слова не видны
  • После двух ответов слова показываются всем; совпадение → конец игры, иначе новый раунд
  • Ведущий: «Открыть загаданное слово» → reveal

shlyapa

  • Объясняющий видит секретное слово и список отгадок с кнопками «Верно» / «Неверно»
  • Угадывающие отправляют guess; очки начисляет объясняющий через judge_guess

yatzy

  • Бросок 5 костей → удержание выбранных → запись в одну из 13 категорий scorecard
  • roll_dice, keep_dice, score_category

koaliciya

  • Кооператив: каждый видит свою руку (числа 1–100), кладёт карту кнопкой play_card
  • Стол и жизни общие; порядок на столе должен возрастать

memo

  • Сетка 4×4: клик по ?flip_tile; пары остаются открытыми, промах — ход сопернику

WebSocket и синхронизация

  • Подключение WS после ensureGuest (иначе токена ещё нет)
  • На игровом экране — poll GET /games/{id}/state каждые 3 с (fallback)
  • После game.state_changed — refresh для viewer-specific actions_available
  • Reconnect WS восстанавливает подписки на топики

Навигация

  • «В лобби» ведёт на /session/{id}/lobby?stay=1не редиректит обратно в активную игру
  • В лобби при активной игре: кнопка «Вернуться в игру» для всех участников
  • Хост в лобби: «Завершить игру для всех»POST /sessions/{id}/games/end
  • Авто-переход в игру из лобби — только без stay=1 (для 2-го игрока при старте)

WebSocket

// Reconnect with exponential backoff
const ws = new WebSocket(`${WS_URL}?token=${token}`);
ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  if (msg.type === 'game.state_changed') setGameState(msg.state);
};

On reconnect: GET /games/{id}/state before resuming UI.

Dual presence

  • Tab focus → report control_channel: web heartbeat
  • Read-only banner if control on TG

Адаптив

  • Mobile-first: поле масштабируется min(90vw, 400px)
  • Touch targets ≥ 44px

FR/NFR

FR-047, FR-048, FR-050, FR-025, FR-028, NFR-019, NFR-020