import { motion } from "framer-motion"; import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { apiPost, apiGet, type Status, type Tag } from "../api"; import { useNfcWs } from "../NfcWsContext"; import { useToast } from "../toast"; import { generalStatusHexLine, sakLabel, scanCheatLines, typeHintLabel, uidWithSeparators, } from "../nfcUtils"; import HeroVisual from "../ui/HeroVisual"; export default function Dashboard() { const toast = useToast(); const { lastTag, applyScanPoll } = useNfcWs(); const [st, setSt] = useState(null); const [scan, setScan] = useState(true); const poll = () => { apiGet("/api/status") .then(setSt) .catch(() => toast("Status unreachable", "err")); }; useEffect(() => { poll(); const id = setInterval(poll, 3000); return () => clearInterval(id); }, [toast]); useEffect(() => { if (st && typeof st.scanning === "boolean") { setScan(st.scanning); } }, [st]); const toggleScan = async () => { try { await apiPost<{ enable: boolean }>("/api/nfc/scan", { enable: !scan }); setScan(!scan); toast(scan ? "Continuous scan off" : "Continuous scan on"); } catch (e) { toast(String(e), "err"); } }; const pollOnce = async () => { try { const j = await apiPost<{ present: boolean; tag?: Tag }>("/api/nfc/poll", {}); if (j.present && j.tag) { applyScanPoll(true, j.tag); toast(`Tag ${j.tag.uid}`); } else { applyScanPoll(false); toast("No tag"); } } catch (e) { toast(String(e), "err"); } }; return (

COMMAND_LAYER

NFC CONTROL

Full-time scan out of the box. Fat RF retries. Browser log + device session export — crank it from your phone.

{st?.session && (st.session.deepCapture || st.session.usedBytes > 0) && ( RAM {st.session.usedBytes}/{st.session.maxBytes} {st.session.full ? " · LOCKED" : ""} Read-all → )}
Poll tag {scan ? "Stop scan" : "Start scan"}

Device stack

{st ? (
  • uptime {(st.uptimeMs / 1000).toFixed(1)}s
  • heap {st.freeHeap}
  • PN532 {st.pn532 ? `IC${st.pn532.ic} v${st.pn532.fwHi}.${st.pn532.fwLo}` : "n/a"}
) : (

Pulling status…

)}

Live tag

{lastTag ? (

UID

{uidWithSeparators(lastTag.uid)}

raw {lastTag.uid} · {lastTag.uidLen} byte{lastTag.uidLen === 1 ? "" : "s"}

ATQA{" "} 0x{(lastTag.atqaHex ?? lastTag.atqa.toString(16).toUpperCase().padStart(4, "0")).slice(-4)} ({lastTag.atqa})
SAK{" "} 0x{(lastTag.sakHex ?? lastTag.sak.toString(16).toUpperCase().padStart(2, "0")).slice(-2)} — {sakLabel(lastTag.sak)}
Guess{" "} {lastTag.typeGuess ?? typeHintLabel(lastTag.typeHint)} · hint {lastTag.typeHint}
{lastTag.pn532GeneralStatus && lastTag.pn532GeneralStatus.length > 0 && (

PN532 general status

{generalStatusHexLine(lastTag.pn532GeneralStatus)}

Raw bytes from the chip right after this inventory (error flags, last command, tag count — see NXP PN532 user manual).

)}
    {scanCheatLines(lastTag).map((line, i) => (
  • {line}
  • ))}
) : (

Listening on the field…

)}
); }