Files
mechanic-pwa/driver-pwa/frontend
tremble7681andClaude Fable 5 a2557897a0 fix(login): обратный отсчёт кода + возврат на оплату после входа
Жалоба: по ссылке на оплату жмёшь «получить код» — «слишком часто».
- на шаге кода кнопка «отправить ещё раз» с отсчётом (60с, синхронно с
  бэкендом); на 429 ведём отсчёт по Retry-After и не пугаем
- кнопка «Получить код» блокируется на время кулдауна
- RequireAuth запоминает путь (ссылку на оплату); после входа по коду и
  через Telegram возвращаем туда, а не на баланс

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 15:22:05 +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.

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

Запущено 22.08.2026: вход открыт всем водителям. Список номеров ниже пуст, то есть гейт выключен. Раздел оставлен как инструкция на случай, если вход снова понадобится сузить — например под обкатку следующей крупной правки.

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-ФЗ не пробивается.