From 3fab500875cc29955b57c5d20ff1a476ee7d7caa Mon Sep 17 00:00:00 2001 From: aamoghS Date: Thu, 24 Sep 2026 22:56:09 -0400 Subject: [PATCH] fix(web): refresh the data each admin and submit action changes With a 5-minute staleTime and no refetch on focus, a query nothing invalidates stays wrong for five minutes: - Edit Hackathon seeds from hackathon.getById, which the save never invalidated. Reopening showed the old values, and a second save wrote them back over the first. - Submit: a withdrawn project stays as a draft row, and the page treated any row as a submission, so after "Withdraw" it still said "Project record exists" with the Withdraw button and "UPDATE RECORD". - Prepare Judging did not refresh judge.liveProgress or judge.tableCards, so the board kept saying "No queues yet" and the table cards it tells you to print next were missing the newly synced projects. - Approving applicants (single, batch, mass-accept email) left hackathon.waveStatus stale, so the acceptance-wave panel and its confirm dialog quoted the wrong pending count. - Applying to judge left judge.myApplications stale, so /judge still offered "Apply to Judge", which then threw "already applied". --- sites/mainweb/app/(portal)/admin/hackathons/page.tsx | 3 +++ sites/mainweb/app/(portal)/admin/judging/page.tsx | 8 +++++++- sites/mainweb/app/(portal)/judge/register/page.tsx | 8 +++++++- sites/mainweb/app/(portal)/submit/page.tsx | 6 +++++- .../mainweb/components/admin/hackathons/AttendeesTab.tsx | 3 +++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/sites/mainweb/app/(portal)/admin/hackathons/page.tsx b/sites/mainweb/app/(portal)/admin/hackathons/page.tsx index b3ddf6e7..f5e374cc 100644 --- a/sites/mainweb/app/(portal)/admin/hackathons/page.tsx +++ b/sites/mainweb/app/(portal)/admin/hackathons/page.tsx @@ -80,6 +80,9 @@ export default function AdminHackathonsPage() { hackathonId={editingId} onClose={() => setEditingId(null)} onSaved={() => { + // The form seeds itself from getById; left cached, reopening it + // shows the old values and a second save writes them back. + utils.hackathon.getById.invalidate({ id: editingId }); setEditingId(null); utils.hackathon.listAll.invalidate(); }} diff --git a/sites/mainweb/app/(portal)/admin/judging/page.tsx b/sites/mainweb/app/(portal)/admin/judging/page.tsx index 3cbb0f8e..6cfde65c 100644 --- a/sites/mainweb/app/(portal)/admin/judging/page.tsx +++ b/sites/mainweb/app/(portal)/admin/judging/page.tsx @@ -101,7 +101,13 @@ export default function AdminResultsPage() { gen: number, message: string, ) => { - await utils.judge.getRankings.invalidate({ hackathonId }); + // The live board and table cards read what prepare just built; neither + // polls until judging is open. + await Promise.all([ + utils.judge.getRankings.invalidate({ hackathonId }), + utils.judge.liveProgress.invalidate(), + utils.judge.tableCards.invalidate(), + ]); if (!stillThisRun(hackathonId, gen)) return; await refetchJudgingStatus(); if (!stillThisRun(hackathonId, gen)) return; diff --git a/sites/mainweb/app/(portal)/judge/register/page.tsx b/sites/mainweb/app/(portal)/judge/register/page.tsx index 767d5aff..2e43bf85 100644 --- a/sites/mainweb/app/(portal)/judge/register/page.tsx +++ b/sites/mainweb/app/(portal)/judge/register/page.tsx @@ -35,8 +35,14 @@ export default function JudgeRegisterPage() { const [success, setSuccess] = useState(false); const { data: hackathons, isLoading } = trpc.hackathon.list.useQuery({}); + const utils = trpc.useUtils(); const registerMutation = trpc.judge.register.useMutation({ - onSuccess: () => setSuccess(true), + onSuccess: () => { + setSuccess(true); + // Or /judge still offers "Apply to Judge", which now throws "already + // applied". + void utils.judge.myApplications.invalidate(); + }, onError: (e) => setError(e.message), }); diff --git a/sites/mainweb/app/(portal)/submit/page.tsx b/sites/mainweb/app/(portal)/submit/page.tsx index 55d7631b..ca7a329e 100644 --- a/sites/mainweb/app/(portal)/submit/page.tsx +++ b/sites/mainweb/app/(portal)/submit/page.tsx @@ -119,8 +119,12 @@ function SubmitPortalContent() { // We get the specific registration / team context based on selected hackathon const currentReg = myRegs?.find((r) => r.hackathonId === selectedHackathonId); + // A withdrawn project stays as a draft row, so the row alone is not a + // submission. const hasSubmitted = - !!mySubmission.data || currentReg?.hasSubmittedProject || projectSubmitted; + (!!mySubmission.data && mySubmission.data.status !== "draft") || + currentReg?.hasSubmittedProject || + projectSubmitted; // Mutations // Joining, creating or leaving a team changes which project this participant diff --git a/sites/mainweb/components/admin/hackathons/AttendeesTab.tsx b/sites/mainweb/components/admin/hackathons/AttendeesTab.tsx index 6656b5f9..7e0fc10b 100644 --- a/sites/mainweb/components/admin/hackathons/AttendeesTab.tsx +++ b/sites/mainweb/components/admin/hackathons/AttendeesTab.tsx @@ -117,6 +117,7 @@ export function AttendeesTab({ utils.hackathon.analytics.invalidate({ hackathonId }); utils.hackathon.getById.invalidate({ id: hackathonId }); utils.hackathon.listAll.invalidate(); + utils.hackathon.waveStatus.invalidate({ hackathonId }); }, // Same reasoning as the batch mutation below. This is the action an // organiser repeats 2000 times; a rate-limit or NOT_FOUND leaves the badge @@ -134,6 +135,7 @@ export function AttendeesTab({ utils.hackathon.analytics.invalidate({ hackathonId }); utils.hackathon.getById.invalidate({ id: hackathonId }); utils.hackathon.listAll.invalidate(); + utils.hackathon.waveStatus.invalidate({ hackathonId }); setSelectedIds(new Set()); setBulkError(null); }, @@ -323,6 +325,7 @@ export function AttendeesTab({ utils.hackathon.analytics.invalidate({ hackathonId }); utils.hackathon.getById.invalidate({ id: hackathonId }); utils.hackathon.listAll.invalidate(); + utils.hackathon.waveStatus.invalidate({ hackathonId }); }; const toggleSelect = (id: string) => {