Приложение обкатывается на боевом контуре, поэтому интерфейс надо проверять на живых данных — а живые данные лежат у настоящих водителей. * «Вход для сотрудника» (/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>
53 lines
2.8 KiB
TypeScript
53 lines
2.8 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { Navigate, Route, Routes } from "react-router-dom";
|
|
import { useAuth } from "@/store/auth";
|
|
import { useStaff } from "@/store/staff";
|
|
import { ImpersonationBar } from "@/components/ImpersonationBar";
|
|
import { LoginPage } from "@/pages/LoginPage";
|
|
import { TgWaitPage } from "@/pages/TgWaitPage";
|
|
import { OnboardingPage } from "@/pages/OnboardingPage";
|
|
import { BalancePage } from "@/pages/BalancePage";
|
|
import { TopupPage } from "@/pages/TopupPage";
|
|
import { PaymentPage } from "@/pages/PaymentPage";
|
|
import { HistoryPage } from "@/pages/HistoryPage";
|
|
import { DevLoginPage } from "@/pages/DevLoginPage";
|
|
import { DebugPage } from "@/pages/DebugPage";
|
|
import { MockPayPage } from "@/pages/MockPayPage";
|
|
|
|
function RequireAuth({ children }: { children: ReactNode }) {
|
|
const token = useAuth((s) => s.token);
|
|
return token ? <>{children}</> : <Navigate to="/login" replace />;
|
|
}
|
|
|
|
/** Отладка существует только для сотрудника: либо он вошёл учёткой CRM, либо
|
|
* прямо сейчас смотрит от лица водителя. Обычному водителю адрес /debug ничего
|
|
* не даёт — его уводит на главную. */
|
|
function RequireStaff({ children }: { children: ReactNode }) {
|
|
const impersonated = useAuth((s) => s.impersonated);
|
|
const staffToken = useStaff((s) => s.token);
|
|
return impersonated || staffToken ? <>{children}</> : <Navigate to="/" replace />;
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<div className="mx-auto max-w-md min-h-full px-4 pt-3 pb-8">
|
|
{/* Плашка живёт над маршрутами: чей экран открыт, видно на каждом из них,
|
|
а не только там, где про неё вспомнили. Сама решает, показываться ли. */}
|
|
<ImpersonationBar />
|
|
<Routes>
|
|
<Route path="/login" element={<LoginPage />} />
|
|
<Route path="/dev" element={<DevLoginPage />} />
|
|
<Route path="/tg-wait" element={<TgWaitPage />} />
|
|
<Route path="/onboarding" element={<OnboardingPage />} />
|
|
<Route path="/" element={<RequireAuth><BalancePage /></RequireAuth>} />
|
|
<Route path="/topup" element={<RequireAuth><TopupPage /></RequireAuth>} />
|
|
<Route path="/pay/:orderId" element={<RequireAuth><PaymentPage /></RequireAuth>} />
|
|
<Route path="/mock-pay/:orderId" element={<RequireAuth><MockPayPage /></RequireAuth>} />
|
|
<Route path="/history" element={<RequireAuth><HistoryPage /></RequireAuth>} />
|
|
<Route path="/debug" element={<RequireAuth><RequireStaff><DebugPage /></RequireStaff></RequireAuth>} />
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
</div>
|
|
);
|
|
}
|