'use client' import { useState } from 'react' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '../ui/dialog' import { Button } from '../ui/button' import { Label } from '../ui/label' import { Calendar } from '../ui/calendar' import { Popover, PopoverContent, PopoverTrigger, } from '../ui/popover' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../ui/select' import { Loader2, CalendarIcon, Clock } from 'lucide-react' import { useToast } from '../toast' import { format, addDays, setHours, setMinutes, isBefore, startOfDay } from 'date-fns' import { cn } from '../../utils/cn' interface SummaryItem { label: string value: string } interface ScheduleDialogProps { open: boolean onOpenChange: (open: boolean) => void /** Summary items displayed at the top of the dialog (e.g. subject, recipient count) */ summaryItems: SummaryItem[] /** Called to schedule the message. Receives the ISO date string for the scheduled time. */ onSchedule: (scheduledAt: string) => Promise /** Called to save draft before scheduling when there are unsaved changes */ onSaveDraft?: () => Promise /** Called to prepare recipients (e.g. expand contact tags) before scheduling */ onPrepareRecipients?: () => Promise /** Whether there are unsaved changes that need saving before scheduling */ hasUnsavedChanges?: boolean /** Dialog title. Defaults to "Schedule Send" */ title?: string /** Dialog description. Defaults to "Choose when to send this email" */ description?: string /** Info message shown after confirming schedule time */ scheduledInfoMessage?: string } // Generate time options in 30-minute increments const generateTimeOptions = () => { const options: { value: string; label: string }[] = [] for (let hour = 0; hour < 24; hour++) { for (const minute of [0, 30]) { const h = hour.toString().padStart(2, '0') const m = minute.toString().padStart(2, '0') const period = hour < 12 ? 'AM' : 'PM' const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour options.push({ value: `${h}:${m}`, label: `${displayHour}:${m.padStart(2, '0')} ${period}`, }) } } return options } const TIME_OPTIONS = generateTimeOptions() // Quick schedule options const QUICK_OPTIONS = [ { label: 'Tomorrow 9 AM', getValue: () => setHours(setMinutes(addDays(new Date(), 1), 0), 9) }, { label: 'Tomorrow 2 PM', getValue: () => setHours(setMinutes(addDays(new Date(), 1), 0), 14) }, { label: 'Monday 9 AM', getValue: () => { const today = new Date() const daysUntilMonday = (8 - today.getDay()) % 7 || 7 return setHours(setMinutes(addDays(today, daysUntilMonday), 0), 9) }}, { label: 'Next week', getValue: () => setHours(setMinutes(addDays(new Date(), 7), 0), 9) }, ] export function ScheduleDialog({ open, onOpenChange, summaryItems, onSchedule, onSaveDraft, onPrepareRecipients, hasUnsavedChanges = false, title = 'Schedule Send', description = 'Choose when to send this email', scheduledInfoMessage = 'You can cancel or edit this scheduled send from the messages list.', }: ScheduleDialogProps) { const { toast } = useToast() const [scheduling, setScheduling] = useState(false) const [selectedDate, setSelectedDate] = useState(addDays(new Date(), 1)) const [selectedTime, setSelectedTime] = useState('09:00') const getScheduledDateTime = (): Date | null => { if (!selectedDate) return null const [hours, minutes] = selectedTime.split(':').map(Number) return setMinutes(setHours(selectedDate, hours), minutes) } const isValidScheduleTime = (): boolean => { const scheduledTime = getScheduledDateTime() if (!scheduledTime) return false // Must be at least 5 minutes in the future return scheduledTime.getTime() > Date.now() + 5 * 60 * 1000 } const handleQuickSelect = (getValue: () => Date) => { const date = getValue() setSelectedDate(startOfDay(date)) const hours = date.getHours().toString().padStart(2, '0') const minutes = date.getMinutes().toString().padStart(2, '0') setSelectedTime(`${hours}:${minutes}`) } const handleSchedule = async () => { const scheduledDateTime = getScheduledDateTime() if (!scheduledDateTime) { toast({ description: 'Please select a date and time', variant: 'destructive' }) return } if (!isValidScheduleTime()) { toast({ description: 'Scheduled time must be at least 5 minutes in the future', variant: 'destructive' }) return } setScheduling(true) try { // Save any pending changes first if (hasUnsavedChanges && onSaveDraft) { await onSaveDraft() } // Prepare recipients if needed if (onPrepareRecipients) { await onPrepareRecipients() } // Delegate scheduling to the caller await onSchedule(scheduledDateTime.toISOString()) toast({ description: `Scheduled for ${format(scheduledDateTime, 'PPP')} at ${format(scheduledDateTime, 'p')}`, }) onOpenChange(false) } catch (error) { console.error('Error scheduling:', error) toast({ description: error instanceof Error ? error.message : 'Failed to schedule', variant: 'destructive', }) } finally { setScheduling(false) } } return ( {title} {description}
{/* Summary */}
{summaryItems.map((item) => (
{item.label}: {item.value}
))}
{/* Quick options */}
{QUICK_OPTIONS.map((option) => ( ))}
{/* Date/Time picker */}
{/* Date picker */}
isBefore(date, startOfDay(new Date()))} initialFocus />
{/* Time picker */}
{/* Preview scheduled time */} {selectedDate && isValidScheduleTime() && (

Scheduled for:{' '} {format(getScheduledDateTime()!, 'PPPP')} at {format(getScheduledDateTime()!, 'p')}

{scheduledInfoMessage && (

{scheduledInfoMessage}

)}
)}
) }