source: frontend/src/components/userProfile/ArtistView.tsx@ db5fb23

main
Last change on this file since db5fb23 was 1579b4f, checked in by Filip Gavrilovski <filipgavrilovski28@…>, 5 months ago

refactor artist, collection, listener components to have similar style as other components; refactor some dto-s

  • Property mode set to 100644
File size: 4.8 KB
RevLine 
[1579b4f]1import { Disc3, Music } from "lucide-react";
[d2af1a6]2import { useState } from "react";
[1579b4f]3import { useNavigate } from "react-router-dom";
4import { toast } from "react-toastify";
[d2af1a6]5import axiosInstance from "../../api/axiosInstance";
[1579b4f]6import type { ArtistContribution } from "../../utils/types";
7import SongItem from "../SongItem";
[fd81a18]8
9interface ArtistViewProps {
[1579b4f]10 contributions: ArtistContribution[];
[fd81a18]11}
12
13const ArtistView = ({ contributions }: ArtistViewProps) => {
[1579b4f]14 const navigate = useNavigate();
15 const [items, setItems] = useState(contributions);
16
17 const albums = items.filter((c) => c.entityType === "ALBUM");
18 const songs = items.filter((c) => c.entityType === "SONG");
19
20 const getRoleColor = (role: string) => {
21 const colors: { [key: string]: string } = {
22 COMPOSER: "bg-purple-500/20 text-purple-300",
23 PERFORMER: "bg-blue-500/20 text-blue-300",
24 PRODUCER: "bg-green-500/20 text-green-300",
25 MAIN_VOCAL: "bg-pink-500/20 text-pink-300",
26 };
27 return colors[role] || "bg-white/10 text-gray-300";
28 };
29
30 const handleLike = async (id: number, title: string) => {
31 try {
32 const response = await axiosInstance.post(`/musical-entity/${id}/like`);
33 const data = response.data;
34
35 setItems((prevItems) =>
36 prevItems.map((item) =>
37 item.id === data.entityId
38 ? { ...item, isLikedByCurrentUser: data.isLiked }
39 : item,
40 ),
41 );
42 toast.success(
43 data.isLiked ? `Liked "${title}"` : `Removed "${title}" from likes`,
44 );
45 } catch (err: any) {
46 toast.error(err.response?.data?.error || "Failed to like the item");
47 }
48 };
49
50 return (
51 <div className="mt-8">
52 {albums.length > 0 && (
53 <div className="mb-12">
54 <div className="flex items-center gap-3 mb-6">
55 <Disc3 className="w-6 h-6 text-[#1db954]" />
56 <h2 className="text-2xl font-bold text-white">Albums</h2>
57 </div>
58 <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
59 {albums.map((album) => (
60 <div
61 key={album.id}
62 className="group cursor-pointer"
63 onClick={() => navigate(`/collection/album/${album.id}`)}
64 >
65 <div className="relative aspect-square rounded-lg mb-3 overflow-hidden shadow-md group-hover:shadow-xl transition-all duration-300 bg-[#181818]">
66 <img
67 src={album.cover || "/favicon.png"}
68 alt={album.title}
69 className="w-full h-full object-cover"
70 onError={(e) => {
71 (e.target as HTMLImageElement).src = "/favicon.png";
72 }}
73 />
74
75 <button
76 className="absolute top-2 right-2 p-2 bg-black/70 hover:bg-black/90 rounded-full shadow-md transition-all duration-200 opacity-0 group-hover:opacity-100 cursor-pointer"
77 title={album.isLikedByCurrentUser ? "Unlike" : "Like"}
78 onClick={(e) => {
79 e.stopPropagation();
80 handleLike(album.id, album.title);
81 }}
82 >
83 <svg
84 className="w-5 h-5"
85 fill={album.isLikedByCurrentUser ? "#ef4444" : "none"}
86 stroke={
87 album.isLikedByCurrentUser ? "#ef4444" : "#9ca3af"
88 }
89 strokeWidth="2"
90 viewBox="0 0 24 24"
91 >
92 <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" />
93 </svg>
94 </button>
95
96 <div className="absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/80 to-transparent p-3">
97 <span
98 className={`text-xs px-2 py-1 rounded-full font-medium ${getRoleColor(album.role)}`}
99 >
100 {album.role.replace("_", " ")}
101 </span>
102 </div>
103 </div>
104 <h3 className="font-semibold text-sm line-clamp-2 text-white group-hover:text-[#1db954] transition-colors">
105 {album.title}
106 </h3>
107 </div>
108 ))}
109 </div>
110 </div>
111 )}
112
113 {songs.length > 0 && (
114 <div className="mb-12">
115 <div className="flex items-center gap-3 mb-6">
116 <Music className="w-6 h-6 text-[#1db954]" />
117 <h2 className="text-2xl font-bold text-white">Songs</h2>
118 </div>
119 <div className="space-y-1">
120 {songs.map((song) => (
121 <SongItem
122 key={song.id}
123 song={{
124 id: song.id,
125 title: song.title,
126 cover: song.cover,
127 genre: song.genre,
128 link: song.link,
129 isLikedByCurrentUser: song.isLikedByCurrentUser,
130 }}
131 role={song.role}
132 onLikeToggle={() => handleLike(song.id, song.title)}
133 />
134 ))}
135 </div>
136 </div>
137 )}
138
139 {contributions.length === 0 && (
140 <div className="flex flex-col items-center justify-center py-16 text-gray-500">
141 <Music className="w-20 h-20 mb-4 opacity-20" />
142 <p className="text-lg font-medium">No contributions yet</p>
143 <p className="text-sm mt-2">Start creating music to see it here</p>
144 </div>
145 )}
146 </div>
147 );
[fd81a18]148};
149
150export default ArtistView;
Note: See TracBrowser for help on using the repository browser.