feat(pwa): вход для сотрудника, просмотр от лица водителя и вкладка «Отладка»

Приложение обкатывается на боевом контуре, поэтому интерфейс надо проверять на
живых данных — а живые данные лежат у настоящих водителей.

* «Вход для сотрудника» (/dev) — логин учётки CRM, поддержан второй шаг 2FA;
  дальше поиск водителя по ФИО/телефону/id и просмотр его экранов.
* Плашка «Смотрю как …» над всеми экранами: интерфейс в этом режиме неотличим
  от водительского, и чужой баланс легко принять за свой.
* Вкладка «Отладка» (/debug, водителю недоступна): смена водителя, driver_id,
  режим, срок токена, адрес API, коммит и время сборки, журнал последних
  40 ответивших запросов, сброс кэша сервис-воркера.
* Пополнение под просмотром идёт в учебный контур: экран /mock-pay/:orderId
  выбирает, что «ответит» банк, а настоящий экран оплаты разбирает эти статусы
  своим боевым кодом. Ни рубля, ни чека 54-ФЗ.

Заодно доезжает незакоммиченная работа прошлой сессии: дев-прокси в
vite.config.ts, его тест и разобранный .env.example.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 13:15:02 +10:00
co-authored by Claude Opus 5
parent aff1a45006
commit ceec3db364
27 changed files with 1308 additions and 17 deletions
+68 -8
View File
@@ -1,13 +1,73 @@
import { defineConfig } from "vite";
import { execSync } from "child_process";
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import { VitePWA } from "vite-plugin-pwa";
import path from "path";
export default defineConfig({
plugins: [
react(),
VitePWA({ registerType: "autoUpdate", manifest: false, includeAssets: ["icons/*.png"] }),
],
resolve: { alias: { "@": path.resolve(__dirname, "src") } },
server: { port: 5174 },
/**
* Что именно сейчас открыто — видно на вкладке «Отладка».
*
* Без этого «правка не приехала» и «приехала, но кэш старый» выглядят
* одинаково, а проверяются по-разному. Коммит берём из git; вне репозитория
* (сборка из архива, CI без .git) — честный «unknown», а не падение сборки.
*/
function buildCommit(): string {
try {
return execSync("git rev-parse --short HEAD", { stdio: ["ignore", "pipe", "ignore"] })
.toString().trim() || "unknown";
} catch {
return "unknown";
}
}
/**
* Локальная разработка ходит в бэкенд ЧЕРЕЗ ПРОКСИ dev-сервера, а не напрямую.
*
* Почему не «разрешить localhost в CORS бэкенда»: через прокси запрос уходит с
* того же origin, что и страница (http://localhost:5174/api/...), поэтому CORS
* в разработке не участвует вообще — ни preflight, ни credentials. Боевой
* список origin'ов на бэкенде остаётся закрытым, и CORS_ALLOW_LOCALHOST ради
* этого фронта включать не нужно.
*
* Дефолтная цель прокси — ЛОКАЛЬНЫЙ бэкенд, а не crm.pptaxi.ru. Контур боевой:
* «по умолчанию проксировать в прод» означало бы, что первый же тычок в
* «Пополнить» на машине разработчика создаёт платёж на живом терминале и живой
* чек 54-ФЗ. Другой адрес задаётся VITE_API_TARGET — тем же именем, что в CRM
* (crm2/frontend/vite.config.ts), чтобы рецепт был один на оба фронта.
*/
export default defineConfig(({ command, mode }) => {
// База для ky (src/api/client.ts): в dev — свой же origin, дальше прокси.
// Ставим ТОЛЬКО если разработчик не задал базу сам (.env.local, .env,
// VITE_API_BASE=... перед командой): loadEnv отдаёт и файлы, и process.env,
// причём process.env приоритетнее файлов — без этой проверки собственная
// настройка молча перебивалась бы нашим дефолтом.
// В сборку (command === "build") не лезем никогда: там база остаётся боевой —
// из .env, либо из дефолта в client.ts.
if (command === "serve" && !loadEnv(mode, __dirname, "VITE_").VITE_API_BASE) {
process.env.VITE_API_BASE = "/api";
}
return {
define: {
__BUILD_COMMIT__: JSON.stringify(buildCommit()),
__BUILD_TIME__: JSON.stringify(new Date().toISOString().slice(0, 16).replace("T", " ")),
},
plugins: [
react(),
VitePWA({ registerType: "autoUpdate", manifest: false, includeAssets: ["icons/*.png"] }),
],
resolve: { alias: { "@": path.resolve(__dirname, "src") } },
server: {
port: 5174,
proxy: {
"/api": {
// secure: false нужен только для VITE_API_TARGET на https-цель с
// самоподписанным сертификатом; на дефолтную http-цель не влияет.
target: process.env.VITE_API_TARGET || "http://127.0.0.1:8000",
changeOrigin: true,
secure: false,
},
},
},
};
});