source: frontend/src/pages/UserDetailView.tsx@ 2b08bed

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

show followers, following and songs in album

  • Property mode set to 100644
File size: 7.6 KB
Line 
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";
6import UserListModal from "../components/userProfile/UserListModal";
7import type {
8 MusicalEntity,
9 Playlist,
10 ArtistContribution,
11 BaseNonAdminUser,
12} from "../utils/types";
13
14interface Artist extends BaseNonAdminUser {
15 userType: "ARTIST";
16 contributions: ArtistContribution[];
17}
18interface Listener extends BaseNonAdminUser {
19 userType: "LISTENER";
20 likedEntities: MusicalEntity[];
21 createdPlaylists: Playlist[];
22}
23
24type UserProfile = Artist | Listener;
25
26const UserDetail = () => {
27 // user refers to the selected user NOT to the user from context
28 const baseURL = import.meta.env.VITE_API_BASE_URL;
29 const { userId } = useParams();
30 const navigate = useNavigate();
31 const [user, setUser] = useState<UserProfile | null>(null);
32 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);
37 const [isFollowing, setIsFollowing] = useState(false);
38
39 const handleFollow = async () => {
40 if (!user) return;
41
42 setIsFollowing(true);
43 try {
44 const response = await axiosInstance.post<UserProfile>(
45 `/users/follow/${userId}`,
46 );
47 setUser(response.data);
48 } catch (err: any) {
49 console.error(err.response?.data?.error);
50 } finally {
51 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);
104 }
105 };
106
107 useEffect(() => {
108 const fetchUser = async () => {
109 setError(null);
110 try {
111 const response = await axiosInstance.get(`/users/${userId}`);
112 console.log(response.data);
113 setUser(response.data);
114 } catch (err: any) {
115 const errorMessage =
116 err.response?.data?.error || "Failed to fetch user";
117 setError(errorMessage);
118 }
119 };
120 fetchUser();
121 }, [userId]);
122
123 if (error) {
124 return (
125 <div className="p-6 bg-red-50 border border-red-200 text-red-700 rounded-lg">
126 <h2 className="font-bold">Error</h2>
127 <p>{error}</p>
128 </div>
129 );
130 }
131
132 if (!user) return <div className="p-6">Loading...</div>;
133
134 return (
135 <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 )}
143 <button
144 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"
146 >
147 ← Back
148 </button>
149
150 <div className="bg-white shadow-lg rounded-lg p-8">
151 <div className="flex items-start gap-6 mb-8">
152 <div className="shrink-0">
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 )}
163 </div>
164 </div>
165
166 <div className="flex-1">
167 <h1 className="text-4xl font-bold mb-2">{user.fullName}</h1>
168 <span className="inline-block px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium mb-4">
169 {user.userType}
170 </span>
171
172 <div className="flex gap-6 mb-4 text-gray-700">
173 <div
174 className={`flex flex-col ${user.userType == "LISTENER" ? "cursor-pointer" : "cursor-default"}`}
175 onClick={
176 user.userType === "LISTENER" ? displayFollowers : undefined
177 }
178 >
179 <span className="text-2xl font-bold">{user.followers}</span>
180 <span className="text-sm text-gray-500">Followers</span>
181 </div>
182 <div
183 className={`flex flex-col ${user.userType == "LISTENER" ? "cursor-pointer" : "cursor-default"}`}
184 onClick={
185 user.userType === "LISTENER" ? displayFollowing : undefined
186 }
187 >
188 <span className="text-2xl font-bold">{user.following}</span>
189 <span className="text-sm text-gray-500">Following</span>
190 </div>
191 </div>
192
193 <button
194 onClick={handleFollow}
195 disabled={isFollowing}
196 className={`
197 px-6 py-2 font-semibold rounded-lg shadow-md
198 transition-colors duration-200
199 ${
200 isFollowing
201 ? "bg-gray-400 text-gray-200 cursor-not-allowed"
202 : user.isFollowedByCurrentUser
203 ? "bg-gray-200 text-gray-700 hover:bg-gray-300 cursor-pointer"
204 : "bg-blue-500 text-white hover:bg-blue-600 cursor-pointer"
205 }
206 `}
207 >
208 {user.isFollowedByCurrentUser ? "Unfollow" : "Follow"}
209 </button>
210 </div>
211 </div>
212
213 {user.userType === "ARTIST" ? (
214 <ArtistView contributions={user.contributions} />
215 ) : (
216 <ListenerView
217 likedEntities={user.likedEntities}
218 playlists={user.createdPlaylists}
219 />
220 )}
221
222 {showModal && (
223 <UserListModal
224 title={modalTitle}
225 users={modalUsers}
226 onClose={() => setShowModal(false)}
227 onFollowToggle={handleFollowInModal}
228 />
229 )}
230 </div>
231 </div>
232 );
233};
234
235export default UserDetail;
Note: See TracBrowser for help on using the repository browser.