From cf38738cb4ff6b90ae5b85a57ffed5fa275a7ed9 Mon Sep 17 00:00:00 2001 From: reehals Date: Mon, 29 Sep 2025 21:54:27 -0700 Subject: [PATCH 1/3] Fix panel judging opt in --- app/(api)/_actions/auth/verifyCode.ts | 15 +- .../_actions/logic/assignJudgesToPanels.ts | 8 +- app/(pages)/_components/AuthForm/AuthForm.tsx | 39 ++-- app/(pages)/_hooks/useAuthForm.ts | 10 +- .../admin/_components/Judges/JudgeCard.tsx | 3 + .../admin/_components/Judges/JudgeForm.tsx | 43 ++++- .../_components/AuthForms/CheckInForm.tsx | 13 +- app/_types/panel.ts | 2 +- app/_types/user.ts | 1 + ...0042214-add-opted-into-panels-to-users.mjs | 176 ++++++++++++++++++ ...930045209-fix-panels-domain-validation.mjs | 112 +++++++++++ 11 files changed, 389 insertions(+), 33 deletions(-) create mode 100644 migrations/20250930042214-add-opted-into-panels-to-users.mjs create mode 100644 migrations/20250930045209-fix-panels-domain-validation.mjs diff --git a/app/(api)/_actions/auth/verifyCode.ts b/app/(api)/_actions/auth/verifyCode.ts index a49dfa38..3aebe2f5 100644 --- a/app/(api)/_actions/auth/verifyCode.ts +++ b/app/(api)/_actions/auth/verifyCode.ts @@ -2,17 +2,24 @@ import { updateUser } from '@actions/users/updateUser'; -export default async function verifyCode(id: string, code: string) { +export default async function verifyCode( + id: string, + code: string, + optedIntoPanels?: boolean +) { try { const validCode = code === (process.env.CHECK_IN_CODE as string); if (!validCode) { throw new Error('Invalid code.'); } + const update: any = { has_checked_in: true }; + if (typeof optedIntoPanels === 'boolean') { + update.opted_into_panels = optedIntoPanels; + } + const res = await updateUser(id, { - $set: { - has_checked_in: true, - }, + $set: update, }); if (!res.ok) { diff --git a/app/(api)/_actions/logic/assignJudgesToPanels.ts b/app/(api)/_actions/logic/assignJudgesToPanels.ts index 0ffa6a6f..aa95b860 100644 --- a/app/(api)/_actions/logic/assignJudgesToPanels.ts +++ b/app/(api)/_actions/logic/assignJudgesToPanels.ts @@ -35,7 +35,13 @@ export default async function assignJudgesToPanels(panelSize: number = 5) { }; } - const judgesRes = await GetManyUsers({ role: 'judge', has_checked_in: true }); + // only judges who have checked in and explicitly opted into panels + const judgesRes = await GetManyUsers({ + role: 'judge', + has_checked_in: true, + opted_into_panels: true, + }); + console.log(judgesRes); if (!judgesRes.ok || judgesRes.body.length === 0) { return { ok: false, diff --git a/app/(pages)/_components/AuthForm/AuthForm.tsx b/app/(pages)/_components/AuthForm/AuthForm.tsx index 53bd3873..491f3f79 100644 --- a/app/(pages)/_components/AuthForm/AuthForm.tsx +++ b/app/(pages)/_components/AuthForm/AuthForm.tsx @@ -11,10 +11,9 @@ import hackerStyles from './HackerAuthForm.module.scss'; import judgeStyles from './JudgeAuthForm.module.scss'; type Role = 'hacker' | 'judge'; -type FieldName = 'email' | 'password' | 'passwordDupe' | 'code'; interface FormField { - name: FieldName; + name: string; type: string; label: string; placeholder?: string; @@ -27,8 +26,8 @@ interface AuthFormProps { buttonText: string; linkText?: string; linkHref?: string; - initialValues: Record; - onSubmit: (values: Record) => Promise; + initialValues: Record; + onSubmit: (values: Record) => Promise; onSuccess: () => void; } @@ -68,17 +67,27 @@ export default function AuthForm({

{errors[field.name]}

- + {field.type === 'checkbox' ? ( + + ) : ( + + )}
))} diff --git a/app/(pages)/_hooks/useAuthForm.ts b/app/(pages)/_hooks/useAuthForm.ts index 38470da9..bb0bf941 100644 --- a/app/(pages)/_hooks/useAuthForm.ts +++ b/app/(pages)/_hooks/useAuthForm.ts @@ -3,7 +3,7 @@ import { useEffect, useState, ChangeEvent, FormEvent } from 'react'; interface FieldValues { - [key: string]: string; + [key: string]: any; } interface FieldErrors { @@ -77,8 +77,12 @@ export default function useAuthForm( }; const handleChange = (e: ChangeEvent) => { - const { name, value } = e.target; - setFieldValue(name, value); + const { name, value, type, checked } = e.target as any; + if (type === 'checkbox') { + setFieldValue(name, checked); + } else { + setFieldValue(name, value); + } }; const handleSubmit = async (e: FormEvent) => { diff --git a/app/(pages)/admin/_components/Judges/JudgeCard.tsx b/app/(pages)/admin/_components/Judges/JudgeCard.tsx index 48a29ce7..fdc0ad12 100644 --- a/app/(pages)/admin/_components/Judges/JudgeCard.tsx +++ b/app/(pages)/admin/_components/Judges/JudgeCard.tsx @@ -36,6 +36,9 @@ export default function JudgeCard({ )}

{judge.email}

+

+ Panel Opt-in: {judge.opted_into_panels ? 'Yes' : 'No'} +

{editable && ( diff --git a/app/(pages)/admin/_components/Judges/JudgeForm.tsx b/app/(pages)/admin/_components/Judges/JudgeForm.tsx index f4e51a45..49d36ee9 100644 --- a/app/(pages)/admin/_components/Judges/JudgeForm.tsx +++ b/app/(pages)/admin/_components/Judges/JudgeForm.tsx @@ -49,10 +49,16 @@ export default function JudgeForm({ teams.body.map((team: any) => [team._id, team]) ); - if (data?._id) { - data.teams = data.teams.map((team: any) => { - return teamMap[team._id]; - }); + if (data?._id && data.teams) { + data.teams = data.teams + .map((team: any) => { + // Handle case where team might be just an ID string or already be a full team object + if (typeof team === 'string') { + return teamMap[team] || team; + } + return team._id ? teamMap[team._id] || team : team; + }) + .filter(Boolean); // Remove any undefined teams } const onSubmit = async (event: React.FormEvent) => { @@ -91,6 +97,11 @@ export default function JudgeForm({ validation: (has_checked_in: any) => has_checked_in === true || has_checked_in === false, }, + { + field: 'opted_into_panels', + validation: (opted_into_panels: any) => + opted_into_panels === true || opted_into_panels === false, + }, ]; verificationList.forEach(({ field, validation }) => { @@ -104,13 +115,23 @@ export default function JudgeForm({ return; } - const { _id, name, email, role, specialties, teams, has_checked_in } = data; + const { + _id, + name, + email, + role, + specialties, + teams, + has_checked_in, + opted_into_panels, + } = data; const body = { name, email, role, specialties, has_checked_in, + opted_into_panels, }; const res = await updateJudgeWithTeams(_id, { $set: body }, teams); @@ -195,7 +216,7 @@ export default function JudgeForm({ itemRenderer={({ key, item, deleteItem, shiftUp, shiftDown }) => { return (
- + {/* */}
@@ -232,6 +253,16 @@ export default function JudgeForm({ { option: 'false', value: false }, ]} /> + updateField('opted_into_panels', value)} + width="400px" + options={[ + { option: 'true', value: true }, + { option: 'false', value: false }, + ]} + />