polish(driver-pwa): error states, typed catch, drop dead avatar, terminal poll
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,12 +1,7 @@
|
|||||||
export function AppHeader({ initials }: { initials?: string }) {
|
export function AppHeader() {
|
||||||
return (
|
return (
|
||||||
<header className="flex items-center justify-between mb-4">
|
<header className="mb-4">
|
||||||
<span className="text-sm font-bold">Премиум Водитель</span>
|
<span className="text-sm font-bold">Премиум Водитель</span>
|
||||||
{initials && (
|
|
||||||
<span className="w-7 h-7 rounded-full bg-ink text-cream text-[10px] font-bold flex items-center justify-center">
|
|
||||||
{initials}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getPaymentState } from "@/api/driver";
|
import { getPaymentState } from "@/api/driver";
|
||||||
|
|
||||||
|
const TERMINAL = new Set(["PAID", "CONFIRMED", "REJECTED", "REVERSED", "REFUNDED", "CANCELED", "AUTH_FAIL"]);
|
||||||
|
|
||||||
export function usePaymentStatus(orderId: string) {
|
export function usePaymentStatus(orderId: string) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["payment", orderId],
|
queryKey: ["payment", orderId],
|
||||||
queryFn: () => getPaymentState(orderId),
|
queryFn: () => getPaymentState(orderId),
|
||||||
refetchInterval: (q) => (q.state.data?.status === "PAID" || q.state.data?.status === "REJECTED" ? false : 1500),
|
refetchInterval: (q) => (q.state.data && TERMINAL.has(q.state.data.status) ? false : 1500),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,16 @@ describe("BalancePage", () => {
|
|||||||
expect(screen.getAllByText("−3 200 ₽").length).toBeGreaterThan(0);
|
expect(screen.getAllByText("−3 200 ₽").length).toBeGreaterThan(0);
|
||||||
expect(screen.getByRole("button", { name: /Пополнить/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /Пополнить/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("shows an error state when balance fails to load", async () => {
|
||||||
|
const driver = await import("@/api/driver");
|
||||||
|
(driver.getBalance as any).mockRejectedValueOnce(new Error("boom"));
|
||||||
|
// re-render fresh
|
||||||
|
const { QueryClient, QueryClientProvider } = await import("@tanstack/react-query");
|
||||||
|
const { MemoryRouter } = await import("react-router-dom");
|
||||||
|
const { render, screen } = await import("@testing-library/react");
|
||||||
|
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||||
|
render(<QueryClientProvider client={qc}><MemoryRouter><BalancePage /></MemoryRouter></QueryClientProvider>);
|
||||||
|
expect(await screen.findByText(/Не удалось загрузить/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ import { Spinner } from "@/components/Spinner";
|
|||||||
export function BalancePage() {
|
export function BalancePage() {
|
||||||
const nav = useNavigate();
|
const nav = useNavigate();
|
||||||
const clear = useAuth((s) => s.clear);
|
const clear = useAuth((s) => s.clear);
|
||||||
const { data, isLoading } = useQuery({ queryKey: ["balance"], queryFn: getBalance });
|
const { data, isLoading, isError, refetch } = useQuery({ queryKey: ["balance"], queryFn: getBalance });
|
||||||
|
|
||||||
const accounts = data?.accounts ?? [];
|
const accounts = data?.accounts ?? [];
|
||||||
const totalDebt = accounts.filter((a) => a.balance < 0).reduce((s, a) => s + a.balance, 0);
|
const totalDebt = accounts.filter((a) => a.balance < 0).reduce((s, a) => s + a.balance, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AppHeader initials="" />
|
<AppHeader />
|
||||||
<div className="card p-4 mb-4 text-center">
|
<div className="card p-4 mb-4 text-center">
|
||||||
<p className="text-muted text-xs mb-1">Общий долг</p>
|
<p className="text-muted text-xs mb-1">Общий долг</p>
|
||||||
<p className={`text-2xl font-extrabold ${totalDebt < 0 ? "text-neg" : ""}`}>{formatMoney(totalDebt)}</p>
|
<p className={`text-2xl font-extrabold ${totalDebt < 0 ? "text-neg" : ""}`}>{formatMoney(totalDebt)}</p>
|
||||||
@@ -25,6 +25,11 @@ export function BalancePage() {
|
|||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex justify-center py-10"><Spinner /></div>
|
<div className="flex justify-center py-10"><Spinner /></div>
|
||||||
|
) : isError ? (
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<p className="text-muted text-sm mb-3">Не удалось загрузить баланс</p>
|
||||||
|
<button className="btn-ghost" onClick={() => refetch()}>Повторить</button>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
{accounts.map((a) => (
|
{accounts.map((a) => (
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const STATUS_RU: Record<string, string> = {
|
|||||||
|
|
||||||
export function HistoryPage() {
|
export function HistoryPage() {
|
||||||
const nav = useNavigate();
|
const nav = useNavigate();
|
||||||
const { data, isLoading } = useQuery({ queryKey: ["payments"], queryFn: getPayments });
|
const { data, isLoading, isError } = useQuery({ queryKey: ["payments"], queryFn: getPayments });
|
||||||
const rows = data?.payments ?? [];
|
const rows = data?.payments ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -19,6 +19,8 @@ export function HistoryPage() {
|
|||||||
<h1 className="text-lg font-extrabold mb-4">История пополнений</h1>
|
<h1 className="text-lg font-extrabold mb-4">История пополнений</h1>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex justify-center py-10"><Spinner /></div>
|
<div className="flex justify-center py-10"><Spinner /></div>
|
||||||
|
) : isError ? (
|
||||||
|
<p className="text-muted text-sm">Не удалось загрузить историю</p>
|
||||||
) : rows.length === 0 ? (
|
) : rows.length === 0 ? (
|
||||||
<p className="text-muted text-sm">Пока нет пополнений</p>
|
<p className="text-muted text-sm">Пока нет пополнений</p>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ export function LoginPage() {
|
|||||||
}
|
}
|
||||||
setChannel(r.channel);
|
setChannel(r.channel);
|
||||||
setStep("code");
|
setStep("code");
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
toast.error(err?.response?.status === 404 ? "Номер не найден. Обратитесь в парк." : "Не удалось отправить код");
|
const status = (err as { response?: { status?: number } })?.response?.status;
|
||||||
|
toast.error(status === 404 ? "Номер не найден. Обратитесь в парк." : "Не удалось отправить код");
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user