# Frontend SPA на **React 19 + Vite + TypeScript**. Общается с бэком по REST (JWT Bearer) и получает живые обновления по SignalR. Типы API генерируются из OpenAPI-схемы бэкенда. > **Раздача из единого контейнера.** В проде собранный фронт (`dist/`) кладётся в `wwwroot` > ASP.NET Core и раздаётся тем же приложением (SPA-fallback на `index.html`). Фронт и бек — один > origin, база API — относительный `/api`, SignalR — `/hubs/panel`. В dev Vite-сервер проксирует > `/api` и `/hubs` на бэкенд. Детали упаковки — [architecture.md](architecture.md#развёртывание-единый-контейнер-приложения). ## Стек | Задача | Выбор | | ----------------- | --------------------------------------- | | Сборка/dev | Vite | | Язык | TypeScript (strict) | | Данные с сервера | TanStack Query | | Роутинг | TanStack Router (типобезопасный) | | UI-компоненты | shadcn/ui + Tailwind CSS v4 | | Иконки | lucide-react | | Клиентский стейт | Zustand (auth, тема) | | Формы | react-hook-form + zod | | Realtime | @microsoft/signalr | | Графики | Recharts | | QR-коды | qrcode.react | | Типы API | openapi-typescript / orval (codegen) | | i18n | react-i18next (RU + EN) | | Пакетный менеджер | pnpm | ## Структура ``` frontend/ src/ app/ # провайдеры (Query, Router, Auth, Theme), корневой layout routes/ # маршруты TanStack Router (login, dashboard, configs, admin/*) features/ auth/ # формы, хуки useLogin/useRegister, стор авторизации configs/ # список/создание/детали конфигов, QR, ссылка-подписка nodes/ # (admin) управление нодами admin/ # пользователи, статистика shared/ api/ # http-клиент (fetch + JWT/refresh), сгенерированные типы, query-хуки realtime/ # инициализация SignalR, подписки → инвалидация Query-кэша ui/ # обёртки над shadcn/ui, общие компоненты lib/ # утилиты, форматирование (байты, даты) config/ # env, константы styles/ # tailwind, темы index.html vite.config.ts package.json ``` ## Дизайн / UX - **Тема оформления**: светлая и тёмная (переключатель в шапке; вариант «системная»). Реализация — Tailwind `dark` (класс на `html`) + shadcn/ui; выбор сохраняется (localStorage). - **Современный и чистый вид**: shadcn/ui + Tailwind, адаптивность, аккуратная типографика. - **Дашборд пользователя**: карточки конфигов (протокол, локация, трафик прогресс-баром, срок, статус), быстрые действия (копировать ссылку, показать QR, перевыпустить, отозвать) + карточка «Общая подписка» (агрегированная ссылка/QR со всеми конфигами). Для неактивированного — экран «запросить активацию». - **Создание конфига**: выбор локации/inbound (по `DisplayName`) + метка + лимит устройств → мгновенная выдача ссылки + QR + краткие инструкции по подключению (iOS/Android/Windows). - **Редактирование конфига**: изменить метку и лимит устройств. - **Настройки аккаунта**: смена пароля, привязка/отвязка Telegram, **удаление аккаунта** (с подтверждением). - **Админка**: таблицы (TanStack Table) с пагинацией/фильтрами для нод, пользователей, конфигов, ролей, журнала аудита; очередь запросов активации; графики трафика (Recharts). Блокировка пользователя — с подтверждением (гасит VPN). - **Состояния**: скелетоны при загрузке, аккуратные пустые состояния и toasts на ошибки/успех. ## Работа с API - HTTP-клиент оборачивает `fetch`: подставляет access-token, при 401 — прозрачно обновляет через refresh-cookie и повторяет запрос; при неуспехе — разлогин. - Все запросы/мутации — через TanStack Query (ключи по фичам, инвалидация после мутаций). - Типы ответов/запросов — из codegen по OpenAPI (никакого ручного дублирования DTO). ## Авторизация на клиенте - **Access-token** — в памяти (не в localStorage), кладётся в `Authorization: Bearer`. - **Refresh-token** — httpOnly Secure cookie (JS не читает), ротация на сервере. - Стор авторизации (Zustand) хранит профиль/роли/`isActivated`; guard-маршруты по роли (`admin` vs обычный) и по активации (неактивированного ведём на экран «запросить активацию»). - **Вход — по username** (email не используется). «Забыли пароль?» ведёт: при привязанном Telegram — восстановление через бота; иначе — подсказка обратиться к админу. - **Баннер привязки Telegram**: пока Telegram не привязан, показываем настойчивый, но не блокирующий баннер/напоминание — это единственный self-service способ восстановить доступ. Настройки: смена пароля. ### Вход и привязка через Telegram - **«Войти через Telegram»**: `POST /api/auth/telegram/login-request` → показать deep-link/QR на бота, затем ждать подтверждения (поллинг `GET …/login-request/{id}` или событие SignalR). При `Approved` — сохранить access, refresh уже в cookie, редирект в панель. - **«Привязать Telegram»** (в настройках, для вошедшего): `POST /api/auth/telegram/link-token` → показать deep-link/QR; статус привязки обновить по факту (поллинг/SignalR). Отвязка — `unlink`. - QR для deep-link — `qrcode.react`. ## Realtime - Одно SignalR-подключение к `/hubs/panel` с JWT; реконнект с бэкоффом. - Обработчики событий (`configTrafficUpdated`, `configStatusChanged`, `nodeStatusChanged`) точечно обновляют/инвалидируют кэш TanStack Query — UI обновляется без перезагрузки. ## Скрипты (ожидаемые) ```bash pnpm dev # dev-сервер Vite pnpm build # прод-сборка pnpm preview # предпросмотр сборки pnpm lint # ESLint pnpm typecheck # tsc --noEmit pnpm gen:api # генерация типов из OpenAPI-схемы бэкенда ```