fix: duplicate React keys, update meta tags, improve mobile card layout

This commit is contained in:
Khoa Vo 2026-01-01 15:16:49 +07:00
parent 35876ee046
commit 50248cf165
3 changed files with 35 additions and 25 deletions

View file

@ -24,6 +24,9 @@ export const metadata: Metadata = {
statusBarStyle: "black-translucent",
title: "Audiophile Web Player",
},
other: {
"mobile-web-app-capable": "yes",
},
icons: {
icon: "/icons/icon-192x192.png",
apple: "/icons/icon-512x512.png",

View file

@ -180,25 +180,25 @@ export default function Home() {
</Link>
</div>
<div className="grid grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6">
<div className="grid grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2 md: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-2 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="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 className="absolute bottom-1 right-1 md:bottom-2 md:right-2 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition duration-300 shadow-xl">
<div className="w-8 h-8 md:w-12 md:h-12 bg-[#1DB954] rounded-full flex items-center justify-center hover:scale-105">
<Play className="fill-black text-black ml-0.5 w-4 h-4 md:w-6 md:h-6" />
</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-xs md:text-base">{playlist.title}</h3>
<p className="text-[10px] md:text-sm text-[#a7a7a7] line-clamp-2">{playlist.description}</p>
</div>
</Link>
))}
@ -314,24 +314,24 @@ function MadeForYouSection() {
))}
</div>
) : (
<div className="grid grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6">
<div className="grid grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2 md: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-2 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="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 className="absolute bottom-1 right-1 md:bottom-2 md:right-2 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition duration-300 shadow-xl">
<div className="w-8 h-8 md:w-12 md:h-12 bg-[#1DB954] rounded-full flex items-center justify-center hover:scale-105">
<Play className="fill-black text-black ml-0.5 w-4 h-4 md:w-6 md:h-6" />
</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-xs md:text-base">{track.title}</h3>
<p className="text-[10px] md:text-sm text-[#a7a7a7] line-clamp-2">{track.artist}</p>
</div>
))}
</div>
@ -388,24 +388,24 @@ function RecommendedAlbumsSection() {
))}
</div>
) : (
<div className="grid grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 md:gap-6">
<div className="grid grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2 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-2 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="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 className="absolute bottom-1 right-1 md:bottom-2 md:right-2 translate-y-4 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition duration-300 shadow-xl">
<div className="w-8 h-8 md:w-12 md:h-12 bg-[#1DB954] rounded-full flex items-center justify-center hover:scale-105">
<Play className="fill-black text-black ml-0.5 w-4 h-4 md:w-6 md:h-6" />
</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-xs md:text-base">{album.title}</h3>
<p className="text-[10px] md:text-sm text-[#a7a7a7] line-clamp-2">{album.description}</p>
</div>
</Link>
))}

View file

@ -30,7 +30,14 @@ export function LibraryProvider({ children }: { children: React.ReactNode }) {
// 2. Local/Backend Content
const browse = await libraryService.getBrowseContent();
const browsePlaylists = Object.values(browse).flat();
// Deduplicate by ID to avoid React duplicate key warnings
const browsePlaylistsRaw = Object.values(browse).flat();
const seenIds = new Map();
const browsePlaylists = browsePlaylistsRaw.filter((p: any) => {
if (seenIds.has(p.id)) return false;
seenIds.set(p.id, true);
return true;
});
const artistsMap = new Map();
const albumsMap = new Map();