| [85512ff] | 1 | import { useState, useEffect } from "react";
|
|---|
| [c8baad1] | 2 | import { toast } from "react-toastify";
|
|---|
| 3 | import axiosInstance from "../../api/axiosInstance";
|
|---|
| [85512ff] | 4 | import type { CreatePlaylistModalProps } from "../../utils/types";
|
|---|
| 5 |
|
|---|
| 6 | const 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 |
|
|---|
| 174 | export default CreatePlaylistModal;
|
|---|