feat: implement trending podcasts page and RSS parsing service for feed data extraction

This commit is contained in:
Michael committed 2026-04-16 13:44:05 +02:00
1 parent 2fbbf70ee0
commit aaf7b82985
4 files changed
+121 -6

No files matched your search

+3
View File
@@ -284,8 +284,11 @@ export const rssParserService = {
podcast.lastFetched = new Date();
if (episodes.length > 0) {
console.log(`[RSS Parser] Setting last episode for ${podcast.title}: ${episodes[0].title} (${episodes[0].pubDate})`);
podcast.lastEpisodeTitle = episodes[0].title;
podcast.lastEpisodeDate = episodes[0].pubDate;
} else {
console.warn(`[RSS Parser] No episodes found for ${podcast.title} during creation.`);
}
await podcast.save();
+88
View File
@@ -0,0 +1,88 @@
import { CheckCircle2, ChevronRight, BookOpen } from 'lucide-react';
import { Link } from 'react-router-dom';
interface SuccessModalProps {
isOpen: boolean;
onClose: () => void;
podcastTitle: string;
podcastAuthor?: string;
podcastImage?: string;
message?: string;
}
export default function SuccessModal({
isOpen,
onClose,
podcastTitle,
podcastAuthor,
podcastImage,
message = "Podcast ajouté avec succès !"
}: SuccessModalProps) {
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-[110] flex items-center justify-center p-4">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/40 backdrop-blur-sm animate-fade-in"
onClick={onClose}
/>
{/* Modal */}
<div className="relative bg-white rounded-3xl shadow-2xl w-full max-w-sm overflow-hidden animate-slide-up">
{/* Top Gradient */}
<div className="h-2 bg-gradient-primary w-full" />
<div className="p-8 flex flex-col items-center text-center">
{/* Animated Success Icon */}
<div className="w-16 h-16 bg-green-50 rounded-full flex items-center justify-center mb-6 animate-pulse">
<CheckCircle2 className="w-8 h-8 text-green-500" />
</div>
<h3 className="text-xl font-bold text-light-900 mb-2">
{message}
</h3>
<div className="w-full bg-light-50 rounded-2xl p-4 mb-8 flex items-center gap-4 border border-light-100">
{podcastImage ? (
<img
src={podcastImage}
alt={podcastTitle}
className="w-16 h-16 rounded-xl object-cover shadow-sm"
/>
) : (
<div className="w-16 h-16 rounded-xl bg-light-200 flex items-center justify-center text-2xl">
🎙️
</div>
)}
<div className="text-left flex-1 min-w-0">
<p className="font-bold text-light-900 truncate">{podcastTitle}</p>
{podcastAuthor && (
<p className="text-sm text-light-500 truncate">{podcastAuthor}</p>
)}
</div>
</div>
<div className="flex flex-col w-full gap-3">
<Link
to="/library"
className="flex items-center justify-center gap-2 w-full py-3.5 rounded-xl bg-blue-500 text-white font-bold hover:bg-blue-600 transition-all shadow-soft active:scale-[0.98]"
onClick={onClose}
>
<BookOpen className="w-5 h-5" />
Voir dans ma Bibliothèque
<ChevronRight className="w-4 h-4" />
</Link>
<button
onClick={onClose}
className="w-full py-3.5 rounded-xl bg-light-100 text-light-700 font-bold hover:bg-light-200 transition-all"
>
Continuer la recherche
</button>
</div>
</div>
</div>
</div>
);
}
+15 -3
View File
@@ -2,9 +2,8 @@ import { useState, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { discoveryService, DiscoveryPodcast } from '../services/discoveryService';
import { podcastService } from '../services/podcastService';
import Header from '../components/Header';
import { Search, Plus, Loader, Rss, Check } from 'lucide-react';
import { authService } from '../services/authService';
import SuccessModal from '../components/SuccessModal';
export default function AddPodcast() {
const queryClient = useQueryClient();
@@ -16,6 +15,10 @@ export default function AddPodcast() {
const [rssError, setRssError] = useState<string | null>(null);
const [rssSuccess, setRssSuccess] = useState<string | null>(null);
// Modal state
const [showSuccessModal, setShowSuccessModal] = useState(false);
const [successPodcast, setSuccessPodcast] = useState<any>(null);
useEffect(() => {
const loadUser = async () => {
try {
@@ -60,7 +63,8 @@ export default function AddPodcast() {
try {
setSubscribingId(podcast.id);
await subscribeMutation.mutateAsync(podcast);
alert(`"${podcast.title}" ajouté à vos abonnements !`);
setSuccessPodcast(podcast);
setShowSuccessModal(true);
queryClient.invalidateQueries({ queryKey: ['episodes', 'latest'] });
} catch (error: any) {
const errorMessage = error.response?.data?.message || 'Failed to subscribe';
@@ -228,6 +232,14 @@ export default function AddPodcast() {
)}
</main>
<SuccessModal
isOpen={showSuccessModal}
onClose={() => setShowSuccessModal(false)}
podcastTitle={successPodcast?.title || ""}
podcastAuthor={successPodcast?.author || ""}
podcastImage={successPodcast?.imageUrl || ""}
/>
</div>
);
}
+15 -3
View File
@@ -2,15 +2,18 @@ import { useState, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { discoveryService, DiscoveryPodcast } from '../services/discoveryService';
import { podcastService } from '../services/podcastService';
import Header from '../components/Header';
import { Loader, Plus, Flame, Check } from 'lucide-react';
import { authService } from '../services/authService';
import SuccessModal from '../components/SuccessModal';
export default function Trending() {
const queryClient = useQueryClient();
const [user, setUser] = useState<any>(null);
const [subscribingId, setSubscribingId] = useState<string | null>(null);
// Modal state
const [showSuccessModal, setShowSuccessModal] = useState(false);
const [successPodcast, setSuccessPodcast] = useState<any>(null);
useEffect(() => {
const loadUser = async () => {
try {
@@ -54,7 +57,8 @@ export default function Trending() {
try {
setSubscribingId(podcast.id);
await subscribeMutation.mutateAsync(podcast);
alert(`"${podcast.title}" ajouté à vos abonnements !`);
setSuccessPodcast(podcast);
setShowSuccessModal(true);
queryClient.invalidateQueries({ queryKey: ['episodes', 'latest'] });
} catch (error: any) {
const errorMessage = error.response?.data?.message || 'Failed to subscribe';
@@ -157,6 +161,14 @@ export default function Trending() {
</div>
)}
</main>
<SuccessModal
isOpen={showSuccessModal}
onClose={() => setShowSuccessModal(false)}
podcastTitle={successPodcast?.title || ""}
podcastAuthor={successPodcast?.author || ""}
podcastImage={successPodcast?.imageUrl || ""}
/>
</div>
);
}