Changeset 2730163 for frontend/src


Ignore:
Timestamp:
01/31/26 22:34:40 (5 months ago)
Author:
Dimitar Arsov <dimitararsov04@…>
Branches:
main
Children:
d2af1a6
Parents:
2b08bed
Message:

show songs in playlists/albums

Location:
frontend/src
Files:
1 added
5 edited
1 moved

Legend:

Unmodified
Added
Removed
  • frontend/src/App.tsx

    r2b08bed r2730163  
    77import Nav from "./pages/Nav";
    88import Register from "./pages/Register";
    9 import UserDetailView from "./pages/UserDetailView";
     9import UserDetail from "./pages/UserDetail";
    1010import MusicalCollection from "./pages/MusicalCollection";
    1111
     
    5252      {
    5353        path: "/users/:userId",
    54         element: <UserDetailView />,
     54        element: <UserDetail />,
    5555      },
    5656      {
  • frontend/src/components/userProfile/ListenerView.tsx

    r2b08bed r2730163  
    2020          <div className="flex items-center gap-2 mb-6 pb-2 border-b-2 border-gray-200">
    2121            <ListMusic className="w-6 h-6 text-gray-700" />
    22             <h3 className="text-2xl font-bold text-gray-800">My Playlists</h3>
     22            <h3 className="text-2xl font-bold text-gray-800">
     23              Created Playlists
     24            </h3>
    2325            <span className="text-sm text-gray-400 ml-1">
    2426              ({playlists.length})
     
    3133                key={playlist.id}
    3234                className="group cursor-pointer"
    33                 onClick={() => navigate(`/playlists/${playlist.id}`)}
     35                onClick={() => navigate(`/collection/playlist/${playlist.id}`)}
    3436              >
    3537                <div className="aspect-square rounded-md overflow-hidden bg-gray-100 mb-2 relative shadow-sm group-hover:shadow-lg transition-all">
     
    105107                key={album.id}
    106108                className="group cursor-pointer"
    107                 onClick={() => navigate(`/musical-entity/${album.id}`)}
     109                onClick={() => navigate(`/collection/album/${album.id}`)}
    108110              >
    109111                <div className="aspect-square rounded-lg overflow-hidden bg-gradient-to-br from-blue-100 to-indigo-100 mb-3 flex items-center justify-center shadow-sm group-hover:shadow-md transition-all">
  • frontend/src/components/userProfile/UserListModal.tsx

    r2b08bed r2730163  
    2525          <button
    2626            onClick={onClose}
    27             className="text-gray-500 hover:text-black text-2xl"
     27            className="text-gray-500 hover:text-black text-2xl cursor-pointer"
    2828          >
    2929            &times;
  • frontend/src/pages/MusicalCollection.tsx

    r2b08bed r2730163  
    22import { useNavigate, useParams } from "react-router-dom";
    33import axiosInstance from "../api/axiosInstance";
    4 
    5 interface Song {
     4import type { Song, Album, Playlist } from "../utils/types";
     5import { handleError } from "../utils/error";
     6interface CollectionView {
    67  id: number;
    78  title: string;
    8   genre: string;
    9   type: "SONG";
    10   releasedBy: string;
    11   isLikedByCurrentUser?: boolean;
    12 }
    13 
    14 interface MusicalEntity {
    15   id: number;
    16   title: string;
    17   genre: string;
     9  genre?: string;
    1810  type: string;
    1911  releasedBy: string;
    2012  isLikedByCurrentUser?: boolean;
    21   songs?: Song[];
     13  songs: Song[];
    2214}
    2315
     
    2517  const { type, id } = useParams();
    2618  const navigate = useNavigate();
    27   const [collection, setCollection] = useState<MusicalEntity | null>(null);
     19  const [collection, setCollection] = useState<CollectionView | null>(null);
    2820  const [isLoading, setIsLoading] = useState(true);
    2921  const [error, setError] = useState<string | null>(null);
     22
     23  const normalizeCollection = (
     24    data: Album | Playlist,
     25    type: string,
     26  ): CollectionView => {
     27    if (type === "album") {
     28      const album = data as Album;
     29      return {
     30        id: album.id,
     31        title: album.title,
     32        genre: album.genre,
     33        type: album.type,
     34        releasedBy: album.releasedBy,
     35        isLikedByCurrentUser: album.isLikedByCurrentUser,
     36        songs: album.songs,
     37      };
     38    } else {
     39      const playlist = data as Playlist;
     40      return {
     41        id: playlist.id,
     42        title: playlist.name,
     43        genre: undefined,
     44        type: "PLAYLIST",
     45        releasedBy: playlist.creatorName,
     46        isLikedByCurrentUser: undefined,
     47        songs: playlist.songsInPlaylist,
     48      };
     49    }
     50  };
    3051
    3152  useEffect(() => {
     
    3758          type === "album" ? `/albums/${id}` : `/playlists/${id}`;
    3859        const response = await axiosInstance.get(endpoint);
    39         console.log(response.data);
    40         setCollection(response.data);
     60
     61        const normalized = normalizeCollection(response.data, type!);
     62        setCollection(normalized);
    4163      } catch (err: any) {
    42         setError(err.response?.data?.error || "Failed to load collection");
     64        setError(handleError(err));
    4365      } finally {
    4466        setIsLoading(false);
     
    6789      <button
    6890        onClick={() => navigate(-1)}
    69         className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200"
     91        className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200 cursor-pointer"
    7092      >
    7193        ← Back
     
    88110            <div className="flex items-center gap-3 text-gray-700 mb-4">
    89111              <span className="font-semibold">{collection.releasedBy}</span>
    90               <span>•</span>
    91               <span className="text-gray-600">{collection.genre}</span>
     112              {collection.genre && (
     113                <>
     114                  <span>•</span>
     115                  <span className="text-gray-600">{collection.genre}</span>
     116                </>
     117              )}
    92118              {collection.songs && (
    93119                <>
     
    101127            </div>
    102128
    103             <button
    104               className="flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors duration-200"
    105               aria-label={collection.isLikedByCurrentUser ? "Unlike" : "Like"}
    106             >
    107               <svg
    108                 className="w-5 h-5"
    109                 fill={collection.isLikedByCurrentUser ? "#ef4444" : "none"}
    110                 stroke={collection.isLikedByCurrentUser ? "#ef4444" : "#6b7280"}
    111                 strokeWidth="2"
    112                 viewBox="0 0 24 24"
     129            {type === "album" && (
     130              <button
     131                className="flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors duration-200"
     132                aria-label={collection.isLikedByCurrentUser ? "Unlike" : "Like"}
    113133              >
    114                 <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" />
    115               </svg>
    116               <span className="text-sm font-medium text-gray-700">
    117                 {collection.isLikedByCurrentUser ? "Liked" : "Like"}
    118               </span>
    119             </button>
     134                <svg
     135                  className="w-5 h-5"
     136                  fill={collection.isLikedByCurrentUser ? "#ef4444" : "none"}
     137                  stroke={
     138                    collection.isLikedByCurrentUser ? "#ef4444" : "#6b7280"
     139                  }
     140                  strokeWidth="2"
     141                  viewBox="0 0 24 24"
     142                >
     143                  <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" />
     144                </svg>
     145                <span className="text-sm font-medium text-gray-700">
     146                  {collection.isLikedByCurrentUser ? "Liked" : "Like"}
     147                </span>
     148              </button>
     149            )}
    120150          </div>
    121151        </div>
  • frontend/src/pages/UserDetail.tsx

    r2b08bed r2730163  
    55import ListenerView from "../components/userProfile/ListenerView";
    66import UserListModal from "../components/userProfile/UserListModal";
     7import { handleError } from "../utils/error";
    78import type {
    89  MusicalEntity,
     
    4344    try {
    4445      const response = await axiosInstance.post<UserProfile>(
    45         `/users/follow/${userId}`,
     46        `/users/${userId}/follow`,
    4647      );
    4748      setUser(response.data);
    4849    } catch (err: any) {
    49       console.error(err.response?.data?.error);
     50      setError(handleError(err));
    5051    } finally {
    5152      setIsFollowing(false);
     
    5657    setIsLoadingModal(true);
    5758    try {
    58       const response = await axiosInstance.get(`/users/followers/${userId}`);
     59      const response = await axiosInstance.get(`/users/${userId}/followers`);
    5960      setModalUsers(response.data);
    6061      setModalTitle("Followers");
    6162      setShowModal(true);
    6263    } catch (err) {
    63       console.error("Failed to fetch followers");
     64      setError(handleError(err));
    6465    } finally {
    6566      setIsLoadingModal(false);
     
    6970    setIsLoadingModal(true);
    7071    try {
    71       const response = await axiosInstance.get(`/users/following/${userId}`);
     72      const response = await axiosInstance.get(`/users/${userId}/following`);
    7273      setModalUsers(response.data);
    7374      setModalTitle("Following");
    7475      setShowModal(true);
    75     } catch (err) {
    76       console.error("Failed to fetch following users");
     76    } catch (err: any) {
     77      setError(handleError(err));
    7778    } finally {
    7879      setIsLoadingModal(false);
     
    8283  const handleFollowInModal = async (targetId: number) => {
    8384    try {
    84       await axiosInstance.post(`/users/follow/${targetId}`);
     85      await axiosInstance.post(`/users/${targetId}/follow`);
    8586      setModalUsers((prevUsers) =>
    8687        prevUsers.map((u) => {
     
    9697      );
    9798
    98       if (user && user.id === targetId) {
    99         const response = await axiosInstance.get(`/users/${targetId}`);
    100         setUser(response.data);
    101       }
    102     } catch (err) {
    103       console.error("Failed to toggle follow in modal", err);
     99      // if (user && user.id === targetId) {
     100      //   const response = await axiosInstance.get(`/users/${targetId}`);
     101      //   setUser(response.data);
     102      // }
     103    } catch (err: any) {
     104      setError(handleError(err));
    104105    }
    105106  };
     
    110111      try {
    111112        const response = await axiosInstance.get(`/users/${userId}`);
    112         console.log(response.data);
    113113        setUser(response.data);
    114114      } catch (err: any) {
    115         const errorMessage =
    116           err.response?.data?.error || "Failed to fetch user";
    117         setError(errorMessage);
     115        setError(handleError(err));
    118116      }
    119117    };
     
    143141      <button
    144142        onClick={() => navigate(-1)}
    145         className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200"
     143        className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200 cursor-pointer"
    146144      >
    147145        ← Back
  • frontend/src/utils/types.ts

    r2b08bed r2730163  
    1313  role: string;
    1414  entityType: string;
     15  isLikedByCurrentUser: boolean;
    1516}
     17
    1618export interface MusicalEntity {
    1719  id: number;
     
    2022  type: string;
    2123  releasedBy: string;
     24  isLikedByCurrentUser?: boolean;
     25}
     26
     27export interface Song extends MusicalEntity {
     28  type: "SONG";
     29}
     30
     31export interface Album extends MusicalEntity {
     32  type: "ALBUM";
     33  songs: Song[];
    2234}
    2335
     
    2739  cover: string;
    2840  creatorName: string;
     41  songsInPlaylist: Song[];
    2942}
    3043
Note: See TracChangeset for help on using the changeset viewer.