mirror of
https://github.com/R0m1k3/podcastic.git
synced 2026-10-11 17:26:20 +02:00
feat: implement EpisodeCard component and integrate it into the dashboard view
This commit is contained in:
1 parent
01c179c68b
commit
05eaa96e75
3 files changed
+65
-7
No files matched your search
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(rtk tsc *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import React from 'react';
|
||||
import { Episode } from '../services/episodeService';
|
||||
import { Play, Calendar, Activity, Check, Sparkles } from 'lucide-react';
|
||||
import { Play, Pause, Calendar, Activity, Check, CheckCircle2, Circle } from 'lucide-react';
|
||||
import { useAudio } from '../context/AudioContext';
|
||||
|
||||
interface EpisodeCardProps {
|
||||
episode: Episode;
|
||||
onPlay: (episode: Episode) => void;
|
||||
onDetails?: (episode: Episode) => void;
|
||||
onToggleRead?: (episode: Episode, completed: boolean) => void;
|
||||
}
|
||||
|
||||
export default function EpisodeCard({ episode, onPlay, onDetails }: EpisodeCardProps) {
|
||||
const { currentEpisode, isPlaying } = useAudio();
|
||||
export default function EpisodeCard({ episode, onPlay, onDetails, onToggleRead }: EpisodeCardProps) {
|
||||
const { currentEpisode, isPlaying, togglePlay } = useAudio();
|
||||
const isCurrent = currentEpisode?._id === episode._id;
|
||||
const podcast = typeof episode.podcastId === 'object' ? episode.podcastId : null;
|
||||
const pubDate = new Date(episode.pubDate).toLocaleDateString('fr-FR', {
|
||||
@@ -41,7 +42,16 @@ export default function EpisodeCard({ episode, onPlay, onDetails }: EpisodeCardP
|
||||
|
||||
const handlePlayClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onPlay(episode);
|
||||
if (isCurrent) {
|
||||
togglePlay();
|
||||
} else {
|
||||
onPlay(episode);
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleRead = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (onToggleRead) onToggleRead(episode, !isCompleted);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -72,12 +82,15 @@ export default function EpisodeCard({ episode, onPlay, onDetails }: EpisodeCardP
|
||||
</div>
|
||||
|
||||
{/* Hover Play Button Overlay */}
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center backdrop-blur-[2px]">
|
||||
<div
|
||||
<div className={`absolute inset-0 bg-black/40 transition-opacity duration-300 flex items-center justify-center backdrop-blur-[2px] ${isCurrent ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'}`}>
|
||||
<div
|
||||
onClick={handlePlayClick}
|
||||
className="w-16 h-16 rounded-full bg-white text-obsidian flex items-center justify-center shadow-glow-indigo transform scale-90 group-hover:scale-100 transition-transform duration-500 active:scale-95"
|
||||
>
|
||||
<Play className="w-6 h-6 fill-current ml-1" />
|
||||
{isCurrent && isPlaying
|
||||
? <Pause className="w-6 h-6 fill-current" />
|
||||
: <Play className="w-6 h-6 fill-current ml-1" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -113,6 +126,23 @@ export default function EpisodeCard({ episode, onPlay, onDetails }: EpisodeCardP
|
||||
<Calendar className="w-3.5 h-3.5" />
|
||||
<span>{pubDate}</span>
|
||||
</div>
|
||||
{onToggleRead && (
|
||||
<button
|
||||
onClick={handleToggleRead}
|
||||
title={isCompleted ? 'Marquer comme non lu' : 'Marquer comme lu'}
|
||||
className={`flex items-center gap-1 px-2 py-1 rounded-lg transition-all duration-200 border ${
|
||||
isCompleted
|
||||
? 'text-emerald-400 border-emerald-500/30 hover:bg-emerald-500/10'
|
||||
: 'text-[var(--text-secondary)] border-transparent hover:text-emerald-400 hover:border-emerald-500/30 hover:bg-emerald-500/10'
|
||||
}`}
|
||||
>
|
||||
{isCompleted
|
||||
? <CheckCircle2 className="w-3.5 h-3.5" />
|
||||
: <Circle className="w-3.5 h-3.5" />
|
||||
}
|
||||
<span>{isCompleted ? 'Lu' : 'Lu ?'}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -40,6 +40,25 @@ export default function Dashboard() {
|
||||
setIsDetailsOpen(true);
|
||||
};
|
||||
|
||||
const handleToggleRead = async (episode: Episode, completed: boolean) => {
|
||||
try {
|
||||
await episodeService.saveProgress(
|
||||
episode._id,
|
||||
completed ? episode.duration : 0,
|
||||
completed
|
||||
);
|
||||
setEpisodes(prev =>
|
||||
prev.map(ep =>
|
||||
ep._id === episode._id
|
||||
? { ...ep, progress: { position: completed ? ep.duration : 0, isCompleted: completed } }
|
||||
: ep
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle read state:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Load user
|
||||
useEffect(() => {
|
||||
const loadUser = async () => {
|
||||
@@ -191,6 +210,7 @@ export default function Dashboard() {
|
||||
episode={episode}
|
||||
onPlay={playEpisode}
|
||||
onDetails={handleOpenDetails}
|
||||
onToggleRead={handleToggleRead}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -223,6 +243,7 @@ export default function Dashboard() {
|
||||
episode={episode}
|
||||
onPlay={playEpisode}
|
||||
onDetails={handleOpenDetails}
|
||||
onToggleRead={handleToggleRead}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user