import { useEffect, useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { episodeService, Episode } from '../services/episodeService'; import { authService } from '../services/authService'; import Header from '../components/Header'; import EpisodeCard from '../components/EpisodeCard'; import EpisodeDetails from '../components/EpisodeDetails'; import { useAudio } from '../context/AudioContext'; import AlertModal, { AlertType } from '../components/AlertModal'; import AudioPlayer from '../components/AudioPlayer'; import { Sparkles, Play, Clock, TrendingUp, Loader, RefreshCcw } from 'lucide-react'; export default function Dashboard() { const { playEpisode, currentEpisode, closePlayer, setUserId } = useAudio(); const queryClient = useQueryClient(); const [user, setUser] = useState(null); const [detailsEpisode, setDetailsEpisode] = useState(null); const [isDetailsOpen, setIsDetailsOpen] = useState(false); const [loading, setLoading] = useState(true); const [episodes, setEpisodes] = useState([]); const [skip, setSkip] = useState(0); const [hasMore, setHasMore] = useState(true); const LIMIT = 32; // Alert states const [alertConfig, setAlertConfig] = useState<{ isOpen: boolean; title: string; message: string; type: AlertType; }>({ isOpen: false, title: '', message: '', type: 'error' }); const handleOpenDetails = (episode: Episode) => { setDetailsEpisode(episode); setIsDetailsOpen(true); }; const handleToggleRead = async (episode: Episode, completed: boolean) => { const newProgress = { position: completed ? episode.duration : 0, isCompleted: completed }; // Optimistic update: local state + all cached query pages setEpisodes(prev => prev.map(ep => ep._id === episode._id ? { ...ep, progress: newProgress } : ep) ); queryClient.setQueriesData( { 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) { console.error('Failed to toggle read state:', error); // Revert on error queryClient.invalidateQueries({ queryKey: ['episodes', 'latest'] }); } }; // Load user useEffect(() => { const loadUser = async () => { try { const response = await authService.getMe(); setUser(response.user); setUserId(response.user._id); } catch (error) { console.error('Failed to load user:', error); } finally { setLoading(false); } }; loadUser(); }, []); // Fetch episodes with pagination const { data: episodesData, isLoading: episodesLoading, isFetching: episodesFetching } = useQuery({ queryKey: ['episodes', 'latest', skip], queryFn: () => episodeService.getLatestEpisodes(LIMIT, skip), enabled: !!user, staleTime: 5 * 60 * 1000, }); useEffect(() => { if (episodesData?.episodes) { if (skip === 0) { setEpisodes(episodesData.episodes); } else { setEpisodes(prev => { // Prevent duplicates const newEpisodes = episodesData.episodes.filter( (newEp: any) => !prev.some(oldEp => oldEp._id === newEp._id) ); return [...prev, ...newEpisodes]; }); } setHasMore(episodesData.episodes.length === LIMIT); } }, [episodesData, skip]); const loadMore = () => { if (hasMore && !episodesFetching) { setSkip(prev => prev + LIMIT); } }; const handleLogout = async () => { try { await authService.logout(); authService.clearTokens(); window.location.href = '/login'; } catch (error) { setAlertConfig({ isOpen: true, title: "Erreur de déconnexion", message: "Une erreur s'est produite lors de la déconnexion. Veuillez rafraîchir la page.", type: 'error' }); } }; // Episodes in progress: has progress, not completed, not ≥90% const episodesInProgress = episodes?.filter(e => { if (!e.progress || e.progress.isCompleted) return false; if (e.duration > 0 && e.progress.position / e.duration >= 0.9) return false; return e.progress.position > 0; }) || []; if (loading) { return (
); } return (
{/* Welcome Section / Hero — becomes audio player when playing */} {currentEpisode ? (
) : (
Podcastic

Découvrez les nouveautés de votre univers audio.

Vous avez des nouveaux épisodes qui n'attendent que vous. Prêt pour une immersion ?

{episodesInProgress.length > 0 ? ( ) : ( Découvrir des podcasts )} Parcourir les tendances
)} {/* Continue Listening Section (Only if progression exists) */} {episodesInProgress.length > 0 && (

Continuer l'écoute

{episodesInProgress.slice(0, 4).map((episode) => ( ))}
)} {/* Latest Episodes Section */}

Récemment Publiés

{episodes.length} CHARGÉS
{episodesLoading && skip === 0 ? (
{[1, 2, 3, 4].map(i => (
))}
) : episodes.length > 0 ? ( <>
{episodes.map((episode) => ( ))}
{hasMore && (
)} ) : (
🧘

Silence radio...

Vous n'avez pas encore d'épisodes ici. Abonnez-vous à vos podcasts favoris pour commencer votre collection.

Explorer les podcasts
)}
{/* Episode Slide-over Details */} setIsDetailsOpen(false)} onPlay={playEpisode} /> setAlertConfig(prev => ({ ...prev, isOpen: false }))} />
); }