feat(web): add /library/liked page with three sections
Each section is its own infinite query against /api/likes/{type}.
Empty sections show 'No liked X yet' (gated on total === 0). Reuses
ArtistRow / AlbumCard / TrackRow for rendering — same components as
the search overflow pages.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
createLikedTracksInfiniteQuery,
|
||||
createLikedAlbumsInfiniteQuery,
|
||||
createLikedArtistsInfiniteQuery
|
||||
} from '$lib/api/likes';
|
||||
import ArtistRow from '$lib/components/ArtistRow.svelte';
|
||||
import AlbumCard from '$lib/components/AlbumCard.svelte';
|
||||
import TrackRow from '$lib/components/TrackRow.svelte';
|
||||
|
||||
const artistsStore = $derived(createLikedArtistsInfiniteQuery());
|
||||
const albumsStore = $derived(createLikedAlbumsInfiniteQuery());
|
||||
const tracksStore = $derived(createLikedTracksInfiniteQuery());
|
||||
|
||||
const artistsQuery = $derived($artistsStore);
|
||||
const albumsQuery = $derived($albumsStore);
|
||||
const tracksQuery = $derived($tracksStore);
|
||||
|
||||
const artists = $derived(artistsQuery?.data?.pages?.flatMap((p) => p.items) ?? []);
|
||||
const albums = $derived(albumsQuery?.data?.pages?.flatMap((p) => p.items) ?? []);
|
||||
const tracks = $derived(tracksQuery?.data?.pages?.flatMap((p) => p.items) ?? []);
|
||||
|
||||
const artistsTotal = $derived(artistsQuery?.data?.pages?.[0]?.total ?? 0);
|
||||
const albumsTotal = $derived(albumsQuery?.data?.pages?.[0]?.total ?? 0);
|
||||
const tracksTotal = $derived(tracksQuery?.data?.pages?.[0]?.total ?? 0);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<h1 class="text-2xl font-semibold">Liked</h1>
|
||||
|
||||
<section>
|
||||
<h2 class="mb-2 text-lg font-semibold">Artists</h2>
|
||||
{#if artistsTotal === 0}
|
||||
<p class="text-text-secondary">No liked artists yet.</p>
|
||||
{:else}
|
||||
<div>
|
||||
{#each artists as a (a.id)}
|
||||
<ArtistRow artist={a} />
|
||||
{/each}
|
||||
</div>
|
||||
{#if artistsQuery?.hasNextPage}
|
||||
<button
|
||||
type="button"
|
||||
class="mt-2 w-full rounded bg-surface py-2 text-sm hover:bg-surface-hover disabled:opacity-60"
|
||||
disabled={artistsQuery.isFetchingNextPage}
|
||||
onclick={() => artistsQuery.fetchNextPage()}
|
||||
>
|
||||
{artistsQuery.isFetchingNextPage ? 'Loading…' : 'Load more'}
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="mb-2 text-lg font-semibold">Albums</h2>
|
||||
{#if albumsTotal === 0}
|
||||
<p class="text-text-secondary">No liked albums yet.</p>
|
||||
{:else}
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
||||
{#each albums as al (al.id)}
|
||||
<AlbumCard album={al} />
|
||||
{/each}
|
||||
</div>
|
||||
{#if albumsQuery?.hasNextPage}
|
||||
<button
|
||||
type="button"
|
||||
class="mt-2 w-full rounded bg-surface py-2 text-sm hover:bg-surface-hover disabled:opacity-60"
|
||||
disabled={albumsQuery.isFetchingNextPage}
|
||||
onclick={() => albumsQuery.fetchNextPage()}
|
||||
>
|
||||
{albumsQuery.isFetchingNextPage ? 'Loading…' : 'Load more'}
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="mb-2 text-lg font-semibold">Tracks</h2>
|
||||
{#if tracksTotal === 0}
|
||||
<p class="text-text-secondary">No liked tracks yet.</p>
|
||||
{:else}
|
||||
<div class="overflow-hidden rounded border border-border">
|
||||
{#each tracks as t, i (t.id)}
|
||||
<TrackRow tracks={tracks} index={i} />
|
||||
{/each}
|
||||
</div>
|
||||
{#if tracksQuery?.hasNextPage}
|
||||
<button
|
||||
type="button"
|
||||
class="mt-2 w-full rounded bg-surface py-2 text-sm hover:bg-surface-hover disabled:opacity-60"
|
||||
disabled={tracksQuery.isFetchingNextPage}
|
||||
onclick={() => tracksQuery.fetchNextPage()}
|
||||
>
|
||||
{tracksQuery.isFetchingNextPage ? 'Loading…' : 'Load more'}
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
</div>
|
||||
Reference in New Issue
Block a user