Ignore:
Timestamp:
02/15/26 00:39:55 (5 months ago)
Author:
Dimitar Arsov <dimitararsov04@…>
Branches:
main
Children:
c8baad1
Parents:
85512ff
Message:

add song to playlist

File:
1 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/pages/SongDetail.tsx

    r85512ff rce45c7a  
    1 import { useEffect, useRef, useState } from "react";
     1import { useEffect, useState } from "react";
    22import { Link, useParams } from "react-router-dom";
    33import { toast } from "react-toastify";
    44import axiosInstance, { baseURL } from "../api/axiosInstance";
     5import PlaylistDropdown from "../components/PlaylistDropdown";
    56import { useAuth } from "../context/authContext";
    67import { usePlayer } from "../context/playerContext";
     
    910
    1011const ROLE_LABELS: Record<string, string> = {
    11         MAIN_VOCAL: "Main Vocal",
    12         FEATURED: "Featured",
    13         PRODUCER: "Producer",
    14         SONGWRITER: "Songwriter",
    15         COMPOSER: "Composer",
    16         MIXER: "Mixer",
    17         ENGINEER: "Engineer",
     12  MAIN_VOCAL: "Main Vocal",
     13  FEATURED: "Featured",
     14  PRODUCER: "Producer",
     15  SONGWRITER: "Songwriter",
     16  COMPOSER: "Composer",
     17  MIXER: "Mixer",
     18  ENGINEER: "Engineer",
    1819};
    1920
    2021const formatRole = (role: string): string => {
    21         return ROLE_LABELS[role] || role.replace(/_/g, " ");
     22  return ROLE_LABELS[role] || role.replace(/_/g, " ");
    2223};
    2324
    2425const renderStars = (grade: number) => {
    25         return (
    26                 <div className="flex gap-0.5">
    27                         {[1, 2, 3, 4, 5].map((star) => (
    28                                 <svg
    29                                         key={star}
    30                                         className={`w-4 h-4 ${star <= grade ? "text-yellow-400" : "text-gray-600"}`}
    31                                         fill="currentColor"
    32                                         viewBox="0 0 20 20"
    33                                 >
    34                                         <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
    35                                 </svg>
    36                         ))}
    37                 </div>
    38         );
     26  return (
     27    <div className="flex gap-0.5">
     28      {[1, 2, 3, 4, 5].map((star) => (
     29        <svg
     30          key={star}
     31          className={`w-4 h-4 ${star <= grade ? "text-yellow-400" : "text-gray-600"}`}
     32          fill="currentColor"
     33          viewBox="0 0 20 20"
     34        >
     35          <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
     36        </svg>
     37      ))}
     38    </div>
     39  );
    3940};
    4041
    4142const SongDetail = () => {
    42         const { id } = useParams<{ id: string }>();
    43         const { user } = useAuth();
    44         const { play, currentSong } = usePlayer();
    45         const [song, setSong] = useState<SongDetailType | null>(null);
    46         const [loading, setLoading] = useState(true);
    47         const [error, setError] = useState<string | null>(null);
    48         const [showReviewModal, setShowReviewModal] = useState(false);
    49         const [reviewRating, setReviewRating] = useState(0);
    50         const [reviewComment, setReviewComment] = useState("");
    51         const [hoverRating, setHoverRating] = useState(0);
    52         const [showPlaylistDropdown, setShowPlaylistDropdown] = useState(false);
    53         const playlistDropdownRef = useRef<HTMLDivElement>(null);
    54         console.log(user);
    55         useEffect(() => {
    56                 const fetchSong = async () => {
    57                         try {
    58                                 setLoading(true);
    59                                 const response = await axiosInstance.get(`/songs/${id}/details`);
    60                                 setSong(response.data);
    61                         } catch (err) {
    62                                 console.error("Error fetching song details:", err);
    63                                 setError("Failed to load song details.");
    64                         } finally {
    65                                 setLoading(false);
    66                         }
    67                 };
    68                 if (id) fetchSong();
    69         }, [id]);
    70 
    71         // handle click outside for playlist dropdown
    72         useEffect(() => {
    73                 const handleClickOutside = (event: MouseEvent) => {
    74                         if (
    75                                 playlistDropdownRef.current &&
    76                                 !playlistDropdownRef.current.contains(event.target as Node)
    77                         ) {
    78                                 setShowPlaylistDropdown(false);
    79                         }
    80                 };
    81 
    82                 document.addEventListener("mousedown", handleClickOutside);
    83                 return () => {
    84                         document.removeEventListener("mousedown", handleClickOutside);
    85                 };
    86         }, []);
    87 
    88         const handleSubmitReview = async () => {
    89                 if (reviewRating === 0) {
    90                         // todo: replace with toast
    91                         alert("Please select a rating");
    92                         return;
    93                 }
    94                 try {
    95                         await axiosInstance.post(`reviews/${song?.id}`, {
    96                                 grade: reviewRating,
    97                                 comment: reviewComment,
    98                         });
    99                         const response = await axiosInstance.get(`/songs/${id}/details`);
    100                         setSong(response.data);
    101                 } catch (err) {
    102                         // todo: replace with toast
    103                         console.error("Error submitting review:", err);
    104                 } finally {
    105                         setShowReviewModal(false);
    106                         setReviewRating(0);
    107                         setReviewComment("");
    108                         setHoverRating(0);
    109                 }
    110         };
    111 
    112         const deleteReview = async () => {
    113                 try {
    114                         await axiosInstance.delete(`reviews/${song?.id}`);
    115                         const response = await axiosInstance.get(`/songs/${id}/details`);
    116                         setSong(response.data);
    117                 } catch (err) {
    118                         toast.error("Failed to delete review");
    119                         console.error("Error deleting review:", err);
    120                 }
    121         };
    122 
    123         const toggleLike = async () => {
    124                 if (!song) return;
    125                 try {
    126                         await axiosInstance.post(`/musical-entity/${song.id}/like`);
    127                         setSong((prev) =>
    128                                 prev
    129                                         ? { ...prev, isLikedByCurrentUser: !prev.isLikedByCurrentUser }
    130                                         : prev,
    131                         );
    132                 } catch (err) {
    133                         toast.error("Failed to toggle like");
    134                         console.error("Error toggling like:", err);
    135                 }
    136         };
    137 
    138         const handleAddToPlaylist = (playlistName: string) => {
    139                 console.log(`Adding song ${song?.id} to ${playlistName}`);
    140                 // TODO: actual API call
    141                 setShowPlaylistDropdown(false);
    142         };
    143 
    144         const handleCreateNewPlaylist = () => {
    145                 console.log(`Creating new playlist for song ${song?.id}`);
    146                 // TODO: actual playlist creation
    147                 setShowPlaylistDropdown(false);
    148         };
    149 
    150         if (loading) {
    151                 return (
    152                         <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] flex items-center justify-center">
    153                                 <div className="flex flex-col items-center gap-4">
    154                                         <div className="w-12 h-12 border-4 border-white/10 border-t-[#1db954] rounded-full animate-spin" />
    155                                         <p className="text-gray-400 text-lg">Loading song…</p>
    156                                 </div>
    157                         </div>
    158                 );
    159         }
    160 
    161         if (error || !song) {
    162                 return (
    163                         <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] flex items-center justify-center">
    164                                 <div className="text-center">
    165                                         <p className="text-red-400 text-xl mb-4">
    166                                                 {error ?? "Song not found"}
    167                                         </p>
    168                                         <Link to="/" className="text-[#1db954] hover:underline text-sm">
    169                                                 ← Back to Home
    170                                         </Link>
    171                                 </div>
    172                         </div>
    173                 );
    174         }
    175 
    176         const otherContributors = song.contributions.filter(
    177                 (c) => c.artistName !== song.releasedBy,
    178         );
    179 
    180         const avgRating =
    181                 song.reviews.length > 0
    182                         ? (
    183                                         song.reviews.reduce((sum, r) => sum + r.grade, 0) /
    184                                         song.reviews.length
    185                                 ).toFixed(1)
    186                         : null;
    187 
    188         return (
    189                 <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] text-white">
    190                         <div className="max-w-5xl mx-auto px-6 py-10">
    191                                 {/* Back link */}
    192                                 <Link
    193                                         to="/"
    194                                         className="inline-flex items-center gap-1 text-gray-400 hover:text-white text-sm mb-8 transition-colors"
    195                                 >
    196                                         <svg
    197                                                 className="w-4 h-4"
    198                                                 fill="none"
    199                                                 stroke="currentColor"
    200                                                 viewBox="0 0 24 24"
    201                                         >
    202                                                 <path
    203                                                         strokeLinecap="round"
    204                                                         strokeLinejoin="round"
    205                                                         strokeWidth={2}
    206                                                         d="M15 19l-7-7 7-7"
    207                                                 />
    208                                         </svg>
    209                                         Back to Home
    210                                 </Link>
    211 
    212                                 {/* Hero section */}
    213                                 <div className="flex flex-col md:flex-row gap-8 mb-10">
    214                                         {/* Cover art */}
    215                                         <div className="w-full md:w-72 shrink-0">
    216                                                 <div className="relative w-full pt-[100%] rounded-xl overflow-hidden shadow-2xl bg-[#181818]">
    217                                                         <img
    218                                                                 src={song.cover ? `${baseURL}/${song.cover}` : "/favicon.png"}
    219                                                                 alt={song.title}
    220                                                                 className="absolute inset-0 w-full h-full object-cover"
    221                                                                 onError={(e) => {
    222                                                                         (e.target as HTMLImageElement).src = "/favicon.png";
    223                                                                 }}
    224                                                         />
    225                                                 </div>
    226                                         </div>
    227 
    228                                         {/* Song info */}
    229                                         <div className="flex flex-col justify-end gap-3 min-w-0">
    230                                                 <span className="text-xs uppercase tracking-widest text-gray-400 font-medium">
    231                                                         {song.genre} • Song
    232                                                 </span>
    233                                                 <h1 className="text-4xl md:text-5xl font-extrabold leading-tight truncate">
    234                                                         {song.title}
    235                                                 </h1>
    236 
    237                                                 {/* Main artist */}
    238                                                 <p className="text-xl text-gray-300 font-semibold">
    239                                                         {song.releasedBy}
    240                                                 </p>
    241 
    242                                                 {/* Album */}
    243                                                 {song.album && (
    244                                                         <p className="text-sm text-gray-500">
    245                                                                 Album: <span className="text-gray-300">{song.album}</span>
    246                                                         </p>
    247                                                 )}
    248 
    249                                                 {/* Rating summary */}
    250                                                 {avgRating && (
    251                                                         <div className="flex items-center gap-2 mt-1">
    252                                                                 {renderStars(Math.round(Number(avgRating)))}
    253                                                                 <span className="text-sm text-gray-400">
    254                                                                         {avgRating} · {song.reviews.length}{" "}
    255                                                                         {song.reviews.length === 1 ? "review" : "reviews"}
    256                                                                 </span>
    257                                                         </div>
    258                                                 )}
    259 
    260                                                 {/* Action buttons */}
    261                                                 <div className="flex items-center gap-3 mt-4">
    262                                                         {/* Play button */}
    263                                                         {song.link && (
    264                                                                 <button
    265                                                                         onClick={() =>
    266                                                                                 play({
    267                                                                                         id: song.id,
    268                                                                                         title: song.title,
    269                                                                                         artist: song.releasedBy,
    270                                                                                         cover: song.cover,
    271                                                                                         embedUrl: toEmbedUrl(song.link!),
    272                                                                                 })
    273                                                                         }
    274                                                                         className={`flex items-center gap-2 px-6 py-3 rounded-full text-sm font-semibold transition-all cursor-pointer ${
    275                                                                                 currentSong?.id === song.id
    276                                                                                         ? "bg-white text-[#1db954]"
    277                                                                                         : "bg-[#1db954] text-black hover:bg-[#1ed760] hover:scale-105"
    278                                                                         }`}
    279                                                                 >
    280                                                                         {currentSong?.id === song.id ? (
    281                                                                                 <>
    282                                                                                         <svg
    283                                                                                                 className="w-5 h-5"
    284                                                                                                 fill="currentColor"
    285                                                                                                 viewBox="0 0 24 24"
    286                                                                                         >
    287                                                                                                 <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
    288                                                                                         </svg>
    289                                                                                         Now Playing
    290                                                                                 </>
    291                                                                         ) : (
    292                                                                                 <>
    293                                                                                         <svg
    294                                                                                                 className="w-5 h-5"
    295                                                                                                 fill="currentColor"
    296                                                                                                 viewBox="0 0 24 24"
    297                                                                                         >
    298                                                                                                 <path d="M8 5v14l11-7z" />
    299                                                                                         </svg>
    300                                                                                         Play Song
    301                                                                                 </>
    302                                                                         )}
    303                                                                 </button>
    304                                                         )}
    305 
    306                                                         {user && (
    307                                                                 <>
    308                                                                         <button
    309                                                                                 onClick={toggleLike}
    310                                                                                 className={`flex items-center gap-2 px-5 py-3 rounded-full text-sm font-semibold transition-colors cursor-pointer ${
    311                                                                                         song.isLikedByCurrentUser
    312                                                                                                 ? "bg-[#1db954] text-black"
    313                                                                                                 : "bg-white/10 text-white hover:bg-white/20"
    314                                                                                 }`}
    315                                                                         >
    316                                                                                 {song.isLikedByCurrentUser ? (
    317                                                                                         <svg
    318                                                                                                 className="w-5 h-5"
    319                                                                                                 fill="currentColor"
    320                                                                                                 viewBox="0 0 24 24"
    321                                                                                         >
    322                                                                                                 <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
    323                                                                                         </svg>
    324                                                                                 ) : (
    325                                                                                         <svg
    326                                                                                                 className="w-5 h-5"
    327                                                                                                 fill="none"
    328                                                                                                 stroke="currentColor"
    329                                                                                                 viewBox="0 0 24 24"
    330                                                                                         >
    331                                                                                                 <path
    332                                                                                                         strokeLinecap="round"
    333                                                                                                         strokeLinejoin="round"
    334                                                                                                         strokeWidth={2}
    335                                                                                                         d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
    336                                                                                                 />
    337                                                                                         </svg>
    338                                                                                 )}
    339                                                                                 {song.isLikedByCurrentUser ? "Liked" : "Like"}
    340                                                                         </button>
    341 
    342                                                                         {/* Add to Playlist button */}
    343                                                                         <div className="relative" ref={playlistDropdownRef}>
    344                                                                                 <button
    345                                                                                         onClick={() =>
    346                                                                                                 setShowPlaylistDropdown(!showPlaylistDropdown)
    347                                                                                         }
    348                                                                                         className="flex items-center gap-2 px-5 py-3 rounded-full text-sm font-semibold bg-white/10 text-white hover:bg-white/20 transition-colors cursor-pointer"
    349                                                                                 >
    350                                                                                         <svg
    351                                                                                                 className="w-5 h-5"
    352                                                                                                 fill="none"
    353                                                                                                 stroke="currentColor"
    354                                                                                                 viewBox="0 0 24 24"
    355                                                                                         >
    356                                                                                                 <path
    357                                                                                                         strokeLinecap="round"
    358                                                                                                         strokeLinejoin="round"
    359                                                                                                         strokeWidth={2}
    360                                                                                                         d="M12 4v16m8-8H4"
    361                                                                                                 />
    362                                                                                         </svg>
    363                                                                                         Add to Playlist
    364                                                                                 </button>
    365 
    366                                                                                 {/* Playlist dropdown */}
    367                                                                                 {showPlaylistDropdown && (
    368                                                                                         <div className="absolute left-0 top-full mt-2 w-56 bg-[#282828] rounded-lg shadow-2xl py-1 z-50 border border-white/10">
    369                                                                                                 <div className="px-3 py-2 text-xs text-gray-400 border-b border-white/10">
    370                                                                                                         Add to playlist
    371                                                                                                 </div>
    372                                                                                                 <button
    373                                                                                                         onClick={() => handleAddToPlaylist("Hello")}
    374                                                                                                         className="w-full text-left px-4 py-2.5 text-sm text-white hover:bg-white/10 transition-colors"
    375                                                                                                 >
    376                                                                                                         Hello
    377                                                                                                 </button>
    378                                                                                                 <button
    379                                                                                                         onClick={() => handleAddToPlaylist("Dimi")}
    380                                                                                                         className="w-full text-left px-4 py-2.5 text-sm text-white hover:bg-white/10 transition-colors"
    381                                                                                                 >
    382                                                                                                         Dimi
    383                                                                                                 </button>
    384                                                                                                 <button
    385                                                                                                         onClick={() => handleAddToPlaylist("lmao")}
    386                                                                                                         className="w-full text-left px-4 py-2.5 text-sm text-white hover:bg-white/10 transition-colors"
    387                                                                                                 >
    388                                                                                                         Kako hello world ama ti si mojot world
    389                                                                                                 </button>
    390                                                                                                 <button
    391                                                                                                         onClick={handleCreateNewPlaylist}
    392                                                                                                         className="w-full text-left px-4 py-2.5 text-sm text-[#1db954] hover:bg-white/10 transition-colors border-t border-white/10 flex items-center gap-2"
    393                                                                                                 >
    394                                                                                                         <svg
    395                                                                                                                 className="w-4 h-4"
    396                                                                                                                 fill="none"
    397                                                                                                                 stroke="currentColor"
    398                                                                                                                 viewBox="0 0 24 24"
    399                                                                                                         >
    400                                                                                                                 <path
    401                                                                                                                         strokeLinecap="round"
    402                                                                                                                         strokeLinejoin="round"
    403                                                                                                                         strokeWidth={2}
    404                                                                                                                         d="M12 4v16m8-8H4"
    405                                                                                                                 />
    406                                                                                                         </svg>
    407                                                                                                         Create new playlist
    408                                                                                                 </button>
    409                                                                                         </div>
    410                                                                                 )}
    411                                                                         </div>
    412                                                                 </>
    413                                                         )}
    414                                                 </div>
    415                                         </div>
    416                                 </div>
    417 
    418                                 {/* Credits / Contributions */}
    419                                 {song.contributions.length > 0 && (
    420                                         <section className="mb-10">
    421                                                 <h2 className="text-2xl font-bold mb-4">Credits</h2>
    422                                                 <div className="bg-[#1a1a2e]/60 rounded-xl p-5 divide-y divide-white/5">
    423                                                         {/* Main artist first */}
    424                                                         {song.contributions
    425                                                                 .filter((c) => c.artistName === song.releasedBy)
    426                                                                 .map((c, i) => (
    427                                                                         <div
    428                                                                                 key={`main-${i}`}
    429                                                                                 className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
    430                                                                         >
    431                                                                                 <div className="flex items-center gap-3">
    432                                                                                         <div className="w-10 h-10 bg-[#1db954] rounded-full flex items-center justify-center text-black font-bold text-sm">
    433                                                                                                 {c.artistName.charAt(0).toUpperCase()}
    434                                                                                         </div>
    435                                                                                         <div>
    436                                                                                                 <p className="text-white font-semibold text-lg">
    437                                                                                                         {c.artistName}
    438                                                                                                 </p>
    439                                                                                         </div>
    440                                                                                 </div>
    441                                                                                 <span className="text-sm text-gray-400 bg-white/5 px-3 py-1 rounded-full">
    442                                                                                         {formatRole(c.role)}
    443                                                                                 </span>
    444                                                                         </div>
    445                                                                 ))}
    446 
    447                                                         {/* Other contributors */}
    448                                                         {otherContributors.map((c, i) => (
    449                                                                 <div
    450                                                                         key={`contrib-${i}`}
    451                                                                         className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
    452                                                                 >
    453                                                                         <div className="flex items-center gap-3">
    454                                                                                 <div className="w-9 h-9 bg-white/10 rounded-full flex items-center justify-center text-white font-medium text-sm">
    455                                                                                         {c.artistName.charAt(0).toUpperCase()}
    456                                                                                 </div>
    457                                                                                 <p className="text-gray-300 font-medium">{c.artistName}</p>
    458                                                                         </div>
    459                                                                         <span className="text-sm text-gray-500 bg-white/5 px-3 py-1 rounded-full">
    460                                                                                 {formatRole(c.role)}
    461                                                                         </span>
    462                                                                 </div>
    463                                                         ))}
    464                                                 </div>
    465                                         </section>
    466                                 )}
    467 
    468                                 {/* Reviews */}
    469                                 <section>
    470                                         <div className="flex items-center justify-between mb-4">
    471                                                 <h2 className="text-2xl font-bold">
    472                                                         Reviews
    473                                                         {song.reviews.length > 0 && (
    474                                                                 <span className="text-base font-normal text-gray-500 ml-2">
    475                                                                         ({song.reviews.length})
    476                                                                 </span>
    477                                                         )}
    478                                                 </h2>
    479                                                 <button
    480                                                         onClick={() => setShowReviewModal(true)}
    481                                                         className="flex items-center gap-2 bg-[#1db954] hover:bg-[#1ed760] text-black text-sm font-semibold px-4 py-2 rounded-full transition-colors"
    482                                                 >
    483                                                         <svg
    484                                                                 className="w-4 h-4"
    485                                                                 fill="none"
    486                                                                 stroke="currentColor"
    487                                                                 viewBox="0 0 24 24"
    488                                                         >
    489                                                                 <path
    490                                                                         strokeLinecap="round"
    491                                                                         strokeLinejoin="round"
    492                                                                         strokeWidth={2}
    493                                                                         d="M12 4v16m8-8H4"
    494                                                                 />
    495                                                         </svg>
    496                                                         Add Review
    497                                                 </button>
    498                                         </div>
    499 
    500                                         {song.reviews.length === 0 ? (
    501                                                 <div className="bg-[#1a1a2e]/60 rounded-xl p-10 text-center">
    502                                                         <p className="text-gray-500 text-lg">No reviews yet.</p>
    503                                                         <p className="text-gray-600 text-sm mt-1">
    504                                                                 Be the first to share your thoughts!
    505                                                         </p>
    506                                                 </div>
    507                                         ) : (
    508                                                 <div className="space-y-4">
    509                                                         {song.reviews.map((review) => (
    510                                                                 <div
    511                                                                         key={`${review.id.listenerId}-${review.id.musicalEntityId}`}
    512                                                                         className="bg-[#1a1a2e]/60 rounded-xl p-5 hover:bg-[#1a1a2e]/80 transition-colors"
    513                                                                 >
    514                                                                         <div className="flex items-start justify-between mb-2">
    515                                                                                 <div className="flex items-center gap-3">
    516                                                                                         <div className="w-9 h-9 bg-blue-500/20 rounded-full flex items-center justify-center">
    517                                                                                                 <span className="text-blue-400 font-semibold text-sm">
    518                                                                                                         {review.author.charAt(0).toUpperCase()}
    519                                                                                                 </span>
    520                                                                                         </div>
    521                                                                                         <div>
    522                                                                                                 <p className="text-white font-medium">
    523                                                                                                         {review.author}
    524                                                                                                 </p>
    525                                                                                                 {renderStars(review.grade)}
    526                                                                                         </div>
    527                                                                                 </div>
    528                                                                                 {user?.username === review.authorUsername && (
    529                                                                                         <button
    530                                                                                                 onClick={deleteReview}
    531                                                                                                 className="text-gray-500 hover:text-red-400 transition-colors p-2 rounded-lg hover:bg-red-400/10"
    532                                                                                                 title="Delete review"
    533                                                                                         >
    534                                                                                                 <svg
    535                                                                                                         className="w-5 h-5"
    536                                                                                                         fill="none"
    537                                                                                                         stroke="currentColor"
    538                                                                                                         viewBox="0 0 24 24"
    539                                                                                                 >
    540                                                                                                         <path
    541                                                                                                                 strokeLinecap="round"
    542                                                                                                                 strokeLinejoin="round"
    543                                                                                                                 strokeWidth={2}
    544                                                                                                                 d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
    545                                                                                                         />
    546                                                                                                 </svg>
    547                                                                                         </button>
    548                                                                                 )}
    549                                                                         </div>
    550                                                                         <p className="text-gray-300 text-sm leading-relaxed mt-3 pl-12">
    551                                                                                 {review.comment}
    552                                                                         </p>
    553                                                                 </div>
    554                                                         ))}
    555                                                 </div>
    556                                         )}
    557                                 </section>
    558                         </div>
    559 
    560                         {/* Review Modal */}
    561                         {showReviewModal && (
    562                                 <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm">
    563                                         <div className="bg-[#1a1a2e] rounded-2xl p-6 max-w-md w-full shadow-2xl">
    564                                                 <div className="flex items-center justify-between mb-6">
    565                                                         <h3 className="text-2xl font-bold">Add Review</h3>
    566                                                         <button
    567                                                                 onClick={() => {
    568                                                                         setShowReviewModal(false);
    569                                                                         setReviewRating(0);
    570                                                                         setReviewComment("");
    571                                                                         setHoverRating(0);
    572                                                                 }}
    573                                                                 className="text-gray-400 hover:text-white transition-colors"
    574                                                         >
    575                                                                 <svg
    576                                                                         className="w-6 h-6"
    577                                                                         fill="none"
    578                                                                         stroke="currentColor"
    579                                                                         viewBox="0 0 24 24"
    580                                                                 >
    581                                                                         <path
    582                                                                                 strokeLinecap="round"
    583                                                                                 strokeLinejoin="round"
    584                                                                                 strokeWidth={2}
    585                                                                                 d="M6 18L18 6M6 6l12 12"
    586                                                                         />
    587                                                                 </svg>
    588                                                         </button>
    589                                                 </div>
    590 
    591                                                 {/* Star Rating */}
    592                                                 <div className="mb-6">
    593                                                         <label className="block text-sm font-medium mb-3">
    594                                                                 Rating <span className="text-red-400">*</span>
    595                                                         </label>
    596                                                         <div className="flex gap-2">
    597                                                                 {[1, 2, 3, 4, 5].map((star) => (
    598                                                                         <button
    599                                                                                 key={star}
    600                                                                                 onClick={() => setReviewRating(star)}
    601                                                                                 onMouseEnter={() => setHoverRating(star)}
    602                                                                                 onMouseLeave={() => setHoverRating(0)}
    603                                                                                 className="transition-transform hover:scale-110"
    604                                                                         >
    605                                                                                 <svg
    606                                                                                         className={`w-10 h-10 ${
    607                                                                                                 star <= (hoverRating || reviewRating)
    608                                                                                                         ? "text-yellow-400"
    609                                                                                                         : "text-gray-600"
    610                                                                                         }`}
    611                                                                                         fill="currentColor"
    612                                                                                         viewBox="0 0 20 20"
    613                                                                                 >
    614                                                                                         <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
    615                                                                                 </svg>
    616                                                                         </button>
    617                                                                 ))}
    618                                                         </div>
    619                                                 </div>
    620 
    621                                                 {/* Comment */}
    622                                                 <div className="mb-6">
    623                                                         <label className="block text-sm font-medium mb-2">
    624                                                                 Comment <span className="text-gray-500">(optional)</span>
    625                                                         </label>
    626                                                         <textarea
    627                                                                 value={reviewComment}
    628                                                                 onChange={(e) => setReviewComment(e.target.value)}
    629                                                                 placeholder="Share your thoughts about this song..."
    630                                                                 rows={4}
    631                                                                 className="w-full bg-[#0f0f1e] border border-white/10 rounded-lg px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-[#1db954] transition-colors resize-none"
    632                                                         />
    633                                                 </div>
    634 
    635                                                 {/* Actions */}
    636                                                 <div className="flex gap-3 justify-end">
    637                                                         <button
    638                                                                 onClick={() => {
    639                                                                         setShowReviewModal(false);
    640                                                                         setReviewRating(0);
    641                                                                         setReviewComment("");
    642                                                                         setHoverRating(0);
    643                                                                 }}
    644                                                                 className="px-5 py-2.5 rounded-full text-sm font-semibold bg-white/10 hover:bg-white/20 transition-colors"
    645                                                         >
    646                                                                 Cancel
    647                                                         </button>
    648                                                         <button
    649                                                                 onClick={handleSubmitReview}
    650                                                                 disabled={reviewRating === 0}
    651                                                                 className="px-5 py-2.5 rounded-full text-sm font-semibold bg-[#1db954] hover:bg-[#1ed760] text-black transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
    652                                                         >
    653                                                                 Submit
    654                                                         </button>
    655                                                 </div>
    656                                         </div>
    657                                 </div>
    658                         )}
    659                 </div>
    660         );
     43  const { id } = useParams<{ id: string }>();
     44  const { user } = useAuth();
     45  const { play, currentSong } = usePlayer();
     46  const [song, setSong] = useState<SongDetailType | null>(null);
     47  const [loading, setLoading] = useState(true);
     48  const [error, setError] = useState<string | null>(null);
     49  const [showReviewModal, setShowReviewModal] = useState(false);
     50  const [reviewRating, setReviewRating] = useState(0);
     51  const [reviewComment, setReviewComment] = useState("");
     52  const [hoverRating, setHoverRating] = useState(0);
     53  const [showPlaylistDropdown, setShowPlaylistDropdown] = useState(false);
     54  console.log(user);
     55  useEffect(() => {
     56    const fetchSong = async () => {
     57      try {
     58        setLoading(true);
     59        const response = await axiosInstance.get(`/songs/${id}/details`);
     60        setSong(response.data);
     61      } catch (err) {
     62        console.error("Error fetching song details:", err);
     63        setError("Failed to load song details.");
     64      } finally {
     65        setLoading(false);
     66      }
     67    };
     68    if (id) fetchSong();
     69  }, [id]);
     70
     71  const handleSubmitReview = async () => {
     72    if (reviewRating === 0) {
     73      // todo: replace with toast
     74      alert("Please select a rating");
     75      return;
     76    }
     77    try {
     78      await axiosInstance.post(`reviews/${song?.id}`, {
     79        grade: reviewRating,
     80        comment: reviewComment,
     81      });
     82      const response = await axiosInstance.get(`/songs/${id}/details`);
     83      setSong(response.data);
     84    } catch (err) {
     85      // todo: replace with toast
     86      console.error("Error submitting review:", err);
     87    } finally {
     88      setShowReviewModal(false);
     89      setReviewRating(0);
     90      setReviewComment("");
     91      setHoverRating(0);
     92    }
     93  };
     94
     95  const deleteReview = async () => {
     96    try {
     97      await axiosInstance.delete(`reviews/${song?.id}`);
     98      const response = await axiosInstance.get(`/songs/${id}/details`);
     99      setSong(response.data);
     100    } catch (err) {
     101      toast.error("Failed to delete review");
     102      console.error("Error deleting review:", err);
     103    }
     104  };
     105
     106  const toggleLike = async () => {
     107    if (!song) return;
     108    try {
     109      await axiosInstance.post(`/musical-entity/${song.id}/like`);
     110      setSong((prev) =>
     111        prev
     112          ? { ...prev, isLikedByCurrentUser: !prev.isLikedByCurrentUser }
     113          : prev,
     114      );
     115    } catch (err) {
     116      toast.error("Failed to toggle like");
     117      console.error("Error toggling like:", err);
     118    }
     119  };
     120
     121  const handleCreateNewPlaylist = () => {
     122    console.log(`Creating new playlist for song ${song?.id}`);
     123    // TODO: actual playlist creation
     124    setShowPlaylistDropdown(false);
     125  };
     126
     127  if (loading) {
     128    return (
     129      <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] flex items-center justify-center">
     130        <div className="flex flex-col items-center gap-4">
     131          <div className="w-12 h-12 border-4 border-white/10 border-t-[#1db954] rounded-full animate-spin" />
     132          <p className="text-gray-400 text-lg">Loading song…</p>
     133        </div>
     134      </div>
     135    );
     136  }
     137
     138  if (error || !song) {
     139    return (
     140      <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] flex items-center justify-center">
     141        <div className="text-center">
     142          <p className="text-red-400 text-xl mb-4">
     143            {error ?? "Song not found"}
     144          </p>
     145          <Link to="/" className="text-[#1db954] hover:underline text-sm">
     146            ← Back to Home
     147          </Link>
     148        </div>
     149      </div>
     150    );
     151  }
     152
     153  const otherContributors = song.contributions.filter(
     154    (c) => c.artistName !== song.releasedBy,
     155  );
     156
     157  const avgRating =
     158    song.reviews.length > 0
     159      ? (
     160          song.reviews.reduce((sum, r) => sum + r.grade, 0) /
     161          song.reviews.length
     162        ).toFixed(1)
     163      : null;
     164
     165  return (
     166    <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] text-white">
     167      <div className="max-w-5xl mx-auto px-6 py-10">
     168        {/* Back link */}
     169        <Link
     170          to="/"
     171          className="inline-flex items-center gap-1 text-gray-400 hover:text-white text-sm mb-8 transition-colors"
     172        >
     173          <svg
     174            className="w-4 h-4"
     175            fill="none"
     176            stroke="currentColor"
     177            viewBox="0 0 24 24"
     178          >
     179            <path
     180              strokeLinecap="round"
     181              strokeLinejoin="round"
     182              strokeWidth={2}
     183              d="M15 19l-7-7 7-7"
     184            />
     185          </svg>
     186          Back to Home
     187        </Link>
     188
     189        {/* Hero section */}
     190        <div className="flex flex-col md:flex-row gap-8 mb-10">
     191          {/* Cover art */}
     192          <div className="w-full md:w-72 shrink-0">
     193            <div className="relative w-full pt-[100%] rounded-xl overflow-hidden shadow-2xl bg-[#181818]">
     194              <img
     195                src={song.cover ? `${baseURL}/${song.cover}` : "/favicon.png"}
     196                alt={song.title}
     197                className="absolute inset-0 w-full h-full object-cover"
     198                onError={(e) => {
     199                  (e.target as HTMLImageElement).src = "/favicon.png";
     200                }}
     201              />
     202            </div>
     203          </div>
     204
     205          {/* Song info */}
     206          <div className="flex flex-col justify-end gap-3 min-w-0">
     207            <span className="text-xs uppercase tracking-widest text-gray-400 font-medium">
     208              {song.genre} • Song
     209            </span>
     210            <h1 className="text-4xl md:text-5xl font-extrabold leading-tight truncate">
     211              {song.title}
     212            </h1>
     213
     214            {/* Main artist */}
     215            <p className="text-xl text-gray-300 font-semibold">
     216              {song.releasedBy}
     217            </p>
     218
     219            {/* Album */}
     220            {song.album && (
     221              <p className="text-sm text-gray-500">
     222                Album: <span className="text-gray-300">{song.album}</span>
     223              </p>
     224            )}
     225
     226            {/* Rating summary */}
     227            {avgRating && (
     228              <div className="flex items-center gap-2 mt-1">
     229                {renderStars(Math.round(Number(avgRating)))}
     230                <span className="text-sm text-gray-400">
     231                  {avgRating} · {song.reviews.length}{" "}
     232                  {song.reviews.length === 1 ? "review" : "reviews"}
     233                </span>
     234              </div>
     235            )}
     236
     237            {/* Action buttons */}
     238            <div className="flex items-center gap-3 mt-4">
     239              {/* Play button */}
     240              {song.link && (
     241                <button
     242                  onClick={() =>
     243                    play({
     244                      id: song.id,
     245                      title: song.title,
     246                      artist: song.releasedBy,
     247                      cover: song.cover,
     248                      embedUrl: toEmbedUrl(song.link!),
     249                    })
     250                  }
     251                  className={`flex items-center gap-2 px-6 py-3 rounded-full text-sm font-semibold transition-all cursor-pointer ${
     252                    currentSong?.id === song.id
     253                      ? "bg-white text-[#1db954]"
     254                      : "bg-[#1db954] text-black hover:bg-[#1ed760] hover:scale-105"
     255                  }`}
     256                >
     257                  {currentSong?.id === song.id ? (
     258                    <>
     259                      <svg
     260                        className="w-5 h-5"
     261                        fill="currentColor"
     262                        viewBox="0 0 24 24"
     263                      >
     264                        <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
     265                      </svg>
     266                      Now Playing
     267                    </>
     268                  ) : (
     269                    <>
     270                      <svg
     271                        className="w-5 h-5"
     272                        fill="currentColor"
     273                        viewBox="0 0 24 24"
     274                      >
     275                        <path d="M8 5v14l11-7z" />
     276                      </svg>
     277                      Play Song
     278                    </>
     279                  )}
     280                </button>
     281              )}
     282
     283              {user && (
     284                <>
     285                  <button
     286                    onClick={toggleLike}
     287                    className={`flex items-center gap-2 px-5 py-3 rounded-full text-sm font-semibold transition-colors cursor-pointer ${
     288                      song.isLikedByCurrentUser
     289                        ? "bg-[#1db954] text-black"
     290                        : "bg-white/10 text-white hover:bg-white/20"
     291                    }`}
     292                  >
     293                    {song.isLikedByCurrentUser ? (
     294                      <svg
     295                        className="w-5 h-5"
     296                        fill="currentColor"
     297                        viewBox="0 0 24 24"
     298                      >
     299                        <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
     300                      </svg>
     301                    ) : (
     302                      <svg
     303                        className="w-5 h-5"
     304                        fill="none"
     305                        stroke="currentColor"
     306                        viewBox="0 0 24 24"
     307                      >
     308                        <path
     309                          strokeLinecap="round"
     310                          strokeLinejoin="round"
     311                          strokeWidth={2}
     312                          d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
     313                        />
     314                      </svg>
     315                    )}
     316                    {song.isLikedByCurrentUser ? "Liked" : "Like"}
     317                  </button>
     318
     319                  {/* Add to Playlist button */}
     320                  <div className="relative">
     321                    <button
     322                      onClick={() => setShowPlaylistDropdown((prev) => !prev)}
     323                      className="flex items-center gap-2 px-5 py-3 rounded-full text-sm font-semibold bg-white/10 text-white hover:bg-white/20 transition-colors cursor-pointer"
     324                    >
     325                      <svg
     326                        className="w-5 h-5"
     327                        fill="none"
     328                        stroke="currentColor"
     329                        viewBox="0 0 24 24"
     330                      >
     331                        <path
     332                          strokeLinecap="round"
     333                          strokeLinejoin="round"
     334                          strokeWidth={2}
     335                          d="M12 4v16m8-8H4"
     336                        />
     337                      </svg>
     338                      Add to Playlist
     339                    </button>
     340
     341                    <PlaylistDropdown
     342                      songId={song.id}
     343                      isOpen={showPlaylistDropdown}
     344                      onClose={() => setShowPlaylistDropdown(false)}
     345                      onCreateNewPlaylist={handleCreateNewPlaylist}
     346                      direction="below"
     347                    />
     348                  </div>
     349                </>
     350              )}
     351            </div>
     352          </div>
     353        </div>
     354
     355        {/* Credits / Contributions */}
     356        {song.contributions.length > 0 && (
     357          <section className="mb-10">
     358            <h2 className="text-2xl font-bold mb-4">Credits</h2>
     359            <div className="bg-[#1a1a2e]/60 rounded-xl p-5 divide-y divide-white/5">
     360              {/* Main artist first */}
     361              {song.contributions
     362                .filter((c) => c.artistName === song.releasedBy)
     363                .map((c, i) => (
     364                  <div
     365                    key={`main-${i}`}
     366                    className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
     367                  >
     368                    <div className="flex items-center gap-3">
     369                      <div className="w-10 h-10 bg-[#1db954] rounded-full flex items-center justify-center text-black font-bold text-sm">
     370                        {c.artistName.charAt(0).toUpperCase()}
     371                      </div>
     372                      <div>
     373                        <p className="text-white font-semibold text-lg">
     374                          {c.artistName}
     375                        </p>
     376                      </div>
     377                    </div>
     378                    <span className="text-sm text-gray-400 bg-white/5 px-3 py-1 rounded-full">
     379                      {formatRole(c.role)}
     380                    </span>
     381                  </div>
     382                ))}
     383
     384              {/* Other contributors */}
     385              {otherContributors.map((c, i) => (
     386                <div
     387                  key={`contrib-${i}`}
     388                  className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
     389                >
     390                  <div className="flex items-center gap-3">
     391                    <div className="w-9 h-9 bg-white/10 rounded-full flex items-center justify-center text-white font-medium text-sm">
     392                      {c.artistName.charAt(0).toUpperCase()}
     393                    </div>
     394                    <p className="text-gray-300 font-medium">{c.artistName}</p>
     395                  </div>
     396                  <span className="text-sm text-gray-500 bg-white/5 px-3 py-1 rounded-full">
     397                    {formatRole(c.role)}
     398                  </span>
     399                </div>
     400              ))}
     401            </div>
     402          </section>
     403        )}
     404
     405        {/* Reviews */}
     406        <section>
     407          <div className="flex items-center justify-between mb-4">
     408            <h2 className="text-2xl font-bold">
     409              Reviews
     410              {song.reviews.length > 0 && (
     411                <span className="text-base font-normal text-gray-500 ml-2">
     412                  ({song.reviews.length})
     413                </span>
     414              )}
     415            </h2>
     416            <button
     417              onClick={() => setShowReviewModal(true)}
     418              className="flex items-center gap-2 bg-[#1db954] hover:bg-[#1ed760] text-black text-sm font-semibold px-4 py-2 rounded-full transition-colors"
     419            >
     420              <svg
     421                className="w-4 h-4"
     422                fill="none"
     423                stroke="currentColor"
     424                viewBox="0 0 24 24"
     425              >
     426                <path
     427                  strokeLinecap="round"
     428                  strokeLinejoin="round"
     429                  strokeWidth={2}
     430                  d="M12 4v16m8-8H4"
     431                />
     432              </svg>
     433              Add Review
     434            </button>
     435          </div>
     436
     437          {song.reviews.length === 0 ? (
     438            <div className="bg-[#1a1a2e]/60 rounded-xl p-10 text-center">
     439              <p className="text-gray-500 text-lg">No reviews yet.</p>
     440              <p className="text-gray-600 text-sm mt-1">
     441                Be the first to share your thoughts!
     442              </p>
     443            </div>
     444          ) : (
     445            <div className="space-y-4">
     446              {song.reviews.map((review) => (
     447                <div
     448                  key={`${review.id.listenerId}-${review.id.musicalEntityId}`}
     449                  className="bg-[#1a1a2e]/60 rounded-xl p-5 hover:bg-[#1a1a2e]/80 transition-colors"
     450                >
     451                  <div className="flex items-start justify-between mb-2">
     452                    <div className="flex items-center gap-3">
     453                      <div className="w-9 h-9 bg-blue-500/20 rounded-full flex items-center justify-center">
     454                        <span className="text-blue-400 font-semibold text-sm">
     455                          {review.author.charAt(0).toUpperCase()}
     456                        </span>
     457                      </div>
     458                      <div>
     459                        <p className="text-white font-medium">
     460                          {review.author}
     461                        </p>
     462                        {renderStars(review.grade)}
     463                      </div>
     464                    </div>
     465                    {user?.username === review.authorUsername && (
     466                      <button
     467                        onClick={deleteReview}
     468                        className="text-gray-500 hover:text-red-400 transition-colors p-2 rounded-lg hover:bg-red-400/10"
     469                        title="Delete review"
     470                      >
     471                        <svg
     472                          className="w-5 h-5"
     473                          fill="none"
     474                          stroke="currentColor"
     475                          viewBox="0 0 24 24"
     476                        >
     477                          <path
     478                            strokeLinecap="round"
     479                            strokeLinejoin="round"
     480                            strokeWidth={2}
     481                            d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
     482                          />
     483                        </svg>
     484                      </button>
     485                    )}
     486                  </div>
     487                  <p className="text-gray-300 text-sm leading-relaxed mt-3 pl-12">
     488                    {review.comment}
     489                  </p>
     490                </div>
     491              ))}
     492            </div>
     493          )}
     494        </section>
     495      </div>
     496
     497      {/* Review Modal */}
     498      {showReviewModal && (
     499        <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm">
     500          <div className="bg-[#1a1a2e] rounded-2xl p-6 max-w-md w-full shadow-2xl">
     501            <div className="flex items-center justify-between mb-6">
     502              <h3 className="text-2xl font-bold">Add Review</h3>
     503              <button
     504                onClick={() => {
     505                  setShowReviewModal(false);
     506                  setReviewRating(0);
     507                  setReviewComment("");
     508                  setHoverRating(0);
     509                }}
     510                className="text-gray-400 hover:text-white transition-colors"
     511              >
     512                <svg
     513                  className="w-6 h-6"
     514                  fill="none"
     515                  stroke="currentColor"
     516                  viewBox="0 0 24 24"
     517                >
     518                  <path
     519                    strokeLinecap="round"
     520                    strokeLinejoin="round"
     521                    strokeWidth={2}
     522                    d="M6 18L18 6M6 6l12 12"
     523                  />
     524                </svg>
     525              </button>
     526            </div>
     527
     528            {/* Star Rating */}
     529            <div className="mb-6">
     530              <label className="block text-sm font-medium mb-3">
     531                Rating <span className="text-red-400">*</span>
     532              </label>
     533              <div className="flex gap-2">
     534                {[1, 2, 3, 4, 5].map((star) => (
     535                  <button
     536                    key={star}
     537                    onClick={() => setReviewRating(star)}
     538                    onMouseEnter={() => setHoverRating(star)}
     539                    onMouseLeave={() => setHoverRating(0)}
     540                    className="transition-transform hover:scale-110"
     541                  >
     542                    <svg
     543                      className={`w-10 h-10 ${
     544                        star <= (hoverRating || reviewRating)
     545                          ? "text-yellow-400"
     546                          : "text-gray-600"
     547                      }`}
     548                      fill="currentColor"
     549                      viewBox="0 0 20 20"
     550                    >
     551                      <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
     552                    </svg>
     553                  </button>
     554                ))}
     555              </div>
     556            </div>
     557
     558            {/* Comment */}
     559            <div className="mb-6">
     560              <label className="block text-sm font-medium mb-2">
     561                Comment <span className="text-gray-500">(optional)</span>
     562              </label>
     563              <textarea
     564                value={reviewComment}
     565                onChange={(e) => setReviewComment(e.target.value)}
     566                placeholder="Share your thoughts about this song..."
     567                rows={4}
     568                className="w-full bg-[#0f0f1e] border border-white/10 rounded-lg px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-[#1db954] transition-colors resize-none"
     569              />
     570            </div>
     571
     572            {/* Actions */}
     573            <div className="flex gap-3 justify-end">
     574              <button
     575                onClick={() => {
     576                  setShowReviewModal(false);
     577                  setReviewRating(0);
     578                  setReviewComment("");
     579                  setHoverRating(0);
     580                }}
     581                className="px-5 py-2.5 rounded-full text-sm font-semibold bg-white/10 hover:bg-white/20 transition-colors"
     582              >
     583                Cancel
     584              </button>
     585              <button
     586                onClick={handleSubmitReview}
     587                disabled={reviewRating === 0}
     588                className="px-5 py-2.5 rounded-full text-sm font-semibold bg-[#1db954] hover:bg-[#1ed760] text-black transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
     589              >
     590                Submit
     591              </button>
     592            </div>
     593          </div>
     594        </div>
     595      )}
     596    </div>
     597  );
    661598};
    662599
Note: See TracChangeset for help on using the changeset viewer.