source: frontend/src/components/playlist/CreatePlaylistModal.tsx

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

create new playlist, delete playlists and add song to playlist on playlist creation

  • Property mode set to 100644
File size: 5.7 KB
RevLine 
[85512ff]1import { useState, useEffect } from "react";
[c8baad1]2import { toast } from "react-toastify";
3import axiosInstance from "../../api/axiosInstance";
[85512ff]4import type { CreatePlaylistModalProps } from "../../utils/types";
5
6const CreatePlaylistModal = ({
7 isOpen,
8 onClose,
[c8baad1]9 onSuccess,
[d1ee039]10 songId,
[85512ff]11}: CreatePlaylistModalProps) => {
12 const [playlistName, setPlaylistName] = useState("");
13 const [isClosing, setIsClosing] = useState(false);
14 const [isOpening, setIsOpening] = useState(false);
[c8baad1]15 const [isSubmitting, setIsSubmitting] = useState(false);
16 const [error, setError] = useState("");
[85512ff]17
18 useEffect(() => {
19 if (isOpen) {
20 setIsClosing(false);
21 setPlaylistName("");
[c8baad1]22 setError("");
[85512ff]23 setIsOpening(true);
24 setTimeout(() => setIsOpening(false), 10);
25 }
26 }, [isOpen]);
27
28 const handleClose = () => {
29 setIsClosing(true);
30 setTimeout(() => {
31 onClose();
32 setIsClosing(false);
33 }, 200);
34 };
35
[c8baad1]36 const handleSubmit = async (e: React.FormEvent) => {
[85512ff]37 e.preventDefault();
[c8baad1]38 if (!playlistName.trim() || isSubmitting) return;
39
40 setIsSubmitting(true);
41 try {
[d1ee039]42 await axiosInstance.post("/playlists", null, {
43 params: {
44 playlistName: playlistName.trim(),
45 ...(songId != null && { songId }),
46 },
47 });
48
[c8baad1]49 toast.success("Playlist created successfully!");
[d1ee039]50 await onSuccess();
[85512ff]51 handleClose();
[c8baad1]52 } catch (err: any) {
53 setError(err?.response?.data?.error || "Failed to create playlist");
54 } finally {
55 setIsSubmitting(false);
[85512ff]56 }
57 };
58
59 if (!isOpen && !isClosing) return null;
60
61 return (
62 <div
63 className={`fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 px-4 transition-opacity duration-200 ${
64 isClosing || isOpening ? "opacity-0" : "opacity-100"
65 }`}
66 onClick={handleClose}
67 >
68 <div
69 className={`bg-[#181818] rounded-xl p-8 w-full max-w-md border border-white/10 transition-all duration-200 ${
70 isClosing || isOpening
71 ? "scale-95 opacity-0"
72 : "scale-100 opacity-100"
73 }`}
74 onClick={(e) => e.stopPropagation()}
75 >
76 <div className="flex justify-between items-center mb-6">
77 <h2 className="text-2xl font-bold text-white">Create Playlist</h2>
78 <button
79 onClick={handleClose}
80 className="text-gray-400 hover:text-white transition-colors cursor-pointer"
81 aria-label="Close modal"
82 >
83 <svg
84 className="w-6 h-6"
85 fill="none"
86 stroke="currentColor"
87 viewBox="0 0 24 24"
88 >
89 <path
90 strokeLinecap="round"
91 strokeLinejoin="round"
92 strokeWidth={2}
93 d="M6 18L18 6M6 6l12 12"
94 />
95 </svg>
96 </button>
97 </div>
98
99 <form onSubmit={handleSubmit} className="space-y-6">
100 <div>
101 <label
102 className="block text-sm font-medium text-gray-300 mb-2"
103 htmlFor="playlistName"
104 >
105 Playlist Name
106 </label>
107 <input
108 type="text"
109 id="playlistName"
110 autoFocus
[c8baad1]111 className={`w-full bg-[#282828] border rounded-lg py-3 px-4 text-white placeholder-gray-500 focus:outline-none transition-all ${
112 error
113 ? "border-red-500 focus:border-red-500 focus:ring-1 focus:ring-red-500"
114 : "border-white/10 focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954]"
115 }`}
[85512ff]116 placeholder="Enter playlist name"
117 value={playlistName}
[c8baad1]118 onChange={(e) => {
119 setPlaylistName(e.target.value);
120 }}
[85512ff]121 />
[c8baad1]122 <div
123 className={`overflow-hidden transition-all duration-200 ease-out ${
124 error ? "max-h-20 opacity-100 mt-2" : "max-h-0 opacity-0"
125 }`}
126 >
127 <p className="text-sm text-red-400 flex items-center gap-1.5">
128 <svg
129 className="w-4 h-4 shrink-0"
130 fill="currentColor"
131 viewBox="0 0 20 20"
132 >
133 <path
134 fillRule="evenodd"
135 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"
136 clipRule="evenodd"
137 />
138 </svg>
139 {error}
140 </p>
141 </div>
[85512ff]142 </div>
143
144 <div className="flex gap-3">
145 <button
146 type="button"
147 onClick={handleClose}
[c8baad1]148 disabled={isSubmitting}
149 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"
[85512ff]150 >
151 Cancel
152 </button>
153 <button
154 type="submit"
[c8baad1]155 disabled={!playlistName.trim() || isSubmitting}
156 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"
[85512ff]157 >
[c8baad1]158 {isSubmitting ? (
159 <>
160 <div className="w-4 h-4 border-2 border-black/20 border-t-black rounded-full animate-spin"></div>
161 Creating...
162 </>
163 ) : (
164 "Create"
165 )}
[85512ff]166 </button>
167 </div>
168 </form>
169 </div>
170 </div>
171 );
172};
173
174export default CreatePlaylistModal;
Note: See TracBrowser for help on using the repository browser.