Feat: Enrich Home Page with simulated content categories

This commit is contained in:
Your Name 2026-01-01 13:59:24 +07:00
parent b67dbe577d
commit 87d53f06da
5 changed files with 120 additions and 44 deletions

View file

@ -276,16 +276,97 @@ class YouTubeService:
# We'll use get_charts "trending" for "Trending" category
# And maybe "Top Songs" for "Top Hits"
# 1. Trending (from Charts)
trending_playlist = {
"id": "trending",
"title": "Trending Now",
"description": "Top music videos right now",
"cover_url": trending_songs[0]['cover_url'] if trending_songs else "",
"tracks": trending_songs,
"type": "Playlist",
"creator": "YouTube Charts"
}
# 2. Top Hits (Simulated via search)
# We'll fetch a few "standard" playlists or results to populate the home page
# This makes the app feel "alive" even without user history
async def get_search_shelf(query, title):
try:
res = self.search(query)
if res and 'tracks' in res:
return {
"id": f"shelf_{query}",
"title": title,
"description": f"Best of {title}",
"cover_url": res['tracks'][0]['cover_url'] if res['tracks'] else "",
"tracks": res['tracks'],
"type": "Playlist",
"creator": "Spotify Clone"
}
except:
return None
# Since this is synchronous, we'll do simple searches or use cached results
# For speed, we might want to hardcode IDs of popular playlists in the future
# But for now, let's just reuse the trending videos for a "Top Hits" section to fill space
# and maybe shuffle them or pick different slice
import random
top_hits_tracks = list(trending_songs)
if len(top_hits_tracks) > 5:
random.shuffle(top_hits_tracks)
top_hits_playlist = {
"id": "top_hits",
"title": "Top Hits Today",
"description": "The hottest tracks right now.",
"cover_url": top_hits_tracks[0]['cover_url'] if top_hits_tracks else "",
"tracks": top_hits_tracks,
"type": "Playlist",
"creator": "Editors"
}
# 3. New Releases (Simulated)
new_releases_tracks = list(trending_songs)
if len(new_releases_tracks) > 2:
# Just rotate them to look different
new_releases_tracks = new_releases_tracks[2:] + new_releases_tracks[:2]
new_releases_playlist = {
"id": "new_releases",
"title": "New Releases",
"description": "Brand new music found for you.",
"cover_url": new_releases_tracks[0]['cover_url'] if new_releases_tracks else "",
"tracks": new_releases_tracks,
"type": "Playlist",
"creator": "Spotify Clone"
}
response = {
"Trending": [{
"id": "trending",
"title": "Trending Now",
"description": "Top music videos right now",
"cover_url": trending_songs[0]['cover_url'] if trending_songs else "",
"tracks": trending_songs,
"type": "Playlist",
"creator": "YouTube Charts"
}]
"Trending": [trending_playlist],
"Top Hits": [top_hits_playlist],
"New Releases": [new_releases_playlist],
"Focus & Chill": [
{
"id": "lofi_beats",
"title": "Lofi Beats",
"description": "Chill beats to study/relax to",
"cover_url": "https://i.ytimg.com/vi/jfKfPfyJRdk/hqdefault.jpg",
"tracks": [], # Empty tracks will force a fetch when clicked if handled
"type": "Playlist",
"creator": "Lofi Girl"
},
{
"id": "jazz_vibes",
"title": "Jazz Vibes",
"description": "Relaxing Jazz instrumental",
"cover_url": "https://i.ytimg.com/vi/DX7W7WUI6w8/hqdefault.jpg",
"tracks": [],
"type": "Playlist",
"creator": "Jazz Cafe"
}
]
}
self.cache.set(cache_key, response, ttl_seconds=3600)

View file

@ -9,11 +9,3 @@ services:
volumes:
- ./data:/app/backend/data
watchtower:
image: containrrr/watchtower
container_name: watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 3600 --cleanup --label-enable=false spotify-clone # Checks every hour for 'spotify-clone' container
restart: always

View file

@ -76,7 +76,7 @@ export default function LibraryPage() {
{playlists.map((playlist) => (
<Link href={`/playlist?id=${playlist.id}`} key={playlist.id}>
<div className="bg-[#181818] p-2 md:p-3 rounded-md hover:bg-[#282828] transition aspect-[3/4] flex flex-col">
<div className="aspect-square w-full mb-2 md:mb-3 overflow-hidden rounded-md shadow-lg">
<div className="aspect-square w-full mb-1 md:mb-3 overflow-hidden rounded-md shadow-lg">
<CoverImage
src={playlist.cover_url}
alt={playlist.title}
@ -84,8 +84,8 @@ export default function LibraryPage() {
fallbackText={playlist.title?.substring(0, 2).toUpperCase()}
/>
</div>
<h3 className="text-white font-bold text-xs md:text-sm truncate">{playlist.title}</h3>
<p className="text-[#a7a7a7] text-[10px] md:text-xs">Playlist You</p>
<h3 className="text-white font-bold text-[10px] md:text-sm truncate w-full">{playlist.title}</h3>
<p className="text-[#a7a7a7] text-[9px] md:text-xs truncate w-full">Playlist You</p>
</div>
</Link>
))}
@ -93,7 +93,7 @@ export default function LibraryPage() {
{browsePlaylists.map((playlist) => (
<Link href={`/playlist?id=${playlist.id}`} key={playlist.id}>
<div className="bg-[#181818] p-2 md:p-3 rounded-md hover:bg-[#282828] transition aspect-[3/4] flex flex-col">
<div className="aspect-square w-full mb-2 md:mb-3 overflow-hidden rounded-md shadow-lg">
<div className="aspect-square w-full mb-1 md:mb-3 overflow-hidden rounded-md shadow-lg">
<CoverImage
src={playlist.cover_url}
alt={playlist.title}
@ -101,8 +101,8 @@ export default function LibraryPage() {
fallbackText={playlist.title?.substring(0, 2).toUpperCase()}
/>
</div>
<h3 className="text-white font-bold text-xs md:text-sm truncate">{playlist.title}</h3>
<p className="text-[#a7a7a7] text-[10px] md:text-xs">Playlist Made for you</p>
<h3 className="text-white font-bold text-[10px] md:text-sm truncate w-full">{playlist.title}</h3>
<p className="text-[#a7a7a7] text-[9px] md:text-xs truncate w-full">Playlist Made for you</p>
</div>
</Link>
))}
@ -113,7 +113,7 @@ export default function LibraryPage() {
{showArtists && artists.map((artist) => (
<Link href={`/artist?name=${encodeURIComponent(artist.title)}`} key={artist.id}>
<div className="bg-[#181818] p-2 md:p-3 rounded-md hover:bg-[#282828] transition aspect-[3/4] flex flex-col items-center text-center">
<div className="aspect-square w-full mb-2 md:mb-3 overflow-hidden rounded-full shadow-lg">
<div className="aspect-square w-full mb-1 md:mb-3 overflow-hidden rounded-full shadow-lg">
<CoverImage
src={artist.cover_url}
alt={artist.title}
@ -121,8 +121,8 @@ export default function LibraryPage() {
fallbackText={artist.title?.substring(0, 2).toUpperCase()}
/>
</div>
<h3 className="text-white font-bold text-xs md:text-sm truncate w-full">{artist.title}</h3>
<p className="text-[#a7a7a7] text-[10px] md:text-xs">Artist</p>
<h3 className="text-white font-bold text-[10px] md:text-sm truncate w-full">{artist.title}</h3>
<p className="text-[#a7a7a7] text-[9px] md:text-xs truncate w-full">Artist</p>
</div>
</Link>
))}
@ -131,7 +131,7 @@ export default function LibraryPage() {
{showAlbums && albums.map((album) => (
<Link href={`/playlist?id=${album.id}`} key={album.id}>
<div className="bg-[#181818] p-2 md:p-3 rounded-md hover:bg-[#282828] transition aspect-[3/4] flex flex-col">
<div className="aspect-square w-full mb-2 md:mb-3 overflow-hidden rounded-md shadow-lg">
<div className="aspect-square w-full mb-1 md:mb-3 overflow-hidden rounded-md shadow-lg">
<CoverImage
src={album.cover_url}
alt={album.title}
@ -139,8 +139,8 @@ export default function LibraryPage() {
fallbackText={album.title?.substring(0, 2).toUpperCase()}
/>
</div>
<h3 className="text-white font-bold text-xs md:text-sm truncate">{album.title}</h3>
<p className="text-[#a7a7a7] text-[10px] md:text-xs">Album {album.creator || 'Spotify'}</p>
<h3 className="text-white font-bold text-[10px] md:text-sm truncate w-full">{album.title}</h3>
<p className="text-[#a7a7a7] text-[9px] md:text-xs truncate w-full">Album {album.creator || 'Spotify'}</p>
</div>
</Link>
))}

View file

@ -183,22 +183,22 @@ export default function Home() {
<div className="grid grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6">
{sortPlaylists(playlists).slice(0, 5).map((playlist: any) => (
<Link href={`/playlist?id=${playlist.id}`} key={playlist.id}>
<div className="bg-[#181818] p-4 rounded-md hover:bg-[#282828] transition duration-300 group cursor-pointer relative h-full flex flex-col">
<div className="relative mb-4">
<div className="bg-[#181818] p-2 md:p-4 rounded-md hover:bg-[#282828] transition duration-300 group cursor-pointer relative h-full flex flex-col">
<div className="relative mb-1 md:mb-4">
<CoverImage
src={playlist.cover_url}
alt={playlist.title}
className="w-full aspect-square object-cover rounded-md shadow-lg"
fallbackText={playlist.title.substring(0, 2).toUpperCase()}
/>
<div className="absolute bottom-2 right-2 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition duration-300 shadow-xl">
<div className="absolute bottom-2 right-2 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition duration-300 shadow-xl hidden md:block">
<div className="w-12 h-12 bg-[#1DB954] rounded-full flex items-center justify-center hover:scale-105">
<Play className="fill-black text-black ml-1" />
</div>
</div>
</div>
<h3 className="font-bold mb-1 truncate">{playlist.title}</h3>
<p className="text-sm text-[#a7a7a7] line-clamp-2">{playlist.description}</p>
<h3 className="font-bold mb-0.5 md:mb-1 truncate text-[10px] md:text-base">{playlist.title}</h3>
<p className="text-[9px] md:text-sm text-[#a7a7a7] line-clamp-2">{playlist.description}</p>
</div>
</Link>
))}
@ -316,22 +316,22 @@ function MadeForYouSection() {
) : (
<div className="grid grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6">
{recommendations.slice(0, 5).map((track, i) => (
<div key={i} onClick={() => playTrack(track, recommendations)} className="bg-[#181818] p-4 rounded-md hover:bg-[#282828] transition duration-300 group cursor-pointer relative h-full flex flex-col">
<div className="relative mb-4">
<div key={i} onClick={() => playTrack(track, recommendations)} className="bg-[#181818] p-2 md:p-4 rounded-md hover:bg-[#282828] transition duration-300 group cursor-pointer relative h-full flex flex-col">
<div className="relative mb-1 md:mb-4">
<CoverImage
src={track.cover_url}
alt={track.title}
className="w-full aspect-square object-cover rounded-md shadow-lg"
fallbackText={track.title?.substring(0, 2).toUpperCase()}
/>
<div className="absolute bottom-2 right-2 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition duration-300 shadow-xl">
<div className="absolute bottom-2 right-2 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition duration-300 shadow-xl hidden md:block">
<div className="w-12 h-12 bg-[#1DB954] rounded-full flex items-center justify-center hover:scale-105">
<Play className="fill-black text-black ml-1" />
</div>
</div>
</div>
<h3 className="font-bold mb-1 truncate">{track.title}</h3>
<p className="text-sm text-[#a7a7a7] line-clamp-2">{track.artist}</p>
<h3 className="font-bold mb-0.5 md:mb-1 truncate text-[10px] md:text-base">{track.title}</h3>
<p className="text-[9px] md:text-sm text-[#a7a7a7] line-clamp-2">{track.artist}</p>
</div>
))}
</div>
@ -391,21 +391,21 @@ function RecommendedAlbumsSection() {
<div className="grid grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 md:gap-6">
{albums.slice(0, 5).map((album, i) => (
<Link href={`/playlist?id=${album.id}`} key={i}>
<div className="bg-[#181818] p-4 rounded-md hover:bg-[#282828] transition duration-300 group cursor-pointer relative h-full flex flex-col">
<div className="relative mb-4">
<div className="bg-[#181818] p-2 md:p-4 rounded-md hover:bg-[#282828] transition duration-300 group cursor-pointer relative h-full flex flex-col">
<div className="relative mb-1 md:mb-4">
<CoverImage
src={album.cover_url}
alt={album.title}
className="w-full aspect-square object-cover rounded-md shadow-lg"
/>
<div className="absolute bottom-2 right-2 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition duration-300 shadow-xl">
<div className="absolute bottom-2 right-2 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition duration-300 shadow-xl hidden md:block">
<div className="w-12 h-12 bg-[#1DB954] rounded-full flex items-center justify-center hover:scale-105">
<Play className="fill-black text-black ml-1" />
</div>
</div>
</div>
<h3 className="font-bold mb-1 truncate">{album.title}</h3>
<p className="text-sm text-[#a7a7a7] line-clamp-2">{album.description}</p>
<h3 className="font-bold mb-0.5 md:mb-1 truncate text-[10px] md:text-base">{album.title}</h3>
<p className="text-[9px] md:text-sm text-[#a7a7a7] line-clamp-2">{album.description}</p>
</div>
</Link>
))}

View file

@ -21,7 +21,10 @@ const nextConfig = {
{ source: '/api/download-status/:path*', destination: 'http://127.0.0.1:8000/api/download-status/:path*' },
{ source: '/api/lyrics/:path*', destination: 'http://127.0.0.1:8000/api/lyrics/:path*' },
{ source: '/api/trending/:path*', destination: 'http://127.0.0.1:8000/api/trending/:path*' },
{ source: '/api/settings/:path*', destination: 'http://127.0.0.1:8000/api/settings/:path*' },
{ source: '/api/recommendations/:path*', destination: 'http://127.0.0.1:8000/api/recommendations/:path*' },
// Catch-all for other new endpoints
{ source: '/api/:path*', destination: 'http://127.0.0.1:8000/api/:path*' },
];
},
images: {