From 81556605e5acc0ac999f8c18e9c6281b9a88c224 Mon Sep 17 00:00:00 2001 From: Ronit Date: Thu, 30 Jul 2026 23:03:31 +0530 Subject: [PATCH 1/5] fix: contest creation bugs, bulk mode CPUser lookup, and non-admin presets --- scripts/seed.ts | 4 +- src/components/contests/CreateRoomModal.tsx | 139 +++++++++++--------- src/lib/actions/contests.ts | 27 +++- src/lib/bracket.ts | 4 +- src/lib/workers/reconciliationWorker.ts | 10 +- 5 files changed, 112 insertions(+), 72 deletions(-) diff --git a/scripts/seed.ts b/scripts/seed.ts index df3e097..5da29e9 100644 --- a/scripts/seed.ts +++ b/scripts/seed.ts @@ -62,8 +62,8 @@ async function seed() { // Seed main dev user const devUser = { - name: "Coding Club IITG", - email: "codingclub@iitg.ac.in", + name: "Ronit Sonawane", + email: "k.sonawane@iitg.ac.in", role: "Secretary", moduleRoles: [], emailVerified: true, diff --git a/src/components/contests/CreateRoomModal.tsx b/src/components/contests/CreateRoomModal.tsx index b39de8e..3b37f72 100644 --- a/src/components/contests/CreateRoomModal.tsx +++ b/src/components/contests/CreateRoomModal.tsx @@ -224,8 +224,44 @@ export default function CreateRoomModal({ registeredUsers.length, ]); + useEffect(() => { + if ( + formData.problemSelectionMode === "fine-tuned" && + formData.format === "bracket" + ) { + const maxP = Math.max(2, formData.maxParticipants || 2); + const totalRounds = Math.ceil(Math.log2(maxP)); + const ppm = formData.bulkProblemCount || 3; + + const syncedRounds: { roundNumber: number; problemIds: string[] }[] = []; + for (let r = 1; r <= totalRounds; r++) { + const matchCount = Math.pow(2, totalRounds - r); + const needed = matchCount * ppm; + const existing = bracketRoundProblems.find((x) => x.roundNumber === r); + const ids = existing ? [...existing.problemIds] : []; + while (ids.length < needed) ids.push(""); + while (ids.length > needed) ids.pop(); + syncedRounds.push({ roundNumber: r, problemIds: ids }); + } + + if ( + JSON.stringify(syncedRounds) !== JSON.stringify(bracketRoundProblems) + ) { + setBracketRoundProblems(syncedRounds); + } + } + }, [ + formData.problemSelectionMode, + formData.format, + formData.maxParticipants, + formData.bulkProblemCount, + bracketRoundProblems, + ]); + if (!isOpen) return null; + const effectivePresetId = isAdmin ? formData.presetId : "custom"; + const isTeamSizeLocked = [ "1v1", "solo-tournament", @@ -313,8 +349,10 @@ export default function CreateRoomModal({ } } + const finalPresetId = isAdmin ? formData.presetId : "custom"; + if (formData.format === "bracket") { - if (!formData.presetId) { + if (!finalPresetId) { alert("Please select a match preset for the bracket."); return; } @@ -347,6 +385,7 @@ export default function CreateRoomModal({ try { const res = await createBracketContest({ ...formData, + presetId: finalPresetId, deadline: start.toISOString(), registrationStartTime: regStartIso, registeredUsers: finalRegisteredUsers, @@ -777,27 +816,7 @@ export default function CreateRoomModal({ const totalRounds = Math.ceil(Math.log2(maxP)); const ppm = formData.bulkProblemCount || 3; // problems per match - // Sync bracketRoundProblems structure with computed rounds - const syncedRounds: { roundNumber: number; problemIds: string[] }[] = - []; - for (let r = 1; r <= totalRounds; r++) { - const matchCount = Math.pow(2, totalRounds - r); - const needed = matchCount * ppm; - const existing = bracketRoundProblems.find( - (x) => x.roundNumber === r, - ); - const ids = existing ? [...existing.problemIds] : []; - while (ids.length < needed) ids.push(""); - while (ids.length > needed) ids.pop(); - syncedRounds.push({ roundNumber: r, problemIds: ids }); - } - if ( - JSON.stringify(syncedRounds) !== - JSON.stringify(bracketRoundProblems) - ) { - // Use setTimeout to avoid updating state during render - setTimeout(() => setBracketRoundProblems(syncedRounds), 0); - } + const syncedRounds = bracketRoundProblems; const getRoundLabel = (r: number) => { const matchCount = Math.pow(2, totalRounds - r); @@ -1047,42 +1066,44 @@ export default function CreateRoomModal({ )} -
- - + setFormData({ ...formData, presetId: e.target.value }) + } + disabled={!!topPresetId} + className={`${styles.formInput} ${styles.formSelect}`} + > + - ))} - - - Bracket tournaments use presets to define the problem - criteria for all rounds. - - {(() => { + + {presets.map((p) => ( + + ))} + + + Bracket tournaments use presets to define the problem + criteria for all rounds. + +
+ )} {(() => { const selectedMatchPreset = presets.find( - (p) => p._id === formData.presetId, + (p) => p._id === effectivePresetId, ); return selectedMatchPreset ? (
@@ -1113,7 +1134,7 @@ export default function CreateRoomModal({ })()}
- {formData.presetId === "custom" && renderProblemConfiguration()} + {effectivePresetId === "custom" && renderProblemConfiguration()}
@@ -1232,10 +1253,10 @@ export default function CreateRoomModal({
- )} {(() => { + {(() => { const selectedMatchPreset = presets.find( (p) => p._id === effectivePresetId, ); @@ -1132,7 +1131,8 @@ export default function CreateRoomModal({
) : null; })()} -
+ + )} {effectivePresetId === "custom" && renderProblemConfiguration()} From 18129e02c62fbbe4d99ab3f9d006cd173096be06 Mon Sep 17 00:00:00 2001 From: Ronit Date: Thu, 30 Jul 2026 23:21:51 +0530 Subject: [PATCH 4/5] perf: memoize CreateRoomModal to prevent lag from parent timer --- src/components/contests/ContestListingClient.tsx | 4 ++-- src/components/contests/CreateRoomModal.tsx | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/contests/ContestListingClient.tsx b/src/components/contests/ContestListingClient.tsx index e8706ae..151ecda 100644 --- a/src/components/contests/ContestListingClient.tsx +++ b/src/components/contests/ContestListingClient.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { type ContestListingItem } from "@/lib/actions/contests"; import Link from "next/link"; @@ -227,7 +227,7 @@ export default function ContestListingClient({ {showCreateModal && ( setShowCreateModal(false)} + onClose={useCallback(() => setShowCreateModal(false), [])} isAdmin={isAdmin} presets={presets} deadlineMinutes={deadlineMinutes} diff --git a/src/components/contests/CreateRoomModal.tsx b/src/components/contests/CreateRoomModal.tsx index b1541e6..0354516 100644 --- a/src/components/contests/CreateRoomModal.tsx +++ b/src/components/contests/CreateRoomModal.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, memo } from "react"; import { GripVertical, Lock, @@ -17,7 +17,7 @@ import { getDisplayName } from "@/lib/utils"; import { CF_CONTEST_YEAR_OPTIONS } from "@/lib/constants"; import styles from "./CreateRoomModal.module.scss"; -export default function CreateRoomModal({ +const CreateRoomModal = memo(function CreateRoomModal({ isOpen, onClose, isAdmin = false, @@ -1872,4 +1872,6 @@ export default function CreateRoomModal({ ); -} +}); + +export default CreateRoomModal; From 2d7095098b54ccea5a1f3b2ebbd7d4bc246ff184 Mon Sep 17 00:00:00 2001 From: Ronit Date: Thu, 30 Jul 2026 23:22:42 +0530 Subject: [PATCH 5/5] fix: resolve Rules of Hooks violation in ContestListingClient --- src/components/contests/ContestListingClient.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/contests/ContestListingClient.tsx b/src/components/contests/ContestListingClient.tsx index 151ecda..54de87b 100644 --- a/src/components/contests/ContestListingClient.tsx +++ b/src/components/contests/ContestListingClient.tsx @@ -222,12 +222,14 @@ export default function ContestListingClient({ } }; + const handleCloseCreateModal = useCallback(() => setShowCreateModal(false), []); + return (
{showCreateModal && ( setShowCreateModal(false), [])} + onClose={handleCloseCreateModal} isAdmin={isAdmin} presets={presets} deadlineMinutes={deadlineMinutes}