Changeset 2b08bed for frontend


Ignore:
Timestamp:
01/30/26 22:14:36 (5 months ago)
Author:
Dimitar Arsov <dimitararsov04@…>
Branches:
main
Children:
2730163
Parents:
98992cf
Message:

show followers, following and songs in album

Location:
frontend/src
Files:
2 added
5 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/App.tsx

    r98992cf r2b08bed  
    88import Register from "./pages/Register";
    99import UserDetailView from "./pages/UserDetailView";
     10import MusicalCollection from "./pages/MusicalCollection";
    1011
    1112const Layout = () => {
    12         return (
    13                 <div className="flex flex-col min-h-screen">
    14                         <Nav />
    15                         <ToastContainer
    16                                 position="top-right"
    17                                 autoClose={2000}
    18                                 hideProgressBar={false}
    19                                 newestOnTop={false}
    20                                 closeOnClick
    21                                 rtl={false}
    22                                 pauseOnFocusLoss
    23                                 draggable
    24                                 pauseOnHover
    25                                 theme="light"
    26                         />
    27                         <main className="grow">
    28                                 <Outlet />
    29                         </main>
    30                 </div>
    31         );
     13  return (
     14    <div className="flex flex-col min-h-screen">
     15      <Nav />
     16      <ToastContainer
     17        position="top-right"
     18        autoClose={2000}
     19        hideProgressBar={false}
     20        newestOnTop={false}
     21        closeOnClick
     22        rtl={false}
     23        pauseOnFocusLoss
     24        draggable
     25        pauseOnHover
     26        theme="light"
     27      />
     28      <main className="grow">
     29        <Outlet />
     30      </main>
     31    </div>
     32  );
    3233};
    3334
    3435const router = createBrowserRouter([
    35         {
    36                 path: "",
    37                 element: <Layout />,
    38                 children: [
    39                         {
    40                                 path: "/",
    41                                 element: <LandingPage />,
    42                         },
    43                         {
    44                                 path: "/login",
    45                                 element: <Login />,
    46                         },
    47                         {
    48                                 path: "/users",
    49                                 element: <AllUsers />,
    50                         },
    51                         {
    52                                 path: "/users/:userId",
    53                                 element: <UserDetailView />,
    54                         },
    55                         {
    56                                 path: "/register",
    57                                 element: <Register />,
    58                         },
    59                 ],
    60         },
     36  {
     37    path: "",
     38    element: <Layout />,
     39    children: [
     40      {
     41        path: "/",
     42        element: <LandingPage />,
     43      },
     44      {
     45        path: "/login",
     46        element: <Login />,
     47      },
     48      {
     49        path: "/users",
     50        element: <AllUsers />,
     51      },
     52      {
     53        path: "/users/:userId",
     54        element: <UserDetailView />,
     55      },
     56      {
     57        path: "/register",
     58        element: <Register />,
     59      },
     60      {
     61        path: "/collection/:type/:id",
     62        element: <MusicalCollection />,
     63      },
     64    ],
     65  },
    6166]);
    6267
    6368function App() {
    64         return <RouterProvider router={router} />;
     69  return <RouterProvider router={router} />;
    6570}
    6671
  • frontend/src/components/userProfile/ArtistView.tsx

    r98992cf r2b08bed  
    3737                className="group cursor-pointer"
    3838                onClick={() =>
    39                   navigate(`/musical-entity/${album.musicalEntityId}`)
     39                  navigate(`/collection/album/${album.musicalEntityId}`)
    4040                }
    4141              >
  • frontend/src/components/userProfile/ListenerView.tsx

    r98992cf r2b08bed  
    3131                key={playlist.id}
    3232                className="group cursor-pointer"
    33                 onClick={() => navigate(`/playlist/${playlist.id}`)}
     33                onClick={() => navigate(`/playlists/${playlist.id}`)}
    3434              >
    3535                <div className="aspect-square rounded-md overflow-hidden bg-gray-100 mb-2 relative shadow-sm group-hover:shadow-lg transition-all">
  • frontend/src/pages/UserDetailView.tsx

    r98992cf r2b08bed  
    44import ArtistView from "../components/userProfile/ArtistView";
    55import ListenerView from "../components/userProfile/ListenerView";
     6import UserListModal from "../components/userProfile/UserListModal";
    67import type {
    78  MusicalEntity,
    89  Playlist,
    910  ArtistContribution,
     11  BaseNonAdminUser,
    1012} from "../utils/types";
    1113
    12 interface BaseUser {
    13   id: number;
    14   fullName: string;
    15   userType: string;
    16   followers: number;
    17   following: number;
    18   isFollowedByCurrentUser: boolean;
    19 }
    20 
    21 interface Artist extends BaseUser {
     14interface Artist extends BaseNonAdminUser {
    2215  userType: "ARTIST";
    2316  contributions: ArtistContribution[];
    2417}
    25 interface Listener extends BaseUser {
     18interface Listener extends BaseNonAdminUser {
    2619  userType: "LISTENER";
    2720  likedEntities: MusicalEntity[];
     
    3326const UserDetail = () => {
    3427  // user refers to the selected user NOT to the user from context
     28  const baseURL = import.meta.env.VITE_API_BASE_URL;
    3529  const { userId } = useParams();
    3630  const navigate = useNavigate();
    3731  const [user, setUser] = useState<UserProfile | null>(null);
    3832  const [error, setError] = useState<string | null>(null);
     33  const [showModal, setShowModal] = useState(false);
     34  const [modalTitle, setModalTitle] = useState("");
     35  const [modalUsers, setModalUsers] = useState<any[]>([]);
     36  const [isLoadingModal, setIsLoadingModal] = useState(false);
    3937  const [isFollowing, setIsFollowing] = useState(false);
    4038
     
    4745        `/users/follow/${userId}`,
    4846      );
     47      setUser(response.data);
    4948    } catch (err: any) {
    5049      console.error(err.response?.data?.error);
    5150    } finally {
    5251      setIsFollowing(false);
     52    }
     53  };
     54
     55  const displayFollowers = async () => {
     56    setIsLoadingModal(true);
     57    try {
     58      const response = await axiosInstance.get(`/users/followers/${userId}`);
     59      setModalUsers(response.data);
     60      setModalTitle("Followers");
     61      setShowModal(true);
     62    } catch (err) {
     63      console.error("Failed to fetch followers");
     64    } finally {
     65      setIsLoadingModal(false);
     66    }
     67  };
     68  const displayFollowing = async () => {
     69    setIsLoadingModal(true);
     70    try {
     71      const response = await axiosInstance.get(`/users/following/${userId}`);
     72      setModalUsers(response.data);
     73      setModalTitle("Following");
     74      setShowModal(true);
     75    } catch (err) {
     76      console.error("Failed to fetch following users");
     77    } finally {
     78      setIsLoadingModal(false);
     79    }
     80  };
     81
     82  const handleFollowInModal = async (targetId: number) => {
     83    try {
     84      await axiosInstance.post(`/users/follow/${targetId}`);
     85      setModalUsers((prevUsers) =>
     86        prevUsers.map((u) => {
     87          if (u.id === targetId) {
     88            const isNowFollowing = !u.isFollowedByCurrentUser;
     89            return {
     90              ...u,
     91              isFollowedByCurrentUser: isNowFollowing,
     92            };
     93          }
     94          return u;
     95        }),
     96      );
     97
     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);
    53104    }
    54105  };
     
    60111        const response = await axiosInstance.get(`/users/${userId}`);
    61112        console.log(response.data);
    62 
    63113        setUser(response.data);
    64114      } catch (err: any) {
     
    84134  return (
    85135    <div className="container mx-auto p-6">
     136      {isLoadingModal && (
     137        <div className="fixed inset-0 z-40 bg-black/30 backdrop-blur-sm flex items-center justify-center">
     138          <div className="flex items-center gap-3">
     139            <div className="w-6 h-6 border-3 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
     140          </div>
     141        </div>
     142      )}
    86143      <button
    87144        onClick={() => navigate(-1)}
     
    94151        <div className="flex items-start gap-6 mb-8">
    95152          <div className="shrink-0">
    96             <div className="w-32 h-32 rounded-full bg-linear-to-br from-blue-400 to-purple-500 flex items-center justify-center text-white text-4xl font-bold shadow-lg">
    97               {user.fullName.charAt(0).toUpperCase()}
     153            <div className="w-32 h-32 rounded-full bg-linear-to-br from-blue-400 to-purple-500 flex items-center justify-center text-white text-4xl font-bold shadow-lg overflow-hidden">
     154              {user.profilePhoto ? (
     155                <img
     156                  src={`${baseURL}/${user.profilePhoto}`}
     157                  alt={user.fullName}
     158                  className="w-full h-full object-cover"
     159                />
     160              ) : (
     161                user.fullName.charAt(0).toUpperCase()
     162              )}
    98163            </div>
    99164          </div>
     
    106171
    107172            <div className="flex gap-6 mb-4 text-gray-700">
    108               <div className="flex flex-col">
     173              <div
     174                className={`flex flex-col ${user.userType == "LISTENER" ? "cursor-pointer" : "cursor-default"}`}
     175                onClick={
     176                  user.userType === "LISTENER" ? displayFollowers : undefined
     177                }
     178              >
    109179                <span className="text-2xl font-bold">{user.followers}</span>
    110180                <span className="text-sm text-gray-500">Followers</span>
    111181              </div>
    112               <div className="flex flex-col">
     182              <div
     183                className={`flex flex-col ${user.userType == "LISTENER" ? "cursor-pointer" : "cursor-default"}`}
     184                onClick={
     185                  user.userType === "LISTENER" ? displayFollowing : undefined
     186                }
     187              >
    113188                <span className="text-2xl font-bold">{user.following}</span>
    114189                <span className="text-sm text-gray-500">Following</span>
     
    144219          />
    145220        )}
     221
     222        {showModal && (
     223          <UserListModal
     224            title={modalTitle}
     225            users={modalUsers}
     226            onClose={() => setShowModal(false)}
     227            onFollowToggle={handleFollowInModal}
     228          />
     229        )}
    146230      </div>
    147231    </div>
  • frontend/src/utils/types.ts

    r98992cf r2b08bed  
    1919  genre: string;
    2020  type: string;
     21  releasedBy: string;
    2122}
    2223
     
    2728  creatorName: string;
    2829}
     30
     31export interface BaseNonAdminUser {
     32  id: number;
     33  fullName: string;
     34  userType: string;
     35  profilePhoto: string;
     36  followers: number;
     37  following: number;
     38  isFollowedByCurrentUser: boolean;
     39}
Note: See TracChangeset for help on using the changeset viewer.