source: frontend/src/pages/UserDetail.tsx@ 2730163

main
Last change on this file since 2730163 was 2730163, checked in by Dimitar Arsov <dimitararsov04@…>, 5 months ago

show songs in playlists/albums

  • Property mode set to 100644
File size: 7.4 KB
RevLine 
[4e5cf92]1import { useEffect, useState } from "react";
2import { useNavigate, useParams } from "react-router-dom";
3import axiosInstance from "../api/axiosInstance";
4import ArtistView from "../components/userProfile/ArtistView";
5import ListenerView from "../components/userProfile/ListenerView";
[2b08bed]6import UserListModal from "../components/userProfile/UserListModal";
[2730163]7import { handleError } from "../utils/error";
[4e5cf92]8import type {
[98992cf]9 MusicalEntity,
[6de2873]10 Playlist,
[98992cf]11 ArtistContribution,
[2b08bed]12 BaseNonAdminUser,
[4e5cf92]13} from "../utils/types";
14
[2b08bed]15interface Artist extends BaseNonAdminUser {
[98992cf]16 userType: "ARTIST";
17 contributions: ArtistContribution[];
[4e5cf92]18}
[2b08bed]19interface Listener extends BaseNonAdminUser {
[98992cf]20 userType: "LISTENER";
21 likedEntities: MusicalEntity[];
22 createdPlaylists: Playlist[];
23}
24
25type UserProfile = Artist | Listener;
[4e5cf92]26
27const UserDetail = () => {
[6de2873]28 // user refers to the selected user NOT to the user from context
[2b08bed]29 const baseURL = import.meta.env.VITE_API_BASE_URL;
[6de2873]30 const { userId } = useParams();
31 const navigate = useNavigate();
[98992cf]32 const [user, setUser] = useState<UserProfile | null>(null);
[6de2873]33 const [error, setError] = useState<string | null>(null);
[2b08bed]34 const [showModal, setShowModal] = useState(false);
35 const [modalTitle, setModalTitle] = useState("");
36 const [modalUsers, setModalUsers] = useState<any[]>([]);
37 const [isLoadingModal, setIsLoadingModal] = useState(false);
[6de2873]38 const [isFollowing, setIsFollowing] = useState(false);
39
40 const handleFollow = async () => {
41 if (!user) return;
42
43 setIsFollowing(true);
44 try {
[98992cf]45 const response = await axiosInstance.post<UserProfile>(
[2730163]46 `/users/${userId}/follow`,
[6de2873]47 );
[2b08bed]48 setUser(response.data);
[6de2873]49 } catch (err: any) {
[2730163]50 setError(handleError(err));
[6de2873]51 } finally {
52 setIsFollowing(false);
53 }
54 };
55
[2b08bed]56 const displayFollowers = async () => {
57 setIsLoadingModal(true);
58 try {
[2730163]59 const response = await axiosInstance.get(`/users/${userId}/followers`);
[2b08bed]60 setModalUsers(response.data);
61 setModalTitle("Followers");
62 setShowModal(true);
63 } catch (err) {
[2730163]64 setError(handleError(err));
[2b08bed]65 } finally {
66 setIsLoadingModal(false);
67 }
68 };
69 const displayFollowing = async () => {
70 setIsLoadingModal(true);
71 try {
[2730163]72 const response = await axiosInstance.get(`/users/${userId}/following`);
[2b08bed]73 setModalUsers(response.data);
74 setModalTitle("Following");
75 setShowModal(true);
[2730163]76 } catch (err: any) {
77 setError(handleError(err));
[2b08bed]78 } finally {
79 setIsLoadingModal(false);
80 }
81 };
82
83 const handleFollowInModal = async (targetId: number) => {
84 try {
[2730163]85 await axiosInstance.post(`/users/${targetId}/follow`);
[2b08bed]86 setModalUsers((prevUsers) =>
87 prevUsers.map((u) => {
88 if (u.id === targetId) {
89 const isNowFollowing = !u.isFollowedByCurrentUser;
90 return {
91 ...u,
92 isFollowedByCurrentUser: isNowFollowing,
93 };
94 }
95 return u;
96 }),
97 );
98
[2730163]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));
[2b08bed]105 }
106 };
107
[6de2873]108 useEffect(() => {
109 const fetchUser = async () => {
110 setError(null);
111 try {
112 const response = await axiosInstance.get(`/users/${userId}`);
113 setUser(response.data);
114 } catch (err: any) {
[2730163]115 setError(handleError(err));
[6de2873]116 }
117 };
118 fetchUser();
119 }, [userId]);
120
121 if (error) {
122 return (
123 <div className="p-6 bg-red-50 border border-red-200 text-red-700 rounded-lg">
124 <h2 className="font-bold">Error</h2>
125 <p>{error}</p>
126 </div>
127 );
128 }
129
130 if (!user) return <div className="p-6">Loading...</div>;
131
132 return (
133 <div className="container mx-auto p-6">
[2b08bed]134 {isLoadingModal && (
135 <div className="fixed inset-0 z-40 bg-black/30 backdrop-blur-sm flex items-center justify-center">
136 <div className="flex items-center gap-3">
137 <div className="w-6 h-6 border-3 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
138 </div>
139 </div>
140 )}
[6de2873]141 <button
142 onClick={() => navigate(-1)}
[2730163]143 className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200 cursor-pointer"
[6de2873]144 >
145 ← Back
146 </button>
147
148 <div className="bg-white shadow-lg rounded-lg p-8">
149 <div className="flex items-start gap-6 mb-8">
150 <div className="shrink-0">
[2b08bed]151 <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">
152 {user.profilePhoto ? (
153 <img
154 src={`${baseURL}/${user.profilePhoto}`}
155 alt={user.fullName}
156 className="w-full h-full object-cover"
157 />
158 ) : (
159 user.fullName.charAt(0).toUpperCase()
160 )}
[6de2873]161 </div>
162 </div>
163
164 <div className="flex-1">
165 <h1 className="text-4xl font-bold mb-2">{user.fullName}</h1>
166 <span className="inline-block px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium mb-4">
167 {user.userType}
168 </span>
169
170 <div className="flex gap-6 mb-4 text-gray-700">
[2b08bed]171 <div
172 className={`flex flex-col ${user.userType == "LISTENER" ? "cursor-pointer" : "cursor-default"}`}
173 onClick={
174 user.userType === "LISTENER" ? displayFollowers : undefined
175 }
176 >
[6de2873]177 <span className="text-2xl font-bold">{user.followers}</span>
178 <span className="text-sm text-gray-500">Followers</span>
179 </div>
[2b08bed]180 <div
181 className={`flex flex-col ${user.userType == "LISTENER" ? "cursor-pointer" : "cursor-default"}`}
182 onClick={
183 user.userType === "LISTENER" ? displayFollowing : undefined
184 }
185 >
[6de2873]186 <span className="text-2xl font-bold">{user.following}</span>
187 <span className="text-sm text-gray-500">Following</span>
188 </div>
189 </div>
190
191 <button
192 onClick={handleFollow}
193 disabled={isFollowing}
194 className={`
195 px-6 py-2 font-semibold rounded-lg shadow-md
196 transition-colors duration-200
197 ${
198 isFollowing
199 ? "bg-gray-400 text-gray-200 cursor-not-allowed"
200 : user.isFollowedByCurrentUser
201 ? "bg-gray-200 text-gray-700 hover:bg-gray-300 cursor-pointer"
202 : "bg-blue-500 text-white hover:bg-blue-600 cursor-pointer"
203 }
204 `}
205 >
206 {user.isFollowedByCurrentUser ? "Unfollow" : "Follow"}
207 </button>
208 </div>
209 </div>
210
[98992cf]211 {user.userType === "ARTIST" ? (
212 <ArtistView contributions={user.contributions} />
213 ) : (
[6de2873]214 <ListenerView
[98992cf]215 likedEntities={user.likedEntities}
[6de2873]216 playlists={user.createdPlaylists}
217 />
218 )}
[2b08bed]219
220 {showModal && (
221 <UserListModal
222 title={modalTitle}
223 users={modalUsers}
224 onClose={() => setShowModal(false)}
225 onFollowToggle={handleFollowInModal}
226 />
227 )}
[6de2873]228 </div>
229 </div>
230 );
[4e5cf92]231};
232
233export default UserDetail;
Note: See TracBrowser for help on using the repository browser.