feat(driver-pwa): top-up with custom keypad
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -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 (
|
||||
<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(commission)})
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user