From 5a8764df05ab907806b5e13b868919285a654669 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Sun, 21 Dec 2025 14:06:37 -0800 Subject: [PATCH] feat: Add Heady Version Leaderboard to Song Page (Phase 4-5) - YouTube embed of #1 rated performance - Top 5 performances ranked with medal icons - Rating + rating count display - YouTube link icons for each performance - Gradient gold styling for heady section --- frontend/app/songs/[id]/page.tsx | 98 +++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/frontend/app/songs/[id]/page.tsx b/frontend/app/songs/[id]/page.tsx index a00c125..049f8b2 100644 --- a/frontend/app/songs/[id]/page.tsx +++ b/frontend/app/songs/[id]/page.tsx @@ -1,7 +1,7 @@ import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { ArrowLeft, PlayCircle, History, Calendar } from "lucide-react" +import { ArrowLeft, PlayCircle, History, Calendar, Trophy, Youtube, Star } from "lucide-react" import Link from "next/link" import { notFound } from "next/navigation" import { Badge } from "@/components/ui/badge" @@ -12,6 +12,7 @@ import { EntityReviews } from "@/components/reviews/entity-reviews" import { SocialWrapper } from "@/components/social/social-wrapper" import { PerformanceList } from "@/components/songs/performance-list" import { SongEvolutionChart } from "@/components/songs/song-evolution-chart" +import { YouTubeEmbed } from "@/components/ui/youtube-embed" async function getSong(id: string) { try { @@ -24,6 +25,15 @@ async function getSong(id: string) { } } +// Get top rated performances for "Heady Version" leaderboard +function getHeadyVersions(performances: any[]) { + if (!performances || performances.length === 0) return [] + return [...performances] + .filter(p => p.avg_rating && p.rating_count > 0) + .sort((a, b) => b.avg_rating - a.avg_rating) + .slice(0, 5) +} + export default async function SongDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params const song = await getSong(id) @@ -32,6 +42,9 @@ export default async function SongDetailPage({ params }: { params: Promise<{ id: notFound() } + const headyVersions = getHeadyVersions(song.performances || []) + const topPerformance = headyVersions[0] + return (
@@ -100,6 +113,89 @@ export default async function SongDetailPage({ params }: { params: Promise<{ id:
+ {/* Heady Version Section */} + {headyVersions.length > 0 && ( + + + + + Heady Version Leaderboard + + + + {/* Top Performance with YouTube */} + {topPerformance && ( +
+ {topPerformance.youtube_link ? ( + + ) : song.youtube_link ? ( + + ) : ( +
+
+ +

No video available

+
+
+ )} +
+
+ 🏆 #1 Heady +
+

+ {topPerformance.show?.date ? new Date(topPerformance.show.date).toLocaleDateString() : "Unknown Date"} +

+

+ {topPerformance.show?.venue?.name || "Unknown Venue"} +

+
+ + {topPerformance.avg_rating?.toFixed(1)} + ({topPerformance.rating_count} ratings) +
+
+
+ )} + + {/* Leaderboard List */} +
+ {headyVersions.map((perf, index) => ( +
+
+ + {index === 0 ? "🥇" : index === 1 ? "🥈" : index === 2 ? "🥉" : `${index + 1}.`} + +
+

+ {perf.show?.date ? new Date(perf.show.date).toLocaleDateString() : "Unknown"} +

+

+ {perf.show?.venue?.name || "Unknown Venue"} +

+
+
+
+ {perf.youtube_link && ( + + + + )} +
+ {perf.avg_rating?.toFixed(1)}★ + ({perf.rating_count}) +
+
+
+ ))} +
+
+
+ )} + {/* Performance List Component (Handles Client Sorting) */}