diff --git a/.gitignore b/.gitignore index 5c27881..5407666 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ Thumbs.db # IDE .idea/ .vscode/ +.superpowers/ diff --git a/docs/superpowers/specs/2026-06-18-premium-driver-pwa-design.md b/docs/superpowers/specs/2026-06-18-premium-driver-pwa-design.md new file mode 100644 index 0000000..17d1077 --- /dev/null +++ b/docs/superpowers/specs/2026-06-18-premium-driver-pwa-design.md @@ -0,0 +1,68 @@ +# Премиум Водитель — PWA (дизайн) + +**Дата:** 2026-06-18 +**Статус:** дизайн согласован (брейншторм + визуальный компаньон). Код не начат. +**Цель:** PWA-приложение водителя «Премиум Водитель» — вход по телефону (код через TG/MAX), просмотр баланса (счета из CRM-леджера), пополнение через Т-Банк (СБП/карта), история пополнений. Поверхность для уже построенного бэкенда (Фаза 1 платежей + авторизация водителя в `crm2/app/modules/payments`). + +## Размещение и стек +- Новый PWA — **сиблинг** `PremiumDriverApp/driver-pwa/frontend/` (рядом с `mechanic-pwa/`), на том же стеке: **React + Vite + vite-plugin-pwa + TypeScript + Tailwind + @tanstack/react-query + ky + zod + react-router-dom + react-hook-form + zustand + sonner (тосты) + lucide-react + vitest**. Структура `src/{api,components,pages,hooks,lib,store}` как у mechanic-pwa. +- Хостинг: **app.pptaxi.ru** (статика + Caddy; деплой как у mechanic — build → dist → tar/rsync, см. mechanic deploy flow). +- Имя приложения (везде в UI, манифест PWA, заголовок): **«Премиум Водитель»**. + +## Визуальный язык — Cream Minimal +- Палитра (без жёлтого): фон `#F3EEE3` (cream), карточка/поверхность `#FBF8F1`, линия-разделитель `#E4DCCB`, чернила `#1C1B19`, вторичный текст `#7C7361`/opacity. Кнопки primary — тёмные `#1C1B19` с текстом `#F3EEE3`. Долг (минус) `#C0493A`, плюс/депозит `#3B6E4A`. +- Типографика: **Inter** (один шрифт), без эмодзи. Иконки — lucide (тонкие, монохром). +- Скругления 12–16px, тонкие 1px-границы, мягкие тени минимально. Крупные тап-таргеты (мобайл-фёрст). App-like / edit-first: минимум лишних кнопок, экран сам реагирует. + +## Экраны (MVP) +1. **Вход.** + - Шаг 1: поле телефона `+7 ___ ___-__-__` → «Получить код». → `POST /api/driver/auth/request {phone}`. + - Если ответ `code_sent` → шаг 2: ввод кода (свой цифровой кейпад) → `POST /api/driver/auth/verify {phone, code}` → сохраняем driver JWT → на «Баланс». + - Если ответ `onboarding` → экран «Подключите бота»: две кнопки **Telegram** / **MAX** (открывают `deep_links.tg`/`.max`); подсказка «нажмите Старт у бота, затем вернитесь и запросите код снова». Кнопка «Я подключил — запросить код». +2. **Баланс** (компоновка B: hero + список). + - Шапка: «Премиум Водитель» + инициалы водителя. + - **Hero-блок «Общий долг»** — крупная сумма (сумма отрицательных счетов). + - **Список счетов** (bucket из `/api/driver/balance`): название + подпись (марка/госномер для аренды, «N неоплаченных» для штрафов) + значение (минус — красный, плюс — зелёный). + - Кнопка **«Пополнить»** (primary). Тап по счёту/кнопке → «Пополнение» с предвыбранным счётом. + - Внизу/в меню: «История», «Выйти». +3. **Пополнение** (компоновка C: свой кейпад). + - Заголовок «Пополнить · <счёт>». Крупная сумма. Строка «к оплате X ₽ (комиссия Y ₽)» — пересчёт на лету по ставке (получаем из ответа `/topup` или показываем оценку и уточняем). + - **Свой цифровой кейпад** (1–9, 0, ·, ⌫) — без системной клавиатуры; крупные клавиши. Быстрые суммы 500/1000/2000 (чипы). + - Сегмент **СБП / Картой**. + - Кнопка **«Оплатить»** → `POST /api/driver/topup {bucket, amount, method}` → получаем `{order_id, pay_url}`. +4. **Оплата / статус** (вариант A: авто-статус, без QR). + - Иконка «СБП», сумма к оплате, кнопка **«Оплатить через СБП»** (или «Оплатить картой») → открывает `pay_url` (СБП deep-link `qr.nspk.ru/...` → приложение банка; карта → форма `pay.tbank.ru`). **QR не показываем, «оплата с другого устройства» убрана** — водитель платит на том же телефоне через приложение банка. + - Под кнопкой — авто-статус «Ожидаем оплату…» (спиннер). Приложение **опрашивает статус** платежа; при `PAID` → экран **«Оплачено»** (зелёная галочка, «<счёт> пополнен на N ₽») → кнопка «На главную» (баланс перезапрашивается). +5. **История пополнений.** + - Список из `driver_payments` водителя: дата, счёт, сумма, статус (Оплачено/Ожидает/Отклонён), способ. Пусто → «Пока нет пополнений». +6. **Выход** — очистка JWT, возврат на «Вход». + +## Поток данных / API (crm2 BFF, префикс `/api`) +Готово (Фаза 1 + авторизация): +- `POST /driver/auth/request {phone}` → `{status:"code_sent",channel}` | `{status:"onboarding",deep_links:{tg,max}}` +- `POST /driver/auth/verify {phone, code}` → `{token, driver_id}` +- `GET /driver/balance` (Bearer) → `{accounts:[{bucket, balance}]}` +- `POST /driver/topup {bucket, amount, method}` (Bearer) → `{order_id, pay_url, commission, total}` + +**Нужно добавить на бэкенде (зависимость этого PWA — отдельные backend-задачи):** +- `GET /driver/payments` (Bearer) → история пополнений водителя (из `driver_payments`: order_id, bucket, amount, commission, method, status, created_at, paid_at). +- `GET /driver/payment/{order_id}` (Bearer) → `{status}` для авто-опроса статуса оплаты (NEW/FORM/PAID/REJECTED). (Альтернатива: опрашивать `/driver/balance` и сравнивать — но явный статус надёжнее и показывает «Оплачено».) +- (Опц.) В `/driver/topup` вернуть и `commission_rate`, чтобы кейпад считал «к оплате» локально до запроса. + +## Сессия / клиент +- driver JWT (typ='driver', 30 дн) хранится в `localStorage`; `ky`-инстанс добавляет `Authorization: Bearer`. На 401 → разлогин (на «Вход»). +- `@tanstack/react-query` для balance/history (кэш + рефетч); `zod` валидирует ответы; `zustand` — сессия (token, driver_id). Тосты ошибок — `sonner`. +- База API из env (`VITE_API_BASE`, по умолчанию `https://crm.pptaxi.ru/api`). CORS: бэкенд должен разрешить origin `https://app.pptaxi.ru` для `/api/driver/*` (backend-задача). + +## PWA +- `vite-plugin-pwa`: manifest (name «Премиум Водитель», cream theme `#F3EEE3`, иконки 192/512, standalone, портрет), service worker (precache оболочки, network-first для API). Устанавливается на телефон без сторов (по ссылке app.pptaxi.ru → «На экран Домой»). + +## Обработка ошибок +- Телефон не найден (404) → «Номер не найден. Обратитесь в парк.» Неверный код (401) → «Неверный или просроченный код» + повтор. Платёж не создан → тост + остаёмся на «Пополнении». Авто-опрос: таймаут N минут → «Не дождались оплаты» + кнопка «Проверить ещё раз»/«Вернуться». + +## Тестирование +- vitest + @testing-library/react: рендер экранов, кейпад (ввод/⌫/быстрые суммы, пересчёт «к оплате»), форма телефона/кода, маппинг ответов API (zod-схемы), guard приватных роутов (нет токена → «Вход»), авто-статус (мок таймера/опроса → success). API мокается (msw или ky-mock), без сети. + +## Скоуп / порядок +MVP-экраны: Вход (+онбординг) · Баланс · Пополнение · Оплата/статус · История · Выход. **Вне MVP:** push-уведомления, вывод средств, профиль/настройки, мультиязычность. +Порядок сборки (для плана): каркас приложения+PWA+API-клиент+сессия → Вход/онбординг → Баланс → Пополнение(кейпад) → Оплата/авто-статус → История → деплой app.pptaxi.ru. Бэкенд-зависимости (`/driver/payments`, `/driver/payment/{order_id}`, CORS) — отдельные мелкие задачи в crm2 (сделать до/параллельно соответствующим экранам).