feat: author pages with /authors/:id route (0.16.0)
- `GET /v1/authors/:id` returns `AuthorWithCount` (id, name, manga_count). - `GET /v1/authors/:id/mangas` paged works by that author. - `GET /v1/authors?search=` autocomplete (already used by Phase 1 forms; now formally exposed). - New `/authors/:id` page on the frontend; author chips on the manga detail page (added in Phase 1) now link to a real page. - Extracts `lib/components/MangaCard.svelte` — already used by the home page, ready for the collection page in Phase 3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
96
frontend/src/routes/authors/[id]/+page.svelte
Normal file
96
frontend/src/routes/authors/[id]/+page.svelte
Normal file
@@ -0,0 +1,96 @@
|
||||
<script lang="ts">
|
||||
import MangaCard from '$lib/components/MangaCard.svelte';
|
||||
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
||||
|
||||
let { data } = $props();
|
||||
const author = $derived(data.author);
|
||||
const mangas = $derived(data.mangas);
|
||||
const total = $derived(data.total);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{author.name} — Mangalord</title>
|
||||
</svelte:head>
|
||||
|
||||
<nav class="back">
|
||||
<a href="/" class="back-link">
|
||||
<ArrowLeft size={16} aria-hidden="true" />
|
||||
<span>Back to search</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<header class="overview">
|
||||
<h1 data-testid="author-name">{author.name}</h1>
|
||||
<p class="count" data-testid="author-manga-count">
|
||||
{author.manga_count}
|
||||
{author.manga_count === 1 ? 'work' : 'works'}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{#if mangas.length === 0}
|
||||
<p class="status" data-testid="author-no-mangas">
|
||||
No mangas attributed to this author.
|
||||
</p>
|
||||
{:else}
|
||||
{#if total != null}
|
||||
<p class="meta" data-testid="author-shown-of-total">
|
||||
Showing {mangas.length} of {total}
|
||||
</p>
|
||||
{/if}
|
||||
<ul class="manga-grid" data-testid="author-manga-list">
|
||||
{#each mangas as m (m.id)}
|
||||
<MangaCard manga={m} testid={`author-manga-${m.id}`} />
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.back {
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.overview {
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
.overview h1 {
|
||||
margin: 0 0 var(--space-1);
|
||||
}
|
||||
|
||||
.count {
|
||||
color: var(--text-muted);
|
||||
margin: 0 0 var(--space-2);
|
||||
}
|
||||
|
||||
.meta {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-sm);
|
||||
margin: 0 0 var(--space-3);
|
||||
}
|
||||
|
||||
.status {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.manga-grid {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: var(--space-4);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user