import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import axiosInstance from "../api/axiosInstance"; import { useAuth } from "../context/authContext"; import { usePlayer } from "../context/playerContext"; import type { SongDetail as SongDetailType } from "../utils/types"; const ROLE_LABELS: Record = { MAIN_VOCAL: "Main Vocal", FEATURED: "Featured", PRODUCER: "Producer", SONGWRITER: "Songwriter", COMPOSER: "Composer", MIXER: "Mixer", ENGINEER: "Engineer", }; const formatRole = (role: string): string => { return ROLE_LABELS[role] || role.replace(/_/g, " "); }; // convert a regular youtube URL to an embeddable URL const toEmbedUrl = (url: string): string => { try { const parsed = new URL(url); // youtube.com/watch?v=ID if ( (parsed.hostname === "www.youtube.com" || parsed.hostname === "youtube.com") && parsed.searchParams.has("v") ) { return `https://www.youtube.com/embed/${parsed.searchParams.get("v")}`; } // youtu.be/ID if (parsed.hostname === "youtu.be") { return `https://www.youtube.com/embed${parsed.pathname}`; } // already an embed URL or other provider – return as-is return url; } catch { return url; } }; const renderStars = (grade: number) => { return (
{[1, 2, 3, 4, 5].map((star) => ( ))}
); }; const SongDetail = () => { const { id } = useParams<{ id: string }>(); const { user } = useAuth(); const { play, currentSong } = usePlayer(); const [song, setSong] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showReviewModal, setShowReviewModal] = useState(false); const [reviewRating, setReviewRating] = useState(0); const [reviewComment, setReviewComment] = useState(""); const [hoverRating, setHoverRating] = useState(0); console.log(user); useEffect(() => { const fetchSong = async () => { try { setLoading(true); const response = await axiosInstance.get(`/songs/${id}/details`); setSong(response.data); // Don't autoplay - user must click play button } catch (err) { console.error("Error fetching song details:", err); setError("Failed to load song details."); } finally { setLoading(false); } }; if (id) fetchSong(); }, [id]); const handleSubmitReview = async () => { if (reviewRating === 0) { // todo: replace with toast alert("Please select a rating"); return; } try { await axiosInstance.post(`reviews/${song?.id}`, { grade: reviewRating, comment: reviewComment, }); const response = await axiosInstance.get(`/songs/${id}/details`); setSong(response.data); } catch (err) { // todo: replace with toast console.error("Error submitting review:", err); } finally { setShowReviewModal(false); setReviewRating(0); setReviewComment(""); setHoverRating(0); } }; const deleteReview = async () => { try { await axiosInstance.delete(`reviews/${song?.id}`); const response = await axiosInstance.get(`/songs/${id}/details`); setSong(response.data); } catch (err) { console.error("Error deleting review:", err); } }; const toggleLike = async () => { if (!song) return; try { await axiosInstance.post(`/musical-entity/${song.id}/like`); setSong((prev) => prev ? { ...prev, isLikedByCurrentUser: !prev.isLikedByCurrentUser } : prev, ); } catch (err) { console.error("Error toggling like:", err); } }; if (loading) { return (

Loading song…

); } if (error || !song) { return (

{error ?? "Song not found"}

← Back to Home
); } const otherContributors = song.contributions.filter( (c) => c.artistName !== song.releasedBy, ); const avgRating = song.reviews.length > 0 ? ( song.reviews.reduce((sum, r) => sum + r.grade, 0) / song.reviews.length ).toFixed(1) : null; return (
{/* Back link */} Back to Home {/* Hero section */}
{/* Cover art */}
{song.title} { (e.target as HTMLImageElement).src = "/favicon.png"; }} />
{/* Song info */}
{song.genre} • Song

{song.title}

{/* Main artist */}

{song.releasedBy}

{/* Album */} {song.album && (

Album: {song.album}

)} {/* Rating summary */} {avgRating && (
{renderStars(Math.round(Number(avgRating)))} {avgRating} · {song.reviews.length}{" "} {song.reviews.length === 1 ? "review" : "reviews"}
)} {/* Action buttons */}
{/* Play button */} {song.link && ( )} {user && ( )}
{/* Credits / Contributions */} {song.contributions.length > 0 && (

Credits

{/* Main artist first */} {song.contributions .filter((c) => c.artistName === song.releasedBy) .map((c, i) => (
{c.artistName.charAt(0).toUpperCase()}

{c.artistName}

{formatRole(c.role)}
))} {/* Other contributors */} {otherContributors.map((c, i) => (
{c.artistName.charAt(0).toUpperCase()}

{c.artistName}

{formatRole(c.role)}
))}
)} {/* Reviews */}

Reviews {song.reviews.length > 0 && ( ({song.reviews.length}) )}

{song.reviews.length === 0 ? (

No reviews yet.

Be the first to share your thoughts!

) : (
{song.reviews.map((review) => (
{review.author.charAt(0).toUpperCase()}

{review.author}

{renderStars(review.grade)}
{user?.username === review.authorUsername && ( )}

{review.comment}

))}
)}
{/* Review Modal */} {showReviewModal && (

Add Review

{/* Star Rating */}
{[1, 2, 3, 4, 5].map((star) => ( ))}
{/* Comment */}