- Timestamp:
- 02/15/26 01:58:19 (5 months ago)
- Branches:
- main
- Children:
- d1ee039
- Parents:
- ce45c7a
- Location:
- frontend/src
- Files:
-
- 6 edited
- 1 moved
-
components/Sidebar.tsx (modified) (2 diffs)
-
components/SongItem.tsx (modified) (1 diff)
-
components/playlist/CreatePlaylistModal.tsx (modified) (7 diffs)
-
components/playlist/PlaylistDropdown.tsx (moved) (moved from frontend/src/components/PlaylistDropdown.tsx ) (1 diff)
-
pages/LandingPage.tsx (modified) (1 diff)
-
pages/SongDetail.tsx (modified) (1 diff)
-
utils/types.ts (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/components/Sidebar.tsx
rce45c7a rc8baad1 43 43 } 44 44 }, [user]); 45 46 const handleCreatePlaylist = async (playlistName: string) => {47 try {48 await axiosInstance.post("/playlists", { name: playlistName });49 toast.success("Playlist created successfully!");50 await refreshPlaylists(false);51 } catch (error) {52 toast.error(getErrorMessage(error));53 }54 };55 45 56 46 const isLoading = songsLoading || playlistsLoading; … … 282 272 isOpen={isModalOpen} 283 273 onClose={() => setIsModalOpen(false)} 284 onSu bmit={handleCreatePlaylist}274 onSuccess={() => refreshPlaylists(false)} 285 275 /> 286 276 </> -
frontend/src/components/SongItem.tsx
rce45c7a rc8baad1 4 4 import { usePlayer } from "../context/playerContext"; 5 5 import { toEmbedUrl } from "../utils/utils"; 6 import PlaylistDropdown from "./ PlaylistDropdown";6 import PlaylistDropdown from "./playlist/PlaylistDropdown"; 7 7 8 8 export interface SongItemData { -
frontend/src/components/playlist/CreatePlaylistModal.tsx
rce45c7a rc8baad1 1 1 import { useState, useEffect } from "react"; 2 import { toast } from "react-toastify"; 3 import axiosInstance from "../../api/axiosInstance"; 2 4 import type { CreatePlaylistModalProps } from "../../utils/types"; 3 5 … … 5 7 isOpen, 6 8 onClose, 7 onSu bmit,9 onSuccess, 8 10 }: CreatePlaylistModalProps) => { 9 11 const [playlistName, setPlaylistName] = useState(""); 10 12 const [isClosing, setIsClosing] = useState(false); 11 13 const [isOpening, setIsOpening] = useState(false); 14 const [isSubmitting, setIsSubmitting] = useState(false); 15 const [error, setError] = useState(""); 12 16 13 17 useEffect(() => { … … 15 19 setIsClosing(false); 16 20 setPlaylistName(""); 21 setError(""); 17 22 setIsOpening(true); 18 23 setTimeout(() => setIsOpening(false), 10); … … 28 33 }; 29 34 30 const handleSubmit = (e: React.FormEvent) => {35 const handleSubmit = async (e: React.FormEvent) => { 31 36 e.preventDefault(); 32 if (playlistName.trim()) { 33 onSubmit(playlistName.trim()); 37 if (!playlistName.trim() || isSubmitting) return; 38 39 setIsSubmitting(true); 40 try { 41 await axiosInstance.post( 42 `/playlists?playlistName=${encodeURIComponent(playlistName.trim())}`, 43 ); 44 toast.success("Playlist created successfully!"); 45 onSuccess(); 34 46 handleClose(); 47 } catch (err: any) { 48 setError(err?.response?.data?.error || "Failed to create playlist"); 49 } finally { 50 setIsSubmitting(false); 35 51 } 36 52 }; … … 88 104 id="playlistName" 89 105 autoFocus 90 className="w-full bg-[#282828] border border-white/10 rounded-lg py-3 px-4 text-white placeholder-gray-500 focus:outline-none focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954] transition-all" 106 className={`w-full bg-[#282828] border rounded-lg py-3 px-4 text-white placeholder-gray-500 focus:outline-none transition-all ${ 107 error 108 ? "border-red-500 focus:border-red-500 focus:ring-1 focus:ring-red-500" 109 : "border-white/10 focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954]" 110 }`} 91 111 placeholder="Enter playlist name" 92 112 value={playlistName} 93 onChange={(e) => setPlaylistName(e.target.value)} 113 onChange={(e) => { 114 setPlaylistName(e.target.value); 115 }} 94 116 /> 117 <div 118 className={`overflow-hidden transition-all duration-200 ease-out ${ 119 error ? "max-h-20 opacity-100 mt-2" : "max-h-0 opacity-0" 120 }`} 121 > 122 <p className="text-sm text-red-400 flex items-center gap-1.5"> 123 <svg 124 className="w-4 h-4 shrink-0" 125 fill="currentColor" 126 viewBox="0 0 20 20" 127 > 128 <path 129 fillRule="evenodd" 130 d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" 131 clipRule="evenodd" 132 /> 133 </svg> 134 {error} 135 </p> 136 </div> 95 137 </div> 96 138 … … 99 141 type="button" 100 142 onClick={handleClose} 101 className="flex-1 py-3 bg-[#282828] rounded-full text-white font-semibold hover:bg-[#3a3a3a] transition-colors cursor-pointer" 143 disabled={isSubmitting} 144 className="flex-1 py-3 bg-[#282828] rounded-full text-white font-semibold hover:bg-[#3a3a3a] transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed" 102 145 > 103 146 Cancel … … 105 148 <button 106 149 type="submit" 107 disabled={!playlistName.trim() }108 className="flex-1 py-3 bg-[#1db954] rounded-full text-black font-semibold hover:bg-[#1ed760] transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-[#1db954] cursor-pointer "150 disabled={!playlistName.trim() || isSubmitting} 151 className="flex-1 py-3 bg-[#1db954] rounded-full text-black font-semibold hover:bg-[#1ed760] transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-[#1db954] cursor-pointer flex items-center justify-center gap-2" 109 152 > 110 Create 153 {isSubmitting ? ( 154 <> 155 <div className="w-4 h-4 border-2 border-black/20 border-t-black rounded-full animate-spin"></div> 156 Creating... 157 </> 158 ) : ( 159 "Create" 160 )} 111 161 </button> 112 162 </div> -
frontend/src/components/playlist/PlaylistDropdown.tsx
rce45c7a rc8baad1 1 1 import { useEffect, useRef, useState } from "react"; 2 2 import { createPortal } from "react-dom"; 3 import axiosInstance from "../ api/axiosInstance";4 import { useCreatedPlaylists } from "../ context/playlistContext";3 import axiosInstance from "../../api/axiosInstance"; 4 import { useCreatedPlaylists } from "../../context/playlistContext"; 5 5 6 6 interface PlaylistDropdownProps { -
frontend/src/pages/LandingPage.tsx
rce45c7a rc8baad1 2 2 import { useNavigate } from "react-router-dom"; 3 3 import axiosInstance, { baseURL } from "../api/axiosInstance"; 4 import PlaylistDropdown from "../components/ PlaylistDropdown";4 import PlaylistDropdown from "../components/playlist/PlaylistDropdown"; 5 5 import AlbumResult from "../components/search/AlbumResult"; 6 6 import SongResult from "../components/search/SongResult"; -
frontend/src/pages/SongDetail.tsx
rce45c7a rc8baad1 3 3 import { toast } from "react-toastify"; 4 4 import axiosInstance, { baseURL } from "../api/axiosInstance"; 5 import PlaylistDropdown from "../components/ PlaylistDropdown";5 import PlaylistDropdown from "../components/playlist/PlaylistDropdown"; 6 6 import { useAuth } from "../context/authContext"; 7 7 import { usePlayer } from "../context/playerContext"; -
frontend/src/utils/types.ts
rce45c7a rc8baad1 74 74 isOpen: boolean; 75 75 onClose: () => void; 76 onSu bmit: (playlistName: string) => void;76 onSuccess: () => void; 77 77 } 78 78
Note:
See TracChangeset
for help on using the changeset viewer.
