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).
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
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>
|
||||
}
|
||||
Reference in New Issue
Block a user