diff --git a/src/components/contests/ContestListingClient.tsx b/src/components/contests/ContestListingClient.tsx
index e8706ae..54de87b 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";
@@ -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}
diff --git a/src/components/contests/CreateRoomModal.tsx b/src/components/contests/CreateRoomModal.tsx
index b39de8e..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,
@@ -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,43 @@ export default function CreateRoomModal({
)}
-
-
-
+ )}
- {formData.presetId === "custom" && renderProblemConfiguration()}
+ {effectivePresetId === "custom" && renderProblemConfiguration()}
@@ -1232,10 +1253,10 @@ export default function CreateRoomModal({
- {[3, 5, 10, 15].map((mins) => (
+ {[4, 5, 10, 15].map((mins) => (
);
-}
+});
+
+export default CreateRoomModal;
diff --git a/src/lib/actions/contests.ts b/src/lib/actions/contests.ts
index cfe7ae4..1e6a379 100644
--- a/src/lib/actions/contests.ts
+++ b/src/lib/actions/contests.ts
@@ -397,6 +397,11 @@ export async function createRoomContest(
}));
}
+ const userIds = (data.registeredUsers || []).map((u: any) => new mongoose.Types.ObjectId(u.id));
+ const registeredCPUsers = await CPUser.find({ userId: { $in: userIds } }).lean();
+ const cpUserIdMap = new Map();
+ registeredCPUsers.forEach((cp: any) => cpUserIdMap.set(cp.userId.toString(), cp._id));
+
const contest = new ContestMatch({
name: data.name,
description: data.description,
@@ -421,7 +426,7 @@ export async function createRoomContest(
maxParticipants: maxParticipants,
},
registrations: (data.registeredUsers || []).map((u: any) => ({
- userId: new mongoose.Types.ObjectId(u.id),
+ userId: cpUserIdMap.get(u.id) || new mongoose.Types.ObjectId(u.id),
cfHandle: u.cfHandle,
teamName: u.teamName,
registeredAt: new Date(),
@@ -491,16 +496,25 @@ export async function getContestRegistrations(contestId: string) {
const User = (await import("@/models/User")).default;
const userIds = (contest.registrations || []).map((r: any) => r.userId);
- const users = await User.find({ _id: { $in: userIds } }, "image").lean();
+ const cpUsers = await CPUser.find({ _id: { $in: userIds } }).lean();
+ const actualUserIds = cpUsers.map((cp: any) => cp.userId);
+ const users = await User.find({ _id: { $in: actualUserIds } }, "image").lean();
const imageMap: Record
= {};
users.forEach((u: any) => {
if (u.image) imageMap[u._id.toString()] = u.image;
});
+ const cpUserMap: Record = {};
+ cpUsers.forEach((cp: any) => {
+ if (imageMap[cp.userId.toString()]) {
+ cpUserMap[cp._id.toString()] = imageMap[cp.userId.toString()];
+ }
+ });
+
const populatedRegistrations = (contest.registrations || []).map(
(r: any) => ({
...r,
- image: imageMap[r.userId.toString()] || null,
+ image: cpUserMap[r.userId.toString()] || null,
}),
);
@@ -749,6 +763,11 @@ export async function createBracketContest(data: any) {
if (isNaN(deadlineMinutes))
throw new Error("REGISTRATION_DEADLINE_MINUTES must be a valid number.");
+ const userIdsBracket = (data.registeredUsers || []).map((u: any) => new mongoose.Types.ObjectId(u.id));
+ const registeredCPUsersBracket = await CPUser.find({ userId: { $in: userIdsBracket } }).lean();
+ const cpUserIdMapBracket = new Map();
+ registeredCPUsersBracket.forEach((cp: any) => cpUserIdMapBracket.set(cp.userId.toString(), cp._id));
+
const contest = await ContestMatch.create({
name: data.name.trim(),
description: data.description?.trim(),
@@ -766,7 +785,7 @@ export async function createBracketContest(data: any) {
bulkMinContestId: bulkMinContestId || undefined,
problemSlots: problemSlots,
registrations: (data.registeredUsers || []).map((u: any) => ({
- userId: new mongoose.Types.ObjectId(u.id),
+ userId: cpUserIdMapBracket.get(u.id) || new mongoose.Types.ObjectId(u.id),
cfHandle: u.cfHandle,
teamName: u.teamName,
registeredAt: new Date(),
diff --git a/src/lib/bracket.ts b/src/lib/bracket.ts
index 71fac71..9f3c9bb 100644
--- a/src/lib/bracket.ts
+++ b/src/lib/bracket.ts
@@ -93,11 +93,11 @@ export async function generateBracket(
);
const cpUsers = await CPUser.find({
- userId: { $in: groupedTeams.flatMap((t) => t.memberIds) },
+ _id: { $in: groupedTeams.flatMap((t) => t.memberIds) },
}).lean();
const ratingMap = new Map();
for (const u of cpUsers) {
- ratingMap.set(toStr(u.userId), u.cfRating || 0);
+ ratingMap.set(toStr(u._id), u.cfRating || 0);
}
const seededTeams = groupedTeams.map((team) => {
diff --git a/src/lib/workers/reconciliationWorker.ts b/src/lib/workers/reconciliationWorker.ts
index 76f23ef..9b2756a 100644
--- a/src/lib/workers/reconciliationWorker.ts
+++ b/src/lib/workers/reconciliationWorker.ts
@@ -54,7 +54,7 @@ async function determineWinner(
let totalRating = 0;
let validMembers = 0;
for (const mId of members) {
- const cpUser = await CPUser.findOne({ userId: mId });
+ const cpUser = await CPUser.findById(mId);
if (cpUser && cpUser.cfRating) {
totalRating += cpUser.cfRating;
validMembers++;
@@ -230,7 +230,7 @@ export const reconciliationWorker = new Worker(
const { fetchCodeforcesUserStatus } = await import("../cf-api");
for (const uid of bracketUserIds) {
- const cpUser = await CPUser.findOne({ userId: uid });
+ const cpUser = await CPUser.findById(uid);
if (!cpUser || !cpUser.cfHandle) continue;
const solvedProblems: any[] = cpUser.solvedProblems || [];
let latestSolvedMs = 0;
@@ -311,7 +311,7 @@ export const reconciliationWorker = new Worker(
// Build solved union from refreshed CPUser docs
const bracketRefreshedUsers = await CPUser.find({
- userId: { $in: bracketUserIds },
+ _id: { $in: bracketUserIds },
});
const bracketSolvedIds = new Set(
bracketRefreshedUsers.flatMap((u) =>
@@ -452,7 +452,7 @@ export const reconciliationWorker = new Worker(
const { fetchCodeforcesUserStatus } = await import("../cf-api");
for (const uid of allUserIds) {
- const cpUser = await CPUser.findOne({ userId: uid });
+ const cpUser = await CPUser.findById(uid);
if (!cpUser || !cpUser.cfHandle) continue;
// Find the timestamp of the most recently recorded solve
@@ -509,7 +509,7 @@ export const reconciliationWorker = new Worker(
}
// Build union of all solved problem IDs from the (now refreshed) CPUser docs
- const refreshedUsers = await CPUser.find({ userId: { $in: allUserIds } });
+ const refreshedUsers = await CPUser.find({ _id: { $in: allUserIds } });
const solvedProblemIds = new Set(
refreshedUsers.flatMap((u) =>
(u.solvedProblems || []).map((sp: any) => sp.problemId),