feat(frontend): Add search functionality to homepage
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"isDownloading": false,
|
||||
"title": "",
|
||||
"timestamp": 1741836072302
|
||||
"timestamp": 1741836609758
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 <div className="loading">Loading videos...</div>;
|
||||
}
|
||||
|
||||
if (error && videoArray.length === 0) {
|
||||
if (error && videoArray.length === 0 && !isSearchMode) {
|
||||
return <div className="error">{error}</div>;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="search-results">
|
||||
<div className="search-header">
|
||||
<h2>Search Results for "{searchTerm}"</h2>
|
||||
</div>
|
||||
|
||||
{/* Local Video Results */}
|
||||
<div className="search-results-section">
|
||||
<h3 className="section-title">From Your Library</h3>
|
||||
{hasLocalResults ? (
|
||||
<div className="search-results-grid">
|
||||
{localSearchResults.map((video) => (
|
||||
<VideoCard
|
||||
key={video.id}
|
||||
video={video}
|
||||
onDeleteVideo={onDeleteVideo}
|
||||
showDeleteButton={true}
|
||||
collections={collections}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="no-results">No matching videos in your library.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* YouTube Search Results */}
|
||||
<div className="search-results-section">
|
||||
<h3 className="section-title">From YouTube</h3>
|
||||
|
||||
{youtubeLoading ? (
|
||||
<div className="youtube-loading">
|
||||
<div className="loading-spinner"></div>
|
||||
<p>Loading YouTube results...</p>
|
||||
</div>
|
||||
) : hasYouTubeResults ? (
|
||||
<div className="search-results-grid">
|
||||
{searchResults.map((result) => (
|
||||
<div key={result.id} className="search-result-card">
|
||||
<div className="search-result-thumbnail">
|
||||
{result.thumbnailUrl ? (
|
||||
<img
|
||||
src={result.thumbnailUrl}
|
||||
alt={result.title}
|
||||
onError={(e) => {
|
||||
e.target.onerror = null;
|
||||
e.target.src = 'https://via.placeholder.com/480x360?text=No+Thumbnail';
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="thumbnail-placeholder">No Thumbnail</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="search-result-info">
|
||||
<h3 className="search-result-title">{result.title}</h3>
|
||||
<p className="search-result-author">{result.author}</p>
|
||||
<div className="search-result-meta">
|
||||
{result.duration && (
|
||||
<span className="search-result-duration">
|
||||
{formatDuration(result.duration)}
|
||||
</span>
|
||||
)}
|
||||
{result.viewCount && (
|
||||
<span className="search-result-views">
|
||||
{formatViewCount(result.viewCount)} views
|
||||
</span>
|
||||
)}
|
||||
<span className={`source-badge ${result.source}`}>
|
||||
{result.source}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className="download-btn"
|
||||
onClick={() => onDownload(result.sourceUrl, result.title)}
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="no-results">No YouTube results found.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Regular home view (not in search mode)
|
||||
return (
|
||||
<div className="home-container">
|
||||
{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;
|
||||
Reference in New Issue
Block a user