Кнопка со смайликом слева от поля ввода открывает поповер с небольшим собственным набором популярных эмодзи (без библиотеки-пикера) — выбор вставляется в позицию курсора. Поповер открывается вверх от кнопки, поэтому на мобильном никогда не перекрывает textarea; закрытие — по клику вне и по Escape (переиспользован useModalDismiss).
Frontend — React SPA
React-приложение VidConf: аутентификация, лобби-хаб, календарь и «мои конференции», вход по номеру/ссылке (в т.ч. гостем), комната конференции на LiveKit с чатом в реальном времени, профиль пользователя, админ-панель. Поддерживает светлую и тёмную темы оформления.
Стек технологий
- Фреймворк: React 18 + TypeScript
- Сборка: Vite (dev сервер с HMR)
- Стили: Tailwind CSS 4 + @tailwindcss/vite
- Компоненты: shadcn/ui,
@base-ui/react - Видео:
@livekit/components-react+livekit-client - Календарь:
@fullcalendar/react(day-grid + interaction) - HTTP: fetch API (клиент в
src/api/client.ts)
Структура проекта
src/
├── pages/ Компоненты страниц верхнего уровня
│ ├── LoginPage.tsx Вход
│ ├── RegisterPage.tsx Регистрация (выбор команды)
│ ├── VerifyEmailPage.tsx Подтверждение email по токену из письма
│ ├── LobbyPage.tsx Лобби-хаб (2×2 плитки: создать/календарь/подключиться/мои)
│ ├── CalendarPage.tsx Календарь (FullCalendar), создание/редактирование/отмена конференций
│ ├── MyConferencesPage.tsx "Мои конференции" с фильтрами (Все/Закреплённые/Предстоящие)
│ ├── ProfilePage.tsx Профиль пользователя (ФИО, команда, аватар, смена пароля)
│ ├── JoinPage.tsx Вход в конференцию по номеру/ссылке (/j/{slug}), гостевой экран
│ ├── RoomPage.tsx Комната конференции (LiveKit): видео, чат, тулбар
│ └── AdminPage.tsx Админ-панель (конференции, пользователи, команды, настройки)
├── components/
│ ├── ui/ shadcn/ui + расширения
│ │ ├── button.tsx
│ │ ├── Avatar.tsx Аватар с заглушкой (инициалы)
│ │ ├── Select.tsx Кастомный Select по дизайн-системе
│ │ ├── ConferenceHoverCard.tsx Ховер-карточка конференции
│ │ ├── CopyPill.tsx Копирование номера/ссылки
│ │ ├── LogoMark.tsx Логотип
│ │ ├── ThemeToggle.tsx Переключатель светлая/тёмная
│ │ └── ToastProvider.tsx Уведомления
│ ├── calendar/
│ │ ├── ConferenceCalendar.tsx FullCalendar-компонент
│ │ ├── ConferenceFormCard.tsx Форма создания/редактирования конференции
│ │ ├── ConferenceOccurrenceDialog.tsx Диалог вхождения повторяющейся конференции
│ │ └── ParticipantsPicker.tsx Кастомный Select для выбора участников с поиском
│ ├── admin/
│ │ ├── AdminConferencesTab.tsx
│ │ ├── AdminUsersTab.tsx / AdminUserCreateDialog.tsx / AdminUserProfileDialog.tsx
│ │ ├── AdminTeamsTab.tsx
│ │ └── AdminSettingsTab.tsx
│ ├── auth/
│ │ └── AuthLayout.tsx Общий layout экранов входа/регистрации
│ ├── layout/
│ │ └── ShellTopbar.tsx Верхняя панель оболочки (лобби/календарь/админка)
│ └── room/
│ ├── RoomStage.tsx / RoomParticipantTile.tsx Видеосетка участников
│ ├── RoomTopbar.tsx / RoomToolbar.tsx Панели управления комнатой
│ ├── ChatPanel.tsx Панель текстового чата
│ └── DeviceSettingsDialog.tsx Выбор камеры/микрофона
├── auth/
│ ├── AuthProvider.tsx / authContext.ts / authStore.ts Хранение сессии, refresh
│ ├── useAuth.ts Hook доступа к текущему пользователю
│ ├── RequireAuth.tsx Route-guard: требует вход
│ └── RequireAdmin.tsx Route-guard: требует роль admin
├── api/
│ ├── client.ts Fetch-клиент (перехват токенов, обработка 401 → refresh)
│ ├── health.ts getHealth()
│ ├── auth.ts register(), login(), verifyEmail(), refreshToken(), logout()
│ ├── conferences.ts createConference(), listMyConferences(), getCalendar(),
│ │ resolveConference(), joinConference(), guestJoinConference(),
│ │ updateConference(), deleteConference()
│ ├── users.ts Профиль, аватары, смена пароля, поиск пользователей
│ └── admin.ts Админ-функции (конференции, пользователи, команды, настройки)
├── hooks/
│ ├── useChat.ts WebSocket-чат конференции (подключение, история, отправка)
│ ├── useTheme.ts Светлая/тёмная тема (localStorage + системный автодетект)
│ ├── useFullscreen.ts Полноэкранный режим комнаты
│ └── useRoomPiP.ts Document Picture-in-Picture для комнаты
├── lib/
│ ├── utils.ts
│ ├── localTime.ts Форматирование времени в локальном часовом поясе
│ ├── recurrenceFormat.ts Человекочитаемое описание правила повторения
│ ├── parseJoinQuery.ts Разбор номера/ссылки конференции из строки
│ └── pluralize.ts
├── styles/ Tailwind + токены дизайн-системы (per-page css + tokens.css)
├── App.tsx Корневой компонент с маршрутизацией (React Router)
├── main.tsx Точка входа
└── index.css Глобальные стили Tailwind
Маршруты (App.tsx)
| Путь | Страница |
|---|---|
/login, /register, /verify-email |
Аутентификация |
/lobby |
Лобби-хаб (требует вход) |
/join, /j/:slug |
Вход в конференцию по ссылке/номеру |
/room/:slug |
Комната конференции |
/calendar |
Календарь (требует вход) |
/my-conferences |
Мои конференции (требует вход) |
/profile |
Профиль (требует вход) |
/admin |
Админ-панель (требует роль admin) |
Быстрый старт
Требования
- Node 20+
- npm
1. Установка зависимостей
cd frontend
npm install
2. Переменные окружения
Отдельный .env для frontend не требуется: Vite proxy перенаправляет
/api/* (включая WebSocket-чат) на backend по адресу http://localhost:8000
(см. vite.config.ts).
3. Запуск dev сервера
npm run dev
Запущен на http://localhost:5173 с HMR.
4. Сборка для продакшена
npm run build
npm run preview
Скрипты
npm run dev # Запустить Vite dev сервер
npm run lint # ESLint
npm run build # tsc -b (проверка типов) + сборка для продакшена
npm run preview # Локальный просмотр production-сборки
Отдельного type-check-скрипта нет — проверка типов встроена в npm run build
(tsc -b); для быстрой проверки без сборки: npx tsc -b --noEmit.
Аутентификация
src/auth/ хранит сессию (access-токен в памяти, refresh — в httpOnly
cookie), предоставляет useAuth() и route-guard'ы RequireAuth/RequireAdmin.
src/api/client.ts автоматически подставляет access-токен и обновляет его при
401 через refreshToken().
Комната конференции
RoomPage.tsx подключается к LiveKit через @livekit/components-react
(LiveKitRoom), рендерит видеосетку (RoomStage/RoomParticipantTile),
тулбар управления (RoomToolbar), диалог настройки устройств
(DeviceSettingsDialog), полноэкранный режим (useFullscreen) и
Picture-in-Picture (useRoomPiP). Чат конференции — ChatPanel.tsx поверх
хука useChat (WebSocket, история последних сообщений, broadcast, отметка
гостей, счётчик непрочитанных); показывается только если бэкенд вернул
chat_enabled: true в JoinOut.
Темы оболочки
Оболочка (auth, лобби, календарь, join, «мои конференции», админка) поддерживает светлую и тёмную тему; комната конференции всегда тёмная, независимо от выбора.
Механизм переключения
- Светлая тема — дефолт, если пользователь не делал явного выбора и системная тема светлая.
- Тёмная тема — см.
design/mockups/dark/README.md. - Системная тема — автодетект
@media (prefers-color-scheme: dark), если явного выбора нет. - Явный выбор — переключатель (☀️/🌙) сохраняет выбор в
localStorageс ключомvidconf-theme('light'или'dark').
Реализация
Токены: design/tokens.css (синхронная копия в frontend/src/styles/tokens.css):
/* Светлая — :root */
:root { --color-bg: #F1F1F1; /* … */ }
/* Тёмная — явный выбор */
:root[data-theme="dark"] { --color-bg: #1E1E1E; /* … */ }
/* Тёмная — автодетект ОС (если data-theme не проставлен) */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) { --color-bg: #1E1E1E; /* … */ }
}
/* Комната конференции — всегда тёмная, независимо от выбора */
[data-theme="room"] { --color-room-bg: #1E1E1E; /* … */ }
Hook: src/hooks/useTheme.ts — управление явным выбором:
readStoredTheme()— читает из localStorageapplyThemeAttribute(theme)— проставляет атрибутdata-themeна<html>useTheme().resolved— текущая активная тема (явный выбор ИЛИ системная)useTheme().setTheme(theme)— задаёт явный выбор и сохраняет в localStorage
Компонент: src/components/ui/ThemeToggle.tsx — сегментированный переключатель (☀️ светлая, 🌙 тёмная):
<ThemeToggle /> // Монтируется в ShellTopbar (лобби/календарь/админка)
// AuthLayout (login/register/verify-email)
// JoinPage (вход по номеру/ссылке)
Анти-FOUC: инлайн-скрипт в frontend/index.html (строки 8–26) выполняется до бандла React и проставляет data-theme на <html> ДО первого рендера, если в localStorage есть явный выбор. Это исключает вспышку светлой темы при загрузке тёмного интерфейса.
Инвариант: комната всегда отдельная
Экран конференции (RoomPage.tsx, [data-theme="room"]) — единственное место, использующее свой тёмный набор токенов --color-room-* (§1.2a DESIGN_SYSTEM.md). Переключатель оболочки на неё не влияет.
Палитра тёмной оболочки
Получена переиспользованием токенов комнаты (не новых цветов, а уже утверждённых). Детали о смешивании, контрасте и исключениях (например, --color-danger-solid для кнопок) — см. design/DESIGN_SYSTEM.md §0.1 и §1.2b, design/mockups/dark/README.md.
Стили
Tailwind CSS 4:
- Утилита-первый подход к стилям
- Тёмная тема через атрибут
data-theme - Дизайн-токены из
design/DESIGN_SYSTEM.md(design/tokens.css)
Компоненты shadcn/ui:
- Предварительно построенные, без стилей (настраиваются с Tailwind)
Дизайн-система:
Дизайн-система и спецификации см. в design/DESIGN_SYSTEM.md:
- Цветовая палитра, типография, спецификации компонентов
- Макеты в
design/mockups/(светлые) иdesign/mockups/dark/(тёмные)
TypeScript
Весь код должен быть type-safe:
// ✓ Правильно
interface PageProps {
userId: string
}
function MyComponent({ userId }: PageProps) {
return <div>{userId}</div>
}
// ✗ Неправильно (implicit any)
function BadComponent(props) {
return <div>{props.userId}</div>
}
Запустите npm run build (или npx tsc -b --noEmit) для проверки.
Тестирование
Автоматических frontend-тестов (unit/E2E) в проекте пока нет — проверка качества ограничивается ESLint и TypeScript. Backend покрыт pytest, см. backend/README.md.
Процесс разработки
npm run dev— запустить dev сервер- Создать фичевую ветку
- Добавить/обновить страницу или компонент в
src/pages/илиsrc/components/ npm run lint && npm run build- Создать коммит с описанием
Пример:
feat: добавить страницу профиля
- Реализовать ProfilePage с редактированием ФИО и команды
- Добавить функции в @/api/users.ts
ESLint: 0 ошибок
Решение проблем
Порт Vite dev сервера 5173 занят:
npm run dev -- --port 3000
API proxy не работает:
Проверьте vite.config.ts:
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
ws: true,
},
},
}
Ошибки TypeScript:
npx tsc -b --noEmit
npm run lint -- --fix
Сборка не удаётся:
npm install --force
npm run build
Ссылки
- Корневой README — обзор проекта
- Справка API — endpoint'ы
- Дизайн-система — рекомендации UI
- Backend README — детали FastAPI
- Документация Vite
- Документация React
- Документация Tailwind