chore: Remove obsolete documentation and regenerate template

Remove outdated migration documentation and regenerate template after
script tag cleanup.

Changes:
- Delete ECHO_V5_MIGRATION.md: Obsolete migration plan, superseded by
  ESBUILD_MIGRATION_PLAN.md
- Delete esbuild-setup.md: Incomplete setup document, replaced by
  comprehensive migration plan
- Regenerate templates/collections_templ.go: Remove collections.js
  script tag (now using main.js bundle)

Template update:
- Removed <script src="/static/collections.js"> from template
- Now uses single main.js bundle (ESBuild output)
- Line number adjustments in generated Go code

Cleanup of obsolete documentation as part of ESBuild migration.
This commit is contained in:
2026-03-08 01:14:48 -05:00
parent 9947a12f09
commit a84ffb253e
3 changed files with 16 additions and 2307 deletions
-338
View File
@@ -1,338 +0,0 @@
# Echo v5 Migration Plan
## Overview
This document outlines the steps required to migrate from Echo v4 to Echo v5, including API changes, type signature updates, and middleware modifications.
## Errors Identified
### 1. Deprecated Middleware
**Error:** `echomiddleware.Logger undefined`
**Solution:** Replace `echomiddleware.Logger()` with `echomiddleware.RequestLogger()`
**Files affected:**
- `cmd/server/main.go` (line 142)
- `internal/router/router.go` (line 144)
### 2. Removed API Methods
**Error:** `a.echo.Close undefined (type *echo.Echo has no field or method Close)`
**Solution:** Remove the `echo.Close()` call as v5 uses different graceful shutdown mechanism
**Files affected:**
- `internal/app/app.go` (line 86)
### 3. Type Signature & Response API Changes
**Error:** Multiple type mismatches and Response API changes in Echo v5
**Root Cause:** Echo v5 uses `*echo.Context` (pointer) for handlers but some code uses `echo.Context` (value). Response wrapper API also changed.
**Solution:**
- Update all middleware to use `*echo.Context`
- Update helper functions to accept `*echo.Context`
- Fix Response wrapper usage for Echo v5 API
**Files affected:**
- `internal/middleware/device_auth.go` (lines 38, 170, 212)
- `internal/middleware/error_handler.go` (lines 44, 69, 82, 84, 87, 89)
- `internal/middleware/rate_limiter.go` (line 102)
- `internal/middleware/request_tracing.go` (lines 48, 58, 60)
- `internal/middleware/security.go` (line 14)
---
## Step-by-Step Fixes
### Step 1: Fix Deprecated Logger Middleware
#### File: `cmd/server/main.go`
**Line 142:**
```go
// BEFORE:
e.Use(echomiddleware.Logger())
// AFTER:
e.Use(echomiddleware.RequestLogger())
```
#### File: `internal/router/router.go`
**Line ~144:**
```go
// BEFORE:
e.Use(echomiddleware.Logger())
// AFTER:
e.Use(echomiddleware.RequestLogger())
```
---
### Step 2: Fix Removed API Methods
#### File: `internal/app/app.go`
**Lines 26-32 - Initialize server field:**
```go
// REPLACE lines 26-32 with:
func New(echo *echo.Echo) *App {
return &App{
echo: echo,
server: nil, // Will be set in StartServer()
shutdownTimeout: 30 * time.Second,
shutdownDone: make(chan struct{}),
}
}
```
**Add StartServer method after New() (after line 32):**
```go
// StartServer creates HTTP server and starts listening
func (a *App) StartServer(addr string) error {
a.server = &http.Server{
Addr: addr,
Handler: a.echo,
}
// Start HTTP server in background
go func() {
if err := a.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed to start: %v", err)
}
}()
return nil
}
```
**Lines 82-94 - Replace goroutine in Shutdown():**
```go
// REPLACE lines 82-94 with:
// Stop accepting new connections and shutdown HTTP server
log.Println("Stopping HTTP server...")
if a.server != nil {
ctx, cancel := context.WithTimeout(context.Background(), a.shutdownTimeout)
defer cancel()
if err := a.server.Shutdown(ctx); err != nil {
log.Printf("Error stopping HTTP server: %v", err)
}
}
log.Println("All services stopped")
```
#### File: `cmd/server/main.go`
**Lines 197-214 - Replace server startup:**
```go
// REPLACE lines 197-214 with:
// ========================================================================
// START SERVER (managed by app lifecycle)
// ========================================================================
log.Printf("Starting server on port %s", cfg.ServerPort)
// Start HTTP server
if err := application.StartServer(":" + cfg.ServerPort); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
// Start application lifecycle (blocks until shutdown signal)
if err := application.Start(); err != nil {
log.Fatalf("Application error: %v", err)
}
```
---
### Step 3: Fix Middleware Type Signatures
#### Pattern for Echo v5 Middleware
**Echo v5 Middleware Pattern:**
```go
func MiddlewareFunc(next echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error { // ← POINTER (required in v5)
// ... middleware logic
return next(c) // ← c is already a pointer, no & needed
}
}
```
#### File: `internal/middleware/device_auth.go`
**Line 38:**
```go
// BEFORE:
return func(c echo.Context) error {
// AFTER:
return func(c *echo.Context) error {
```
**Line 170:**
```go
// BEFORE:
return func(c echo.Context) error {
// AFTER:
return func(c *echo.Context) error {
```
**Line 212:**
```go
// BEFORE:
return func(c echo.Context) error {
// AFTER:
return func(c *echo.Context) error {
```
#### File: `internal/middleware/error_handler.go`
**Line 44:**
```go
// BEFORE:
func RespondWithError(c echo.Context, code int, message string, err error) error {
// AFTER:
func RespondWithError(c *echo.Context, code int, message string, err error) error {
```
**Line 69:**
```go
// BEFORE:
func RespondWithHTTPError(c echo.Context, httpErr *HTTPError) error {
// AFTER:
func RespondWithHTTPError(c *echo.Context, httpErr *HTTPError) error {
```
**Line 82:**
```go
// BEFORE:
func WrapHandler(fn func(c echo.Context) error) echo.HandlerFunc {
return func(c *echo.Context) error {
err := fn(c)
// AFTER:
func WrapHandler(fn func(*echo.Context) error) echo.HandlerFunc {
return func(c *echo.Context) error {
err := fn(c) // c is already *echo.Context
```
**Lines 84, 87, 89:** No change - calls to helpers will now work with updated signatures
#### File: `internal/middleware/rate_limiter.go`
**Line 102:**
```go
// BEFORE:
return func(c echo.Context) error {
// AFTER:
return func(c *echo.Context) error {
```
#### File: `internal/middleware/request_tracing.go`
**Line 48:**
```go
// BEFORE:
return func(c echo.Context) error {
// AFTER:
return func(c *echo.Context) error {
```
**Lines 57-61:**
```go
// BEFORE:
recorder := &responseWriter{
ResponseWriter: c.Response().Writer,
}
c.Response().Writer = recorder
// AFTER:
recorder := &responseWriter{
ResponseWriter: *c.Response(), // Dereference: *echo.Response -> http.ResponseWriter
}
```
**Note:** `c.Response().Status` (line 108) should work - Status field exists in Echo v5
#### File: `internal/middleware/security.go`
**Line 14:**
```go
// BEFORE:
return func(c echo.Context) error {
// AFTER:
return func(c *echo.Context) error {
```
---
## WebSocket Fix
After Echo v5 migration, the WebSocket handler should now work natively:
### File: `internal/handlers/websocket.go`
The existing code should now work:
```go
ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
```
This previously failed with "response does not implement http.Hijacker" but Echo v5's Response now supports the `rwUnwrapper` interface needed for WebSocket upgrades.
---
## Verification
After making all changes:
1. **Build the project:**
```bash
go build ./cmd/server
```
2. **Run tests:**
```bash
go test ./cmd/server/tests/... -v
```
3. **Test WebSocket manually:**
- Get JWT token: `curl -X POST http://localhost:8765/api/auth/login -H "Content-Type: application/json" -d '{"login":"testuser@tests.bookhoard.internal","password":"Test@Pass123!"}' | jq -r '.access_token'`
- Connect in browser console: `new WebSocket('ws://localhost:8765/ws/sync?token=YOUR_TOKEN')`
---
## Rollback Plan
If Echo v5 migration fails:
1. Revert go.mod changes:
```go
// Change back to v4:
github.com/labstack/echo/v4 v4.15.1
github.com/labstack/echo-jwt/v4 v4.4.0
```
2. Restore import statements in all modified files
3. Revert all code changes
---
## References
- [Echo v5 Changelog](https://github.com/labstack/echo/blob/master/CHANGELOG.md)
- [Echo v5 Middleware Documentation](https://echo.labstack.com/docs/middleware)
- [Echo WebSocket Cookbook](https://echo.labstack.com/docs/cookbook/websocket)
-1953
View File
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -31,7 +31,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Collections - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/toast.js\"></script><script src=\"/static/collections.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body class=\"theme-{ user.Theme }\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Collections - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/toast.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body class=\"theme-{ user.Theme }\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -57,7 +57,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var2 string var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID) templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 64, Col: 84} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 63, Col: 84}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -70,7 +70,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(col.Color) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(col.Color)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 68, Col: 30} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 67, Col: 30}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -83,7 +83,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Icon) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Icon)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 71, Col: 41} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 70, Col: 41}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -96,7 +96,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID + "/edit-modal") templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("/collections/" + col.ID + "/edit-modal")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 74, Col: 60} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 73, Col: 60}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -109,7 +109,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var6 string var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("/api/collections/" + col.ID + "") templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("/api/collections/" + col.ID + "")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 83, Col: 56} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 82, Col: 56}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -122,7 +122,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(col.Name) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(col.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 93, Col: 92} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 92, Col: 92}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -135,7 +135,7 @@ func Collection(user User, collections []CollectionData, errorMessage string) te
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(col.Description) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(col.Description)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 94, Col: 86} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 93, Col: 86}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -190,7 +190,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var10 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 111, Col: 27} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 110, Col: 27}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -211,7 +211,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var11 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 124, Col: 81} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 123, Col: 81}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -224,7 +224,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var12 string var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name) templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 126, Col: 90} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 125, Col: 90}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -237,13 +237,13 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var13 string var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Description) templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Description)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 127, Col: 71} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 126, Col: 71}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</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, 19, "</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=\"removebooksToAdd()\" 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -261,7 +261,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var14 string var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title) templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(book.Title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 185, Col: 22} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 184, Col: 22}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -279,7 +279,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var15 string var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(book.Author) templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(book.Author)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 192, Col: 27} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 191, Col: 27}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -302,7 +302,7 @@ func CollectionDetail(user User, collection CollectionData, books []handlers.Boo
var templ_7745c5c3_Var16 string var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(book.CoverImagePath) templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(book.CoverImagePath)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 200, Col: 36} return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collections.templ`, Line: 199, Col: 36}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {