From 924f8071e154228d7a652840ca04f8d59bc13a84 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 3 Mar 2026 22:57:52 +0100 Subject: [PATCH] feat: add email preview to award notification and finalization tab - Award "Notify Pool" dialog now uses EmailPreviewDialog with live preview - Button shows eligible project count: "Notify Pool (38)" - Finalization tab email section has "Preview" buttons for both advancement and rejection messages - EmailPreviewDialog supports previewOnly mode (close button, no send) - Backend: previewAwardSelectionEmail, previewFinalizationAdvancementEmail, previewFinalizationRejectionEmail queries Co-Authored-By: Claude Opus 4.6 --- src/app/(admin)/admin/awards/[id]/page.tsx | 83 +++++-------------- .../admin/round/email-preview-dialog.tsx | 46 +++++----- .../admin/round/finalization-tab.tsx | 76 ++++++++++++++++- src/server/routers/roundEngine.ts | 64 ++++++++++++++ src/server/routers/specialAward.ts | 27 +++++- 5 files changed, 212 insertions(+), 84 deletions(-) diff --git a/src/app/(admin)/admin/awards/[id]/page.tsx b/src/app/(admin)/admin/awards/[id]/page.tsx index 27eb32f..be4ec05 100644 --- a/src/app/(admin)/admin/awards/[id]/page.tsx +++ b/src/app/(admin)/admin/awards/[id]/page.tsx @@ -53,11 +53,11 @@ import { } from '@/components/ui/dialog' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Input } from '@/components/ui/input' -import { Textarea } from '@/components/ui/textarea' import { Progress } from '@/components/ui/progress' import { UserAvatar } from '@/components/shared/user-avatar' import { AnimatedCard } from '@/components/shared/animated-container' import { Pagination } from '@/components/shared/pagination' +import { EmailPreviewDialog } from '@/components/admin/round/email-preview-dialog' import { toast } from 'sonner' import { Tooltip, @@ -158,7 +158,7 @@ export default function AwardDetailPage({ const [addRoundOpen, setAddRoundOpen] = useState(false) const [roundForm, setRoundForm] = useState({ name: '', roundType: 'EVALUATION' as string }) const [notifyDialogOpen, setNotifyDialogOpen] = useState(false) - const [notifyCustomMessage, setNotifyCustomMessage] = useState('') + const [notifyCustomMessage, setNotifyCustomMessage] = useState() // Pagination for eligibility list const [eligibilityPage, setEligibilityPage] = useState(1) @@ -287,15 +287,15 @@ export default function AwardDetailPage({ onError: (err) => toast.error(err.message), }) - const { data: notifyStats } = trpc.specialAward.getNotificationStats.useQuery( - { awardId }, + const notifyPreview = trpc.specialAward.previewAwardSelectionEmail.useQuery( + { awardId, customMessage: notifyCustomMessage }, { enabled: notifyDialogOpen } ) const notifyEligible = trpc.specialAward.notifyEligibleProjects.useMutation({ onSuccess: (result) => { toast.success(`Notified ${result.notified} projects (${result.emailsSent} emails sent${result.emailsFailed ? `, ${result.emailsFailed} failed` : ''})`) setNotifyDialogOpen(false) - setNotifyCustomMessage('') + setNotifyCustomMessage(undefined) }, onError: (err) => toast.error(err.message), }) @@ -486,63 +486,22 @@ export default function AwardDetailPage({ )} {award.status === 'NOMINATIONS_OPEN' && ( <> - - - - - - - Notify Eligible Projects - - Send "Selected for {award.name}" emails to all {award.eligibleCount} eligible projects. - - -
- {notifyStats && ( -
- {notifyStats.needsInvite > 0 && ( - - {notifyStats.needsInvite} will receive Create Account link - - )} - {notifyStats.hasAccount > 0 && ( - - {notifyStats.hasAccount} will receive Dashboard link - - )} -
- )} -
- -