import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import axiosInstance from "../api/axiosInstance"; import { useCreatedPlaylists } from "../context/playlistContext"; interface PlaylistDropdownProps { songId: number; isOpen: boolean; onClose: () => void; position?: { top: number; left: number }; usePortal?: boolean; direction?: "above" | "below"; onCreateNewPlaylist?: () => void; } const PlaylistDropdown = ({ songId, isOpen, onClose, position, usePortal = false, direction = "above", onCreateNewPlaylist, }: PlaylistDropdownProps) => { const { createdPlaylists, refreshPlaylists } = useCreatedPlaylists(); const [containingPlaylistIds, setContainingPlaylistIds] = useState( [], ); const [loading, setLoading] = useState(false); const [processingPlaylistId, setProcessingPlaylistId] = useState< number | null >(null); 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 (processingPlaylistId !== null) return; setProcessingPlaylistId(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(() => { setProcessingPlaylistId(null); }, 500); } }; const handleCreateNew = () => { onCreateNewPlaylist?.(); onClose(); }; if (!isOpen) return null; const inlinePositionClass = direction === "below" ? "absolute left-0 top-full mt-2" : "absolute right-0 bottom-full mb-2"; const dropdownContent = (
e.stopPropagation()} >
Add to playlist
{loading ? (
) : createdPlaylists && createdPlaylists.length > 0 ? ( createdPlaylists.map((playlist) => { const isProcessing = processingPlaylistId === playlist.id; const isChecked = containingPlaylistIds.includes(playlist.id); return ( ); }) ) : (
No playlists created
)}
); return usePortal ? createPortal(dropdownContent, document.body) : dropdownContent; }; export default PlaylistDropdown;