import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import axiosInstance from "../../api/axiosInstance"; import { useCreatedPlaylists } from "../../context/playlistContext"; import CreatePlaylistModal from "./CreatePlaylistModal"; interface PlaylistDropdownProps { songId: number; isOpen: boolean; onClose: () => void; position?: { top: number; left: number }; usePortal?: boolean; direction?: "above" | "below"; } const PlaylistDropdown = ({ songId, isOpen, onClose, position, usePortal = false, direction = "above", }: PlaylistDropdownProps) => { const { createdPlaylists, refreshPlaylists } = useCreatedPlaylists(); const [containingPlaylistIds, setContainingPlaylistIds] = useState( [], ); const [loading, setLoading] = useState(false); const [processingIds, setProcessingIds] = useState>(new Set()); const [isModalOpen, setIsModalOpen] = useState(false); const dropdownRef = useRef(null); useEffect(() => { if (isOpen && songId) { const fetchPlaylistIds = async () => { setLoading(true); setContainingPlaylistIds([]); try { const response = await axiosInstance.get( `/playlists/song/${songId}`, ); setContainingPlaylistIds(response.data); } catch (error) { console.error("Failed to fetch song presence:", error); } finally { setLoading(false); } }; fetchPlaylistIds(); } else { setContainingPlaylistIds([]); } }, [isOpen, songId]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { onClose(); } }; if (isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => document.removeEventListener("mousedown", handleClickOutside); }, [isOpen, onClose]); const handleTogglePlaylist = async (playlistId: number, songId: number) => { if (processingIds.has(playlistId)) return; setProcessingIds((prev) => new Set(prev).add(playlistId)); try { const response = await axiosInstance.post<{ playlistId: number; isSongAddedToPlaylist: boolean; }>(`/playlists/${playlistId}/song/${songId}`); const { playlistId: returnedPlaylistId, isSongAddedToPlaylist } = response.data; if (isSongAddedToPlaylist) { setContainingPlaylistIds((prev) => prev.includes(returnedPlaylistId) ? prev : [...prev, returnedPlaylistId], ); } else { setContainingPlaylistIds((prev) => prev.filter((id) => id !== returnedPlaylistId), ); } } catch (error) { console.error("Failed to toggle playlist:", error); } finally { refreshPlaylists(true); setTimeout(() => { setProcessingIds((prev) => { const next = new Set(prev); next.delete(playlistId); return next; }); }, 500); } }; const handleCreateNew = () => { onClose(); setIsModalOpen(true); }; const handleModalSuccess = async () => { await refreshPlaylists(false); }; const inlinePositionClass = direction === "below" ? "absolute left-0 top-full mt-2" : "absolute right-0 bottom-full mb-2"; const dropdownContent = !isOpen ? null : (
e.stopPropagation()} >
Add to playlist
{loading ? (
) : createdPlaylists && createdPlaylists.length > 0 ? ( createdPlaylists.map((playlist) => { const isProcessing = processingIds.has(playlist.id); const isChecked = containingPlaylistIds.includes(playlist.id); return ( ); }) ) : (
No playlists created
)}
); const dropdown = dropdownContent ? usePortal ? createPortal(dropdownContent, document.body) : dropdownContent : null; return ( <> {dropdown} {createPortal( setIsModalOpen(false)} onSuccess={handleModalSuccess} songId={songId} />, document.body, )} ); }; export default PlaylistDropdown;