Ignore:
Timestamp:
02/15/26 23:28:14 (5 months ago)
Author:
Dimitar Arsov <dimitararsov04@…>
Branches:
main
Children:
615dcee, c807e22
Parents:
c8baad1
Message:

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

Location:
frontend/src/components/playlist
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/components/playlist/CreatePlaylistModal.tsx

    rc8baad1 rd1ee039  
    88  onClose,
    99  onSuccess,
     10  songId,
    1011}: CreatePlaylistModalProps) => {
    1112  const [playlistName, setPlaylistName] = useState("");
     
    3940    setIsSubmitting(true);
    4041    try {
    41       await axiosInstance.post(
    42         `/playlists?playlistName=${encodeURIComponent(playlistName.trim())}`,
    43       );
     42      await axiosInstance.post("/playlists", null, {
     43        params: {
     44          playlistName: playlistName.trim(),
     45          ...(songId != null && { songId }),
     46        },
     47      });
     48
    4449      toast.success("Playlist created successfully!");
    45       onSuccess();
     50      await onSuccess();
    4651      handleClose();
    4752    } catch (err: any) {
  • frontend/src/components/playlist/PlaylistDropdown.tsx

    rc8baad1 rd1ee039  
    33import axiosInstance from "../../api/axiosInstance";
    44import { useCreatedPlaylists } from "../../context/playlistContext";
     5import CreatePlaylistModal from "./CreatePlaylistModal";
    56
    67interface PlaylistDropdownProps {
     
    1314
    1415  direction?: "above" | "below";
    15 
    16   onCreateNewPlaylist?: () => void;
    1716}
    1817
     
    2423  usePortal = false,
    2524  direction = "above",
    26   onCreateNewPlaylist,
    2725}: PlaylistDropdownProps) => {
    2826  const { createdPlaylists, refreshPlaylists } = useCreatedPlaylists();
     
    3129  );
    3230  const [loading, setLoading] = useState(false);
    33   const [processingPlaylistId, setProcessingPlaylistId] = useState<
    34     number | null
    35   >(null);
     31  const [processingIds, setProcessingIds] = useState<Set<number>>(new Set());
     32  const [isModalOpen, setIsModalOpen] = useState(false);
    3633  const dropdownRef = useRef<HTMLDivElement>(null);
    3734
     
    7572
    7673  const handleTogglePlaylist = async (playlistId: number, songId: number) => {
    77     if (processingPlaylistId !== null) return;
    78 
    79     setProcessingPlaylistId(playlistId);
     74    if (processingIds.has(playlistId)) return;
     75
     76    setProcessingIds((prev) => new Set(prev).add(playlistId));
    8077
    8178    try {
     
    104101      refreshPlaylists(true);
    105102      setTimeout(() => {
    106         setProcessingPlaylistId(null);
     103        setProcessingIds((prev) => {
     104          const next = new Set(prev);
     105          next.delete(playlistId);
     106          return next;
     107        });
    107108      }, 500);
    108109    }
     
    110111
    111112  const handleCreateNew = () => {
    112     onCreateNewPlaylist?.();
    113113    onClose();
     114    setIsModalOpen(true);
    114115  };
    115116
    116   if (!isOpen) return null;
     117  const handleModalSuccess = async () => {
     118    await refreshPlaylists(false);
     119  };
    117120
    118121  const inlinePositionClass =
     
    121124      : "absolute right-0 bottom-full mb-2";
    122125
    123   const dropdownContent = (
     126  const dropdownContent = !isOpen ? null : (
    124127    <div
    125128      ref={dropdownRef}
    126       className={`${usePortal ? "fixed" : inlinePositionClass} w-56 bg-[#282828] rounded-lg shadow-2xl py-2 z-[9999] border border-white/10 max-h-60 overflow-y-auto custom-scrollbar`}
     129      className={`${usePortal ? "fixed" : inlinePositionClass} w-56 bg-[#282828] rounded-lg shadow-2xl py-2 z-9999 border border-white/10 max-h-60 overflow-y-auto custom-scrollbar`}
    127130      style={
    128131        usePortal && position
     
    147150      ) : createdPlaylists && createdPlaylists.length > 0 ? (
    148151        createdPlaylists.map((playlist) => {
    149           const isProcessing = processingPlaylistId === playlist.id;
     152          const isProcessing = processingIds.has(playlist.id);
    150153          const isChecked = containingPlaylistIds.includes(playlist.id);
    151154
     
    153156            <label
    154157              key={playlist.id}
    155               className={`flex items-center px-4 py-2 hover:bg-white/10 transition-colors group/item ${
     158              className={`flex items-center gap-1.5 px-4 py-2 hover:bg-white/10 transition-colors group/item ${
    156159                isProcessing ? "pointer-events-none" : "cursor-pointer"
    157160              }`}
    158161              onClick={(e) => e.stopPropagation()}
    159162            >
    160               <div className="relative flex items-center justify-center">
     163              <div className="relative flex items-center justify-center  shrink-0">
    161164                <input
    162165                  type="checkbox"
    163166                  className="peer sr-only"
    164167                  checked={isChecked}
    165                   disabled={processingPlaylistId !== null}
     168                  disabled={isProcessing}
    166169                  onChange={() => handleTogglePlaylist(playlist.id, songId)}
    167170                />
     
    172175                      : isChecked
    173176                        ? "bg-[#1db954] border-[#1db954]"
    174                         : "border-gray-500"
     177                        : "border-gray-500 text=wjot"
    175178                  }`}
    176179                >
     
    182185                </div>
    183186                <svg
    184                   className={`absolute w-3 h-3  transition-opacity ${
     187                  className={`absolute w-3 h-3 transition-opacity ${
    185188                    isChecked && !isProcessing ? "opacity-100" : "opacity-0"
    186189                  }`}
    187190                  fill="none"
    188                   stroke="currentColor"
     191                  stroke="white"
    189192                  strokeWidth="3"
    190193                  viewBox="0 0 24 24"
     
    198201              </div>
    199202              <span
    200                 className={`ml-3 text-sm truncate transition-all ${
     203                className={`text-sm truncate transition-all ${
    201204                  isProcessing
    202205                    ? "text-[#1db954] animate-pulse"
     
    206209                {playlist.name}
    207210              </span>
    208               {isProcessing && (
    209                 <span className="ml-auto text-xs text-[#1db954] animate-pulse">
    210                   •••
    211                 </span>
    212               )}
    213211            </label>
    214212          );
     
    245243  );
    246244
    247   return usePortal
    248     ? createPortal(dropdownContent, document.body)
    249     : dropdownContent;
     245  const dropdown = dropdownContent
     246    ? usePortal
     247      ? createPortal(dropdownContent, document.body)
     248      : dropdownContent
     249    : null;
     250
     251  return (
     252    <>
     253      {dropdown}
     254      {createPortal(
     255        <CreatePlaylistModal
     256          isOpen={isModalOpen}
     257          onClose={() => setIsModalOpen(false)}
     258          onSuccess={handleModalSuccess}
     259          songId={songId}
     260        />,
     261        document.body,
     262      )}
     263    </>
     264  );
    250265};
    251266
Note: See TracChangeset for help on using the changeset viewer.