source: frontend/src/components/userProfile/ArtistView.tsx@ 929e93c

main
Last change on this file since 929e93c was d2af1a6, checked in by Dimitar Arsov <dimitararsov04@…>, 5 months ago

add song/album to favourites

  • Property mode set to 100644
File size: 8.3 KB
Line 
1import { useNavigate } from "react-router-dom";
2import { useState } from "react";
3import { Music, Disc3, Play, Plus } from "lucide-react";
4import type { ArtistContribution } from "../../utils/types";
5import axiosInstance from "../../api/axiosInstance";
6
7interface ArtistViewProps {
8 contributions: ArtistContribution[];
9}
10
11const ArtistView = ({ contributions }: ArtistViewProps) => {
12 const navigate = useNavigate();
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");
21
22 const getRoleColor = (role: string) => {
23 const colors: { [key: string]: string } = {
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",
28 };
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 }
58 };
59
60 return (
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
70 {albums.length > 0 && (
71 <div className="mb-12">
72 <div className="flex items-center gap-3 mb-6">
73 <Disc3 className="w-6 h-6 text-gray-700" />
74 <h2 className="text-2xl font-bold text-gray-800">Albums</h2>
75 </div>
76 <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
77 {albums.map((album) => (
78 <div
79 key={album.id}
80 className="group cursor-pointer"
81 onClick={() => navigate(`/collection/album/${album.id}`)}
82 >
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">
84 <div className="absolute inset-0 flex items-center justify-center">
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">
110 <span
111 className={`text-xs px-2 py-1 rounded-full font-medium ${getRoleColor(album.role)}`}
112 >
113 {album.role.replace("_", " ")}
114 </span>
115 </div>
116 </div>
117 <h3 className="font-semibold text-sm line-clamp-2 text-gray-900 group-hover:text-blue-600 transition-colors">
118 {album.title}
119 </h3>
120 </div>
121 ))}
122 </div>
123 </div>
124 )}
125
126 {songs.length > 0 && (
127 <div className="mb-12">
128 <div className="flex items-center gap-3 mb-6">
129 <Music className="w-6 h-6 text-gray-700" />
130 <h2 className="text-2xl font-bold text-gray-800">Songs</h2>
131 </div>
132 <div className="space-y-2">
133 {songs.map((song) => (
134 <div
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}`)}
138 >
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
151 <div className="flex-1 min-w-0">
152 <h3 className="font-semibold text-base text-gray-900 group-hover:text-blue-600 transition-colors truncate">
153 {song.title}
154 </h3>
155 <div className="flex items-center gap-2 mt-1">
156 <span className="text-sm text-gray-600">{song.genre}</span>
157 </div>
158 </div>
159
160 <span
161 className={`px-3 py-1 rounded-full text-sm font-medium ${getRoleColor(song.role)}`}
162 >
163 {song.role.replace("_", " ")}
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>
193 </div>
194 ))}
195 </div>
196 </div>
197 )}
198
199 {contributions.length === 0 && (
200 <div className="flex flex-col items-center justify-center py-16 text-gray-400">
201 <Music className="w-20 h-20 mb-4 opacity-20" />
202 <p className="text-lg font-medium">No contributions yet</p>
203 <p className="text-sm mt-2">Start creating music to see it here</p>
204 </div>
205 )}
206 </div>
207 );
208};
209
210export default ArtistView;
Note: See TracBrowser for help on using the repository browser.