fix: better error handling on client

main
Gabe Farrell 2 weeks ago
parent fed2c5b956
commit fdaea6284e

@ -16,60 +16,65 @@ interface getActivityArgs {
track_id: number; track_id: number;
} }
function getLastListens( async function handleJson<T>(r: Response): Promise<T> {
if (!r.ok) {
const err = await r.json();
throw Error(err.error);
}
return (await r.json()) as T;
}
async function getLastListens(
args: getItemsArgs args: getItemsArgs
): Promise<PaginatedResponse<Listen>> { ): Promise<PaginatedResponse<Listen>> {
return fetch( const r = await fetch(
`/apis/web/v1/listens?period=${args.period}&limit=${args.limit}&artist_id=${args.artist_id}&album_id=${args.album_id}&track_id=${args.track_id}&page=${args.page}` `/apis/web/v1/listens?period=${args.period}&limit=${args.limit}&artist_id=${args.artist_id}&album_id=${args.album_id}&track_id=${args.track_id}&page=${args.page}`
).then((r) => r.json() as Promise<PaginatedResponse<Listen>>); );
return handleJson<PaginatedResponse<Listen>>(r);
} }
function getTopTracks(args: getItemsArgs): Promise<PaginatedResponse<Track>> { async function getTopTracks(
if (args.artist_id) { args: getItemsArgs
return fetch( ): Promise<PaginatedResponse<Track>> {
`/apis/web/v1/top-tracks?period=${args.period}&limit=${args.limit}&artist_id=${args.artist_id}&page=${args.page}` let url = `/apis/web/v1/top-tracks?period=${args.period}&limit=${args.limit}&page=${args.page}`;
).then((r) => r.json() as Promise<PaginatedResponse<Track>>);
} else if (args.album_id) { if (args.artist_id) url += `&artist_id=${args.artist_id}`;
return fetch( else if (args.album_id) url += `&album_id=${args.album_id}`;
`/apis/web/v1/top-tracks?period=${args.period}&limit=${args.limit}&album_id=${args.album_id}&page=${args.page}`
).then((r) => r.json() as Promise<PaginatedResponse<Track>>); const r = await fetch(url);
} else { return handleJson<PaginatedResponse<Track>>(r);
return fetch(
`/apis/web/v1/top-tracks?period=${args.period}&limit=${args.limit}&page=${args.page}`
).then((r) => r.json() as Promise<PaginatedResponse<Track>>);
}
} }
function getTopAlbums(args: getItemsArgs): Promise<PaginatedResponse<Album>> { async function getTopAlbums(
const baseUri = `/apis/web/v1/top-albums?period=${args.period}&limit=${args.limit}&page=${args.page}`; args: getItemsArgs
if (args.artist_id) { ): Promise<PaginatedResponse<Album>> {
return fetch(baseUri + `&artist_id=${args.artist_id}`).then( let url = `/apis/web/v1/top-albums?period=${args.period}&limit=${args.limit}&page=${args.page}`;
(r) => r.json() as Promise<PaginatedResponse<Album>> if (args.artist_id) url += `&artist_id=${args.artist_id}`;
);
} else { const r = await fetch(url);
return fetch(baseUri).then( return handleJson<PaginatedResponse<Album>>(r);
(r) => r.json() as Promise<PaginatedResponse<Album>>
);
}
} }
function getTopArtists(args: getItemsArgs): Promise<PaginatedResponse<Artist>> { async function getTopArtists(
const baseUri = `/apis/web/v1/top-artists?period=${args.period}&limit=${args.limit}&page=${args.page}`; args: getItemsArgs
return fetch(baseUri).then( ): Promise<PaginatedResponse<Artist>> {
(r) => r.json() as Promise<PaginatedResponse<Artist>> const url = `/apis/web/v1/top-artists?period=${args.period}&limit=${args.limit}&page=${args.page}`;
); const r = await fetch(url);
return handleJson<PaginatedResponse<Artist>>(r);
} }
function getActivity(args: getActivityArgs): Promise<ListenActivityItem[]> { async function getActivity(
return fetch( args: getActivityArgs
): Promise<ListenActivityItem[]> {
const r = await fetch(
`/apis/web/v1/listen-activity?step=${args.step}&range=${args.range}&month=${args.month}&year=${args.year}&album_id=${args.album_id}&artist_id=${args.artist_id}&track_id=${args.track_id}` `/apis/web/v1/listen-activity?step=${args.step}&range=${args.range}&month=${args.month}&year=${args.year}&album_id=${args.album_id}&artist_id=${args.artist_id}&track_id=${args.track_id}`
).then((r) => r.json() as Promise<ListenActivityItem[]>); );
return handleJson<ListenActivityItem[]>(r);
} }
function getStats(period: string): Promise<Stats> { async function getStats(period: string): Promise<Stats> {
return fetch(`/apis/web/v1/stats?period=${period}`).then( const r = await fetch(`/apis/web/v1/stats?period=${period}`);
(r) => r.json() as Promise<Stats>
); return handleJson<Stats>(r);
} }
function search(q: string): Promise<SearchResponse> { function search(q: string): Promise<SearchResponse> {
@ -416,4 +421,5 @@ export type {
ApiError, ApiError,
Config, Config,
NowPlaying, NowPlaying,
Stats,
}; };

@ -73,8 +73,14 @@ export default function ActivityGrid({
<p>Loading...</p> <p>Loading...</p>
</div> </div>
); );
} else if (isError) {
return (
<div className="w-[500px]">
<h2>Activity</h2>
<p className="error">Error: {error.message}</p>
</div>
);
} }
if (isError) return <p className="error">Error:{error.message}</p>;
// from https://css-tricks.com/snippets/javascript/lighten-darken-color/ // from https://css-tricks.com/snippets/javascript/lighten-darken-color/
function LightenDarkenColor(hex: string, lum: number) { function LightenDarkenColor(hex: string, lum: number) {

@ -1,12 +1,11 @@
import { useQuery } from "@tanstack/react-query" import { useQuery } from "@tanstack/react-query";
import { getStats } from "api/api" import { getStats, type Stats, type ApiError } from "api/api";
export default function AllTimeStats() { export default function AllTimeStats() {
const { isPending, isError, data, error } = useQuery({ const { isPending, isError, data, error } = useQuery({
queryKey: ['stats', 'all_time'], queryKey: ["stats", "all_time"],
queryFn: ({ queryKey }) => getStats(queryKey[1]), queryFn: ({ queryKey }) => getStats(queryKey[1]),
}) });
if (isPending) { if (isPending) {
return ( return (
@ -14,19 +13,31 @@ export default function AllTimeStats() {
<h2>All Time Stats</h2> <h2>All Time Stats</h2>
<p>Loading...</p> <p>Loading...</p>
</div> </div>
) );
} } else if (isError) {
if (isError) { return (
return <p className="error">Error:{error.message}</p> <>
<div>
<h2>All Time Stats</h2>
<p className="error">Error: {error.message}</p>
</div>
</>
);
} }
const numberClasses = 'header-font font-bold text-xl' const numberClasses = "header-font font-bold text-xl";
return ( return (
<div> <div>
<h2>All Time Stats</h2> <h2>All Time Stats</h2>
<div> <div>
<span className={numberClasses} title={data.minutes_listened + " minutes"}>{Math.floor(data.minutes_listened / 60)}</span> Hours Listened <span
className={numberClasses}
title={data.minutes_listened + " minutes"}
>
{Math.floor(data.minutes_listened / 60)}
</span>{" "}
Hours Listened
</div> </div>
<div> <div>
<span className={numberClasses}>{data.listen_count}</span> Plays <span className={numberClasses}>{data.listen_count}</span> Plays
@ -41,5 +52,5 @@ export default function AllTimeStats() {
<span className={numberClasses}>{data.track_count}</span> Tracks <span className={numberClasses}>{data.track_count}</span> Tracks
</div> </div>
</div> </div>
) );
} }

@ -67,12 +67,14 @@ export default function LastPlays(props: Props) {
<p>Loading...</p> <p>Loading...</p>
</div> </div>
); );
} else if (isError) {
return (
<div className="w-[300px] sm:w-[500px]">
<h2>Last Played</h2>
<p className="error">Error: {error.message}</p>
</div>
);
} }
if (isError) {
return <p className="error">Error: {error.message}</p>;
}
if (!data.items) return;
const listens = items ?? data.items; const listens = items ?? data.items;

@ -37,11 +37,14 @@ export default function TopAlbums(props: Props) {
<p>Loading...</p> <p>Loading...</p>
</div> </div>
); );
} else if (isError) {
return (
<div className="w-[300px]">
<h2>Top Albums</h2>
<p className="error">Error: {error.message}</p>
</div>
);
} }
if (isError) {
return <p className="error">Error:{error.message}</p>;
}
if (!data.items) return;
return ( return (
<div> <div>

@ -28,11 +28,14 @@ export default function TopArtists(props: Props) {
<p>Loading...</p> <p>Loading...</p>
</div> </div>
); );
} else if (isError) {
return (
<div className="w-[300px]">
<h2>Top Artists</h2>
<p className="error">Error: {error.message}</p>
</div>
);
} }
if (isError) {
return <p className="error">Error:{error.message}</p>;
}
if (!data.items) return;
return ( return (
<div> <div>

@ -35,9 +35,13 @@ const TopTracks = (props: Props) => {
<p>Loading...</p> <p>Loading...</p>
</div> </div>
); );
} } else if (isError) {
if (isError) { return (
return <p className="error">Error:{error.message}</p>; <div className="w-[300px]">
<h2>Top Tracks</h2>
<p className="error">Error: {error.message}</p>
</div>
);
} }
if (!data.items) return; if (!data.items) return;

Loading…
Cancel
Save