From 7e355d5edadae1016a6f45fde6406479030883be Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Tue, 15 Jul 2025 19:44:19 -0700 Subject: [PATCH] (UI/FIX): Fixed the favorite button rendering and updating! I don't like the way that templ requires JS, but it is what it is. I just don't want to return HTML from the server. --- internal/app/handlers/page_handler.go | 5 +- internal/app/service/recipe_service.go | 4 + internal/templates/pages/recipe.templ | 77 ++++++++++++++++--- internal/templates/pages/recipe_templ.go | 98 ++++++++++++++++-------- 4 files changed, 137 insertions(+), 47 deletions(-) diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go index b0e9f16..a5c4ec0 100644 --- a/internal/app/handlers/page_handler.go +++ b/internal/app/handlers/page_handler.go @@ -106,7 +106,8 @@ func RecipePage(ctx *gin.Context) { // Get signed in user, if they exist var userId *int = nil - if domainServer.IsLoggedIn(ctx) { + var loggedIn = domainServer.IsLoggedIn(ctx) + if loggedIn { storeId := ctx.MustGet("userId").(int) userId = &storeId } @@ -139,7 +140,7 @@ func RecipePage(ctx *gin.Context) { // I also do not really like that this runs on refresh, might need some better handling title := "Potion - View Recipe" - page := pages.RecipePage(*recipe, *user) + page := pages.RecipePage(*recipe, *user, loggedIn) ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) } diff --git a/internal/app/service/recipe_service.go b/internal/app/service/recipe_service.go index 87cdae8..0f5a68b 100644 --- a/internal/app/service/recipe_service.go +++ b/internal/app/service/recipe_service.go @@ -123,6 +123,10 @@ func (s *RecipeService) CreateRecipe(ctx *gin.Context) (*domain.Recipe, error) { // GetRecipe will get a recipe via its ID. Any errors will be bubbled to the caller. Furthermore, // if the recipe is nil, an error will be returned, so the caller does not need to check for a nil // recipe (e.g., if the error is nil the recipe exists) +// +// A userId should be provided to allow the favorite status to be updated. Without a userId (nil), +// the favorite status will return false, not because its not a favorite, but because it cannot find +// out! func (s *RecipeService) GetRecipe(id int, userId *int) (*domain.Recipe, error) { recipe, err := s.recipeRepository.GetRecipe(id, userId) diff --git a/internal/templates/pages/recipe.templ b/internal/templates/pages/recipe.templ index 202ac5d..f853b55 100644 --- a/internal/templates/pages/recipe.templ +++ b/internal/templates/pages/recipe.templ @@ -151,7 +151,7 @@ templ tagList(tags []domain.Tag, created time.Time, modified *time.Time) { templ ingredientListItem(name, quantity string) {
  • @@ -173,7 +173,7 @@ templ instructionListItem(num int, content string) {

    { num }

    -

    { content }

    +

    { content }

  • } @@ -183,13 +183,17 @@ templ tagListItem(content string) { } -templ favoriteButton(favorited bool, id int) { +templ favoriteButton(favorited bool, id int, loggedIn bool) { if favorited { - `; } + function favoriteButtonHandler() { + const button = document.getElementById("favorite-button"); + + console.log(button.classList); + console.log(button.classList.contains("border-blue-300")); + + const toggleClasses = [ + "border-gray-300", "hover:bg-gray-50", "hover:border-blue-300", + "border-blue-300", "bg-blue-50", "hover:bg-blue-100", "hover:border-blue-500" + ]; + + for (const cls of toggleClasses) { + console.log("toggling class " + cls); + button.classList.toggle(cls); + } + + if (!button.classList.contains("border-blue-300")) { + button.innerHTML = ` + + + + Favorite + `; + + } else { + button.innerHTML = ` + + + + Unfavorite + `; + } + + } + } diff --git a/internal/templates/pages/recipe_templ.go b/internal/templates/pages/recipe_templ.go index 0e2b1b1..e26ba3f 100644 --- a/internal/templates/pages/recipe_templ.go +++ b/internal/templates/pages/recipe_templ.go @@ -426,7 +426,7 @@ func ingredientListItem(name, quantity string) templ.Component { templ_7745c5c3_Var13 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
  • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
  • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -494,14 +494,14 @@ func instructionListItem(num int, content string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(content) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 176, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 176, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -557,7 +557,7 @@ func tagListItem(content string) templ.Component { }) } -func favoriteButton(favorited bool, id int) templ.Component { +func favoriteButton(favorited bool, id int, loggedIn bool) 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 { @@ -592,25 +592,45 @@ func favoriteButton(favorited bool, id int) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\" hx-trigger=\"click\" hx-swap=\"none\" class=\"flex items-center justify-center gap-x-1 rounded-lg border border-blue-300 bg-blue-50 text-gray-800 px-6 py-3 flex-grow hover:bg-blue-100 hover:border-blue-500 duration-300\"> Unfavorite") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\" hx-trigger=\"click\" hx-swap=\"none\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if loggedIn { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " hx-on:click=\"favoriteButtonHandler();\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, " class=\"flex items-center justify-center gap-x-1 rounded-lg border border-blue-300 bg-blue-50 text-gray-800 px-6 py-3 flex-grow hover:bg-blue-100 hover:border-blue-500 duration-300\" id=\"favorite-button\"> Unfavorite") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\" hx-trigger=\"click\" hx-swap=\"none\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if loggedIn { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " hx-on:click=\"favoriteButtonHandler();\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " class=\"flex items-center justify-center gap-x-1 rounded-lg border border-gray-300 text-gray-800 px-6 py-3 flex-grow hover:bg-gray-50 hover:border-blue-300 duration-300\" id=\"favorite-button\"> Favorite") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -619,7 +639,7 @@ func favoriteButton(favorited bool, id int) templ.Component { }) } -func madeButton(id int) templ.Component { +func madeButton(id int, loggedIn bool) 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 { @@ -640,20 +660,30 @@ func madeButton(id int) templ.Component { templ_7745c5c3_Var24 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "\" hx-trigger=\"click\" hx-swap=\"none\" id=\"make-button\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if loggedIn { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " hx-on:click=\"makeButtonHandler();\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, " class=\"flex items-center justify-center gap-x-1 rounded-lg border border-gray-300 text-gray-800 px-6 py-3 flex-grow hover:bg-gray-50 hover:border-blue-300 duration-300\"> Made This!") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -682,7 +712,7 @@ func shareButton() templ.Component { templ_7745c5c3_Var26 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -690,7 +720,7 @@ func shareButton() templ.Component { }) } -func buttonSection(favorited bool, id int) templ.Component { +func buttonSection(favorited bool, id int, loggedIn bool) 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 { @@ -711,15 +741,15 @@ func buttonSection(favorited bool, id int) templ.Component { templ_7745c5c3_Var27 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = favoriteButton(favorited, id).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = favoriteButton(favorited, id, loggedIn).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = madeButton(id).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = madeButton(id, loggedIn).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -727,7 +757,7 @@ func buttonSection(favorited bool, id int) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -735,7 +765,7 @@ func buttonSection(favorited bool, id int) templ.Component { }) } -func RecipePage(recipe domain.Recipe, user domainUser.User) templ.Component { +func RecipePage(recipe domain.Recipe, user domainUser.User, loggedIn bool) 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 { @@ -760,46 +790,46 @@ func RecipePage(recipe domain.Recipe, user domainUser.User) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
    \"\"

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
    \"\"

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 287, Col: 75} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 297, Col: 75} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "

    Author: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "

    Author: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 288, Col: 66} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 298, Col: 66} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "

    Category: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "

    Category: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 289, Col: 69} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 299, Col: 69} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -807,24 +837,24 @@ func RecipePage(recipe domain.Recipe, user domainUser.User) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = buttonSection(recipe.Favorite, recipe.Id).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = buttonSection(recipe.Favorite, recipe.Id, loggedIn).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "

    About this recipe

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "

    About this recipe

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var32 string templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Description) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 295, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 305, Col: 49} } _, 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, 52, "

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -840,7 +870,7 @@ func RecipePage(recipe domain.Recipe, user domainUser.User) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -873,19 +903,19 @@ func scripts(id int) templ.Component { templ_7745c5c3_Var33 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "\"\n navigator.clipboard.writeText(url).then(() => {\n button.outerHTML = `\n \n `;\n\n setTimeout(() => {\n const newButton = document.getElementById(\"share-button\");\n newButton.outerHTML = before;\n }, 2000);\n });\n } else {\n console.warn(\"Clipboard API not available.\");\n\n button.outerHTML = `\n \n `;\n\n setTimeout(() => {\n const newButton = document.getElementById(\"share-button\");\n newButton.outerHTML = before;\n }, 2000);\n }\n }\n\n function makeButtonHandler() {\n const button = document.getElementById(\"make-button\");\n\n button.outerHTML = `\n \n \n \n \n \n \n Made This!\n \n `;\n }\n\n function favoriteButtonHandler() {\n const button = document.getElementById(\"favorite-button\");\n\n console.log(button.classList);\n console.log(button.classList.contains(\"border-blue-300\"));\n\n const toggleClasses = [\n \"border-gray-300\", \"hover:bg-gray-50\", \"hover:border-blue-300\",\n \"border-blue-300\", \"bg-blue-50\", \"hover:bg-blue-100\", \"hover:border-blue-500\"\n ];\n\n for (const cls of toggleClasses) {\n console.log(\"toggling class \" + cls);\n button.classList.toggle(cls);\n }\n\n if (!button.classList.contains(\"border-blue-300\")) {\n button.innerHTML = `\n \n \n \n Favorite\n `;\n\n } else {\n button.innerHTML = `\n \n \n \n Unfavorite\n `;\n }\n\n }\n\n") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }