source: frontend/src/pages/MySongs.tsx@ 765e166

main
Last change on this file since 765e166 was d85e8e3, checked in by Filip Gavrilovski <filipgavrilovski28@…>, 5 months ago

fix bug in song repository and add sequence generator for musical entities

  • Property mode set to 100644
File size: 8.2 KB
Line 
1import { useEffect, useState } from "react";
2import { Link, useNavigate } from "react-router-dom";
3import axiosInstance, { baseURL } from "../api/axiosInstance";
4import { useAuth } from "../context/authContext";
5import type { CatalogItem } from "../utils/types";
6
7const MySongs = () => {
8 const { user } = useAuth();
9 const navigate = useNavigate();
10 const [artistCatalog, setArtistCatalog] = useState<CatalogItem[]>([]);
11 const [loading, setLoading] = useState(true);
12 const [activeTab, setActiveTab] = useState<"singles" | "albums">("singles");
13
14 useEffect(() => {
15 const fetchArtistCatalog = async () => {
16 try {
17 setLoading(true);
18 const response =
19 await axiosInstance.get<CatalogItem[]>("/songs/catalog");
20 const data = response.data;
21 setArtistCatalog(data);
22 } catch (error) {
23 console.error("Error fetching music:", error);
24 } finally {
25 setLoading(false);
26 }
27 };
28
29 if (user?.isArtist) {
30 fetchArtistCatalog();
31 }
32 }, [user]);
33
34 if (!user?.isArtist) {
35 return (
36 <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] flex items-center justify-center">
37 <div className="text-center">
38 <p className="text-red-400 text-xl mb-4">
39 You need to be an artist to access this page.
40 </p>
41 <Link to="/" className="text-[#1db954] hover:underline text-sm">
42 ← Back to Home
43 </Link>
44 </div>
45 </div>
46 );
47 }
48
49 if (loading) {
50 return (
51 <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] flex items-center justify-center">
52 <div className="flex flex-col items-center gap-4">
53 <div className="w-12 h-12 border-4 border-white/10 border-t-[#1db954] rounded-full animate-spin" />
54 <p className="text-gray-400 text-lg">Loading your music…</p>
55 </div>
56 </div>
57 );
58 }
59
60 return (
61 <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] text-white">
62 <div className="max-w-6xl mx-auto px-6 py-10">
63 {/* Header */}
64 <div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
65 <div>
66 <h1 className="text-4xl font-extrabold mb-2">My Music</h1>
67 <p className="text-gray-400">Manage your songs and albums</p>
68 </div>
69 <button
70 onClick={() => navigate("/publish")}
71 className="flex items-center gap-2 px-6 py-3 bg-[#1db954] text-black rounded-full font-semibold hover:bg-[#1ed760] hover:scale-105 transition-all cursor-pointer"
72 >
73 <svg
74 className="w-5 h-5"
75 fill="none"
76 stroke="currentColor"
77 viewBox="0 0 24 24"
78 >
79 <path
80 strokeLinecap="round"
81 strokeLinejoin="round"
82 strokeWidth={2}
83 d="M12 4v16m8-8H4"
84 />
85 </svg>
86 Publish New
87 </button>
88 </div>
89
90 {/* Tabs */}
91 <div className="flex gap-1 mb-8 bg-[#181818] rounded-lg p-1 w-fit">
92 <button
93 onClick={() => setActiveTab("singles")}
94 className={`px-6 py-2.5 rounded-md text-sm font-medium transition-colors cursor-pointer ${
95 activeTab === "singles"
96 ? "bg-[#282828] text-white"
97 : "text-gray-400 hover:text-white"
98 }`}
99 >
100 Singles (
101 {artistCatalog.filter((item) => item.type === "SONG").length})
102 </button>
103 <button
104 onClick={() => setActiveTab("albums")}
105 className={`px-6 py-2.5 rounded-md text-sm font-medium transition-colors cursor-pointer ${
106 activeTab === "albums"
107 ? "bg-[#282828] text-white"
108 : "text-gray-400 hover:text-white"
109 }`}
110 >
111 Albums (
112 {artistCatalog.filter((item) => item.type === "ALBUM").length})
113 </button>
114 </div>
115
116 {/* Content */}
117 {activeTab === "singles" ? (
118 <div className="space-y-2">
119 {artistCatalog.filter((item) => item.type === "SONG").length ===
120 0 ? (
121 <div className="text-center py-16">
122 <svg
123 className="w-16 h-16 mx-auto text-gray-600 mb-4"
124 fill="none"
125 stroke="currentColor"
126 viewBox="0 0 24 24"
127 >
128 <path
129 strokeLinecap="round"
130 strokeLinejoin="round"
131 strokeWidth={1.5}
132 d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"
133 />
134 </svg>
135 <p className="text-gray-400 text-lg mb-4">
136 You haven't published any singles yet
137 </p>
138 <button
139 onClick={() => navigate("/publish")}
140 className="text-[#1db954] hover:underline"
141 >
142 Publish your first song
143 </button>
144 </div>
145 ) : (
146 artistCatalog
147 .filter((item) => item.type === "SONG")
148 .map((song, index) => (
149 <div
150 key={song.id}
151 className="flex items-center gap-4 p-4 rounded-lg hover:bg-white/5 transition-colors group"
152 >
153 <span className="w-8 text-center text-gray-500 text-sm">
154 {index + 1}
155 </span>
156 <img
157 src={
158 song.cover ? `${baseURL}/${song.cover}` : "/favicon.png"
159 }
160 alt={song.title}
161 className="w-12 h-12 rounded object-cover"
162 onError={(e) => {
163 (e.target as HTMLImageElement).src = "/favicon.png";
164 }}
165 />
166 <div className="flex-1 min-w-0">
167 <Link
168 to={`/songs/${song.id}`}
169 className="text-white font-medium hover:underline truncate block"
170 >
171 {song.title}
172 </Link>
173 <p className="text-gray-400 text-sm">{song.genre}</p>
174 </div>
175 <p className="text-gray-400 text-sm">{song.releaseDate}</p>
176 </div>
177 ))
178 )}
179 </div>
180 ) : (
181 <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
182 {artistCatalog.filter((item) => item.type === "ALBUM").length ===
183 0 ? (
184 <div className="col-span-full text-center py-16">
185 <svg
186 className="w-16 h-16 mx-auto text-gray-600 mb-4"
187 fill="none"
188 stroke="currentColor"
189 viewBox="0 0 24 24"
190 >
191 <path
192 strokeLinecap="round"
193 strokeLinejoin="round"
194 strokeWidth={1.5}
195 d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
196 />
197 </svg>
198 <p className="text-gray-400 text-lg mb-4">
199 You haven't published any albums yet
200 </p>
201 <button
202 onClick={() => navigate("/publish")}
203 className="text-[#1db954] hover:underline"
204 >
205 Publish your first album
206 </button>
207 </div>
208 ) : (
209 artistCatalog
210 .filter((item) => item.type === "ALBUM")
211 .map((album) => (
212 <Link
213 key={album.id}
214 to={`/collection/album/${album.id}`}
215 className="bg-[#181818] rounded-xl p-4 hover:bg-[#282828] transition-colors group"
216 >
217 <div className="relative w-full pt-[100%] rounded-lg overflow-hidden mb-4 bg-[#282828]">
218 <img
219 src={
220 album.cover
221 ? `${baseURL}/${album.cover}`
222 : "/favicon.png"
223 }
224 alt={album.title}
225 className="absolute inset-0 w-full h-full object-cover"
226 onError={(e) => {
227 (e.target as HTMLImageElement).src = "/favicon.png";
228 }}
229 />
230 <div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
231 <div className="w-12 h-12 bg-[#1db954] rounded-full flex items-center justify-center shadow-xl transform translate-y-2 group-hover:translate-y-0 transition-transform">
232 <svg
233 className="w-6 h-6 text-black"
234 fill="currentColor"
235 viewBox="0 0 24 24"
236 >
237 <path d="M8 5v14l11-7z" />
238 </svg>
239 </div>
240 </div>
241 </div>
242 <h3 className="font-semibold text-white truncate mb-1">
243 {album.title}
244 </h3>
245 <p className="text-gray-400 text-sm">{album.genre}</p>
246 <p className="text-gray-400 text-sm">{album.releaseDate}</p>
247 </Link>
248 ))
249 )}
250 </div>
251 )}
252 </div>
253 </div>
254 );
255};
256
257export default MySongs;
Note: See TracBrowser for help on using the repository browser.