From cb46224db400cc08e77585d2eae00fc7b4ba16d0 Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 15 Sep 2026 11:15:51 +0200 Subject: [PATCH 1/7] made clicking on card route to detail page with dummy data --- webdev-prosjekt1/src/App.tsx | 7 ++++--- webdev-prosjekt1/src/components/AlbumCard.tsx | 7 +++++-- webdev-prosjekt1/src/components/ArtistCard.tsx | 7 +++++-- webdev-prosjekt1/src/components/TrackCard.css | 6 ++++++ webdev-prosjekt1/src/components/TrackCard.tsx | 7 +++++-- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/webdev-prosjekt1/src/App.tsx b/webdev-prosjekt1/src/App.tsx index c11b91c..afc62ec 100644 --- a/webdev-prosjekt1/src/App.tsx +++ b/webdev-prosjekt1/src/App.tsx @@ -71,6 +71,7 @@ function App() { uri: 'spotify:album:album-id', }; const handleSearch = (nextQuery: string) => setQuery(nextQuery); + const navigateToDetail = (route: string) => window.location.assign(route); const { data: results, isLoading, isFetching, isError, error } = useQuery({ queryKey: ['search', query], queryFn: () => search(query), @@ -92,15 +93,15 @@ function App() {

Tracks

-
    {results?.tracks.map((track) => )}
+
    {results?.tracks.map((track) => navigateToDetail('/song-detail')} />)}

Albums

-
    {results?.albums.map((album) => )}
+
    {results?.albums.map((album) => navigateToDetail('/album-detail')} />)}

Artists

-
    {results?.artists.map((artist) => )}
+
    {results?.artists.map((artist) => navigateToDetail('/artist-detail')} />)}
) : ( diff --git a/webdev-prosjekt1/src/components/AlbumCard.tsx b/webdev-prosjekt1/src/components/AlbumCard.tsx index 84d442d..d2fa81c 100644 --- a/webdev-prosjekt1/src/components/AlbumCard.tsx +++ b/webdev-prosjekt1/src/components/AlbumCard.tsx @@ -4,15 +4,18 @@ import musicwizimage from '../assets/musicwiz.png'; type AlbumCardProps = { album: Album; + onClick: () => void; } -export const AlbumCard = ({ album }: AlbumCardProps) => { +export const AlbumCard = ({ album, onClick }: AlbumCardProps) => { const image = album.images[0]?.url ?? musicwizimage; const artistNames = album.artists.map((artist) => artist.name).join(', '); const year = album.release_date.slice(0, 4); return ( -
  • +
  • { + if (event.key === 'Enter' || event.key === ' ') onClick(); + }}>
    diff --git a/webdev-prosjekt1/src/components/ArtistCard.tsx b/webdev-prosjekt1/src/components/ArtistCard.tsx index a2a5463..101ed9f 100644 --- a/webdev-prosjekt1/src/components/ArtistCard.tsx +++ b/webdev-prosjekt1/src/components/ArtistCard.tsx @@ -5,13 +5,16 @@ import musicwizimage from '../assets/musicwiz.png'; type ArtistCardProps = { artist: Artist; + onClick: () => void; } -export const ArtistCard = ({ artist }: ArtistCardProps) => { +export const ArtistCard = ({ artist, onClick }: ArtistCardProps) => { const image = artist.images[0]?.url ?? musicwizimage; return ( -
  • +
  • { + if (event.key === 'Enter' || event.key === ' ') onClick(); + }}>
    diff --git a/webdev-prosjekt1/src/components/TrackCard.css b/webdev-prosjekt1/src/components/TrackCard.css index 78f1c74..9e6f94e 100644 --- a/webdev-prosjekt1/src/components/TrackCard.css +++ b/webdev-prosjekt1/src/components/TrackCard.css @@ -7,6 +7,12 @@ border-radius: 0.5rem; background-color: rgb(43, 43, 43); + cursor: pointer; +} + +.search-result-card:focus-visible { + outline: 2px solid var(--primary_color); + outline-offset: 3px; } .card-image { diff --git a/webdev-prosjekt1/src/components/TrackCard.tsx b/webdev-prosjekt1/src/components/TrackCard.tsx index 3c3ea05..2c97267 100644 --- a/webdev-prosjekt1/src/components/TrackCard.tsx +++ b/webdev-prosjekt1/src/components/TrackCard.tsx @@ -4,6 +4,7 @@ import musicwizimage from '../assets/musicwiz.png'; type TrackCardProps = { track: Track; + onClick: () => void; } const formatDuration = (durationMs: number) => { @@ -13,12 +14,14 @@ const formatDuration = (durationMs: number) => { return `${minutes}:${seconds.toString().padStart(2, '0')}`; } -export const TrackCard = ({ track }: TrackCardProps) => { +export const TrackCard = ({ track, onClick }: TrackCardProps) => { const image = track.album.images[0]?.url ?? musicwizimage; const artistNames = track.artists.map((artist) => artist.name).join(', '); return ( -
  • +
  • { + if (event.key === 'Enter' || event.key === ' ') onClick(); + }}>
    From c3ea6773f71cd69098b23bad1b5f5968998b7ec4 Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 15 Sep 2026 11:24:38 +0200 Subject: [PATCH 2/7] made song detail page contain correct data --- webdev-prosjekt1/src/App.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/webdev-prosjekt1/src/App.tsx b/webdev-prosjekt1/src/App.tsx index afc62ec..72ba701 100644 --- a/webdev-prosjekt1/src/App.tsx +++ b/webdev-prosjekt1/src/App.tsx @@ -18,6 +18,7 @@ import musicWizImage from './assets/musicwiz.png'; function App() { const [query, setQuery] = useState(''); + const [selectedTrack, setSelectedTrack] = useState(null); const hasSearched = query.trim().length > 0; const isSongDetailRoute = window.location.pathname === '/song-detail' || window.location.pathname === '/song-detail/'; const isAlbumDetailRoute = window.location.pathname === '/album-detail' || window.location.pathname === '/album-detail/'; @@ -71,6 +72,10 @@ function App() { uri: 'spotify:album:album-id', }; const handleSearch = (nextQuery: string) => setQuery(nextQuery); + const handleTrackClick = (track: Track) => { + setSelectedTrack(track); + window.history.pushState({}, '', '/song-detail'); + }; const navigateToDetail = (route: string) => window.location.assign(route); const { data: results, isLoading, isFetching, isError, error } = useQuery({ queryKey: ['search', query], @@ -80,7 +85,7 @@ function App() { if (isAlbumDetailRoute) return ; if (isArtistDetailRoute) return ; - if (isSongDetailRoute) return ; + if (isSongDetailRoute) return ; return (
    @@ -93,7 +98,7 @@ function App() {

    Tracks

    -
      {results?.tracks.map((track) => navigateToDetail('/song-detail')} />)}
    +
      {results?.tracks.map((track) => handleTrackClick(track)} />)}

    Albums

    From 3d4288690b5ef06cf122d8767ad4b0f373a70a9a Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 15 Sep 2026 11:29:46 +0200 Subject: [PATCH 3/7] made album details show correct data --- webdev-prosjekt1/src/App.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/webdev-prosjekt1/src/App.tsx b/webdev-prosjekt1/src/App.tsx index 72ba701..ce1f4f7 100644 --- a/webdev-prosjekt1/src/App.tsx +++ b/webdev-prosjekt1/src/App.tsx @@ -19,6 +19,7 @@ import musicWizImage from './assets/musicwiz.png'; function App() { const [query, setQuery] = useState(''); const [selectedTrack, setSelectedTrack] = useState(null); + const [selectedAlbum, setSelectedAlbum] = useState(null); const hasSearched = query.trim().length > 0; const isSongDetailRoute = window.location.pathname === '/song-detail' || window.location.pathname === '/song-detail/'; const isAlbumDetailRoute = window.location.pathname === '/album-detail' || window.location.pathname === '/album-detail/'; @@ -76,6 +77,10 @@ function App() { setSelectedTrack(track); window.history.pushState({}, '', '/song-detail'); }; + const handleAlbumClick = (album: Album) => { + setSelectedAlbum(album); + window.history.pushState({}, '', '/album-detail'); + }; const navigateToDetail = (route: string) => window.location.assign(route); const { data: results, isLoading, isFetching, isError, error } = useQuery({ queryKey: ['search', query], @@ -83,7 +88,7 @@ function App() { enabled: query.trim().length > 0, }); - if (isAlbumDetailRoute) return ; + if (isAlbumDetailRoute) return ; if (isArtistDetailRoute) return ; if (isSongDetailRoute) return ; @@ -102,7 +107,7 @@ function App() {

    Albums

    -
      {results?.albums.map((album) => navigateToDetail('/album-detail')} />)}
    +
      {results?.albums.map((album) => handleAlbumClick(album)} />)}

    Artists

    From b08eaf6af1f13a1766c277dd4a7c21ae715bc68d Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 15 Sep 2026 11:35:10 +0200 Subject: [PATCH 4/7] made track list appear as cards in album detail page --- webdev-prosjekt1/src/App.tsx | 11 +++++++--- webdev-prosjekt1/src/api/spotify.ts | 20 ++++++++++++++++- webdev-prosjekt1/src/api/types.ts | 11 ++++++++++ .../src/components/AlbumDetailPage.css | 9 ++++++++ .../src/components/AlbumDetailPage.tsx | 22 ++++++++++++++++--- webdev-prosjekt1/src/components/TrackCard.tsx | 10 +++++---- 6 files changed, 72 insertions(+), 11 deletions(-) diff --git a/webdev-prosjekt1/src/App.tsx b/webdev-prosjekt1/src/App.tsx index ce1f4f7..eaa657b 100644 --- a/webdev-prosjekt1/src/App.tsx +++ b/webdev-prosjekt1/src/App.tsx @@ -11,8 +11,8 @@ import { Header } from './components/Header'; import { TrackCard } from './components/TrackCard'; import { AlbumCard } from './components/AlbumCard'; import { ArtistCard } from './components/ArtistCard'; -import type { Album, Artist, Track } from './api/types'; -import { search } from './api/spotify'; +import type { Album, AlbumTrack, Artist, Track } from './api/types'; +import { fetchTrack, search } from './api/spotify'; import filterIcon from './assets/filter.png'; import musicWizImage from './assets/musicwiz.png'; @@ -81,6 +81,11 @@ function App() { setSelectedAlbum(album); window.history.pushState({}, '', '/album-detail'); }; + const handleAlbumTrackClick = async (track: AlbumTrack) => { + const fullTrack = await fetchTrack(track.id); + setSelectedTrack(fullTrack); + window.history.pushState({}, '', '/song-detail'); + }; const navigateToDetail = (route: string) => window.location.assign(route); const { data: results, isLoading, isFetching, isError, error } = useQuery({ queryKey: ['search', query], @@ -88,7 +93,7 @@ function App() { enabled: query.trim().length > 0, }); - if (isAlbumDetailRoute) return ; + if (isAlbumDetailRoute) return ; if (isArtistDetailRoute) return ; if (isSongDetailRoute) return ; diff --git a/webdev-prosjekt1/src/api/spotify.ts b/webdev-prosjekt1/src/api/spotify.ts index 580b7ee..73c07c7 100644 --- a/webdev-prosjekt1/src/api/spotify.ts +++ b/webdev-prosjekt1/src/api/spotify.ts @@ -1,4 +1,4 @@ -import type { SearchResult, Track } from "./types"; +import type { AlbumTrack, SearchResult, Track } from "./types"; const BASE_URL = "https://api.spotify.com/v1" @@ -112,6 +112,24 @@ export async function fetchTrack(id: string): Promise { return track; } +export async function fetchAlbumTracks(id: string): Promise { + const token = await getValidToken(); + + const response = await fetch(`${BASE_URL}/albums/${id}/tracks?market=NO&limit=50`, { + method: "GET", + headers: { + "Authorization": `Bearer ${token}` + } + }); + + if (!response.ok) { + throw new Error(`Spotify api error: ${response.status}`); + } + + const json = await response.json(); + return json.items; +} + export async function fetchTracks(ids: string[]): Promise { let tracks: Track[] = []; diff --git a/webdev-prosjekt1/src/api/types.ts b/webdev-prosjekt1/src/api/types.ts index 816cb1c..5a53178 100644 --- a/webdev-prosjekt1/src/api/types.ts +++ b/webdev-prosjekt1/src/api/types.ts @@ -40,6 +40,17 @@ export type Track = { uri: string; } +export type AlbumTrack = { + id: string; + name: string; + type: string; + href: string; + artists: Track['artists']; + duration_ms: number; + track_number: number; + uri: string; +} + export type Album = { id: string; name: string; diff --git a/webdev-prosjekt1/src/components/AlbumDetailPage.css b/webdev-prosjekt1/src/components/AlbumDetailPage.css index 20799ce..76dd114 100644 --- a/webdev-prosjekt1/src/components/AlbumDetailPage.css +++ b/webdev-prosjekt1/src/components/AlbumDetailPage.css @@ -115,6 +115,15 @@ line-height: 1.9; } +.album-track-list { + display: grid; + gap: 10px; + max-width: 760px; + margin: 28px 0 0; + padding: 0; + list-style: none; +} + @media (max-width: 900px) { .album-detail-page .detail-content { grid-template-columns: 1fr; diff --git a/webdev-prosjekt1/src/components/AlbumDetailPage.tsx b/webdev-prosjekt1/src/components/AlbumDetailPage.tsx index 3cdc455..76c5f91 100644 --- a/webdev-prosjekt1/src/components/AlbumDetailPage.tsx +++ b/webdev-prosjekt1/src/components/AlbumDetailPage.tsx @@ -1,15 +1,23 @@ import './AlbumDetailPage.css'; import '../variables.css'; import { Header } from './Header'; -import type { Album } from '../api/types'; +import { useQuery } from '@tanstack/react-query'; +import { fetchAlbumTracks } from '../api/spotify'; +import type { Album, AlbumTrack } from '../api/types'; +import { TrackCard } from './TrackCard'; type AlbumDetailPageProps = { album: Album; + onTrackClick: (track: AlbumTrack) => void; }; -export const AlbumDetailPage = ({ album }: AlbumDetailPageProps) => { +export const AlbumDetailPage = ({ album, onTrackClick }: AlbumDetailPageProps) => { const artistNames = album.artists.map((artist) => artist.name).join(', '); const albumImage = album.images[0]?.url; + const { data: tracks, isLoading, isError } = useQuery({ + queryKey: ['album-tracks', album.id], + queryFn: () => fetchAlbumTracks(album.id), + }); return (
    @@ -40,7 +48,15 @@ export const AlbumDetailPage = ({ album }: AlbumDetailPageProps) => {

    Track listing

    -

    The tracks from this album will appear here when the album data is connected.

    + {isLoading &&

    Loading tracks...

    } + {isError &&

    Could not load tracks for this album.

    } + {tracks && ( +
      + {tracks.map((track) => ( + onTrackClick(track)} /> + ))} +
    + )}
    ); diff --git a/webdev-prosjekt1/src/components/TrackCard.tsx b/webdev-prosjekt1/src/components/TrackCard.tsx index 2c97267..7cf0f22 100644 --- a/webdev-prosjekt1/src/components/TrackCard.tsx +++ b/webdev-prosjekt1/src/components/TrackCard.tsx @@ -1,9 +1,10 @@ import './TrackCard.css'; -import type { Track } from '../api/types'; +import type { Album, AlbumTrack, Track } from '../api/types'; import musicwizimage from '../assets/musicwiz.png'; type TrackCardProps = { - track: Track; + track: Track | AlbumTrack; + album?: Album; onClick: () => void; } @@ -14,8 +15,9 @@ const formatDuration = (durationMs: number) => { return `${minutes}:${seconds.toString().padStart(2, '0')}`; } -export const TrackCard = ({ track, onClick }: TrackCardProps) => { - const image = track.album.images[0]?.url ?? musicwizimage; +export const TrackCard = ({ track, album, onClick }: TrackCardProps) => { + const trackAlbum = 'album' in track ? track.album : undefined; + const image = trackAlbum?.images[0]?.url ?? album?.images[0]?.url ?? musicwizimage; const artistNames = track.artists.map((artist) => artist.name).join(', '); return ( From c25be5ab67ff89b564675073cb863dfe76e09919 Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 15 Sep 2026 11:37:42 +0200 Subject: [PATCH 5/7] made artist detail page show correct data --- webdev-prosjekt1/src/App.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/webdev-prosjekt1/src/App.tsx b/webdev-prosjekt1/src/App.tsx index eaa657b..ca1efce 100644 --- a/webdev-prosjekt1/src/App.tsx +++ b/webdev-prosjekt1/src/App.tsx @@ -20,6 +20,7 @@ function App() { const [query, setQuery] = useState(''); const [selectedTrack, setSelectedTrack] = useState(null); const [selectedAlbum, setSelectedAlbum] = useState(null); + const [selectedArtist, setSelectedArtist] = useState(null); const hasSearched = query.trim().length > 0; const isSongDetailRoute = window.location.pathname === '/song-detail' || window.location.pathname === '/song-detail/'; const isAlbumDetailRoute = window.location.pathname === '/album-detail' || window.location.pathname === '/album-detail/'; @@ -86,7 +87,10 @@ function App() { setSelectedTrack(fullTrack); window.history.pushState({}, '', '/song-detail'); }; - const navigateToDetail = (route: string) => window.location.assign(route); + const handleArtistClick = (artist: Artist) => { + setSelectedArtist(artist); + window.history.pushState({}, '', '/artist-detail'); + }; const { data: results, isLoading, isFetching, isError, error } = useQuery({ queryKey: ['search', query], queryFn: () => search(query), @@ -94,7 +98,7 @@ function App() { }); if (isAlbumDetailRoute) return ; - if (isArtistDetailRoute) return ; + if (isArtistDetailRoute) return ; if (isSongDetailRoute) return ; return ( @@ -116,7 +120,7 @@ function App() {

    Artists

    -
      {results?.artists.map((artist) => navigateToDetail('/artist-detail')} />)}
    +
      {results?.artists.map((artist) => handleArtistClick(artist)} />)}
    ) : ( From 1a0ec9a48246f3229f2cf6f25f585bc9d7bbd1a8 Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 15 Sep 2026 11:40:23 +0200 Subject: [PATCH 6/7] made detail pages persistent on reload --- webdev-prosjekt1/src/App.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/webdev-prosjekt1/src/App.tsx b/webdev-prosjekt1/src/App.tsx index ca1efce..94621b2 100644 --- a/webdev-prosjekt1/src/App.tsx +++ b/webdev-prosjekt1/src/App.tsx @@ -16,11 +16,20 @@ import { fetchTrack, search } from './api/spotify'; import filterIcon from './assets/filter.png'; import musicWizImage from './assets/musicwiz.png'; +const readStoredItem = (key: string): T | null => { + try { + const storedItem = localStorage.getItem(key); + return storedItem ? JSON.parse(storedItem) as T : null; + } catch { + return null; + } +}; + function App() { const [query, setQuery] = useState(''); - const [selectedTrack, setSelectedTrack] = useState(null); - const [selectedAlbum, setSelectedAlbum] = useState(null); - const [selectedArtist, setSelectedArtist] = useState(null); + const [selectedTrack, setSelectedTrack] = useState(() => readStoredItem('music-wiz-selected-track')); + const [selectedAlbum, setSelectedAlbum] = useState(() => readStoredItem('music-wiz-selected-album')); + const [selectedArtist, setSelectedArtist] = useState(() => readStoredItem('music-wiz-selected-artist')); const hasSearched = query.trim().length > 0; const isSongDetailRoute = window.location.pathname === '/song-detail' || window.location.pathname === '/song-detail/'; const isAlbumDetailRoute = window.location.pathname === '/album-detail' || window.location.pathname === '/album-detail/'; @@ -76,19 +85,23 @@ function App() { const handleSearch = (nextQuery: string) => setQuery(nextQuery); const handleTrackClick = (track: Track) => { setSelectedTrack(track); + localStorage.setItem('music-wiz-selected-track', JSON.stringify(track)); window.history.pushState({}, '', '/song-detail'); }; const handleAlbumClick = (album: Album) => { setSelectedAlbum(album); + localStorage.setItem('music-wiz-selected-album', JSON.stringify(album)); window.history.pushState({}, '', '/album-detail'); }; const handleAlbumTrackClick = async (track: AlbumTrack) => { const fullTrack = await fetchTrack(track.id); setSelectedTrack(fullTrack); + localStorage.setItem('music-wiz-selected-track', JSON.stringify(fullTrack)); window.history.pushState({}, '', '/song-detail'); }; const handleArtistClick = (artist: Artist) => { setSelectedArtist(artist); + localStorage.setItem('music-wiz-selected-artist', JSON.stringify(artist)); window.history.pushState({}, '', '/artist-detail'); }; const { data: results, isLoading, isFetching, isError, error } = useQuery({ From 20c591dc887f6918627af013fecd6e22c8dc0305 Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 15 Sep 2026 11:45:16 +0200 Subject: [PATCH 7/7] fixed bug where navigating back to album from song takes multiple tries --- webdev-prosjekt1/src/App.tsx | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/webdev-prosjekt1/src/App.tsx b/webdev-prosjekt1/src/App.tsx index 94621b2..3c969a0 100644 --- a/webdev-prosjekt1/src/App.tsx +++ b/webdev-prosjekt1/src/App.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import './App.css'; import './variables.css'; @@ -27,13 +27,14 @@ const readStoredItem = (key: string): T | null => { function App() { const [query, setQuery] = useState(''); + const [currentPath, setCurrentPath] = useState(window.location.pathname); const [selectedTrack, setSelectedTrack] = useState(() => readStoredItem('music-wiz-selected-track')); const [selectedAlbum, setSelectedAlbum] = useState(() => readStoredItem('music-wiz-selected-album')); const [selectedArtist, setSelectedArtist] = useState(() => readStoredItem('music-wiz-selected-artist')); const hasSearched = query.trim().length > 0; - const isSongDetailRoute = window.location.pathname === '/song-detail' || window.location.pathname === '/song-detail/'; - const isAlbumDetailRoute = window.location.pathname === '/album-detail' || window.location.pathname === '/album-detail/'; - const isArtistDetailRoute = window.location.pathname === '/artist-detail' || window.location.pathname === '/artist-detail/'; + const isSongDetailRoute = currentPath === '/song-detail' || currentPath === '/song-detail/'; + const isAlbumDetailRoute = currentPath === '/album-detail' || currentPath === '/album-detail/'; + const isArtistDetailRoute = currentPath === '/artist-detail' || currentPath === '/artist-detail/'; const dummyAlbumImage = { url: musicWizImage, height: 640, @@ -82,27 +83,37 @@ function App() { total_tracks: 10, uri: 'spotify:album:album-id', }; + useEffect(() => { + const handlePopState = () => setCurrentPath(window.location.pathname); + window.addEventListener('popstate', handlePopState); + + return () => window.removeEventListener('popstate', handlePopState); + }, []); + const navigateTo = (path: string) => { + window.history.pushState({}, '', path); + setCurrentPath(path); + }; const handleSearch = (nextQuery: string) => setQuery(nextQuery); const handleTrackClick = (track: Track) => { setSelectedTrack(track); localStorage.setItem('music-wiz-selected-track', JSON.stringify(track)); - window.history.pushState({}, '', '/song-detail'); + navigateTo('/song-detail'); }; const handleAlbumClick = (album: Album) => { setSelectedAlbum(album); localStorage.setItem('music-wiz-selected-album', JSON.stringify(album)); - window.history.pushState({}, '', '/album-detail'); + navigateTo('/album-detail'); }; const handleAlbumTrackClick = async (track: AlbumTrack) => { const fullTrack = await fetchTrack(track.id); setSelectedTrack(fullTrack); localStorage.setItem('music-wiz-selected-track', JSON.stringify(fullTrack)); - window.history.pushState({}, '', '/song-detail'); + navigateTo('/song-detail'); }; const handleArtistClick = (artist: Artist) => { setSelectedArtist(artist); localStorage.setItem('music-wiz-selected-artist', JSON.stringify(artist)); - window.history.pushState({}, '', '/artist-detail'); + navigateTo('/artist-detail'); }; const { data: results, isLoading, isFetching, isError, error } = useQuery({ queryKey: ['search', query],