From c9df086e3cc1aa05d325349fc440043170b60f48 Mon Sep 17 00:00:00 2001 From: sacha <23283108+sacha-l@users.noreply.github.com> Date: Tue, 16 Jun 2026 00:16:35 +0200 Subject: [PATCH] feat(judging): scores stay editable after submit + in-app video review modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two judging-flow tweaks from event prep: 1. Scores are no longer hard-locked on submit. Removed the isSubmitted -> 409 guard on upsertScore + saveScores so a judge can revise and re-save their own scores anytime; the submitted ballot still counts and the leaderboard reflects the latest values (leaderboard already counts submitted ballots only, on submit — unchanged). Client: score inputs always editable, SAVE BATCH always available, the red 'LOCKED' banner becomes a calm 'submitted, you can still revise + save' note. claimBatch stays gated after submit (not part of this). 2. Clicking VIDEO opens an in-app review modal (ProjectReviewModal, reusing demoUtils embed + the Dialog) with an 'open in new window' button, instead of spawning a tab. GITHUB stays a new-tab link (X-Frame-Options blocks framing). Tests: controller lock tests flipped to assert edit-after-submit succeeds; sim journey updated likewise; SIMULATION_REPORT regenerated. Server 397 green; client build + lint clean; stadium-tester 4/4 (video modal opens; edit+re-save after submit works). --- .../admin/ProgramJudgingSection.tsx | 49 +++++++------ .../components/admin/ProjectReviewModal.tsx | 69 +++++++++++++++++++ .../__tests__/submission.controller.test.js | 22 +++--- .../api/controllers/submission.controller.js | 20 ++---- server/sim/SIMULATION_REPORT.md | 2 +- server/sim/__tests__/judging-journey.test.js | 6 +- 6 files changed, 120 insertions(+), 48 deletions(-) create mode 100644 client/src/components/admin/ProjectReviewModal.tsx diff --git a/client/src/components/admin/ProgramJudgingSection.tsx b/client/src/components/admin/ProgramJudgingSection.tsx index cfbc4af..607b683 100644 --- a/client/src/components/admin/ProgramJudgingSection.tsx +++ b/client/src/components/admin/ProgramJudgingSection.tsx @@ -1,5 +1,5 @@ import { Fragment, useCallback, useEffect, useMemo, useState } from "react"; -import { Loader2, ExternalLink, Lock, Trophy, Plus, ChevronRight, ChevronDown } from "lucide-react"; +import { Loader2, ExternalLink, Lock, Trophy, Plus, ChevronRight, ChevronDown, Play } from "lucide-react"; import { api, type AdminAuthArg, @@ -11,6 +11,7 @@ import { import { prizeTiersFor } from "@/lib/constants"; import { useToast } from "@/hooks/use-toast"; import { cn } from "@/lib/utils"; +import { ProjectReviewModal } from "@/components/admin/ProjectReviewModal"; type Draft = { requirements: number; techStack: number; innovation: number; notes: string }; type Tab = "score" | "results"; @@ -61,6 +62,8 @@ export function ProgramJudgingSection({ }) { const { toast } = useToast(); const [tab, setTab] = useState("score"); + // The demo currently previewed in the in-app review modal (null = closed). + const [review, setReview] = useState<{ url: string; title: string } | null>(null); const [view, setView] = useState(null); const [board, setBoard] = useState(null); const [drafts, setDrafts] = useState>({}); @@ -202,7 +205,7 @@ export function ProgramJudgingSection({ try { const auth = await getAuth(); await api.submitBallot(programSlug, auth); - toast({ title: "Scores submitted", description: "Your ballot is locked." }); + toast({ title: "Scores submitted", description: "Your scores now count. You can still revise and re-save them." }); await loadSubmissions(); } catch (e) { toast({ @@ -336,9 +339,13 @@ export function ProgramJudgingSection({ {!s.eligible && ( ·NOT IN LUMA )} - - VIDEO - + GITHUB @@ -357,7 +364,6 @@ export function ProgramJudgingSection({ type="number" min={0} max={BOUNDS[field]} - disabled={locked} value={d[field]} onChange={(e) => setField(s.id, field, e.target.value)} className="w-full font-mono text-[13px] bg-panel-deep border border-hairline text-display px-2 py-1.5 focus:outline-none focus:border-display disabled:opacity-50" @@ -368,7 +374,6 @@ export function ProgramJudgingSection({