Files
mechanic-pwa/driver-pwa/frontend/src/pages/MockPayPage.tsx
T
tremble7681andClaude Opus 5 ceec3db364 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>
2026-08-18 13:15:02 +10:00

78 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { toast } from "sonner";
import { apiErrorText, mockPayOutcome } from "@/api/driver";
import { useAuth } from "@/store/auth";
import { Spinner } from "@/components/Spinner";
/**
* Учебная «касса»: чем закончится платёж, который сотрудник создал под просмотром.
*
* Это подмена банка, а не экрана приложения. Настоящий экран оплаты
* (`/pay/:orderId`) остаётся тем же самым и разбирает те же статусы — иначе
* отладка проверяла бы поведение, которого у водителя не будет.
*/
const OUTCOMES = [
{ key: "paid", label: "Оплата прошла", hint: "PAID — экран «Оплачено»" },
{ key: "rejected", label: "Банк отклонил", hint: "REJECTED — «Оплата не прошла»" },
{ key: "expired", label: "Счёт протух", hint: "DEADLINE_EXPIRED — «Срок оплаты истёк»" },
{ key: "reversed", label: "Деньги вернулись", hint: "REVERSED — «Платёж возвращён»" },
] as const;
export function MockPayPage() {
const { orderId = "" } = useParams();
const nav = useNavigate();
const impersonated = useAuth((s) => s.impersonated);
const [busy, setBusy] = useState<string | null>(null);
async function choose(outcome: (typeof OUTCOMES)[number]["key"]) {
setBusy(outcome);
try {
await mockPayOutcome(orderId, outcome);
nav(`/pay/${orderId}`, { replace: true });
} catch (e: unknown) {
toast.error(await apiErrorText(e, "Не удалось разыграть исход"));
} finally {
setBusy(null);
}
}
if (!impersonated) {
return (
<div className="pt-24 text-center">
<p className="text-sm font-semibold mb-1">Учебная оплата недоступна</p>
<p className="text-muted text-xs mb-6">Этот экран работает только при просмотре сотрудником.</p>
<button className="btn-ghost" onClick={() => nav("/")}>На главную</button>
</div>
);
}
return (
<div className="pt-8">
<button className="text-muted text-xs mb-3" onClick={() => nav(-1)}> Назад</button>
<h1 className="text-lg font-extrabold mb-1">Учебная оплата</h1>
<p className="text-muted text-xs mb-5">
Банка в этом платеже нет: ни списания, ни чека, ни проводки. Выберите, что
«ответит» банк приложение покажет ровно то же, что увидел бы водитель.
</p>
<div className="card divide-y divide-line">
{OUTCOMES.map((o) => (
<button
key={o.key}
disabled={busy !== null}
onClick={() => choose(o.key)}
className="w-full text-left px-4 py-3 flex items-center justify-between gap-3 disabled:opacity-50"
>
<span>
<span className="block text-sm font-semibold">{o.label}</span>
<span className="block text-muted text-xs">{o.hint}</span>
</span>
{busy === o.key && <Spinner />}
</button>
))}
</div>
<p className="text-muted text-xs mt-4">Заказ: {orderId}</p>
</div>
);
}