Ignore:
Timestamp:
02/10/26 21:58:39 (5 months ago)
Author:
Filip Gavrilovski <filipgavrilovski28@…>
Branches:
main
Children:
92db381
Parents:
0808ef2
Message:

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/components/userProfile/ArtistView.tsx

    r0808ef2 r1579b4f  
     1import { Disc3, Music } from "lucide-react";
     2import { useState } from "react";
    13import { useNavigate } from "react-router-dom";
    2 import { useState } from "react";
    3 import { Music, Disc3, Play, Plus } from "lucide-react";
     4import { toast } from "react-toastify";
     5import axiosInstance from "../../api/axiosInstance";
    46import type { ArtistContribution } from "../../utils/types";
    5 import axiosInstance from "../../api/axiosInstance";
     7import SongItem from "../SongItem";
    68
    79interface ArtistViewProps {
    8   contributions: ArtistContribution[];
     10        contributions: ArtistContribution[];
    911}
    1012
    1113const 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   });
     14        const navigate = useNavigate();
     15        const [items, setItems] = useState(contributions);
    1816
    19   const albums = items.filter((c) => c.entityType === "ALBUM");
    20   const songs = items.filter((c) => c.entityType === "SONG");
     17        const albums = items.filter((c) => c.entityType === "ALBUM");
     18        const songs = items.filter((c) => c.entityType === "SONG");
    2119
    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   };
     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        };
    3129
    32   const showToast = (message: string) => {
    33     setToast({ message, show: true });
    34     setTimeout(() => {
    35       setToast({ message: "", show: false });
    36     }, 2000);
    37   };
     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;
    3834
    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;
     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        };
    4349
    44       setItems((prevItems) =>
    45         prevItems.map((item) =>
    46           item.id === data.entityId
    47             ? { ...item, isLikedByCurrentUser: data.isLiked }
    48             : item,
    49         ),
    50       );
     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                                                                        />
    5174
    52       showToast(
    53         data.isLiked ? `Liked "${title}"` : `Removed ${title} from likes`,
    54       );
    55     } catch (err: any) {
    56       showToast(err.response?.data?.error);
    57     }
    58   };
     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>
    5995
    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       )}
     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                        )}
    69112
    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>
     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                        )}
    87138
    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   );
     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        );
    208148};
    209149
Note: See TracChangeset for help on using the changeset viewer.