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 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 22:57:52 +01:00
parent f79a6d1341
commit 924f8071e1
5 changed files with 212 additions and 84 deletions

View File

@@ -39,9 +39,11 @@ import {
ChevronRight,
Mail,
Send,
Eye,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { projectStateConfig } from '@/lib/round-config'
import { EmailPreviewDialog } from './email-preview-dialog'
// ── Types ──────────────────────────────────────────────────────────────────
@@ -81,6 +83,10 @@ export function FinalizationTab({ roundId, roundStatus }: FinalizationTabProps)
const [emailSectionOpen, setEmailSectionOpen] = useState(false)
const [advancementMessage, setAdvancementMessage] = useState('')
const [rejectionMessage, setRejectionMessage] = useState('')
const [advancePreviewOpen, setAdvancePreviewOpen] = useState(false)
const [rejectPreviewOpen, setRejectPreviewOpen] = useState(false)
const [advancePreviewMsg, setAdvancePreviewMsg] = useState<string | undefined>()
const [rejectPreviewMsg, setRejectPreviewMsg] = useState<string | undefined>()
// Mutations
const updateOutcome = trpc.roundEngine.updateProposedOutcome.useMutation({
@@ -121,6 +127,16 @@ export function FinalizationTab({ roundId, roundStatus }: FinalizationTabProps)
onError: (err) => toast.error(err.message),
})
// Email preview queries
const advancePreview = trpc.roundEngine.previewFinalizationAdvancementEmail.useQuery(
{ roundId, customMessage: advancePreviewMsg },
{ enabled: advancePreviewOpen }
)
const rejectPreview = trpc.roundEngine.previewFinalizationRejectionEmail.useQuery(
{ roundId, customMessage: rejectPreviewMsg },
{ enabled: rejectPreviewOpen }
)
// Filtered projects
const filteredProjects = useMemo(() => {
if (!summary) return []
@@ -624,7 +640,21 @@ export function FinalizationTab({ roundId, roundStatus }: FinalizationTabProps)
</div>
)}
<div>
<label className="text-sm font-medium mb-1.5 block">Advancement Message</label>
<div className="flex items-center justify-between mb-1.5">
<label className="text-sm font-medium">Advancement Message</label>
<Button
variant="ghost"
size="sm"
className="h-7 text-xs"
onClick={() => {
setAdvancePreviewMsg(advancementMessage || undefined)
setAdvancePreviewOpen(true)
}}
>
<Eye className="h-3.5 w-3.5 mr-1" />
Preview
</Button>
</div>
<Textarea
placeholder="Custom message for projects that are advancing (added to the standard email template)..."
value={advancementMessage}
@@ -633,7 +663,21 @@ export function FinalizationTab({ roundId, roundStatus }: FinalizationTabProps)
/>
</div>
<div>
<label className="text-sm font-medium mb-1.5 block">Rejection Message</label>
<div className="flex items-center justify-between mb-1.5">
<label className="text-sm font-medium">Rejection Message</label>
<Button
variant="ghost"
size="sm"
className="h-7 text-xs"
onClick={() => {
setRejectPreviewMsg(rejectionMessage || undefined)
setRejectPreviewOpen(true)
}}
>
<Eye className="h-3.5 w-3.5 mr-1" />
Preview
</Button>
</div>
<Textarea
placeholder="Custom message for projects that are not advancing (added to the standard email template)..."
value={rejectionMessage}
@@ -718,6 +762,34 @@ export function FinalizationTab({ roundId, roundStatus }: FinalizationTabProps)
</CardContent>
</Card>
)}
{/* Email Preview Dialogs */}
<EmailPreviewDialog
open={advancePreviewOpen}
onOpenChange={setAdvancePreviewOpen}
title="Advancement Email Preview"
description="Preview of the email sent to advancing project teams"
recipientCount={advancePreview.data?.recipientCount ?? passedCount}
previewHtml={advancePreview.data?.html}
isPreviewLoading={advancePreview.isLoading}
onSend={() => setAdvancePreviewOpen(false)}
isSending={false}
showCustomMessage={false}
previewOnly
/>
<EmailPreviewDialog
open={rejectPreviewOpen}
onOpenChange={setRejectPreviewOpen}
title="Rejection Email Preview"
description="Preview of the email sent to non-advancing project teams"
recipientCount={rejectPreview.data?.recipientCount ?? rejectedCount}
previewHtml={rejectPreview.data?.html}
isPreviewLoading={rejectPreview.isLoading}
onSend={() => setRejectPreviewOpen(false)}
isSending={false}
showCustomMessage={false}
previewOnly
/>
</div>
)
}