From 61c4d0eb7593d45ea3948b7c22b30b2d3cd66d22 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 23 Feb 2026 19:14:37 +0100 Subject: [PATCH] Fix evaluation double-click submit: autosave was blocking the submit button Root cause: submit button was disabled when autosaveMutation.isPending was true. If the 3-second autosave timer fired while the user clicked Submit, the click was silently swallowed. User had to wait for autosave to finish, then click again. Fixes: - Remove autosaveMutation.isPending from submit button disabled state - Cancel pending autosave timer when submit starts (prevents race condition) - Add isSubmittingRef guard to prevent autosave from firing during submit - Reset submitting flag on validation failure or submit error Co-Authored-By: Claude Opus 4.6 --- .../projects/[projectId]/evaluate/page.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/app/(jury)/jury/competitions/[roundId]/projects/[projectId]/evaluate/page.tsx b/src/app/(jury)/jury/competitions/[roundId]/projects/[projectId]/evaluate/page.tsx index 19aa2bc..e00d154 100644 --- a/src/app/(jury)/jury/competitions/[roundId]/projects/[projectId]/evaluate/page.tsx +++ b/src/app/(jury)/jury/competitions/[roundId]/projects/[projectId]/evaluate/page.tsx @@ -40,6 +40,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) { const isDirtyRef = useRef(false) const evaluationIdRef = useRef(null) const isSubmittedRef = useRef(false) + const isSubmittingRef = useRef(false) const autosaveTimerRef = useRef | null>(null) const [lastSavedAt, setLastSavedAt] = useState(null) @@ -203,7 +204,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) { // Perform autosave const performAutosave = useCallback(async () => { - if (!isDirtyRef.current || isSubmittedRef.current) return + if (!isDirtyRef.current || isSubmittedRef.current || isSubmittingRef.current) return if (existingEvaluation?.status === 'SUBMITTED') return let evalId = evaluationIdRef.current @@ -311,8 +312,16 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) { } const handleSubmit = async () => { + // Cancel any pending autosave to avoid race conditions + if (autosaveTimerRef.current) { + clearTimeout(autosaveTimerRef.current) + autosaveTimerRef.current = null + } + isSubmittingRef.current = true + if (!myAssignment) { toast.error('Assignment not found') + isSubmittingRef.current = false return } @@ -325,14 +334,17 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) { const val = criteriaValues[c.id] if (c.type === 'numeric' && (val === undefined || val === null)) { toast.error(`Please score "${c.label}"`) + isSubmittingRef.current = false return } if (c.type === 'boolean' && val === undefined) { toast.error(`Please answer "${c.label}"`) + isSubmittingRef.current = false return } if (c.type === 'text' && (!val || (typeof val === 'string' && !val.trim()))) { toast.error(`Please fill in "${c.label}"`) + isSubmittingRef.current = false return } } @@ -342,6 +354,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) { const score = parseInt(globalScore, 10) if (isNaN(score) || score < 1 || score > 10) { toast.error('Please enter a valid score between 1 and 10') + isSubmittingRef.current = false return } } @@ -349,6 +362,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) { if (scoringMode === 'binary') { if (!binaryDecision) { toast.error('Please select accept or reject') + isSubmittingRef.current = false return } } @@ -356,6 +370,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) { if (requireFeedback) { if (!feedbackText.trim() || feedbackText.length < feedbackMinLength) { toast.error(`Please provide feedback (minimum ${feedbackMinLength} characters)`) + isSubmittingRef.current = false return } } @@ -398,6 +413,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) { }) } catch { // Error toast already handled by onError callback + isSubmittingRef.current = false } } @@ -862,7 +878,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {