// PIN pairing modal — a gamepad-navigable digit grid (the OSK is unreliable in Gaming Mode). // The host displays the PIN after the operator arms pairing; the user enters it here. import { DialogButton, Focusable, ModalRoot, Spinner } from "@decky/ui"; import { toaster } from "@decky/api"; import { FC, useState } from "react"; import { Host, pair } from "./backend"; export const PairModal: FC<{ host: Host; closeModal?: () => void; onPaired: () => void; }> = ({ host, closeModal, onPaired }) => { const [pin, setPin] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const press = (d: string) => setPin((p) => (p.length >= 4 ? p : p + d)); const back = () => setPin((p) => p.slice(0, -1)); const submit = async () => { setBusy(true); setError(null); try { const res = await pair(host.host, host.port, pin, "Steam Deck"); if (res.ok) { toaster.toast({ title: "Punktfunk", body: `Paired with ${host.name}` }); onPaired(); closeModal?.(); } else { setError(res.error ?? "pairing failed"); setPin(""); } } catch (e) { setError(String(e)); } finally { setBusy(false); } }; return (
Pair with {host.name}
Arm pairing on the host (its console or web UI), then enter the 4-digit PIN it shows.
{pin.padEnd(4, "•")}
{error && (
{error}
)} {["1", "2", "3", "4", "5", "6", "7", "8", "9"].map((d) => ( press(d)}> {d} ))} press("0")}> 0 {busy ? : "Pair"}
); };