refactor(templates): add error toast support to page templates

Update all page templates to accept an optional error message parameter
and display it via the ErrorToast component when data loading fails.
This commit is contained in:
john-okeefe committed 2026-02-20 13:25:49 -05:00
1 parent 7c652d5a3a
commit 0c515a277c
22 files changed
+198 -142

No files matched your search

+32 -24
View File
@@ -10,7 +10,7 @@ import templruntime "github.com/a-h/templ/runtime"
import "bookhoard/internal/handlers"
func Collection(user User, collections []CollectionData) templ.Component {
func Collection(user User, collections []CollectionData, errorMessage string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -57,7 +57,7 @@ func Collection(user User, collections []CollectionData) templ.Component {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(col.Icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 48, Col: 60}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 48, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
@@ -70,7 +70,7 @@ func Collection(user User, collections []CollectionData) templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(col.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 60, Col: 109}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 60, Col: 109}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -83,7 +83,7 @@ func Collection(user User, collections []CollectionData) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 61, Col: 103}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 61, Col: 103}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -94,7 +94,15 @@ func Collection(user User, collections []CollectionData) templ.Component {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div><div id=\"create-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-md mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Create Collection</h2><button onclick=\"hideCreateModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><form id=\"create-form\" onsubmit=\"handleCreate(event)\"><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Name</label> <input type=\"text\" id=\"collection-name\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"My Reading List\"></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Description</label> <textarea id=\"collection-description\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"Optional description\" rows=\"3\"></textarea></div><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Color</label><div class=\"flex gap-2\"><button type=\"button\" onclick=\"selectColor('#7aa2f7')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #7aa2f7;\"></button> <button type=\"button\" onclick=\"selectColor('#f7768e')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #f7768e;\"></button> <button type=\"button\" onclick=\"selectColor('#e0af68')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #e0af68;\"></button> <button type=\"button\" onclick=\"selectColor('#9ece6a')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #9ece6a;\"></button> <button type=\"button\" onclick=\"selectColor('#7dcfff')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #7dcfff;\"></button> <button type=\"button\" onclick=\"selectColor('#bb9af7')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #bb9af7;\"></button></div><input type=\"hidden\" id=\"collection-color\" value=\"#7aa2f7\"></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideCreateModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Cancel</button> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded-lg\">Create Collection</button></div></form></div></div><script>\n let selectedColor = '#7aa2f7';\n\n function showCreateModal() {\n document.getElementById('create-modal').classList.remove('hidden');\n }\n\n function hideCreateModal() {\n document.getElementById('create-modal').classList.add('hidden');\n document.getElementById('create-form').reset();\n selectedColor = '#7aa2f7';\n updateColorSelection();\n }\n\n function selectColor(color) {\n selectedColor = color;\n document.getElementById('collection-color').value = color;\n updateColorSelection();\n }\n\n function updateColorSelection() {\n document.querySelectorAll('.color-option').forEach(btn => {\n btn.style.outline = 'none';\n btn.style.outlineOffset = '0';\n });\n const selectedBtn = document.querySelector(`.color-option[onclick=\"selectColor('${selectedColor}')\"]`);\n if (selectedBtn) {\n selectedBtn.style.outline = '3px solid var(--text-primary)';\n selectedBtn.style.outlineOffset = '3px';\n }\n }\n\n function handleCreate(event) {\n event.preventDefault();\n\n const data = {\n name: document.getElementById('collection-name').value,\n description: document.getElementById('collection-description').value,\n color: selectedColor,\n icon: '📚'\n };\n\n fetch('/api/collections', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + localStorage.getItem('token')\n },\n body: JSON.stringify(data)\n })\n .then(response => response.json())\n .then(result => {\n showToast('Collection creaLine truncated
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div><div id=\"create-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-md mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Create Collection</h2><button onclick=\"hideCreateModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><form id=\"create-form\" onsubmit=\"handleCreate(event)\"><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Name</label> <input type=\"text\" id=\"collection-name\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"My Reading List\"></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Description</label> <textarea id=\"collection-description\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"Optional description\" rows=\"3\"></textarea></div><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Color</label><div class=\"flex gap-2\"><button type=\"button\" onclick=\"selectColor('#7aa2f7')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #7aa2f7;\"></button> <button type=\"button\" onclick=\"selectColor('#f7768e')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #f7768e;\"></button> <button type=\"button\" onclick=\"selectColor('#e0af68')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #e0af68;\"></button> <button type=\"button\" onclick=\"selectColor('#9ece6a')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #9ece6a;\"></button> <button type=\"button\" onclick=\"selectColor('#7dcfff')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #7dcfff;\"></button> <button type=\"button\" onclick=\"selectColor('#bb9af7')\" class=\"w-8 h-8 rounded-full color-option\" style=\"background-color: #bb9af7;\"></button></div><input type=\"hidden\" id=\"collection-color\" value=\"#7aa2f7\"></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideCreateModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Cancel</button> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded-lg\">Create Collection</button></div></form></div></div><script>\n let selectedColor = '#7aa2f7';\n\n function showCreateModal() {\n document.getElementById('create-modal').classList.remove('hidden');\n }\n\n function hideCreateModal() {\n document.getElementById('create-modal').classList.add('hidden');\n document.getElementById('create-form').reset();\n selectedColor = '#7aa2f7';\n updateColorSelection();\n }\n\n function selectColor(color) {\n selectedColor = color;\n document.getElementById('collection-color').value = color;\n updateColorSelection();\n }\n\n function updateColorSelection() {\n document.querySelectorAll('.color-option').forEach(btn => {\n btn.style.outline = 'none';\n btn.style.outlineOffset = '0';\n });\n const selectedBtn = document.querySelector(`.color-option[onclick=\"selectColor('${selectedColor}')\"]`);\n if (selectedBtn) {\n selectedBtn.style.outline = '3px solid var(--text-primary)';\n selectedBtn.style.outlineOffset = '3px';\n }\n }\n\n function handleCreate(event) {\n event.preventDefault();\n\n const data = {\n name: document.getElementById('collection-name').value,\n description: document.getElementById('collection-description').value,\n color: selectedColor,\n icon: '📚'\n };\n\n fetch('/api/collections', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + localStorage.getItem('token')\n },\n body: JSON.stringify(data)\n })\n .then(response => response.json())\n .then(result => {\n showToast('Collection creaLine truncated
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = ErrorToast(errorMessage).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -123,20 +131,20 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 221, Col: 32}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 223, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/toast.js\"></script><script src=\"/static/header.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body class=\"theme-{ user.Theme }\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/toast.js\"></script><script src=\"/static/header.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body class=\"theme-{ user.Theme }\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -144,98 +152,98 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8\"><div class=\"mb-6\"><button onclick=\"backToCollections()\" class=\"btn-secondary px-4 py-2 rounded-lg mb-4\">← Back to Collections</button><div class=\"flex items-center gap-4\"><div class=\"text-4xl\" style=\"color: { collection.Color }\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8\"><div class=\"mb-6\"><button onclick=\"backToCollections()\" class=\"btn-secondary px-4 py-2 rounded-lg mb-4\">← Back to Collections</button><div class=\"flex items-center gap-4\"><div class=\"text-4xl\" style=\"color: { collection.Color }\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 236, Col: 95}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 238, Col: 95}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div><div><h1 class=\"text-3xl font-bold\" style=\"color: var(--text-primary)\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div><div><h1 class=\"text-3xl font-bold\" style=\"color: var(--text-primary)\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 238, Col: 107}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 240, Col: 107}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</h1><p style=\"color: var(--text-secondary)\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</h1><p style=\"color: var(--text-secondary)\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 239, Col: 88}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 241, Col: 88}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</p></div></div></div><div class=\"mb-6 flex justify-between items-center\"><div class=\"flex items-center gap-4\"><h2 class=\"text-xl font-semibold\" style=\"color: var(--text-primary)\">Books in this Collection</h2><span id=\"selected-count\" class=\"hidden px-3 py-1 text-sm rounded\" style=\"background-color: var(--accent); color: var(--bg-primary);\">0 selected</span></div><div class=\"flex gap-3\"><div class=\"flex-1 max-w-md\"><input type=\"text\" id=\"collection-search\" placeholder=\"Search within collection...\" onkeyup=\"filterCollectionBooks()\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div><button id=\"bulk-remove-btn\" onclick=\"removeSelectedBooks()\" disabled class=\"btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed\">🗑️ Remove Selected</button> <button onclick=\"showAddBooksModal()\" class=\"btn-primary px-4 py-2 rounded-lg\">➕ Add Books</button></div></div><div id=\"books-container\" class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</p></div></div></div><div class=\"mb-6 flex justify-between items-center\"><div class=\"flex items-center gap-4\"><h2 class=\"text-xl font-semibold\" style=\"color: var(--text-primary)\">Books in this Collection</h2><span id=\"selected-count\" class=\"hidden px-3 py-1 text-sm rounded\" style=\"background-color: var(--accent); color: var(--bg-primary);\">0 selected</span></div><div class=\"flex gap-3\"><div class=\"flex-1 max-w-md\"><input type=\"text\" id=\"collection-search\" placeholder=\"Search within collection...\" onkeyup=\"filterCollectionBooks()\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div><button id=\"bulk-remove-btn\" onclick=\"removeSelectedBooks()\" disabled class=\"btn-danger px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed\">🗑️ Remove Selected</button> <button onclick=\"showAddBooksModal()\" class=\"btn-primary px-4 py-2 rounded-lg\">➕ Add Books</button></div></div><div id=\"books-container\" class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(books) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div id=\"empty-state\" class=\"col-span-full text-center py-16\" style=\"color: var(--text-secondary)\">No books in this collection yet.</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div id=\"empty-state\" class=\"col-span-full text-center py-16\" style=\"color: var(--text-secondary)\">No books in this collection yet.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
for _, book := range books {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div class=\"card p-4 rounded-lg border hover:shadow-lg transition-shadow\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex items-start gap-4\"><input type=\"checkbox\" onchange=\"toggleBookForRemoval('{ book.MediaItemID }')\" class=\"w-5 h-5 mt-2\"><div class=\"aspect-w-3 aspect-h-4 flex-shrink-0 w-24 mb-3 overflow-hidden rounded\"><img src=\"{ book.CoverImagePath }\" alt=\"Cover\" class=\"w-full h-32 object-cover rounded\" onerror=\"this.src='/static/placeholder-book.svg'\"></div><div class=\"flex-1\"><h3 class=\"font-semibold text-lg mb-1 line-clamp-2\" style=\"color: var(--text-primary)\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"card p-4 rounded-lg border hover:shadow-lg transition-shadow\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex items-start gap-4\"><input type=\"checkbox\" onchange=\"toggleBookForRemoval('{ book.MediaItemID }')\" class=\"w-5 h-5 mt-2\"><div class=\"aspect-w-3 aspect-h-4 flex-shrink-0 w-24 mb-3 overflow-hidden rounded\"><img src=\"{ book.CoverImagePath }\" alt=\"Cover\" class=\"w-full h-32 object-cover rounded\" onerror=\"this.src='/static/placeholder-book.svg'\"></div><div class=\"flex-1\"><h3 class=\"font-semibold text-lg mb-1 line-clamp-2\" style=\"color: var(--text-primary)\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 286, Col: 131}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 288, Col: 131}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</h3>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if book.Author != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<p class=\"text-sm\" style=\"color: var(--text-secondary)\">by ")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<p class=\"text-sm\" style=\"color: var(--text-secondary)\">by ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(book.Author)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `collections.templ`, Line: 288, Col: 108}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 290, Col: 108}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</p>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<button onclick=\"removeBook('{ book.MediaItemID }')\" class=\"mt-2 px-3 py-1 text-sm border rounded hover:opacity-80\" style=\"border-color: var(--border); color: var(--text-secondary);\">Remove</button></div></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<button onclick=\"removeBook('{ book.MediaItemID }')\" class=\"mt-2 px-3 py-1 text-sm border rounded hover:opacity-80\" style=\"border-color: var(--border); color: var(--text-secondary);\">Remove</button></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div></div><div id=\"add-books-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-2xl mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Add Books to Collection</h2><button onclick=\"hideAddBooksModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><p class=\"mb-4\" style=\"color: var(--text-secondary)\">Search and select books to add to this collection.</p><div class=\"mb-4\"><input type=\"text\" id=\"book-search\" placeholder=\"Search books...\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div><div id=\"book-results\" class=\"max-h-64 overflow-y-auto mb-4\"></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideAddBooksModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Cancel</button> <button type=\"button\" onclick=\"addSelectedBooks()\" class=\"btn-primary px-4 py-2 rounded-lg\">Add Selected Books</button></div></div></div><script>\n let collectionId = '{ collection.ID }';\n let selectedBooks = new Set();\n let booksToRemove = new Set();\n let allBooks = [\n { range books }{\n {\n id: '{ book.MediaItemID }',\n title: '{ book.Title }',\n author: '{ book.Author }',\n cover: '{ book.CoverImagePath }'\n },\n }{ end }\n ];\n let ws = null;\n\n function backToCollections() {\n window.location.href = '/collections';\n }\n\n function connectWebSocket() {\n const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n const wsUrl = `${protocol}//${window.location.host}/ws/sync`;\n\n ws = new WebSocket(wsUrl);\n\n ws.onopen = function() {\n console.log('WebSocket connected');\n };\n\n ws.onmessage = function(event) {\n const message = JSON.parse(event.data);\n\n if (message.type === 'collection_updated' && message.data.collection_id === collectionId) {\n showToast(`Collection updated: ${message.data.action} (${message.data.count} books)`, 'info');\n\n setTimeout(() => {\n location.reload();\n }, 1000);\n }\n };\n\n ws.onclose = function() {\n console.log('WebSocket disconnected, reconnecting in 5s...');\n setTimeout(connectWebSocket, 5000);\n };\n\n ws.onerror = function(error) {\n console.error('WebSocket error:', error);\n };\n }\n\n connectWebSocket();\n\n function filterCollectionBooks() {\n const searchTerm = document.getElementById('collection-search').value.toLowerCase();\n const booksContainer = document.getElementById('books-container');\n const bookCards = booksContainer.children;\n\n let visibleCount = 0;\n for (let i = 0; i < bookCards.length; i++) {\n const card = bookCards[i];\n if (card.id === 'empty-state') continue;\n\n const title = card.querySelector('.font-semibold')?.textContent.toLowerCase() || '';\n const author = card.querySelector('.text-sm')?.textContent.toLowerCase() || '';\n\n const matches = title.includes(searchTerm) || author.includes(searchTerm);\n\n if (matches || searchTerm === '') {\n card.style.display = '';\n visibleCount++;\n } else {\n card.style.display = 'none';\n }\n }\n }\n\n function showAddBooksModal() {\n document.getElementById('add-books-modal').classList.remove('hidden');\n selectedBooks.clear();\n document.getElementById('book-results').innerHTML = '<p class=\"text-sm\" style=\"color: var(--text-secondary)\">Enter at least 2 characters to search.</p>';\n }\n\n function hideAddBooksModal() {\n document.getElementById('add-books-modal').classList.add('hidden');\n document.getElementById('book-search').value =Line truncated
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</div></div><div id=\"add-books-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-2xl mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Add Books to Collection</h2><button onclick=\"hideAddBooksModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><p class=\"mb-4\" style=\"color: var(--text-secondary)\">Search and select books to add to this collection.</p><div class=\"mb-4\"><input type=\"text\" id=\"book-search\" placeholder=\"Search books...\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div><div id=\"book-results\" class=\"max-h-64 overflow-y-auto mb-4\"></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideAddBooksModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Cancel</button> <button type=\"button\" onclick=\"addSelectedBooks()\" class=\"btn-primary px-4 py-2 rounded-lg\">Add Selected Books</button></div></div></div><script>\n let collectionId = '{ collection.ID }';\n let selectedBooks = new Set();\n let booksToRemove = new Set();\n let allBooks = [\n { range books }{\n {\n id: '{ book.MediaItemID }',\n title: '{ book.Title }',\n author: '{ book.Author }',\n cover: '{ book.CoverImagePath }'\n },\n }{ end }\n ];\n let ws = null;\n\n function backToCollections() {\n window.location.href = '/collections';\n }\n\n function connectWebSocket() {\n const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n const wsUrl = `${protocol}//${window.location.host}/ws/sync`;\n\n ws = new WebSocket(wsUrl);\n\n ws.onopen = function() {\n console.log('WebSocket connected');\n };\n\n ws.onmessage = function(event) {\n const message = JSON.parse(event.data);\n\n if (message.type === 'collection_updated' && message.data.collection_id === collectionId) {\n showToast(`Collection updated: ${message.data.action} (${message.data.count} books)`, 'info');\n\n setTimeout(() => {\n location.reload();\n }, 1000);\n }\n };\n\n ws.onclose = function() {\n console.log('WebSocket disconnected, reconnecting in 5s...');\n setTimeout(connectWebSocket, 5000);\n };\n\n ws.onerror = function(error) {\n console.error('WebSocket error:', error);\n };\n }\n\n connectWebSocket();\n\n function filterCollectionBooks() {\n const searchTerm = document.getElementById('collection-search').value.toLowerCase();\n const booksContainer = document.getElementById('books-container');\n const bookCards = booksContainer.children;\n\n let visibleCount = 0;\n for (let i = 0; i < bookCards.length; i++) {\n const card = bookCards[i];\n if (card.id === 'empty-state') continue;\n\n const title = card.querySelector('.font-semibold')?.textContent.toLowerCase() || '';\n const author = card.querySelector('.text-sm')?.textContent.toLowerCase() || '';\n\n const matches = title.includes(searchTerm) || author.includes(searchTerm);\n\n if (matches || searchTerm === '') {\n card.style.display = '';\n visibleCount++;\n } else {\n card.style.display = 'none';\n }\n }\n }\n\n function showAddBooksModal() {\n document.getElementById('add-books-modal').classList.remove('hidden');\n selectedBooks.clear();\n document.getElementById('book-results').innerHTML = '<p class=\"text-sm\" style=\"color: var(--text-secondary)\">Enter at least 2 characters to search.</p>';\n }\n\n function hideAddBooksModal() {\n document.getElementById('add-books-modal').classList.add('hidden');\n document.getElementById('book-search').value =Line truncated
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}