fix: lu badge persistence + remove ? + fix duplicate buttons

- EpisodeCard: change 'Lu ?' → 'Marquer' so the toggle button no longer
  shows a confusing question mark next to completed episodes

- Dashboard: fix episodes reappearing in 'Continuer l'écoute' after
  navigation. handleToggleRead now does an optimistic update on BOTH
  local state AND all TanStack Query cache pages (setQueriesData),
  so the 'lu' state survives page navigation without a server roundtrip.
  On error, cache is invalidated to revert to server truth.

- Dashboard: 'Découvrir' button now links to /add (discover new podcasts)
  instead of /trending — was identical to 'Parcourir les tendances' button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Sonnet 4.6 committed 2026-04-21 10:06:09 +02:00
1 parent 454d8517fb
commit e06f342c8e
2 files changed
+24 -15

No files matched your search

+1 -1
View File
@@ -139,7 +139,7 @@ export default function EpisodeCard({ episode, onPlay, onDetails, onToggleRead }
? <CheckCircle2 className="w-3.5 h-3.5" /> ? <CheckCircle2 className="w-3.5 h-3.5" />
: <Circle className="w-3.5 h-3.5" /> : <Circle className="w-3.5 h-3.5" />
} }
<span>{isCompleted ? 'Lu' : 'Lu ?'}</span> <span>{isCompleted ? 'Lu' : 'Marquer'}</span>
</button> </button>
)} )}
</div> </div>
+23 -14
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useQuery } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { episodeService, Episode } from '../services/episodeService'; import { episodeService, Episode } from '../services/episodeService';
import { authService } from '../services/authService'; import { authService } from '../services/authService';
@@ -13,6 +13,7 @@ import { Sparkles, Play, Clock, TrendingUp, Loader, RefreshCcw } from 'lucide-re
export default function Dashboard() { export default function Dashboard() {
const { playEpisode, currentEpisode, closePlayer, setUserId } = useAudio(); const { playEpisode, currentEpisode, closePlayer, setUserId } = useAudio();
const queryClient = useQueryClient();
const [user, setUser] = useState<any>(null); const [user, setUser] = useState<any>(null);
const [detailsEpisode, setDetailsEpisode] = useState<Episode | null>(null); const [detailsEpisode, setDetailsEpisode] = useState<Episode | null>(null);
const [isDetailsOpen, setIsDetailsOpen] = useState(false); const [isDetailsOpen, setIsDetailsOpen] = useState(false);
@@ -41,21 +42,29 @@ export default function Dashboard() {
}; };
const handleToggleRead = async (episode: Episode, completed: boolean) => { const handleToggleRead = async (episode: Episode, completed: boolean) => {
try { const newProgress = { position: completed ? episode.duration : 0, isCompleted: completed };
await episodeService.saveProgress( // Optimistic update: local state + all cached query pages
episode._id,
completed ? episode.duration : 0,
completed
);
setEpisodes(prev => setEpisodes(prev =>
prev.map(ep => prev.map(ep => ep._id === episode._id ? { ...ep, progress: newProgress } : ep)
ep._id === episode._id
? { ...ep, progress: { position: completed ? ep.duration : 0, isCompleted: completed } }
: ep
)
); );
queryClient.setQueriesData<any>(
{ queryKey: ['episodes', 'latest'], exact: false },
(old: any) => {
if (!old?.episodes) return old;
return {
...old,
episodes: old.episodes.map((ep: Episode) =>
ep._id === episode._id ? { ...ep, progress: newProgress } : ep
),
};
}
);
try {
await episodeService.saveProgress(episode._id, newProgress.position, completed);
} catch (error) { } catch (error) {
console.error('Failed to toggle read state:', error); console.error('Failed to toggle read state:', error);
// Revert on error
queryClient.invalidateQueries({ queryKey: ['episodes', 'latest'] });
} }
}; };
@@ -181,9 +190,9 @@ export default function Dashboard() {
Reprendre : {episodesInProgress[0].title.substring(0, 20)}... Reprendre : {episodesInProgress[0].title.substring(0, 20)}...
</button> </button>
) : ( ) : (
<Link to="/trending" className="neon-button flex items-center gap-2"> <Link to="/add" className="neon-button flex items-center gap-2">
<TrendingUp className="w-4 h-4" /> <TrendingUp className="w-4 h-4" />
Découvrir Découvrir des podcasts
</Link> </Link>
)} )}
<Link to="/trending" className="px-6 py-3 rounded-2xl bg-[var(--bg-secondary)] text-[var(--text-secondary)] font-bold text-sm hover:bg-[var(--accent-primary)]/10 transition-all border border-[var(--border-color)] flex items-center justify-center"> <Link to="/trending" className="px-6 py-3 rounded-2xl bg-[var(--bg-secondary)] text-[var(--text-secondary)] font-bold text-sm hover:bg-[var(--accent-primary)]/10 transition-all border border-[var(--border-color)] flex items-center justify-center">