Ignore:
Timestamp:
01/26/26 20:19:20 (6 months ago)
Author:
Dimitar Arsov <dimitararsov04@…>
Branches:
main
Children:
d47e225
Parents:
fd81a18
Message:

add Global exception handler

Location:
frontend/src/components/userProfile
Files:
3 edited
1 moved

Legend:

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

    rfd81a18 ra3ae097  
    1 import { useEffect, useState } from "react";
     1import React, { useEffect, useState } from "react";
    22import { useNavigate } from "react-router-dom";
    33
     
    1010const AllUsers = () => {
    1111  const [users, setUsers] = useState<User[]>([]);
     12  const [searchedUser, setSearchedUser] = useState<string>("");
     13  const [error, setError] = useState<string | null>(null);
    1214  const navigate = useNavigate();
    1315
     
    2527  };
    2628
     29  const handleSearch = async (e: React.FormEvent) => {
     30    e.preventDefault();
     31    try {
     32      const response = await fetch(
     33        `http://localhost:8080/users/search?name=${searchedUser}`,
     34      );
     35      if (!response.ok) throw new Error("Search failed");
     36
     37      const data = await response.json();
     38      setUsers(data);
     39    } catch (err: any) {
     40      setError(err.message);
     41    }
     42  };
     43
     44  if (users.length == 0) return <div className="p-6">Loading...</div>;
     45
    2746  return (
    2847    <div className="container mx-auto p-6">
    2948      <h1 className="text-3xl font-bold mb-6">All Users</h1>
     49      <form onSubmit={handleSearch} className="flex gap-2 mb-10">
     50        <input
     51          type="text"
     52          value={searchedUser}
     53          onChange={(e) => setSearchedUser(e.target.value)}
     54          className="border p-2 rounded w-full"
     55          placeholder="Search for a user..."
     56        />
     57        <button
     58          type="submit"
     59          className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 transition-colors"
     60        >
     61          Search
     62        </button>
     63      </form>
     64
    3065      <div className="grid gap-4">
    3166        {users.map((u) => (
  • frontend/src/components/userProfile/ArtistView.tsx

    rfd81a18 ra3ae097  
    11import { useNavigate } from "react-router-dom";
     2import { Music, Disc3, Mic2 } from "lucide-react";
    23
    34interface ArtistContributionDTO {
     
    1516  const navigate = useNavigate();
    1617
     18  const albums = contributions.filter((c) => c.entityType === "Album");
     19  const songs = contributions.filter((c) => c.entityType === "Song");
     20
     21  const getRoleColor = (role: string) => {
     22    const colors: { [key: string]: string } = {
     23      COMPOSER: "bg-purple-500",
     24      PERFORMER: "bg-blue-500",
     25      PRODUCER: "bg-green-500",
     26      MAIN_VOCAL: "bg-pink-500",
     27    };
     28    return colors[role] || "bg-gray-500";
     29  };
     30
    1731  return (
    18     <div className="mt-8 border-t pt-6">
    19       <h2 className="text-2xl font-bold mb-4">Contributions</h2>
    20       <div className="grid gap-3">
    21         {contributions.map((contribution, index) => (
    22           <div
    23             key={index}
    24             className="p-4 border rounded hover:shadow-md transition-shadow cursor-pointer"
    25             onClick={() =>
    26               navigate(`/musical-entity/${contribution.musicalEntityId}`)
    27             }
    28           >
    29             <div className="flex justify-between items-center">
    30               <div className="flex-1">
    31                 <p className="font-semibold text-lg">{contribution.title}</p>
    32                 <p className="text-sm text-gray-500 mt-1">
    33                   ID: {contribution.musicalEntityId}
    34                 </p>
     32    <div className="mt-8">
     33      {/* Albums Section */}
     34      {albums.length > 0 && (
     35        <div className="mb-12">
     36          <div className="flex items-center gap-3 mb-6">
     37            <Disc3 className="w-8 h-8 text-purple-600" />
     38            <h2 className="text-3xl font-bold">Albums</h2>
     39          </div>
     40          <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
     41            {albums.map((album, index) => (
     42              <div
     43                key={index}
     44                className="group cursor-pointer"
     45                onClick={() =>
     46                  navigate(`/musical-entity/${album.musicalEntityId}`)
     47                }
     48              >
     49                <div className="relative aspect-square bg-linear-to-br from-purple-400 via-pink-400 to-blue-400 rounded-lg mb-3 overflow-hidden shadow-lg group-hover:shadow-2xl transition-all duration-300 group-hover:scale-105">
     50                  <div className="absolute inset-0 flex items-center justify-center">
     51                    <Disc3 className="w-20 h-20 text-white opacity-40" />
     52                  </div>
     53                  <div className="absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/60 to-transparent p-4">
     54                    <span
     55                      className={`text-xs px-2 py-1 rounded-full text-white ${getRoleColor(album.role)}`}
     56                    >
     57                      {album.role.replace("_", " ")}
     58                    </span>
     59                  </div>
     60                </div>
     61                <h3 className="font-semibold text-base line-clamp-2 group-hover:text-blue-600 transition-colors">
     62                  {album.title}
     63                </h3>
    3564              </div>
    36               <div className="flex gap-2">
    37                 <span className="bg-purple-100 text-purple-800 px-3 py-1 rounded-full text-sm font-medium">
    38                   {contribution.role}
    39                 </span>
    40                 <span className="bg-gray-100 text-gray-800 px-3 py-1 rounded-full text-sm font-medium">
    41                   {contribution.entityType}
     65            ))}
     66          </div>
     67        </div>
     68      )}
     69
     70      {songs.length > 0 && (
     71        <div className="mb-12">
     72          <div className="flex items-center gap-3 mb-6">
     73            <Music className="w-8 h-8 text-blue-600" />
     74            <h2 className="text-3xl font-bold">Songs</h2>
     75          </div>
     76          <div className="space-y-2">
     77            {songs.map((song, index) => (
     78              <div
     79                key={index}
     80                className="group flex items-center gap-4 p-4 rounded-lg hover:bg-gray-50 transition-all cursor-pointer border border-transparent hover:border-gray-200"
     81                onClick={() =>
     82                  navigate(`/musical-entity/${song.musicalEntityId}`)
     83                }
     84              >
     85                <div className="shrink-0 w-14 h-14 bg-linear-to-br from-blue-400 to-cyan-400 rounded flex items-center justify-center group-hover:scale-110 transition-transform shadow-md">
     86                  <Music className="w-7 h-7 text-white" />
     87                </div>
     88                <div className="flex-1 min-w-0">
     89                  <h3 className="font-semibold text-lg group-hover:text-blue-600 transition-colors truncate">
     90                    {song.title}
     91                  </h3>
     92                  <div className="flex items-center gap-2 mt-1">
     93                    <Mic2 className="w-4 h-4 text-gray-400" />
     94                  </div>
     95                </div>
     96                <span
     97                  className={`px-4 py-2 rounded-full text-white text-sm font-medium ${getRoleColor(song.role)} shadow-md`}
     98                >
     99                  {song.role.replace("_", " ")}
    42100                </span>
    43101              </div>
    44             </div>
     102            ))}
    45103          </div>
    46         ))}
    47       </div>
     104        </div>
     105      )}
     106
    48107      {contributions.length === 0 && (
    49         <p className="text-gray-500 text-center py-4">No contributions yet</p>
     108        <div className="flex flex-col items-center justify-center py-16 text-gray-400">
     109          <Music className="w-24 h-24 mb-4 opacity-20" />
     110          <p className="text-xl font-medium">No contributions yet</p>
     111          <p className="text-sm mt-2">Start creating music to see it here</p>
     112        </div>
    50113      )}
    51114    </div>
  • frontend/src/components/userProfile/ListenerView.tsx

    rfd81a18 ra3ae097  
    11import { useNavigate } from "react-router-dom";
     2import { Heart, ListMusic, Music, Album } from "lucide-react";
    23
    34interface MusicalEntityDTO {
     
    2324  const navigate = useNavigate();
    2425
     26  const likedSongs = likedEntities.filter((e) => e.type === "SONG");
     27  const likedAlbums = likedEntities.filter((e) => e.type === "ALBUM");
     28
    2529  return (
    26     <div className="mt-8 border-t pt-6 space-y-10">
    27       {/* --- PLAYLISTS SECTION --- */}
    28       <section>
    29         <h3 className="text-2xl font-bold mb-6 flex items-center gap-2">
    30           <span>My Playlists</span>
    31           <span className="text-sm font-normal text-gray-400">
    32             ({playlists?.length})
    33           </span>
    34         </h3>
     30    <div className="mt-8 space-y-12">
     31      {playlists && playlists.length > 0 && (
     32        <section>
     33          <div className="flex items-center gap-2 mb-6 pb-2 border-b-2 border-gray-200">
     34            <ListMusic className="w-6 h-6 text-gray-700" />
     35            <h3 className="text-2xl font-bold text-gray-800">My Playlists</h3>
     36            <span className="text-sm text-gray-400 ml-1">
     37              ({playlists.length})
     38            </span>
     39          </div>
    3540
    36         <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-6">
    37           {playlists?.map((playlist) => (
    38             <div
    39               key={playlist.id}
    40               className="group cursor-pointer"
    41               onClick={() => navigate(`/playlist/${playlist.id}`)}
    42             >
    43               {/* Playlist Cover Container */}
    44               <div className="aspect-square relative overflow-hidden rounded-xl shadow-md bg-gray-200 mb-3">
    45                 {playlist.cover ? (
    46                   <img
    47                     src={playlist.cover}
    48                     alt={playlist.name}
    49                     className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
    50                   />
    51                 ) : (
    52                   <div className="w-full h-full flex items-center justify-center bg-linear-to-br from-gray-300 to-gray-400 text-white">
    53                     <svg
    54                       xmlns="http://www.w3.org/2000/svg"
    55                       className="h-12 w-12"
    56                       fill="none"
    57                       viewBox="0 0 24 24"
    58                       stroke="currentColor"
    59                     >
    60                       <path
    61                         strokeLinecap="round"
    62                         strokeLinejoin="round"
    63                         strokeWidth={2}
    64                         d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"
    65                       />
    66                     </svg>
    67                   </div>
    68                 )}
    69                 {/* Hover Overlay */}
    70                 <div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
    71                   <div className="bg-blue-500 p-3 rounded-full text-white shadow-xl transform translate-y-4 group-hover:translate-y-0 transition-transform">
    72                     <svg
    73                       xmlns="http://www.w3.org/2000/svg"
    74                       className="h-6 w-6"
    75                       fill="none"
    76                       viewBox="0 0 24 24"
    77                       stroke="currentColor"
    78                     >
    79                       <path
    80                         strokeLinecap="round"
    81                         strokeLinejoin="round"
    82                         strokeWidth={2}
    83                         d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
    84                       />
    85                     </svg>
    86                   </div>
     41          <div className="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4">
     42            {playlists.map((playlist) => (
     43              <div
     44                key={playlist.id}
     45                className="group cursor-pointer"
     46                onClick={() => navigate(`/playlist/${playlist.id}`)}
     47              >
     48                <div className="aspect-square rounded-md overflow-hidden bg-gray-100 mb-2 relative shadow-sm group-hover:shadow-lg transition-all">
     49                  {playlist.cover ? (
     50                    <img
     51                      src={playlist.cover}
     52                      alt={playlist.name}
     53                      className="w-full h-full object-cover group-hover:opacity-90 transition-opacity"
     54                    />
     55                  ) : (
     56                    <div className="w-full h-full bg-gradient-to-br from-gray-200 to-gray-300 flex items-center justify-center">
     57                      <ListMusic className="w-10 h-10 text-gray-400" />
     58                    </div>
     59                  )}
     60                </div>
     61                <p className="text-sm font-medium text-gray-800 truncate group-hover:text-blue-600 transition-colors">
     62                  {playlist.name}
     63                </p>
     64              </div>
     65            ))}
     66          </div>
     67        </section>
     68      )}
     69
     70      {likedSongs.length > 0 && (
     71        <section>
     72          <div className="flex items-center gap-2 mb-6 pb-2 border-b-2 border-gray-200">
     73            <Heart className="w-6 h-6 text-red-500 fill-red-500" />
     74            <h3 className="text-2xl font-bold text-gray-800">Liked Songs</h3>
     75            <span className="text-sm text-gray-400 ml-1">
     76              ({likedSongs.length})
     77            </span>
     78          </div>
     79
     80          <div className="space-y-1">
     81            {likedSongs.map((song, index) => (
     82              <div
     83                key={song.id}
     84                className="flex items-center gap-4 p-3 rounded hover:bg-gray-50 cursor-pointer group transition-colors"
     85                onClick={() => navigate(`/musical-entity/${song.id}`)}
     86              >
     87                <span className="text-sm text-gray-400 w-8 text-right font-medium">
     88                  {index + 1}
     89                </span>
     90                <div className="w-12 h-12 rounded bg-gradient-to-br from-red-100 to-pink-100 flex items-center justify-center flex-shrink-0">
     91                  <Music className="w-6 h-6 text-red-600" />
     92                </div>
     93                <div className="flex-1 min-w-0">
     94                  <p className="font-medium text-gray-800 truncate group-hover:text-blue-600 transition-colors">
     95                    {song.title}
     96                  </p>
     97                  <p className="text-xs text-gray-500">{song.genre}</p>
    8798                </div>
    8899              </div>
     100            ))}
     101          </div>
     102        </section>
     103      )}
    89104
    90               <h4 className="font-bold text-gray-900 truncate group-hover:text-blue-600 transition-colors">
    91                 {playlist.name}
    92               </h4>
    93               <p className="text-xs text-gray-500 uppercase tracking-wider">
    94                 Playlist
    95               </p>
    96             </div>
    97           ))}
     105      {likedAlbums.length > 0 && (
     106        <section>
     107          <div className="flex items-center gap-2 mb-6 pb-2 border-b-2 border-gray-200">
     108            <Album className="w-6 h-6 text-blue-600" />
     109            <h3 className="text-2xl font-bold text-gray-800">Liked Albums</h3>
     110            <span className="text-sm text-gray-400 ml-1">
     111              ({likedAlbums.length})
     112            </span>
     113          </div>
    98114
    99           {playlists?.length === 0 && (
    100             <div className="col-span-full py-8 border-2 border-dashed border-gray-200 rounded-xl text-center">
    101               <p className="text-gray-400">No playlists created yet</p>
    102             </div>
    103           )}
     115          <div className="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-5">
     116            {likedAlbums.map((album) => (
     117              <div
     118                key={album.id}
     119                className="group cursor-pointer"
     120                onClick={() => navigate(`/musical-entity/${album.id}`)}
     121              >
     122                <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">
     123                  <Album className="w-16 h-16 text-blue-600 opacity-60" />
     124                </div>
     125                <p className="font-medium text-sm text-gray-800 truncate group-hover:text-blue-600 transition-colors">
     126                  {album.title}
     127                </p>
     128                <p className="text-xs text-gray-500 mt-0.5">{album.genre}</p>
     129              </div>
     130            ))}
     131          </div>
     132        </section>
     133      )}
     134
     135      {likedEntities.length === 0 && (!playlists || playlists.length === 0) && (
     136        <div className="flex flex-col items-center justify-center py-16 text-gray-300">
     137          <p className="text-lg font-medium text-gray-400">Nothing here yet</p>
     138          <p className="text-sm text-gray-400 mt-1">
     139            Start exploring music to build your collection
     140          </p>
    104141        </div>
    105       </section>
    106 
    107       {/* --- LIKED MUSIC SECTION --- */}
    108       <section>
    109         <h3 className="text-2xl font-bold mb-4">Liked Music</h3>
    110         <div className="grid gap-2">
    111           {likedEntities.map((entity) => (
    112             <div
    113               key={entity.id}
    114               className="p-4 bg-gray-50 rounded-lg border border-transparent hover:border-gray-200 hover:bg-white transition-all cursor-pointer group shadow-xs"
    115               onClick={() => navigate(`/musical-entity/${entity.id}`)}
    116             >
    117               <div className="flex justify-between items-center">
    118                 <div className="flex-1">
    119                   <span className="font-semibold text-gray-800 group-hover:text-blue-600">
    120                     {entity.title}
    121                   </span>
    122                   <p className="text-sm text-gray-500 mt-1">{entity.genre}</p>
    123                 </div>
    124                 <span
    125                   className={`px-3 py-1 rounded-full text-xs font-bold tracking-wider ${
    126                     entity.type === "SONG"
    127                       ? "bg-green-100 text-green-700"
    128                       : "bg-blue-100 text-blue-700"
    129                   }`}
    130                 >
    131                   {entity.type}
    132                 </span>
    133               </div>
    134             </div>
    135           ))}
    136         </div>
    137         {likedEntities.length === 0 && (
    138           <p className="text-gray-500 text-center py-4 italic">
    139             No liked music yet
    140           </p>
    141         )}
    142       </section>
     142      )}
    143143    </div>
    144144  );
  • frontend/src/components/userProfile/UserDetailView.tsx

    rfd81a18 ra3ae097  
    4848  const navigate = useNavigate();
    4949  const [user, setUser] = useState<User | null>(null);
     50  const [error, setError] = useState<string | null>(null);
    5051
    5152  useEffect(() => {
    5253    const fetchUser = async () => {
    53       console.log(userId);
    54       const response = await fetch(`http://localhost:8080/users/${userId}`);
    55       const data = await response.json();
    56       console.log("Fetched user data:", data);
    57       setUser(data);
     54      setError(null);
     55      try {
     56        const response = await fetch(`http://localhost:8080/users/${userId}`);
     57
     58        if (!response.ok) {
     59          const errorData = await response.json();
     60          throw new Error(errorData.error || "Failed to fetch user");
     61        }
     62
     63        const data = await response.json();
     64        setUser(data);
     65      } catch (err: any) {
     66        setError(err.message);
     67      }
    5868    };
    5969    fetchUser();
    6070  }, [userId]);
     71
     72  if (error) {
     73    return (
     74      <div className="p-6 bg-red-50 border border-red-200 text-red-700 rounded-lg">
     75        <h2 className="font-bold">Error</h2>
     76        <p>{error}</p>
     77      </div>
     78    );
     79  }
    6180
    6281  if (!user) return <div className="p-6">Loading...</div>;
     
    7291
    7392      <div className="bg-white shadow-lg rounded-lg p-8">
    74         {/* Profile Header */}
    7593        <div className="flex items-start gap-6 mb-8">
    76           {/* Profile Photo */}
    7794          <div className="shrink-0">
    7895            <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">
     
    8198          </div>
    8299
    83           {/* User Info */}
    84100          <div className="flex-1">
    85101            <h1 className="text-4xl font-bold mb-2">{user.fullName}</h1>
     
    88104            </span>
    89105
    90             {/* Followers/Following Stats */}
    91106            <div className="flex gap-6 mb-4 text-gray-700">
    92107              <div className="flex flex-col">
Note: See TracChangeset for help on using the changeset viewer.