mirror of
https://github.com/R0m1k3/podcastic.git
synced 2026-10-11 17:26:20 +02:00
feat: implement trending podcasts page and RSS parsing service for feed data extraction
This commit is contained in:
1 parent
2fbbf70ee0
commit
aaf7b82985
4 files changed
+121
-6
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user