194 lines
7.0 KiB
TypeScript
194 lines
7.0 KiB
TypeScript
'use client'
|
|
|
|
import { SearchBar } from '@/components/SearchBar'
|
|
import { SongCard } from '@/components/SongCard'
|
|
import { AlbumCard } from '@/components/AlbumCard'
|
|
import { ArtistCard } from '@/components/ArtistCard'
|
|
import { SectionHeader } from '@/components/SectionHeader'
|
|
import { useState, useEffect, Suspense } from 'react'
|
|
import { useSearchParams } from 'next/navigation'
|
|
|
|
function SearchContent() {
|
|
const searchParams = useSearchParams()
|
|
const [results, setResults] = useState<any>(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [searchedQuery, setSearchedQuery] = useState('')
|
|
|
|
useEffect(() => {
|
|
const q = searchParams.get('q')
|
|
if (q) {
|
|
performSearch(q)
|
|
}
|
|
}, [searchParams])
|
|
|
|
const performSearch = async (searchQuery: string) => {
|
|
if (!searchQuery.trim()) {
|
|
setResults(null)
|
|
return
|
|
}
|
|
|
|
setSearchedQuery(searchQuery)
|
|
setLoading(true)
|
|
try {
|
|
const res = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`)
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setResults(data)
|
|
}
|
|
} catch (error) {
|
|
console.error('Search failed:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleSearch = (query: string) => {
|
|
if (query.trim()) {
|
|
const url = new URL(window.location.href)
|
|
url.searchParams.set('q', query)
|
|
window.history.pushState({}, '', url)
|
|
performSearch(query)
|
|
} else {
|
|
setResults(null)
|
|
setSearchedQuery('')
|
|
}
|
|
}
|
|
|
|
const hasResults = results && (
|
|
(results.songs && results.songs.length > 0) ||
|
|
(results.albums && results.albums.length > 0) ||
|
|
(results.artists && results.artists.length > 0)
|
|
)
|
|
|
|
return (
|
|
<main className="min-h-screen bg-zinc-950">
|
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
|
{/* Header */}
|
|
<div className="mb-8">
|
|
<h1 className="text-4xl font-bold text-white mb-4">Search</h1>
|
|
<p className="text-xl text-zinc-400 mb-6">
|
|
Find songs, artists, albums, and playlists
|
|
</p>
|
|
<SearchBar onSearch={handleSearch} autoFocus />
|
|
</div>
|
|
|
|
{/* Results */}
|
|
{loading ? (
|
|
<div className="text-center py-16">
|
|
<div className="w-12 h-12 border-4 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
|
<p className="text-zinc-400">Searching...</p>
|
|
</div>
|
|
) : hasResults ? (
|
|
<div className="space-y-10">
|
|
{/* Songs Section */}
|
|
{results.songs && results.songs.length > 0 && (
|
|
<section>
|
|
<SectionHeader title="Songs" subtitle={`${results.songs.length} results`} />
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mt-4">
|
|
{results.songs.map((song: any) => (
|
|
<SongCard
|
|
key={song.id}
|
|
id={song.id}
|
|
title={song.title}
|
|
artistName={song.artist?.name || 'Unknown Artist'}
|
|
coverUrl={song.coverUrl || song.album?.coverUrl}
|
|
duration={song.duration || 0}
|
|
plays={song.plays}
|
|
/>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{/* Artists Section */}
|
|
{results.artists && results.artists.length > 0 && (
|
|
<section>
|
|
<SectionHeader title="Artists" subtitle={`${results.artists.length} results`} />
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 mt-4">
|
|
{results.artists.map((artist: any) => (
|
|
<ArtistCard
|
|
key={artist.id}
|
|
id={artist.id}
|
|
name={artist.name}
|
|
avatarUrl={artist.avatarUrl}
|
|
verified={artist.verified}
|
|
/>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
|
|
{/* Albums Section */}
|
|
{results.albums && results.albums.length > 0 && (
|
|
<section>
|
|
<SectionHeader title="Albums" subtitle={`${results.albums.length} results`} />
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 mt-4">
|
|
{results.albums.map((album: any) => (
|
|
<AlbumCard
|
|
key={album.id}
|
|
id={album.id}
|
|
title={album.title}
|
|
artistName={album.artist?.name || 'Unknown Artist'}
|
|
coverUrl={album.coverUrl}
|
|
releaseYear={album.releaseDate ? new Date(album.releaseDate).getFullYear() : undefined}
|
|
trackCount={album._count?.songs || album.songs?.length}
|
|
/>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
</div>
|
|
) : searchedQuery ? (
|
|
<div className="text-center py-16">
|
|
<svg className="w-24 h-24 text-zinc-700 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<p className="text-xl text-zinc-400 mb-2">No results found for "{searchedQuery}"</p>
|
|
<p className="text-zinc-500">Try different keywords or browse by genre</p>
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-16">
|
|
<p className="text-xl text-zinc-400 mb-4">Start typing to search</p>
|
|
<p className="text-zinc-500">Find your favorite music</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Popular Searches */}
|
|
{!searchedQuery && (
|
|
<div className="mt-12">
|
|
<h2 className="text-2xl font-bold text-white mb-4">Popular Searches</h2>
|
|
<div className="flex flex-wrap gap-3">
|
|
{['Hip Hop', 'Rock', 'Electronic', 'Jazz', 'Pop', 'Classical'].map((genre) => (
|
|
<button
|
|
key={genre}
|
|
onClick={() => handleSearch(genre)}
|
|
className="px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-white rounded-full transition-colors"
|
|
>
|
|
{genre}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
export default function SearchPage() {
|
|
return (
|
|
<Suspense fallback={
|
|
<main className="min-h-screen bg-zinc-950">
|
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
|
<div className="text-center py-16">
|
|
<div className="w-12 h-12 border-4 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
|
<p className="text-zinc-400">Loading search...</p>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
}>
|
|
<SearchContent />
|
|
</Suspense>
|
|
)
|
|
}
|