Files
vidconf/frontend
Max Ronzhin b35209a775
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
feat(admin): контактный адрес и тестовое письмо в настройках админки
Поле включения/адреса в карточке «Контактный адрес» и кнопка «Отправить
тестовое письмо» с выводом результата — рядом, по образцу соседних
настроек AdminSettingsTab.
2026-07-27 00:35:59 +03:00
..

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() — читает из localStorage
  • applyThemeAttribute(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 (строки 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:

// ✓ Правильно
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.

Процесс разработки

  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 занят:

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

Ссылки