feat(final-docs): admin manual reminder button
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 && (
|
||||
<>
|
||||
<FinalistEnrollmentCard programId={programId} roundId={roundId} />
|
||||
<div className="flex justify-end">
|
||||
<FinalDocsReminderButton programId={programId} />
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<FinalistSlotsCard programId={programId} />
|
||||
<WaitlistCard programId={programId} />
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<Mail className="mr-2 h-4 w-4" /> Remind teams to upload final documents
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Remind finalist teams</DialogTitle>
|
||||
<DialogDescription>
|
||||
Sends an in-app + email reminder to every finalist team with missing required
|
||||
documents.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="sm:justify-between">
|
||||
<Button variant="ghost" size="sm" onClick={() => setPreviewOpen(true)}>
|
||||
<Eye className="mr-2 h-4 w-4" /> Preview email
|
||||
</Button>
|
||||
<Button onClick={() => send.mutate({ programId })} disabled={send.isPending}>
|
||||
<Send className="mr-2 h-4 w-4" /> {send.isPending ? 'Sending…' : 'Send reminders'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Self-contained preview dialog — rendered as a sibling so it is not nested
|
||||
inside the confirm dialog's content. */}
|
||||
<EmailPreviewDialog
|
||||
open={previewOpen}
|
||||
onOpenChange={setPreviewOpen}
|
||||
title="Final Documents Reminder"
|
||||
description="Preview of the email finalist teams receive."
|
||||
recipientCount={0}
|
||||
previewHtml={preview.data?.html}
|
||||
isPreviewLoading={preview.isLoading}
|
||||
onSend={() => {}}
|
||||
isSending={false}
|
||||
previewOnly
|
||||
showCustomMessage={false}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -119,6 +119,11 @@ const NOTIFICATION_SAMPLE_DATA: Record<string, Record<string, unknown>> = {
|
||||
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',
|
||||
|
||||
Reference in New Issue
Block a user