From 0edc868888ce75db8b5fefed2cf60b5498598300 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Mon, 6 Apr 2026 07:03:30 +0200 Subject: [PATCH] feat: add watchlist management and layouts --- internal/handlers/watchlist.go | 160 ++++++++ internal/templates/anime.templ | 141 +++++++ internal/templates/anime_templ.go | 490 ++++++++++++++++++++++ internal/templates/watchlist.templ | 100 +++++ internal/templates/watchlist_templ.go | 559 ++++++++++++++++++++++++++ 5 files changed, 1450 insertions(+) create mode 100644 internal/handlers/watchlist.go create mode 100644 internal/templates/anime.templ create mode 100644 internal/templates/anime_templ.go create mode 100644 internal/templates/watchlist.templ create mode 100644 internal/templates/watchlist_templ.go diff --git a/internal/handlers/watchlist.go b/internal/handlers/watchlist.go new file mode 100644 index 0000000..34dd021 --- /dev/null +++ b/internal/handlers/watchlist.go @@ -0,0 +1,160 @@ +package handlers + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/google/uuid" + + "malago/internal/database" + "malago/internal/middleware" + "malago/internal/templates" +) + +type WatchlistHandler struct { + db database.Querier +} + +func NewWatchlistHandler(db database.Querier) *WatchlistHandler { + return &WatchlistHandler{db: db} +} + +func (h *WatchlistHandler) HandleUpdateWatchlist(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + user, ok := r.Context().Value(middleware.UserContextKey).(*database.User) + if !ok || user == nil { + w.Header().Set("HX-Redirect", "/login") + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + if err := r.ParseForm(); err != nil { + http.Error(w, "invalid form", http.StatusBadRequest) + return + } + + animeIDStr := r.FormValue("anime_id") + animeTitle := r.FormValue("anime_title") + animeImage := r.FormValue("anime_image") + status := r.FormValue("status") + + animeID, err := strconv.ParseInt(animeIDStr, 10, 64) + if err != nil { + http.Error(w, "invalid anime ID", http.StatusBadRequest) + return + } + + // Ensure the anime exists in our local DB first (foreign key constraint) + _, err = h.db.UpsertAnime(r.Context(), database.UpsertAnimeParams{ + ID: animeID, + Title: animeTitle, + ImageUrl: animeImage, + }) + if err != nil { + http.Error(w, fmt.Sprintf("failed to save anime reference: %v", err), http.StatusInternalServerError) + return + } + + // Now insert/update the watchlist entry + entryID := uuid.New().String() + _, err = h.db.UpsertWatchListEntry(r.Context(), database.UpsertWatchListEntryParams{ + ID: entryID, + UserID: user.ID, + AnimeID: animeID, + Status: status, + }) + if err != nil { + http.Error(w, fmt.Sprintf("failed to update watchlist: %v", err), http.StatusInternalServerError) + return + } + + // For HTMX, we can just return a success toast or update a portion of the UI + displayStatus := status + switch status { + case "on_hold": + displayStatus = "on hold" + case "plan_to_watch": + displayStatus = "plan to watch" + } + + w.Header().Set("HX-Trigger", fmt.Sprintf(`{"toast": "added to %s"}`, displayStatus)) + w.WriteHeader(http.StatusNoContent) +} + +func (h *WatchlistHandler) HandleDeleteWatchlist(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodDelete { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + user, ok := r.Context().Value(middleware.UserContextKey).(*database.User) + if !ok || user == nil { + w.Header().Set("HX-Redirect", "/login") + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + animeIDStr := r.URL.Path[len("/api/watchlist/"):] + animeID, err := strconv.ParseInt(animeIDStr, 10, 64) + if err != nil { + http.Error(w, "invalid anime ID", http.StatusBadRequest) + return + } + + err = h.db.DeleteWatchListEntry(r.Context(), database.DeleteWatchListEntryParams{ + UserID: user.ID, + AnimeID: animeID, + }) + if err != nil { + http.Error(w, fmt.Sprintf("failed to delete from watchlist: %v", err), http.StatusInternalServerError) + return + } + + w.Header().Set("HX-Trigger", `{"toast": "removed from watchlist"}`) + w.WriteHeader(http.StatusOK) +} + +func (h *WatchlistHandler) HandleGetWatchlist(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + layout := r.URL.Query().Get("view") + if layout != "grid" && layout != "table" { + layout = "table" + } + + statusFilter := r.URL.Query().Get("status") + + user, ok := r.Context().Value(middleware.UserContextKey).(*database.User) + if !ok || user == nil { + http.Redirect(w, r, "/login", http.StatusFound) + return + } + + entries, err := h.db.GetUserWatchList(r.Context(), user.ID) + if err != nil { + http.Error(w, fmt.Sprintf("failed to fetch watchlist: %v", err), http.StatusInternalServerError) + return + } + + var filteredEntries []database.GetUserWatchListRow + if statusFilter != "" && statusFilter != "all" { + for _, entry := range entries { + if entry.Status == statusFilter { + filteredEntries = append(filteredEntries, entry) + } + } + } else { + statusFilter = "all" + filteredEntries = entries + } + + templates.Watchlist(filteredEntries, layout, statusFilter).Render(r.Context(), w) +} diff --git a/internal/templates/anime.templ b/internal/templates/anime.templ new file mode 100644 index 0000000..d60a004 --- /dev/null +++ b/internal/templates/anime.templ @@ -0,0 +1,141 @@ +package templates + +import "malago/internal/jikan" +import "fmt" + +templ AnimeDetails(anime jikan.Anime) { + @Layout("malago - " + anime.DisplayTitle()) { +
+
+

{ anime.DisplayTitle() }

+ if anime.TitleJapanese != "" { +
{ anime.TitleJapanese }
+ } +
+ +
+ + +
+

Synopsis

+ if anime.Synopsis != "" { +
{ anime.Synopsis }
+ } else { +
No synopsis available.
+ } + +
+
> loading relations...
+
+
+
+
+ } +} + +templ AnimeRelationsList(relations []jikan.RelationEntry) { +
+

Related Anime (Chronological)

+ if len(relations) <= 1 { +
no prequel or sequel relations found.
+ } else { + + } +
+} \ No newline at end of file diff --git a/internal/templates/anime_templ.go b/internal/templates/anime_templ.go new file mode 100644 index 0000000..ed788c7 --- /dev/null +++ b/internal/templates/anime_templ.go @@ -0,0 +1,490 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package templates + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "malago/internal/jikan" +import "fmt" + +func AnimeDetails(anime jikan.Anime) 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 { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(anime.DisplayTitle()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/anime.templ`, Line: 10, Col: 30} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if anime.TitleJapanese != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(anime.TitleJapanese) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/anime.templ`, Line: 12, Col: 56} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

Synopsis

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if anime.Synopsis != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(anime.Synopsis) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/anime.templ`, Line: 108, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
No synopsis available.
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
> loading relations...
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("malago - "+anime.DisplayTitle()).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func AnimeRelationsList(relations []jikan.RelationEntry) 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 { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var21 := templ.GetChildren(ctx) + if templ_7745c5c3_Var21 == nil { + templ_7745c5c3_Var21 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "

Related Anime (Chronological)

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(relations) <= 1 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
no prequel or sequel relations found.
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/internal/templates/watchlist.templ b/internal/templates/watchlist.templ new file mode 100644 index 0000000..9effe45 --- /dev/null +++ b/internal/templates/watchlist.templ @@ -0,0 +1,100 @@ +package templates + +import ( + "fmt" + "malago/internal/database" +) + +templ Watchlist(entries []database.GetUserWatchListRow, layout string, currentStatus string) { + @Layout("My Watchlist") { +
+

My Watchlist

+
+ Grid + | + Table +
+
+ +
+ All + Watching + On Hold + Plan to Watch + Dropped + Completed +
+ + if len(entries) == 0 { +
+ Your watchlist is empty. Go search for some anime! +
+ } else { + if layout == "grid" { +
+ for _, entry := range entries { +
+ + if entry.ImageUrl != "" { + { + } else { +
no image
+ } +
+
+ { entry.Title } +
{ entry.Status }
+
+ +
+ } +
+ } else { + + + + + + + + + + + for _, entry := range entries { + + + + + + + } + +
ImageTitleStatus
+ + { + + + + { entry.Title } + + { entry.Status } + +
+ } + } + } +} + +func viewLinkStyle(active bool) string { + if active { + return "color: var(--text); font-weight: bold; text-decoration: none;" + } + return "color: var(--text-muted); text-decoration: none;" +} + +func tabLinkStyle(active bool) string { + if active { + return "color: var(--text); font-weight: bold; text-decoration: none; border-bottom: 2px solid var(--text); padding-bottom: 7px;" + } + return "color: var(--text-muted); text-decoration: none; padding-bottom: 7px;" +} diff --git a/internal/templates/watchlist_templ.go b/internal/templates/watchlist_templ.go new file mode 100644 index 0000000..6b220bd --- /dev/null +++ b/internal/templates/watchlist_templ.go @@ -0,0 +1,559 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package templates + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "malago/internal/database" +) + +func Watchlist(entries []database.GetUserWatchListRow, layout string, currentStatus 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 { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

My Watchlist

Grid | Table
All Watching On Hold Plan to Watch Dropped Completed
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(entries) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
Your watchlist is empty. Go search for some anime!
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + if layout == "grid" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, entry := range entries { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if entry.ImageUrl != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
no image
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 45, Col: 21} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Status) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 46, Col: 95} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, entry := range entries { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
ImageTitleStatus
\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 72, Col: 31} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Status) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 75, Col: 26} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + return nil + }) + templ_7745c5c3_Err = Layout("My Watchlist").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func viewLinkStyle(active bool) string { + if active { + return "color: var(--text); font-weight: bold; text-decoration: none;" + } + return "color: var(--text-muted); text-decoration: none;" +} + +func tabLinkStyle(active bool) string { + if active { + return "color: var(--text); font-weight: bold; text-decoration: none; border-bottom: 2px solid var(--text); padding-bottom: 7px;" + } + return "color: var(--text-muted); text-decoration: none; padding-bottom: 7px;" +} + +var _ = templruntime.GeneratedTemplate