108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
interface PodcastEpisode {
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
audio_url: string;
|
|
published_at: string;
|
|
duration: string;
|
|
image_url: string;
|
|
}
|
|
|
|
interface PodcastFeed {
|
|
title: string;
|
|
description: string;
|
|
image_url: string;
|
|
episodes: PodcastEpisode[];
|
|
}
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://216.158.230.94:8001/api/v1';
|
|
|
|
export default function PodcastPage() {
|
|
const [feed, setFeed] = useState<PodcastFeed | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
const fetchPodcasts = async () => {
|
|
try {
|
|
const response = await fetch(`${API_URL}/podcast/`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch podcasts');
|
|
}
|
|
const data = await response.json();
|
|
setFeed(data);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'An error occurred');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchPodcasts();
|
|
}, []);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex justify-center items-center min-h-screen bg-background">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="flex justify-center items-center min-h-screen bg-background">
|
|
<div className="text-red-500 text-xl">Error: {error}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background text-text p-8">
|
|
<div className="max-w-4xl mx-auto">
|
|
<header className="mb-12 text-center">
|
|
<h1 className="text-4xl font-heading font-bold text-primary mb-4">Podcasts</h1>
|
|
<p className="text-lg text-text opacity-80 font-body">Listen to our latest episodes and stories.</p>
|
|
</header>
|
|
|
|
<div className="space-y-8">
|
|
{feed?.episodes.map((episode) => (
|
|
<div key={episode.id} className="bg-surface rounded-xl shadow-card p-6 flex flex-col md:flex-row gap-6 transition-transform hover:scale-[1.01]">
|
|
<div className="flex-shrink-0">
|
|
<img
|
|
src={episode.image_url || feed.image_url}
|
|
alt={episode.title}
|
|
className="w-32 h-32 rounded-lg object-cover bg-secondary"
|
|
/>
|
|
</div>
|
|
<div className="flex-grow">
|
|
<h2 className="text-2xl font-heading font-semibold text-primary mb-2">{episode.title}</h2>
|
|
<div className="flex items-center gap-4 text-sm text-text opacity-60 mb-4 font-body">
|
|
<span>{new Date(episode.published_at).toLocaleDateString()}</span>
|
|
<span>•</span>
|
|
<span>{episode.duration}</span>
|
|
</div>
|
|
<div
|
|
className="text-text opacity-80 mb-6 font-body line-clamp-3"
|
|
dangerouslySetInnerHTML={{ __html: episode.description }}
|
|
/>
|
|
|
|
<audio
|
|
controls
|
|
className="w-full"
|
|
src={episode.audio_url}
|
|
>
|
|
Your browser does not support the audio element.
|
|
</audio>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|