329 lines
16 KiB
Markdown
329 lines
16 KiB
Markdown
# 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` (строки 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:
|
||
|
||
```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/)
|