# 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. Установка зависимостей
```bash
cd frontend
npm install
```
### 2. Переменные окружения
Отдельный `.env` для frontend не требуется: Vite proxy перенаправляет
`/api/*` (включая WebSocket-чат) на backend по адресу `http://localhost:8000`
(см. `vite.config.ts`).
### 3. Запуск dev сервера
```bash
npm run dev
```
Запущен на `http://localhost:5173` с HMR.
### 4. Сборка для продакшена
```bash
npm run build
npm run preview
```
## Скрипты
```bash
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`):
```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()` — читает из localStorage
- `applyThemeAttribute(theme)` — проставляет атрибут `data-theme` на ``
- `useTheme().resolved` — текущая активная тема (явный выбор ИЛИ системная)
- `useTheme().setTheme(theme)` — задаёт явный выбор и сохраняет в localStorage
**Компонент:** `src/components/ui/ThemeToggle.tsx` — сегментированный переключатель (☀️ светлая, 🌙 тёмная):
```tsx