Changeset c8baad1 for frontend


Ignore:
Timestamp:
02/15/26 01:58:19 (5 months ago)
Author:
Dimitar Arsov <dimitararsov04@…>
Branches:
main
Children:
d1ee039
Parents:
ce45c7a
Message:

create playlists from sidebar

Location:
frontend/src
Files:
6 edited
1 moved

Legend:

Unmodified
Added
Removed
  • frontend/src/components/Sidebar.tsx

    rce45c7a rc8baad1  
    4343    }
    4444  }, [user]);
    45 
    46   const handleCreatePlaylist = async (playlistName: string) => {
    47     try {
    48       await axiosInstance.post("/playlists", { name: playlistName });
    49       toast.success("Playlist created successfully!");
    50       await refreshPlaylists(false);
    51     } catch (error) {
    52       toast.error(getErrorMessage(error));
    53     }
    54   };
    5545
    5646  const isLoading = songsLoading || playlistsLoading;
     
    282272        isOpen={isModalOpen}
    283273        onClose={() => setIsModalOpen(false)}
    284         onSubmit={handleCreatePlaylist}
     274        onSuccess={() => refreshPlaylists(false)}
    285275      />
    286276    </>
  • frontend/src/components/SongItem.tsx

    rce45c7a rc8baad1  
    44import { usePlayer } from "../context/playerContext";
    55import { toEmbedUrl } from "../utils/utils";
    6 import PlaylistDropdown from "./PlaylistDropdown";
     6import PlaylistDropdown from "./playlist/PlaylistDropdown";
    77
    88export interface SongItemData {
  • frontend/src/components/playlist/CreatePlaylistModal.tsx

    rce45c7a rc8baad1  
    11import { useState, useEffect } from "react";
     2import { toast } from "react-toastify";
     3import axiosInstance from "../../api/axiosInstance";
    24import type { CreatePlaylistModalProps } from "../../utils/types";
    35
     
    57  isOpen,
    68  onClose,
    7   onSubmit,
     9  onSuccess,
    810}: CreatePlaylistModalProps) => {
    911  const [playlistName, setPlaylistName] = useState("");
    1012  const [isClosing, setIsClosing] = useState(false);
    1113  const [isOpening, setIsOpening] = useState(false);
     14  const [isSubmitting, setIsSubmitting] = useState(false);
     15  const [error, setError] = useState("");
    1216
    1317  useEffect(() => {
     
    1519      setIsClosing(false);
    1620      setPlaylistName("");
     21      setError("");
    1722      setIsOpening(true);
    1823      setTimeout(() => setIsOpening(false), 10);
     
    2833  };
    2934
    30   const handleSubmit = (e: React.FormEvent) => {
     35  const handleSubmit = async (e: React.FormEvent) => {
    3136    e.preventDefault();
    32     if (playlistName.trim()) {
    33       onSubmit(playlistName.trim());
     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();
    3446      handleClose();
     47    } catch (err: any) {
     48      setError(err?.response?.data?.error || "Failed to create playlist");
     49    } finally {
     50      setIsSubmitting(false);
    3551    }
    3652  };
     
    88104              id="playlistName"
    89105              autoFocus
    90               className="w-full bg-[#282828] border border-white/10 rounded-lg py-3 px-4 text-white placeholder-gray-500 focus:outline-none focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954] transition-all"
     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              }`}
    91111              placeholder="Enter playlist name"
    92112              value={playlistName}
    93               onChange={(e) => setPlaylistName(e.target.value)}
     113              onChange={(e) => {
     114                setPlaylistName(e.target.value);
     115              }}
    94116            />
     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>
    95137          </div>
    96138
     
    99141              type="button"
    100142              onClick={handleClose}
    101               className="flex-1 py-3 bg-[#282828] rounded-full text-white font-semibold hover:bg-[#3a3a3a] transition-colors cursor-pointer"
     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"
    102145            >
    103146              Cancel
     
    105148            <button
    106149              type="submit"
    107               disabled={!playlistName.trim()}
    108               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"
     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"
    109152            >
    110               Create
     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              )}
    111161            </button>
    112162          </div>
  • frontend/src/components/playlist/PlaylistDropdown.tsx

    rce45c7a rc8baad1  
    11import { useEffect, useRef, useState } from "react";
    22import { createPortal } from "react-dom";
    3 import axiosInstance from "../api/axiosInstance";
    4 import { useCreatedPlaylists } from "../context/playlistContext";
     3import axiosInstance from "../../api/axiosInstance";
     4import { useCreatedPlaylists } from "../../context/playlistContext";
    55
    66interface PlaylistDropdownProps {
  • frontend/src/pages/LandingPage.tsx

    rce45c7a rc8baad1  
    22import { useNavigate } from "react-router-dom";
    33import axiosInstance, { baseURL } from "../api/axiosInstance";
    4 import PlaylistDropdown from "../components/PlaylistDropdown";
     4import PlaylistDropdown from "../components/playlist/PlaylistDropdown";
    55import AlbumResult from "../components/search/AlbumResult";
    66import SongResult from "../components/search/SongResult";
  • frontend/src/pages/SongDetail.tsx

    rce45c7a rc8baad1  
    33import { toast } from "react-toastify";
    44import axiosInstance, { baseURL } from "../api/axiosInstance";
    5 import PlaylistDropdown from "../components/PlaylistDropdown";
     5import PlaylistDropdown from "../components/playlist/PlaylistDropdown";
    66import { useAuth } from "../context/authContext";
    77import { usePlayer } from "../context/playerContext";
  • frontend/src/utils/types.ts

    rce45c7a rc8baad1  
    7474  isOpen: boolean;
    7575  onClose: () => void;
    76   onSubmit: (playlistName: string) => void;
     76  onSuccess: () => void;
    7777}
    7878
Note: See TracChangeset for help on using the changeset viewer.