project-standalo-note-to-app/app/recordings/[id]/page.tsx

153 lines
4.7 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useRouter, useParams } from 'next/navigation';
import Header from '../../components/Header';
import Sidebar from '../../components/Sidebar';
import AudioPlayer from '../../components/AudioPlayer';
import TranscriptViewer from '../../components/TranscriptViewer';
import SummaryDisplay from '../../components/SummaryDisplay';
import type { User, Recording } from '@/types';
export default function RecordingDetailPage() {
const [user, setUser] = useState<User | null>(null);
const [recording, setRecording] = useState<Recording | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [isTranscribing, setIsTranscribing] = useState(false);
const router = useRouter();
const params = useParams();
const id = params?.id as string;
useEffect(() => {
// Fetch current user
fetch('/api/auth/me')
.then(res => {
if (!res.ok) {
router.push('/login');
return null;
}
return res.json();
})
.then(data => {
if (data) setUser(data);
})
.catch(() => router.push('/login'));
// Fetch recording
if (id) {
fetch(`/api/recordings/${id}`)
.then(res => {
if (!res.ok) throw new Error('Recording not found');
return res.json();
})
.then(data => {
setRecording(data);
setIsTranscribing(data.isTranscribing);
})
.catch(() => router.push('/recordings'));
}
}, [id, router]);
const handleTranscribe = async () => {
if (!id) return;
try {
const response = await fetch(`/api/recordings/${id}/transcribe`, {
method: 'POST',
});
if (response.ok) {
setIsTranscribing(true);
}
} catch (error) {
console.error('Failed to start transcription:', error);
}
};
const handleSummarize = async () => {
if (!id) return;
try {
const response = await fetch(`/api/recordings/${id}/summarize`, {
method: 'POST',
});
if (response.ok) {
const data = await response.json();
setRecording(prev => prev ? { ...prev, summary: data.summary } : null);
}
} catch (error) {
console.error('Failed to generate summary:', error);
}
};
if (!recording) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" />
</div>
);
}
return (
<div className="flex min-h-screen bg-gray-50">
<Sidebar activePath="/recordings" />
<div className="flex-1">
<Header user={user} />
<main className="p-6">
<div className="max-w-4xl mx-auto">
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-900 mb-2">
{recording.title}
</h1>
<p className="text-gray-600">
{recording.createdAt && new Date(recording.createdAt).toLocaleString()}
</p>
</div>
<div className="space-y-6">
<AudioPlayer
audioUrl={recording.audioFilePath}
duration={recording.duration}
onPlayPause={(playing) => setIsPlaying(playing)}
onSeek={(time) => setCurrentTime(time)}
/>
<div className="flex gap-4">
<button
onClick={handleTranscribe}
disabled={isTranscribing || !!recording.transcript}
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isTranscribing ? 'Transcribing...' : recording.transcript ? 'Transcribed' : 'Transcribe'}
</button>
<button
onClick={handleSummarize}
disabled={!recording.transcript || !!recording.summary}
className="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed"
>
{recording.summary ? 'Summary Generated' : 'Generate Summary'}
</button>
</div>
{recording.transcript && (
<TranscriptViewer
transcript={recording.transcript}
isLive={isTranscribing}
/>
)}
{recording.summary && (
<SummaryDisplay
summary={recording.summary}
/>
)}
</div>
</div>
</main>
</div>
</div>
);
}