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 (
+ <>
+
+
+ {/* 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',