Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>

This commit is contained in:
mbrucedogs 2025-07-20 19:42:14 -05:00
parent 50eba457f6
commit 3aee1fc44e
13 changed files with 163 additions and 209 deletions

View File

@ -5,6 +5,7 @@ import TopPlayed from './features/TopPlayed/Top100';
import { FirebaseProvider } from './firebase/FirebaseProvider';
import { ErrorBoundary } from './components/common';
import { AuthInitializer } from './components/Auth';
import { SongInfoProvider } from './components/common/SongInfoProvider';
function App() {
return (
@ -12,22 +13,24 @@ function App() {
<FirebaseProvider>
<Router>
<AuthInitializer>
<Layout>
<Routes>
<Route path="/" element={<Navigate to="/queue" replace />} />
<Route path="/search" element={<Search />} />
<Route path="/queue" element={<Queue />} />
<Route path="/favorites" element={<Favorites />} />
<Route path="/new-songs" element={<NewSongs />} />
<Route path="/artists" element={<Artists />} />
<Route path="/song-lists" element={<SongLists />} />
<Route path="/history" element={<History />} />
<Route path="/top-played" element={<TopPlayed />} />
<Route path="/singers" element={<Singers />} />
<Route path="/settings" element={<Settings />} />
<Route path="*" element={<Navigate to="/queue" replace />} />
</Routes>
</Layout>
<SongInfoProvider>
<Layout>
<Routes>
<Route path="/" element={<Navigate to="/queue" replace />} />
<Route path="/search" element={<Search />} />
<Route path="/queue" element={<Queue />} />
<Route path="/favorites" element={<Favorites />} />
<Route path="/new-songs" element={<NewSongs />} />
<Route path="/artists" element={<Artists />} />
<Route path="/song-lists" element={<SongLists />} />
<Route path="/history" element={<History />} />
<Route path="/top-played" element={<TopPlayed />} />
<Route path="/singers" element={<Singers />} />
<Route path="/settings" element={<Settings />} />
<Route path="*" element={<Navigate to="/queue" replace />} />
</Routes>
</Layout>
</SongInfoProvider>
</AuthInitializer>
</Router>
</FirebaseProvider>

View File

@ -0,0 +1,48 @@
import React, { useState, useCallback } from 'react';
import type { ReactNode } from 'react';
import SongInfo from './SongInfo';
import { SongInfoContext, type SongInfoContextType } from '../../contexts/SongInfoContext';
import type { Song } from '../../types';
interface SongInfoProviderProps {
children: ReactNode;
}
export const SongInfoProvider: React.FC<SongInfoProviderProps> = ({ children }) => {
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
const [isOpen, setIsOpen] = useState(false);
const openSongInfo = useCallback((song: Song) => {
setSelectedSong(song);
setIsOpen(true);
}, []);
const closeSongInfo = useCallback(() => {
setIsOpen(false);
setSelectedSong(null);
}, []);
const contextValue: SongInfoContextType = {
openSongInfo,
closeSongInfo,
isOpen,
selectedSong,
};
return (
<SongInfoContext.Provider value={contextValue}>
{children}
{/* Song Info Modal */}
{selectedSong && (
<SongInfo
isOpen={isOpen}
onClose={closeSongInfo}
song={selectedSong}
/>
)}
</SongInfoContext.Provider>
);
};

View File

@ -3,6 +3,9 @@ import { IonItem, IonLabel } from '@ionic/react';
import ActionButton from './ActionButton';
import { useAppSelector } from '../../redux';
import { selectQueue, selectFavorites } from '../../redux';
import { useSongOperations } from '../../hooks/useSongOperations';
import { useToast } from '../../hooks/useToast';
import { useSongInfo } from '../../hooks/useSongInfoContext';
import { debugLog } from '../../utils/logger';
import type { SongItemProps, QueueItem, Song } from '../../types';
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
@ -92,11 +95,11 @@ export const SongActionButtons: React.FC<{
showRemoveButton?: boolean;
showDeleteButton?: boolean;
showFavoriteButton?: boolean;
onDeleteItem?: () => void;
onAddToQueue?: () => void;
onRemoveFromQueue?: () => void;
onToggleFavorite?: () => void;
onDeleteItem?: () => void;
onSelectSinger?: () => void;
onShowSongInfo?: () => void;
}> = ({
isAdmin,
isInQueue,
@ -106,20 +109,20 @@ export const SongActionButtons: React.FC<{
showRemoveButton = false,
showDeleteButton = false,
showFavoriteButton = false,
onDeleteItem,
onAddToQueue,
onRemoveFromQueue,
onToggleFavorite,
onDeleteItem,
onSelectSinger
onShowSongInfo
}) => {
const buttons = [];
// Info button
if (showInfoButton && onSelectSinger) {
if (showInfoButton && onShowSongInfo) {
buttons.push(
<ActionButton
key="info"
onClick={onSelectSinger}
onClick={onShowSongInfo}
variant={ActionButtonVariant.SECONDARY}
size={ActionButtonSize.SMALL}
icon={Icons.INFORMATION_CIRCLE}
@ -129,11 +132,11 @@ export const SongActionButtons: React.FC<{
}
// Add to Queue button
if (showAddButton && !isInQueue) {
if (showAddButton && !isInQueue && onAddToQueue) {
buttons.push(
<ActionButton
key="add"
onClick={onAddToQueue || (() => {})}
onClick={onAddToQueue}
variant={ActionButtonVariant.PRIMARY}
size={ActionButtonSize.SMALL}
icon={Icons.ADD}
@ -171,11 +174,11 @@ export const SongActionButtons: React.FC<{
}
// Toggle Favorite button
if (showFavoriteButton) {
if (showFavoriteButton && onToggleFavorite) {
buttons.push(
<ActionButton
key="favorite"
onClick={onToggleFavorite || (() => {})}
onClick={onToggleFavorite}
variant={isInFavorites ? ActionButtonVariant.DANGER : ActionButtonVariant.SECONDARY}
size={ActionButtonSize.SMALL}
icon={isInFavorites ? Icons.HEART : Icons.HEART_OUTLINE}
@ -195,11 +198,7 @@ export const SongActionButtons: React.FC<{
const SongItem: React.FC<SongItemProps> = ({
song,
context,
onAddToQueue,
onRemoveFromQueue,
onToggleFavorite,
onDeleteItem,
onSelectSinger,
isAdmin = false,
className = '',
showActions = true,
@ -215,10 +214,20 @@ const SongItem: React.FC<SongItemProps> = ({
const queue = useAppSelector(selectQueue);
const favorites = useAppSelector(selectFavorites);
// Get song operations and hooks
const { addToQueue, removeFromQueue, toggleFavorite } = useSongOperations();
const { showSuccess, showError } = useToast();
const { openSongInfo } = useSongInfo();
// Check if song is in queue or favorites based on path
const isInQueue = (Object.values(queue) as QueueItem[]).some(item => item.song.path === song.path);
const isInFavorites = (Object.values(favorites) as Song[]).some(favSong => favSong.path === song.path);
// Find queue item key for removal (only needed for queue context)
const queueItemKey = context === 'queue'
? (Object.entries(queue) as [string, QueueItem][]).find(([, item]) => item.song.path === song.path)?.[0]
: null;
// Debug logging for favorites
debugLog('SongItem render:', {
songTitle: song.title,
@ -239,6 +248,40 @@ const SongItem: React.FC<SongItemProps> = ({
const shouldShowDeleteButton = showDeleteButton !== undefined ? showDeleteButton : context === 'history' && isAdmin;
const shouldShowFavoriteButton = showFavoriteButton !== undefined ? showFavoriteButton : false; // Disabled for all contexts
// Handle song operations internally
const handleAddToQueue = async () => {
try {
await addToQueue(song);
showSuccess('Song added to queue');
} catch {
showError('Failed to add song to queue');
}
};
const handleRemoveFromQueue = async () => {
if (!queueItemKey) return;
try {
await removeFromQueue(queueItemKey);
showSuccess('Song removed from queue');
} catch {
showError('Failed to remove song from queue');
}
};
const handleToggleFavorite = async () => {
try {
await toggleFavorite(song);
showSuccess(isInFavorites ? 'Removed from favorites' : 'Added to favorites');
} catch {
showError('Failed to update favorites');
}
};
const handleSelectSinger = () => {
openSongInfo(song);
};
return (
<IonItem className={className}>
<SongInfoDisplay
@ -258,11 +301,11 @@ const SongItem: React.FC<SongItemProps> = ({
showRemoveButton={shouldShowRemoveButton}
showDeleteButton={shouldShowDeleteButton}
showFavoriteButton={shouldShowFavoriteButton}
onAddToQueue={onAddToQueue}
onRemoveFromQueue={onRemoveFromQueue}
onToggleFavorite={onToggleFavorite}
onDeleteItem={onDeleteItem}
onSelectSinger={onSelectSinger}
onAddToQueue={context === 'queue' ? handleRemoveFromQueue : handleAddToQueue}
onRemoveFromQueue={context === 'queue' ? handleRemoveFromQueue : onDeleteItem}
onToggleFavorite={context === 'favorites' ? onDeleteItem : handleToggleFavorite}
onShowSongInfo={handleSelectSinger}
/>
</div>
)}

View File

@ -0,0 +1,11 @@
import { createContext } from 'react';
import type { Song } from '../types';
export interface SongInfoContextType {
openSongInfo: (song: Song) => void;
closeSongInfo: () => void;
isOpen: boolean;
selectedSong: Song | null;
}
export const SongInfoContext = createContext<SongInfoContextType | null>(null);

View File

@ -1,14 +1,14 @@
import React, { useState } from 'react';
import { IonSearchbar, IonModal, IonHeader, IonToolbar, IonTitle, IonContent, IonItem } from '@ionic/react';
import { list } from 'ionicons/icons';
import { InfiniteScrollList, SongItem, ListItem, NumberDisplay, SongInfo, ActionButton } from '../../components/common';
import { InfiniteScrollList, SongItem, ListItem, NumberDisplay, ActionButton } from '../../components/common';
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
import { Icons } from '../../constants';
import { useArtists } from '../../hooks';
import { useAppSelector } from '../../redux';
import { selectSongs } from '../../redux';
import { debugLog } from '../../utils/logger';
import type { Song } from '../../types';
const Artists: React.FC = () => {
const {
@ -19,15 +19,11 @@ const Artists: React.FC = () => {
handleSearchChange,
getSongsByArtist,
getSongCountByArtist,
handleAddToQueue,
} = useArtists();
const songs = useAppSelector(selectSongs);
const songsCount = Object.keys(songs).length;
const [selectedArtist, setSelectedArtist] = useState<string | null>(null);
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
const [isSongInfoOpen, setIsSongInfoOpen] = useState(false);
// Debug logging
debugLog('Artists component - artists count:', artists.length);
debugLog('Artists component - selected artist:', selectedArtist);
@ -42,16 +38,6 @@ const Artists: React.FC = () => {
setSelectedArtist(null);
};
const handleSongInfo = (song: Song) => {
setSelectedSong(song);
setIsSongInfoOpen(true);
};
const handleCloseSongInfo = () => {
setIsSongInfoOpen(false);
setSelectedSong(null);
};
const selectedArtistSongs = selectedArtist ? getSongsByArtist(selectedArtist) : [];
// Render artist item for InfiniteScrollList
@ -156,8 +142,6 @@ const Artists: React.FC = () => {
<SongItem
song={song}
context="search"
onAddToQueue={() => handleAddToQueue(song)}
onSelectSinger={() => handleSongInfo(song)}
showAddButton={true}
showInfoButton={true}
showFavoriteButton={false}
@ -173,14 +157,6 @@ const Artists: React.FC = () => {
</IonContent>
</IonModal>
{/* Song Info Modal */}
{selectedSong && (
<SongInfo
isOpen={isSongInfoOpen}
onClose={handleCloseSongInfo}
song={selectedSong}
/>
)}
</div>
</>
);

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { InfiniteScrollList, SongItem, SongInfo } from '../../components/common';
import React from 'react';
import { InfiniteScrollList, SongItem } from '../../components/common';
import { useFavorites } from '../../hooks';
import { useAppSelector } from '../../redux';
import { selectFavorites } from '../../redux';
@ -14,9 +14,6 @@ const Favorites: React.FC = () => {
handleToggleFavorite,
} = useFavorites();
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
const [isSongInfoOpen, setIsSongInfoOpen] = useState(false);
const favorites = useAppSelector(selectFavorites);
const favoritesCount = Object.keys(favorites).length;
@ -24,16 +21,6 @@ const Favorites: React.FC = () => {
debugLog('Favorites component - favorites count:', favoritesCount);
debugLog('Favorites component - favorites items:', favoritesItems);
const handleSongInfo = (song: Song) => {
setSelectedSong(song);
setIsSongInfoOpen(true);
};
const handleCloseSongInfo = () => {
setIsSongInfoOpen(false);
setSelectedSong(null);
};
return (
<>
<InfiniteScrollList<Song>
@ -48,7 +35,6 @@ const Favorites: React.FC = () => {
showInfoButton={true}
showAddButton={false}
showDeleteButton={true}
onSelectSinger={() => handleSongInfo(song)}
onDeleteItem={() => handleToggleFavorite(song)}
/>
)}
@ -58,14 +44,6 @@ const Favorites: React.FC = () => {
loadingMessage="Please wait while favorites data is being loaded"
/>
{/* Song Info Modal */}
{selectedSong && (
<SongInfo
isOpen={isSongInfoOpen}
onClose={handleCloseSongInfo}
song={selectedSong}
/>
)}
</>
);
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import { IonChip, IonIcon } from '@ionic/react';
import { time } from 'ionicons/icons';
import { InfiniteScrollList, SongItem, SongInfo } from '../../components/common';
import { InfiniteScrollList, SongItem } from '../../components/common';
import { useHistory } from '../../hooks';
import { useAppSelector } from '../../redux';
import { selectHistory, selectIsAdmin } from '../../redux';
@ -14,30 +14,16 @@ const History: React.FC = () => {
historyItems,
hasMore,
loadMore,
handleAddToQueue,
handleDeleteFromHistory,
} = useHistory();
const history = useAppSelector(selectHistory);
const isAdmin = useAppSelector(selectIsAdmin);
const historyCount = Object.keys(history).length;
const [selectedSong, setSelectedSong] = React.useState<Song | null>(null);
const [isSongInfoOpen, setIsSongInfoOpen] = React.useState(false);
// Debug logging
debugLog('History component - history count:', historyCount);
debugLog('History component - history items:', historyItems);
const handleSongInfo = (song: Song) => {
setSelectedSong(song);
setIsSongInfoOpen(true);
};
const handleCloseSongInfo = () => {
setIsSongInfoOpen(false);
setSelectedSong(null);
};
// Render extra content for history items (play date)
const renderExtraContent = (item: Song) => {
if (item.date) {
@ -64,8 +50,6 @@ const History: React.FC = () => {
<SongItem
song={song}
context="history"
onAddToQueue={() => handleAddToQueue(song)}
onSelectSinger={() => handleSongInfo(song)}
onDeleteItem={() => handleDeleteFromHistory(song)}
isAdmin={isAdmin}
showAddButton={true}
@ -83,14 +67,6 @@ const History: React.FC = () => {
loadingMessage="Please wait while history data is being loaded"
/>
{/* Song Info Modal */}
{selectedSong && (
<SongInfo
isOpen={isSongInfoOpen}
onClose={handleCloseSongInfo}
song={selectedSong}
/>
)}
</>
);
};

View File

@ -1,5 +1,5 @@
import React from 'react';
import { InfiniteScrollList, SongItem, SongInfo } from '../../components/common';
import { InfiniteScrollList, SongItem } from '../../components/common';
import { useNewSongs } from '../../hooks';
import { useAppSelector } from '../../redux';
import { selectNewSongsArray } from '../../redux';
@ -11,28 +11,15 @@ const NewSongs: React.FC = () => {
newSongsItems,
hasMore,
loadMore,
handleAddToQueue,
} = useNewSongs();
const newSongsArray = useAppSelector(selectNewSongsArray);
const newSongsCount = newSongsArray.length;
const [selectedSong, setSelectedSong] = React.useState<Song | null>(null);
const [isSongInfoOpen, setIsSongInfoOpen] = React.useState(false);
// Debug logging
debugLog('NewSongs component - new songs count:', newSongsCount);
debugLog('NewSongs component - new songs items:', newSongsItems);
const handleSongInfo = (song: Song) => {
setSelectedSong(song);
setIsSongInfoOpen(true);
};
const handleCloseSongInfo = () => {
setIsSongInfoOpen(false);
setSelectedSong(null);
};
return (
<>
<InfiniteScrollList<Song>
@ -44,8 +31,6 @@ const NewSongs: React.FC = () => {
<SongItem
song={song}
context="search"
onAddToQueue={() => handleAddToQueue(song)}
onSelectSinger={() => handleSongInfo(song)}
showAddButton={true}
showInfoButton={true}
showFavoriteButton={false}
@ -57,14 +42,6 @@ const NewSongs: React.FC = () => {
loadingMessage="Please wait while new songs data is being loaded"
/>
{/* Song Info Modal */}
{selectedSong && (
<SongInfo
isOpen={isSongInfoOpen}
onClose={handleCloseSongInfo}
song={selectedSong}
/>
)}
</>
);
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import { IonSearchbar } from '@ionic/react';
import { InfiniteScrollList, SongItem, SongInfo } from '../../components/common';
import { useSearch, useSongInfo } from '../../hooks';
import { InfiniteScrollList, SongItem } from '../../components/common';
import { useSearch } from '../../hooks';
import { useAppSelector } from '../../redux';
import { selectIsAdmin, selectSongs } from '../../redux';
import { debugLog } from '../../utils/logger';
@ -12,12 +12,9 @@ const Search: React.FC = () => {
searchTerm,
searchResults,
handleSearchChange,
handleAddToQueue,
loadMore,
} = useSearch();
const { isOpen, selectedSong, openSongInfo, closeSongInfo } = useSongInfo();
const isAdmin = useAppSelector(selectIsAdmin);
const songs = useAppSelector(selectSongs);
const songsCount = Object.keys(songs).length;
@ -63,8 +60,6 @@ const Search: React.FC = () => {
<SongItem
song={song}
context="search"
onAddToQueue={() => handleAddToQueue(song)}
onSelectSinger={() => openSongInfo(song)}
isAdmin={isAdmin}
showAddButton={true}
showInfoButton={true}
@ -86,14 +81,6 @@ const Search: React.FC = () => {
)}
</div>
{/* Song Info Modal */}
{selectedSong && (
<SongInfo
isOpen={isOpen}
onClose={closeSongInfo}
song={selectedSong}
/>
)}
</>
);
};

View File

@ -1,7 +1,7 @@
import React, { useState, useMemo, useCallback } from 'react';
import { IonItem, IonModal, IonHeader, IonToolbar, IonTitle, IonChip, IonContent, IonList, IonAccordionGroup, IonAccordion } from '@ionic/react';
import { list } from 'ionicons/icons';
import { InfiniteScrollList, SongItem, ListItem, TwoLineDisplay, NumberDisplay, SongInfo, ActionButton } from '../../components/common';
import { InfiniteScrollList, SongItem, ListItem, TwoLineDisplay, NumberDisplay, ActionButton } from '../../components/common';
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
import { Icons } from '../../constants';
import { useSongLists } from '../../hooks';
@ -17,15 +17,13 @@ const SongLists: React.FC = () => {
hasMore,
loadMore,
checkSongAvailability,
handleAddToQueue,
} = useSongLists();
const songListData = useAppSelector(selectSongList);
const songListCount = Object.keys(songListData).length;
const [selectedSongList, setSelectedSongList] = useState<string | null>(null);
const [expandedSongKey, setExpandedSongKey] = useState<string | null>(null);
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
const [isSongInfoOpen, setIsSongInfoOpen] = useState(false);
@ -43,15 +41,7 @@ const SongLists: React.FC = () => {
setExpandedSongKey(expandedSongKey === songKey ? null : songKey);
}, [expandedSongKey]);
const handleSongInfo = useCallback((song: Song) => {
setSelectedSong(song);
setIsSongInfoOpen(true);
}, []);
const handleCloseSongInfo = useCallback(() => {
setIsSongInfoOpen(false);
setSelectedSong(null);
}, []);
const finalSelectedList = selectedSongList
? allSongLists.find(list => list.key === selectedSongList)
@ -152,8 +142,6 @@ const SongLists: React.FC = () => {
key={song.key || `${song.title}-${song.artist}-${sidx}`}
song={song}
context="search"
onAddToQueue={() => handleAddToQueue(song)}
onSelectSinger={() => handleSongInfo(song)}
showAddButton={true}
showInfoButton={true}
showFavoriteButton={false}
@ -195,14 +183,6 @@ const SongLists: React.FC = () => {
</IonContent>
</IonModal>
{/* Song Info Modal */}
{selectedSong && (
<SongInfo
isOpen={isSongInfoOpen}
onClose={handleCloseSongInfo}
song={selectedSong}
/>
)}
</div>
</>
);

View File

@ -4,14 +4,13 @@ import { list } from 'ionicons/icons';
import { useTopPlayed } from '../../hooks';
import { useAppSelector } from '../../redux';
import { selectTopPlayed, selectSongsArray } from '../../redux';
import { InfiniteScrollList, SongItem, ListItem, SongInfo, ActionButton } from '../../components/common';
import { InfiniteScrollList, SongItem, ListItem, ActionButton } from '../../components/common';
import { filterSongs } from '../../utils/dataProcessing';
import { debugLog } from '../../utils/logger';
import { useSongOperations } from '../../hooks';
import { useToast } from '../../hooks';
import { ActionButtonVariant, ActionButtonSize, ActionButtonIconSlot } from '../../types';
import { Icons } from '../../constants';
import type { TopPlayed, Song } from '../../types';
import type { TopPlayed } from '../../types';
const Top100: React.FC = () => {
debugLog('Top100 component - RENDERING START');
@ -26,11 +25,8 @@ const Top100: React.FC = () => {
const topPlayed = useAppSelector(selectTopPlayed);
const topPlayedCount = Object.keys(topPlayed).length;
const allSongs = useAppSelector(selectSongsArray);
const { addToQueue } = useSongOperations();
const { showSuccess, showError } = useToast();
const [selectedTopPlayed, setSelectedTopPlayed] = useState<TopPlayed | null>(null);
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
const [isSongInfoOpen, setIsSongInfoOpen] = useState(false);
debugLog('Top100 component - Redux data:', { topPlayedCount, topPlayedItems: topPlayedItems.length });
@ -42,15 +38,7 @@ const Top100: React.FC = () => {
setSelectedTopPlayed(null);
}, []);
const handleSongInfo = useCallback((song: Song) => {
setSelectedSong(song);
setIsSongInfoOpen(true);
}, []);
const handleCloseSongInfo = useCallback(() => {
setIsSongInfoOpen(false);
setSelectedSong(null);
}, []);
// Find songs that match the selected top played item
const selectedSongs = useMemo(() => {
@ -75,15 +63,6 @@ const Top100: React.FC = () => {
return filteredSongs;
}, [selectedTopPlayed, allSongs]);
const handleAddToQueue = useCallback(async (song: Song) => {
try {
await addToQueue(song);
showSuccess('Song added to queue');
} catch {
showError('Failed to add song to queue');
}
}, [addToQueue, showSuccess, showError]);
// Use real Firebase data from the hook
@ -161,8 +140,6 @@ const Top100: React.FC = () => {
key={song.key || `${song.title}-${song.artist}`}
song={song}
context="search"
onAddToQueue={() => handleAddToQueue(song)}
onSelectSinger={() => handleSongInfo(song)}
showAddButton={true}
showInfoButton={true}
showFavoriteButton={false}
@ -172,14 +149,6 @@ const Top100: React.FC = () => {
</IonContent>
</IonModal>
{/* Song Info Modal */}
{selectedSong && (
<SongInfo
isOpen={isSongInfoOpen}
onClose={handleCloseSongInfo}
song={selectedSong}
/>
)}
</>
);
};

View File

@ -0,0 +1,10 @@
import { useContext } from 'react';
import { SongInfoContext, type SongInfoContextType } from '../contexts/SongInfoContext';
export const useSongInfo = (): SongInfoContextType => {
const context = useContext(SongInfoContext);
if (context === null) {
throw new Error('useSongInfo must be used within a SongInfoProvider');
}
return context;
};

View File

@ -165,11 +165,7 @@ export interface ActionButtonProps {
export interface SongItemProps {
song: Song;
context: 'search' | 'queue' | 'favorites' | 'history' | 'songlists' | 'top100' | 'new';
onAddToQueue?: () => void;
onRemoveFromQueue?: () => void;
onToggleFavorite?: () => void;
onDeleteItem?: () => void;
onSelectSinger?: () => void;
isAdmin?: boolean;
className?: string;
showActions?: boolean;