refactor(templates): generalize SeriesDetail into reusable BrowseDetail

Replace the single-purpose SeriesDetail template with a parameterized
BrowseDetail component that accepts badge icon/label, title, page title,
back URL/label, empty state icon/message, and book list. Both series
detail and new tag detail pages use the same template with different
params, eliminating duplication.

Series detail: 📚 Series, back to /series, "All Series"
Tag detail: 🏷️ Tag, back to /bookshelf, "Bookshelf"

Deleted series_detail.templ and series_detail_templ.go.
Updated frontend.go series route to call BrowseDetail with series params.
Added /tags/detail route calling BrowseDetail with tag params.
This commit is contained in:
2026-05-10 16:11:29 -04:00
parent 1724bc0767
commit 0acacc1aa0
4 changed files with 277 additions and 137 deletions
+65 -1
View File
@@ -269,7 +269,71 @@ func registerFrontendRoutes(cfg *Config) {
}
var buf bytes.Buffer
err = templates.SeriesDetail(user, seriesName, bookInfoList, libraryID, errorMsg).Render(c.Request().Context(), &buf)
err = templates.BrowseDetail(user, "📚", "Series", seriesName, seriesName, "/series", "All Series", "📚", "This series doesn't have any books in this library yet", bookInfoList, errorMsg).Render(c.Request().Context(), &buf)
if err != nil {
return err
}
return c.HTML(http.StatusOK, buf.String())
})
frontendProtected.GET("/tags/detail", func(c *echo.Context) error {
user, err := getTemplateUserWithTheme(c, cfg)
if err != nil {
return renderErrorPage(c, "Error loading user", "user_load_error")
}
var errorMsg string
tagName := c.QueryParam("name")
if tagName == "" {
return renderErrorPage(c, "Tag name required", "bad_request")
}
libraryID := c.QueryParam("library_id")
userUUID, _ := uuid.Parse(user.ID)
if libraryID == "" {
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
if err == nil && len(libraries) > 0 {
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
libraryID = libUUID.String()
} else {
errorMsg = "No libraries available"
}
}
var bookInfoList []handlers.BookInfo
if libraryID != "" && errorMsg == "" {
libUUID, err := uuid.Parse(libraryID)
if err == nil {
books, err := cfg.Queries.GetBooksByTag(c.Request().Context(), database.GetBooksByTagParams{
LibraryID: uuidToPGType(libUUID),
Column2: tagName,
})
if err != nil {
log.Printf("GetBooksByTag failed: %v", err)
errorMsg = "Error loading tag books"
} else {
bookInfoList = make([]handlers.BookInfo, 0, len(books))
for _, item := range books {
itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16])
bookInfoList = append(bookInfoList, handlers.BookInfo{
MediaItemID: itemUUID.String(),
Title: item.Title,
Author: textToString(item.Author),
CoverImagePath: utils.ResolveMediaURL(item.LibraryID, item.CoverImagePath),
})
}
}
}
}
if bookInfoList == nil {
bookInfoList = []handlers.BookInfo{}
}
var buf bytes.Buffer
err = templates.BrowseDetail(user, "🏷️", "Tag", tagName, tagName, "/bookshelf", "Bookshelf", "🏷️", "No books found with this tag", bookInfoList, errorMsg).Render(c.Request().Context(), &buf)
if err != nil {
return err
}