diff --git a/driver-pwa/frontend/src/pages/TopupPage.test.tsx b/driver-pwa/frontend/src/pages/TopupPage.test.tsx new file mode 100644 index 0000000..01b367a --- /dev/null +++ b/driver-pwa/frontend/src/pages/TopupPage.test.tsx @@ -0,0 +1,26 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MemoryRouter } from "react-router-dom"; + +vi.mock("@/api/driver", () => ({ + topup: vi.fn(async () => ({ order_id: "o9", pay_url: "https://qr.nspk.ru/x", commission: 52, total: 1052 })), +})); +import { TopupPage } from "./TopupPage"; +import * as driver from "@/api/driver"; + +const wrap = () => render(); + +describe("TopupPage", () => { + it("keypad builds amount, shows total, and submits topup", async () => { + wrap(); + await userEvent.click(screen.getByRole("button", { name: "1" })); + await userEvent.click(screen.getByRole("button", { name: "0" })); + await userEvent.click(screen.getByRole("button", { name: "0" })); + await userEvent.click(screen.getByRole("button", { name: "0" })); + expect(screen.getByTestId("amount").textContent).toBe("1 000 ₽"); + expect(screen.getByTestId("total").textContent).toContain("1 052"); + await userEvent.click(screen.getByRole("button", { name: /Оплатить/i })); + expect(driver.topup).toHaveBeenCalledWith("Долг аренда", 1000, "qr"); + }); +}); diff --git a/driver-pwa/frontend/src/pages/TopupPage.tsx b/driver-pwa/frontend/src/pages/TopupPage.tsx index 5e3b567..a213614 100644 --- a/driver-pwa/frontend/src/pages/TopupPage.tsx +++ b/driver-pwa/frontend/src/pages/TopupPage.tsx @@ -1 +1,82 @@ -export function TopupPage() { return null; } +import { useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import { Delete } from "lucide-react"; +import { toast } from "sonner"; +import { topup } from "@/api/driver"; +import { keypadReduce, withCommission } from "@/lib/amount"; +import { formatMoney } from "@/lib/format"; +import { Segment } from "@/components/Segment"; +import { Spinner } from "@/components/Spinner"; + +const RATE = 0.052; +const QUICK = ["500", "1000", "2000"]; +const KEYS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "·", "0", "back"]; + +export function TopupPage() { + const nav = useNavigate(); + const loc = useLocation(); + const bucket = (loc.state as { bucket?: string } | null)?.bucket ?? "Долг аренда"; + const [raw, setRaw] = useState(""); + const [method, setMethod] = useState("qr"); + const [busy, setBusy] = useState(false); + + const base = raw ? parseInt(raw, 10) : 0; + const { commission, total } = withCommission(base, RATE); + + function press(k: string) { + if (k === "·") return; + setRaw((r) => keypadReduce(r, k)); + } + + async function pay() { + if (base <= 0) return; + setBusy(true); + try { + const r = await topup(bucket, base, method as "qr" | "card"); + nav(`/pay/${r.order_id}`, { state: { payUrl: r.pay_url, total: r.total, bucket, base } }); + } catch { + toast.error("Не удалось создать платёж"); + } finally { + setBusy(false); + } + } + + return ( +
+ +

{formatMoney(base)}

+

+ к оплате {formatMoney(total)} (комиссия {formatMoney(commission)}) +

+ +
+ {QUICK.map((q) => ( + + ))} +
+ +
+ {KEYS.map((k) => ( + + ))} +
+ +
+ +
+ + +
+ ); +}