Files
mechanic-pwa/driver-pwa/frontend
tremble7681andClaude Opus 5 39ee2b7a49 fix(pwa): код двухфакторки уходит сам, а не ждёт кнопки
Второй шаг входа сотрудника не отправлялся вовсе: у поля с кодом не было ни
обработки Enter, ни автоотправки, а «Готово» на клавиатуре телефона — это как
раз Enter. Со стороны выглядело как «код не принят», хотя запрос на /auth/2fa
не уходил ни разу (в логах бэкенда — только login с ответом «нужен второй шаг»).

Теперь шесть цифр отправляются сразу, Enter тоже работает, поле получает фокус и
autocomplete=one-time-code (подсказка кода из СМС/менеджера паролей).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 15:19:11 +10:00
..

Премиум Водитель — PWA водителя

React + Vite + TypeScript. Прод: https://app.pptaxi.ru. Бэкенд — модуль payments в Premium CRM (https://crm.pptaxi.ru/api, репозиторий crm2, backend/app/modules/payments/).

Как поднять локально

npm install
npm run dev            # http://localhost:5174

Отдельно поднимается бэкенд crm2 на 127.0.0.1:8000 (тот же порт, что в backend/Dockerfile). Больше настраивать нечего: dev-сервер сам подставляет VITE_API_BASE=/api и проксирует /api на бэкенд — см. vite.config.ts.

Почему через прокси, а не «разрешить localhost в CORS бэкенда». Прокси делает запрос ОДНОГО происхождения со страницей (http://localhost:5174/api/…), поэтому CORS в разработке не участвует вообще. Боевой список origin'ов на бэкенде остаётся закрытым, а CORS_ALLOW_LOCALHOST=true в backend/.env для этого фронта включать не надо. (Раньше рецепт с CORS_ALLOW_LOCALHOST тут и не работал: прокси не было, и фронт по умолчанию уходил на боевой домен, а не на локальный бэкенд.)

Сервис-воркер в режиме разработки не регистрируется (devOptions у vite-plugin-pwa по умолчанию выключены) — старый кэш прод-версии в дев не лезет.

Бэкенд не на 127.0.0.1:8000

VITE_API_TARGET=http://127.0.0.1:8090 npm run dev

В PowerShell инлайн-префикса VAR=... команда нет — переменную ставят отдельно:

$env:VITE_API_TARGET="http://127.0.0.1:8090"; npm run dev

Имя переменной то же, что в CRM (crm2/frontend/vite.config.ts), — рецепт один на оба фронта.

Ходить в API напрямую, мимо прокси

.env.local (в git не попадает):

VITE_API_BASE=http://127.0.0.1:8000/api

Своя настройка приоритетнее дев-дефолта — специально, чтобы npm run dev её не перебивал. Для похода в чужой origin бэкенду понадобится CORS_ALLOW_LOCALHOST=true в backend/.env и рестарт.

⚠️ crm.pptaxi.ru — боевой терминал Т-Банка и боевая касса АТОЛ. Пополнение, сделанное «просто посмотреть», — это реальные деньги водителя, реальный чек 54-ФЗ и реальная проводка в леджере. Поэтому прокси по умолчанию смотрит в локальный бэкенд, а не в прод.

Проверки

npx tsc -b             # типы
npm test               # vitest run

npx tsc --noEmit без -b здесь всегда зелёный и ничего не значит: корневой tsconfig.json — solution-файл (files: [] + references), под него не попадает ни один файл (tsc --noEmit --listFiles печатает пустоту). Типы живут в tsconfig.app.json (src) и tsconfig.node.json (конфиги + dev-proxy.test.ts), и до обоих добирается только tsc -b.

Сборка

npm run build          # tsc -b && vite build → dist/

Дев-дефолт VITE_API_BASE=/api подставляется только при запущенном dev-сервере и в бандл не попадает: в собранном assets/index-*.js лежит https://crm.pptaxi.ru/api (проверено сборкой). Поэтому VITE_API_BASE боевой сборке задавать не обязательно — без неё src/api/client.ts подставляет ровно этот адрес.

⚠️ npm run preview поднимает уже собранный бандл, а база в нём абсолютная: запросы уходят на боевой crm.pptaxi.ru мимо прокси dev-сервера. Смотреть локальные правки — только npm run dev.

Куда выкладывается dist/ — в плане проекта: ../../docs/superpowers/plans/2026-06-18-premium-driver-pwa.md, раздел «Deployment notes»: статика на app.pptaxi.ru, Caddy с фолбэком на index.html.

Обкатка: кто может войти и как смотреть чужой экран

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

1. Вход только по списку номеров

Переменная бэкенда (crm2/backend/.env, читается в app/config.py):

DRIVER_APP_ALLOWED_PHONES=+7 (914) 123-45-67, 79990001122

Разделители — запятая, точка с запятой, перенос строки (пробел разделителем не считается: номер вписывают в привычном виде). Сверяются последние 10 цифр. Пусто — гейт выключен, вход открыт всем водителям.

Проверка стоит в единственной воронке выдачи токена (modules/payments/driver_auth.create_driver_token_for), поэтому закрыты сразу все пять путей входа: код в бот, кнопка «Войти через Telegram», колбэки своего бота и два аварийных отката на n8n. Бот отвечает человеку текстом, остальные пути — 403. Логика гейта: modules/payments/login_gate.py.

2. Просмотр от лица водителя (сотрудник)

На экране входа внизу — «Вход для сотрудника» (/dev): логин и пароль учётки CRM, при включённой двухфакторке спросит код. Дальше поиск по ФИО, телефону или id — и приложение открывается от лица выбранного водителя.

  • Токен помечен claim'ом imp, живёт 8 часов (обычный водительский — 30 дней).
  • Гейт — is_admin в CRM. Каждая выдача пишется в аудит (driver_app.impersonate).
  • Кнопка «Отозвать доступ» в CRM гасит и просмотр сотрудника — поколение общее.
  • Гейт обкатки на этот путь не распространяется: смотреть надо как раз на водителей, которых в allowlist нет.

Пока просмотр действует, сверху на всех экранах висит плашка «Смотрю как …» со ссылкой на вкладку «Отладка» (/debug, водителю недоступна):

  • от чьего лица открыт экран и кнопка сменить водителя;
  • driver_id, режим, сотрудник, когда истекает токен, адрес API, коммит и время сборки, состояние сети;
  • журнал последних 40 ответивших запросов (метод, путь, статус, миллисекунды);
  • сброс кэша сервис-воркера с перезагрузкой — лечит «правка не приехала»;
  • выходы: из просмотра, из учётки сотрудника.

Деньги под просмотром: учебный платёж

Пополнение под imp-токеном не идёт в банк. Проверки суммы, категории и расчёт комиссии настоящие, а вместо Init на терминале заводится учебный заказ в Redis (modules/payments/mock_pay.py, префикс mock-, час жизни). Экран /mock-pay/:orderId даёт выбрать, что «ответит» банк — оплачено, отказ, истёкший счёт, возврат, — и настоящий экран оплаты разбирает эти статусы своим боевым кодом. В driver_payments не попадает ни строки, чек 54-ФЗ не пробивается.