diff --git a/src/app/(admin)/admin/rounds/[roundId]/page.tsx b/src/app/(admin)/admin/rounds/[roundId]/page.tsx index b28ad03..d40f535 100644 --- a/src/app/(admin)/admin/rounds/[roundId]/page.tsx +++ b/src/app/(admin)/admin/rounds/[roundId]/page.tsx @@ -96,6 +96,7 @@ import { MentoringProjectsTable } from '@/components/admin/round/mentoring-proje import { FinalistSlotsCard } from '@/components/admin/grand-finale/finalist-slots-card' import { WaitlistCard } from '@/components/admin/grand-finale/waitlist-card' import { FinalistEnrollmentCard } from '@/components/admin/grand-finale/finalist-enrollment-card' +import { FinalDocsReminderButton } from '@/components/admin/grand-finale/final-docs-reminder-button' import { RankingDashboard } from '@/components/admin/round/ranking-dashboard' import { CoverageReport } from '@/components/admin/assignment/coverage-report' import { AssignmentPreviewSheet } from '@/components/admin/assignment/assignment-preview-sheet' @@ -1529,6 +1530,9 @@ export default function RoundDetailPage() { {isGrandFinale && programId && ( <> +
+ +
diff --git a/src/components/admin/grand-finale/final-docs-reminder-button.tsx b/src/components/admin/grand-finale/final-docs-reminder-button.tsx new file mode 100644 index 0000000..15e38f6 --- /dev/null +++ b/src/components/admin/grand-finale/final-docs-reminder-button.tsx @@ -0,0 +1,82 @@ +'use client' + +import { useState } from 'react' +import { trpc } from '@/lib/trpc/client' +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog' +import { EmailPreviewDialog } from '@/components/admin/round/email-preview-dialog' +import { Eye, Mail, Send } from 'lucide-react' +import { toast } from 'sonner' + +const REMINDER_TYPE = 'GRAND_FINAL_DOCS_REMINDER' + +export function FinalDocsReminderButton({ programId }: { programId: string }) { + const [open, setOpen] = useState(false) + const [previewOpen, setPreviewOpen] = useState(false) + + const preview = trpc.notification.previewEmailTemplate.useQuery( + { notificationType: REMINDER_TYPE }, + { enabled: previewOpen }, + ) + + const send = trpc.finalist.sendDocumentReminders.useMutation({ + onSuccess: (r) => { + toast.success(`Reminder sent to ${r.sent} team${r.sent === 1 ? '' : 's'}`) + setOpen(false) + }, + onError: (e) => toast.error(e.message), + }) + + return ( + <> + + + + + + + Remind finalist teams + + Sends an in-app + email reminder to every finalist team with missing required + documents. + + + + + + + + + + {/* Self-contained preview dialog — rendered as a sibling so it is not nested + inside the confirm dialog's content. */} + {}} + isSending={false} + previewOnly + showCustomMessage={false} + /> + + ) +} diff --git a/src/server/routers/notification.ts b/src/server/routers/notification.ts index f5988e7..83062c2 100644 --- a/src/server/routers/notification.ts +++ b/src/server/routers/notification.ts @@ -119,6 +119,11 @@ const NOTIFICATION_SAMPLE_DATA: Record> = { FINALIST_EXPIRED: { projectTitle: 'Ocean Cleanup Initiative', category: 'STARTUP' }, FINALIST_WAITLIST_PROMOTED: { projectTitle: 'Reef Guardians', category: 'STARTUP' }, FINALIST_REMINDER: { projectTitle: 'Ocean Cleanup Initiative', deadline: new Date(Date.now() + 86_400_000).toISOString() }, + GRAND_FINAL_DOCS_REMINDER: { + projectTitle: 'Ocean Cleanup Initiative', + deadline: new Date(Date.now() + 5 * 86_400_000).toISOString(), + missing: ['Pitch deck', 'Final video', 'Team photo'], + }, FINALIST_WITHDRAWN: { projectTitle: 'Ocean Cleanup Initiative', reason: 'Schedule conflict' }, TRAVEL_CONFIRMED: { projectTitle: 'Ocean Cleanup Initiative',