We use cookies to enhance your experience on the site
CodeWorlds

Search, Discovery, and Recommendations

Spotify's power lies in discovery - powerful search, personalized playlists (Discover Weekly, Release Radar), and recommendations.

Search System

1interface SearchResults {
2  tracks: Track[];
3  albums: Album[];
4  artists: Artist[];
5  playlists: Playlist[];
6}
7
8interface SearchFilters {
9  type?: 'track' | 'album' | 'artist' | 'playlist';
10  genre?: string[];
11  year?: { min?: number; max?: number };
12  explicit?: boolean;
13}
14
15// Search API
16async function search(
17  query: string,
18  filters?: SearchFilters
19): Promise<SearchResults> {
20  // Implementation...
21}

Search Page

1'use client';
2
3import { useState, useEffect } from 'react';
4
5export default function SearchPage() {
6  const [query, setQuery] = useState('');
7  const [activeTab, setActiveTab] = useState<'all' | 'tracks' | 'albums' | 'artists' | 'playlists'>('all');
8  const [results, setResults] = useState<SearchResults | null>(null);
9
10  useEffect(() => {
11    if (query.length > 0) {
12      // Debounced search
13      const timeout = setTimeout(() => {
14        performSearch(query);
15      }, 300);
16
17      return () => clearTimeout(timeout);
18    }
19  }, [query]);
20
21  const performSearch = async (searchQuery: string) => {
22    // Mock search results
23    setResults({
24      tracks: mockTracks.filter(t =>
25        t.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
26        t.artist.name.toLowerCase().includes(searchQuery.toLowerCase())
27      ),
28      albums: mockAlbums.filter(a =>
29        a.title.toLowerCase().includes(searchQuery.toLowerCase())
30      ),
31      artists: mockArtists.filter(a =>
32        a.name.toLowerCase().includes(searchQuery.toLowerCase())
33      ),
34      playlists: mockPlaylists.filter(p =>
35        p.name.toLowerCase().includes(searchQuery.toLowerCase())
36      )
37    });
38  };
39
40  return (
41    <div className="min-h-screen bg-black text-white p-8">
42      {/* Search Input */}
43      <div className="mb-6">
44        <div className="relative max-w-md">
45          <input
46            type="text"
47            value={query}
48            onChange={(e) => setQuery(e.target.value)}
49            placeholder="What do you want to listen to?"
50            className="w-full bg-gray-800 text-white px-12 py-3 rounded-full outline-none focus:ring-2 focus:ring-white"
51            autoFocus
52          />
53          <span className="absolute left-4 top-1/2 transform -translate-y-1/2 text-xl">
54            🔍
55          </span>
56        </div>
57      </div>
58
59      {!query ? (
60        // Browse All / Recent Searches
61        <div>
62          <h2 className="text-2xl font-bold mb-4">Browse All</h2>
63          <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
64            {['Sea Shanties', 'Battle Anthems', 'Treasure Hunting', 'Calm Seas', 'Epic Adventures', 'Ship Repairs'].map(genre => (
65              <button
66                key={genre}
67                className="aspect-square bg-gradient-to-br from-purple-600 to-pink-600 rounded-lg p-4 font-bold text-lg hover:scale-105 transition"
68              >
69                {genre}
70              </button>
71            ))}
72          </div>
73        </div>
74      ) : results && (
75        <>
76          {/* Tabs */}
77          <div className="flex gap-4 mb-6 border-b border-gray-800">
78            {[
79              { key: 'all', label: 'All' },
80              { key: 'tracks', label: 'Songs' },
81              { key: 'albums', label: 'Albums' },
82              { key: 'artists', label: 'Artists' },
83              { key: 'playlists', label: 'Playlists' }
84            ].map(tab => (
85              <button
86                key={tab.key}
87                onClick={() => setActiveTab(tab.key as any)}
88                className={`pb-2 px-4 font-semibold ${
89                  activeTab === tab.key
90                    ? 'border-b-2 border-green-500'
91                    : 'text-gray-400'
92                }`}
93              >
94                {tab.label}
95              </button>
96            ))}
97          </div>
98
99          {/* Results */}
100          <div className="space-y-6">
101            {(activeTab === 'all' || activeTab === 'tracks') && results.tracks.length > 0 && (
102              <div>
103                <h3 className="text-xl font-bold mb-4">Songs</h3>
104                <div className="space-y-2">
105                  {results.tracks.slice(0, activeTab === 'all' ? 4 : undefined).map(track => (
106                    <div
107                      key={track.id}
108                      className="flex items-center gap-4 p-2 rounded hover:bg-gray-800 cursor-pointer"
109                    >
110                      <img
111                        src={track.album.coverArt}
112                        alt={track.title}
113                        className="w-12 h-12 rounded"
114                      />
115                      <div className="flex-1">
116                        <div className="font-semibold">{track.title}</div>
117                        <div className="text-sm text-gray-400">
118                          {track.artist.name}
119                        </div>
120                      </div>
121                      <span className="text-sm text-gray-400">
122                        {Math.floor(track.duration / 60)}:{(track.duration % 60).toString().padStart(2, '0')}
123                      </span>
124                    </div>
125                  ))}
126                </div>
127              </div>
128            )}
129
130            {(activeTab === 'all' || activeTab === 'artists') && results.artists.length > 0 && (
131              <div>
132                <h3 className="text-xl font-bold mb-4">Artists</h3>
133                <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
134                  {results.artists.slice(0, activeTab === 'all' ? 4 : undefined).map(artist => (
135                    <div
136                      key={artist.id}
137                      className="bg-gray-900 p-4 rounded hover:bg-gray-800 cursor-pointer"
138                    >
139                      <img
140                        src={artist.avatar}
141                        alt={artist.name}
142                        className="w-full aspect-square rounded-full mb-4"
143                      />
144                      <div className="font-semibold">{artist.name}</div>
145                      <div className="text-sm text-gray-400">Artist</div>
146                    </div>
147                  ))}
148                </div>
149              </div>
150            )}
151          </div>
152        </>
153      )}
154    </div>
155  );
156}

Recommendations System

1interface RecommendationEngine {
2  // Based on listening history
3  discoverWeekly: (userId: string) => Playlist;
4
5  // New releases from followed artists
6  releaseRadar: (userId: string) => Playlist;
7
8  // Based on specific seed
9  radioStation: (seed: Track | Artist | Genre) => Track[];
10
11  // Similar to a track
12  similarTracks: (trackId: string, limit?: number) => Track[];
13}
14
15// Discover Weekly - personalized playlist based on listening history
16function generateDiscoverWeekly(
17  listeningHistory: Track[],
18  likedTracks: Track[],
19  followedArtists: Artist[]
20): Playlist {
21  // Algorithm:
22  // 1. Analyze user's taste (genres, artists, audio features)
23  // 2. Find similar tracks user hasn't heard
24  // 3. Weight by popularity and recency
25  // 4. Limit to 30 tracks
26
27  return {
28    id: 'discover-weekly',
29    name: 'Discover Weekly',
30    description: 'Your weekly mixtape of fresh music',
31    coverImage: '/discover-weekly.jpg',
32    owner: { id: 'spotify', username: 'Spotify', avatar: '/spotify.png' },
33    tracks: [...], // 30 recommended tracks
34    followers: 0,
35    isPublic: false,
36    isCollaborative: false,
37    totalDuration: 0,
38    createdAt: new Date(),
39    updatedAt: new Date()
40  };
41}

Summary 🎓

Search system - tracks, albums, artists, playlists ✅ Debounced search - 300ms delay for performance ✅ Search tabs - filter results by type ✅ Browse categories - genre cards ✅ Recent searches - history tracking ✅ Discover Weekly - personalized recommendations ✅ Release Radar - new music from followed artists ✅ Radio station - endless playback based on seed ✅ Similar tracks - recommendation algorithm

You now have a complete Spotify clone with player, playlists, search, and recommendations! 🎵

See you soon! 🚀

Go to CodeWorlds