feat: round finalization with ranking-based outcomes + award pool notifications
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m0s

- processRoundClose EVALUATION uses ranking scores + advanceMode config
  (threshold vs count) to auto-set proposedOutcome instead of defaulting all to PASSED
- Advancement emails generate invite tokens for passwordless users with
  "Create Your Account" CTA; rejection emails have no link
- Finalization UI shows account stats (invite vs dashboard link counts)
- Fixed getFinalizationSummary ranking query (was using non-existent rankingsJson)
- New award pool notification system: getAwardSelectionNotificationTemplate email,
  notifyEligibleProjects mutation with invite token generation,
  "Notify Pool" button on award detail page with custom message dialog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 19:14:41 +01:00
parent 7735f3ecdf
commit cfee3bc8a9
48 changed files with 5294 additions and 676 deletions

View File

@@ -0,0 +1,62 @@
'use client'
import { useState } from 'react'
import { trpc } from '@/lib/trpc/client'
import { toast } from 'sonner'
import { Trophy } from 'lucide-react'
import { EmailPreviewDialog } from './email-preview-dialog'
interface NotifyAdvancedButtonProps {
roundId: string
targetRoundId?: string
}
export function NotifyAdvancedButton({ roundId, targetRoundId }: NotifyAdvancedButtonProps) {
const [open, setOpen] = useState(false)
const [customMessage, setCustomMessage] = useState<string | undefined>()
const preview = trpc.round.previewAdvancementEmail.useQuery(
{ roundId, targetRoundId, customMessage },
{ enabled: open }
)
const sendMutation = trpc.round.sendAdvancementNotifications.useMutation({
onSuccess: (data) => {
toast.success(
`Sent ${data.sent} notification${data.sent !== 1 ? 's' : ''}${data.failed ? ` (${data.failed} failed)` : ''}`
)
setOpen(false)
},
onError: (err) => toast.error(err.message),
})
return (
<>
<button
onClick={() => setOpen(true)}
className="flex items-start gap-3 p-4 rounded-lg border border-l-4 border-l-emerald-500 hover:-translate-y-0.5 hover:shadow-md transition-all text-left"
>
<Trophy className="h-5 w-5 text-emerald-600 mt-0.5 shrink-0" />
<div>
<p className="text-sm font-medium">Notify Advanced Teams</p>
<p className="text-xs text-muted-foreground mt-0.5">
Send advancement emails to passed projects
</p>
</div>
</button>
<EmailPreviewDialog
open={open}
onOpenChange={setOpen}
title="Notify Advanced Teams"
description="Send advancement notification emails to project team members"
recipientCount={preview.data?.recipientCount ?? 0}
previewHtml={preview.data?.html}
isPreviewLoading={preview.isLoading}
onSend={(msg) => sendMutation.mutate({ roundId, targetRoundId, customMessage: msg })}
isSending={sendMutation.isPending}
onRefreshPreview={(msg) => setCustomMessage(msg)}
/>
</>
)
}