Changeset 7621d7b for frontend/src/pages
- Timestamp:
- 02/08/26 15:38:51 (5 months ago)
- Branches:
- main
- Children:
- 821dc0e
- Parents:
- 799f27d
- File:
-
- 1 edited
-
frontend/src/pages/SongDetail.tsx (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/pages/SongDetail.tsx
r799f27d r7621d7b 67 67 const [loading, setLoading] = useState(true); 68 68 const [error, setError] = useState<string | null>(null); 69 69 const [showReviewModal, setShowReviewModal] = useState(false); 70 const [reviewRating, setReviewRating] = useState(0); 71 const [reviewComment, setReviewComment] = useState(""); 72 const [hoverRating, setHoverRating] = useState(0); 73 console.log(user); 70 74 useEffect(() => { 71 75 const fetchSong = async () => { … … 84 88 if (id) fetchSong(); 85 89 }, [id]); 90 91 const handleSubmitReview = async () => { 92 if (reviewRating === 0) { 93 // todo: replace with toast 94 alert("Please select a rating"); 95 return; 96 } 97 try { 98 await axiosInstance.post(`reviews/${song?.id}`, { 99 grade: reviewRating, 100 comment: reviewComment, 101 }); 102 const response = await axiosInstance.get(`/songs/${id}/details`); 103 setSong(response.data); 104 } catch (err) { 105 // todo: replace with toast 106 console.error("Error submitting review:", err); 107 } finally { 108 setShowReviewModal(false); 109 setReviewRating(0); 110 setReviewComment(""); 111 setHoverRating(0); 112 } 113 }; 114 115 const deleteReview = async () => { 116 try { 117 await axiosInstance.delete(`reviews/${song?.id}`); 118 const response = await axiosInstance.get(`/songs/${id}/details`); 119 setSong(response.data); 120 } catch (err) { 121 console.error("Error deleting review:", err); 122 } 123 }; 86 124 87 125 const toggleLike = async () => { … … 294 332 </div> 295 333 296 {/* Play button — plays in persistent mini-player */}297 {song.link && (298 <section className="mb-10">299 {currentSong?.id === song.id ? (300 <div className="bg-[#1a1a2e]/60 rounded-xl p-4 flex items-center gap-3">301 <div className="w-10 h-10 rounded-full bg-[#1db954] flex items-center justify-center text-black animate-pulse">302 <svg303 className="w-5 h-5"304 fill="currentColor"305 viewBox="0 0 24 24"306 >307 <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />308 </svg>309 </div>310 <p className="text-[#1db954] font-medium">311 Now playing in mini-player below312 </p>313 </div>314 ) : (315 <button316 onClick={() =>317 play({318 id: song.id,319 title: song.title,320 artist: song.releasedBy,321 cover: song.cover,322 embedUrl: toEmbedUrl(song.link!),323 })324 }325 className="w-full bg-[#1a1a2e]/60 hover:bg-[#1a1a2e]/80 rounded-xl p-4 flex items-center gap-3 transition-colors cursor-pointer group"326 >327 <div className="w-10 h-10 rounded-full bg-[#1db954] flex items-center justify-center text-black group-hover:scale-105 transition-transform">328 <svg329 className="w-5 h-5"330 fill="currentColor"331 viewBox="0 0 24 24"332 >333 <path d="M8 5v14l11-7z" />334 </svg>335 </div>336 <p className="text-white font-medium">Play this song</p>337 </button>338 )}339 </section>340 )}341 342 334 {/* Credits / Contributions */} 343 335 {song.contributions.length > 0 && ( … … 402 394 </h2> 403 395 <button 404 onClick={() => { 405 /* TODO: add review modal */ 406 }} 396 onClick={() => setShowReviewModal(true)} 407 397 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" 408 398 > … … 452 442 </div> 453 443 </div> 444 {user?.username === review.authorUsername && ( 445 <button 446 onClick={deleteReview} 447 className="text-gray-500 hover:text-red-400 transition-colors p-2 rounded-lg hover:bg-red-400/10" 448 title="Delete review" 449 > 450 <svg 451 className="w-5 h-5" 452 fill="none" 453 stroke="currentColor" 454 viewBox="0 0 24 24" 455 > 456 <path 457 strokeLinecap="round" 458 strokeLinejoin="round" 459 strokeWidth={2} 460 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" 461 /> 462 </svg> 463 </button> 464 )} 454 465 </div> 455 466 <p className="text-gray-300 text-sm leading-relaxed mt-3 pl-12"> … … 462 473 </section> 463 474 </div> 475 476 {/* Review Modal */} 477 {showReviewModal && ( 478 <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm"> 479 <div className="bg-[#1a1a2e] rounded-2xl p-6 max-w-md w-full shadow-2xl"> 480 <div className="flex items-center justify-between mb-6"> 481 <h3 className="text-2xl font-bold">Add Review</h3> 482 <button 483 onClick={() => { 484 setShowReviewModal(false); 485 setReviewRating(0); 486 setReviewComment(""); 487 setHoverRating(0); 488 }} 489 className="text-gray-400 hover:text-white transition-colors" 490 > 491 <svg 492 className="w-6 h-6" 493 fill="none" 494 stroke="currentColor" 495 viewBox="0 0 24 24" 496 > 497 <path 498 strokeLinecap="round" 499 strokeLinejoin="round" 500 strokeWidth={2} 501 d="M6 18L18 6M6 6l12 12" 502 /> 503 </svg> 504 </button> 505 </div> 506 507 {/* Star Rating */} 508 <div className="mb-6"> 509 <label className="block text-sm font-medium mb-3"> 510 Rating <span className="text-red-400">*</span> 511 </label> 512 <div className="flex gap-2"> 513 {[1, 2, 3, 4, 5].map((star) => ( 514 <button 515 key={star} 516 onClick={() => setReviewRating(star)} 517 onMouseEnter={() => setHoverRating(star)} 518 onMouseLeave={() => setHoverRating(0)} 519 className="transition-transform hover:scale-110" 520 > 521 <svg 522 className={`w-10 h-10 ${ 523 star <= (hoverRating || reviewRating) 524 ? "text-yellow-400" 525 : "text-gray-600" 526 }`} 527 fill="currentColor" 528 viewBox="0 0 20 20" 529 > 530 <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" /> 531 </svg> 532 </button> 533 ))} 534 </div> 535 </div> 536 537 {/* Comment */} 538 <div className="mb-6"> 539 <label className="block text-sm font-medium mb-2"> 540 Comment <span className="text-gray-500">(optional)</span> 541 </label> 542 <textarea 543 value={reviewComment} 544 onChange={(e) => setReviewComment(e.target.value)} 545 placeholder="Share your thoughts about this song..." 546 rows={4} 547 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" 548 /> 549 </div> 550 551 {/* Actions */} 552 <div className="flex gap-3 justify-end"> 553 <button 554 onClick={() => { 555 setShowReviewModal(false); 556 setReviewRating(0); 557 setReviewComment(""); 558 setHoverRating(0); 559 }} 560 className="px-5 py-2.5 rounded-full text-sm font-semibold bg-white/10 hover:bg-white/20 transition-colors" 561 > 562 Cancel 563 </button> 564 <button 565 onClick={handleSubmitReview} 566 disabled={reviewRating === 0} 567 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" 568 > 569 Submit 570 </button> 571 </div> 572 </div> 573 </div> 574 )} 464 575 </div> 465 576 );
Note:
See TracChangeset
for help on using the changeset viewer.
