From 6e36704bb160cf85c469c539b275bc2231af2f23 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 29 Apr 2026 13:17:29 +0200 Subject: [PATCH] feat(awards): notify jurors on assignment + admin reminder button The previous addJuror / bulkAddJurors / bulkInviteJurors flows silently created AwardJuror rows with no notification when the user already had an account. The result: assigned jurors had no idea they were assigned unless they happened to log in and check /jury/awards manually. Three changes: 1. New email template + sender (sendAwardJurorNotificationEmail). Tells the juror what the award is, how many projects are eligible, when voting closes, and links straight to /jury/awards/. Reused for both the initial assignment notification and admin reminders. 2. Auto-send on assignment. addJuror / bulkAddJurors / bulkInviteJurors now send the email to newly-attached jurors. bulkInviteJurors checks for a prior AwardJuror row before sending so duplicate "Bulk Invite" clicks don't spam jurors who were already assigned. addJuror / bulkAddJurors accept a `sendEmail` flag so admin tooling can opt out. 3. New admin procedure specialAward.notifyJurors(awardId, userIds?, customMessage?). Surfaced in the Jurors tab as a "Send reminder to all" button at the top and a per-row mail icon for individual reminders. Audit-logged with action: 'JUROR_REMINDER'. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/(admin)/admin/awards/[id]/page.tsx | 34 ++++- src/lib/email.ts | 96 +++++++++++++ src/server/routers/specialAward.ts | 156 +++++++++++++++++++-- 3 files changed, 277 insertions(+), 9 deletions(-) diff --git a/src/app/(admin)/admin/awards/[id]/page.tsx b/src/app/(admin)/admin/awards/[id]/page.tsx index 450316d..6dd9f2d 100644 --- a/src/app/(admin)/admin/awards/[id]/page.tsx +++ b/src/app/(admin)/admin/awards/[id]/page.tsx @@ -513,6 +513,13 @@ export default function AwardDetailPage({ }, onError: (err) => toast.error(err.message), }) + const notifyJurors = trpc.specialAward.notifyJurors.useMutation({ + onSuccess: (data) => { + const failedNote = data.failed > 0 ? ` (${data.failed} failed)` : '' + toast.success(`Reminder sent to ${data.sent} of ${data.targeted} juror(s)${failedNote}`) + }, + onError: (err) => toast.error(err.message), + }) const setWinner = trpc.specialAward.setWinner.useMutation({ onSuccess: invalidateAward, }) @@ -1335,7 +1342,7 @@ export default function AwardDetailPage({ {/* Jurors Tab */} -
+