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 (
|
||||
<header className="flex items-center justify-between mb-4">
|
||||
<header className="mb-4">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getPaymentState } from "@/api/driver";
|
||||
|
||||
const TERMINAL = new Set(["PAID", "CONFIRMED", "REJECTED", "REVERSED", "REFUNDED", "CANCELED", "AUTH_FAIL"]);
|
||||
|
||||
export function usePaymentStatus(orderId: string) {
|
||||
return useQuery({
|
||||
queryKey: ["payment", 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.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() {
|
||||
const nav = useNavigate();
|
||||
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 totalDebt = accounts.filter((a) => a.balance < 0).reduce((s, a) => s + a.balance, 0);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppHeader initials="" />
|
||||
<AppHeader />
|
||||
<div className="card p-4 mb-4 text-center">
|
||||
<p className="text-muted text-xs mb-1">Общий долг</p>
|
||||
<p className={`text-2xl font-extrabold ${totalDebt < 0 ? "text-neg" : ""}`}>{formatMoney(totalDebt)}</p>
|
||||
@@ -25,6 +25,11 @@ export function BalancePage() {
|
||||
|
||||
{isLoading ? (
|
||||
<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">
|
||||
{accounts.map((a) => (
|
||||
|
||||
@@ -10,7 +10,7 @@ const STATUS_RU: Record<string, string> = {
|
||||
|
||||
export function HistoryPage() {
|
||||
const nav = useNavigate();
|
||||
const { data, isLoading } = useQuery({ queryKey: ["payments"], queryFn: getPayments });
|
||||
const { data, isLoading, isError } = useQuery({ queryKey: ["payments"], queryFn: getPayments });
|
||||
const rows = data?.payments ?? [];
|
||||
|
||||
return (
|
||||
@@ -19,6 +19,8 @@ export function HistoryPage() {
|
||||
<h1 className="text-lg font-extrabold mb-4">История пополнений</h1>
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-10"><Spinner /></div>
|
||||
) : isError ? (
|
||||
<p className="text-muted text-sm">Не удалось загрузить историю</p>
|
||||
) : rows.length === 0 ? (
|
||||
<p className="text-muted text-sm">Пока нет пополнений</p>
|
||||
) : (
|
||||
|
||||
@@ -28,8 +28,9 @@ export function LoginPage() {
|
||||
}
|
||||
setChannel(r.channel);
|
||||
setStep("code");
|
||||
} catch (err: any) {
|
||||
toast.error(err?.response?.status === 404 ? "Номер не найден. Обратитесь в парк." : "Не удалось отправить код");
|
||||
} catch (err: unknown) {
|
||||
const status = (err as { response?: { status?: number } })?.response?.status;
|
||||
toast.error(status === 404 ? "Номер не найден. Обратитесь в парк." : "Не удалось отправить код");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user