import React, { useEffect, useMemo, useRef, useState } from 'react'; import FocusTrap from 'focus-trap-react'; import { Box, Button, color, config, Dialog, Header, Icon, IconButton, Icons, Line, Overlay, OverlayBackdrop, OverlayCenter, Text, } from 'folds'; import { stopPropagation } from '../../../utils/keyboard'; import { useReminders } from '../../../hooks/useReminders'; import { useModalStyle } from '../../../hooks/useModalStyle'; import { toLocalDate, toLocalTime, parseLocalDateTime, pickerInputStyle, formatFriendlyDateTime, } from '../../../utils/datetimeInput'; type RemindMeDialogProps = { roomId: string; eventId: string; previewText: string; onClose: () => void; }; function getPresets(): Array<{ label: string; ms: number }> { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(9, 0, 0, 0); const timeLabel = tomorrow.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); return [ { label: 'In 20 minutes', ms: 20 * 60_000 }, { label: 'In 1 hour', ms: 60 * 60_000 }, { label: 'In 3 hours', ms: 3 * 60 * 60_000 }, { label: `Tomorrow at ${timeLabel}`, ms: tomorrow.getTime() - Date.now() }, ]; } // Default custom pick: 1 hour from now, rounded up to the nearest 5 minutes. function defaultCustomDate(): Date { const d = new Date(Date.now() + 60 * 60 * 1000); d.setSeconds(0, 0); d.setMinutes(Math.ceil(d.getMinutes() / 5) * 5); return d; } export function RemindMeDialog({ roomId, eventId, previewText, onClose }: RemindMeDialogProps) { const modalStyle = useModalStyle(320); const { addReminder, removeReminder, reminders } = useReminders(); const presets = useMemo(() => getPresets(), []); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [customOpen, setCustomOpen] = useState(false); const def = useMemo(() => defaultCustomDate(), []); const [dateValue, setDateValue] = useState(() => toLocalDate(def)); const [timeValue, setTimeValue] = useState(() => toLocalTime(def)); const dateInputRef = useRef(null); // Reminders already set on this message (soonest first) — so the user can see // and cancel them instead of silently stacking duplicates. const existing = useMemo( () => reminders.filter((r) => r.eventId === eventId).sort((a, b) => a.timestamp - b.timestamp), [reminders, eventId], ); // Move focus into the revealed date input for keyboard/SR users. useEffect(() => { if (customOpen) dateInputRef.current?.focus(); }, [customOpen]); const handleCancelExisting = (timestamp: number) => { // Optimistic, matching removeBookmark: the shared account-data store drops // the reminder locally at once (no rollback) and re-syncs from the server. // We deliberately show no inline error — the store has no rollback path, so a // failed write simply reappears on the next sync rather than leaving a stale // "couldn't cancel" message beside an already-vanished row. removeReminder(eventId, timestamp).catch(() => undefined); }; const commit = async (timestamp: number) => { if (busy) return; setBusy(true); setError(null); try { await addReminder({ roomId, eventId, timestamp, message: previewText || 'Reminder', }); onClose(); } catch { setBusy(false); setError('Could not set reminder. Try again.'); } }; const handlePick = (ms: number) => commit(Date.now() + ms); const customDate = parseLocalDateTime(dateValue, timeValue); const customValid = !!customDate && customDate.getTime() - Date.now() >= 60_000; const handleCustom = () => { if (!customDate) { setError('Please select a valid date and time.'); return; } if (customDate.getTime() - Date.now() < 60_000) { setError('Reminder time must be at least 1 minute in the future.'); return; } commit(customDate.getTime()); }; return ( }>
Remind Me
{previewText && ( <> {previewText} )} {existing.length > 0 && ( <> {existing.length === 1 ? 'Reminder set' : 'Reminders set'} {existing.map((r, idx) => ( // Composite key: two custom reminders on one message can share // a minute-precision timestamp; index keeps React keys unique. {formatFriendlyDateTime(r.timestamp)} handleCancelExisting(r.timestamp)} aria-label={`Cancel reminder for ${formatFriendlyDateTime(r.timestamp)}`} > ))} )} {presets.map((p) => ( ))} {customOpen ? ( Date { setDateValue(e.target.value); setError(null); }} style={pickerInputStyle(color, config)} /> Time { setTimeValue(e.target.value); setError(null); }} style={pickerInputStyle(color, config)} /> {!customValid && (dateValue || timeValue) && ( Must be at least 1 minute in the future )} ) : ( )} {error && ( {error} )}
); }