import React, { useState, useEffect, useCallback } from 'react'; import { MediaList } from '../components/MediaList'; import { MediaItem } from '../api/jellyfin'; import { Loader } from '../components/Loader'; import { usePageTitle } from '../context/PageTitleContext/PageTitleContext'; import { usePullToRefresh } from '../hooks/usePullToRefresh'; import { useScrollRestore } from '../hooks/useScrollRestore'; import { PullToRefreshIndicator } from '../components/PullToRefreshIndicator'; import { useNavigate } from 'react-router-dom'; interface GenreSection { genre: string; title: string; description: string; tracks: MediaItem[]; } interface GenresData { title: string; description: string; genres: GenreSection[]; total_count: number; } export const GenresOverview: React.FC = () => { const { setPageTitle } = usePageTitle(); const navigate = useNavigate(); const [genresData, setGenresData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchGenresData = useCallback(async () => { setLoading(true); setError(null); try { // Determine the correct backend URL based on environment let fetchUrl; // Check if we're on the live site if (window.location.hostname === 'music.samcloud.ca') { // Use relative URL for live site (will use the same domain with reverse proxy) fetchUrl = `/api/discover/genres?t=${Date.now()}`; } else { // Use localhost for development fetchUrl = `http://localhost:5140/api/discover/genres?t=${Date.now()}`; } console.log('Fetching genres overview from:', fetchUrl); const response = await fetch(fetchUrl); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data: GenresData = await response.json(); console.log('Genres overview data:', data); setGenresData(data); setPageTitle(`${data.title || 'All Genres'} (${data.total_count || 0} genres)`); } catch (error) { console.error('Error fetching genres overview:', error); setError(error instanceof Error ? error.message : 'Unknown error occurred'); } finally { setLoading(false); } }, [setPageTitle]); // Enhanced pull-to-refresh with desktop support and animated indicator const { containerRef, isRefreshing, pullDistance, refreshThreshold } = usePullToRefresh({ onRefresh: fetchGenresData }); // Restore scroll position when navigating back useScrollRestore(containerRef as unknown as React.MutableRefObject, 'genres-overview') useEffect(() => { fetchGenresData(); }, [fetchGenresData]); if (loading) { return (

All Genres

); } if (error) { return (

All Genres

Error: {error}

); } if (!genresData || !genresData.genres || genresData.genres.length === 0) { return (

All Genres

No genres available.

); } return (
{/* Pull indicator + relocated Refresh action inside the pull space */} {(pullDistance > 0 || isRefreshing) && (
)}

{genresData.title}

{genresData.description}

{/* Genre sections - vertical layout */}
{genresData.genres.map((genreSection: GenreSection, index: number) => (
{genreSection.title}
{genreSection.description}
navigate(`/section/genre-${encodeURIComponent(genreSection.genre)}?limit=100`)} > See more (100)
))}
); };