| 1 | import { useEffect, useState } from "react";
|
|---|
| 2 | import { Link, useNavigate } from "react-router-dom";
|
|---|
| 3 | import axiosInstance, { baseURL } from "../api/axiosInstance";
|
|---|
| 4 | import { useAuth } from "../context/authContext";
|
|---|
| 5 | import type { CatalogItem } from "../utils/types";
|
|---|
| 6 |
|
|---|
| 7 | const 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 |
|
|---|
| 257 | export default MySongs;
|
|---|