diff --git a/spotizerr-ui/src/routes/home.tsx b/spotizerr-ui/src/routes/home.tsx index 723d747..d024545 100644 --- a/spotizerr-ui/src/routes/home.tsx +++ b/spotizerr-ui/src/routes/home.tsx @@ -3,10 +3,11 @@ import { useNavigate, useSearch, useRouterState } from "@tanstack/react-router"; import { useDebounce } from "use-debounce"; import { toast } from "sonner"; import type { TrackType, AlbumType, SearchResult } from "@/types/spotify"; -import { parseSpotifyUrl} from "@/lib/spotify-utils"; +import { parseSpotifyUrl } from "@/lib/spotify-utils"; import { QueueContext } from "@/contexts/queue-context"; import { SearchResultCard } from "@/components/SearchResultCard"; import { indexRoute } from "@/router"; +import { Music, Disc, User, ListMusic } from "lucide-react"; import { authApiClient } from "@/lib/api-client"; import { useSettings } from "@/contexts/settings-context"; import { FaEye, FaDownload } from "react-icons/fa"; @@ -16,12 +17,12 @@ const safelyGetProperty = (obj: any, path: string[], fallback: T): T => { try { let current = obj; for (const key of path) { - if (current == null || typeof current !== 'object') { + if (current == null || typeof current !== "object") { return fallback; } current = current[key]; } - return current ?? fallback; + return (current ?? fallback) as T; } catch { return fallback; } @@ -37,7 +38,9 @@ export const Home = () => { const { settings } = useSettings(); const [query, setQuery] = useState(q || ""); - const [searchType, setSearchType] = useState<"track" | "album" | "artist" | "playlist">(type || "track"); + const [searchType, setSearchType] = useState< + "track" | "album" | "artist" | "playlist" + >(type || "track"); const [debouncedQuery] = useDebounce(query, 500); const [activeTab, setActiveTab] = useState<"search" | "bulkAdd">("search"); const [linksInput, setLinksInput] = useState(""); @@ -49,8 +52,6 @@ export const Home = () => { const context = useContext(QueueContext); const loaderRef = useRef(null); - // Removed scroll locking on mobile empty state to avoid blocking scroll globally - useEffect(() => { navigate({ search: (prev) => ({ ...prev, q: debouncedQuery, type: searchType }) }); }, [debouncedQuery, searchType, navigate]); @@ -65,7 +66,10 @@ export const Home = () => { const { addItem } = context; const handleAddBulkLinks = useCallback(async () => { - const allLinks = linksInput.split("\n").map((link) => link.trim()).filter(Boolean); + const allLinks = linksInput + .split("\n") + .map((link) => link.trim()) + .filter(Boolean); if (allLinks.length === 0) { toast.info("No links provided to add."); return; @@ -96,12 +100,16 @@ export const Home = () => { setIsBulkAdding(true); try { - const response = await authApiClient.client.post("/bulk/bulk-add-spotify-links", { links: supportedLinks }); - const {count, failed_links } = response.data; + const response = await authApiClient.client.post("/bulk/bulk-add-spotify-links", { + links: supportedLinks, + }); + const { count, failed_links } = response.data; if (failed_links && failed_links.length > 0) { toast.warning("Bulk Add Completed with Warnings", { - description: `${count} links added. Failed to add ${failed_links.length} links: ${failed_links.join(", ")}`, + description: `${count} links added. Failed to add ${failed_links.length} links: ${failed_links.join( + ", " + )}`, }); } else { toast.success("Bulk Add Successful", { @@ -130,7 +138,10 @@ export const Home = () => { }, [linksInput]); const handleWatchBulkLinks = useCallback(async () => { - const links = linksInput.split("\n").map((link) => link.trim()).filter(Boolean); + const links = linksInput + .split("\n") + .map((link) => link.trim()) + .filter(Boolean); if (links.length === 0) { toast.info("No links provided to watch."); return; @@ -197,7 +208,7 @@ export const Home = () => { loadMore(); } }, - { threshold: 1.0 }, + { threshold: 1.0 } ); const currentLoader = loaderRef.current; @@ -218,7 +229,7 @@ export const Home = () => { addItem({ spotifyId: track.id, type: "track", name: track.name, artist: artistName }); toast.info(`Adding ${track.name} to queue...`); }, - [addItem], + [addItem] ); const handleDownloadAlbum = useCallback( @@ -227,53 +238,63 @@ export const Home = () => { addItem({ spotifyId: album.id, type: "album", name: album.name, artist: artistName }); toast.info(`Adding ${album.name} to queue...`); }, - [addItem], + [addItem] ); const resultComponent = useMemo(() => { return (
- {displayedResults.map((item) => { - // Add safety checks for essential properties - if (!item || !item.id || !item.name || !item.model) { - return null; - } + {displayedResults + .map((item) => { + // Add safety checks for essential properties + if (!item || !item.id || !item.name || !item.model) { + return null; + } - let imageUrl; - let onDownload; - let subtitle; + let imageUrl; + let onDownload: (() => void) | undefined; + let subtitle: string | undefined; - if (item.model === "track") { - imageUrl = safelyGetProperty(item, ['album', 'images', '0', 'url'], undefined); - onDownload = () => handleDownloadTrack(item as TrackType); - const artists = safelyGetProperty(item, ['artists'], []); - subtitle = Array.isArray(artists) ? artists.map((a: any) => safelyGetProperty(a, ['name'], 'Unknown')).join(", ") : "Unknown Artist"; - } else if (item.model === "album") { - imageUrl = safelyGetProperty(item, ['images', '0', 'url'], undefined); - onDownload = () => handleDownloadAlbum(item as AlbumType); - const artists = safelyGetProperty(item, ['artists'], []); - subtitle = Array.isArray(artists) ? artists.map((a: any) => safelyGetProperty(a, ['name'], 'Unknown')).join(", ") : "Unknown Artist"; - } else if (item.model === "artist") { - imageUrl = safelyGetProperty(item, ['images', '0', 'url'], undefined); - subtitle = "Artist"; - } else if (item.model === "playlist") { - imageUrl = safelyGetProperty(item, ['images', '0', 'url'], undefined); - const ownerName = safelyGetProperty(item, ['owner', 'display_name'], 'Unknown'); - subtitle = `By ${ownerName}`; - } + if (item.model === "track") { + imageUrl = safelyGetProperty(item, ["album", "images", "0", "url"], undefined); + onDownload = () => handleDownloadTrack(item as TrackType); + const artists = safelyGetProperty(item, ["artists"], []); + subtitle = Array.isArray(artists) + ? artists + .map((a: any) => safelyGetProperty(a, ["name"], "Unknown")) + .join(", ") + : "Unknown Artist"; + } else if (item.model === "album") { + imageUrl = safelyGetProperty(item, ["images", "0", "url"], undefined); + onDownload = () => handleDownloadAlbum(item as AlbumType); + const artists = safelyGetProperty(item, ["artists"], []); + subtitle = Array.isArray(artists) + ? artists + .map((a: any) => safelyGetProperty(a, ["name"], "Unknown")) + .join(", ") + : "Unknown Artist"; + } else if (item.model === "artist") { + imageUrl = safelyGetProperty(item, ["images", "0", "url"], undefined); + subtitle = "Artist"; + } else if (item.model === "playlist") { + imageUrl = safelyGetProperty(item, ["images", "0", "url"], undefined); + const ownerName = safelyGetProperty(item, ["owner", "display_name"], "Unknown"); + subtitle = `By ${ownerName}`; + } - return ( - - ); - }).filter(Boolean)} {/* Filter out null components */} + return ( + + ); + }) + .filter(Boolean)}
); }, [displayedResults, handleDownloadTrack, handleDownloadAlbum]); @@ -284,6 +305,7 @@ export const Home = () => {

Spotizerr

+ {/* Tabs */}
+ ))} +
+ + {/* Select for smaller screens */} -
0 ? 'overflow-y-auto md:overflow-visible' : '' - }`}> + +
0 ? "overflow-y-auto md:overflow-visible" : "" + }`} + > {isLoading ? (

Loading results...

) : ( <> {resultComponent}
- {isLoadingMore &&

Loading more results...

} + {isLoadingMore && ( +

Loading more results...

+ )} )}