source: frontend/src/pages/MySongs.tsx@ 27660af

main
Last change on this file since 27660af was 8dd3e22, checked in by Filip Gavrilovski <filipgavrilovski28@…>, 5 months ago

add client side form for publishing new music with mock data and api calls

  • Property mode set to 100644
File size: 8.1 KB
Line 
1import { useEffect, useState } from "react";
2import { Link, useNavigate } from "react-router-dom";
3import axiosInstance 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={song.cover || "/favicon.png"}
158 alt={song.title}
159 className="w-12 h-12 rounded object-cover"
160 onError={(e) => {
161 (e.target as HTMLImageElement).src = "/favicon.png";
162 }}
163 />
164 <div className="flex-1 min-w-0">
165 <Link
166 to={`/songs/${song.id}`}
167 className="text-white font-medium hover:underline truncate block"
168 >
169 {song.title}
170 </Link>
171 <p className="text-gray-400 text-sm">{song.genre}</p>
172 </div>
173 <p className="text-gray-400 text-sm">{song.releaseDate}</p>
174 </div>
175 ))
176 )}
177 </div>
178 ) : (
179 <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
180 {artistCatalog.filter((item) => item.type === "ALBUM").length ===
181 0 ? (
182 <div className="col-span-full text-center py-16">
183 <svg
184 className="w-16 h-16 mx-auto text-gray-600 mb-4"
185 fill="none"
186 stroke="currentColor"
187 viewBox="0 0 24 24"
188 >
189 <path
190 strokeLinecap="round"
191 strokeLinejoin="round"
192 strokeWidth={1.5}
193 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"
194 />
195 </svg>
196 <p className="text-gray-400 text-lg mb-4">
197 You haven't published any albums yet
198 </p>
199 <button
200 onClick={() => navigate("/publish")}
201 className="text-[#1db954] hover:underline"
202 >
203 Publish your first album
204 </button>
205 </div>
206 ) : (
207 artistCatalog
208 .filter((item) => item.type === "ALBUM")
209 .map((album) => (
210 <Link
211 key={album.id}
212 to={`/collection/album/${album.id}`}
213 className="bg-[#181818] rounded-xl p-4 hover:bg-[#282828] transition-colors group"
214 >
215 <div className="relative w-full pt-[100%] rounded-lg overflow-hidden mb-4 bg-[#282828]">
216 <img
217 src={album.cover || "/favicon.png"}
218 alt={album.title}
219 className="absolute inset-0 w-full h-full object-cover"
220 onError={(e) => {
221 (e.target as HTMLImageElement).src = "/favicon.png";
222 }}
223 />
224 <div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
225 <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">
226 <svg
227 className="w-6 h-6 text-black"
228 fill="currentColor"
229 viewBox="0 0 24 24"
230 >
231 <path d="M8 5v14l11-7z" />
232 </svg>
233 </div>
234 </div>
235 </div>
236 <h3 className="font-semibold text-white truncate mb-1">
237 {album.title}
238 </h3>
239 <p className="text-gray-400 text-sm">{album.genre}</p>
240 <p className="text-gray-400 text-sm">{album.releaseDate}</p>
241 </Link>
242 ))
243 )}
244 </div>
245 )}
246 </div>
247 </div>
248 );
249};
250
251export default MySongs;
Note: See TracBrowser for help on using the repository browser.