diff --git a/sites/mainweb/app/(portal)/hackathons/[id]/judge/page.tsx b/sites/mainweb/app/(portal)/hackathons/[id]/judge/page.tsx index 3e0f28dc..60f16359 100644 --- a/sites/mainweb/app/(portal)/hackathons/[id]/judge/page.tsx +++ b/sites/mainweb/app/(portal)/hackathons/[id]/judge/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { loginHref } from "@/lib/safe-callback"; import { useSession } from "next-auth/react"; import { useParams, useRouter } from "next/navigation"; @@ -257,6 +257,26 @@ export default function JudgeHackathonPage() { onError: recoverOrReport, }); + /** + * Tap-to-start. The NFC tag on a table holds this page's URL with + * ?table=, so tapping it opens the page and starts that table exactly + * as scanning the printed card would. Waits for judge access and an open + * round; the param is dropped once used so a reload cannot replay it + * (startByQrCode is idempotent anyway). + */ + const tappedTable = useRef(false); + useEffect(() => { + if (tappedTable.current) return; + if (!hackathonId || judgeCheck.data?.isJudge !== true || !judgingOpen) return; + const url = new URL(window.location.href); + const code = url.searchParams.get("table"); + if (!code) return; + tappedTable.current = true; + url.searchParams.delete("table"); + window.history.replaceState(null, "", url.pathname + url.search + url.hash); + startByQr.mutate({ qrCode: code.trim() }); + }, [hackathonId, judgeCheck.data?.isJudge, judgingOpen, startByQr]); + useEffect(() => { if (status === "unauthenticated") router.push(loginHref()); }, [status, router]); @@ -478,9 +498,10 @@ export default function JudgeHackathonPage() { Scan the team's code to begin

- Walk to table {project.tableNumber ?? "?"} and scan the card - on their desk. Your judging time starts from the scan, not - from when this table was assigned to you. + Walk to table {project.tableNumber ?? "?"} and tap your phone + on the NFC tag, or scan the card on their desk. Your judging + time starts from then, not from when this table was assigned + to you.

+ )} + + {(writes[card.id] === "written" || writes[card.id] === "copied") && ( + + + {writes[card.id] === "written" ? "Tag written" : "Copied"} + + )} + {typeof writes[card.id] === "object" && ( +

+ {(writes[card.id] as { error: string }).error} +

+ )} + ))}