# 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/)
