Ignore:
Timestamp:
02/05/26 10:29:09 (5 months ago)
Author:
Dimitar Arsov <dimitararsov04@…>
Branches:
main
Children:
3de009b
Parents:
d2af1a6
Message:

show saved playlists by user

Location:
frontend/src/components/userProfile
Files:
2 edited

Legend:

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

    rd2af1a6 r0792d02  
    44import axiosInstance from "../../api/axiosInstance";
    55import { useState } from "react";
     6
    67interface ListenerViewProps {
    78  likedEntities: MusicalEntity[] | [];
    8   playlists: Playlist[] | [];
     9  createdPlaylists: Playlist[] | [];
     10  savedPlaylists: Playlist[] | [];
    911}
    1012
    11 const ListenerView = ({ likedEntities, playlists }: ListenerViewProps) => {
     13const ListenerView = ({
     14  likedEntities,
     15  createdPlaylists,
     16  savedPlaylists,
     17}: ListenerViewProps) => {
    1218  const navigate = useNavigate();
    1319  const [items, setItems] = useState(likedEntities);
     20  const [savedItems, setSavedItems] = useState(savedPlaylists);
    1421  const [toast, setToast] = useState<{ message: string; show: boolean }>({
    1522    message: "",
     
    2734  const likedAlbums = items.filter((e) => e.type === "ALBUM");
    2835
    29   const handleSavePlaylist = (e: React.MouseEvent, playlistId: number) => {
     36  const handleSavePlaylist = async (
     37    e: React.MouseEvent,
     38    playlistId: number,
     39    playlistName: string,
     40  ) => {
    3041    e.stopPropagation();
    3142
    32     console.log("Save playlist:", playlistId);
     43    try {
     44      const response = await axiosInstance.post(`/playlist/${playlistId}/save`);
     45      const data = response.data;
     46
     47      setSavedItems((prevItems) =>
     48        data.isSaved
     49          ? [...prevItems, prevItems.find((p) => p.id === playlistId)!]
     50          : prevItems.filter((p) => p.id !== playlistId),
     51      );
     52
     53      showToast(
     54        data.isSaved
     55          ? `Saved "${playlistName}"`
     56          : `Removed "${playlistName}" from saved playlists`,
     57      );
     58    } catch (err: any) {
     59      showToast(err.response?.data?.error || "Failed to save playlist");
     60    }
    3361  };
    3462
     
    6391        </div>
    6492      )}
    65       {playlists && playlists.length > 0 && (
     93
     94      {createdPlaylists && createdPlaylists.length > 0 && (
    6695        <section>
    6796          <div className="flex items-center gap-3 mb-6">
     
    7099              Created Playlists
    71100            </h3>
    72             <span className="text-sm text-gray-500">({playlists.length})</span>
     101            <span className="text-sm text-gray-500">
     102              ({createdPlaylists.length})
     103            </span>
    73104          </div>
    74105
    75106          <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
    76             {playlists.map((playlist) => (
     107            {createdPlaylists.map((playlist) => (
    77108              <div
    78109                key={playlist.id}
     
    93124                  )}
    94125
    95                   {/* Save button overlay */}
    96126                  <button
    97                     onClick={(e) => handleSavePlaylist(e, playlist.id)}
     127                    onClick={(e) =>
     128                      handleSavePlaylist(e, playlist.id, playlist.name)
     129                    }
    98130                    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"
    99131                    title={playlist.isSavedByCurrentUser ? "Unsave" : "Save"}
     
    106138                      }`}
    107139                    />
     140                  </button>
     141                </div>
     142                <p className="text-sm font-semibold text-gray-900 truncate group-hover:text-blue-600 transition-colors">
     143                  {playlist.name}
     144                </p>
     145                <p className="text-xs text-gray-500 truncate">
     146                  {playlist.creatorName}
     147                </p>
     148              </div>
     149            ))}
     150          </div>
     151        </section>
     152      )}
     153
     154      {savedItems && savedItems.length > 0 && (
     155        <section>
     156          <div className="flex items-center gap-3 mb-6">
     157            <Bookmark className="w-6 h-6 text-blue-600 fill-blue-600" />
     158            <h3 className="text-2xl font-bold text-gray-800">
     159              Saved Playlists
     160            </h3>
     161            <span className="text-sm text-gray-500">({savedItems.length})</span>
     162          </div>
     163
     164          <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
     165            {savedItems.map((playlist) => (
     166              <div
     167                key={playlist.id}
     168                className="group cursor-pointer"
     169                onClick={() => navigate(`/collection/playlist/${playlist.id}`)}
     170              >
     171                <div className="relative aspect-square rounded-lg overflow-hidden bg-gray-100 mb-3 shadow-md group-hover:shadow-lg transition-all">
     172                  {playlist.cover ? (
     173                    <img
     174                      src={playlist.cover}
     175                      alt={playlist.name}
     176                      className="w-full h-full object-cover"
     177                    />
     178                  ) : (
     179                    <div className="w-full h-full bg-gradient-to-br from-blue-400 to-purple-500 flex items-center justify-center">
     180                      <ListMusic className="w-16 h-16 text-white opacity-30" />
     181                    </div>
     182                  )}
     183
     184                  <button
     185                    onClick={(e) =>
     186                      handleSavePlaylist(e, playlist.id, playlist.name)
     187                    }
     188                    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"
     189                    title="Unsave"
     190                  >
     191                    <Bookmark className="w-5 h-5 fill-blue-600 text-blue-600" />
    108192                  </button>
    109193                </div>
     
    234318      {likedEntities &&
    235319        likedEntities.length === 0 &&
    236         (!playlists || playlists.length === 0) && (
     320        (!createdPlaylists || createdPlaylists.length === 0) &&
     321        (!savedItems || savedItems.length === 0) && (
    237322          <div className="flex flex-col items-center justify-center py-16 text-gray-400">
    238323            <Music className="w-20 h-20 mb-4 opacity-20" />
  • frontend/src/components/userProfile/UserListModal.tsx

    rd2af1a6 r0792d02  
    66  users: BaseNonAdminUser[];
    77  onClose: () => void;
    8   onFollowToggle: (targetId: number) => Promise<void>;
     8  onFollowToggle: (targetUsername: string) => Promise<void>;
    99}
    1010
     
    3737            users.map((u) => (
    3838              <div
    39                 key={u.id}
     39                key={u.username}
    4040                className="flex items-center justify-between p-3 hover:bg-gray-50 rounded-lg transition-colors"
    4141              >
     
    4444                  onClick={() => {
    4545                    onClose();
    46                     navigate(`/users/${u.id}`);
     46                    navigate(`/users/${u.username}`);
    4747                  }}
    4848                >
     
    6464
    6565                <button
    66                   onClick={() => onFollowToggle(u.id)}
     66                  onClick={() => onFollowToggle(u.username)}
    6767                  className={`px-4 py-1 text-sm font-medium rounded-md transition-colors cursor-pointer ${
    6868                    u.isFollowedByCurrentUser
Note: See TracChangeset for help on using the changeset viewer.