'use client' import { useState } from 'react' import Link from 'next/link' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Badge } from '@/components/ui/badge' import { Skeleton } from '@/components/ui/skeleton' import { Checkbox } from '@/components/ui/checkbox' import { Switch } from '@/components/ui/switch' import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Tabs, TabsContent, TabsList, TabsTrigger, } from '@/components/ui/tabs' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Send, Mail, Bell, Clock, Loader2, LayoutTemplate, AlertCircle, Inbox, CheckCircle2, Eye, } from 'lucide-react' import { toast } from 'sonner' import { formatDate } from '@/lib/utils' type RecipientType = 'ALL' | 'ROLE' | 'STAGE_JURY' | 'PROGRAM_TEAM' | 'USER' const RECIPIENT_TYPE_OPTIONS: { value: RecipientType; label: string }[] = [ { value: 'ALL', label: 'All Users' }, { value: 'ROLE', label: 'By Role' }, { value: 'STAGE_JURY', label: 'Stage Jury' }, { value: 'PROGRAM_TEAM', label: 'Program Team' }, { value: 'USER', label: 'Specific User' }, ] const ROLES = ['JURY_MEMBER', 'MENTOR', 'OBSERVER', 'APPLICANT', 'PROGRAM_ADMIN'] export default function MessagesPage() { const [recipientType, setRecipientType] = useState('ALL') const [selectedRole, setSelectedRole] = useState('') const [stageId, setStageId] = useState('') const [selectedProgramId, setSelectedProgramId] = useState('') const [selectedUserId, setSelectedUserId] = useState('') const [subject, setSubject] = useState('') const [body, setBody] = useState('') const [selectedTemplateId, setSelectedTemplateId] = useState('') const [deliveryChannels, setDeliveryChannels] = useState(['EMAIL', 'IN_APP']) const [isScheduled, setIsScheduled] = useState(false) const [scheduledAt, setScheduledAt] = useState('') const [showPreview, setShowPreview] = useState(false) const utils = trpc.useUtils() // Fetch supporting data // Get programs with stages const { data: programs } = trpc.program.list.useQuery({ includeStages: true }) const rounds = programs?.flatMap((p) => ((p.stages ?? []) as Array<{ id: string; name: string }>).map((s: { id: string; name: string }) => ({ ...s, program: { name: p.name } })) ) || [] const { data: templates } = trpc.message.listTemplates.useQuery() const { data: users } = trpc.user.list.useQuery( { page: 1, perPage: 100 }, { enabled: recipientType === 'USER' } ) // Fetch sent messages for history const { data: sentMessages, isLoading: loadingSent } = trpc.message.inbox.useQuery( { page: 1, pageSize: 50 } ) const sendMutation = trpc.message.send.useMutation({ onSuccess: (data) => { const count = (data as Record)?.recipientCount || '' toast.success(`Message sent successfully${count ? ` to ${count} recipients` : ''}`) resetForm() utils.message.inbox.invalidate() }, onError: (e) => toast.error(e.message), }) const resetForm = () => { setSubject('') setBody('') setSelectedTemplateId('') setSelectedRole('') setStageId('') setSelectedProgramId('') setSelectedUserId('') setIsScheduled(false) setScheduledAt('') } const handleTemplateSelect = (templateId: string) => { setSelectedTemplateId(templateId) if (templateId && templateId !== '__none__' && templates) { const template = (templates as Array>).find( (t) => String(t.id) === templateId ) if (template) { setSubject(String(template.subject || '')) setBody(String(template.body || '')) } } } const toggleChannel = (channel: string) => { setDeliveryChannels((prev) => prev.includes(channel) ? prev.filter((c) => c !== channel) : [...prev, channel] ) } const buildRecipientFilter = (): unknown => { switch (recipientType) { case 'ROLE': return selectedRole ? { role: selectedRole } : undefined case 'USER': return selectedUserId ? { userId: selectedUserId } : undefined case 'PROGRAM_TEAM': return selectedProgramId ? { programId: selectedProgramId } : undefined default: return undefined } } const getRecipientDescription = (): string => { switch (recipientType) { case 'ALL': return 'All platform users' case 'ROLE': { const roleLabel = selectedRole ? selectedRole.replace(/_/g, ' ') : '' return roleLabel ? `All ${roleLabel}s` : 'By Role (none selected)' } case 'STAGE_JURY': { if (!stageId) return 'Stage Jury (none selected)' const stage = rounds?.find( (r) => r.id === stageId ) return stage ? `Jury of ${stage.program ? `${stage.program.name} - ` : ''}${stage.name}` : 'Stage Jury' } case 'PROGRAM_TEAM': { if (!selectedProgramId) return 'Program Team (none selected)' const program = (programs as Array<{ id: string; name: string }> | undefined)?.find( (p) => p.id === selectedProgramId ) return program ? `Team of ${program.name}` : 'Program Team' } case 'USER': { if (!selectedUserId) return 'Specific User (none selected)' const userList = (users as { users: Array<{ id: string; name: string | null; email: string }> } | undefined)?.users const user = userList?.find((u) => u.id === selectedUserId) return user ? (user.name || user.email) : 'Specific User' } default: return 'Unknown' } } const handlePreview = () => { if (!subject.trim()) { toast.error('Subject is required') return } if (!body.trim()) { toast.error('Message body is required') return } if (deliveryChannels.length === 0) { toast.error('Select at least one delivery channel') return } if (recipientType === 'ROLE' && !selectedRole) { toast.error('Please select a role') return } if (recipientType === 'STAGE_JURY' && !stageId) { toast.error('Please select a stage') return } if (recipientType === 'PROGRAM_TEAM' && !selectedProgramId) { toast.error('Please select a program') return } if (recipientType === 'USER' && !selectedUserId) { toast.error('Please select a user') return } setShowPreview(true) } const handleActualSend = () => { sendMutation.mutate({ recipientType, recipientFilter: buildRecipientFilter(), stageId: stageId || undefined, subject: subject.trim(), body: body.trim(), deliveryChannels, scheduledAt: isScheduled && scheduledAt ? new Date(scheduledAt).toISOString() : undefined, templateId: selectedTemplateId && selectedTemplateId !== '__none__' ? selectedTemplateId : undefined, }) setShowPreview(false) } return (
{/* Header */}

Communication Hub

Send messages and notifications to platform users

Compose Sent History {/* Compose Form */} Compose Message Send a message via email, in-app notifications, or both {/* Recipient type */}
{/* Conditional sub-filters */} {recipientType === 'ROLE' && (
)} {recipientType === 'STAGE_JURY' && (
)} {recipientType === 'PROGRAM_TEAM' && (
)} {recipientType === 'USER' && (
)} {recipientType === 'ALL' && (

This message will be sent to all platform users.

)} {/* Template selector */} {templates && (templates as unknown[]).length > 0 && (
)} {/* Subject */}
setSubject(e.target.value)} />
{/* Body */}
Variables: {'{{projectName}}'}, {'{{userName}}'}, {'{{deadline}}'}, {'{{roundName}}'}, {'{{programName}}'}