Files
vidconf/frontend/src/pages/LobbyPage.tsx
Max Ronzhin 10a3f8b3b4 feat(room): очередь поднятых рук видна всем + отключаемый модуль
Раньше HandQueueMenu.tsx рендерился только организатору — теперь очередь
видит любой участник, но опустить чужую руку по-прежнему может только
организатор (сервер это уже проверял, менял только фронт). Кнопка
«Опустить» показывается у записи, только если это своя рука или
пользователь — организатор.

Модуль «поднятие руки» (кнопка «Рука» + очередь целиком) — отключаемый
в админке (instance_settings.hand_queue, дефолт enabled=true, как у
chat_enabled). Настройка едет участнику в JoinOut ещё до входа в
комнату; выключенный модуль гасит кнопки и на фронте, и на бэке —
raise_hand/lower_hand отклоняются кодом hand_queue_disabled, если
модуль выключен, даже если у клиента на руках старый JoinOut.
2026-08-04 18:27:14 +03:00

124 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Calendar, Link2, User, Video } from 'lucide-react'
import { ShellTopbar } from '@/components/layout/ShellTopbar'
import { AppFooter } from '@/components/layout/AppFooter'
import { LogoMedallion } from '@/components/ui/LogoMark'
import { createConference } from '@/api/conferences'
import { useToast } from '@/components/ui/ToastProvider'
import '@/styles/lobby.css'
/**
* Лобби — «хаб действий» (см. design/mockups/lobby.html).
*
* Предустановленных переговорных комнат больше нет: конференции создаются
* динамически. Мозаика из 4 плиток — единственный вход в остальные сценарии:
* мгновенное создание, календарь плановых конференций, вход по ссылке/номеру,
* «Мои конференции».
*
* Отклонение от макета: топбар здесь — переиспользуемый `ShellTopbar` (прямые
* ссылки Лобби/Календарь/Админка + пилюля пользователя), а не бургер-меню из
* lobby.html. `ShellTopbar` — общий компонент светлой оболочки (используется
* также в календаре/админке), поэтому бургер-меню из макета здесь
* сознательно не воспроизводится.
*/
export function LobbyPage() {
const navigate = useNavigate()
const toast = useToast()
const [creating, setCreating] = useState(false)
async function handleCreate() {
if (creating) return
setCreating(true)
try {
const conference = await createConference({})
if (!conference.join) {
toast.show('Не удалось создать конференцию. Попробуйте ещё раз', 'error')
return
}
navigate(`/room/${conference.slug}`, {
state: {
livekitUrl: conference.join.livekit_url,
token: conference.join.token,
title: conference.title,
conferenceId: conference.join.conference_id,
chatEnabled: conference.join.chat_enabled,
handQueueEnabled: conference.join.hand_queue_enabled,
publishQualityCap: conference.join.publish_quality_cap,
stageMaxTiles: conference.join.stage_max_tiles,
number: conference.number,
slug: conference.slug,
},
})
} catch {
toast.show('Не удалось создать конференцию. Попробуйте ещё раз', 'error')
} finally {
setCreating(false)
}
}
return (
<div className="lobby-shell">
<ShellTopbar />
<main className="hub-main">
<div className="hub-wrap">
<div className="hub-intro">
<p className="eyebrow">[ ДОБРО ПОЖАЛОВАТЬ В VIDCONF ]</p>
<h1>Что делаем сегодня?</h1>
</div>
<div className="hub">
<button
type="button"
className="action action--create"
onClick={handleCreate}
disabled={creating}
aria-busy={creating}
>
<span className="action-icon" aria-hidden="true">
<Video className="icon" aria-hidden="true" />
</span>
<h2 className="action-title">Создать</h2>
<p className="action-object">конференцию</p>
<p className="action-note">{creating ? 'Создаём…' : 'начнётся сразу — вы первый участник'}</p>
</button>
<button type="button" className="action action--calendar" onClick={() => navigate('/calendar')}>
<span className="action-icon" aria-hidden="true">
<Calendar className="icon" aria-hidden="true" />
</span>
<h2 className="action-title">Календарь</h2>
<p className="action-object">конференций</p>
<p className="action-note">запланировать · закрепить · повторять</p>
</button>
<button type="button" className="action action--join" onClick={() => navigate('/join')}>
<span className="action-icon" aria-hidden="true">
<Link2 className="icon" aria-hidden="true" />
</span>
<h2 className="action-title">Подключиться</h2>
<p className="action-object">к конференции</p>
<p className="action-note">по ссылке или номеру</p>
</button>
<button type="button" className="action action--mine" onClick={() => navigate('/my-conferences')}>
<span className="action-icon" aria-hidden="true">
<User className="icon" aria-hidden="true" />
</span>
<h2 className="action-title">Мои</h2>
<p className="action-object">конференции</p>
<p className="action-note">закреплённые и предстоящие</p>
</button>
<div className="hub-logo" aria-hidden="true">
<LogoMedallion className="logo-mark" />
<span className="logo-word">VidConf</span>
</div>
</div>
</div>
</main>
<AppFooter />
</div>
)
}