feat: add API availability checks and fallback for cover art in various components

This commit is contained in:
2025-06-22 21:10:15 -05:00
parent 6fcf58e7ba
commit b07685fe79
12 changed files with 78 additions and 38 deletions

View File

@@ -80,8 +80,12 @@ export default function AlbumPage() {
console.error('Failed to play album from track:', error); console.error('Failed to play album from track:', error);
} }
}; };
const handleAddToQueue = (song: Song) => { const handleAddToQueue = (song: Song) => {
if (!api) {
console.error('Navidrome API not available');
return;
}
const track = { const track = {
id: song.id, id: song.id,
name: song.title, name: song.title,
@@ -106,9 +110,8 @@ export default function AlbumPage() {
const seconds = duration % 60; const seconds = duration % 60;
return `${minutes}:${seconds.toString().padStart(2, '0')}`; return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}; };
// Get cover art URL with proper fallback // Get cover art URL with proper fallback
const coverArtUrl = album.coverArt const coverArtUrl = album.coverArt && api
? api.getCoverArtUrl(album.coverArt, 300) ? api.getCoverArtUrl(album.coverArt, 300)
: '/default-user.jpg'; : '/default-user.jpg';

View File

@@ -89,9 +89,8 @@ export default function ArtistPage() {
</div> </div>
); );
} }
// Get artist image URL with proper fallback // Get artist image URL with proper fallback
const artistImageUrl = artist.coverArt const artistImageUrl = artist.coverArt && api
? api.getCoverArtUrl(artist.coverArt, 300) ? api.getCoverArtUrl(artist.coverArt, 300)
: '/default-user.jpg'; : '/default-user.jpg';

View File

@@ -24,8 +24,12 @@ export default function BrowsePage() {
const albumsPerPage = 84; const albumsPerPage = 84;
const api = getNavidromeAPI(); const api = getNavidromeAPI();
const loadAlbums = async (page: number, append: boolean = false) => { const loadAlbums = async (page: number, append: boolean = false) => {
if (!api) {
console.error('Navidrome API not available');
return;
}
try { try {
setIsLoadingAlbums(true); setIsLoadingAlbums(true);
const offset = page * albumsPerPage; const offset = page * albumsPerPage;

View File

@@ -57,9 +57,8 @@ export function AlbumArtwork({
starItem(album.id, 'album'); starItem(album.id, 'album');
} }
}; };
// Get cover art URL with proper fallback // Get cover art URL with proper fallback
const coverArtUrl = album.coverArt const coverArtUrl = album.coverArt && api
? api.getCoverArtUrl(album.coverArt, 300) ? api.getCoverArtUrl(album.coverArt, 300)
: '/default-user.jpg'; : '/default-user.jpg';

View File

@@ -52,9 +52,8 @@ export function ArtistIcon({
starItem(artist.id, 'artist'); starItem(artist.id, 'artist');
} }
}; };
// Get cover art URL with proper fallback // Get cover art URL with proper fallback
const artistImageUrl = artist.coverArt const artistImageUrl = artist.coverArt && api
? api.getCoverArtUrl(artist.coverArt, 200) ? api.getCoverArtUrl(artist.coverArt, 200)
: '/default-user.jpg'; : '/default-user.jpg';

View File

@@ -51,9 +51,8 @@ const PlaylistsPage: React.FC = () => {
<Separator className="my-4" /> <Separator className="my-4" />
<div className="relative"> <div className="relative">
<ScrollArea> <ScrollArea>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 pb-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 pb-4"> {playlists.map((playlist) => {
{playlists.map((playlist) => { const playlistCoverUrl = playlist.coverArt && api
const playlistCoverUrl = playlist.coverArt
? api.getCoverArtUrl(playlist.coverArt, 200) ? api.getCoverArtUrl(playlist.coverArt, 200)
: '/default-user.jpg'; : '/default-user.jpg';

View File

@@ -101,8 +101,12 @@ export default function SongsPage() {
setFilteredSongs(filtered); setFilteredSongs(filtered);
}, [songs, searchQuery, sortBy, sortDirection]); }, [songs, searchQuery, sortBy, sortDirection]);
const handlePlaySong = (song: Song) => { const handlePlaySong = (song: Song) => {
if (!api) {
console.error('Navidrome API not available');
return;
}
const track = { const track = {
id: song.id, id: song.id,
name: song.title, name: song.title,
@@ -117,8 +121,12 @@ export default function SongsPage() {
playTrack(track); playTrack(track);
}; };
const handleAddToQueue = (song: Song) => { const handleAddToQueue = (song: Song) => {
if (!api) {
console.error('Navidrome API not available');
return;
}
const track = { const track = {
id: song.id, id: song.id,
name: song.title, name: song.title,
@@ -229,9 +237,8 @@ export default function SongsPage() {
</div> </div>
{/* Album Art */} {/* Album Art */}
<div className="w-12 h-12 mr-4 flex-shrink-0"> <div className="w-12 h-12 mr-4 flex-shrink-0"> <Image
<Image src={song.coverArt && api ? api.getCoverArtUrl(song.coverArt, 100) : '/default-user.jpg'}
src={song.coverArt ? api.getCoverArtUrl(song.coverArt, 100) : '/default-user.jpg'}
alt={song.album} alt={song.album}
width={48} width={48}
height={48} height={48}

View File

@@ -44,8 +44,12 @@ export default function PlaylistPage() {
fetchPlaylist(); fetchPlaylist();
} }
}, [id, getPlaylist]); }, [id, getPlaylist]);
const handlePlayClick = (song: Song) => { const handlePlayClick = (song: Song) => {
if (!api) {
console.error('Navidrome API not available');
return;
}
const track = { const track = {
id: song.id, id: song.id,
name: song.title, name: song.title,
@@ -59,8 +63,12 @@ export default function PlaylistPage() {
}; };
playTrack(track); playTrack(track);
}; };
const handleAddToQueue = (song: Song) => { const handleAddToQueue = (song: Song) => {
if (!api) {
console.error('Navidrome API not available');
return;
}
const track = { const track = {
id: song.id, id: song.id,
name: song.title, name: song.title,
@@ -74,9 +82,11 @@ export default function PlaylistPage() {
}; };
addToQueue(track); addToQueue(track);
}; };
const handlePlayPlaylist = () => { const handlePlayPlaylist = () => {
if (tracklist.length === 0) return; if (tracklist.length === 0 || !api) {
if (!api) console.error('Navidrome API not available');
return;
}
// Convert all songs to tracks // Convert all songs to tracks
const tracks = tracklist.map(song => ({ const tracks = tracklist.map(song => ({
@@ -125,9 +135,8 @@ export default function PlaylistPage() {
</div> </div>
); );
} }
// Get playlist cover art URL with fallback // Get playlist cover art URL with fallback
const playlistCoverUrl = playlist.coverArt const playlistCoverUrl = playlist.coverArt && api
? api.getCoverArtUrl(playlist.coverArt, 300) ? api.getCoverArtUrl(playlist.coverArt, 300)
: '/default-user.jpg'; : '/default-user.jpg';
@@ -196,9 +205,8 @@ export default function PlaylistPage() {
</div> </div>
{/* Album Art */} {/* Album Art */}
<div className="w-12 h-12 mr-4 flex-shrink-0"> <div className="w-12 h-12 mr-4 flex-shrink-0"> <Image
<Image src={song.coverArt && api ? api.getCoverArtUrl(song.coverArt, 100) : '/default-user.jpg'}
src={song.coverArt ? api.getCoverArtUrl(song.coverArt, 100) : '/default-user.jpg'}
alt={song.album} alt={song.album}
width={48} width={48}
height={48} height={48}

View File

@@ -22,11 +22,13 @@ const RadioStationsPage = () => {
}); });
const { toast } = useToast(); const { toast } = useToast();
const { playTrack } = useAudioPlayer(); const { playTrack } = useAudioPlayer();
const loadRadioStations = useCallback(async () => { const loadRadioStations = useCallback(async () => {
setIsLoading(true); setIsLoading(true);
try { try {
const api = getNavidromeAPI(); const api = getNavidromeAPI();
if (!api) {
throw new Error('Navidrome API not available');
}
const stationList = await api.getInternetRadioStations(); const stationList = await api.getInternetRadioStations();
setStations(stationList); setStations(stationList);
} catch (error) { } catch (error) {
@@ -53,10 +55,11 @@ const RadioStationsPage = () => {
variant: "destructive" variant: "destructive"
}); });
return; return;
} } try {
try {
const api = getNavidromeAPI(); const api = getNavidromeAPI();
if (!api) {
throw new Error('Navidrome API not available');
}
await api.createInternetRadioStation( await api.createInternetRadioStation(
newStation.name, newStation.name,
newStation.streamUrl, newStation.streamUrl,
@@ -81,9 +84,11 @@ const RadioStationsPage = () => {
} }
}; };
const deleteRadioStation = async (stationId: string) => { const deleteRadioStation = async (stationId: string) => { try {
try {
const api = getNavidromeAPI(); const api = getNavidromeAPI();
if (!api) {
throw new Error('Navidrome API not available');
}
await api.deleteInternetRadioStation(stationId); await api.deleteInternetRadioStation(stationId);
toast({ toast({

View File

@@ -51,8 +51,12 @@ export default function SearchPage() {
return () => clearTimeout(timeoutId); return () => clearTimeout(timeoutId);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchQuery]); }, [searchQuery]);
const handlePlaySong = (song: Song) => { const handlePlaySong = (song: Song) => {
if (!api) {
console.error('Navidrome API not available');
return;
}
const track = { const track = {
id: song.id, id: song.id,
name: song.title, name: song.title,
@@ -67,8 +71,12 @@ export default function SearchPage() {
playTrack(track); playTrack(track);
}; };
const handleAddToQueue = (song: Song) => { const handleAddToQueue = (song: Song) => {
if (!api) {
console.error('Navidrome API not available');
return;
}
const track = { const track = {
id: song.id, id: song.id,
name: song.title, name: song.title,
@@ -182,9 +190,8 @@ export default function SearchPage() {
</div> </div>
{/* Song Cover */} {/* Song Cover */}
<div className="flex-shrink-0"> <div className="flex-shrink-0"> <Image
<Image src={song.coverArt && api ? api.getCoverArtUrl(song.coverArt, 64) : '/default-user.jpg'}
src={song.coverArt ? api.getCoverArtUrl(song.coverArt, 64) : '/default-user.jpg'}
alt={song.album} alt={song.album}
width={48} width={48}
height={48} height={48}

View File

@@ -30,6 +30,7 @@
"lucide-react": "^0.469.0", "lucide-react": "^0.469.0",
"next": "^15.0.3", "next": "^15.0.3",
"posthog-js": "^1.255.0", "posthog-js": "^1.255.0",
"posthog-node": "^5.1.1",
"react": "^19", "react": "^19",
"react-dom": "^19", "react-dom": "^19",
"react-hook-form": "^7.53.2", "react-hook-form": "^7.53.2",

9
pnpm-lock.yaml generated
View File

@@ -71,6 +71,9 @@ importers:
posthog-js: posthog-js:
specifier: ^1.255.0 specifier: ^1.255.0
version: 1.255.0 version: 1.255.0
posthog-node:
specifier: ^5.1.1
version: 5.1.1
react: react:
specifier: ^19 specifier: ^19
version: 19.0.0 version: 19.0.0
@@ -1968,6 +1971,10 @@ packages:
rrweb-snapshot: rrweb-snapshot:
optional: true optional: true
posthog-node@5.1.1:
resolution: {integrity: sha512-6VISkNdxO24ehXiDA4dugyCSIV7lpGVaEu5kn/dlAj+SJ1lgcDru9PQ8p/+GSXsXVxohd1t7kHL2JKc9NoGb0w==}
engines: {node: '>=20'}
preact@10.26.9: preact@10.26.9:
resolution: {integrity: sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==} resolution: {integrity: sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==}
@@ -4391,6 +4398,8 @@ snapshots:
preact: 10.26.9 preact: 10.26.9
web-vitals: 4.2.4 web-vitals: 4.2.4
posthog-node@5.1.1: {}
preact@10.26.9: {} preact@10.26.9: {}
prelude-ls@1.2.1: {} prelude-ls@1.2.1: {}