fix(mentor): unbreak the mentor pipeline end-to-end
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m42s

Adding the MENTOR role from /admin/members/[id] only updated React state — the
AlertDialog "Add role" confirmation never called the server, so prod ended up
with zero users in MENTOR roles[] and /admin/mentors showed "No mentors yet".
The dialog now awaits updateUser.mutateAsync({ roles }) before closing.

Other corrections in the same area:

- DialogContent uses flex flex-col with max-h-[90vh] overflow-y-auto so tall
  modals (e.g. Add Project to Round) scroll internally instead of overflowing
  past their own rounded background.
- getProjectsNeedingMentor now matches autoAssignBulkForRound exactly: both
  filter mentorAssignments by droppedAt: null and require
  finalistConfirmation: CONFIRMED, so the toolbar count never exceeds what
  auto-fill actually processes. The toolbar surfaces hasNoMentors /
  hasNoEligible / count / all-assigned as distinct states instead of one
  misleading "All eligible projects have a mentor" line.
- New per-team table (MentoringProjectsTable) replaces ProjectStatesTable on
  the Projects tab of MENTORING rounds. Lists every project with its active
  mentors (multi-mentor aware), filter pills, search, finalist-confirmation
  badge, and a per-row link to /admin/projects/[id]/mentor for assigning.
- Applicant team page now lists ALL active mentors (PR8 Task 7) instead of
  just mentorAssignments[0].
- Hard guard in src/lib/email.ts short-circuits sendEmail when NODE_ENV=test
  or VITEST=true so test runs can never emit real notifications again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-05-26 13:01:05 +02:00
parent 5b99d6a530
commit 921019aaa4
8 changed files with 443 additions and 41 deletions

View File

@@ -357,15 +357,33 @@ export default function ApplicantProjectPage() {
)}
</div>
{/* Mentor info — TODO(PR8 Task 7): list ALL assigned mentors */}
{project.mentorAssignments?.[0]?.mentor && (
<div className="rounded-lg border p-3 bg-muted/50">
<p className="text-sm font-medium mb-1">Assigned Mentor</p>
<p className="text-sm text-muted-foreground">
{project.mentorAssignments[0].mentor.name} ({project.mentorAssignments[0].mentor.email})
</p>
</div>
)}
{(() => {
type MentorAssignment = {
droppedAt: Date | string | null
mentor: { name: string | null; email: string } | null
}
const active = (
(project.mentorAssignments as MentorAssignment[] | undefined) ?? []
).filter((a) => !a.droppedAt && a.mentor)
if (active.length === 0) return null
return (
<div className="rounded-lg border p-3 bg-muted/50">
<p className="text-sm font-medium mb-1">
{active.length === 1 ? 'Assigned Mentor' : `Assigned Mentors (${active.length})`}
</p>
<ul className="space-y-0.5">
{active.map((a, idx) => (
<li key={`${a.mentor!.email}-${idx}`} className="text-sm text-muted-foreground">
{a.mentor!.name ?? a.mentor!.email}
{a.mentor!.name && (
<span className="text-xs"> ({a.mentor!.email})</span>
)}
</li>
))}
</ul>
</div>
)
})()}
{/* Tags */}
{project.tags && project.tags.length > 0 && (