Changeset 2730163 for frontend/src
- Timestamp:
- 01/31/26 22:34:40 (5 months ago)
- Branches:
- main
- Children:
- d2af1a6
- Parents:
- 2b08bed
- Location:
- frontend/src
- Files:
-
- 1 added
- 5 edited
- 1 moved
-
App.tsx (modified) (2 diffs)
-
components/userProfile/ListenerView.tsx (modified) (3 diffs)
-
components/userProfile/UserListModal.tsx (modified) (1 diff)
-
pages/MusicalCollection.tsx (modified) (6 diffs)
-
pages/UserDetail.tsx (moved) (moved from frontend/src/pages/UserDetailView.tsx ) (8 diffs)
-
utils/error.ts (added)
-
utils/types.ts (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/App.tsx
r2b08bed r2730163 7 7 import Nav from "./pages/Nav"; 8 8 import Register from "./pages/Register"; 9 import UserDetail View from "./pages/UserDetailView";9 import UserDetail from "./pages/UserDetail"; 10 10 import MusicalCollection from "./pages/MusicalCollection"; 11 11 … … 52 52 { 53 53 path: "/users/:userId", 54 element: <UserDetail View/>,54 element: <UserDetail />, 55 55 }, 56 56 { -
frontend/src/components/userProfile/ListenerView.tsx
r2b08bed r2730163 20 20 <div className="flex items-center gap-2 mb-6 pb-2 border-b-2 border-gray-200"> 21 21 <ListMusic className="w-6 h-6 text-gray-700" /> 22 <h3 className="text-2xl font-bold text-gray-800">My Playlists</h3> 22 <h3 className="text-2xl font-bold text-gray-800"> 23 Created Playlists 24 </h3> 23 25 <span className="text-sm text-gray-400 ml-1"> 24 26 ({playlists.length}) … … 31 33 key={playlist.id} 32 34 className="group cursor-pointer" 33 onClick={() => navigate(`/ playlists/${playlist.id}`)}35 onClick={() => navigate(`/collection/playlist/${playlist.id}`)} 34 36 > 35 37 <div className="aspect-square rounded-md overflow-hidden bg-gray-100 mb-2 relative shadow-sm group-hover:shadow-lg transition-all"> … … 105 107 key={album.id} 106 108 className="group cursor-pointer" 107 onClick={() => navigate(`/ musical-entity/${album.id}`)}109 onClick={() => navigate(`/collection/album/${album.id}`)} 108 110 > 109 111 <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"> -
frontend/src/components/userProfile/UserListModal.tsx
r2b08bed r2730163 25 25 <button 26 26 onClick={onClose} 27 className="text-gray-500 hover:text-black text-2xl "27 className="text-gray-500 hover:text-black text-2xl cursor-pointer" 28 28 > 29 29 × -
frontend/src/pages/MusicalCollection.tsx
r2b08bed r2730163 2 2 import { useNavigate, useParams } from "react-router-dom"; 3 3 import axiosInstance from "../api/axiosInstance"; 4 5 interface Song { 4 import type { Song, Album, Playlist } from "../utils/types"; 5 import { handleError } from "../utils/error"; 6 interface CollectionView { 6 7 id: number; 7 8 title: string; 8 genre: string; 9 type: "SONG"; 10 releasedBy: string; 11 isLikedByCurrentUser?: boolean; 12 } 13 14 interface MusicalEntity { 15 id: number; 16 title: string; 17 genre: string; 9 genre?: string; 18 10 type: string; 19 11 releasedBy: string; 20 12 isLikedByCurrentUser?: boolean; 21 songs ?: Song[];13 songs: Song[]; 22 14 } 23 15 … … 25 17 const { type, id } = useParams(); 26 18 const navigate = useNavigate(); 27 const [collection, setCollection] = useState< MusicalEntity| null>(null);19 const [collection, setCollection] = useState<CollectionView | null>(null); 28 20 const [isLoading, setIsLoading] = useState(true); 29 21 const [error, setError] = useState<string | null>(null); 22 23 const normalizeCollection = ( 24 data: Album | Playlist, 25 type: string, 26 ): CollectionView => { 27 if (type === "album") { 28 const album = data as Album; 29 return { 30 id: album.id, 31 title: album.title, 32 genre: album.genre, 33 type: album.type, 34 releasedBy: album.releasedBy, 35 isLikedByCurrentUser: album.isLikedByCurrentUser, 36 songs: album.songs, 37 }; 38 } else { 39 const playlist = data as Playlist; 40 return { 41 id: playlist.id, 42 title: playlist.name, 43 genre: undefined, 44 type: "PLAYLIST", 45 releasedBy: playlist.creatorName, 46 isLikedByCurrentUser: undefined, 47 songs: playlist.songsInPlaylist, 48 }; 49 } 50 }; 30 51 31 52 useEffect(() => { … … 37 58 type === "album" ? `/albums/${id}` : `/playlists/${id}`; 38 59 const response = await axiosInstance.get(endpoint); 39 console.log(response.data); 40 setCollection(response.data); 60 61 const normalized = normalizeCollection(response.data, type!); 62 setCollection(normalized); 41 63 } catch (err: any) { 42 setError( err.response?.data?.error || "Failed to load collection");64 setError(handleError(err)); 43 65 } finally { 44 66 setIsLoading(false); … … 67 89 <button 68 90 onClick={() => navigate(-1)} 69 className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200 "91 className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200 cursor-pointer" 70 92 > 71 93 ← Back … … 88 110 <div className="flex items-center gap-3 text-gray-700 mb-4"> 89 111 <span className="font-semibold">{collection.releasedBy}</span> 90 <span>•</span> 91 <span className="text-gray-600">{collection.genre}</span> 112 {collection.genre && ( 113 <> 114 <span>•</span> 115 <span className="text-gray-600">{collection.genre}</span> 116 </> 117 )} 92 118 {collection.songs && ( 93 119 <> … … 101 127 </div> 102 128 103 <button 104 className="flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors duration-200" 105 aria-label={collection.isLikedByCurrentUser ? "Unlike" : "Like"} 106 > 107 <svg 108 className="w-5 h-5" 109 fill={collection.isLikedByCurrentUser ? "#ef4444" : "none"} 110 stroke={collection.isLikedByCurrentUser ? "#ef4444" : "#6b7280"} 111 strokeWidth="2" 112 viewBox="0 0 24 24" 129 {type === "album" && ( 130 <button 131 className="flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors duration-200" 132 aria-label={collection.isLikedByCurrentUser ? "Unlike" : "Like"} 113 133 > 114 <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 115 </svg> 116 <span className="text-sm font-medium text-gray-700"> 117 {collection.isLikedByCurrentUser ? "Liked" : "Like"} 118 </span> 119 </button> 134 <svg 135 className="w-5 h-5" 136 fill={collection.isLikedByCurrentUser ? "#ef4444" : "none"} 137 stroke={ 138 collection.isLikedByCurrentUser ? "#ef4444" : "#6b7280" 139 } 140 strokeWidth="2" 141 viewBox="0 0 24 24" 142 > 143 <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 144 </svg> 145 <span className="text-sm font-medium text-gray-700"> 146 {collection.isLikedByCurrentUser ? "Liked" : "Like"} 147 </span> 148 </button> 149 )} 120 150 </div> 121 151 </div> -
frontend/src/pages/UserDetail.tsx
r2b08bed r2730163 5 5 import ListenerView from "../components/userProfile/ListenerView"; 6 6 import UserListModal from "../components/userProfile/UserListModal"; 7 import { handleError } from "../utils/error"; 7 8 import type { 8 9 MusicalEntity, … … 43 44 try { 44 45 const response = await axiosInstance.post<UserProfile>( 45 `/users/ follow/${userId}`,46 `/users/${userId}/follow`, 46 47 ); 47 48 setUser(response.data); 48 49 } catch (err: any) { 49 console.error(err.response?.data?.error);50 setError(handleError(err)); 50 51 } finally { 51 52 setIsFollowing(false); … … 56 57 setIsLoadingModal(true); 57 58 try { 58 const response = await axiosInstance.get(`/users/ followers/${userId}`);59 const response = await axiosInstance.get(`/users/${userId}/followers`); 59 60 setModalUsers(response.data); 60 61 setModalTitle("Followers"); 61 62 setShowModal(true); 62 63 } catch (err) { 63 console.error("Failed to fetch followers");64 setError(handleError(err)); 64 65 } finally { 65 66 setIsLoadingModal(false); … … 69 70 setIsLoadingModal(true); 70 71 try { 71 const response = await axiosInstance.get(`/users/ following/${userId}`);72 const response = await axiosInstance.get(`/users/${userId}/following`); 72 73 setModalUsers(response.data); 73 74 setModalTitle("Following"); 74 75 setShowModal(true); 75 } catch (err ) {76 console.error("Failed to fetch following users");76 } catch (err: any) { 77 setError(handleError(err)); 77 78 } finally { 78 79 setIsLoadingModal(false); … … 82 83 const handleFollowInModal = async (targetId: number) => { 83 84 try { 84 await axiosInstance.post(`/users/ follow/${targetId}`);85 await axiosInstance.post(`/users/${targetId}/follow`); 85 86 setModalUsers((prevUsers) => 86 87 prevUsers.map((u) => { … … 96 97 ); 97 98 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);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)); 104 105 } 105 106 }; … … 110 111 try { 111 112 const response = await axiosInstance.get(`/users/${userId}`); 112 console.log(response.data);113 113 setUser(response.data); 114 114 } catch (err: any) { 115 const errorMessage = 116 err.response?.data?.error || "Failed to fetch user"; 117 setError(errorMessage); 115 setError(handleError(err)); 118 116 } 119 117 }; … … 143 141 <button 144 142 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 "143 className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200 cursor-pointer" 146 144 > 147 145 ← Back -
frontend/src/utils/types.ts
r2b08bed r2730163 13 13 role: string; 14 14 entityType: string; 15 isLikedByCurrentUser: boolean; 15 16 } 17 16 18 export interface MusicalEntity { 17 19 id: number; … … 20 22 type: string; 21 23 releasedBy: string; 24 isLikedByCurrentUser?: boolean; 25 } 26 27 export interface Song extends MusicalEntity { 28 type: "SONG"; 29 } 30 31 export interface Album extends MusicalEntity { 32 type: "ALBUM"; 33 songs: Song[]; 22 34 } 23 35 … … 27 39 cover: string; 28 40 creatorName: string; 41 songsInPlaylist: Song[]; 29 42 } 30 43
Note:
See TracChangeset
for help on using the changeset viewer.
