# Frontend SPA на **React 19 + Vite + TypeScript**. Общается с бэком по REST (JWT Bearer) и получает живые обновления по SignalR. > **Раздача из единого контейнера.** В проде собранный фронт (`dist/`) кладётся в `wwwroot` > ASP.NET Core и раздаётся тем же приложением (SPA-fallback на `index.html`). Фронт и бек — один > origin, база API — относительный `/api`, SignalR — `/hubs/panel`. В dev Vite-сервер проксирует > `/api` и `/hubs` на бэкенд (`vite.config.ts`, цель — `http://localhost:8080` по умолчанию, > переопределяется `VITE_API_TARGET`). Детали упаковки — [architecture.md](architecture.md#развёртывание-единый-контейнер-приложения). ## Стек | Задача | Выбор | | ----------------- | ------------------------------------------------------------ | | Сборка/dev | Vite (Rolldown-based) | | Язык | TypeScript (strict) | | Данные с сервера | TanStack Query | | Роутинг | TanStack Router (файловый, `src/routes/`, кодогенерация `routeTree.gen.ts`) | | UI-компоненты | shadcn-стиль поверх Radix (`@radix-ui/react-*`) + Tailwind CSS v4 | | Иконки | lucide-react | | Клиентский стейт | Zustand — только auth-стор (`features/auth/store.ts`); тема — React Context + localStorage, не Zustand | | Формы | react-hook-form + zod | | Realtime | @microsoft/signalr | | QR-коды | qrcode.react (рендерит QR из готовой строки на клиенте) | | Типы API | openapi-typescript (`pnpm gen:api`) — генерирует `schema.gen.ts` для сверки; фичи импортируют руками написанный `shared/api/types.ts` | | i18n | react-i18next (RU + EN) | | Линт | oxlint (не ESLint) | | Пакетный менеджер | pnpm | Установлены, но не используются: `recharts` (админская статистика — карточки с цифрами, без графиков), `@tanstack/react-table` (админские таблицы написаны руками, без TanStack Table). ## Структура Фактическая структура (`frontend/src/`): ``` frontend/ src/ main.tsx # точка входа: QueryClientProvider > ThemeProvider > ToastProvider > RealtimeProvider > RouterProvider router.tsx # createRouter из routeTree.gen.ts routeTree.gen.ts # сгенерировано @tanstack/router-plugin, не редактируется руками index.css # Tailwind v4 (@import), без отдельной styles/-папки routes/ # файловый роутинг TanStack Router __root.tsx # шапка (лого, нав, переключатель языка/темы), Outlet index.tsx, login.tsx, register.tsx, dashboard.tsx, instructions.tsx, settings.tsx admin.tsx # layout админки (вкладки) + Outlet admin/ index.tsx, activation.tsx, users.tsx, roles.tsx, nodes.tsx, apps.tsx, audit.tsx features/ auth/ # api.ts, store.ts (zustand), guards.ts, LoginForm.tsx, RegisterForm.tsx activation/ # api.ts, ActivationGate.tsx (экран "запросить активацию") configs/ # api.ts, ConfigCard.tsx, CreateConfigDialog.tsx, SubscriptionCard.tsx apps/ # api.ts, AppsCatalog.tsx (для /instructions) telegram/ # api.ts, TelegramLoginButton.tsx settings/ # ChangePasswordForm.tsx, TelegramLinkCard.tsx, DeleteAccountSection.tsx admin/ users/, roles/, activation/, nodes/, inbounds/, apps/, audit/, stats/ # api.ts + диалоги CRUD в каждой theme/ ThemeProvider.tsx # React Context + localStorage (`pnv-theme`), НЕ zustand shared/ api/ client.ts # apiRequest(), HttpError, access-token в памяти модуля, 401 → silent refresh → повтор types.ts # руками написанные типы ответов/запросов (сверены со schema.gen.ts) schema.gen.ts # генерируется pnpm gen:api, не импортируется напрямую фичами lib/ i18n.ts, cn.ts, format.ts realtime/ connection.ts, RealtimeProvider.tsx ui/ button.tsx, input.tsx, label.tsx, card.tsx, dialog.tsx, select.tsx, badge.tsx, progress.tsx, toast-store.tsx, toaster.tsx index.html vite.config.ts package.json ``` Нет `app/`-папки с провайдерами (они прямо в `main.tsx`), нет `shared/config/` (переменные окружения читаются точечно через `import.meta.env`), нет `styles/` (один `index.css` с Tailwind). ## Дизайн / UX - **Тема оформления**: светлая/тёмная/системная (переключатель в шапке). Реализация — класс `dark` на `` + Tailwind, выбор в `localStorage` (`ThemeProvider`, React Context). - **Дашборд пользователя** (`/dashboard`): карточки конфигов (протокол, локация, использованный трафик, статус), кнопки на карточке — показать ссылку/QR (запрашивает `GET .../link` по клику, не сразу при создании), перевыпустить, отозвать; отдельная карточка «Общая подписка». Для неактивированного — `ActivationGate` вместо дашборда. - **Создание конфига**: диалог — выбор инбаунда (по `displayName`) + метка + лимит устройств. После успеха карточка конфига появляется в списке; ссылку/QR пользователь открывает отдельно. - **Страница инструкций** (`/instructions`): статичные шаги + каталог приложений (`GET /api/apps`), сгруппированный по ОС; клик по приложению открывает ссылку на скачивание. - **Настройки** (`/settings`): смена пароля, привязка/отвязка Telegram (`TelegramLinkCard`), удаление аккаунта с подтверждением (`DeleteAccountSection`). - **Админка** (`/admin/*`): вкладки — обзор (карточки статистики, без графиков), запросы активации, пользователи, роли, ноды (+ публикация инбаундов), приложения, аудит. Таблицы — обычные `