import { useState, useEffect, useRef } from 'react'; import { useSocket } from '../hooks/useSocket'; import { useDebounce } from '../hooks/useDebounce'; import { SearchIcon, DownloadingIcon } from '../components/SvgIcons'; import { toast } from 'sonner'; import { useDownloadContext } from '../context/DownloadContext/DownloadContext'; import './OnlineSearch.css'; // Modal component for displaying detailed information const DetailModal = ({ item, onClose }: { item: SearchResult | null; onClose: () => void }) => { if (!item) return null; return (
e.stopPropagation()}>
{item.name}

{item.name}

{item.artist}

{item.album &&

Album: {item.album}

} {item.type.toUpperCase()}

Details

{item.duration && (

Duration: {Math.floor(item.duration / 60000)}:{String(Math.floor((item.duration % 60000) / 1000)).padStart(2, '0')}

)} {item.total_tracks && (

Total Tracks: {item.total_tracks}

)} {item.release_date && (

Release Date: {item.release_date}

)}
🎵 Open in Spotify
); }; interface SearchResult { id: string; name: string; artist: string; album: string; image: string; type: 'track' | 'album' | 'playlist' | 'artist'; duration?: number; url: string; total_tracks?: number; release_date?: string; } interface DownloadStatus { status: string; progress?: number; id?: string; name?: string; } export const OnlineSearch = () => { const [query, setQuery] = useState(''); const [searchType, setSearchType] = useState<'track' | 'album' | 'playlist' | 'artist'>('track'); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [downloadStatus, setDownloadStatus] = useState<{ [key: string]: DownloadStatus }>({}); const [selectedItem, setSelectedItem] = useState(null); const { socket, isConnected, connectionError } = useSocket(); const downloadContext = useDownloadContext(); const containerRef = useRef(null); const searchTimeoutRef = useRef(null); const handleSearch = () => { if (!query.trim()) { toast.error('Please enter a search query.'); return; } if (!socket) { toast.error('Not connected to the server.'); return; } if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } setLoading(true); socket.emit('search', { query, type: searchType }); searchTimeoutRef.current = setTimeout(() => { setLoading(false); toast.error('Search timed out. Please try again.'); searchTimeoutRef.current = null; }, 10000); }; useEffect(() => { if (!socket) return; const handleSearchResults = (data: { results: SearchResult[] }) => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); searchTimeoutRef.current = null; } setResults(data.results || []); setLoading(false); }; const handleSearchError = (error: any) => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); searchTimeoutRef.current = null; } toast.error(error.message || 'An error occurred during search.'); setLoading(false); }; const handleDownloadUpdate = (data: any) => { console.log('Download update received:', data); if (data.id && data.status) { setDownloadStatus(prev => ({ ...prev, [data.id]: { status: data.status, progress: data.progress || 0, id: data.id, name: data.name } })); // Update the MediaItem offlineState in DownloadContext if (downloadContext && downloadContext.updateDownloadStatus) { // Map the status to the offlineState values let offlineState: string | undefined = 'downloading'; switch (data.status) { case 'Downloading': case 'downloading': offlineState = 'downloading'; break; case 'Downloading...': offlineState = 'processing'; break; case 'Complete': offlineState = 'finished'; break; case 'downloaded': offlineState = 'downloaded'; break; case 'Failed': offlineState = undefined; break; case 'Accepted': case 'accepted': offlineState = 'accepted'; break; case 'Uploading': case 'uploading': offlineState = 'uploading'; break; default: offlineState = data.status; } // Call updateDownloadStatus with the download ID and status downloadContext.updateDownloadStatus(data.id, offlineState, data.progress || 0); } // Show live progress updates in toast for better feedback if (data.status === 'Downloading...' && data.progress) { // Only show progress updates every 10% to avoid spam const progress = Math.floor(data.progress); if (progress % 10 === 0 || progress === 100) { toast.info(`${data.name || 'Item'}: ${progress}% downloaded`, { duration: 2000, position: 'bottom-center' // More visible position }); } // Always update the toast with latest progress for smooth experience if (progress > 0 && progress < 100) { toast.info(`${data.name || 'Item'}: ${progress}% downloaded`, { id: `download-${data.id}`, // Use consistent ID to update same toast duration: 2000, position: 'bottom-center' }); } } else if (data.status === 'Complete') { toast.success(`${data.name || 'Item'} downloaded successfully!`, { position: 'bottom-center' }); } else if (data.status === 'Failed') { toast.error(`${data.name || 'Item'} failed to download.`, { position: 'bottom-center' }); } } }; socket.on('search_results', handleSearchResults); socket.on('search_error', handleSearchError); socket.on('update_status', handleDownloadUpdate); return () => { socket.off('search_results', handleSearchResults); socket.off('search_error', handleSearchError); socket.off('update_status', handleDownloadUpdate); }; }, [socket]); const debouncedSearch = useDebounce(handleSearch, 500); useEffect(() => { if (query.trim() !== '') { debouncedSearch(); } else { setResults([]); setLoading(false); } }, [query, searchType]); // Remove debouncedSearch to prevent infinite loops const handleDownload = (result: SearchResult) => { if (!socket) { toast.error('Not connected to the server.'); return; } console.log('🔽 Starting download for:', result); toast.info(`Downloading ${result.name}...`); // Use URL as key if ID is not available (fallback for backend compatibility) const downloadKey = result.id || result.url; socket.emit('download', { url: result.url, searchResult: result, name: result.name, artist: result.artist, type: result.type, id: result.id }); setDownloadStatus(prevStatus => ({ ...prevStatus, [downloadKey]: { status: 'Downloading...', progress: 0, id: result.id, name: result.name } })); }; const formatDuration = (duration: number) => { const minutes = Math.floor(duration / 60000); const seconds = ((duration % 60000) / 1000).toFixed(0); return `${minutes}:${seconds.padStart(2, '0')}`; }; return (

Search & Download

Find and add music from Spotify to your library

setQuery(e.target.value)} placeholder="Search for music..." className="search-input" onKeyPress={(e) => e.key === 'Enter' && handleSearch()} />
{(['track', 'album', 'artist', 'playlist'] as const).map((type) => ( ))}
{!isConnected && (
Not connected to the server. Please check your connection.
)}
{loading && query.trim() !== '' && (

Searching...

)} {!loading && results.length > 0 ? ( results.map((result) => (
{result.name} setSelectedItem(result)} title={`${result.name} by ${result.artist}`} />

{result.name}

{result.artist}

{result.duration &&

{formatDuration(result.duration)}

}
)) ) : ( !loading && query.trim() !== '' &&
No results found.
)}
setSelectedItem(null)} />
); };