'use client'; import { useState, useEffect, useCallback } from 'react'; import { useTimerStore } from '@/lib/store'; import { useTickLoop } from '@/lib/use-tick'; import { useKeyboardShortcuts, SHORTCUT_MAP } from '@/lib/use-keyboard-shortcuts'; import { TimerCard } from './TimerCard'; import { PomodoroView } from './PomodoroView'; import { QuickTimerBar } from './QuickTimerBar'; import { CreateTimerModal } from './CreateTimerModal'; import { AlarmOverlay } from './AlarmOverlay'; import { requestNotificationPermission } from '@/lib/notifications'; import { formatTime, formatDate } from '@/lib/format'; import { Plus, Clock, Bell, Keyboard, Sun, Moon, Settings, Eye, BarChart3, ListChecks, Cloud, CloudOff, Loader2 } from 'lucide-react'; import { useSync } from '@/lib/use-sync'; import { BUILT_IN_CATEGORIES, matchesCategory } from '@/lib/categories'; import Link from 'next/link'; import { FeedbackButton } from './FeedbackButton'; import { InstallPrompt } from './InstallPrompt'; import { useTheme } from '@/lib/use-theme'; import { getSnoozeSuggestions } from '@/lib/adaptive-snooze'; import type { SnoozeSuggestion } from '@/lib/adaptive-snooze'; export function Dashboard() { const [isCreateOpen, setIsCreateOpen] = useState(false); const [showShortcuts, setShowShortcuts] = useState(false); const [mounted, setMounted] = useState(false); const [filterCategory, setFilterCategory] = useState(null); const [dismissedSuggestions, setDismissedSuggestions] = useState>(new Set()); const [snoozeSuggestions, setSnoozeSuggestions] = useState([]); const timers = useTimerStore((s) => s.timers); const now = useTimerStore((s) => s.now); const { pause, resume } = useTimerStore(); const { theme, toggle: toggleTheme } = useTheme(); // Cloud sync const { isSyncing, syncEnabled, pendingChanges, lastError } = useSync(); // Start the tick loop useTickLoop(); // Keyboard shortcuts const getFirstActiveTimer = useCallback(() => { return timers.find((t) => ['active', 'warning', 'paused'].includes(t.state)); }, [timers]); useKeyboardShortcuts({ onNewTimer: () => setIsCreateOpen(true), onQuickTimer: () => setIsCreateOpen(true), onTogglePause: () => { const t = getFirstActiveTimer(); if (!t) return; if (t.state === 'paused') resume(t.id); else if (t.type !== 'alarm') pause(t.id); }, onDismiss: () => { if (isCreateOpen) setIsCreateOpen(false); else if (showShortcuts) setShowShortcuts(false); }, onShowHelp: () => setShowShortcuts((p) => !p), }); // Hydration guard useEffect(() => { setMounted(true); requestNotificationPermission(); setSnoozeSuggestions(getSnoozeSuggestions()); }, []); if (!mounted) { return (
); } const activeTimers = timers .filter((t) => ['active', 'warning', 'snoozed', 'firing', 'paused'].includes(t.state)) .filter((t) => matchesCategory(t.category, filterCategory)); const completedTimers = timers .filter((t) => ['dismissed', 'completed'].includes(t.state)) .filter((t) => matchesCategory(t.category, filterCategory)) .slice(-10) .reverse(); // Tab title update with flash on fire const hasFiring = timers.some((t) => t.state === 'firing'); useEffect(() => { if (hasFiring) { // Flash between alarm text and timer label const firingTimer = timers.find((t) => t.state === 'firing'); const label = firingTimer?.label ?? 'Timer'; let flash = true; const interval = setInterval(() => { document.title = flash ? `๐Ÿ”” TIME! โ€” ${label}` : `โฐ ${label} | ChronoMind`; flash = !flash; }, 800); return () => clearInterval(interval); } const next = activeTimers .filter((t) => ['active', 'warning'].includes(t.state)) .sort((a, b) => a.targetTime - b.targetTime)[0]; if (next) { const remaining = Math.max(0, next.targetTime - now); const mins = Math.floor(remaining / 60000); const secs = Math.floor((remaining % 60000) / 1000); document.title = `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')} โ€” ${next.label} | ChronoMind`; } else { document.title = 'ChronoMind โ€” Smart Pre-Warning Timer'; } }, [now, activeTimers, hasFiring, timers]); return (
{/* Skip to content */} Skip to content {/* Alarm overlay for firing timers */} {/* Header */}

ChronoMind

{formatTime(now)} ยท {formatDate(now)} {syncEnabled && ( 0 ? `${pendingChanges} pending` : 'Synced'} > {isSyncing ? : lastError ? : } )}
{/* Keyboard shortcuts overlay */} {showShortcuts && (
setShowShortcuts(false)} />

Keyboard Shortcuts

{SHORTCUT_MAP.map((s) => (
{s.description} {s.key}
))}
)} {/* Main content */}
{/* Quick timer bar */}
{/* Category filter */}
{BUILT_IN_CATEGORIES.map((cat) => ( ))}
{/* Snooze suggestions */} {snoozeSuggestions.filter((s) => !dismissedSuggestions.has(s.labelPattern)).length > 0 && (
{snoozeSuggestions .filter((s) => !dismissedSuggestions.has(s.labelPattern)) .slice(0, 2) .map((suggestion) => (

Snooze pattern detected

{suggestion.message}

))}
)} {/* Active timers */} {activeTimers.length > 0 ? (

Active ({activeTimers.length})

{activeTimers .sort((a, b) => a.targetTime - b.targetTime) .map((timer) => timer.type === 'pomodoro' ? ( ) : ( ) )}
) : ( /* Empty state */

No active timers

Create your first timer and never be caught off-guard again.

)} {/* Completed timers */} {completedTimers.length > 0 && (

Recent ({completedTimers.length})

{completedTimers.map((timer) => ( ))}
)}
{/* Create Timer Modal */} setIsCreateOpen(false)} /> {/* Feedback button */} {/* Install prompt */}
{/* Footer */}
); }