Files
vidconf/frontend/README.md

329 lines
16 KiB
Markdown
Raw Permalink 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.
# 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` на `<html>`
- `useTheme().resolved` — текущая активная тема (явный выбор ИЛИ системная)
- `useTheme().setTheme(theme)` — задаёт явный выбор и сохраняет в localStorage
**Компонент:** `src/components/ui/ThemeToggle.tsx` — сегментированный переключатель (☀️ светлая, 🌙 тёмная):
```tsx
<ThemeToggle /> // Монтируется в ShellTopbar (лобби/календарь/админка)
// AuthLayout (login/register/verify-email)
// JoinPage (вход по номеру/ссылке)
```
**Анти-FOUC:** инлайн-скрипт в `frontend/index.html` (строки 826) выполняется до бандла 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:
```typescript
// ✓ Правильно
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](../backend/README.md).
## Процесс разработки
1. `npm run dev` — запустить dev сервер
2. Создать фичевую ветку
3. Добавить/обновить страницу или компонент в `src/pages/` или `src/components/`
4. `npm run lint && npm run build`
5. Создать коммит с описанием
Пример:
```
feat: добавить страницу профиля
- Реализовать ProfilePage с редактированием ФИО и команды
- Добавить функции в @/api/users.ts
ESLint: 0 ошибок
```
## Решение проблем
**Порт Vite dev сервера 5173 занят:**
```bash
npm run dev -- --port 3000
```
**API proxy не работает:**
Проверьте `vite.config.ts`:
```typescript
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
ws: true,
},
},
}
```
**Ошибки TypeScript:**
```bash
npx tsc -b --noEmit
npm run lint -- --fix
```
**Сборка не удаётся:**
```bash
npm install --force
npm run build
```
## Ссылки
- [Корневой README](../README.md) — обзор проекта
- [Справка API](../docs/api/README.md) — endpoint'ы
- [Дизайн-система](../design/DESIGN_SYSTEM.md) — рекомендации UI
- [Backend README](../backend/README.md) — детали FastAPI
- [Документация Vite](https://vitejs.dev/)
- [Документация React](https://react.dev/)
- [Документация Tailwind](https://tailwindcss.com/)