Changeset 0792d02
- Timestamp:
- 02/05/26 10:29:09 (5 months ago)
- Branches:
- main
- Children:
- 3de009b
- Parents:
- d2af1a6
- Files:
-
- 18 edited
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/NonAdminUserController.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/ListenerDto.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/NonAdminUserDto.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/ArtistRepository.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/ListenerRepository.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/NonAdminUserRepository.java (modified) (3 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/SavedPlaylistRepository.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/FollowService.java (modified) (6 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/ListenerService.java (modified) (1 diff)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/NonAdminUserService.java (modified) (5 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/PlaylistService.java (modified) (2 diffs)
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/mappers/NonAdminUserMapper.java (modified) (2 diffs)
-
frontend/src/App.tsx (modified) (1 diff)
-
frontend/src/components/userProfile/ListenerView.tsx (modified) (7 diffs)
-
frontend/src/components/userProfile/UserListModal.tsx (modified) (4 diffs)
-
frontend/src/pages/AllUsers.tsx (modified) (1 diff)
-
frontend/src/pages/UserDetail.tsx (modified) (9 diffs)
-
frontend/src/utils/types.ts (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/NonAdminUserController.java
rd2af1a6 r0792d02 26 26 } 27 27 28 @GetMapping("/{ id}")29 public HttpEntity<NonAdminUserDto>getById(@PathVariable Long id){30 return ResponseEntity.ok(nonAdminUserService.get ById(id));28 @GetMapping("/{username}") 29 public HttpEntity<NonAdminUserDto>getById(@PathVariable String username){ 30 return ResponseEntity.ok(nonAdminUserService.getNonAdminUserProfile(username)); 31 31 } 32 32 … … 36 36 } 37 37 38 @GetMapping("/{ id}/followers")39 public HttpEntity<List<NonAdminUserDto>>getFollowersForUser(@PathVariable Long id){40 return ResponseEntity.ok(followService.getFollowersForUser( id));38 @GetMapping("/{username}/followers") 39 public HttpEntity<List<NonAdminUserDto>>getFollowersForUser(@PathVariable String username){ 40 return ResponseEntity.ok(followService.getFollowersForUser(username)); 41 41 } 42 42 43 @GetMapping("/{ id}/following")44 public HttpEntity<List<NonAdminUserDto>>getFollowingForUser(@PathVariable Long id){45 return ResponseEntity.ok(followService.getFollowingForUser( id));43 @GetMapping("/{username}/following") 44 public HttpEntity<List<NonAdminUserDto>>getFollowingForUser(@PathVariable String username){ 45 return ResponseEntity.ok(followService.getFollowingForUser(username)); 46 46 } 47 47 48 @PostMapping("/{ id}/follow")49 public HttpEntity<FollowStatusDto>followUser(@PathVariable Long id){48 @PostMapping("/{username}/follow") 49 public HttpEntity<FollowStatusDto>followUser(@PathVariable String username){ 50 50 51 return ResponseEntity.ok(followService.toggleFollow( id));51 return ResponseEntity.ok(followService.toggleFollow(username)); 52 52 } 53 53 -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/ListenerDto.java
rd2af1a6 r0792d02 11 11 private List<MusicalEntityDto> likedEntities; 12 12 private List<PlaylistDto> createdPlaylists; 13 private List<PlaylistDto>savedPlaylists; 13 14 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/dto/NonAdminUserDto.java
rd2af1a6 r0792d02 9 9 10 10 public class NonAdminUserDto { 11 private Long id;12 11 private String username; 13 12 private String fullName; -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/ArtistRepository.java
rd2af1a6 r0792d02 19 19 Optional<Artist>findByIdWithUser(@Param("artistId")Long artistId); 20 20 21 @Query("SELECT a FROM Artist a " + 22 "JOIN FETCH a.nonAdminUser nau " + 23 "JOIN FETCH nau.user u " + 24 "WHERE u.username = :username") 25 Optional<Artist> findByUsername(@Param("username") String username); 26 27 21 28 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/ListenerRepository.java
rd2af1a6 r0792d02 17 17 "WHERE l.id = :listenerId") 18 18 Optional<Listener> findByIdWithUser(@Param("listenerId") Long listenerId); 19 20 @Query("SELECT l FROM Listener l " + 21 "JOIN FETCH l.nonAdminUser nu " + 22 "JOIN FETCH nu.user u " + 23 "WHERE u.username=:username") 24 25 Optional<Listener>findByUsername(@Param("username")String username); 19 26 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/NonAdminUserRepository.java
rd2af1a6 r0792d02 3 3 import org.springframework.data.jpa.repository.JpaRepository; 4 4 import org.springframework.data.jpa.repository.Query; 5 import org.springframework.data.repository.query.Param; 5 6 import org.springframework.stereotype.Repository; 6 7 … … 8 9 9 10 import java.util.List; 11 import java.util.Optional; 10 12 11 13 @Repository … … 20 22 List<NonAdminUser> searchByName(String name); 21 23 24 @Query("SELECT nau FROM NonAdminUser nau " + 25 "JOIN FETCH nau.user u " + 26 "WHERE u.username = :username") 27 Optional<NonAdminUser> findByUsername(@Param("username") String username); 28 22 29 23 30 -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/SavedPlaylistRepository.java
rd2af1a6 r0792d02 1 1 package com.ukim.finki.develop.finkwave.repository; 2 2 3 import com.ukim.finki.develop.finkwave.model.Playlist; 3 4 import org.springframework.data.jpa.repository.JpaRepository; 5 import org.springframework.data.jpa.repository.Query; 6 import org.springframework.data.repository.query.Param; 4 7 import org.springframework.stereotype.Repository; 5 8 … … 13 16 14 17 List<SavedPlaylist>findAllByListener_Id(Long currentUserId); 18 19 @Query("SELECT p FROM SavedPlaylist sp " + 20 "JOIN sp.playlist p " + 21 "JOIN FETCH p.createdBy l " + 22 "JOIN FETCH l.nonAdminUser nau " + 23 "JOIN FETCH nau.user u " + 24 "WHERE sp.id.listenerId = :listenerId") 25 List<Playlist> findSavedPlaylistsMetadata(@Param("listenerId") Long listenerId); 15 26 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/FollowService.java
rd2af1a6 r0792d02 3 3 import com.ukim.finki.develop.finkwave.exceptions.FollowException; 4 4 import com.ukim.finki.develop.finkwave.exceptions.UserNotFoundException; 5 import com.ukim.finki.develop.finkwave.model.Artist; 5 6 import com.ukim.finki.develop.finkwave.model.Follow; 6 7 import com.ukim.finki.develop.finkwave.model.FollowId; … … 17 18 import java.util.ArrayList; 18 19 import java.util.List; 20 import java.util.Optional; 19 21 import java.util.Set; 20 22 import java.util.stream.Collectors; … … 39 41 } 40 42 41 public List<NonAdminUserDto>getFollowersForUser(Long id){ 42 if (artistRepository.existsById(id)){ 43 private NonAdminUser getNonAdminUser(String username){ 44 Optional<Artist>artistOptional=artistRepository.findByUsername(username); 45 if (artistOptional.isPresent()){ 43 46 throw new FollowException("Cannot view for artist"); 44 47 } 48 return nonAdminUserRepository.findByUsername(username) 49 .orElseThrow(()-> new UserNotFoundException("Cannot find user with username: "+ username)); 50 } 51 52 public List<NonAdminUserDto>getFollowersForUser(String username){ 53 NonAdminUser nonAdminUser=getNonAdminUser(username); 54 55 Long id=nonAdminUser.getId(); 45 56 List<Follow> followers = followRepository.findFollowersWithProfile(id); 46 57 Long currentUserId=authService.getCurrentUserID(); … … 50 61 .map(f -> { 51 62 NonAdminUser follower = f.getFollower(); 52 NonAdminUserDto dto = mapper.toDto(follower, null, null, null);63 boolean isFollowedByCurrentUser=isFollowedByCurrentUser(currentUserId,follower.getId()); 53 64 54 dto.setIsFollowedByCurrentUser(isFollowedByCurrentUser(currentUserId,follower.getId())); 55 return dto; 65 return mapper.toDto(follower, null, new FollowStatusDto(isFollowedByCurrentUser,null,null)); 56 66 }) 57 67 .toList(); 58 68 } 59 69 60 public List<NonAdminUserDto>getFollowingForUser(Long id){ 61 if (artistRepository.existsById(id)){ 62 throw new FollowException("Cannot view for artist"); 63 } 70 public List<NonAdminUserDto>getFollowingForUser(String username){ 71 NonAdminUser nonAdminUser=getNonAdminUser(username); 72 Long id=nonAdminUser.getId(); 64 73 List<Follow>followings=followRepository.getFollowsByFollower_Id(id); 65 74 Long currentUserId=authService.getCurrentUserID(); … … 67 76 .map(f->{ 68 77 NonAdminUser followee=f.getFollowee(); 69 NonAdminUserDto dto=mapper.toDto(f.getFollowee(),null,null,null); 70 dto.setIsFollowedByCurrentUser(isFollowedByCurrentUser(currentUserId,followee.getId())); 71 return dto; 78 boolean isFollowedByCurrentUser=isFollowedByCurrentUser(currentUserId,followee.getId()); 79 return mapper.toDto(f.getFollowee(),null,new FollowStatusDto(isFollowedByCurrentUser,null,null)); 72 80 }) 73 81 .toList(); … … 76 84 77 85 78 public FollowStatusDto toggleFollow( Long id){86 public FollowStatusDto toggleFollow(String username){ 79 87 Long currentUserId=authService.getCurrentUserID(); 80 88 boolean isFollowing; 89 NonAdminUser nonAdminUser=nonAdminUserRepository.findByUsername(username) 90 .orElseThrow(()-> new UserNotFoundException("Cannot find user with username: "+ username)); 91 Long id=nonAdminUser.getId(); 81 92 82 93 if (currentUserId.equals(id)){ -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/ListenerService.java
rd2af1a6 r0792d02 28 28 return playlistService.findByCreatorId(listenerId); 29 29 } 30 31 public List<Playlist>getPlaylistsSavedByUser(Long listenerId){ 32 return playlistService.findSavedByUser(listenerId); 33 } 30 34 } 31 35 -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/NonAdminUserService.java
rd2af1a6 r0792d02 9 9 import org.springframework.stereotype.Service; 10 10 import org.springframework.transaction.annotation.Transactional; 11 12 import java.util.Collections; 11 13 import java.util.List; 14 import java.util.Optional; 12 15 import java.util.Set; 13 16 import java.util.stream.Collectors; … … 31 34 .map(user -> { 32 35 String type = determineType(user.getId()); 33 return mapper.toDto(user, type, n ull, null);36 return mapper.toDto(user, type, new FollowStatusDto(false,null,null)); 34 37 }) 35 38 .collect(Collectors.toList()); … … 37 40 38 41 @Transactional(readOnly = true) 39 public NonAdminUserDto getById(Long id) { 40 Long followers=followRepository.countByFolloweeId(id); 41 Long following=followRepository.countByFollowerId(id); 42 public NonAdminUserDto getNonAdminUserProfile(String username) { 43 42 44 Long currentUserId= authService.getCurrentUserID(); 43 45 44 boolean isFollowedByCurrentUser=false; 45 if (currentUserId != null) { 46 isFollowedByCurrentUser = followRepository.isFollowing(currentUserId, id); 46 47 Optional<Artist>artistOptional=artistRepository.findByUsername(username); 48 49 if (artistOptional.isPresent()) { 50 Artist artist = artistOptional.get(); 51 return getArtistProfile(artist, currentUserId); 47 52 } 48 NonAdminUserDto dto; 49 50 if (artistRepository.existsById(id)) {51 dto=getArtistProfile(id, followers, following);52 dto.setIsFollowedByCurrentUser(isFollowedByCurrentUser);53 return dto;53 54 Optional<Listener>listenerOptional=listenerRepository.findByUsername(username); 55 56 if (listenerOptional.isPresent()) { 57 Listener listener=listenerOptional.get(); 58 return getListenerProfile(listener,currentUserId); 54 59 } 55 56 if (listenerRepository.existsById(id)) { 57 dto=getListenerProfile(id,currentUserId, followers, following); 58 dto.setIsFollowedByCurrentUser(isFollowedByCurrentUser); 59 return dto; 60 } 61 62 throw new RuntimeException("User not found with id: " + id); 60 61 throw new UserNotFoundException("User not found: " + username); 63 62 } 64 63 65 private NonAdminUserDto getArtistProfile(Long artistId, Long followers, Long following) { 66 Artist artist = artistRepository.findByIdWithUser(artistId) 67 .orElseThrow(()->new UserNotFoundException("Artist not found with id: " + artistId)); 64 private FollowStatusDto getFollowingInfo(Long id,Long currentUserId){ 65 Long followers = followRepository.countByFolloweeId(id); 66 Long following = followRepository.countByFollowerId(id); 67 boolean isFollowing = followRepository.isFollowing(currentUserId, id); 68 return new FollowStatusDto(isFollowing,followers,following); 69 } 70 71 private NonAdminUserDto getArtistProfile(Artist artist,Long currentUserId) { 72 Long artistId=artist.getId(); 73 74 List<ArtistContributionDto>artistContributionDtos=artistService.getArtistContributions(artistId); 75 FollowStatusDto followStatusDto=getFollowingInfo(artistId,currentUserId); 76 77 return mapper.toArtistDTO(artist, artistContributionDtos,followStatusDto); 78 } 79 80 private NonAdminUserDto getListenerProfile(Listener listener, Long currentUserId) { 81 Long profileId = listener.getId(); 82 83 FollowStatusDto followStatusDto = getFollowingInfo(profileId, currentUserId); 68 84 69 85 70 List< ArtistContributionDto>artistContributionDtos=artistService.getArtistContributions(artistId);86 List<MusicalEntityDto> likedEntities = listenerService.getLikedEntities(currentUserId, profileId); 71 87 72 return mapper.toArtistDTO(artist, artistContributionDtos,followers,following); 73 } 74 75 private NonAdminUserDto getListenerProfile(Long listenerId,Long currentUserId, Long followers, Long following) { 76 Listener listener = listenerRepository.findByIdWithUser(listenerId) 77 .orElseThrow(()->new UserNotFoundException("Listener not found with id: " + listenerId)); 88 Set<Long> visitorSavedPlaylistIds = (currentUserId != null) 89 ? savedPlaylistRepository.findAllByListener_Id(currentUserId).stream() 90 .map(sp -> sp.getPlaylist().getId()) 91 .collect(Collectors.toSet()) 92 : Collections.emptySet(); 78 93 79 94 80 List<MusicalEntityDto>musicalEntityDtos=listenerService.getLikedEntities(currentUserId,listenerId); 81 Set<Long> savedIds = savedPlaylistRepository.findAllByListener_Id(currentUserId) 82 .stream().map(sp -> sp.getPlaylist().getId()).collect(Collectors.toSet()); 83 List<PlaylistDto>playlists=listenerService.getPlaylistsCreatedByUser(listenerId).stream() 84 .map(p->new PlaylistDto( 85 p.getId(), 86 p.getName(), 87 p.getCover(), 88 p.getCreatedBy().getNonAdminUser().getUser().getFullName(), 89 null, 90 savedIds.contains(p.getId()) 91 )).toList(); 95 List<PlaylistDto> createdPlaylists = listenerService.getPlaylistsCreatedByUser(profileId).stream() 96 .map(p -> mapToPlaylistDto(p, visitorSavedPlaylistIds)) 97 .toList(); 92 98 93 return mapper.toListenerDTO(listener, musicalEntityDtos,followers,following,playlists); 99 100 List<PlaylistDto> savedPlaylists = savedPlaylistRepository.findSavedPlaylistsMetadata(profileId).stream() 101 .map(p -> mapToPlaylistDto(p, visitorSavedPlaylistIds)) 102 .toList(); 103 104 return mapper.toListenerDTO(listener, likedEntities, followStatusDto, createdPlaylists, savedPlaylists); 94 105 } 95 106 … … 104 115 return nonAdminUserRepository.searchByName(name).stream() 105 116 .map(user -> { 106 String type = determineType(user.getId());107 Long followers = followRepository.countByFolloweeId(user.getId());108 Long following = followRepository.countByFollowerId(user.getId());109 return mapper.toDto(user, type, followers, following);117 Long userId= user.getId(); 118 String type = determineType(userId); 119 FollowStatusDto followStatusDto=getFollowingInfo(userId,null); 120 return mapper.toDto(user, type,followStatusDto); 110 121 }) 111 122 .collect(Collectors.toList()); … … 113 124 114 125 115 126 private PlaylistDto mapToPlaylistDto(Playlist p, Set<Long> savedBy) { 127 return new PlaylistDto( 128 p.getId(), 129 p.getName(), 130 p.getCover(), 131 p.getCreatedBy().getNonAdminUser().getUser().getFullName(), 132 null, 133 savedBy.contains(p.getId()) 134 ); 135 } 116 136 private String determineType(Long id) { 117 137 if (artistRepository.existsById(id)) { -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/PlaylistService.java
rd2af1a6 r0792d02 29 29 } 30 30 31 public List<Playlist>findSavedByUser(Long id){ 32 return savedPlaylistRepository.findSavedPlaylistsMetadata(id); 33 } 34 31 35 public PlaylistDto getPlaylist(Long id){ 32 36 Long currentUserId=authService.getCurrentUserID(); … … 48 52 49 53 54 55 50 56 } -
finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/mappers/NonAdminUserMapper.java
rd2af1a6 r0792d02 15 15 public class NonAdminUserMapper { 16 16 17 public NonAdminUserDto toArtistDTO(Artist artist, List<ArtistContributionDto> entitiesDTO, Long followers, Long following) {17 public NonAdminUserDto toArtistDTO(Artist artist, List<ArtistContributionDto> entitiesDTO, FollowStatusDto followStatusDto) { 18 18 ArtistDto dto=new ArtistDto(); 19 populateBaseClassFields(dto,artist.getNonAdminUser(),"ARTIST",follow ers,following);19 populateBaseClassFields(dto,artist.getNonAdminUser(),"ARTIST",followStatusDto); 20 20 dto.setContributions(entitiesDTO); 21 21 return dto; 22 22 } 23 23 24 public NonAdminUserDto toListenerDTO(Listener listener, List<MusicalEntityDto> musicalEntityDtos, Long followers, Long following, List<PlaylistDto>playlists) { 24 public NonAdminUserDto toListenerDTO(Listener listener, 25 List<MusicalEntityDto> musicalEntityDtos, 26 FollowStatusDto followStatusDto, 27 List<PlaylistDto>createdPlaylists, 28 List<PlaylistDto>savedPlaylists) { 25 29 ListenerDto dto=new ListenerDto(); 26 populateBaseClassFields(dto,listener.getNonAdminUser(),"LISTENER",follow ers,following);30 populateBaseClassFields(dto,listener.getNonAdminUser(),"LISTENER",followStatusDto); 27 31 dto.setLikedEntities(musicalEntityDtos); 28 dto.setCreatedPlaylists(playlists); 32 dto.setCreatedPlaylists(createdPlaylists); 33 dto.setSavedPlaylists(savedPlaylists); 29 34 return dto; 30 35 } 31 36 32 public NonAdminUserDto toDto(NonAdminUser user,String type, Long followers,Long following){37 public NonAdminUserDto toDto(NonAdminUser user,String type, FollowStatusDto followStatusDto){ 33 38 NonAdminUserDto dto=new NonAdminUserDto(); 34 populateBaseClassFields(dto,user,type,follow ers,following);39 populateBaseClassFields(dto,user,type,followStatusDto); 35 40 return dto; 36 41 } … … 38 43 39 44 40 private void populateBaseClassFields(NonAdminUserDto dto,NonAdminUser user,String type,Long followers,Long following){ 41 42 dto.setId(user.getId()); 45 private void populateBaseClassFields(NonAdminUserDto dto,NonAdminUser user,String type,FollowStatusDto followStatusDto){ 43 46 dto.setUsername(user.getUser().getUsername()); 44 47 dto.setFullName(user.getUser().getFullName()); 45 48 dto.setProfilePhoto(user.getUser().getProfilePhoto()); 46 49 dto.setUserType(type); 47 dto.setFollowers(followers); 48 dto.setFollowing(following); 50 dto.setFollowers(followStatusDto.followerCount()); 51 dto.setFollowing(followStatusDto.followingCount()); 52 dto.setIsFollowedByCurrentUser(followStatusDto.isFollowing()); 49 53 } 50 54 } -
frontend/src/App.tsx
rd2af1a6 r0792d02 51 51 }, 52 52 { 53 path: "/users/:user Id",53 path: "/users/:username", 54 54 element: <UserDetail />, 55 55 }, -
frontend/src/components/userProfile/ListenerView.tsx
rd2af1a6 r0792d02 4 4 import axiosInstance from "../../api/axiosInstance"; 5 5 import { useState } from "react"; 6 6 7 interface ListenerViewProps { 7 8 likedEntities: MusicalEntity[] | []; 8 playlists: Playlist[] | []; 9 createdPlaylists: Playlist[] | []; 10 savedPlaylists: Playlist[] | []; 9 11 } 10 12 11 const ListenerView = ({ likedEntities, playlists }: ListenerViewProps) => { 13 const ListenerView = ({ 14 likedEntities, 15 createdPlaylists, 16 savedPlaylists, 17 }: ListenerViewProps) => { 12 18 const navigate = useNavigate(); 13 19 const [items, setItems] = useState(likedEntities); 20 const [savedItems, setSavedItems] = useState(savedPlaylists); 14 21 const [toast, setToast] = useState<{ message: string; show: boolean }>({ 15 22 message: "", … … 27 34 const likedAlbums = items.filter((e) => e.type === "ALBUM"); 28 35 29 const handleSavePlaylist = (e: React.MouseEvent, playlistId: number) => { 36 const handleSavePlaylist = async ( 37 e: React.MouseEvent, 38 playlistId: number, 39 playlistName: string, 40 ) => { 30 41 e.stopPropagation(); 31 42 32 console.log("Save playlist:", playlistId); 43 try { 44 const response = await axiosInstance.post(`/playlist/${playlistId}/save`); 45 const data = response.data; 46 47 setSavedItems((prevItems) => 48 data.isSaved 49 ? [...prevItems, prevItems.find((p) => p.id === playlistId)!] 50 : prevItems.filter((p) => p.id !== playlistId), 51 ); 52 53 showToast( 54 data.isSaved 55 ? `Saved "${playlistName}"` 56 : `Removed "${playlistName}" from saved playlists`, 57 ); 58 } catch (err: any) { 59 showToast(err.response?.data?.error || "Failed to save playlist"); 60 } 33 61 }; 34 62 … … 63 91 </div> 64 92 )} 65 {playlists && playlists.length > 0 && ( 93 94 {createdPlaylists && createdPlaylists.length > 0 && ( 66 95 <section> 67 96 <div className="flex items-center gap-3 mb-6"> … … 70 99 Created Playlists 71 100 </h3> 72 <span className="text-sm text-gray-500">({playlists.length})</span> 101 <span className="text-sm text-gray-500"> 102 ({createdPlaylists.length}) 103 </span> 73 104 </div> 74 105 75 106 <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> 76 { playlists.map((playlist) => (107 {createdPlaylists.map((playlist) => ( 77 108 <div 78 109 key={playlist.id} … … 93 124 )} 94 125 95 {/* Save button overlay */}96 126 <button 97 onClick={(e) => handleSavePlaylist(e, playlist.id)} 127 onClick={(e) => 128 handleSavePlaylist(e, playlist.id, playlist.name) 129 } 98 130 className="absolute top-2 right-2 p-2 bg-white/90 hover:bg-white rounded-full shadow-md transition-all duration-200 opacity-0 group-hover:opacity-100" 99 131 title={playlist.isSavedByCurrentUser ? "Unsave" : "Save"} … … 106 138 }`} 107 139 /> 140 </button> 141 </div> 142 <p className="text-sm font-semibold text-gray-900 truncate group-hover:text-blue-600 transition-colors"> 143 {playlist.name} 144 </p> 145 <p className="text-xs text-gray-500 truncate"> 146 {playlist.creatorName} 147 </p> 148 </div> 149 ))} 150 </div> 151 </section> 152 )} 153 154 {savedItems && savedItems.length > 0 && ( 155 <section> 156 <div className="flex items-center gap-3 mb-6"> 157 <Bookmark className="w-6 h-6 text-blue-600 fill-blue-600" /> 158 <h3 className="text-2xl font-bold text-gray-800"> 159 Saved Playlists 160 </h3> 161 <span className="text-sm text-gray-500">({savedItems.length})</span> 162 </div> 163 164 <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> 165 {savedItems.map((playlist) => ( 166 <div 167 key={playlist.id} 168 className="group cursor-pointer" 169 onClick={() => navigate(`/collection/playlist/${playlist.id}`)} 170 > 171 <div className="relative aspect-square rounded-lg overflow-hidden bg-gray-100 mb-3 shadow-md group-hover:shadow-lg transition-all"> 172 {playlist.cover ? ( 173 <img 174 src={playlist.cover} 175 alt={playlist.name} 176 className="w-full h-full object-cover" 177 /> 178 ) : ( 179 <div className="w-full h-full bg-gradient-to-br from-blue-400 to-purple-500 flex items-center justify-center"> 180 <ListMusic className="w-16 h-16 text-white opacity-30" /> 181 </div> 182 )} 183 184 <button 185 onClick={(e) => 186 handleSavePlaylist(e, playlist.id, playlist.name) 187 } 188 className="absolute top-2 right-2 p-2 bg-white/90 hover:bg-white rounded-full shadow-md transition-all duration-200 opacity-0 group-hover:opacity-100" 189 title="Unsave" 190 > 191 <Bookmark className="w-5 h-5 fill-blue-600 text-blue-600" /> 108 192 </button> 109 193 </div> … … 234 318 {likedEntities && 235 319 likedEntities.length === 0 && 236 (!playlists || playlists.length === 0) && ( 320 (!createdPlaylists || createdPlaylists.length === 0) && 321 (!savedItems || savedItems.length === 0) && ( 237 322 <div className="flex flex-col items-center justify-center py-16 text-gray-400"> 238 323 <Music className="w-20 h-20 mb-4 opacity-20" /> -
frontend/src/components/userProfile/UserListModal.tsx
rd2af1a6 r0792d02 6 6 users: BaseNonAdminUser[]; 7 7 onClose: () => void; 8 onFollowToggle: (target Id: number) => Promise<void>;8 onFollowToggle: (targetUsername: string) => Promise<void>; 9 9 } 10 10 … … 37 37 users.map((u) => ( 38 38 <div 39 key={u. id}39 key={u.username} 40 40 className="flex items-center justify-between p-3 hover:bg-gray-50 rounded-lg transition-colors" 41 41 > … … 44 44 onClick={() => { 45 45 onClose(); 46 navigate(`/users/${u. id}`);46 navigate(`/users/${u.username}`); 47 47 }} 48 48 > … … 64 64 65 65 <button 66 onClick={() => onFollowToggle(u. id)}66 onClick={() => onFollowToggle(u.username)} 67 67 className={`px-4 py-1 text-sm font-medium rounded-md transition-colors cursor-pointer ${ 68 68 u.isFollowedByCurrentUser -
frontend/src/pages/AllUsers.tsx
rd2af1a6 r0792d02 4 4 5 5 interface User { 6 id: number; 7 username: string; 8 fullName: string; 6 username: string; 7 fullName: string; 9 8 } 10 9 11 10 const AllUsers = () => { 12 const [users, setUsers] = useState<User[]>([]);13 const [searchedUser, setSearchedUser] = useState<string>("");14 const [error, setError] = useState<string | null>(null);15 const navigate = useNavigate();11 const [users, setUsers] = useState<User[]>([]); 12 const [searchedUser, setSearchedUser] = useState<string>(""); 13 const [error, setError] = useState<string | null>(null); 14 const navigate = useNavigate(); 16 15 17 useEffect(() => {18 const fetchUsers = async () => {19 const response = axiosInstance.get("/users/all");20 setUsers((await response).data);21 };22 fetchUsers();23 }, []);16 useEffect(() => { 17 const fetchUsers = async () => { 18 const response = axiosInstance.get("/users/all"); 19 setUsers((await response).data); 20 }; 21 fetchUsers(); 22 }, []); 24 23 25 const handleUserClick = (userId: number) => {26 navigate(`/users/${userId}`);27 };24 const handleUserClick = (username: string) => { 25 navigate(`/users/${username}`); 26 }; 28 27 29 const handleSearch = async (e: React.FormEvent) => {30 e.preventDefault();31 try {32 const response = await axiosInstance.get(`/users/search`, {33 params: { name: searchedUser },34 });35 setUsers(response.data);36 } catch (err: any) {37 const errorMessage = err.response?.data?.error || "Search failed";38 setError(errorMessage);39 }40 };28 const handleSearch = async (e: React.FormEvent) => { 29 e.preventDefault(); 30 try { 31 const response = await axiosInstance.get(`/users/search`, { 32 params: { name: searchedUser }, 33 }); 34 setUsers(response.data); 35 } catch (err: any) { 36 const errorMessage = err.response?.data?.error || "Search failed"; 37 setError(errorMessage); 38 } 39 }; 41 40 42 if (users.length == 0) return <div className="p-6">Loading...</div>;43 if (error) {44 return (45 <div className="p-6 bg-red-50 border border-red-200 text-red-700 rounded-lg">46 <h2 className="font-bold">Error</h2>47 <p>{error}</p>48 </div>49 );50 }41 if (users.length == 0) return <div className="p-6">Loading...</div>; 42 if (error) { 43 return ( 44 <div className="p-6 bg-red-50 border border-red-200 text-red-700 rounded-lg"> 45 <h2 className="font-bold">Error</h2> 46 <p>{error}</p> 47 </div> 48 ); 49 } 51 50 52 return (53 <div className="container mx-auto p-6">54 <h1 className="text-3xl font-bold mb-6">All Users</h1>55 <form onSubmit={handleSearch} className="flex gap-2 mb-10">56 <input57 type="text"58 value={searchedUser}59 onChange={(e) => setSearchedUser(e.target.value)}60 className="border p-2 rounded w-full"61 placeholder="Search for a user..."62 />63 <button64 type="submit"65 className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 transition-colors"66 >67 Search68 </button>69 </form>51 return ( 52 <div className="container mx-auto p-6"> 53 <h1 className="text-3xl font-bold mb-6">All Users</h1> 54 <form onSubmit={handleSearch} className="flex gap-2 mb-10"> 55 <input 56 type="text" 57 value={searchedUser} 58 onChange={(e) => setSearchedUser(e.target.value)} 59 className="border p-2 rounded w-full" 60 placeholder="Search for a user..." 61 /> 62 <button 63 type="submit" 64 className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 transition-colors" 65 > 66 Search 67 </button> 68 </form> 70 69 71 <div className="grid gap-4"> 72 {users.map((u) => ( 73 <div 74 key={u.id} 75 onClick={() => handleUserClick(u.id)} 76 className="bg-white shadow-md rounded-lg p-4 hover:shadow-lg hover:bg-gray-50 cursor-pointer transition-all" 77 > 78 <h2 className="text-xl font-semibold">{u.fullName}</h2> 79 <p className="text-gray-600">@{u.username}</p> 80 <p className="text-gray-600">{u.id}</p> 81 </div> 82 ))} 83 </div> 84 </div> 85 ); 70 <div className="grid gap-4"> 71 {users.map((u) => ( 72 <div 73 key={u.username} 74 onClick={() => handleUserClick(u.username)} 75 className="bg-white shadow-md rounded-lg p-4 hover:shadow-lg hover:bg-gray-50 cursor-pointer transition-all" 76 > 77 <h2 className="text-xl font-semibold">{u.fullName}</h2> 78 <p className="text-gray-600">@{u.username}</p> 79 </div> 80 ))} 81 </div> 82 </div> 83 ); 86 84 }; 87 85 -
frontend/src/pages/UserDetail.tsx
rd2af1a6 r0792d02 27 27 likedEntities: MusicalEntity[]; 28 28 createdPlaylists: Playlist[]; 29 savedPlaylists: Playlist[]; 29 30 } 30 31 … … 34 35 // user refers to the selected user NOT to the user from context 35 36 const baseURL = import.meta.env.VITE_API_BASE_URL; 36 const { user Id} = useParams();37 const { username } = useParams(); 37 38 const navigate = useNavigate(); 38 39 const [user, setUser] = useState<UserProfile | null>(null); … … 50 51 try { 51 52 const response = await axiosInstance.post<FollowStatus>( 52 `/users/${user Id}/follow`,53 `/users/${username}/follow`, 53 54 ); 54 55 setUser((prev) => { … … 68 69 }; 69 70 70 const handleFollowInModal = async (target Id: number) => {71 const handleFollowInModal = async (targetUsername: string) => { 71 72 try { 72 73 const response = await axiosInstance.post<FollowStatus>( 73 `/users/${ targetId}/follow`,74 `/users/${username}/follow`, 74 75 ); 75 76 76 77 setModalUsers((prevUsers) => 77 78 prevUsers.map((u) => 78 u. id === targetId79 u.username === targetUsername 79 80 ? { ...u, isFollowedByCurrentUser: response.data.isFollowing } 80 81 : u, … … 100 101 setIsLoadingModal(true); 101 102 try { 102 const response = await axiosInstance.get(`/users/${user Id}/followers`);103 const response = await axiosInstance.get(`/users/${username}/followers`); 103 104 setModalUsers(response.data); 104 105 setModalTitle("Followers"); … … 113 114 setIsLoadingModal(true); 114 115 try { 115 const response = await axiosInstance.get(`/users/${user Id}/following`);116 const response = await axiosInstance.get(`/users/${username}/following`); 116 117 setModalUsers(response.data); 117 118 setModalTitle("Following"); … … 128 129 setError(null); 129 130 try { 130 const response = await axiosInstance.get(`/users/${user Id}`);131 const response = await axiosInstance.get(`/users/${username}`); 131 132 console.log(response.data); 132 133 setUser(response.data); … … 136 137 }; 137 138 fetchUser(); 138 }, [user Id]);139 }, [username]); 139 140 140 141 if (error) { … … 233 234 <ListenerView 234 235 likedEntities={user.likedEntities} 235 playlists={user.createdPlaylists} 236 createdPlaylists={user.createdPlaylists} 237 savedPlaylists={user.savedPlaylists} 236 238 /> 237 239 )} -
frontend/src/utils/types.ts
rd2af1a6 r0792d02 44 44 45 45 export interface BaseNonAdminUser { 46 id: number;46 username: string; 47 47 fullName: string; 48 48 userType: string;
Note:
See TracChangeset
for help on using the changeset viewer.
