- Timestamp:
- 02/19/26 20:23:01 (5 months ago)
- Branches:
- main
- Children:
- 765e166
- Parents:
- c807e22 (diff), 615dcee (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)links above to see all the changes relative to each parent. - Location:
- frontend/src
- Files:
-
- 6 edited
-
components/SongItem.tsx (modified) (5 diffs)
-
components/userProfile/ArtistView.tsx (modified) (1 diff)
-
components/userProfile/UserListModal.tsx (modified) (3 diffs)
-
pages/LandingPage.tsx (modified) (4 diffs)
-
pages/SongDetail.tsx (modified) (1 diff)
-
pages/UserDetail.tsx (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/components/SongItem.tsx
rc807e22 red8f998 1 import { use Ref, useState } from "react";1 import { use, useRef, useState } from "react"; 2 2 import { useNavigate } from "react-router-dom"; 3 3 import { baseURL } from "../api/axiosInstance"; … … 5 5 import { toEmbedUrl } from "../utils/utils"; 6 6 import PlaylistDropdown from "./playlist/PlaylistDropdown"; 7 import { useAuth } from "../context/authContext"; 7 8 8 9 export interface SongItemData { … … 50 51 const navigate = useNavigate(); 51 52 const { play, currentSong } = usePlayer(); 53 const { user } = useAuth(); 52 54 const [internalOpen, setInternalOpen] = useState(false); 53 55 const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 }); … … 165 167 166 168 {/* Like button */} 167 { onLikeToggle && (169 {user && onLikeToggle && ( 168 170 <button 169 171 onClick={(e) => { … … 187 189 188 190 {/* Three-dot menu */} 189 <div className="relative"> 190 <button 191 ref={buttonRef} 192 onClick={(e) => { 193 e.stopPropagation(); 194 if (playlistOpen) { 195 setPlaylistOpen(false); 196 } else { 197 if (buttonRef.current) { 198 const rect = buttonRef.current.getBoundingClientRect(); 199 const spaceBelow = window.innerHeight - rect.bottom; 200 const dropdownHeight = 260; 201 202 if (spaceBelow < dropdownHeight) { 203 setDropdownDirection("above"); 204 setDropdownPosition({ 205 top: rect.top - 8, 206 left: rect.right - 224, 207 }); 208 } else { 209 setDropdownDirection("below"); 210 setDropdownPosition({ 211 top: rect.bottom + 8, 212 left: rect.right - 224, 213 }); 191 {user && ( 192 <div className="relative"> 193 <button 194 ref={buttonRef} 195 onClick={(e) => { 196 e.stopPropagation(); 197 if (playlistOpen) { 198 setPlaylistOpen(false); 199 } else { 200 if (buttonRef.current) { 201 const rect = buttonRef.current.getBoundingClientRect(); 202 const spaceBelow = window.innerHeight - rect.bottom; 203 const dropdownHeight = 260; 204 205 if (spaceBelow < dropdownHeight) { 206 setDropdownDirection("above"); 207 setDropdownPosition({ 208 top: rect.top - 8, 209 left: rect.right - 224, 210 }); 211 } else { 212 setDropdownDirection("below"); 213 setDropdownPosition({ 214 top: rect.bottom + 8, 215 left: rect.right - 224, 216 }); 217 } 214 218 } 219 setPlaylistOpen(true); 215 220 } 216 setPlaylistOpen(true); 217 } 218 }} 219 className="p-2 hover:bg-white/10 rounded-full transition-colors cursor-pointer text-gray-400 hover:text-white" 220 aria-label="More options" 221 > 222 <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"> 223 <circle cx="12" cy="5" r="1.5" /> 224 <circle cx="12" cy="12" r="1.5" /> 225 <circle cx="12" cy="19" r="1.5" /> 226 </svg> 227 </button> 228 229 <PlaylistDropdown 230 songId={song.id} 231 isOpen={playlistOpen} 232 onClose={() => setPlaylistOpen(false)} 233 position={dropdownPosition} 234 usePortal={true} 235 direction={dropdownDirection} 236 /> 237 </div> 221 }} 222 className="p-2 hover:bg-white/10 rounded-full transition-colors cursor-pointer text-gray-400 hover:text-white" 223 aria-label="More options" 224 > 225 <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"> 226 <circle cx="12" cy="5" r="1.5" /> 227 <circle cx="12" cy="12" r="1.5" /> 228 <circle cx="12" cy="19" r="1.5" /> 229 </svg> 230 </button> 231 232 <PlaylistDropdown 233 songId={song.id} 234 isOpen={playlistOpen} 235 onClose={() => setPlaylistOpen(false)} 236 position={dropdownPosition} 237 usePortal={true} 238 direction={dropdownDirection} 239 /> 240 </div> 241 )} 242 243 {/* Three-dot menu */} 238 244 </div> 239 245 ); -
frontend/src/components/userProfile/ArtistView.tsx
rc807e22 red8f998 6 6 import type { ArtistContribution } from "../../utils/types"; 7 7 import SongItem from "../SongItem"; 8 import { useAuth } from "../../context/authContext"; 8 9 9 10 interface ArtistViewProps { -
frontend/src/components/userProfile/UserListModal.tsx
rc807e22 red8f998 3 3 import type { BaseNonAdminUser } from "../../utils/types"; 4 4 import { useEffect } from "react"; 5 import { useAuth } from "../../context/authContext"; 5 6 6 7 interface ModalProps { … … 18 19 }: ModalProps) => { 19 20 const navigate = useNavigate(); 21 const { user } = useAuth(); 20 22 useEffect(() => { 21 23 document.body.style.overflow = "hidden"; … … 72 74 </div> 73 75 </div> 74 75 <button 76 onClick={() => onFollowToggle(u.username)} 77 className={`px-4 py-1.5 text-sm font-medium rounded-full transition-colors cursor-pointer ${ 78 u.isFollowedByCurrentUser 79 ? "bg-white/10 text-white hover:bg-white/20" 80 : "bg-[#1db954] text-black hover:bg-[#1ed760]" 81 }`} 82 > 83 {u.isFollowedByCurrentUser ? "Following" : "Follow"} 84 </button> 76 {user && ( 77 <button 78 onClick={() => onFollowToggle(u.username)} 79 className={`px-4 py-1.5 text-sm font-medium rounded-full transition-colors cursor-pointer ${ 80 u.isFollowedByCurrentUser 81 ? "bg-white/10 text-white hover:bg-white/20" 82 : "bg-[#1db954] text-black hover:bg-[#1ed760]" 83 }`} 84 > 85 {u.isFollowedByCurrentUser ? "Following" : "Follow"} 86 </button> 87 )} 85 88 </div> 86 89 )) -
frontend/src/pages/LandingPage.tsx
rc807e22 red8f998 14 14 } from "../utils/types"; 15 15 import { toEmbedUrl } from "../utils/utils"; 16 import { useAuth } from "../context/authContext"; 16 17 17 18 const CATEGORIES: { value: SearchCategory; label: string }[] = [ … … 25 26 const navigate = useNavigate(); 26 27 const { play, currentSong } = usePlayer(); 28 const { user } = useAuth(); 27 29 const [songs, setSongs] = useState<Song[]>([]); 28 30 const [loading, setLoading] = useState<boolean>(true); 29 31 30 // search state31 32 const [searchInput, setSearchInput] = useState(""); 32 33 const [activeQuery, setActiveQuery] = useState(""); … … 36 37 const [hasSearched, setHasSearched] = useState(false); 37 38 38 // playlist dropdown state39 39 const [openPlaylistDropdown, setOpenPlaylistDropdown] = useState< 40 40 number | null … … 415 415 {song.releasedBy} 416 416 </p> 417 <div className="flex items-center justify-between mt-4 pt-3 border-t border-white/10"> 418 {/* Like button */} 419 <button 420 onClick={(e) => { 421 e.stopPropagation(); 422 toggleLike(song.id); 423 }} 424 className="text-gray-400 hover:text-[#1db954] transition-colors cursor-pointer" 425 aria-label={ 426 song.isLikedByCurrentUser 427 ? "Unlike song" 428 : "Like song" 429 } 430 > 431 {song.isLikedByCurrentUser ? ( 432 <svg 433 className="w-6 h-6 fill-[#1db954]" 434 viewBox="0 0 24 24" 435 > 436 <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" /> 437 </svg> 438 ) : ( 439 <svg 440 className="w-6 h-6" 441 fill="none" 442 stroke="currentColor" 443 viewBox="0 0 24 24" 444 > 445 <path 446 strokeLinecap="round" 447 strokeLinejoin="round" 448 strokeWidth={2} 449 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" 450 /> 451 </svg> 452 )} 453 </button> 454 455 {/* Add to playlist button */} 456 <div className="relative"> 417 {user && ( 418 <div className="flex items-center justify-between mt-4 pt-3 border-t border-white/10"> 419 {/* Like button */} 457 420 <button 458 421 onClick={(e) => { 459 422 e.stopPropagation(); 460 toggle PlaylistDropdown(song.id);423 toggleLike(song.id); 461 424 }} 462 425 className="text-gray-400 hover:text-[#1db954] transition-colors cursor-pointer" 463 aria-label="Add to playlist" 426 aria-label={ 427 song.isLikedByCurrentUser 428 ? "Unlike song" 429 : "Like song" 430 } 464 431 > 465 <svg 466 className="w-6 h-6" 467 fill="none" 468 stroke="currentColor" 469 viewBox="0 0 24 24" 432 {song.isLikedByCurrentUser ? ( 433 <svg 434 className="w-6 h-6 fill-[#1db954]" 435 viewBox="0 0 24 24" 436 > 437 <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" /> 438 </svg> 439 ) : ( 440 <svg 441 className="w-6 h-6" 442 fill="none" 443 stroke="currentColor" 444 viewBox="0 0 24 24" 445 > 446 <path 447 strokeLinecap="round" 448 strokeLinejoin="round" 449 strokeWidth={2} 450 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" 451 /> 452 </svg> 453 )} 454 </button> 455 456 {/* Add to playlist button */} 457 <div className="relative"> 458 <button 459 onClick={(e) => { 460 e.stopPropagation(); 461 togglePlaylistDropdown(song.id); 462 }} 463 className="text-gray-400 hover:text-[#1db954] transition-colors cursor-pointer" 464 aria-label="Add to playlist" 470 465 > 471 <path 472 strokeLinecap="round" 473 strokeLinejoin="round" 474 strokeWidth={2} 475 d="M12 4v16m8-8H4" 476 /> 477 </svg> 478 </button> 479 480 <PlaylistDropdown 481 songId={song.id} 482 isOpen={openPlaylistDropdown === song.id} 483 onClose={() => setOpenPlaylistDropdown(null)} 484 direction="above" 485 /> 466 <svg 467 className="w-6 h-6" 468 fill="none" 469 stroke="currentColor" 470 viewBox="0 0 24 24" 471 > 472 <path 473 strokeLinecap="round" 474 strokeLinejoin="round" 475 strokeWidth={2} 476 d="M12 4v16m8-8H4" 477 /> 478 </svg> 479 </button> 480 481 <PlaylistDropdown 482 songId={song.id} 483 isOpen={openPlaylistDropdown === song.id} 484 onClose={() => setOpenPlaylistDropdown(null)} 485 direction="above" 486 /> 487 </div> 486 488 </div> 487 </div>489 )} 488 490 </div> 489 491 </div> -
frontend/src/pages/SongDetail.tsx
rc807e22 red8f998 407 407 )} 408 408 </h2> 409 <button 410 onClick={() => setShowReviewModal(true)} 411 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" 412 > 413 <svg 414 className="w-4 h-4" 415 fill="none" 416 stroke="currentColor" 417 viewBox="0 0 24 24" 409 {user != null && ( 410 <button 411 onClick={() => setShowReviewModal(true)} 412 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" 418 413 > 419 <path 420 strokeLinecap="round" 421 strokeLinejoin="round" 422 strokeWidth={2} 423 d="M12 4v16m8-8H4" 424 /> 425 </svg> 426 Add Review 427 </button> 414 <svg 415 className="w-4 h-4" 416 fill="none" 417 stroke="currentColor" 418 viewBox="0 0 24 24" 419 > 420 <path 421 strokeLinecap="round" 422 strokeLinejoin="round" 423 strokeWidth={2} 424 d="M12 4v16m8-8H4" 425 /> 426 </svg> 427 Add Review 428 </button> 429 )} 428 430 </div> 429 431 -
frontend/src/pages/UserDetail.tsx
rc807e22 red8f998 259 259 260 260 {/* Follow button - hidden on own profile */} 261 { !isOwnProfile && (261 {currentUser && !isOwnProfile && ( 262 262 <div className="mt-4"> 263 263 <button
Note:
See TracChangeset
for help on using the changeset viewer.
