'use client'; import { useState } from 'react'; import { useTimerStore } from '@/lib/store'; import { URGENCY_ORDER, getUrgencyConfig } from '@/lib/urgency'; import type { UrgencyLevel } from '@/lib/urgency'; import { CASCADE_PRESET_LABELS } from '@/lib/cascade'; import type { CascadePreset } from '@/lib/cascade'; import { X, AlarmClock, Timer, Coffee, Sparkles, CalendarDays } from 'lucide-react'; import { useMaintenanceMode } from '@/app/providers'; import { BUILT_IN_CATEGORIES, getCategoryById } from '@/lib/categories'; import { parseNaturalLanguage } from '@/lib/nl-parser'; import type { ParseResult } from '@/lib/nl-parser'; import { alarmSchema, countdownSchema, pomodoroSchema, eventSchema } from '@/lib/schemas'; type TabType = 'alarm' | 'countdown' | 'pomodoro' | 'event'; interface CreateTimerModalProps { isOpen: boolean; onClose: () => void; } export function CreateTimerModal({ isOpen, onClose }: CreateTimerModalProps) { const { addAlarm, addCountdown, addPomodoro, addEvent } = useTimerStore(); const maintenanceMode = useMaintenanceMode(); const [tab, setTab] = useState('countdown'); const [nlInput, setNlInput] = useState(''); const [nlResult, setNlResult] = useState(null); const [label, setLabel] = useState(''); const [urgency, setUrgency] = useState('standard'); const [cascadePreset, setCascadePreset] = useState('standard'); const [category, setCategory] = useState(''); // Alarm fields const [alarmTime, setAlarmTime] = useState(''); // Countdown fields const [hours, setHours] = useState(0); const [minutes, setMinutes] = useState(25); const [seconds, setSeconds] = useState(0); // Pomodoro fields const [workMin, setWorkMin] = useState(25); const [breakMin, setBreakMin] = useState(5); const [longBreakMin, setLongBreakMin] = useState(15); const [rounds, setRounds] = useState(4); // Event fields const [eventDate, setEventDate] = useState(''); // Custom pre-warning message const [customMessage, setCustomMessage] = useState(''); // Validation errors const [errors, setErrors] = useState>({}); if (!isOpen) return null; const handleNlChange = (value: string) => { setNlInput(value); if (value.trim()) { setNlResult(parseNaturalLanguage(value)); } else { setNlResult(null); } }; const handleNlCreate = () => { if (!nlResult?.success || !nlResult.timer) return; const t = nlResult.timer; if (t.type === 'countdown' && t.durationMs) { addCountdown({ label: t.label, durationMs: t.durationMs, urgency: t.urgency, cascade: { preset: t.cascade, intervals: [] }, }); } else if (t.type === 'alarm' && t.targetTime) { addAlarm({ label: t.label, targetTime: t.targetTime, urgency: t.urgency, cascade: { preset: t.cascade, intervals: [] }, }); } else if (t.type === 'pomodoro') { addPomodoro({ label: t.label, config: { workMinutes: 25, breakMinutes: 5, longBreakMinutes: 15, rounds: t.pomodoroRounds ?? 4 }, urgency: t.urgency, }); } setNlInput(''); setNlResult(null); setLabel(''); onClose(); }; // When category changes, update urgency + cascade defaults const handleCategoryChange = (catId: string) => { setCategory(catId); if (catId) { const cat = getCategoryById(catId); if (cat) { setUrgency(cat.defaultUrgency); setCascadePreset(cat.defaultCascade); } } }; const handleCreate = () => { setErrors({}); const cascade = { preset: cascadePreset, intervals: [] as number[] }; const catOrUndef = category || undefined; if (tab === 'alarm') { const result = alarmSchema.safeParse({ label: label || 'Alarm', alarmTime, urgency, cascadePreset }); if (!result.success) { const fieldErrors: Record = {}; for (const issue of result.error.issues) { fieldErrors[issue.path[0] as string] = issue.message; } setErrors(fieldErrors); return; } const [h, m] = alarmTime.split(':').map(Number); const target = new Date(); target.setHours(h, m, 0, 0); if (target.getTime() <= Date.now()) { target.setDate(target.getDate() + 1); } addAlarm({ label: result.data.label, targetTime: target.getTime(), urgency, cascade, category: catOrUndef, customMessage: customMessage || undefined, }); } else if (tab === 'countdown') { const result = countdownSchema.safeParse({ label: label || 'Countdown', hours, minutes, seconds, urgency, cascadePreset }); if (!result.success) { const fieldErrors: Record = {}; for (const issue of result.error.issues) { fieldErrors[issue.path[0] as string] = issue.message; } setErrors(fieldErrors); return; } const durationMs = (hours * 3600 + minutes * 60 + seconds) * 1000; addCountdown({ label: result.data.label, durationMs, urgency, cascade, category: catOrUndef, customMessage: customMessage || undefined, }); } else if (tab === 'pomodoro') { const result = pomodoroSchema.safeParse({ label: label || 'Focus Session', workMinutes: workMin, breakMinutes: breakMin, longBreakMinutes: longBreakMin, rounds, urgency }); if (!result.success) { const fieldErrors: Record = {}; for (const issue of result.error.issues) { fieldErrors[issue.path[0] as string] = issue.message; } setErrors(fieldErrors); return; } addPomodoro({ label: result.data.label, config: { workMinutes: workMin, breakMinutes: breakMin, longBreakMinutes: longBreakMin, rounds, }, urgency, }); } else if (tab === 'event') { const result = eventSchema.safeParse({ label: label || 'Event Countdown', eventDate, urgency, cascadePreset }); if (!result.success) { const fieldErrors: Record = {}; for (const issue of result.error.issues) { fieldErrors[issue.path[0] as string] = issue.message; } setErrors(fieldErrors); return; } const target = new Date(eventDate).getTime(); addEvent({ label: result.data.label, targetTime: target, urgency, category: catOrUndef, customMessage: customMessage || undefined, }); } // Reset form setLabel(''); setAlarmTime(''); setHours(0); setMinutes(25); setSeconds(0); setCategory(''); setEventDate(''); setCustomMessage(''); setErrors({}); onClose(); }; const tabs: { key: TabType; label: string; icon: React.ReactNode }[] = [ { key: 'countdown', label: 'Countdown', icon: }, { key: 'alarm', label: 'Alarm', icon: }, { key: 'pomodoro', label: 'Pomodoro', icon: }, { key: 'event', label: 'Event', icon: }, ]; return (
{/* Backdrop */}
{/* Modal */}
{/* Header */}

New Timer

{/* Natural Language Input */}
handleNlChange(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && nlResult?.success) handleNlCreate(); }} placeholder='e.g. "meeting in 30 min" or "alarm at 3pm"' className="flex-1 px-3 py-2 rounded-lg border text-sm focus:outline-none focus:ring-2" style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: nlResult?.success ? 'var(--cm-accent-secondary)' : 'var(--cm-border)', color: 'var(--cm-text-primary)', }} /> {nlResult?.success && ( )}
{nlResult && nlInput.trim() && (
{nlResult.success && nlResult.timer ? ( {nlResult.timer.type === 'pomodoro' ? 'Pomodoro' : nlResult.timer.type === 'alarm' ? 'Alarm' : 'Countdown'} {' — '}{nlResult.timer.label} {nlResult.timer.durationMs ? ` (${Math.round(nlResult.timer.durationMs / 60000)}m)` : ''} {nlResult.timer.urgency !== 'standard' ? ` [${nlResult.timer.urgency}]` : ''} ) : ( {nlResult.error} )}
)}
{/* Tabs */}
{tabs.map((t) => ( ))}
{/* Form */}
{/* Label */}
setLabel(e.target.value)} placeholder={tab === 'pomodoro' ? 'Focus Session' : tab === 'alarm' ? 'Wake up' : 'Timer'} className="w-full px-3 py-2 rounded-lg border text-sm focus:outline-none focus:ring-2" style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: errors.label ? 'var(--cm-danger)' : 'var(--cm-border)', color: 'var(--cm-text-primary)', }} /> {errors.label &&

{errors.label}

}
{/* Tab-specific fields */} {tab === 'alarm' && (
setAlarmTime(e.target.value)} className="w-full px-3 py-2 rounded-lg border text-sm focus:outline-none focus:ring-2" style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: errors.alarmTime ? 'var(--cm-danger)' : 'var(--cm-border)', color: 'var(--cm-text-primary)', }} /> {errors.alarmTime &&

{errors.alarmTime}

}
)} {tab === 'countdown' && (
{[ { label: 'H', value: hours, setter: setHours, max: 23 }, { label: 'M', value: minutes, setter: setMinutes, max: 59 }, { label: 'S', value: seconds, setter: setSeconds, max: 59 }, ].map((field) => (
field.setter(Math.min(field.max, Math.max(0, parseInt(e.target.value) || 0)))} className="w-full px-3 py-2 rounded-lg border text-sm text-center focus:outline-none focus:ring-2" style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: 'var(--cm-border)', color: 'var(--cm-text-primary)', }} /> {field.label}
))}
{errors.minutes &&

{errors.minutes}

} {/* Quick presets */}
{[ { label: '5m', h: 0, m: 5, s: 0 }, { label: '15m', h: 0, m: 15, s: 0 }, { label: '25m', h: 0, m: 25, s: 0 }, { label: '45m', h: 0, m: 45, s: 0 }, { label: '1h', h: 1, m: 0, s: 0 }, ].map((preset) => ( ))}
)} {tab === 'pomodoro' && (
{[ { label: 'Work (min)', value: workMin, setter: setWorkMin }, { label: 'Break (min)', value: breakMin, setter: setBreakMin }, { label: 'Long Break (min)', value: longBreakMin, setter: setLongBreakMin }, { label: 'Rounds', value: rounds, setter: setRounds }, ].map((field) => (
field.setter(Math.max(1, parseInt(e.target.value) || 1))} className="w-full px-3 py-2 rounded-lg border text-sm text-center focus:outline-none focus:ring-2" style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: 'var(--cm-border)', color: 'var(--cm-text-primary)', }} />
))}
)} {tab === 'event' && (
setEventDate(e.target.value)} min={new Date().toISOString().split('T')[0]} className="w-full px-3 py-2 rounded-lg border text-sm focus:outline-none focus:ring-2" style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: 'var(--cm-border)', color: 'var(--cm-text-primary)', }} /> {errors.eventDate &&

{errors.eventDate}

} {eventDate && new Date(eventDate).getTime() > Date.now() && (

{Math.ceil((new Date(eventDate).getTime() - Date.now()) / 86_400_000)} days from now · Milestone warnings at 30, 7, 3, 1 days

)}
)} {/* Category */} {tab !== 'pomodoro' && (
{BUILT_IN_CATEGORIES.map((cat) => ( ))}
)} {/* Urgency (non-pomodoro) */} {tab !== 'pomodoro' && (
{URGENCY_ORDER.map((level) => { const config = getUrgencyConfig(level); return ( ); })}
)} {/* Custom pre-warning message (non-pomodoro) */} {tab !== 'pomodoro' && (
setCustomMessage(e.target.value)} placeholder='e.g. "Review your agenda" or "Bring your insurance card"' maxLength={200} className="w-full px-3 py-2 rounded-lg border text-sm focus:outline-none focus:ring-2" style={{ backgroundColor: 'var(--cm-surface-card)', borderColor: 'var(--cm-border)', color: 'var(--cm-text-primary)', }} />

Shown in pre-warning notifications instead of auto-generated tips

)} {/* Cascade preset (non-pomodoro) */} {tab !== 'pomodoro' && (
)} {/* Create button */}
); }