feat(web): add artist detail page at /artists/:id
Renders the artist name + album count and a responsive AlbumCard grid from the nested ArtistDetail response. 404 surfaces a distinct non-retryable 'not found' state; other errors share the retry banner.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { createArtistQuery } from '$lib/api/queries';
|
||||
import AlbumCard from '$lib/components/AlbumCard.svelte';
|
||||
import LibrarySkeleton from '$lib/components/LibrarySkeleton.svelte';
|
||||
import ApiErrorBanner from '$lib/components/ApiErrorBanner.svelte';
|
||||
import { useDelayed } from '$lib/utils/useDelayed.svelte';
|
||||
import type { ApiError } from '$lib/api/client';
|
||||
|
||||
const id = $derived(page.params.id ?? '');
|
||||
const queryStore = $derived(createArtistQuery(id));
|
||||
const query = $derived($queryStore);
|
||||
const showSkeleton = useDelayed(() => query.isPending);
|
||||
|
||||
const notFound = $derived(
|
||||
query.isError && (query.error as unknown as ApiError | undefined)?.code === 'not_found'
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<a href="/" class="text-sm text-text-secondary hover:text-text-primary">← Library</a>
|
||||
|
||||
{#if notFound}
|
||||
<div role="alert" class="rounded border border-border bg-surface p-4">
|
||||
<p>Artist not found.</p>
|
||||
<a href="/" class="mt-2 inline-block text-sm text-accent">Back to Library</a>
|
||||
</div>
|
||||
{:else if query.isError}
|
||||
<ApiErrorBanner error={query.error} onRetry={query.refetch} />
|
||||
{:else if showSkeleton.value && !query.data}
|
||||
<LibrarySkeleton variant="grid" count={12} />
|
||||
{:else if query.data}
|
||||
{@const detail = query.data}
|
||||
<header>
|
||||
<h1 class="text-2xl font-semibold">{detail.name}</h1>
|
||||
<p class="text-sm text-text-secondary">
|
||||
{detail.album_count} {detail.album_count === 1 ? 'album' : 'albums'}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{#if detail.albums.length === 0}
|
||||
<p class="text-text-secondary">This artist has no albums in the library.</p>
|
||||
{:else}
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
||||
{#each detail.albums as album (album.id)}
|
||||
<AlbumCard {album} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user