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 React from 'react';
|
||||||
import { Episode } from '../services/episodeService';
|
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';
|
import { useAudio } from '../context/AudioContext';
|
||||||
|
|
||||||
interface EpisodeCardProps {
|
interface EpisodeCardProps {
|
||||||
episode: Episode;
|
episode: Episode;
|
||||||
onPlay: (episode: Episode) => void;
|
onPlay: (episode: Episode) => void;
|
||||||
onDetails?: (episode: Episode) => void;
|
onDetails?: (episode: Episode) => void;
|
||||||
|
onToggleRead?: (episode: Episode, completed: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EpisodeCard({ episode, onPlay, onDetails }: EpisodeCardProps) {
|
export default function EpisodeCard({ episode, onPlay, onDetails, onToggleRead }: EpisodeCardProps) {
|
||||||
const { currentEpisode, isPlaying } = useAudio();
|
const { currentEpisode, isPlaying, togglePlay } = useAudio();
|
||||||
const isCurrent = currentEpisode?._id === episode._id;
|
const isCurrent = currentEpisode?._id === episode._id;
|
||||||
const podcast = typeof episode.podcastId === 'object' ? episode.podcastId : null;
|
const podcast = typeof episode.podcastId === 'object' ? episode.podcastId : null;
|
||||||
const pubDate = new Date(episode.pubDate).toLocaleDateString('fr-FR', {
|
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) => {
|
const handlePlayClick = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onPlay(episode);
|
if (isCurrent) {
|
||||||
|
togglePlay();
|
||||||
|
} else {
|
||||||
|
onPlay(episode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleRead = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (onToggleRead) onToggleRead(episode, !isCompleted);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -72,12 +82,15 @@ export default function EpisodeCard({ episode, onPlay, onDetails }: EpisodeCardP
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Hover Play Button Overlay */}
|
{/* 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 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
|
<div
|
||||||
onClick={handlePlayClick}
|
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"
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -113,6 +126,23 @@ export default function EpisodeCard({ episode, onPlay, onDetails }: EpisodeCardP
|
|||||||
<Calendar className="w-3.5 h-3.5" />
|
<Calendar className="w-3.5 h-3.5" />
|
||||||
<span>{pubDate}</span>
|
<span>{pubDate}</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,25 @@ export default function Dashboard() {
|
|||||||
setIsDetailsOpen(true);
|
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
|
// Load user
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadUser = async () => {
|
const loadUser = async () => {
|
||||||
@@ -191,6 +210,7 @@ export default function Dashboard() {
|
|||||||
episode={episode}
|
episode={episode}
|
||||||
onPlay={playEpisode}
|
onPlay={playEpisode}
|
||||||
onDetails={handleOpenDetails}
|
onDetails={handleOpenDetails}
|
||||||
|
onToggleRead={handleToggleRead}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -223,6 +243,7 @@ export default function Dashboard() {
|
|||||||
episode={episode}
|
episode={episode}
|
||||||
onPlay={playEpisode}
|
onPlay={playEpisode}
|
||||||
onDetails={handleOpenDetails}
|
onDetails={handleOpenDetails}
|
||||||
|
onToggleRead={handleToggleRead}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user