| 1 | import { useState, useEffect } from "react";
|
|---|
| 2 | import { toast } from "react-toastify";
|
|---|
| 3 | import axiosInstance from "../../api/axiosInstance";
|
|---|
| 4 | import type { CreatePlaylistModalProps } from "../../utils/types";
|
|---|
| 5 |
|
|---|
| 6 | const CreatePlaylistModal = ({
|
|---|
| 7 | isOpen,
|
|---|
| 8 | onClose,
|
|---|
| 9 | onSuccess,
|
|---|
| 10 | }: CreatePlaylistModalProps) => {
|
|---|
| 11 | const [playlistName, setPlaylistName] = useState("");
|
|---|
| 12 | const [isClosing, setIsClosing] = useState(false);
|
|---|
| 13 | const [isOpening, setIsOpening] = useState(false);
|
|---|
| 14 | const [isSubmitting, setIsSubmitting] = useState(false);
|
|---|
| 15 | const [error, setError] = useState("");
|
|---|
| 16 |
|
|---|
| 17 | useEffect(() => {
|
|---|
| 18 | if (isOpen) {
|
|---|
| 19 | setIsClosing(false);
|
|---|
| 20 | setPlaylistName("");
|
|---|
| 21 | setError("");
|
|---|
| 22 | setIsOpening(true);
|
|---|
| 23 | setTimeout(() => setIsOpening(false), 10);
|
|---|
| 24 | }
|
|---|
| 25 | }, [isOpen]);
|
|---|
| 26 |
|
|---|
| 27 | const handleClose = () => {
|
|---|
| 28 | setIsClosing(true);
|
|---|
| 29 | setTimeout(() => {
|
|---|
| 30 | onClose();
|
|---|
| 31 | setIsClosing(false);
|
|---|
| 32 | }, 200);
|
|---|
| 33 | };
|
|---|
| 34 |
|
|---|
| 35 | const handleSubmit = async (e: React.FormEvent) => {
|
|---|
| 36 | e.preventDefault();
|
|---|
| 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();
|
|---|
| 46 | handleClose();
|
|---|
| 47 | } catch (err: any) {
|
|---|
| 48 | setError(err?.response?.data?.error || "Failed to create playlist");
|
|---|
| 49 | } finally {
|
|---|
| 50 | setIsSubmitting(false);
|
|---|
| 51 | }
|
|---|
| 52 | };
|
|---|
| 53 |
|
|---|
| 54 | if (!isOpen && !isClosing) return null;
|
|---|
| 55 |
|
|---|
| 56 | return (
|
|---|
| 57 | <div
|
|---|
| 58 | className={`fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 px-4 transition-opacity duration-200 ${
|
|---|
| 59 | isClosing || isOpening ? "opacity-0" : "opacity-100"
|
|---|
| 60 | }`}
|
|---|
| 61 | onClick={handleClose}
|
|---|
| 62 | >
|
|---|
| 63 | <div
|
|---|
| 64 | className={`bg-[#181818] rounded-xl p-8 w-full max-w-md border border-white/10 transition-all duration-200 ${
|
|---|
| 65 | isClosing || isOpening
|
|---|
| 66 | ? "scale-95 opacity-0"
|
|---|
| 67 | : "scale-100 opacity-100"
|
|---|
| 68 | }`}
|
|---|
| 69 | onClick={(e) => e.stopPropagation()}
|
|---|
| 70 | >
|
|---|
| 71 | <div className="flex justify-between items-center mb-6">
|
|---|
| 72 | <h2 className="text-2xl font-bold text-white">Create Playlist</h2>
|
|---|
| 73 | <button
|
|---|
| 74 | onClick={handleClose}
|
|---|
| 75 | className="text-gray-400 hover:text-white transition-colors cursor-pointer"
|
|---|
| 76 | aria-label="Close modal"
|
|---|
| 77 | >
|
|---|
| 78 | <svg
|
|---|
| 79 | className="w-6 h-6"
|
|---|
| 80 | fill="none"
|
|---|
| 81 | stroke="currentColor"
|
|---|
| 82 | viewBox="0 0 24 24"
|
|---|
| 83 | >
|
|---|
| 84 | <path
|
|---|
| 85 | strokeLinecap="round"
|
|---|
| 86 | strokeLinejoin="round"
|
|---|
| 87 | strokeWidth={2}
|
|---|
| 88 | d="M6 18L18 6M6 6l12 12"
|
|---|
| 89 | />
|
|---|
| 90 | </svg>
|
|---|
| 91 | </button>
|
|---|
| 92 | </div>
|
|---|
| 93 |
|
|---|
| 94 | <form onSubmit={handleSubmit} className="space-y-6">
|
|---|
| 95 | <div>
|
|---|
| 96 | <label
|
|---|
| 97 | className="block text-sm font-medium text-gray-300 mb-2"
|
|---|
| 98 | htmlFor="playlistName"
|
|---|
| 99 | >
|
|---|
| 100 | Playlist Name
|
|---|
| 101 | </label>
|
|---|
| 102 | <input
|
|---|
| 103 | type="text"
|
|---|
| 104 | id="playlistName"
|
|---|
| 105 | autoFocus
|
|---|
| 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 | }`}
|
|---|
| 111 | placeholder="Enter playlist name"
|
|---|
| 112 | value={playlistName}
|
|---|
| 113 | onChange={(e) => {
|
|---|
| 114 | setPlaylistName(e.target.value);
|
|---|
| 115 | }}
|
|---|
| 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>
|
|---|
| 137 | </div>
|
|---|
| 138 |
|
|---|
| 139 | <div className="flex gap-3">
|
|---|
| 140 | <button
|
|---|
| 141 | type="button"
|
|---|
| 142 | onClick={handleClose}
|
|---|
| 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"
|
|---|
| 145 | >
|
|---|
| 146 | Cancel
|
|---|
| 147 | </button>
|
|---|
| 148 | <button
|
|---|
| 149 | type="submit"
|
|---|
| 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"
|
|---|
| 152 | >
|
|---|
| 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 | )}
|
|---|
| 161 | </button>
|
|---|
| 162 | </div>
|
|---|
| 163 | </form>
|
|---|
| 164 | </div>
|
|---|
| 165 | </div>
|
|---|
| 166 | );
|
|---|
| 167 | };
|
|---|
| 168 |
|
|---|
| 169 | export default CreatePlaylistModal;
|
|---|