Files
bookhoard/templates/series_detail.templ
T
john-okeefe ddc14e3314 feat(series): add dedicated series detail page instead of bookshelf filter
Create a new /series/detail?name=X&library_id=Y SSR page that shows
all books in a specific series, replacing the broken approach of
linking to /bookshelf?series_filter=X (the bookshelf SSR handler
ignores all filter query params).

The series detail page features:
- Back link to /series browse page
- Library selector dropdown (full page navigation on change)
- Series name header with book count badge
- Book grid using the shared BookCard template
- Empty state for series with no books

Update all links to point to the new page:
- Series cards on /series browse page
- Series badge on book detail page
- JS-rendered cards in series.ts switchLibrary

Add seriesDetailPage Alpine component for the detail page's
library switcher (simple navigation, no AJAX needed).
2026-05-08 20:50:59 -04:00

73 lines
2.7 KiB
Templ

package templates
import (
"bookhoard/internal/handlers"
"fmt"
)
templ SeriesDetail(user User, seriesName string, books []handlers.BookInfo, libData []LibraryData, currentLibraryID string, errorMessage string) {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{ seriesName } - Bookhoard</title>
<link href="/static/style.css" rel="stylesheet"/>
</head>
<body x-data="seriesDetailPage" x-init="initSeriesDetailPage()" class="theme-{ user.Theme }">
@Header(user, "/series")
<!-- Library Selector -->
<div class="sticky top-0 z-40 bg-opacity-95 backdrop-blur border-b" style="background-color: var(--bg-primary);">
<div class="w-full px-4 py-3 flex items-center justify-between">
<div class="flex items-center gap-4">
<a href="/series" class="text-sm hover:opacity-80 transition-opacity" style="color: var(--text-secondary); text-decoration: none;">
All Series
</a>
<span style="color: var(--border);">|</span>
<label class="text-sm font-medium" style="color: var(--text-secondary)">Library:</label>
<select
id="library-select"
name="library_id"
class="px-4 py-2 rounded-lg border focus:ring-2 focus:ring-blue-500"
style="background-color: var(--bg-secondary); color: var(--text-primary);"
>
for _, lib := range libData {
if lib.ID == currentLibraryID {
<option value={ lib.ID } selected>{ lib.Name }</option>
} else {
<option value={ lib.ID }>{ lib.Name }</option>
}
}
</select>
</div>
</div>
</div>
<!-- Series Header -->
<div class="w-full px-4 py-8">
<div class="flex items-center gap-4 mb-6">
<span class="px-3 py-1 rounded-full text-sm font-semibold" style="background-color: var(--accent); color: white;">
📚 Series
</span>
<h1 class="text-3xl font-bold" style="color: var(--text-primary)">{ seriesName }</h1>
<span class="text-sm" style="color: var(--text-secondary)">{ fmt.Sprintf("%d", len(books)) } books</span>
</div>
<!-- Books Grid -->
if len(books) > 0 {
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4">
for _, book := range books {
@BookCard(book)
}
</div>
} else {
<div class="text-center py-16" style="color: var(--text-secondary)">
<div class="text-6xl mb-4">📚</div>
<h3 class="text-xl font-semibold mb-2" style="color: var(--text-primary)">No Books Found</h3>
<p>This series doesn't have any books in this library yet</p>
</div>
}
</div>
@ErrorToast(errorMessage)
</body>
</html>
}