'use client'
import { useEffect, useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { X, AlarmClock, Trash2 } from 'lucide-react'
interface Timer {
id: string
label: string
startedAt: string
endsAt: string
}
type Mode =
| { type: 'control'; timer: Timer }
| { type: 'create' }
export interface TimerModalProps {
mode: Mode | null
onClose: () => void
}
function formatBig(ms: number): string {
if (ms <= 0) return '0:00'
const total = Math.ceil(ms / 1000)
const h = Math.floor(total / 3600)
const m = Math.floor((total % 3600) / 60)
const s = total % 60
if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
return `${m}:${s.toString().padStart(2, '0')}`
}
async function postTimer(body: any) {
return fetch('/api/voice/timer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
credentials: 'include',
})
}
const ADJUST_OPTIONS = [
{ label: '−5 мин', delta: -300 },
{ label: '−1 мин', delta: -60 },
{ label: '−10 сек', delta: -10 },
{ label: '+10 сек', delta: 10 },
{ label: '+1 мин', delta: 60 },
{ label: '+5 мин', delta: 300 },
]
const PRESETS = [
{ label: '1 мин', seconds: 60 },
{ label: '3 мин', seconds: 180 },
{ label: '5 мин', seconds: 300 },
{ label: '10 мин', seconds: 600 },
{ label: '15 мин', seconds: 900 },
{ label: '30 мин', seconds: 1800 },
{ label: '1 час', seconds: 3600 },
]
const DEFAULT_LABELS = ['Таймер', 'Чайник', 'Паста', 'Яйца', 'Разогрев', 'Стирка', 'Напомни']
export default function TimerModal({ mode, onClose }: TimerModalProps) {
return (
{mode && (
e.stopPropagation()}
style={{
width: '100%', maxWidth: 520,
background: 'var(--surface-1)',
border: '1px solid var(--border-subtle)',
borderRadius: 28,
boxShadow: 'var(--shadow-xl)',
padding: 28,
display: 'flex', flexDirection: 'column', gap: 22,
position: 'relative',
}}
>
{mode.type === 'control' ? (
) : (
)}
)}
)
}
function ControlView({ timer, onClose }: { timer: Timer; onClose: () => void }) {
const [tick, setTick] = useState(0)
useEffect(() => {
const t = setInterval(() => setTick(x => x + 1), 500)
return () => clearInterval(t)
}, [])
// tick is just to force re-render
const now = Date.now()
const end = new Date(timer.endsAt).getTime()
const start = new Date(timer.startedAt).getTime()
const remain = Math.max(0, end - now)
const total = Math.max(1, end - start)
const progress = Math.max(0, Math.min(1, 1 - remain / total))
const expired = remain <= 0
const imminent = !expired && remain < 10_000
const accent = expired ? '#f87171' : imminent ? '#fb923c' : '#818cf8'
const handleAdjust = (delta: number) => {
postTimer({ action: 'adjust', id: timer.id, delta_seconds: delta })
}
const handleCancel = async () => {
await postTimer({ action: 'cancel', id: timer.id })
onClose()
}
return (
<>
{/* Header */}
{expired ? 'Прозвенел' : 'Таймер'}
{timer.label}
{/* Big countdown */}
{formatBig(remain)}
{/* Progress track */}
{/* Adjust grid 3x2 */}
{!expired && (
{ADJUST_OPTIONS.map((opt) => {
const negative = opt.delta < 0
return (
)
})}
)}
{/* Cancel — destructive */}
>
)
}
function CreateView({ onClose }: { onClose: () => void }) {
const [seconds, setSeconds] = useState(300) // 5 min default
const [label, setLabel] = useState('Таймер')
const [submitting, setSubmitting] = useState(false)
const submit = async () => {
if (submitting) return
setSubmitting(true)
try {
await postTimer({
action: 'start',
seconds,
label: (label.trim() || 'Таймер').slice(0, 80),
agent: 'cosmo',
})
onClose()
} finally {
setSubmitting(false)
}
}
return (
<>
Новый таймер
Выбери длительность
{/* Preset grid */}
{PRESETS.map(p => {
const active = seconds === p.seconds
return (
)
})}
{/* Label selector */}
Название
setLabel(e.target.value)}
placeholder="Таймер"
maxLength={80}
style={{
width: '100%', padding: '14px 16px', borderRadius: 14,
background: 'var(--surface-2)',
border: '1px solid var(--border-subtle)',
color: 'var(--text-primary)',
fontSize: 15, fontFamily: 'inherit', outline: 'none',
boxSizing: 'border-box' as any,
}}
/>
{DEFAULT_LABELS.map(l => (
))}
{/* Start button */}
>
)
}