Files
vidconf/frontend/src/pages/LobbyPage.tsx
Max Ronzhin 3290eb5d07 feat(room): показывать название конференции вместо технического room_name
RoomTopbar рендерил `JoinOut.room_name` — техническое имя комнаты LiveKit
вида `hC-Dmos9KEM`, которое туда попало по ошибке (перепутано с
человекочитаемым title конференции). Теперь топбар получает `title`
(из резолва номера/ссылки или из карточки конференции в лобби) и падает
на «Конференция без названия», если организатор его не задал. Номер и
slug остаются на своих местах — в инвайт-чипе и в адресе, где они нужны
функционально.
2026-07-30 00:23:18 +03:00

121 lines
5.3 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,
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>
)
}