- Timestamp:
- 01/30/26 22:14:36 (5 months ago)
- Branches:
- main
- Children:
- 2730163
- Parents:
- 98992cf
- Location:
- frontend/src
- Files:
-
- 2 added
- 5 edited
-
App.tsx (modified) (1 diff)
-
components/userProfile/ArtistView.tsx (modified) (1 diff)
-
components/userProfile/ListenerView.tsx (modified) (1 diff)
-
components/userProfile/UserListModal.tsx (added)
-
pages/MusicalCollection.tsx (added)
-
pages/UserDetailView.tsx (modified) (8 diffs)
-
utils/types.ts (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/App.tsx
r98992cf r2b08bed 8 8 import Register from "./pages/Register"; 9 9 import UserDetailView from "./pages/UserDetailView"; 10 import MusicalCollection from "./pages/MusicalCollection"; 10 11 11 12 const Layout = () => { 12 return (13 <div className="flex flex-col min-h-screen">14 <Nav />15 <ToastContainer16 position="top-right"17 autoClose={2000}18 hideProgressBar={false}19 newestOnTop={false}20 closeOnClick21 rtl={false}22 pauseOnFocusLoss23 draggable24 pauseOnHover25 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 ); 32 33 }; 33 34 34 35 const 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 }, 61 66 ]); 62 67 63 68 function App() { 64 return <RouterProvider router={router} />;69 return <RouterProvider router={router} />; 65 70 } 66 71 -
frontend/src/components/userProfile/ArtistView.tsx
r98992cf r2b08bed 37 37 className="group cursor-pointer" 38 38 onClick={() => 39 navigate(`/ musical-entity/${album.musicalEntityId}`)39 navigate(`/collection/album/${album.musicalEntityId}`) 40 40 } 41 41 > -
frontend/src/components/userProfile/ListenerView.tsx
r98992cf r2b08bed 31 31 key={playlist.id} 32 32 className="group cursor-pointer" 33 onClick={() => navigate(`/playlist /${playlist.id}`)}33 onClick={() => navigate(`/playlists/${playlist.id}`)} 34 34 > 35 35 <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 4 4 import ArtistView from "../components/userProfile/ArtistView"; 5 5 import ListenerView from "../components/userProfile/ListenerView"; 6 import UserListModal from "../components/userProfile/UserListModal"; 6 7 import type { 7 8 MusicalEntity, 8 9 Playlist, 9 10 ArtistContribution, 11 BaseNonAdminUser, 10 12 } from "../utils/types"; 11 13 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 { 14 interface Artist extends BaseNonAdminUser { 22 15 userType: "ARTIST"; 23 16 contributions: ArtistContribution[]; 24 17 } 25 interface Listener extends Base User {18 interface Listener extends BaseNonAdminUser { 26 19 userType: "LISTENER"; 27 20 likedEntities: MusicalEntity[]; … … 33 26 const UserDetail = () => { 34 27 // user refers to the selected user NOT to the user from context 28 const baseURL = import.meta.env.VITE_API_BASE_URL; 35 29 const { userId } = useParams(); 36 30 const navigate = useNavigate(); 37 31 const [user, setUser] = useState<UserProfile | null>(null); 38 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); 39 37 const [isFollowing, setIsFollowing] = useState(false); 40 38 … … 47 45 `/users/follow/${userId}`, 48 46 ); 47 setUser(response.data); 49 48 } catch (err: any) { 50 49 console.error(err.response?.data?.error); 51 50 } finally { 52 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); 53 104 } 54 105 }; … … 60 111 const response = await axiosInstance.get(`/users/${userId}`); 61 112 console.log(response.data); 62 63 113 setUser(response.data); 64 114 } catch (err: any) { … … 84 134 return ( 85 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 )} 86 143 <button 87 144 onClick={() => navigate(-1)} … … 94 151 <div className="flex items-start gap-6 mb-8"> 95 152 <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 )} 98 163 </div> 99 164 </div> … … 106 171 107 172 <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 > 109 179 <span className="text-2xl font-bold">{user.followers}</span> 110 180 <span className="text-sm text-gray-500">Followers</span> 111 181 </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 > 113 188 <span className="text-2xl font-bold">{user.following}</span> 114 189 <span className="text-sm text-gray-500">Following</span> … … 144 219 /> 145 220 )} 221 222 {showModal && ( 223 <UserListModal 224 title={modalTitle} 225 users={modalUsers} 226 onClose={() => setShowModal(false)} 227 onFollowToggle={handleFollowInModal} 228 /> 229 )} 146 230 </div> 147 231 </div> -
frontend/src/utils/types.ts
r98992cf r2b08bed 19 19 genre: string; 20 20 type: string; 21 releasedBy: string; 21 22 } 22 23 … … 27 28 creatorName: string; 28 29 } 30 31 export 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.
