From 02b07431efa36e2b74afbad5dbfe58b6e80d5aea Mon Sep 17 00:00:00 2001 From: Peifan Li Date: Wed, 12 Mar 2025 23:30:21 -0400 Subject: [PATCH] feat(frontend): Add search functionality to homepage --- backend/data/status.json | 2 +- frontend/src/App.jsx | 7 ++ frontend/src/components/Header.jsx | 4 +- frontend/src/pages/Home.jsx | 129 ++++++++++++++++++++++++++++- 4 files changed, 136 insertions(+), 6 deletions(-) diff --git a/backend/data/status.json b/backend/data/status.json index 9e977a3..c4a47f8 100644 --- a/backend/data/status.json +++ b/backend/data/status.json @@ -1,5 +1,5 @@ { "isDownloading": false, "title": "", - "timestamp": 1741836072302 + "timestamp": 1741836609758 } \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 01b43a9..23a87a4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -363,6 +363,7 @@ function App() { error: 'Failed to search. Please try again.' }; } + return { success: false, error: 'Search was cancelled' }; } finally { // Only update loading state if the request wasn't aborted if (searchAbortController.current && !searchAbortController.current.signal.aborted) { @@ -652,6 +653,12 @@ function App() { error={error} onDeleteVideo={handleDeleteVideo} collections={collections} + isSearchMode={isSearchMode} + searchTerm={searchTerm} + localSearchResults={localSearchResults} + youtubeLoading={youtubeLoading} + searchResults={searchResults} + onDownload={handleDownloadFromSearch} /> } /> diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index a400ddf..2009457 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -42,7 +42,7 @@ const Header = ({ onSubmit, onSearch, downloadingTitle, isDownloading }) => { const searchResult = await onSearch(videoUrl); if (searchResult.success) { setVideoUrl(''); - navigate('/'); // Navigate to home which will show search results + navigate('/'); // Stay on homepage to show search results } else { setError(searchResult.error); } @@ -55,7 +55,7 @@ const Header = ({ onSubmit, onSearch, downloadingTitle, isDownloading }) => { if (result.success) { setVideoUrl(''); - // Stay on home page which will show search results + // Stay on homepage to show search results navigate('/'); } else { setError(result.error); diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 01e9f21..07eaa5b 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -2,15 +2,27 @@ import AuthorsList from '../components/AuthorsList'; import Collections from '../components/Collections'; import VideoCard from '../components/VideoCard'; -const Home = ({ videos = [], loading, error, onDeleteVideo, collections = [] }) => { +const Home = ({ + videos = [], + loading, + error, + onDeleteVideo, + collections = [], + isSearchMode = false, + searchTerm = '', + localSearchResults = [], + youtubeLoading = false, + searchResults = [], + onDownload +}) => { // Add default empty array to ensure videos is always an array const videoArray = Array.isArray(videos) ? videos : []; - if (loading && videoArray.length === 0) { + if (loading && videoArray.length === 0 && !isSearchMode) { return
Loading videos...
; } - if (error && videoArray.length === 0) { + if (error && videoArray.length === 0 && !isSearchMode) { return
{error}
; } @@ -34,6 +46,101 @@ const Home = ({ videos = [], loading, error, onDeleteVideo, collections = [] }) }); }); + // If in search mode, show search results + if (isSearchMode) { + const hasLocalResults = localSearchResults && localSearchResults.length > 0; + const hasYouTubeResults = searchResults && searchResults.length > 0; + + return ( +
+
+

Search Results for "{searchTerm}"

+
+ + {/* Local Video Results */} +
+

From Your Library

+ {hasLocalResults ? ( +
+ {localSearchResults.map((video) => ( + + ))} +
+ ) : ( +

No matching videos in your library.

+ )} +
+ + {/* YouTube Search Results */} +
+

From YouTube

+ + {youtubeLoading ? ( +
+
+

Loading YouTube results...

+
+ ) : hasYouTubeResults ? ( +
+ {searchResults.map((result) => ( +
+
+ {result.thumbnailUrl ? ( + {result.title} { + e.target.onerror = null; + e.target.src = 'https://via.placeholder.com/480x360?text=No+Thumbnail'; + }} + /> + ) : ( +
No Thumbnail
+ )} +
+
+

{result.title}

+

{result.author}

+
+ {result.duration && ( + + {formatDuration(result.duration)} + + )} + {result.viewCount && ( + + {formatViewCount(result.viewCount)} views + + )} + + {result.source} + +
+ +
+
+ ))} +
+ ) : ( +

No YouTube results found.

+ )} +
+
+ ); + } + + // Regular home view (not in search mode) return (
{videoArray.length === 0 ? ( @@ -69,4 +176,20 @@ const Home = ({ videos = [], loading, error, onDeleteVideo, collections = [] }) ); }; +// Helper function to format duration in seconds to MM:SS +const formatDuration = (seconds) => { + if (!seconds) return ''; + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.floor(seconds % 60); + return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; +}; + +// Helper function to format view count +const formatViewCount = (count) => { + if (!count) return '0'; + if (count < 1000) return count.toString(); + if (count < 1000000) return `${(count / 1000).toFixed(1)}K`; + return `${(count / 1000000).toFixed(1)}M`; +}; + export default Home; \ No newline at end of file