A decentralized music tracking and discovery platform built on AT Protocol 🎵

Add genre chart and normalize track types

Add API and hook for fetching genre scrobbles charts and wire it into
ScrobblesAreaChart. Rename Track fields from snake_case to camelCase to
match app conventions and update mapping in useLibrary. Remove an
unnecessary any cast.

+34 -11
+10
apps/web/src/api/charts.ts
··· 44 44 } 45 45 return response.data; 46 46 }; 47 + 48 + export const getGenreChart = async (genre: string) => { 49 + const response = await axios.get( 50 + `${API_URL}/xrpc/app.rocksky.charts.getScrobblesChart?genre=${genre}`, 51 + ); 52 + if (response.status !== 200) { 53 + return []; 54 + } 55 + return response.data; 56 + };
+7 -2
apps/web/src/components/ScrobblesAreaChart/ScrobblesAreaChart.tsx
··· 4 4 import numeral from "numeral"; 5 5 import { useEffect, useState } from "react"; 6 6 import { Area, AreaChart, Tooltip, TooltipProps, XAxis } from "recharts"; 7 - import useChart from "../../hooks/useChart"; 7 + import useChart, { useGenreChartQuery } from "../../hooks/useChart"; 8 8 9 9 const CustomTooltip = ({ 10 10 active, ··· 42 42 location: { pathname }, 43 43 }, 44 44 } = useRouter(); 45 - const { did, rkey } = useParams({ strict: false }); 45 + const { did, rkey, id: genre } = useParams({ strict: false }); 46 46 const [data, setData] = useState< 47 47 { 48 48 date: string; 49 49 count: number; 50 50 }[] 51 51 >([]); 52 + const { data: genreCharts } = useGenreChartQuery(genre!); 53 + 54 + useEffect(() => { 55 + setData(genreCharts); 56 + }, [genreCharts]); 52 57 53 58 useEffect(() => { 54 59 const fetchScrobblesChart = async () => {
+8
apps/web/src/hooks/useChart.tsx
··· 4 4 import { 5 5 getAlbumChart, 6 6 getArtistChart, 7 + getGenreChart, 7 8 getProfileChart, 8 9 getSongChart, 9 10 } from "../api/charts"; ··· 41 42 useQuery({ 42 43 queryKey: ["profileChart", did], 43 44 queryFn: () => getProfileChart(did), 45 + select: (data) => data.scrobbles || [], 46 + }); 47 + 48 + export const useGenreChartQuery = (genre: string) => 49 + useQuery({ 50 + queryKey: ["genreChart", genre], 51 + queryFn: () => getGenreChart(genre), 44 52 select: (data) => data.scrobbles || [], 45 53 }); 46 54
+1 -1
apps/web/src/hooks/useLibrary.tsx
··· 207 207 queryFn: async ({ pageParam = 0 }) => { 208 208 const data = await getTracksByGenre(genre, pageParam * limit, limit); 209 209 return { 210 - tracks: data?.tracks.map((x: any) => ({ 210 + tracks: data?.tracks.map((x) => ({ 211 211 ...x, 212 212 scrobbles: x.playCount, 213 213 })),
+8 -8
apps/web/src/types/track.ts
··· 1 1 export type Track = { 2 2 id: string; 3 3 uri: string; 4 - unique_listeners: number; 5 - play_count: number; 4 + uniqueListeners: number; 5 + playCount: number; 6 6 title: string; 7 7 artist: string; 8 - artist_uri: string; 8 + artistUri: string; 9 9 album: string; 10 - album_uri: string; 11 - album_art: string; 12 - album_artist: string; 13 - copyright_message: string; 14 - disc_number: number; 10 + albumUri: string; 11 + albumArt: string; 12 + albumArtist: string; 13 + copyrightMessage: string; 14 + discNumber: number; 15 15 duration: number; 16 16 sha256: string; 17 17 track_number: number;