diff --git a/backend/src/services/rssParserService.ts b/backend/src/services/rssParserService.ts index facd589..3fb1460 100644 --- a/backend/src/services/rssParserService.ts +++ b/backend/src/services/rssParserService.ts @@ -241,7 +241,24 @@ export const rssParserService = { // Update podcast metadata podcast.lastFetched = new Date(); podcast.episodeCount = await Episode.countDocuments({ podcastId }); - + + // Refresh podcast metadata from fresh RSS data (fixes "Unknown Podcast" legacy records) + if (feedData.title && feedData.title !== 'Unknown Podcast') { + podcast.title = feedData.title; + } + if (feedData.author && (!podcast.author || podcast.author.trim() === '' || force)) { + podcast.author = feedData.author; + } + if (feedData.imageUrl && (!podcast.imageUrl || force)) { + podcast.imageUrl = feedData.imageUrl; + } + if (feedData.description && (!podcast.description || force)) { + podcast.description = feedData.description; + } + if (feedData.language && (!podcast.language || force)) { + podcast.language = feedData.language; + } + if (episodes.length > 0) { podcast.lastEpisodeTitle = episodes[0].title; podcast.lastEpisodeDate = episodes[0].pubDate; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6dd3051..5e069ba 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useState, useEffect } from 'react'; import './styles/globals.css'; @@ -24,6 +24,8 @@ const queryClient = new QueryClient({ // App Content Wrapper to access AudioContext function AppContent() { const { currentEpisode, closePlayer } = useAudio(); + const location = useLocation(); + const isDashboard = location.pathname === '/dashboard' || location.pathname === '/'; const [user, setUser] = useState(null); // Load user for the player @@ -82,7 +84,7 @@ function AppContent() { } /> - {currentEpisode && ( + {currentEpisode && !isDashboard && ( void; userId?: string; + mode?: 'floating' | 'inline'; } -export default function AudioPlayer({ episode, onClose, userId }: AudioPlayerProps) { +export default function AudioPlayer({ episode, onClose, userId, mode = 'floating' }: AudioPlayerProps) { const { isPlaying, setIsPlaying, togglePlay } = useAudio(); const { theme } = useTheme(); const audioRef = useRef(null); @@ -35,6 +37,8 @@ export default function AudioPlayer({ episode, onClose, userId }: AudioPlayerPro const [isExpanded, setIsExpanded] = useState(false); const [isResuming, setIsResuming] = useState(false); const [error, setError] = useState(null); + const [showMiniBar, setShowMiniBar] = useState(false); + const inlineWrapperRef = useRef(null); // Refs for stable tracking without re-renders const currentTimeRef = useRef(0); @@ -88,6 +92,19 @@ export default function AudioPlayer({ episode, onClose, userId }: AudioPlayerPro }; }, [episode?._id, userId, isPlaying]); // Restart if episode or isPlaying changes to maintain correct interval behavior + // IntersectionObserver: show mini-bar when inline player scrolls out of view + useEffect(() => { + if (mode !== 'inline' || !inlineWrapperRef.current) return; + const observer = new IntersectionObserver( + ([entry]) => { + setShowMiniBar(!entry.isIntersecting); + }, + { threshold: 0, rootMargin: '-40px 0px 0px 0px' } + ); + observer.observe(inlineWrapperRef.current); + return () => observer.disconnect(); + }, [mode, episode?._id]); + // Load previous progress useEffect(() => { if (!episode || !userId) return; @@ -154,6 +171,200 @@ export default function AudioPlayer({ episode, onClose, userId }: AudioPlayerPro const inverseTheme = theme === 'light' ? 'dark-theme' : 'light-theme'; + // ── INLINE MODE (embedded in hero card) ── + if (mode === 'inline') { + return ( + <> +
+