Changeset d2af1a6 for frontend/src/components/userProfile/ArtistView.tsx
- Timestamp:
- 02/01/26 17:29:49 (5 months ago)
- Branches:
- main
- Children:
- 0792d02
- Parents:
- 2730163
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/components/userProfile/ArtistView.tsx
r2730163 rd2af1a6 1 1 import { useNavigate } from "react-router-dom"; 2 import { Music, Disc3 } from "lucide-react"; 2 import { useState } from "react"; 3 import { Music, Disc3, Play, Plus } from "lucide-react"; 3 4 import type { ArtistContribution } from "../../utils/types"; 5 import axiosInstance from "../../api/axiosInstance"; 4 6 5 7 interface ArtistViewProps { … … 9 11 const ArtistView = ({ contributions }: ArtistViewProps) => { 10 12 const navigate = useNavigate(); 11 12 const albums = contributions.filter((c) => c.entityType === "ALBUM"); 13 const songs = contributions.filter((c) => c.entityType === "SONG"); 13 const [items, setItems] = useState(contributions); 14 const [toast, setToast] = useState<{ message: string; show: boolean }>({ 15 message: "", 16 show: false, 17 }); 18 19 const albums = items.filter((c) => c.entityType === "ALBUM"); 20 const songs = items.filter((c) => c.entityType === "SONG"); 14 21 15 22 const getRoleColor = (role: string) => { 16 23 const colors: { [key: string]: string } = { 17 COMPOSER: "bg-purple- 500",18 PERFORMER: "bg-blue- 500",19 PRODUCER: "bg-green- 500",20 MAIN_VOCAL: "bg-pink- 500",24 COMPOSER: "bg-purple-100 text-purple-700", 25 PERFORMER: "bg-blue-100 text-blue-700", 26 PRODUCER: "bg-green-100 text-green-700", 27 MAIN_VOCAL: "bg-pink-100 text-pink-700", 21 28 }; 22 return colors[role] || "bg-gray-500"; 29 return colors[role] || "bg-gray-100 text-gray-700"; 30 }; 31 32 const showToast = (message: string) => { 33 setToast({ message, show: true }); 34 setTimeout(() => { 35 setToast({ message: "", show: false }); 36 }, 2000); 37 }; 38 39 const handleLike = async (id: number, title: string) => { 40 try { 41 const response = await axiosInstance.post(`/musical-entity/${id}/like`); 42 const data = response.data; 43 44 setItems((prevItems) => 45 prevItems.map((item) => 46 item.id === data.entityId 47 ? { ...item, isLikedByCurrentUser: data.isLiked } 48 : item, 49 ), 50 ); 51 52 showToast( 53 data.isLiked ? `Liked "${title}"` : `Removed ${title} from likes`, 54 ); 55 } catch (err: any) { 56 showToast(err.response?.data?.error); 57 } 23 58 }; 24 59 25 60 return ( 26 61 <div className="mt-8"> 62 {toast.show && ( 63 <div className="fixed bottom-8 left-1/2 -translate-x-1/2 z-50 animate-fade-in-up"> 64 <div className="bg-gray-900 text-white px-6 py-3 rounded-full shadow-lg text-sm font-medium"> 65 {toast.message} 66 </div> 67 </div> 68 )} 69 27 70 {albums.length > 0 && ( 28 71 <div className="mb-12"> 29 72 <div className="flex items-center gap-3 mb-6"> 30 <Disc3 className="w- 8 h-8 text-purple-600" />31 <h2 className="text- 3xl font-bold">Albums</h2>73 <Disc3 className="w-6 h-6 text-gray-700" /> 74 <h2 className="text-2xl font-bold text-gray-800">Albums</h2> 32 75 </div> 33 76 <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> 34 {albums.map((album , index) => (77 {albums.map((album) => ( 35 78 <div 36 key={ index}79 key={album.id} 37 80 className="group cursor-pointer" 38 onClick={() => 39 navigate(`/collection/album/${album.musicalEntityId}`) 40 } 81 onClick={() => navigate(`/collection/album/${album.id}`)} 41 82 > 42 <div className="relative aspect-square bg- linear-to-br from-purple-400 via-pink-400 to-blue-400 rounded-lg mb-3 overflow-hidden shadow-lg group-hover:shadow-2xl transition-all duration-300 group-hover:scale-105">83 <div className="relative aspect-square bg-gradient-to-br from-blue-400 to-purple-500 rounded-lg mb-3 overflow-hidden shadow-md group-hover:shadow-lg transition-all duration-300"> 43 84 <div className="absolute inset-0 flex items-center justify-center"> 44 <Disc3 className="w-20 h-20 text-white opacity-40" /> 45 </div> 46 <div className="absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/60 to-transparent p-4"> 85 <Disc3 className="w-16 h-16 text-white opacity-30" /> 86 </div> 87 88 <button 89 className="absolute top-2 right-2 p-2 bg-white/90 hover:bg-white rounded-full shadow-md transition-all duration-200 opacity-0 group-hover:opacity-100 cursor-pointer" 90 title={album.isLikedByCurrentUser ? "Unlike" : "Like"} 91 onClick={(e) => { 92 e.stopPropagation(); 93 handleLike(album.id, album.title); 94 }} 95 > 96 <svg 97 className="w-5 h-5" 98 fill={album.isLikedByCurrentUser ? "#ef4444" : "none"} 99 stroke={ 100 album.isLikedByCurrentUser ? "#ef4444" : "#6b7280" 101 } 102 strokeWidth="2" 103 viewBox="0 0 24 24" 104 > 105 <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 106 </svg> 107 </button> 108 109 <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/60 to-transparent p-3"> 47 110 <span 48 className={`text-xs px-2 py-1 rounded-full text-white${getRoleColor(album.role)}`}111 className={`text-xs px-2 py-1 rounded-full font-medium ${getRoleColor(album.role)}`} 49 112 > 50 113 {album.role.replace("_", " ")} … … 52 115 </div> 53 116 </div> 54 <h3 className="font-semibold text- base line-clamp-2group-hover:text-blue-600 transition-colors">117 <h3 className="font-semibold text-sm line-clamp-2 text-gray-900 group-hover:text-blue-600 transition-colors"> 55 118 {album.title} 56 119 </h3> … … 64 127 <div className="mb-12"> 65 128 <div className="flex items-center gap-3 mb-6"> 66 <Music className="w- 8 h-8 text-blue-600" />67 <h2 className="text- 3xl font-bold">Songs</h2>129 <Music className="w-6 h-6 text-gray-700" /> 130 <h2 className="text-2xl font-bold text-gray-800">Songs</h2> 68 131 </div> 69 132 <div className="space-y-2"> 70 {songs.map((song , index) => (133 {songs.map((song) => ( 71 134 <div 72 key={index} 73 className="group flex items-center gap-4 p-4 rounded-lg hover:bg-gray-50 transition-all cursor-pointer border border-transparent hover:border-gray-200" 74 onClick={() => 75 navigate(`/musical-entity/${song.musicalEntityId}`) 76 } 135 key={song.id} 136 className="group relative flex items-center gap-4 p-3 rounded-lg hover:bg-gray-50 transition-all cursor-pointer" 137 onClick={() => navigate(`/musical-entity/${song.id}`)} 77 138 > 78 <div className="shrink-0 w-14 h-14 bg-linear-to-br from-blue-400 to-cyan-400 rounded flex items-center justify-center group-hover:scale-110 transition-transform shadow-md"> 79 <Music className="w-7 h-7 text-white" /> 80 </div> 139 <div className="relative shrink-0"> 140 <div className="w-12 h-12 bg-gradient-to-br from-blue-400 to-purple-500 rounded flex items-center justify-center shadow-sm"> 141 <Music className="w-6 h-6 text-white" /> 142 </div> 143 <button 144 className="absolute inset-0 bg-black/60 rounded flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200" 145 aria-label="Play song" 146 > 147 <Play className="w-6 h-6 text-white fill-white" /> 148 </button> 149 </div> 150 81 151 <div className="flex-1 min-w-0"> 82 <h3 className="font-semibold text- lggroup-hover:text-blue-600 transition-colors truncate">152 <h3 className="font-semibold text-base text-gray-900 group-hover:text-blue-600 transition-colors truncate"> 83 153 {song.title} 84 154 </h3> 85 155 <div className="flex items-center gap-2 mt-1"> 86 {song.genre} 87 </div> 88 </div> 156 <span className="text-sm text-gray-600">{song.genre}</span> 157 </div> 158 </div> 159 89 160 <span 90 className={`px- 4 py-2 rounded-full text-white text-sm font-medium ${getRoleColor(song.role)} shadow-md`}161 className={`px-3 py-1 rounded-full text-sm font-medium ${getRoleColor(song.role)}`} 91 162 > 92 163 {song.role.replace("_", " ")} 93 164 </span> 165 166 <div className="flex items-center gap-2"> 167 <button 168 className="p-2 hover:bg-gray-200 rounded-full transition-colors duration-200 cursor-pointer" 169 title="Add to playlist" 170 > 171 <Plus className="w-5 h-5 text-gray-600" /> 172 </button> 173 174 <button 175 className="p-2 hover:bg-gray-200 rounded-full transition-colors duration-200 cursor-pointer" 176 title={song.isLikedByCurrentUser ? "Unlike" : "Like"} 177 onClick={(e) => { 178 e.stopPropagation(); 179 handleLike(song.id, song.title); 180 }} 181 > 182 <svg 183 className="w-5 h-5" 184 fill={song.isLikedByCurrentUser ? "#ef4444" : "none"} 185 stroke={song.isLikedByCurrentUser ? "#ef4444" : "#6b7280"} 186 strokeWidth="2" 187 viewBox="0 0 24 24" 188 > 189 <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 190 </svg> 191 </button> 192 </div> 94 193 </div> 95 194 ))} … … 100 199 {contributions.length === 0 && ( 101 200 <div className="flex flex-col items-center justify-center py-16 text-gray-400"> 102 <Music className="w-2 4 h-24mb-4 opacity-20" />103 <p className="text- xlfont-medium">No contributions yet</p>201 <Music className="w-20 h-20 mb-4 opacity-20" /> 202 <p className="text-lg font-medium">No contributions yet</p> 104 203 <p className="text-sm mt-2">Start creating music to see it here</p> 105 204 </div>
Note:
See TracChangeset
for help on using the changeset viewer.
