Changeset 6de2873 for frontend/src


Ignore:
Timestamp:
01/27/26 17:30:07 (6 months ago)
Author:
Dimitar Arsov <dimitararsov04@…>
Branches:
main
Children:
98992cf
Parents:
c1d2f07
Message:

implement following a user

File:
1 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/pages/UserDetailView.tsx

    rc1d2f07 r6de2873  
    55import ListenerView from "../components/userProfile/ListenerView";
    66import type {
    7         ArtistContributionDTO,
    8         MusicalEntityDTO,
    9         Playlist,
     7  ArtistContributionDTO,
     8  MusicalEntityDTO,
     9  Playlist,
    1010} from "../utils/types";
    1111
    1212interface User {
    13         id: number;
    14         username: string;
    15         fullName: string;
    16         userType: string;
    17         followers: number;
    18         following: number;
    19         isFollowedByCurrentUser: boolean;
     13  id: number;
     14  username: string;
     15  fullName: string;
     16  userType: string;
     17  followers: number;
     18  following: number;
     19  isFollowedByCurrentUser: boolean;
    2020
    21         musicalEntities?: {
    22                 contributions: ArtistContributionDTO[];
    23         };
     21  musicalEntities?: {
     22    contributions: ArtistContributionDTO[];
     23  };
    2424
    25         likes?: {
    26                 likedEntities: MusicalEntityDTO[];
    27         };
     25  likes?: {
     26    likedEntities: MusicalEntityDTO[];
     27  };
    2828
    29         createdPlaylists?: Playlist[];
     29  createdPlaylists?: Playlist[];
    3030}
    3131
    3232const UserDetail = () => {
    33         // user refers to the selected user NOT to the user from context
    34         const { userId } = useParams();
    35         const navigate = useNavigate();
    36         const [user, setUser] = useState<User | null>(null);
    37         const [error, setError] = useState<string | null>(null);
     33  // user refers to the selected user NOT to the user from context
     34  const { userId } = useParams();
     35  const navigate = useNavigate();
     36  const [user, setUser] = useState<User | null>(null);
     37  const [error, setError] = useState<string | null>(null);
     38  const [isFollowing, setIsFollowing] = useState(false);
    3839
    39         useEffect(() => {
    40                 const fetchUser = async () => {
    41                         setError(null);
    42                         try {
    43                                 const response = await axiosInstance.get(`/users/${userId}`);
     40  const handleFollow = async () => {
     41    if (!user) return;
    4442
    45                                 setUser(response.data);
    46                         } catch (err: any) {
    47                                 const errorMessage =
    48                                         err.response?.data?.error || "Failed to fetch user";
    49                                 setError(errorMessage);
    50                         }
    51                 };
    52                 fetchUser();
    53         }, [userId]);
     43    setIsFollowing(true);
     44    try {
     45      const response = await axiosInstance.post<User>(
     46        `/users/follow/${userId}`,
     47      );
     48      setUser(response.data);
     49    } catch (err: any) {
     50      console.error(err.response?.data?.error);
     51    } finally {
     52      setIsFollowing(false);
     53    }
     54  };
    5455
    55         if (error) {
    56                 return (
    57                         <div className="p-6 bg-red-50 border border-red-200 text-red-700 rounded-lg">
    58                                 <h2 className="font-bold">Error</h2>
    59                                 <p>{error}</p>
    60                         </div>
    61                 );
    62         }
     56  useEffect(() => {
     57    const fetchUser = async () => {
     58      setError(null);
     59      try {
     60        const response = await axiosInstance.get(`/users/${userId}`);
    6361
    64         if (!user) return <div className="p-6">Loading...</div>;
     62        setUser(response.data);
     63      } catch (err: any) {
     64        const errorMessage =
     65          err.response?.data?.error || "Failed to fetch user";
     66        setError(errorMessage);
     67      }
     68    };
     69    fetchUser();
     70  }, [userId]);
    6571
    66         return (
    67                 <div className="container mx-auto p-6">
    68                         <button
    69                                 onClick={() => navigate(-1)}
    70                                 className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
    71                         >
    72                                 ← Back
    73                         </button>
     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  }
    7480
    75                         <div className="bg-white shadow-lg rounded-lg p-8">
    76                                 <div className="flex items-start gap-6 mb-8">
    77                                         <div className="shrink-0">
    78                                                 <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">
    79                                                         {user.fullName.charAt(0).toUpperCase()}
    80                                                 </div>
    81                                         </div>
     81  if (!user) return <div className="p-6">Loading...</div>;
    8282
    83                                         <div className="flex-1">
    84                                                 <h1 className="text-4xl font-bold mb-2">{user.fullName}</h1>
    85                                                 <span className="inline-block px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium mb-4">
    86                                                         {user.userType}
    87                                                 </span>
     83  return (
     84    <div className="container mx-auto p-6">
     85      <button
     86        onClick={() => navigate(-1)}
     87        className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200"
     88      >
     89        ← Back
     90      </button>
    8891
    89                                                 <div className="flex gap-6 mb-4 text-gray-700">
    90                                                         <div className="flex flex-col">
    91                                                                 <span className="text-2xl font-bold">{user.followers}</span>
    92                                                                 <span className="text-sm text-gray-500">Followers</span>
    93                                                         </div>
    94                                                         <div className="flex flex-col">
    95                                                                 <span className="text-2xl font-bold">{user.following}</span>
    96                                                                 <span className="text-sm text-gray-500">Following</span>
    97                                                         </div>
    98                                                 </div>
     92      <div className="bg-white shadow-lg rounded-lg p-8">
     93        <div className="flex items-start gap-6 mb-8">
     94          <div className="shrink-0">
     95            <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">
     96              {user.fullName.charAt(0).toUpperCase()}
     97            </div>
     98          </div>
    9999
    100                                                 <button className="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg shadow-md transition-colors duration-200 cursor-pointer">
    101                                                         {user.isFollowedByCurrentUser ? "Unfollow" : "Follow"}
    102                                                 </button>
    103                                         </div>
    104                                 </div>
     100          <div className="flex-1">
     101            <h1 className="text-4xl font-bold mb-2">{user.fullName}</h1>
     102            <span className="inline-block px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium mb-4">
     103              {user.userType}
     104            </span>
    105105
    106                                 {user.userType === "ARTIST" && user.musicalEntities?.contributions && (
    107                                         <ArtistView contributions={user.musicalEntities.contributions} />
    108                                 )}
     106            <div className="flex gap-6 mb-4 text-gray-700">
     107              <div className="flex flex-col">
     108                <span className="text-2xl font-bold">{user.followers}</span>
     109                <span className="text-sm text-gray-500">Followers</span>
     110              </div>
     111              <div className="flex flex-col">
     112                <span className="text-2xl font-bold">{user.following}</span>
     113                <span className="text-sm text-gray-500">Following</span>
     114              </div>
     115            </div>
    109116
    110                                 {user.userType === "LISTENER" && user.likes?.likedEntities && (
    111                                         <ListenerView
    112                                                 likedEntities={user.likes.likedEntities}
    113                                                 playlists={user.createdPlaylists}
    114                                         />
    115                                 )}
    116                         </div>
    117                 </div>
    118         );
     117            <button
     118              onClick={handleFollow}
     119              disabled={isFollowing}
     120              className={`
     121                px-6 py-2 font-semibold rounded-lg shadow-md
     122                transition-colors duration-200
     123                ${
     124                  isFollowing
     125                    ? "bg-gray-400 text-gray-200 cursor-not-allowed"
     126                    : user.isFollowedByCurrentUser
     127                      ? "bg-gray-200 text-gray-700 hover:bg-gray-300 cursor-pointer"
     128                      : "bg-blue-500 text-white hover:bg-blue-600 cursor-pointer"
     129                }
     130              `}
     131            >
     132              {user.isFollowedByCurrentUser ? "Unfollow" : "Follow"}
     133            </button>
     134          </div>
     135        </div>
     136
     137        {user.userType === "ARTIST" && user.musicalEntities?.contributions && (
     138          <ArtistView contributions={user.musicalEntities.contributions} />
     139        )}
     140
     141        {user.userType === "LISTENER" && user.likes?.likedEntities && (
     142          <ListenerView
     143            likedEntities={user.likes.likedEntities}
     144            playlists={user.createdPlaylists}
     145          />
     146        )}
     147      </div>
     148    </div>
     149  );
    119150};
    120151
Note: See TracChangeset for help on using the changeset viewer.