source: frontend/src/pages/MusicalCollection.tsx@ d2af1a6

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

show songs in playlists/albums

  • Property mode set to 100644
File size: 7.0 KB
Line 
1import { useEffect, useState } from "react";
2import { useNavigate, useParams } from "react-router-dom";
3import axiosInstance from "../api/axiosInstance";
4import type { Song, Album, Playlist } from "../utils/types";
5import { handleError } from "../utils/error";
6interface CollectionView {
7 id: number;
8 title: string;
9 genre?: string;
10 type: string;
11 releasedBy: string;
12 isLikedByCurrentUser?: boolean;
13 songs: Song[];
14}
15
16const MusicalCollection = () => {
17 const { type, id } = useParams();
18 const navigate = useNavigate();
19 const [collection, setCollection] = useState<CollectionView | null>(null);
20 const [isLoading, setIsLoading] = useState(true);
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 };
51
52 useEffect(() => {
53 const fetchData = async () => {
54 setIsLoading(true);
55 setError(null);
56 try {
57 const endpoint =
58 type === "album" ? `/albums/${id}` : `/playlists/${id}`;
59 const response = await axiosInstance.get(endpoint);
60
61 const normalized = normalizeCollection(response.data, type!);
62 setCollection(normalized);
63 } catch (err: any) {
64 setError(handleError(err));
65 } finally {
66 setIsLoading(false);
67 }
68 };
69 fetchData();
70 }, [id, type]);
71
72 if (isLoading) {
73 return <div className="p-6">Loading...</div>;
74 }
75
76 if (error) {
77 return (
78 <div className="p-6 bg-red-50 border border-red-200 text-red-700 rounded-lg">
79 <h2 className="font-bold">Error</h2>
80 <p>{error}</p>
81 </div>
82 );
83 }
84
85 if (!collection) return null;
86
87 return (
88 <div className="container mx-auto p-6">
89 <button
90 onClick={() => navigate(-1)}
91 className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors duration-200 cursor-pointer"
92 >
93 ← Back
94 </button>
95
96 <div className="bg-white shadow-lg rounded-lg p-8">
97 <div className="flex items-start gap-6 mb-8">
98 <div className="shrink-0">
99 <div className="w-40 h-40 rounded-lg bg-gradient-to-br from-blue-400 to-purple-500 flex items-center justify-center text-white text-5xl font-bold shadow-lg">
100 {collection.title.charAt(0).toUpperCase()}
101 </div>
102 </div>
103
104 <div className="flex-1">
105 <span className="inline-block px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium mb-2">
106 {collection.type}
107 </span>
108 <h1 className="text-4xl font-bold mb-3">{collection.title}</h1>
109
110 <div className="flex items-center gap-3 text-gray-700 mb-4">
111 <span className="font-semibold">{collection.releasedBy}</span>
112 {collection.genre && (
113 <>
114 <span>•</span>
115 <span className="text-gray-600">{collection.genre}</span>
116 </>
117 )}
118 {collection.songs && (
119 <>
120 <span>•</span>
121 <span className="text-gray-600">
122 {collection.songs.length} song
123 {collection.songs.length !== 1 ? "s" : ""}
124 </span>
125 </>
126 )}
127 </div>
128
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"}
133 >
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 )}
150 </div>
151 </div>
152
153 <div className="border-t pt-6">
154 <h2 className="text-2xl font-bold mb-4 text-gray-800">Songs</h2>
155
156 {collection.songs && collection.songs.length > 0 ? (
157 <div className="space-y-2">
158 {collection.songs.map((song, index) => (
159 <div
160 key={song.id}
161 className="flex items-center gap-4 p-3 rounded-lg hover:bg-gray-50 transition-colors duration-150"
162 >
163 <span className="text-gray-500 font-medium w-8 text-center">
164 {index + 1}
165 </span>
166
167 <div className="flex-1 min-w-0">
168 <p className="font-semibold text-gray-900 truncate">
169 {song.title}
170 </p>
171 <p className="text-sm text-gray-600 truncate">
172 {song.releasedBy}
173 </p>
174 </div>
175
176 <span className="text-sm text-gray-600 px-3 py-1 bg-gray-100 rounded-full">
177 {song.genre}
178 </span>
179
180 <button
181 className="p-2 hover:bg-gray-100 rounded-full transition-colors duration-200"
182 aria-label={song.isLikedByCurrentUser ? "Unlike" : "Like"}
183 >
184 <svg
185 className="w-5 h-5"
186 fill={song.isLikedByCurrentUser ? "#ef4444" : "none"}
187 stroke={song.isLikedByCurrentUser ? "#ef4444" : "#6b7280"}
188 strokeWidth="2"
189 viewBox="0 0 24 24"
190 >
191 <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" />
192 </svg>
193 </button>
194 </div>
195 ))}
196 </div>
197 ) : (
198 <p className="text-center text-gray-500 py-8">No songs available</p>
199 )}
200 </div>
201 </div>
202 </div>
203 );
204};
205
206export default MusicalCollection;
Note: See TracBrowser for help on using the repository browser.