midterm / podcraft /src /pages /Podcasts.tsx
Nagesh Muralidhar
midterm-submission
b4dc0bf
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import '../App.css';
// Use relative URLs in production, full URLs in development
const isDevelopment = window.location.hostname === 'localhost';
const API_URL = isDevelopment ? 'http://localhost:8000' : '';
interface Podcast {
id: number;
title: string;
description: string;
audio_file: string;
filename: string;
category: string;
}
const Podcasts: React.FC = () => {
const navigate = useNavigate();
const [podcasts, setPodcasts] = useState<Podcast[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
fetchPodcasts();
}, []);
const handleDelete = async (podcast: Podcast) => {
try {
const response = await fetch(`${API_URL}/api/audio/${podcast.filename}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(
`Failed to delete podcast (${response.status}): ${errorData}`
);
}
// Refresh the podcast list after successful deletion
await fetchPodcasts();
} catch (err) {
console.error('Delete error:', err);
setError(err instanceof Error ? err.message : 'Failed to delete podcast');
setTimeout(() => setError(""), 5000);
}
};
const fetchPodcasts = async () => {
try {
const response = await fetch(`${API_URL}/api/audio-list`);
if (!response.ok) {
throw new Error('Failed to fetch podcasts');
}
const files = await response.json();
if (!Array.isArray(files) || files.length === 0) {
setPodcasts([]);
setLoading(false);
return;
}
const podcastList: Podcast[] = files.map((file: any, index: number) => {
const filename = file.filename;
const parts = filename.split('-');
let queryPart = '', descriptionPart = '', categoryWithExt = '';
// Handle filenames that might not have all parts
if (parts.length >= 3) {
[queryPart, descriptionPart, categoryWithExt] = parts;
} else {
queryPart = parts[0] || '';
descriptionPart = parts[1] || queryPart;
categoryWithExt = parts[2] || 'general.mp3';
}
const category = categoryWithExt.replace('.mp3', '');
return {
id: index + 1,
title: `${descriptionPart.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase())}`,
description: `A debate exploring ${queryPart.replace(/_/g, ' ')}`,
audio_file: `${API_URL}${file.path}`, // Add API_URL for audio files
filename: filename,
category: category.replace(/_/g, ' ')
};
});
setPodcasts(podcastList);
} catch (err) {
console.error('Fetch error:', err);
setError(err instanceof Error ? err.message : 'An error occurred');
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="podcasts-container">
<div className="loading-message">Loading podcasts...</div>
</div>
);
}
return (
<div className="podcasts-container">
<header className="podcasts-header">
<h1>Your Generated Podcasts</h1>
<p>Listen to AI-generated debate podcasts on various topics</p>
</header>
{error && (
<div className="error-message">Error: {error}</div>
)}
<div className="podcasts-grid">
{podcasts.length > 0 ? (
podcasts.map(podcast => (
<div
key={podcast.id}
className="podcast-card"
onClick={() => navigate(`/podcast/${podcast.id}`)}
style={{ cursor: 'pointer' }}
>
<div className="podcast-content">
<div className="podcast-header">
<h2 className="podcast-title">{podcast.title}</h2>
<button
className="delete-button"
onClick={(e) => {
e.stopPropagation();
handleDelete(podcast);
}}
aria-label="Delete podcast"
>
×
</button>
</div>
<div className="category-pill">{podcast.category}</div>
<p className="description">{podcast.description}</p>
<div className="audio-player" onClick={e => e.stopPropagation()}>
<audio
controls
src={podcast.audio_file}
>
Your browser does not support the audio element.
</audio>
</div>
</div>
</div>
))
) : (
<div className="no-podcasts-message">
No podcasts found. Generate your first podcast from the home page!
</div>
)}
</div>
</div>
);
};
export default Podcasts;