From 77f2a71efd1b2bbcf87aa83830fbfaa9fd831c55 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Fri, 17 Apr 2026 21:45:39 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20composant=20PodcastCard=20unifi=C3=A9?= =?UTF-8?q?=20pour=20Library,=20Trending,=20AddPodcast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Crée PodcastCard réutilisable avec le même design qu'EpisodeCard (image carrée, auteur, titre, footer avec actions). Remplace les 3 designs inline différents par un seul composant cohérent. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/PodcastCard.tsx | 169 ++++++++++++++++++++++++ frontend/src/pages/AddPodcast.tsx | 39 ++---- frontend/src/pages/Library.tsx | 120 +++-------------- frontend/src/pages/Trending.tsx | 63 ++------- 4 files changed, 210 insertions(+), 181 deletions(-) create mode 100644 frontend/src/components/PodcastCard.tsx diff --git a/frontend/src/components/PodcastCard.tsx b/frontend/src/components/PodcastCard.tsx new file mode 100644 index 0000000..7226720 --- /dev/null +++ b/frontend/src/components/PodcastCard.tsx @@ -0,0 +1,169 @@ +import React from 'react'; +import { Play, Plus, Check, Loader, Trash2, RefreshCw } from 'lucide-react'; + +interface PodcastCardProps { + title: string; + author?: string; + imageUrl?: string; + rank?: number; + episodeCount?: number; + language?: string; + lastEpisodeTitle?: string; + lastEpisodeDate?: string; + // Subscribe action + onSubscribe?: () => void; + isSubscribed?: boolean; + isSubscribing?: boolean; + // Library actions + onSync?: () => void; + isSyncing?: boolean; + onUnsubscribe?: () => void; + confirmDelete?: boolean; + onConfirmDelete?: () => void; + onCancelDelete?: () => void; +} + +export default function PodcastCard({ + title, + author, + imageUrl, + rank, + episodeCount, + language, + lastEpisodeTitle, + lastEpisodeDate, + onSubscribe, + isSubscribed, + isSubscribing, + onSync, + isSyncing, + onUnsubscribe, + confirmDelete, + onConfirmDelete, + onCancelDelete, +}: PodcastCardProps) { + return ( +
+ {/* Image */} +
+ {imageUrl ? ( + {title} + ) : ( +
🎙️
+ )} + + {rank && ( +
+ #{rank} +
+ )} + + {isSubscribed && ( +
+ + Abonné +
+ )} + + {/* Hover play overlay */} +
+
+ +
+
+ + {(episodeCount !== undefined || language) && ( +
+ {episodeCount !== undefined && ( +
+ {episodeCount} ép. +
+ )} + {language && ( +
+ {language} +
+ )} +
+ )} +
+ + {/* Content */} +
+ {author && ( + + {author} + + )} + +

+ {title} +

+ + {lastEpisodeTitle && ( +
+

Dernier épisode

+

{lastEpisodeTitle}

+ {lastEpisodeDate && ( +

+ {new Date(lastEpisodeDate).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' })} +

+ )} +
+ )} + + {/* Footer */} +
+ {confirmDelete ? ( +
+ + +
+ ) : onUnsubscribe ? ( +
+ {onSync && ( + + )} + +
+ ) : onSubscribe ? ( + + ) : null} +
+
+
+ ); +} diff --git a/frontend/src/pages/AddPodcast.tsx b/frontend/src/pages/AddPodcast.tsx index 087fbb4..15e6349 100644 --- a/frontend/src/pages/AddPodcast.tsx +++ b/frontend/src/pages/AddPodcast.tsx @@ -8,7 +8,8 @@ import EpisodeCard from '../components/EpisodeCard'; import EpisodeDetails from '../components/EpisodeDetails'; import { useAudio } from '../context/AudioContext'; import AlertModal, { AlertType } from '../components/AlertModal'; -import { Search, Plus, Loader, Rss, Check, Globe, Sparkles, PlusCircle, LayoutGrid, ListMusic } from 'lucide-react'; +import { Search, Loader, Rss, Globe, Sparkles, PlusCircle, LayoutGrid, ListMusic } from 'lucide-react'; +import PodcastCard from '../components/PodcastCard'; import { authService } from '../services/authService'; import SuccessModal from '../components/SuccessModal'; @@ -237,33 +238,15 @@ export default function AddPodcast() { ) : searchType === 'podcasts' && (searchQuery.length > 0 ? searchResults?.podcasts : trendingResults?.podcasts) ? (
{(searchQuery.length > 0 ? searchResults?.podcasts : trendingResults?.podcasts).map((podcast: any) => ( -
-
- {podcast.imageUrl ? ( - {podcast.title} - ) : ( -
🎙️
- )} -
-
- -
-

{podcast.title}

-

{podcast.author || 'Inconnu'}

- - -
-
+ handleSubscribe(podcast)} + isSubscribed={isSubscribed(podcast.rssUrl)} + isSubscribing={subscribingId === podcast.id} + /> ))}
) : searchType === 'episodes' && episodeResults?.episodes ? ( diff --git a/frontend/src/pages/Library.tsx b/frontend/src/pages/Library.tsx index c974919..34ac14f 100644 --- a/frontend/src/pages/Library.tsx +++ b/frontend/src/pages/Library.tsx @@ -3,7 +3,8 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { podcastService, Podcast } from '../services/podcastService'; import { authService } from '../services/authService'; import Header from '../components/Header'; -import { BookOpen, Trash2, Loader, Rss, Play, RefreshCw, AlertCircle } from 'lucide-react'; +import { BookOpen, Rss } from 'lucide-react'; +import PodcastCard from '../components/PodcastCard'; import { Link } from 'react-router-dom'; import AlertModal, { AlertType } from '../components/AlertModal'; @@ -149,107 +150,22 @@ export default function Library() { ) : (
{podcasts.map((podcast) => ( -
-
-
-
- {podcast.imageUrl ? ( - {podcast.title} - ) : ( -
🎙️
- )} -
-
- -
-
- -
-
-

- {podcast.title} -

-
-

{podcast.author || 'Artiste Inconnu'}

- -
- - {podcast.episodeCount} EPISODES - - {podcast.language && ( - - {podcast.language} - - )} -
-
-
- - {/* Status Section */} -
- {podcast.episodeCount === 0 ? ( -
-
- - Besoin de synchronisation -
- -
- ) : podcast.lastEpisodeTitle && ( -
-

Dernier épisode

-

{podcast.lastEpisodeTitle}

-

- {new Date(podcast.lastEpisodeDate!).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' })} -

-
- )} -
- - {/* Actions Footer */} -
- {confirmDeleteId === (podcast.subscriptionId || podcast._id) ? ( -
- - -
- ) : ( -
- - -
- )} -
-
+ syncMutation.mutate(podcast._id)} + isSyncing={syncMutation.isPending && syncMutation.variables === podcast._id} + onUnsubscribe={() => setConfirmDeleteId(podcast.subscriptionId || podcast._id)} + confirmDelete={confirmDeleteId === (podcast.subscriptionId || podcast._id)} + onConfirmDelete={handleConfirmUnsubscribe} + onCancelDelete={() => setConfirmDeleteId(null)} + /> ))}
)} diff --git a/frontend/src/pages/Trending.tsx b/frontend/src/pages/Trending.tsx index 34cca85..2c89dd3 100644 --- a/frontend/src/pages/Trending.tsx +++ b/frontend/src/pages/Trending.tsx @@ -3,7 +3,8 @@ 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, Sparkles, TrendingUp } from 'lucide-react'; +import { Loader, Flame, Sparkles, TrendingUp } from 'lucide-react'; +import PodcastCard from '../components/PodcastCard'; import { authService } from '../services/authService'; import SuccessModal from '../components/SuccessModal'; import AlertModal, { AlertType } from '../components/AlertModal'; @@ -201,56 +202,16 @@ export default function Trending() { <>
{podcasts.map((podcast, index) => ( -
-
- {podcast.imageUrl ? ( - {podcast.title} - ) : ( -
🎙️
- )} -
- Rank #{index + 1} -
-
-
- -
-

- {podcast.title} -

- {podcast.author && ( -

{podcast.author}

- )} - - -
-
+ handleSubscribe(podcast)} + isSubscribed={isSubscribed(podcast.rssUrl)} + isSubscribing={subscribingId === podcast.id} + /> ))}