85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
import { useState } from "react";
|
||
import { useLocation, useNavigate } from "react-router-dom";
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import { Delete } from "lucide-react";
|
||
import { toast } from "sonner";
|
||
import { topup, getCommission } 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 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 { data: commission } = useQuery({ queryKey: ["commission"], queryFn: getCommission });
|
||
const rate = commission?.rate ?? 0.052; // fallback only while loading
|
||
|
||
const base = raw ? parseInt(raw, 10) : 0;
|
||
const { commission: fee, 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 (
|
||
<div className="pt-2">
|
||
<button className="text-muted text-xs mb-3" onClick={() => nav(-1)}>‹ {bucket}</button>
|
||
<p data-testid="amount" className="text-3xl font-extrabold text-center mt-2">{formatMoney(base)}</p>
|
||
<p data-testid="total" className="text-muted text-xs text-center mb-3">
|
||
к оплате {formatMoney(total)} (комиссия {formatMoney(fee)})
|
||
</p>
|
||
|
||
<div className="flex gap-1.5 mb-3">
|
||
{QUICK.map((q) => (
|
||
<button key={q} className="flex-1 py-2 rounded-xl2 text-xs bg-surface border border-line" onClick={() => setRaw(q)}>
|
||
{q}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="grid grid-cols-3 gap-2 mb-4">
|
||
{KEYS.map((k) => (
|
||
<button
|
||
key={k}
|
||
aria-label={k}
|
||
className="bg-surface border border-line rounded-xl2 py-3 text-lg font-semibold flex items-center justify-center"
|
||
onClick={() => press(k)}
|
||
>
|
||
{k === "back" ? <Delete size={18} /> : k}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="mb-4">
|
||
<Segment value={method} onChange={setMethod} options={[{ value: "qr", label: "СБП" }, { value: "card", label: "Картой" }]} />
|
||
</div>
|
||
|
||
<button className="btn-primary" disabled={busy || base <= 0} onClick={pay}>
|
||
{busy ? <Spinner /> : "Оплатить"}
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|