Ignore:
Timestamp:
02/21/26 19:19:51 (5 months ago)
Author:
Filip Gavrilovski <filipgavrilovski28@…>
Branches:
main
Parents:
765e166
Message:

add cursor pointer to buttons

File:
1 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/pages/LandingPage.tsx

    r765e166 r947fcaa  
    66import SongResult from "../components/search/SongResult";
    77import UserResult from "../components/search/UserResult";
     8import { useAuth } from "../context/authContext";
    89import { usePlayer } from "../context/playerContext";
    910import type {
    10   Album,
    11   BaseNonAdminUser,
    12   SearchCategory,
    13   Song,
     11        Album,
     12        BaseNonAdminUser,
     13        SearchCategory,
     14        Song,
    1415} from "../utils/types";
    1516import { toEmbedUrl } from "../utils/utils";
    16 import { useAuth } from "../context/authContext";
    1717
    1818const CATEGORIES: { value: SearchCategory; label: string }[] = [
    19   { value: "songs", label: "Songs" },
    20   { value: "albums", label: "Albums" },
    21   { value: "artists", label: "Artists" },
    22   { value: "users", label: "Users" },
     19        { value: "songs", label: "Songs" },
     20        { value: "albums", label: "Albums" },
     21        { value: "artists", label: "Artists" },
     22        { value: "users", label: "Users" },
    2323];
    2424
    2525const LandingPage = () => {
    26   const navigate = useNavigate();
    27   const { play, currentSong } = usePlayer();
    28   const { user } = useAuth();
    29   const [songs, setSongs] = useState<Song[]>([]);
    30   const [loading, setLoading] = useState<boolean>(true);
    31 
    32   const [searchInput, setSearchInput] = useState("");
    33   const [activeQuery, setActiveQuery] = useState("");
    34   const [searchCategory, setSearchCategory] = useState<SearchCategory>("songs");
    35   const [searchResults, setSearchResults] = useState<unknown[]>([]);
    36   const [searchLoading, setSearchLoading] = useState(false);
    37   const [hasSearched, setHasSearched] = useState(false);
    38 
    39   const [openPlaylistDropdown, setOpenPlaylistDropdown] = useState<
    40     number | null
    41   >(null);
    42 
    43   useEffect(() => {
    44     const fetchSongs = async () => {
    45       try {
    46         const response = await axiosInstance.get("/songs/top");
    47         setSongs(response.data);
    48       } catch (error) {
    49         console.error("Error fetching songs:", error);
    50       } finally {
    51         setLoading(false);
    52       }
    53     };
    54     fetchSongs();
    55   }, []);
    56 
    57   const toggleLike = async (songId: number) => {
    58     try {
    59       await axiosInstance.post(`/musical-entity/${songId}/like`);
    60       setSongs((prevSongs) =>
    61         prevSongs.map((song) =>
    62           song.id === songId
    63             ? { ...song, isLikedByCurrentUser: !song.isLikedByCurrentUser }
    64             : song,
    65         ),
    66       );
    67     } catch (error) {
    68       console.error("Error toggling like:", error);
    69     }
    70   };
    71 
    72   const togglePlaylistDropdown = (songId: number) => {
    73     if (openPlaylistDropdown === songId) {
    74       setOpenPlaylistDropdown(null);
    75     } else {
    76       setOpenPlaylistDropdown(songId);
    77     }
    78   };
    79 
    80   const performSearch = async (query: string, category: SearchCategory) => {
    81     if (!query.trim()) return;
    82 
    83     setSearchLoading(true);
    84     setHasSearched(true);
    85     setActiveQuery(query);
    86     setSearchCategory(category);
    87 
    88     try {
    89       let endpoint = "";
    90       switch (category) {
    91         case "songs":
    92           endpoint = `/songs/search?q=${encodeURIComponent(query)}`;
    93           break;
    94         case "albums":
    95           endpoint = `/albums/search?q=${encodeURIComponent(query)}`;
    96           break;
    97         case "artists":
    98           endpoint = `/users/search?type=ARTIST&q=${encodeURIComponent(query)}`;
    99           break;
    100         case "users":
    101           endpoint = `/users/search?type=LISTENER&q=${encodeURIComponent(query)}`;
    102           break;
    103       }
    104 
    105       const response = await axiosInstance.get(endpoint);
    106       setSearchResults(response.data);
    107     } catch (error) {
    108       console.error("Search error:", error);
    109       setSearchResults([]);
    110     } finally {
    111       setSearchLoading(false);
    112     }
    113   };
    114 
    115   const handleSearch = () => {
    116     performSearch(searchInput, searchCategory);
    117   };
    118 
    119   const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    120     if (e.key === "Enter") {
    121       handleSearch();
    122     }
    123   };
    124 
    125   const handleCategorySwitch = (category: SearchCategory) => {
    126     performSearch(activeQuery, category);
    127   };
    128 
    129   const clearSearch = () => {
    130     setSearchInput("");
    131     setActiveQuery("");
    132     setHasSearched(false);
    133     setSearchResults([]);
    134   };
    135 
    136   const renderResults = () => {
    137     if (searchLoading) {
    138       return (
    139         <div className="flex justify-center py-12">
    140           <div className="w-10 h-10 border-4 border-white/10 border-t-[#1db954] rounded-full animate-spin"></div>
    141         </div>
    142       );
    143     }
    144 
    145     if (searchResults.length === 0) {
    146       return (
    147         <div className="text-center py-12 text-gray-400">
    148           <p className="text-lg">
    149             No {searchCategory} found for "{activeQuery}"
    150           </p>
    151         </div>
    152       );
    153     }
    154 
    155     return (
    156       <div className="divide-y divide-white/5">
    157         {searchResults.map((result, index) => {
    158           switch (searchCategory) {
    159             case "songs":
    160               return (
    161                 <SongResult key={(result as Song).id} song={result as Song} />
    162               );
    163             case "albums":
    164               return (
    165                 <AlbumResult
    166                   key={(result as Album).id}
    167                   album={result as Album}
    168                 />
    169               );
    170             case "artists":
    171               return (
    172                 <UserResult
    173                   key={(result as BaseNonAdminUser).username ?? index}
    174                   user={result as BaseNonAdminUser}
    175                   label="Artist"
    176                 />
    177               );
    178             case "users":
    179               return (
    180                 <UserResult
    181                   key={(result as BaseNonAdminUser).username ?? index}
    182                   user={result as BaseNonAdminUser}
    183                   label="User"
    184                 />
    185               );
    186           }
    187         })}
    188       </div>
    189     );
    190   };
    191 
    192   return (
    193     <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] text-white flex">
    194       <div className="flex-1">
    195         <div className="p-8">
    196           <div className="max-w-7xl mx-auto">
    197             {/* Search Bar */}
    198             <div className="mb-8 flex flex-col md:flex-row gap-3">
    199               <div className="flex flex-1 gap-0">
    200                 {/* Category Dropdown */}
    201                 <select
    202                   value={searchCategory}
    203                   onChange={(e) =>
    204                     setSearchCategory(e.target.value as SearchCategory)
    205                   }
    206                   className="bg-[#282828] border border-white/10 border-r-0 rounded-l-full py-2 px-4 text-white text-sm focus:outline-none focus:border-[#1db954] appearance-none cursor-pointer"
    207                 >
    208                   {CATEGORIES.map((cat) => (
    209                     <option key={cat.value} value={cat.value}>
    210                       {cat.label}
    211                     </option>
    212                   ))}
    213                 </select>
    214 
    215                 {/* Search Input */}
    216                 <input
    217                   type="text"
    218                   placeholder={`Search ${searchCategory}...`}
    219                   value={searchInput}
    220                   onChange={(e) => setSearchInput(e.target.value)}
    221                   onKeyDown={handleKeyDown}
    222                   className="flex-1 bg-[#282828] border border-white/10 border-r-0 py-2 px-4 text-white focus:outline-none focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954] transition-all"
    223                 />
    224 
    225                 {/* Search Button */}
    226                 <button
    227                   onClick={handleSearch}
    228                   className="bg-[#1db954] hover:bg-[#1ed760] text-black font-medium px-6 rounded-r-full transition-colors flex items-center gap-2"
    229                 >
    230                   <svg
    231                     className="w-5 h-5"
    232                     fill="none"
    233                     stroke="currentColor"
    234                     viewBox="0 0 24 24"
    235                   >
    236                     <path
    237                       strokeLinecap="round"
    238                       strokeLinejoin="round"
    239                       strokeWidth={2}
    240                       d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
    241                     />
    242                   </svg>
    243                   <span className="hidden sm:inline">Search</span>
    244                 </button>
    245               </div>
    246             </div>
    247 
    248             {/* Search Results Section */}
    249             {hasSearched ? (
    250               <div>
    251                 <div className="flex items-center justify-between mb-4">
    252                   <h2 className="text-2xl font-bold text-white">
    253                     Results for "{activeQuery}"
    254                   </h2>
    255                   <button
    256                     onClick={clearSearch}
    257                     className="text-sm text-gray-400 hover:text-white transition-colors"
    258                   >
    259                     Clear search
    260                   </button>
    261                 </div>
    262 
    263                 {/* Quick category switch buttons */}
    264                 <div className="flex gap-2 mb-6">
    265                   {CATEGORIES.map((cat) => (
    266                     <button
    267                       key={cat.value}
    268                       onClick={() => handleCategorySwitch(cat.value)}
    269                       className={`px-4 py-1.5 rounded-full text-sm font-medium transition-colors cursor-pointer ${
    270                         searchCategory === cat.value
    271                           ? "bg-[#1db954] text-black"
    272                           : "bg-white/10 text-white hover:bg-white/20"
    273                       }`}
    274                     >
    275                       {cat.label}
    276                     </button>
    277                   ))}
    278                 </div>
    279 
    280                 {/* Results list */}
    281                 <div className="bg-[#1a1a2e]/50 rounded-xl overflow-hidden">
    282                   {renderResults()}
    283                 </div>
    284               </div>
    285             ) : (
    286               /* Default song grid */
    287               <>
    288                 <div className="mb-8 border-b border-white/10 pb-6">
    289                   <h1 className="text-5xl font-bold mb-3 pb-1 bg-linear-to-r from-[#1db954] to-[#1ed760] bg-clip-text text-transparent">
    290                     Top Songs
    291                   </h1>
    292                   <p className="text-xl text-gray-400">
    293                     Listen to the newest tracks on FinkWave
    294                   </p>
    295                 </div>
    296 
    297                 {loading ? (
    298                   <div className="flex flex-col items-center justify-center min-h-[40vh] gap-6">
    299                     <div className="w-12 h-12 border-4 border-white/10 border-t-[#1db954] rounded-full animate-spin"></div>
    300                     <p className="text-xl text-gray-400">Loading songs...</p>
    301                   </div>
    302                 ) : (
    303                   <div className="grid grid-cols-[repeat(auto-fill,minmax(220px,1fr))] gap-8 py-4">
    304                     {songs.map((song) => (
    305                       <div
    306                         key={song.id}
    307                         onClick={() => navigate(`/songs/${song.id}`)}
    308                         onMouseEnter={() => {
    309                           if (
    310                             openPlaylistDropdown !== null &&
    311                             openPlaylistDropdown !== song.id
    312                           ) {
    313                             setOpenPlaylistDropdown(null);
    314                           }
    315                         }}
    316                         className="bg-[#282828] rounded-xl cursor-pointer shadow-lg transition-all duration-300 ease-in-out hover:-translate-y-2 hover:shadow-2xl group relative"
    317                       >
    318                         <div className="relative w-full pt-[100%] overflow-hidden bg-[#181818] rounded-t-xl">
    319                           <img
    320                             src={
    321                               song.cover
    322                                 ? `${baseURL}/${song.cover}`
    323                                 : "/favicon.png"
    324                             }
    325                             alt={song.title}
    326                             className="absolute top-0 left-0 w-full h-full object-cover"
    327                             onError={(e) => {
    328                               (e.target as HTMLImageElement).src =
    329                                 "/favicon.png";
    330                             }}
    331                           />
    332                           <div className="absolute top-0 left-0 w-full h-full bg-black/60 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
    333                             {song.link ? (
    334                               <button
    335                                 onClick={(e) => {
    336                                   e.stopPropagation();
    337                                   play({
    338                                     id: song.id,
    339                                     title: song.title,
    340                                     artist: song.releasedBy,
    341                                     cover: song.cover,
    342                                     embedUrl: toEmbedUrl(song.link!),
    343                                   });
    344                                 }}
    345                                 className={`w-14 h-14 rounded-full flex items-center justify-center text-xl shadow-[0_4px_12px_rgba(29,185,84,0.5)] transition-all cursor-pointer ${
    346                                   currentSong?.id === song.id
    347                                     ? "bg-white text-[#1db954]"
    348                                     : "bg-[#1db954] text-black hover:scale-105"
    349                                 }`}
    350                                 aria-label={
    351                                   currentSong?.id === song.id
    352                                     ? "Now playing"
    353                                     : "Play song"
    354                                 }
    355                               >
    356                                 {currentSong?.id === song.id ? (
    357                                   <svg
    358                                     className="w-6 h-6"
    359                                     fill="currentColor"
    360                                     viewBox="0 0 24 24"
    361                                   >
    362                                     <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
    363                                   </svg>
    364                                 ) : (
    365                                   <svg
    366                                     className="w-6 h-6"
    367                                     fill="currentColor"
    368                                     viewBox="0 0 24 24"
    369                                   >
    370                                     <path d="M8 5v14l11-7z" />
    371                                   </svg>
    372                                 )}
    373                               </button>
    374                             ) : (
    375                               <div className="w-14 h-14 rounded-full bg-[#1db954] flex items-center justify-center text-xl text-black shadow-[0_4px_12px_rgba(29,185,84,0.5)]">
    376                                
    377                               </div>
    378                             )}
    379                           </div>
    380                         </div>
    381                         <div className="p-4">
    382                           <h3 className="text-lg font-semibold mb-2 text-white overflow-hidden text-ellipsis whitespace-nowrap">
    383                             {song.title}
    384                           </h3>
    385                           {song.album && (
    386                             <p
    387                               onClick={(e) => {
    388                                 e.stopPropagation();
    389                                 if (song.albumId) {
    390                                   navigate(`/collection/album/${song.albumId}`);
    391                                 }
    392                               }}
    393                               className={`text-sm text-gray-400 mb-3 overflow-hidden text-ellipsis whitespace-nowrap ${
    394                                 song.albumId
    395                                   ? "hover:underline cursor-pointer hover:text-white"
    396                                   : ""
    397                               }`}
    398                             >
    399                               {song.album}
    400                             </p>
    401                           )}
    402                           <p
    403                             onClick={(e) => {
    404                               e.stopPropagation();
    405                               if (song.artistUsername) {
    406                                 navigate(`/users/${song.artistUsername}`);
    407                               }
    408                             }}
    409                             className={`text-sm text-gray-400 mb-3 overflow-hidden text-ellipsis whitespace-nowrap ${
    410                               song.artistUsername
    411                                 ? "hover:underline cursor-pointer hover:text-white"
    412                                 : ""
    413                             }`}
    414                           >
    415                             {song.releasedBy}
    416                           </p>
    417                           {user && (
    418                             <div className="flex items-center justify-between mt-4 pt-3 border-t border-white/10">
    419                               {/* Like button */}
    420                               <button
    421                                 onClick={(e) => {
    422                                   e.stopPropagation();
    423                                   toggleLike(song.id);
    424                                 }}
    425                                 className="text-gray-400 hover:text-[#1db954] transition-colors cursor-pointer"
    426                                 aria-label={
    427                                   song.isLikedByCurrentUser
    428                                     ? "Unlike song"
    429                                     : "Like song"
    430                                 }
    431                               >
    432                                 {song.isLikedByCurrentUser ? (
    433                                   <svg
    434                                     className="w-6 h-6 fill-[#1db954]"
    435                                     viewBox="0 0 24 24"
    436                                   >
    437                                     <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
    438                                   </svg>
    439                                 ) : (
    440                                   <svg
    441                                     className="w-6 h-6"
    442                                     fill="none"
    443                                     stroke="currentColor"
    444                                     viewBox="0 0 24 24"
    445                                   >
    446                                     <path
    447                                       strokeLinecap="round"
    448                                       strokeLinejoin="round"
    449                                       strokeWidth={2}
    450                                       d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
    451                                     />
    452                                   </svg>
    453                                 )}
    454                               </button>
    455 
    456                               {/* Add to playlist button */}
    457                               <div className="relative">
    458                                 <button
    459                                   onClick={(e) => {
    460                                     e.stopPropagation();
    461                                     togglePlaylistDropdown(song.id);
    462                                   }}
    463                                   className="text-gray-400 hover:text-[#1db954] transition-colors cursor-pointer"
    464                                   aria-label="Add to playlist"
    465                                 >
    466                                   <svg
    467                                     className="w-6 h-6"
    468                                     fill="none"
    469                                     stroke="currentColor"
    470                                     viewBox="0 0 24 24"
    471                                   >
    472                                     <path
    473                                       strokeLinecap="round"
    474                                       strokeLinejoin="round"
    475                                       strokeWidth={2}
    476                                       d="M12 4v16m8-8H4"
    477                                     />
    478                                   </svg>
    479                                 </button>
    480 
    481                                 <PlaylistDropdown
    482                                   songId={song.id}
    483                                   isOpen={openPlaylistDropdown === song.id}
    484                                   onClose={() => setOpenPlaylistDropdown(null)}
    485                                   direction="above"
    486                                 />
    487                               </div>
    488                             </div>
    489                           )}
    490                         </div>
    491                       </div>
    492                     ))}
    493                   </div>
    494                 )}
    495               </>
    496             )}
    497           </div>
    498         </div>
    499       </div>
    500     </div>
    501   );
     26        const navigate = useNavigate();
     27        const { play, currentSong } = usePlayer();
     28        const { user } = useAuth();
     29        const [songs, setSongs] = useState<Song[]>([]);
     30        const [loading, setLoading] = useState<boolean>(true);
     31
     32        const [searchInput, setSearchInput] = useState("");
     33        const [activeQuery, setActiveQuery] = useState("");
     34        const [searchCategory, setSearchCategory] = useState<SearchCategory>("songs");
     35        const [searchResults, setSearchResults] = useState<unknown[]>([]);
     36        const [searchLoading, setSearchLoading] = useState(false);
     37        const [hasSearched, setHasSearched] = useState(false);
     38
     39        const [openPlaylistDropdown, setOpenPlaylistDropdown] = useState<
     40                number | null
     41        >(null);
     42
     43        useEffect(() => {
     44                const fetchSongs = async () => {
     45                        try {
     46                                const response = await axiosInstance.get("/songs/top");
     47                                setSongs(response.data);
     48                        } catch (error) {
     49                                console.error("Error fetching songs:", error);
     50                        } finally {
     51                                setLoading(false);
     52                        }
     53                };
     54                fetchSongs();
     55        }, []);
     56
     57        const toggleLike = async (songId: number) => {
     58                try {
     59                        await axiosInstance.post(`/musical-entity/${songId}/like`);
     60                        setSongs((prevSongs) =>
     61                                prevSongs.map((song) =>
     62                                        song.id === songId
     63                                                ? { ...song, isLikedByCurrentUser: !song.isLikedByCurrentUser }
     64                                                : song,
     65                                ),
     66                        );
     67                } catch (error) {
     68                        console.error("Error toggling like:", error);
     69                }
     70        };
     71
     72        const togglePlaylistDropdown = (songId: number) => {
     73                if (openPlaylistDropdown === songId) {
     74                        setOpenPlaylistDropdown(null);
     75                } else {
     76                        setOpenPlaylistDropdown(songId);
     77                }
     78        };
     79
     80        const performSearch = async (query: string, category: SearchCategory) => {
     81                if (!query.trim()) return;
     82
     83                setSearchLoading(true);
     84                setHasSearched(true);
     85                setActiveQuery(query);
     86                setSearchCategory(category);
     87
     88                try {
     89                        let endpoint = "";
     90                        switch (category) {
     91                                case "songs":
     92                                        endpoint = `/songs/search?q=${encodeURIComponent(query)}`;
     93                                        break;
     94                                case "albums":
     95                                        endpoint = `/albums/search?q=${encodeURIComponent(query)}`;
     96                                        break;
     97                                case "artists":
     98                                        endpoint = `/users/search?type=ARTIST&q=${encodeURIComponent(query)}`;
     99                                        break;
     100                                case "users":
     101                                        endpoint = `/users/search?type=LISTENER&q=${encodeURIComponent(query)}`;
     102                                        break;
     103                        }
     104
     105                        const response = await axiosInstance.get(endpoint);
     106                        setSearchResults(response.data);
     107                } catch (error) {
     108                        console.error("Search error:", error);
     109                        setSearchResults([]);
     110                } finally {
     111                        setSearchLoading(false);
     112                }
     113        };
     114
     115        const handleSearch = () => {
     116                performSearch(searchInput, searchCategory);
     117        };
     118
     119        const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
     120                if (e.key === "Enter") {
     121                        handleSearch();
     122                }
     123        };
     124
     125        const handleCategorySwitch = (category: SearchCategory) => {
     126                performSearch(activeQuery, category);
     127        };
     128
     129        const clearSearch = () => {
     130                setSearchInput("");
     131                setActiveQuery("");
     132                setHasSearched(false);
     133                setSearchResults([]);
     134        };
     135
     136        const renderResults = () => {
     137                if (searchLoading) {
     138                        return (
     139                                <div className="flex justify-center py-12">
     140                                        <div className="w-10 h-10 border-4 border-white/10 border-t-[#1db954] rounded-full animate-spin"></div>
     141                                </div>
     142                        );
     143                }
     144
     145                if (searchResults.length === 0) {
     146                        return (
     147                                <div className="text-center py-12 text-gray-400">
     148                                        <p className="text-lg">
     149                                                No {searchCategory} found for "{activeQuery}"
     150                                        </p>
     151                                </div>
     152                        );
     153                }
     154
     155                return (
     156                        <div className="divide-y divide-white/5">
     157                                {searchResults.map((result, index) => {
     158                                        switch (searchCategory) {
     159                                                case "songs":
     160                                                        return (
     161                                                                <SongResult key={(result as Song).id} song={result as Song} />
     162                                                        );
     163                                                case "albums":
     164                                                        return (
     165                                                                <AlbumResult
     166                                                                        key={(result as Album).id}
     167                                                                        album={result as Album}
     168                                                                />
     169                                                        );
     170                                                case "artists":
     171                                                        return (
     172                                                                <UserResult
     173                                                                        key={(result as BaseNonAdminUser).username ?? index}
     174                                                                        user={result as BaseNonAdminUser}
     175                                                                        label="Artist"
     176                                                                />
     177                                                        );
     178                                                case "users":
     179                                                        return (
     180                                                                <UserResult
     181                                                                        key={(result as BaseNonAdminUser).username ?? index}
     182                                                                        user={result as BaseNonAdminUser}
     183                                                                        label="User"
     184                                                                />
     185                                                        );
     186                                        }
     187                                })}
     188                        </div>
     189                );
     190        };
     191
     192        return (
     193                <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] text-white flex">
     194                        <div className="flex-1">
     195                                <div className="p-8">
     196                                        <div className="max-w-7xl mx-auto">
     197                                                {/* Search Bar */}
     198                                                <div className="mb-8 flex flex-col md:flex-row gap-3">
     199                                                        <div className="flex flex-1 gap-0">
     200                                                                {/* Category Dropdown */}
     201                                                                <select
     202                                                                        value={searchCategory}
     203                                                                        onChange={(e) =>
     204                                                                                setSearchCategory(e.target.value as SearchCategory)
     205                                                                        }
     206                                                                        className="bg-[#282828] border border-white/10 border-r-0 rounded-l-full py-2 px-4 text-white text-sm focus:outline-none focus:border-[#1db954] appearance-none cursor-pointer"
     207                                                                >
     208                                                                        {CATEGORIES.map((cat) => (
     209                                                                                <option key={cat.value} value={cat.value}>
     210                                                                                        {cat.label}
     211                                                                                </option>
     212                                                                        ))}
     213                                                                </select>
     214
     215                                                                {/* Search Input */}
     216                                                                <input
     217                                                                        type="text"
     218                                                                        placeholder={`Search ${searchCategory}...`}
     219                                                                        value={searchInput}
     220                                                                        onChange={(e) => setSearchInput(e.target.value)}
     221                                                                        onKeyDown={handleKeyDown}
     222                                                                        className="flex-1 bg-[#282828] border border-white/10 border-r-0 py-2 px-4 text-white focus:outline-none focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954] transition-all"
     223                                                                />
     224
     225                                                                {/* Search Button */}
     226                                                                <button
     227                                                                        onClick={handleSearch}
     228                                                                        className="bg-[#1db954] hover:bg-[#1ed760] text-black font-medium px-6 rounded-r-full transition-colors flex items-center gap-2 cursor-pointer"
     229                                                                >
     230                                                                        <svg
     231                                                                                className="w-5 h-5"
     232                                                                                fill="none"
     233                                                                                stroke="currentColor"
     234                                                                                viewBox="0 0 24 24"
     235                                                                        >
     236                                                                                <path
     237                                                                                        strokeLinecap="round"
     238                                                                                        strokeLinejoin="round"
     239                                                                                        strokeWidth={2}
     240                                                                                        d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
     241                                                                                />
     242                                                                        </svg>
     243                                                                        <span className="hidden sm:inline">Search</span>
     244                                                                </button>
     245                                                        </div>
     246                                                </div>
     247
     248                                                {/* Search Results Section */}
     249                                                {hasSearched ? (
     250                                                        <div>
     251                                                                <div className="flex items-center justify-between mb-4">
     252                                                                        <h2 className="text-2xl font-bold text-white">
     253                                                                                Results for "{activeQuery}"
     254                                                                        </h2>
     255                                                                        <button
     256                                                                                onClick={clearSearch}
     257                                                                                className="text-sm text-gray-400 hover:text-white transition-colors"
     258                                                                        >
     259                                                                                Clear search
     260                                                                        </button>
     261                                                                </div>
     262
     263                                                                {/* Quick category switch buttons */}
     264                                                                <div className="flex gap-2 mb-6">
     265                                                                        {CATEGORIES.map((cat) => (
     266                                                                                <button
     267                                                                                        key={cat.value}
     268                                                                                        onClick={() => handleCategorySwitch(cat.value)}
     269                                                                                        className={`px-4 py-1.5 rounded-full text-sm font-medium transition-colors cursor-pointer ${
     270                                                                                                searchCategory === cat.value
     271                                                                                                        ? "bg-[#1db954] text-black"
     272                                                                                                        : "bg-white/10 text-white hover:bg-white/20"
     273                                                                                        }`}
     274                                                                                >
     275                                                                                        {cat.label}
     276                                                                                </button>
     277                                                                        ))}
     278                                                                </div>
     279
     280                                                                {/* Results list */}
     281                                                                <div className="bg-[#1a1a2e]/50 rounded-xl overflow-hidden">
     282                                                                        {renderResults()}
     283                                                                </div>
     284                                                        </div>
     285                                                ) : (
     286                                                        /* Default song grid */
     287                                                        <>
     288                                                                <div className="mb-8 border-b border-white/10 pb-6">
     289                                                                        <h1 className="text-5xl font-bold mb-3 pb-1 bg-linear-to-r from-[#1db954] to-[#1ed760] bg-clip-text text-transparent">
     290                                                                                Top Songs
     291                                                                        </h1>
     292                                                                        <p className="text-xl text-gray-400">
     293                                                                                Listen to the newest tracks on FinkWave
     294                                                                        </p>
     295                                                                </div>
     296
     297                                                                {loading ? (
     298                                                                        <div className="flex flex-col items-center justify-center min-h-[40vh] gap-6">
     299                                                                                <div className="w-12 h-12 border-4 border-white/10 border-t-[#1db954] rounded-full animate-spin"></div>
     300                                                                                <p className="text-xl text-gray-400">Loading songs...</p>
     301                                                                        </div>
     302                                                                ) : (
     303                                                                        <div className="grid grid-cols-[repeat(auto-fill,minmax(220px,1fr))] gap-8 py-4">
     304                                                                                {songs.map((song) => (
     305                                                                                        <div
     306                                                                                                key={song.id}
     307                                                                                                onClick={() => navigate(`/songs/${song.id}`)}
     308                                                                                                onMouseEnter={() => {
     309                                                                                                        if (
     310                                                                                                                openPlaylistDropdown !== null &&
     311                                                                                                                openPlaylistDropdown !== song.id
     312                                                                                                        ) {
     313                                                                                                                setOpenPlaylistDropdown(null);
     314                                                                                                        }
     315                                                                                                }}
     316                                                                                                className="bg-[#282828] rounded-xl cursor-pointer shadow-lg transition-all duration-300 ease-in-out hover:-translate-y-2 hover:shadow-2xl group relative"
     317                                                                                        >
     318                                                                                                <div className="relative w-full pt-[100%] overflow-hidden bg-[#181818] rounded-t-xl">
     319                                                                                                        <img
     320                                                                                                                src={
     321                                                                                                                        song.cover
     322                                                                                                                                ? `${baseURL}/${song.cover}`
     323                                                                                                                                : "/favicon.png"
     324                                                                                                                }
     325                                                                                                                alt={song.title}
     326                                                                                                                className="absolute top-0 left-0 w-full h-full object-cover"
     327                                                                                                                onError={(e) => {
     328                                                                                                                        (e.target as HTMLImageElement).src =
     329                                                                                                                                "/favicon.png";
     330                                                                                                                }}
     331                                                                                                        />
     332                                                                                                        <div className="absolute top-0 left-0 w-full h-full bg-black/60 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
     333                                                                                                                {song.link ? (
     334                                                                                                                        <button
     335                                                                                                                                onClick={(e) => {
     336                                                                                                                                        e.stopPropagation();
     337                                                                                                                                        play({
     338                                                                                                                                                id: song.id,
     339                                                                                                                                                title: song.title,
     340                                                                                                                                                artist: song.releasedBy,
     341                                                                                                                                                cover: song.cover,
     342                                                                                                                                                embedUrl: toEmbedUrl(song.link!),
     343                                                                                                                                        });
     344                                                                                                                                }}
     345                                                                                                                                className={`w-14 h-14 rounded-full flex items-center justify-center text-xl shadow-[0_4px_12px_rgba(29,185,84,0.5)] transition-all cursor-pointer ${
     346                                                                                                                                        currentSong?.id === song.id
     347                                                                                                                                                ? "bg-white text-[#1db954]"
     348                                                                                                                                                : "bg-[#1db954] text-black hover:scale-105"
     349                                                                                                                                }`}
     350                                                                                                                                aria-label={
     351                                                                                                                                        currentSong?.id === song.id
     352                                                                                                                                                ? "Now playing"
     353                                                                                                                                                : "Play song"
     354                                                                                                                                }
     355                                                                                                                        >
     356                                                                                                                                {currentSong?.id === song.id ? (
     357                                                                                                                                        <svg
     358                                                                                                                                                className="w-6 h-6"
     359                                                                                                                                                fill="currentColor"
     360                                                                                                                                                viewBox="0 0 24 24"
     361                                                                                                                                        >
     362                                                                                                                                                <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
     363                                                                                                                                        </svg>
     364                                                                                                                                ) : (
     365                                                                                                                                        <svg
     366                                                                                                                                                className="w-6 h-6"
     367                                                                                                                                                fill="currentColor"
     368                                                                                                                                                viewBox="0 0 24 24"
     369                                                                                                                                        >
     370                                                                                                                                                <path d="M8 5v14l11-7z" />
     371                                                                                                                                        </svg>
     372                                                                                                                                )}
     373                                                                                                                        </button>
     374                                                                                                                ) : (
     375                                                                                                                        <div className="w-14 h-14 rounded-full bg-[#1db954] flex items-center justify-center text-xl text-black shadow-[0_4px_12px_rgba(29,185,84,0.5)]">
     376                                                                                                                               
     377                                                                                                                        </div>
     378                                                                                                                )}
     379                                                                                                        </div>
     380                                                                                                </div>
     381                                                                                                <div className="p-4">
     382                                                                                                        <h3 className="text-lg font-semibold mb-2 text-white overflow-hidden text-ellipsis whitespace-nowrap">
     383                                                                                                                {song.title}
     384                                                                                                        </h3>
     385                                                                                                        {song.album && (
     386                                                                                                                <p
     387                                                                                                                        onClick={(e) => {
     388                                                                                                                                e.stopPropagation();
     389                                                                                                                                if (song.albumId) {
     390                                                                                                                                        navigate(`/collection/album/${song.albumId}`);
     391                                                                                                                                }
     392                                                                                                                        }}
     393                                                                                                                        className={`text-sm text-gray-400 mb-3 overflow-hidden text-ellipsis whitespace-nowrap ${
     394                                                                                                                                song.albumId
     395                                                                                                                                        ? "hover:underline cursor-pointer hover:text-white"
     396                                                                                                                                        : ""
     397                                                                                                                        }`}
     398                                                                                                                >
     399                                                                                                                        {song.album}
     400                                                                                                                </p>
     401                                                                                                        )}
     402                                                                                                        <p
     403                                                                                                                onClick={(e) => {
     404                                                                                                                        e.stopPropagation();
     405                                                                                                                        if (song.artistUsername) {
     406                                                                                                                                navigate(`/users/${song.artistUsername}`);
     407                                                                                                                        }
     408                                                                                                                }}
     409                                                                                                                className={`text-sm text-gray-400 mb-3 overflow-hidden text-ellipsis whitespace-nowrap ${
     410                                                                                                                        song.artistUsername
     411                                                                                                                                ? "hover:underline cursor-pointer hover:text-white"
     412                                                                                                                                : ""
     413                                                                                                                }`}
     414                                                                                                        >
     415                                                                                                                {song.releasedBy}
     416                                                                                                        </p>
     417                                                                                                        {user && (
     418                                                                                                                <div className="flex items-center justify-between mt-4 pt-3 border-t border-white/10">
     419                                                                                                                        {/* Like button */}
     420                                                                                                                        <button
     421                                                                                                                                onClick={(e) => {
     422                                                                                                                                        e.stopPropagation();
     423                                                                                                                                        toggleLike(song.id);
     424                                                                                                                                }}
     425                                                                                                                                className="text-gray-400 hover:text-[#1db954] transition-colors cursor-pointer"
     426                                                                                                                                aria-label={
     427                                                                                                                                        song.isLikedByCurrentUser
     428                                                                                                                                                ? "Unlike song"
     429                                                                                                                                                : "Like song"
     430                                                                                                                                }
     431                                                                                                                        >
     432                                                                                                                                {song.isLikedByCurrentUser ? (
     433                                                                                                                                        <svg
     434                                                                                                                                                className="w-6 h-6 fill-[#1db954]"
     435                                                                                                                                                viewBox="0 0 24 24"
     436                                                                                                                                        >
     437                                                                                                                                                <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
     438                                                                                                                                        </svg>
     439                                                                                                                                ) : (
     440                                                                                                                                        <svg
     441                                                                                                                                                className="w-6 h-6"
     442                                                                                                                                                fill="none"
     443                                                                                                                                                stroke="currentColor"
     444                                                                                                                                                viewBox="0 0 24 24"
     445                                                                                                                                        >
     446                                                                                                                                                <path
     447                                                                                                                                                        strokeLinecap="round"
     448                                                                                                                                                        strokeLinejoin="round"
     449                                                                                                                                                        strokeWidth={2}
     450                                                                                                                                                        d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
     451                                                                                                                                                />
     452                                                                                                                                        </svg>
     453                                                                                                                                )}
     454                                                                                                                        </button>
     455
     456                                                                                                                        {/* Add to playlist button */}
     457                                                                                                                        <div className="relative">
     458                                                                                                                                <button
     459                                                                                                                                        onClick={(e) => {
     460                                                                                                                                                e.stopPropagation();
     461                                                                                                                                                togglePlaylistDropdown(song.id);
     462                                                                                                                                        }}
     463                                                                                                                                        className="text-gray-400 hover:text-[#1db954] transition-colors cursor-pointer"
     464                                                                                                                                        aria-label="Add to playlist"
     465                                                                                                                                >
     466                                                                                                                                        <svg
     467                                                                                                                                                className="w-6 h-6"
     468                                                                                                                                                fill="none"
     469                                                                                                                                                stroke="currentColor"
     470                                                                                                                                                viewBox="0 0 24 24"
     471                                                                                                                                        >
     472                                                                                                                                                <path
     473                                                                                                                                                        strokeLinecap="round"
     474                                                                                                                                                        strokeLinejoin="round"
     475                                                                                                                                                        strokeWidth={2}
     476                                                                                                                                                        d="M12 4v16m8-8H4"
     477                                                                                                                                                />
     478                                                                                                                                        </svg>
     479                                                                                                                                </button>
     480
     481                                                                                                                                <PlaylistDropdown
     482                                                                                                                                        songId={song.id}
     483                                                                                                                                        isOpen={openPlaylistDropdown === song.id}
     484                                                                                                                                        onClose={() => setOpenPlaylistDropdown(null)}
     485                                                                                                                                        direction="above"
     486                                                                                                                                />
     487                                                                                                                        </div>
     488                                                                                                                </div>
     489                                                                                                        )}
     490                                                                                                </div>
     491                                                                                        </div>
     492                                                                                ))}
     493                                                                        </div>
     494                                                                )}
     495                                                        </>
     496                                                )}
     497                                        </div>
     498                                </div>
     499                        </div>
     500                </div>
     501        );
    502502};
    503503
Note: See TracChangeset for help on using the changeset viewer.