Ignore:
Timestamp:
02/21/26 19:19:51 (5 months ago)
Author:
Filip Gavrilovski <filipgavrilovski28@…>
Branches:
main
Parents:
765e166
Message:

add cursor pointer to buttons

File:
1 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/components/Sidebar.tsx

    r765e166 r947fcaa  
    55import { useAuth } from "../context/authContext";
    66import { usePlayer } from "../context/playerContext";
     7import { useCreatedPlaylists } from "../context/playlistContext";
     8import { getErrorMessage } from "../utils/error";
    79import type { BasicSong, SidebarProps } from "../utils/types";
    810import { toEmbedUrl } from "../utils/utils";
    9 import { useCreatedPlaylists } from "../context/playlistContext";
    1011import CreatePlaylistModal from "./playlist/CreatePlaylistModal";
    11 import { getErrorMessage } from "../utils/error";
    1212
    1313const Sidebar = ({ isOpen, onClose }: SidebarProps) => {
    14   const { user } = useAuth();
    15   const {
    16     createdPlaylists,
    17     isLoading: playlistsLoading,
    18     refreshPlaylists,
    19   } = useCreatedPlaylists();
    20   const navigate = useNavigate();
    21   const { play, currentSong } = usePlayer();
    22   const [recentlyListened, setRecentlyListened] = useState<BasicSong[]>([]);
    23   const [songsLoading, setSongsLoading] = useState(false);
    24   const [isModalOpen, setIsModalOpen] = useState(false);
    25 
    26   useEffect(() => {
    27     const fetchData = async () => {
    28       setSongsLoading(true);
    29       try {
    30         const data = await axiosInstance.get<BasicSong[]>("/songs/recent");
    31         setRecentlyListened(data.data);
    32       } catch (error) {
    33         toast.error(getErrorMessage(error));
    34       } finally {
    35         setSongsLoading(false);
    36       }
    37     };
    38     if (user) {
    39       fetchData();
    40     } else {
    41       setRecentlyListened([]);
    42       setSongsLoading(false);
    43     }
    44   }, [user]);
    45 
    46   const isLoading = songsLoading || playlistsLoading;
    47 
    48   return (
    49     <>
    50       <div
    51         className={`fixed left-0 top-0 h-full bg-[#121212] border-r border-white/10 transition-transform duration-300 ease-in-out z-40 ${
    52           isOpen ? "translate-x-0" : "-translate-x-full"
    53         } w-64 overflow-y-auto`}
    54       >
    55         <div className="p-6">
    56           {/* Sidebar Header */}
    57           <div className="flex justify-between items-center mb-6 pt-2">
    58             <h2 className="text-xl font-bold text-white">Library</h2>
    59             <button
    60               onClick={onClose}
    61               className="text-gray-400 hover:text-white transition-colors"
    62               aria-label="Close sidebar"
    63             >
    64               <svg
    65                 className="w-6 h-6"
    66                 fill="none"
    67                 stroke="currentColor"
    68                 viewBox="0 0 24 24"
    69               >
    70                 <path
    71                   strokeLinecap="round"
    72                   strokeLinejoin="round"
    73                   strokeWidth={2}
    74                   d="M6 18L18 6M6 6l12 12"
    75                 />
    76               </svg>
    77             </button>
    78           </div>
    79 
    80           {/* Loading State */}
    81           {isLoading ? (
    82             <div className="flex items-center justify-center py-16">
    83               <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#1db954]"></div>
    84             </div>
    85           ) : (
    86             <>
    87               {/* Recently Listened */}
    88               <div className="mb-8">
    89                 <h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">
    90                   Recently Played
    91                 </h3>
    92                 <div className="space-y-3">
    93                   {recentlyListened.map((song) => (
    94                     <div
    95                       key={song.id}
    96                       className="flex items-center gap-3 p-2 rounded-lg hover:bg-white/5 cursor-pointer transition-colors group relative"
    97                     >
    98                       <img
    99                         src={
    100                           song.cover
    101                             ? `${baseURL}/${song.cover}`
    102                             : "/favicon.png"
    103                         }
    104                         alt={song.title}
    105                         className="w-10 h-10 rounded object-cover"
    106                         onError={(e) => {
    107                           (e.target as HTMLImageElement).src = "/favicon.png";
    108                         }}
    109                       />
    110                       <div className="flex-1 min-w-0">
    111                         <p
    112                           onClick={() => navigate(`/songs/${song.id}`)}
    113                           className="text-sm font-medium text-white truncate hover:underline cursor-pointer"
    114                         >
    115                           {song.title}
    116                         </p>
    117                         <div className="flex items-center gap-1 text-xs text-gray-400">
    118                           <span
    119                             onClick={(e) => {
    120                               e.stopPropagation();
    121                               if (song.artistUsername) {
    122                                 navigate(`/users/${song.artistUsername}`);
    123                               }
    124                             }}
    125                             className={`truncate ${
    126                               song.artistUsername
    127                                 ? "hover:underline cursor-pointer hover:text-white"
    128                                 : ""
    129                             }`}
    130                           >
    131                             {song.artist}
    132                           </span>
    133                           {song.album && (
    134                             <>
    135                               <span>•</span>
    136                               <span
    137                                 onClick={(e) => {
    138                                   e.stopPropagation();
    139                                   if (song.albumId) {
    140                                     navigate(
    141                                       `/collection/album/${song.albumId}`,
    142                                     );
    143                                   }
    144                                 }}
    145                                 className={`truncate ${
    146                                   song.albumId
    147                                     ? "hover:underline cursor-pointer hover:text-white"
    148                                     : ""
    149                                 }`}
    150                               >
    151                                 {song.album}
    152                               </span>
    153                             </>
    154                           )}
    155                         </div>
    156                       </div>
    157                       {song.link && (
    158                         <button
    159                           onClick={(e) => {
    160                             e.stopPropagation();
    161                             play({
    162                               id: song.id,
    163                               title: song.title,
    164                               artist: song.artist,
    165                               cover: song.cover,
    166                               embedUrl: toEmbedUrl(song.link!),
    167                             });
    168                           }}
    169                           className={`p-1.5 rounded-full transition-all opacity-0 group-hover:opacity-100 ${
    170                             currentSong?.id === song.id
    171                               ? "bg-white text-[#1db954]"
    172                               : "bg-[#1db954] text-black hover:scale-110"
    173                           }`}
    174                           aria-label={
    175                             currentSong?.id === song.id
    176                               ? "Now playing"
    177                               : "Play song"
    178                           }
    179                         >
    180                           {currentSong?.id === song.id ? (
    181                             <svg
    182                               className="w-4 h-4"
    183                               fill="currentColor"
    184                               viewBox="0 0 24 24"
    185                             >
    186                               <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
    187                             </svg>
    188                           ) : (
    189                             <svg
    190                               className="w-4 h-4"
    191                               fill="currentColor"
    192                               viewBox="0 0 24 24"
    193                             >
    194                               <path d="M8 5v14l11-7z" />
    195                             </svg>
    196                           )}
    197                         </button>
    198                       )}
    199                     </div>
    200                   ))}
    201                 </div>
    202               </div>
    203 
    204               {/* Playlists */}
    205               <div>
    206                 <h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">
    207                   Your Playlists
    208                 </h3>
    209                 <div className="space-y-2">
    210                   {createdPlaylists?.map((playlist) => (
    211                     <div
    212                       key={playlist.id}
    213                       onClick={() => {
    214                         navigate(`/collection/playlist/${playlist.id}`);
    215                       }}
    216                       className="flex items-center justify-between p-3 rounded-lg hover:bg-white/5 cursor-pointer transition-colors"
    217                     >
    218                       <div className="flex items-center gap-3">
    219                         <div className="w-10 h-10 bg-linear-to-br from-purple-500 to-pink-500 rounded flex items-center justify-center">
    220                           <svg
    221                             className="w-5 h-5 text-white"
    222                             fill="currentColor"
    223                             viewBox="0 0 20 20"
    224                           >
    225                             <path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v9.114A4.369 4.369 0 005 14c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.37 4.37 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z" />
    226                           </svg>
    227                         </div>
    228                         <div>
    229                           <p className="text-sm font-medium text-white">
    230                             {playlist.name}
    231                           </p>
    232                           <p className="text-xs text-gray-400">
    233                             {playlist.songCount} songs
    234                           </p>
    235                         </div>
    236                       </div>
    237                     </div>
    238                   ))}
    239 
    240                   {/* Create Playlist Button */}
    241                   <button
    242                     onClick={() => setIsModalOpen(true)}
    243                     className="w-full flex items-center gap-3 p-3 rounded-lg hover:bg-white/5 cursor-pointer transition-colors group mt-2"
    244                   >
    245                     <div className="w-10 h-10 bg-[#282828] group-hover:bg-[#3a3a3a] rounded flex items-center justify-center transition-colors">
    246                       <svg
    247                         className="w-5 h-5 text-gray-400 group-hover:text-white transition-colors"
    248                         fill="none"
    249                         stroke="currentColor"
    250                         viewBox="0 0 24 24"
    251                       >
    252                         <path
    253                           strokeLinecap="round"
    254                           strokeLinejoin="round"
    255                           strokeWidth={2}
    256                           d="M12 4v16m8-8H4"
    257                         />
    258                       </svg>
    259                     </div>
    260                     <p className="text-sm font-medium text-gray-400 group-hover:text-white transition-colors">
    261                       Create Playlist
    262                     </p>
    263                   </button>
    264                 </div>
    265               </div>
    266             </>
    267           )}
    268         </div>
    269       </div>
    270 
    271       <CreatePlaylistModal
    272         isOpen={isModalOpen}
    273         onClose={() => setIsModalOpen(false)}
    274         onSuccess={() => refreshPlaylists(false)}
    275         songId={null}
    276       />
    277     </>
    278   );
     14        const { user } = useAuth();
     15        const {
     16                createdPlaylists,
     17                isLoading: playlistsLoading,
     18                refreshPlaylists,
     19        } = useCreatedPlaylists();
     20        const navigate = useNavigate();
     21        const { play, currentSong } = usePlayer();
     22        const [recentlyListened, setRecentlyListened] = useState<BasicSong[]>([]);
     23        const [songsLoading, setSongsLoading] = useState(false);
     24        const [isModalOpen, setIsModalOpen] = useState(false);
     25
     26        useEffect(() => {
     27                const fetchData = async () => {
     28                        setSongsLoading(true);
     29                        try {
     30                                const data = await axiosInstance.get<BasicSong[]>("/songs/recent");
     31                                setRecentlyListened(data.data);
     32                        } catch (error) {
     33                                toast.error(getErrorMessage(error));
     34                        } finally {
     35                                setSongsLoading(false);
     36                        }
     37                };
     38                if (user) {
     39                        fetchData();
     40                } else {
     41                        setRecentlyListened([]);
     42                        setSongsLoading(false);
     43                }
     44        }, [user]);
     45
     46        const isLoading = songsLoading || playlistsLoading;
     47
     48        return (
     49                <>
     50                        <div
     51                                className={`fixed left-0 top-0 h-full bg-[#121212] border-r border-white/10 transition-transform duration-300 ease-in-out z-40 ${
     52                                        isOpen ? "translate-x-0" : "-translate-x-full"
     53                                } w-64 overflow-y-auto`}
     54                        >
     55                                <div className="p-6">
     56                                        {/* Sidebar Header */}
     57                                        <div className="flex justify-between items-center mb-6 pt-2">
     58                                                <h2 className="text-xl font-bold text-white">Library</h2>
     59                                                <button
     60                                                        onClick={onClose}
     61                                                        className="text-gray-400 hover:text-white transition-colors cursor-pointer"
     62                                                        aria-label="Close sidebar"
     63                                                >
     64                                                        <svg
     65                                                                className="w-6 h-6"
     66                                                                fill="none"
     67                                                                stroke="currentColor"
     68                                                                viewBox="0 0 24 24"
     69                                                        >
     70                                                                <path
     71                                                                        strokeLinecap="round"
     72                                                                        strokeLinejoin="round"
     73                                                                        strokeWidth={2}
     74                                                                        d="M6 18L18 6M6 6l12 12"
     75                                                                />
     76                                                        </svg>
     77                                                </button>
     78                                        </div>
     79
     80                                        {/* Loading State */}
     81                                        {isLoading ? (
     82                                                <div className="flex items-center justify-center py-16">
     83                                                        <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#1db954]"></div>
     84                                                </div>
     85                                        ) : (
     86                                                <>
     87                                                        {/* Recently Listened */}
     88                                                        <div className="mb-8">
     89                                                                <h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">
     90                                                                        Recently Played
     91                                                                </h3>
     92                                                                <div className="space-y-3">
     93                                                                        {recentlyListened.map((song) => (
     94                                                                                <div
     95                                                                                        key={song.id}
     96                                                                                        className="flex items-center gap-3 p-2 rounded-lg hover:bg-white/5 cursor-pointer transition-colors group relative"
     97                                                                                >
     98                                                                                        <img
     99                                                                                                src={
     100                                                                                                        song.cover
     101                                                                                                                ? `${baseURL}/${song.cover}`
     102                                                                                                                : "/favicon.png"
     103                                                                                                }
     104                                                                                                alt={song.title}
     105                                                                                                className="w-10 h-10 rounded object-cover"
     106                                                                                                onError={(e) => {
     107                                                                                                        (e.target as HTMLImageElement).src = "/favicon.png";
     108                                                                                                }}
     109                                                                                        />
     110                                                                                        <div className="flex-1 min-w-0">
     111                                                                                                <p
     112                                                                                                        onClick={() => navigate(`/songs/${song.id}`)}
     113                                                                                                        className="text-sm font-medium text-white truncate hover:underline cursor-pointer"
     114                                                                                                >
     115                                                                                                        {song.title}
     116                                                                                                </p>
     117                                                                                                <div className="flex items-center gap-1 text-xs text-gray-400">
     118                                                                                                        <span
     119                                                                                                                onClick={(e) => {
     120                                                                                                                        e.stopPropagation();
     121                                                                                                                        if (song.artistUsername) {
     122                                                                                                                                navigate(`/users/${song.artistUsername}`);
     123                                                                                                                        }
     124                                                                                                                }}
     125                                                                                                                className={`truncate ${
     126                                                                                                                        song.artistUsername
     127                                                                                                                                ? "hover:underline cursor-pointer hover:text-white"
     128                                                                                                                                : ""
     129                                                                                                                }`}
     130                                                                                                        >
     131                                                                                                                {song.artist}
     132                                                                                                        </span>
     133                                                                                                        {song.album && (
     134                                                                                                                <>
     135                                                                                                                        <span>•</span>
     136                                                                                                                        <span
     137                                                                                                                                onClick={(e) => {
     138                                                                                                                                        e.stopPropagation();
     139                                                                                                                                        if (song.albumId) {
     140                                                                                                                                                navigate(
     141                                                                                                                                                        `/collection/album/${song.albumId}`,
     142                                                                                                                                                );
     143                                                                                                                                        }
     144                                                                                                                                }}
     145                                                                                                                                className={`truncate ${
     146                                                                                                                                        song.albumId
     147                                                                                                                                                ? "hover:underline cursor-pointer hover:text-white"
     148                                                                                                                                                : ""
     149                                                                                                                                }`}
     150                                                                                                                        >
     151                                                                                                                                {song.album}
     152                                                                                                                        </span>
     153                                                                                                                </>
     154                                                                                                        )}
     155                                                                                                </div>
     156                                                                                        </div>
     157                                                                                        {song.link && (
     158                                                                                                <button
     159                                                                                                        onClick={(e) => {
     160                                                                                                                e.stopPropagation();
     161                                                                                                                play({
     162                                                                                                                        id: song.id,
     163                                                                                                                        title: song.title,
     164                                                                                                                        artist: song.artist,
     165                                                                                                                        cover: song.cover,
     166                                                                                                                        embedUrl: toEmbedUrl(song.link!),
     167                                                                                                                });
     168                                                                                                        }}
     169                                                                                                        className={`p-1.5 rounded-full transition-all opacity-0 group-hover:opacity-100 ${
     170                                                                                                                currentSong?.id === song.id
     171                                                                                                                        ? "bg-white text-[#1db954]"
     172                                                                                                                        : "bg-[#1db954] text-black hover:scale-110 cursor-pointer"
     173                                                                                                        }`}
     174                                                                                                        aria-label={
     175                                                                                                                currentSong?.id === song.id
     176                                                                                                                        ? "Now playing"
     177                                                                                                                        : "Play song"
     178                                                                                                        }
     179                                                                                                >
     180                                                                                                        {currentSong?.id === song.id ? (
     181                                                                                                                <svg
     182                                                                                                                        className="w-4 h-4"
     183                                                                                                                        fill="currentColor"
     184                                                                                                                        viewBox="0 0 24 24"
     185                                                                                                                >
     186                                                                                                                        <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
     187                                                                                                                </svg>
     188                                                                                                        ) : (
     189                                                                                                                <svg
     190                                                                                                                        className="w-4 h-4"
     191                                                                                                                        fill="currentColor"
     192                                                                                                                        viewBox="0 0 24 24"
     193                                                                                                                >
     194                                                                                                                        <path d="M8 5v14l11-7z" />
     195                                                                                                                </svg>
     196                                                                                                        )}
     197                                                                                                </button>
     198                                                                                        )}
     199                                                                                </div>
     200                                                                        ))}
     201                                                                </div>
     202                                                        </div>
     203
     204                                                        {/* Playlists */}
     205                                                        <div>
     206                                                                <h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">
     207                                                                        Your Playlists
     208                                                                </h3>
     209                                                                <div className="space-y-2">
     210                                                                        {createdPlaylists?.map((playlist) => (
     211                                                                                <div
     212                                                                                        key={playlist.id}
     213                                                                                        onClick={() => {
     214                                                                                                navigate(`/collection/playlist/${playlist.id}`);
     215                                                                                        }}
     216                                                                                        className="flex items-center justify-between p-3 rounded-lg hover:bg-white/5 cursor-pointer transition-colors"
     217                                                                                >
     218                                                                                        <div className="flex items-center gap-3">
     219                                                                                                <div className="w-10 h-10 bg-linear-to-br from-purple-500 to-pink-500 rounded flex items-center justify-center">
     220                                                                                                        <svg
     221                                                                                                                className="w-5 h-5 text-white"
     222                                                                                                                fill="currentColor"
     223                                                                                                                viewBox="0 0 20 20"
     224                                                                                                        >
     225                                                                                                                <path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v9.114A4.369 4.369 0 005 14c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.37 4.37 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z" />
     226                                                                                                        </svg>
     227                                                                                                </div>
     228                                                                                                <div>
     229                                                                                                        <p className="text-sm font-medium text-white">
     230                                                                                                                {playlist.name}
     231                                                                                                        </p>
     232                                                                                                        <p className="text-xs text-gray-400">
     233                                                                                                                {playlist.songCount} songs
     234                                                                                                        </p>
     235                                                                                                </div>
     236                                                                                        </div>
     237                                                                                </div>
     238                                                                        ))}
     239
     240                                                                        {/* Create Playlist Button */}
     241                                                                        <button
     242                                                                                onClick={() => setIsModalOpen(true)}
     243                                                                                className="w-full flex items-center gap-3 p-3 rounded-lg hover:bg-white/5 cursor-pointer transition-colors group mt-2"
     244                                                                        >
     245                                                                                <div className="w-10 h-10 bg-[#282828] group-hover:bg-[#3a3a3a] rounded flex items-center justify-center transition-colors">
     246                                                                                        <svg
     247                                                                                                className="w-5 h-5 text-gray-400 group-hover:text-white transition-colors"
     248                                                                                                fill="none"
     249                                                                                                stroke="currentColor"
     250                                                                                                viewBox="0 0 24 24"
     251                                                                                        >
     252                                                                                                <path
     253                                                                                                        strokeLinecap="round"
     254                                                                                                        strokeLinejoin="round"
     255                                                                                                        strokeWidth={2}
     256                                                                                                        d="M12 4v16m8-8H4"
     257                                                                                                />
     258                                                                                        </svg>
     259                                                                                </div>
     260                                                                                <p className="text-sm font-medium text-gray-400 group-hover:text-white transition-colors">
     261                                                                                        Create Playlist
     262                                                                                </p>
     263                                                                        </button>
     264                                                                </div>
     265                                                        </div>
     266                                                </>
     267                                        )}
     268                                </div>
     269                        </div>
     270
     271                        <CreatePlaylistModal
     272                                isOpen={isModalOpen}
     273                                onClose={() => setIsModalOpen(false)}
     274                                onSuccess={() => refreshPlaylists(false)}
     275                                songId={null}
     276                        />
     277                </>
     278        );
    279279};
    280280
Note: See TracChangeset for help on using the changeset viewer.