'use client'; import { useState, useRef, useEffect } from 'react'; const API_URL = process.env.NEXT_PUBLIC_API_URL || ''; export default function CapturePage() { const [state, setState] = useState<'idle' | 'recording' | 'transcribing' | 'saved' | 'error'>('idle'); const [countdown, setCountdown] = useState(0); const [recentCaptures, setRecentCaptures] = useState<{name: string, duration: string}[]>([]); const mediaRecorderRef = useRef(null); const chunksRef = useRef([]); const streamRef = useRef(null); const wakeLockRef = useRef(null); const countdownRef = useRef(null); const mimeTypeRef = useRef('audio/webm'); const MAX_SECONDS = 600; useEffect(() => { loadRecentCaptures(); }, []); async function loadRecentCaptures() { try { const res = await fetch(`${API_URL}/api/captures`); if (res.ok) { const data = await res.json(); setRecentCaptures(data.captures || []); } } catch {} } async function acquireWakeLock() { try { if ('wakeLock' in navigator) { wakeLockRef.current = await (navigator as any).wakeLock.request('screen'); } } catch {} } function releaseWakeLock() { wakeLockRef.current?.release(); wakeLockRef.current = null; } async function startRecording() { try { if (!streamRef.current || streamRef.current.getTracks().every(t => t.readyState === 'ended')) { streamRef.current = await navigator.mediaDevices.getUserMedia({ audio: true }); } const mimeType = MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm' : MediaRecorder.isTypeSupported('audio/mp4') ? 'audio/mp4' : 'audio/ogg'; mimeTypeRef.current = mimeType; chunksRef.current = []; const mr = new MediaRecorder(streamRef.current, { mimeType }); mr.ondataavailable = e => { if (e.data.size > 0) chunksRef.current.push(e.data); }; mr.onstop = async () => { if (chunksRef.current.length === 0) return; setState('transcribing'); try { const blob = new Blob(chunksRef.current, { type: mimeTypeRef.current }); const form = new FormData(); form.append('audio', blob, 'capture.webm'); const res = await fetch(`${API_URL}/api/capture`, { method: 'POST', body: form }); if (res.ok) { setState('saved'); await loadRecentCaptures(); setTimeout(() => setState('idle'), 3000); } else { setState('error'); setTimeout(() => setState('idle'), 3000); } } catch { setState('error'); setTimeout(() => setState('idle'), 3000); } }; mr.start(1000); mediaRecorderRef.current = mr; setState('recording'); setCountdown(MAX_SECONDS); await acquireWakeLock(); countdownRef.current = setInterval(() => { setCountdown(prev => { if (prev <= 1) { stopRecording(); return 0; } return prev - 1; }); }, 1000); } catch { setState('error'); setTimeout(() => setState('idle'), 3000); } } function stopRecording() { if (countdownRef.current) { clearInterval(countdownRef.current); countdownRef.current = null; } releaseWakeLock(); mediaRecorderRef.current?.stop(); } function handleTap() { if (state === 'recording') stopRecording(); else if (state === 'idle') startRecording(); } const isRecording = state === 'recording'; const isBusy = state === 'transcribing'; return (
{/* Header */}

Bird — field recorder

{/* Main button area */}
{/* Waveform — decorative */}
{[4,8,16,6,24,10,32,14,20,8,12,28,6,18,4,22,10,16,8,4].map((h, i) => (
))}
{/* Big button */} {/* Status */}
{state === 'idle' && (

tap to capture

)} {state === 'recording' && ( <>

recording

{countdown <= 15 && (

{countdown}s

)} )} {state === 'transcribing' && (

transcribing...

)} {state === 'saved' && (

saved ✓

)} {state === 'error' && (

error — try again

)}
{/* Recent captures */}

recent captures

{recentCaptures.length === 0 ? (

none yet

) : (
{recentCaptures.slice(0, 4).map((c, i) => (
{c.name} {c.duration}
))}
)}
); }