| [fd81a18] | 1 | import { useNavigate } from "react-router-dom";
|
|---|
| [d2af1a6] | 2 | import { useState } from "react";
|
|---|
| 3 | import { Music, Disc3, Play, Plus } from "lucide-react";
|
|---|
| [98992cf] | 4 | import type { ArtistContribution } from "../../utils/types";
|
|---|
| [d2af1a6] | 5 | import axiosInstance from "../../api/axiosInstance";
|
|---|
| [fd81a18] | 6 |
|
|---|
| 7 | interface ArtistViewProps {
|
|---|
| [98992cf] | 8 | contributions: ArtistContribution[];
|
|---|
| [fd81a18] | 9 | }
|
|---|
| 10 |
|
|---|
| 11 | const ArtistView = ({ contributions }: ArtistViewProps) => {
|
|---|
| 12 | const navigate = useNavigate();
|
|---|
| [d2af1a6] | 13 | const [items, setItems] = useState(contributions);
|
|---|
| 14 | const [toast, setToast] = useState<{ message: string; show: boolean }>({
|
|---|
| 15 | message: "",
|
|---|
| 16 | show: false,
|
|---|
| 17 | });
|
|---|
| [fd81a18] | 18 |
|
|---|
| [d2af1a6] | 19 | const albums = items.filter((c) => c.entityType === "ALBUM");
|
|---|
| 20 | const songs = items.filter((c) => c.entityType === "SONG");
|
|---|
| [a3ae097] | 21 |
|
|---|
| 22 | const getRoleColor = (role: string) => {
|
|---|
| 23 | const colors: { [key: string]: string } = {
|
|---|
| [d2af1a6] | 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",
|
|---|
| [a3ae097] | 28 | };
|
|---|
| [d2af1a6] | 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 | }
|
|---|
| [a3ae097] | 58 | };
|
|---|
| 59 |
|
|---|
| [fd81a18] | 60 | return (
|
|---|
| [a3ae097] | 61 | <div className="mt-8">
|
|---|
| [d2af1a6] | 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 |
|
|---|
| [a3ae097] | 70 | {albums.length > 0 && (
|
|---|
| 71 | <div className="mb-12">
|
|---|
| 72 | <div className="flex items-center gap-3 mb-6">
|
|---|
| [d2af1a6] | 73 | <Disc3 className="w-6 h-6 text-gray-700" />
|
|---|
| 74 | <h2 className="text-2xl font-bold text-gray-800">Albums</h2>
|
|---|
| [a3ae097] | 75 | </div>
|
|---|
| 76 | <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
|---|
| [d2af1a6] | 77 | {albums.map((album) => (
|
|---|
| [a3ae097] | 78 | <div
|
|---|
| [d2af1a6] | 79 | key={album.id}
|
|---|
| [a3ae097] | 80 | className="group cursor-pointer"
|
|---|
| [d2af1a6] | 81 | onClick={() => navigate(`/collection/album/${album.id}`)}
|
|---|
| [a3ae097] | 82 | >
|
|---|
| [d2af1a6] | 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">
|
|---|
| [a3ae097] | 84 | <div className="absolute inset-0 flex items-center justify-center">
|
|---|
| [d2af1a6] | 85 | <Disc3 className="w-16 h-16 text-white opacity-30" />
|
|---|
| [a3ae097] | 86 | </div>
|
|---|
| [d2af1a6] | 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">
|
|---|
| [a3ae097] | 110 | <span
|
|---|
| [d2af1a6] | 111 | className={`text-xs px-2 py-1 rounded-full font-medium ${getRoleColor(album.role)}`}
|
|---|
| [a3ae097] | 112 | >
|
|---|
| 113 | {album.role.replace("_", " ")}
|
|---|
| 114 | </span>
|
|---|
| 115 | </div>
|
|---|
| 116 | </div>
|
|---|
| [d2af1a6] | 117 | <h3 className="font-semibold text-sm line-clamp-2 text-gray-900 group-hover:text-blue-600 transition-colors">
|
|---|
| [a3ae097] | 118 | {album.title}
|
|---|
| 119 | </h3>
|
|---|
| [fd81a18] | 120 | </div>
|
|---|
| [a3ae097] | 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">
|
|---|
| [d2af1a6] | 129 | <Music className="w-6 h-6 text-gray-700" />
|
|---|
| 130 | <h2 className="text-2xl font-bold text-gray-800">Songs</h2>
|
|---|
| [a3ae097] | 131 | </div>
|
|---|
| 132 | <div className="space-y-2">
|
|---|
| [d2af1a6] | 133 | {songs.map((song) => (
|
|---|
| [a3ae097] | 134 | <div
|
|---|
| [d2af1a6] | 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}`)}
|
|---|
| [a3ae097] | 138 | >
|
|---|
| [d2af1a6] | 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>
|
|---|
| [a3ae097] | 149 | </div>
|
|---|
| [d2af1a6] | 150 |
|
|---|
| [a3ae097] | 151 | <div className="flex-1 min-w-0">
|
|---|
| [d2af1a6] | 152 | <h3 className="font-semibold text-base text-gray-900 group-hover:text-blue-600 transition-colors truncate">
|
|---|
| [a3ae097] | 153 | {song.title}
|
|---|
| 154 | </h3>
|
|---|
| 155 | <div className="flex items-center gap-2 mt-1">
|
|---|
| [d2af1a6] | 156 | <span className="text-sm text-gray-600">{song.genre}</span>
|
|---|
| [a3ae097] | 157 | </div>
|
|---|
| 158 | </div>
|
|---|
| [d2af1a6] | 159 |
|
|---|
| [a3ae097] | 160 | <span
|
|---|
| [d2af1a6] | 161 | className={`px-3 py-1 rounded-full text-sm font-medium ${getRoleColor(song.role)}`}
|
|---|
| [a3ae097] | 162 | >
|
|---|
| 163 | {song.role.replace("_", " ")}
|
|---|
| [fd81a18] | 164 | </span>
|
|---|
| [d2af1a6] | 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>
|
|---|
| [fd81a18] | 193 | </div>
|
|---|
| [a3ae097] | 194 | ))}
|
|---|
| [fd81a18] | 195 | </div>
|
|---|
| [a3ae097] | 196 | </div>
|
|---|
| 197 | )}
|
|---|
| 198 |
|
|---|
| [fd81a18] | 199 | {contributions.length === 0 && (
|
|---|
| [a3ae097] | 200 | <div className="flex flex-col items-center justify-center py-16 text-gray-400">
|
|---|
| [d2af1a6] | 201 | <Music className="w-20 h-20 mb-4 opacity-20" />
|
|---|
| 202 | <p className="text-lg font-medium">No contributions yet</p>
|
|---|
| [a3ae097] | 203 | <p className="text-sm mt-2">Start creating music to see it here</p>
|
|---|
| 204 | </div>
|
|---|
| [fd81a18] | 205 | )}
|
|---|
| 206 | </div>
|
|---|
| 207 | );
|
|---|
| 208 | };
|
|---|
| 209 |
|
|---|
| 210 | export default ArtistView;
|
|---|