From d2835c636c02d946872e02eb0e1ef469b6adb12e Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Sun, 13 Jul 2025 21:34:54 -0700
Subject: [PATCH 01/10] (DB/FEAT): Began the implementation of the user
engagement!
The database requirements have been added, as well as the service/repo
architecture. A few small functions have been created, but the system is
not complete by any means. More work is required to mark this task
complete.
---
doc/TechnicalSpecification.md | 27 +-
internal/app/handlers/page_handler.go | 24 +-
internal/app/server/server.go | 9 +-
internal/app/service/engagement_service.go | 60 ++++
internal/domain/engagement/engagement.go | 28 ++
internal/domain/engagement/repository.go | 7 +
internal/domain/engagement/service.go | 7 +
internal/domain/server/server.go | 10 +-
.../migrations/006_create_engagment_enum.sql | 16 ++
.../007_create_engagement_table.sql | 16 ++
.../repository/engagement_repository.go | 153 ++++++++++
internal/templates/pages/profile.templ | 269 ++++++++----------
internal/templates/pages/profile_templ.go | 117 +++++---
13 files changed, 536 insertions(+), 207 deletions(-)
create mode 100644 internal/app/service/engagement_service.go
create mode 100644 internal/domain/engagement/engagement.go
create mode 100644 internal/domain/engagement/repository.go
create mode 100644 internal/domain/engagement/service.go
create mode 100644 internal/infrastructure/database/migrations/006_create_engagment_enum.sql
create mode 100644 internal/infrastructure/database/migrations/007_create_engagement_table.sql
create mode 100644 internal/infrastructure/database/repository/engagement_repository.go
diff --git a/doc/TechnicalSpecification.md b/doc/TechnicalSpecification.md
index dce8da4..8af89c5 100644
--- a/doc/TechnicalSpecification.md
+++ b/doc/TechnicalSpecification.md
@@ -291,12 +291,13 @@ found in **OTHER** section.
- [x] GoogleRefreshToken () text
- [x] Created (Required) date/time stamp
-- [ ] Engagements: Represents a single engagement from a single user.
- - [ ] ID (PK) Serial
- - [ ] Message () text (Used to store any relevant notes, if needed)
- - [ ] Entity (Serial) Serial (Used to relate an entity, if needed)
- - [ ] UserId (FK: User.Id, Required) Serial
- - [ ] Created (Required) date/time stamp
+- [x] Engagements: Represents a single engagement from a single user.
+ - [x] ID (PK) Serial
+ - [x] Type (Required) E_Engagement
+ - [x] Message () text (Used to store any relevant notes, if needed)
+ - [x] Entity (Serial) Serial (Used to relate an entity, if needed)
+ - [x] UserId (FK: User.Id) Serial, optional for not logged in users
+ - [x] Created (Required) date/time stamp
- [ ] Likes: **Many-to-many** table to represent a list of recipes liked by a user.
- [ ] ID (PK) *Composite key***
@@ -368,10 +369,10 @@ Various tables will reference these types.
- [ ] like: string
- [ ] system: string
-- [ ] E_Engagement: Type to represent a type of user engagement.
- - [ ] made: string
- - [ ] liked: string
- - [ ] viewed: string
- - [ ] shared: string
- - [ ] reviewed: string
- - [ ] rated: string
+- [x] E_Engagement: Type to represent a type of user engagement.
+ - [x] made: string
+ - [x] liked: string
+ - [x] viewed: string
+ - [x] shared: string
+ - [x] reviewed: string
+ - [x] rated: string
diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go
index d375b78..a11959f 100644
--- a/internal/app/handlers/page_handler.go
+++ b/internal/app/handlers/page_handler.go
@@ -60,7 +60,6 @@ func ProfilePage(ctx *gin.Context) {
user := deps.UserService.GetAuthenicatedUser(ctx)
recipes, err := deps.RecipeService.GetUserRecipes(user.Id)
if err != nil {
- fmt.Printf("Error getting recipes. %s\n", err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": fmt.Sprintf("Error getting recipes. %s\n", err.Error()),
@@ -68,8 +67,18 @@ func ProfilePage(ctx *gin.Context) {
return
}
+ // Get the engagement data, not sure what will happen when errors occur
+ engagements, err := deps.EngagementService.GetUserEngagement(user.Id, 6)
+ if err != nil {
+ ctx.JSON(http.StatusInternalServerError, gin.H{
+ "status": http.StatusInternalServerError,
+ "message": fmt.Sprintf("Error getting user engagements. %s\n", err.Error()),
+ })
+ return
+ }
+
title := "Potion - Profile"
- page := pages.ProfilePage(user, recipes)
+ page := pages.ProfilePage(user, recipes, engagements)
ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page))
}
@@ -111,6 +120,17 @@ func RecipePage(ctx *gin.Context) {
return
}
+ // Add engagement
+ if user != nil {
+ if _, err = deps.EngagementService.UserViewRecipe(user.Id, recipe.Id); err != nil {
+ fmt.Printf("ERROR: %s\n", err.Error())
+ ctx.JSON(400, err.Error())
+ return
+ }
+ }
+ // TODO: Need handling for anon viewing of the recipe
+ // 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)
diff --git a/internal/app/server/server.go b/internal/app/server/server.go
index 406169b..33865f9 100644
--- a/internal/app/server/server.go
+++ b/internal/app/server/server.go
@@ -115,14 +115,17 @@ func (s *Server) Setup() *Server {
// Initialize and inject dependencies
userRepo := repository.NewUserRepository(s.DB)
recipeRepo := repository.NewRecipeRepository(s.DB)
+ engagementRepo := repository.NewEngagementRepository(s.DB)
userService := service.NewUserService(userRepo)
authService := service.NewAuthService(userRepo, jwtSecret)
recipeService := service.NewRecipeService(recipeRepo)
+ engagementService := service.NewEngagementService(engagementRepo, recipeRepo)
deps := &domain.InjectedDependencies{
- UserService: userService,
- AuthService: authService,
- RecipeService: recipeService,
+ UserService: userService,
+ AuthService: authService,
+ RecipeService: recipeService,
+ EngagementService: engagementService,
}
// Apply middleware
diff --git a/internal/app/service/engagement_service.go b/internal/app/service/engagement_service.go
new file mode 100644
index 0000000..1ff43fb
--- /dev/null
+++ b/internal/app/service/engagement_service.go
@@ -0,0 +1,60 @@
+package service
+
+import (
+ "fmt"
+
+ domain "github.com/haydenhargreaves/Potion/internal/domain/engagement"
+ domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
+ _ "github.com/lib/pq"
+)
+
+type EngagementService struct {
+ engagementRepository domain.EngagementRepository
+ recipeRepository domainRecipe.RecipeRepository
+}
+
+// Compile-time check to ensure the EngagementService implements domain.EngagementService
+var _ domain.EngagementService = (*EngagementService)(nil)
+
+// NewUserRepository creates a user repository object which is used by the user service to access
+// the database. Any user related database operations will take place in this repository.
+func NewEngagementService(engagementRepository domain.EngagementRepository, recipeRepository domainRecipe.RecipeRepository) domain.EngagementService {
+ return &EngagementService{
+ engagementRepository: engagementRepository,
+ recipeRepository: recipeRepository,
+ }
+}
+
+// UserViewRecipe requires a user ID and a recipe ID to create an engagement record in the database.
+// A message will be generated using the recipe data and then used to add a view engagement to the
+// database.
+func (s *EngagementService) UserViewRecipe(userId, recipeId int) (domain.Engagement, error) {
+ recipe, err := s.recipeRepository.GetRecipe(recipeId)
+ if err != nil {
+ return domain.Engagement{}, err
+ }
+
+ message := fmt.Sprintf("Viewed \"%s\"", recipe.Title)
+
+ return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementViewed)
+}
+
+// UserLikeRecipe requires a user ID and a recipe ID to create an engagement record in the database.
+// A message will be generated using the recipe data and then used to add a like engagement to the
+// database.
+func (s *EngagementService) UserLikeRecipe(userId, recipeId int) (domain.Engagement, error) {
+ recipe, err := s.recipeRepository.GetRecipe(recipeId)
+ if err != nil {
+ return domain.Engagement{}, err
+ }
+
+ message := fmt.Sprintf("Liked \"%s\"", recipe.Title)
+
+ return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementLiked)
+}
+
+// GetUserEngagement returns a list of the users most recent engagement entries. The number of records
+// is determined by the limit passed into this function. The results are sorted, newest-to-oldest.
+func (s *EngagementService) GetUserEngagement(userId, limit int) ([]domain.Engagement, error) {
+ return s.engagementRepository.GetUserEngagement(userId, limit)
+}
diff --git a/internal/domain/engagement/engagement.go b/internal/domain/engagement/engagement.go
new file mode 100644
index 0000000..92ca859
--- /dev/null
+++ b/internal/domain/engagement/engagement.go
@@ -0,0 +1,28 @@
+package domain
+
+import "time"
+
+// EngagementType is the database enum E_ENGAGEMENT which defines the type of a user engagement
+// of a recipe. Postgres enums are case sensitive so these must match the values in the database
+// exactly.
+type EngagementType string
+
+const (
+ EngagementMade EngagementType = "made"
+ EngagementLiked EngagementType = "liked"
+ EngagementViewed EngagementType = "viewed"
+ EngagementShared EngagementType = "shared"
+ EngagementReviewed EngagementType = "reviewed"
+ EngagementRated EngagementType = "rated"
+)
+
+// Engagement is the database model of a user engagement. There is no need to map to a different
+// model so this will remain in the domain.
+type Engagement struct {
+ Id int
+ Type EngagementType
+ Message string
+ Entity int
+ UserId int
+ Created time.Time
+}
diff --git a/internal/domain/engagement/repository.go b/internal/domain/engagement/repository.go
new file mode 100644
index 0000000..204682a
--- /dev/null
+++ b/internal/domain/engagement/repository.go
@@ -0,0 +1,7 @@
+package domain
+
+type EngagementRepository interface {
+ AddUserEngagement(userId int, message string, engagementType EngagementType) (Engagement, error)
+ AddUserEntityEngagement(userId, entityId int, message string, engagementType EngagementType) (Engagement, error)
+ GetUserEngagement(userId, limit int) ([]Engagement, error)
+}
diff --git a/internal/domain/engagement/service.go b/internal/domain/engagement/service.go
new file mode 100644
index 0000000..a4884b4
--- /dev/null
+++ b/internal/domain/engagement/service.go
@@ -0,0 +1,7 @@
+package domain
+
+type EngagementService interface {
+ UserViewRecipe(userId, recipeId int) (Engagement, error)
+ UserLikeRecipe(userId, recipeId int) (Engagement, error)
+ GetUserEngagement(userId, limit int) ([]Engagement, error)
+}
diff --git a/internal/domain/server/server.go b/internal/domain/server/server.go
index 71f9fa5..a8861aa 100644
--- a/internal/domain/server/server.go
+++ b/internal/domain/server/server.go
@@ -4,6 +4,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
domainAuth "github.com/haydenhargreaves/Potion/internal/domain/auth"
+ domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement"
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
)
@@ -11,12 +12,13 @@ import (
// InjectedDependencies is a collection of dependencies that are injected into the application. They
// are stored in the context and can be accessed by handlers via the context.
type InjectedDependencies struct {
- UserService domainUser.UserService
- AuthService domainAuth.AuthService
- RecipeService domainRecipe.RecipeService
+ UserService domainUser.UserService
+ AuthService domainAuth.AuthService
+ RecipeService domainRecipe.RecipeService
+ EngagementService domainEngagement.EngagementService
}
-// JwtClaims is the data stored in the JSON web token. All that is needed is the users ID and their
+// JwtClaims is the data stored in the JSON web token. All that is needed is the users ID and their
// Google email provided.
type JwtClaims struct {
UserId int `json:"id"`
diff --git a/internal/infrastructure/database/migrations/006_create_engagment_enum.sql b/internal/infrastructure/database/migrations/006_create_engagment_enum.sql
new file mode 100644
index 0000000..8d64d23
--- /dev/null
+++ b/internal/infrastructure/database/migrations/006_create_engagment_enum.sql
@@ -0,0 +1,16 @@
+-- Author: Hayden Hargreaves (hhargreaves2006@gmail.com)
+-- Desc: Create the E_ENGAGEMENT enum.
+-- Date: 07/13/2025
+
+BEGIN;
+
+CREATE TYPE E_ENGAGEMENT AS ENUM(
+ 'made',
+ 'liked', -- this is the same as saved/favorited
+ 'viewed',
+ 'shared',
+ 'reviewed',
+ 'rated'
+);
+
+COMMIT;
diff --git a/internal/infrastructure/database/migrations/007_create_engagement_table.sql b/internal/infrastructure/database/migrations/007_create_engagement_table.sql
new file mode 100644
index 0000000..a8831ff
--- /dev/null
+++ b/internal/infrastructure/database/migrations/007_create_engagement_table.sql
@@ -0,0 +1,16 @@
+-- Author: Hayden Hargreaves (hhargreaves2006@gmail.com)
+-- Desc: Create the user engagement table.
+-- Date: 07/13/2025
+
+BEGIN;
+
+CREATE TABLE IF NOT EXISTS Engagements (
+ Id SERIAL PRIMARY KEY NOT NULL,
+ Type E_ENGAGEMENT NOT NULL,
+ Message TEXT,
+ Entity INT, -- Used to map to other DB objects, recipes, users, etc...
+ UserId INTEGER REFERENCES users(id), -- Can be null, when users aren't logged in
+ Created TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+COMMIT;
diff --git a/internal/infrastructure/database/repository/engagement_repository.go b/internal/infrastructure/database/repository/engagement_repository.go
new file mode 100644
index 0000000..4d9faf8
--- /dev/null
+++ b/internal/infrastructure/database/repository/engagement_repository.go
@@ -0,0 +1,153 @@
+package repository
+
+import (
+ "database/sql"
+ "fmt"
+ "time"
+
+ domain "github.com/haydenhargreaves/Potion/internal/domain/engagement"
+ _ "github.com/lib/pq"
+)
+
+type EngagementRepository struct {
+ db *sql.DB
+}
+
+// Compile-time check to ensure the EngagementRepository implements domain.EngagementRepository
+var _ domain.EngagementRepository = (*EngagementRepository)(nil)
+
+// NewUserRepository creates a user repository object which is used by the user service to access
+// the database. Any user related database operations will take place in this repository.
+func NewEngagementRepository(db *sql.DB) domain.EngagementRepository {
+ return &EngagementRepository{db: db}
+}
+
+// AddUserEngagement creates an engagement record in the database with the user ID provided. This
+// function does not accept an entity ID as it should be used when there is no need to reference
+// an entity. The message should be provided, but a blank string ("") is acceptable. The engagement
+// type parameter determines the labeling of the engagement in the database. Any errors will be
+// bubbled to the caller.
+func (r *EngagementRepository) AddUserEngagement(userId int, message string, engagementType domain.EngagementType) (domain.Engagement, error) {
+ tx, err := r.db.Begin()
+ if err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, err
+ }
+
+ query := `
+ INSERT INTO Engagements (
+ type, message, entity, userid, created
+ ) VALUES (
+ $1, $2, NULL, $3, $4
+ ) RETURNING *;
+ `
+
+ var engagement domain.Engagement
+ if err := tx.QueryRow(query, engagementType, message, userId, time.Now()).Scan(
+ &engagement.Id,
+ &engagement.Type,
+ &engagement.Message,
+ &engagement.Entity,
+ &engagement.UserId,
+ &engagement.Created,
+ ); err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, fmt.Errorf("Failed to insert engagement into database. %s", err.Error())
+ }
+
+ if err := tx.Commit(); err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, err
+ }
+
+ return engagement, nil
+}
+
+// AddUserEngagement creates an engagement record in the database with the user ID provided. This
+// function requires an entity ID as it should be used when there is a reference to external an
+// entity. The message should be provided, but a blank string ("") is acceptable. The engagement
+// type parameter determines the labeling of the engagement in the database. Any errors will be
+// bubbled to the caller.
+func (r *EngagementRepository) AddUserEntityEngagement(userId, entityId int, message string, engagementType domain.EngagementType) (domain.Engagement, error) {
+ tx, err := r.db.Begin()
+ if err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, err
+ }
+
+ query := `
+ INSERT INTO Engagements (
+ type, message, entity, userid, created
+ ) VALUES (
+ $1, $2, $3, $4, $5
+ ) RETURNING *;
+ `
+
+ var engagement domain.Engagement
+ if err := tx.QueryRow(query, engagementType, message, entityId, userId, time.Now()).Scan(
+ &engagement.Id,
+ &engagement.Type,
+ &engagement.Message,
+ &engagement.Entity,
+ &engagement.UserId,
+ &engagement.Created,
+ ); err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, fmt.Errorf("Failed to insert engagement into database. %s", err.Error())
+ }
+
+ if err := tx.Commit(); err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, err
+ }
+
+ return engagement, nil
+}
+
+// GetUserEngagement returns a list of the users most recent engagement entries. The number of records
+// is determined by the limit passed into this function. The results are sorted, newest-to-oldest.
+func (r *EngagementRepository) GetUserEngagement(userId, limit int) ([]domain.Engagement, error) {
+ tx, err := r.db.Begin()
+ if err != nil {
+ tx.Rollback()
+ return []domain.Engagement{}, err
+ }
+
+ query := `
+ SELECT * FROM Engagements
+ WHERE Userid = $1
+ ORDER BY created DESC LIMIT $2;
+ `
+
+ rows, err := tx.Query(query, userId, limit)
+ if err != nil {
+ tx.Rollback()
+ return []domain.Engagement{}, fmt.Errorf("Failed to get user engagements. %s", err.Error())
+ }
+ defer rows.Close()
+
+ var engagements []domain.Engagement
+ for rows.Next() {
+ var engagement domain.Engagement
+ if err := rows.Scan(
+ &engagement.Id,
+ &engagement.Type,
+ &engagement.Message,
+ &engagement.Entity,
+ &engagement.UserId,
+ &engagement.Created,
+ ); err != nil {
+ tx.Rollback()
+ return []domain.Engagement{}, fmt.Errorf("Failed to scan user engagement. %s", err.Error())
+ }
+
+ engagements = append(engagements, engagement)
+ }
+
+ if err := tx.Commit(); err != nil {
+ tx.Rollback()
+ return []domain.Engagement{}, err
+ }
+
+ return engagements, err
+}
diff --git a/internal/templates/pages/profile.templ b/internal/templates/pages/profile.templ
index 00e0a76..1ed940c 100644
--- a/internal/templates/pages/profile.templ
+++ b/internal/templates/pages/profile.templ
@@ -6,178 +6,157 @@ import "strings"
import domain "github.com/haydenhargreaves/Potion/internal/domain/server"
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
import domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
+import domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement"
func displayDifficulty(diff int) string {
- switch diff {
- case 1:
- return "Beginner"
- case 2:
- return "Easy"
- case 3:
- return "Intermediate"
- case 4:
- return "Challenging"
- case 5:
- return "Extreme"
- default:
- return ""
- }
+switch diff {
+case 1:
+return "Beginner"
+case 2:
+return "Easy"
+case 3:
+return "Intermediate"
+case 4:
+return "Challenging"
+case 5:
+return "Extreme"
+default:
+return ""
+}
}
func displayTags(tags []domainRecipe.Tag) string {
- names := make([]string, 0, len(tags))
- for _, tag := range tags {
- names = append(names, tag.Name)
- }
- return strings.Join(names, ", ")
+names := make([]string, 0, len(tags))
+for _, tag := range tags {
+names = append(names, tag.Name)
+}
+return strings.Join(names, ", ")
}
templ userDetailsSection(user domainUser.User, recipeCount int) {
-
-
+
+
if user.ImageUrl != "" {
-
+
} else {
-
-
+
}
-
-
-
{ user.Name }
-
{ user.Email }
-
-
-
{ recipeCount } recipes
-
0 favorites
-
-
-
-
+
+
+
{ user.Name }
+
{ user.Email }
+
+
+
{ recipeCount } recipes
+
0 favorites
+
+
+
+
}
templ recipesSection(recipes []domainRecipe.Recipe) {
-
- My Recipes
-
- if len(recipes) <= 4 {
- for _, recipe :=range recipes {
- @recipeListItem(recipe)
- }
- } else {
- for _, recipe := range recipes[:4] {
- @recipeListItem(recipe)
- }
- }
-
-
- See all...
-
-
-
-
+
+ My Recipes
+
+ if len(recipes) <= 4 { for _, recipe :=range recipes { @recipeListItem(recipe) } } else { for _, recipe :=range
+ recipes[:4] { @recipeListItem(recipe) } }
+
+ See all...
+
+
+
+
}
templ favoritesSection(recipes []domainRecipe.Recipe) {
-
- My Favorites
- Favorites section is under construction!
-
+
+ My Favorites
+ Favorites section is under construction!
+
}
-templ activitySection() {
-
- Recent Activity
- Activity section is under construction!
-
-
+templ activitySection(engagement []domainEngagement.Engagement) {
+
+ Recent Activity
+ Activity section is under construction!
+
+
}
templ recipeListItem(recipe domainRecipe.Recipe) {
-
-
-
- { recipe.Title }
-
-
-
- Difficulty: { displayDifficulty(recipe.Difficulty) }
- | Duration: { recipe.Duration.Total } min
- | Category: { recipe.Category }
-
-
- Difficulty: { displayDifficulty(recipe.Difficulty) }
-
-
- Duration: { recipe.Duration.Total } min
-
-
- Category: { recipe.Category }
-
- if len(recipe.Tags) > 0 {
-
- Tags: { displayTags(recipe.Tags) }
-
- }
-
+
+
+
+ { recipe.Title }
+
+
+
+ Difficulty: { displayDifficulty(recipe.Difficulty) }
+ | Duration: { recipe.Duration.Total } min
+ | Category: { recipe.Category }
+
+
+ Difficulty: { displayDifficulty(recipe.Difficulty) }
+
+
+ Duration: { recipe.Duration.Total } min
+
+
+ Category: { recipe.Category }
+
+ if len(recipe.Tags) > 0 {
+
+ Tags: { displayTags(recipe.Tags) }
+
+ }
+
}
-templ activityListItem() {
-
-
- Rated "Spicy Chicken Wings"
-
-
- 2 days ago
-
-
+templ activityListItem(engagement domainEngagement.Engagement) {
+
+
+ { engagement.Message }
+
+
+ { engagement.Created.Format("01/02/2006") }
+
+
}
templ logoutSection() {
-
+
}
-templ ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe) {
- @components.Navbar(" profile")
-
-
- @userDetailsSection(user, len(recipes))
- @recipesSection(recipes)
- @favoritesSection(recipes)
- @activitySection()
- @logoutSection()
-
-
+templ ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe, engagement []domainEngagement.Engagement) {
+@components.Navbar(" profile")
+
+
+ @userDetailsSection(user, len(recipes))
+ @recipesSection(recipes)
+ @favoritesSection(recipes)
+ @activitySection(engagement)
+ @logoutSection()
+
+
}
diff --git a/internal/templates/pages/profile_templ.go b/internal/templates/pages/profile_templ.go
index b035f02..c6310b6 100644
--- a/internal/templates/pages/profile_templ.go
+++ b/internal/templates/pages/profile_templ.go
@@ -14,6 +14,7 @@ import "strings"
import domain "github.com/haydenhargreaves/Potion/internal/domain/server"
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
import domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
+import domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement"
func displayDifficulty(diff int) string {
switch diff {
@@ -73,7 +74,7 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user.ImageUrl)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 41, Col: 23}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 42, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
@@ -91,7 +92,7 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("https://ui-avatars.com/api/?name=%s+%s&size=150", strings.Split(user.Name, " ")[0], strings.Split(user.Name, " ")[1]))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 46, Col: 140}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 47, Col: 141}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -226,7 +227,7 @@ func favoritesSection(recipes []domainRecipe.Recipe) templ.Component {
})
}
-func activitySection() templ.Component {
+func activitySection(engagement []domainEngagement.Engagement) 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 {
@@ -247,7 +248,17 @@ func activitySection() templ.Component {
templ_7745c5c3_Var9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "Recent Activity Activity section is under construction!
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "Recent Activity Activity section is under construction!
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, eng := range engagement {
+ templ_7745c5c3_Err = activityListItem(eng).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "See all... ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -276,7 +287,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" class=\"hover:text-blue-600 duration-100\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 123, Col: 18}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 118, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
Difficulty: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
Difficulty: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(displayDifficulty(recipe.Difficulty))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 127, Col: 81}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 122, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, " | Duration: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " | Duration: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Total)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 128, Col: 66}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 123, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " min | Category: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " min | Category: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 129, Col: 60}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 124, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
Difficulty: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
Difficulty: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(displayDifficulty(recipe.Difficulty))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 132, Col: 81}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 127, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
Duration: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
Duration: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Total)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 135, Col: 64}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 130, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " min
Category: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, " min
Category: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 138, Col: 58}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 133, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(recipe.Tags) > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "Tags: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
Tags: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(displayTags(recipe.Tags))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 142, Col: 36}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 137, Col: 36}
}
_, 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, 24, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -407,7 +418,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
})
}
-func activityListItem() templ.Component {
+func activityListItem(engagement domainEngagement.Engagement) 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 {
@@ -428,7 +439,33 @@ func activityListItem() templ.Component {
templ_7745c5c3_Var20 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "Rated \"Spicy Chicken Wings\"
2 days ago
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var21 string
+ templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(engagement.Message)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 148, Col: 24}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
+ 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_Var22 string
+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(engagement.Created.Format("01/02/2006"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 151, Col: 47}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
+ 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
}
@@ -452,21 +489,21 @@ func logoutSection() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var21 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var21 == nil {
- templ_7745c5c3_Var21 = templ.NopComponent
+ templ_7745c5c3_Var23 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var23 == nil {
+ templ_7745c5c3_Var23 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\" class=\"text-center border border-red-500 text-red-500 w-9/10 md:w-1/3 py-2 rounded-lg hover:cursor-pointer hover:bg-red-100 duration-300\">Logout")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -474,7 +511,7 @@ func logoutSection() templ.Component {
})
}
-func ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe) templ.Component {
+func ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe, engagement []domainEngagement.Engagement) 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 {
@@ -490,16 +527,16 @@ func ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe) templ.Comp
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var23 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var23 == nil {
- templ_7745c5c3_Var23 = templ.NopComponent
+ templ_7745c5c3_Var25 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var25 == nil {
+ templ_7745c5c3_Var25 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = components.Navbar(" profile").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -515,7 +552,7 @@ func ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = activitySection().Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = activitySection(engagement).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -523,7 +560,7 @@ func ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe) templ.Comp
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
From 4a94b1a08c4338463933042f66eee7b06af5692a Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Sun, 13 Jul 2025 21:37:51 -0700
Subject: [PATCH 02/10] (UI/FIX): Fixed the retarded formatting.
Templ's formatter sucks.
---
internal/templates/pages/profile.templ | 263 ++++++++++++----------
internal/templates/pages/profile_templ.go | 31 +--
2 files changed, 156 insertions(+), 138 deletions(-)
diff --git a/internal/templates/pages/profile.templ b/internal/templates/pages/profile.templ
index 1ed940c..1dcf8a2 100644
--- a/internal/templates/pages/profile.templ
+++ b/internal/templates/pages/profile.templ
@@ -9,154 +9,171 @@ import domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
import domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement"
func displayDifficulty(diff int) string {
-switch diff {
-case 1:
-return "Beginner"
-case 2:
-return "Easy"
-case 3:
-return "Intermediate"
-case 4:
-return "Challenging"
-case 5:
-return "Extreme"
-default:
-return ""
-}
+ switch diff {
+ case 1:
+ return "Beginner"
+ case 2:
+ return "Easy"
+ case 3:
+ return "Intermediate"
+ case 4:
+ return "Challenging"
+ case 5:
+ return "Extreme"
+ default:
+ return ""
+ }
}
func displayTags(tags []domainRecipe.Tag) string {
-names := make([]string, 0, len(tags))
-for _, tag := range tags {
-names = append(names, tag.Name)
-}
-return strings.Join(names, ", ")
+ names := make([]string, 0, len(tags))
+ for _, tag := range tags {
+ names = append(names, tag.Name)
+ }
+ return strings.Join(names, ", ")
}
templ userDetailsSection(user domainUser.User, recipeCount int) {
-
-
- if user.ImageUrl != "" {
-
- } else {
-
- }
-
-
-
{ user.Name }
-
{ user.Email }
-
-
-
{ recipeCount } recipes
-
0 favorites
-
-
-
-
+
+
+ if user.ImageUrl != "" {
+
+ } else {
+
+ }
+
+
+
{ user.Name }
+
{ user.Email }
+
+
+
{ recipeCount } recipes
+
0 favorites
+
+
+
+
}
templ recipesSection(recipes []domainRecipe.Recipe) {
-
- My Recipes
-
- if len(recipes) <= 4 { for _, recipe :=range recipes { @recipeListItem(recipe) } } else { for _, recipe :=range
- recipes[:4] { @recipeListItem(recipe) } }
-
- See all...
-
-
-
-
+
+ My Recipes
+
+ if len(recipes) <= 4 {
+ for _, recipe :=range recipes {
+ @recipeListItem(recipe)
+ }
+ } else {
+ for _, recipe := range recipes[:4] {
+ @recipeListItem(recipe)
+ }
+ }
+
+
+ See all...
+
+
+
+
}
templ favoritesSection(recipes []domainRecipe.Recipe) {
-
- My Favorites
- Favorites section is under construction!
-
+
+ My Favorites
+ Favorites section is under construction!
+
}
templ activitySection(engagement []domainEngagement.Engagement) {
-
- Recent Activity
- Activity section is under construction!
-
-
+
+ Recent Activity
+ Activity section is under construction!
+
+
}
templ recipeListItem(recipe domainRecipe.Recipe) {
-
-
-
- { recipe.Title }
-
-
-
- Difficulty: { displayDifficulty(recipe.Difficulty) }
- | Duration: { recipe.Duration.Total } min
- | Category: { recipe.Category }
-
-
- Difficulty: { displayDifficulty(recipe.Difficulty) }
-
-
- Duration: { recipe.Duration.Total } min
-
-
- Category: { recipe.Category }
-
- if len(recipe.Tags) > 0 {
-
- Tags: { displayTags(recipe.Tags) }
-
- }
-
+
+
+
+ { recipe.Title }
+
+
+
+ Difficulty: { displayDifficulty(recipe.Difficulty) }
+ | Duration: { recipe.Duration.Total } min
+ | Category: { recipe.Category }
+
+
+ Difficulty: { displayDifficulty(recipe.Difficulty) }
+
+
+ Duration: { recipe.Duration.Total } min
+
+
+ Category: { recipe.Category }
+
+ if len(recipe.Tags) > 0 {
+
+ Tags: { displayTags(recipe.Tags) }
+
+ }
+
}
templ activityListItem(engagement domainEngagement.Engagement) {
-
-
- { engagement.Message }
-
-
- { engagement.Created.Format("01/02/2006") }
-
-
+
+
+ { engagement.Message }
+
+
+ { engagement.Created.Format("01/02/2006") }
+
+
}
templ logoutSection() {
-
+
}
templ ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe, engagement []domainEngagement.Engagement) {
-@components.Navbar(" profile")
-
-
- @userDetailsSection(user, len(recipes))
- @recipesSection(recipes)
- @favoritesSection(recipes)
- @activitySection(engagement)
- @logoutSection()
-
-
+ @components.Navbar(" profile")
+
+
+ @userDetailsSection(user, len(recipes))
+ @recipesSection(recipes)
+ @favoritesSection(recipes)
+ @activitySection(engagement)
+ @logoutSection()
+
+
}
diff --git a/internal/templates/pages/profile_templ.go b/internal/templates/pages/profile_templ.go
index c6310b6..6e31303 100644
--- a/internal/templates/pages/profile_templ.go
+++ b/internal/templates/pages/profile_templ.go
@@ -90,9 +90,10 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
- templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("https://ui-avatars.com/api/?name=%s+%s&size=150", strings.Split(user.Name, " ")[0], strings.Split(user.Name, " ")[1]))
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("https://ui-avatars.com/api/?name=%s+%s&size=150", strings.Split(user.Name, " ")[0],
+ strings.Split(user.Name, " ")[1]))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 47, Col: 141}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 48, Col: 40}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -110,7 +111,7 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 52, Col: 62}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 53, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -123,7 +124,7 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 53, Col: 47}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 54, Col: 47}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@@ -136,7 +137,7 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(recipeCount)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 56, Col: 72}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 57, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@@ -303,7 +304,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 118, Col: 18}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 119, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
@@ -316,7 +317,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(displayDifficulty(recipe.Difficulty))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 122, Col: 81}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 123, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
@@ -329,7 +330,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Total)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 123, Col: 66}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 124, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
@@ -342,7 +343,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 124, Col: 60}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 125, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
@@ -355,7 +356,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(displayDifficulty(recipe.Difficulty))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 127, Col: 81}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 128, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
@@ -368,7 +369,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Total)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 130, Col: 64}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 131, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
@@ -381,7 +382,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 133, Col: 58}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 134, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
@@ -399,7 +400,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(displayTags(recipe.Tags))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 137, Col: 36}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 138, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
@@ -446,7 +447,7 @@ func activityListItem(engagement domainEngagement.Engagement) templ.Component {
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(engagement.Message)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 148, Col: 24}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 149, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
@@ -459,7 +460,7 @@ func activityListItem(engagement domainEngagement.Engagement) templ.Component {
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(engagement.Created.Format("01/02/2006"))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 151, Col: 47}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 152, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
From acbb07cb34f829bc72f9a5e8a82d5f745602c236 Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Mon, 14 Jul 2025 18:07:58 -0700
Subject: [PATCH 03/10] (UI): Small UI tweaks.
---
internal/templates/pages/favorites.templ | 2 +-
internal/templates/pages/favorites_templ.go | 2 +-
internal/templates/pages/home.templ | 6 ++-
internal/templates/pages/home_templ.go | 28 ++++++++------
internal/templates/pages/list.templ | 2 +-
internal/templates/pages/list_templ.go | 2 +-
internal/templates/pages/search.templ | 2 +-
internal/templates/pages/search_templ.go | 4 +-
web/static/css/tailwind.css | 42 +++++++++++++++++++--
9 files changed, 65 insertions(+), 25 deletions(-)
diff --git a/internal/templates/pages/favorites.templ b/internal/templates/pages/favorites.templ
index 70b1267..8d0e238 100644
--- a/internal/templates/pages/favorites.templ
+++ b/internal/templates/pages/favorites.templ
@@ -7,7 +7,7 @@ templ FavoritesPage() {
-
Page Under Construction
+
Page Under Construction
Sit tight, this page is coming soon!
diff --git a/internal/templates/pages/favorites_templ.go b/internal/templates/pages/favorites_templ.go
index 325310c..a09f819 100644
--- a/internal/templates/pages/favorites_templ.go
+++ b/internal/templates/pages/favorites_templ.go
@@ -35,7 +35,7 @@ func FavoritesPage() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Page Under Construction Sit tight, this page is coming soon!
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Page Under Construction Sit tight, this page is coming soon!
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/internal/templates/pages/home.templ b/internal/templates/pages/home.templ
index 05a0e70..3cc3a4b 100644
--- a/internal/templates/pages/home.templ
+++ b/internal/templates/pages/home.templ
@@ -29,8 +29,10 @@ templ introSection() {
templ searchSection() {
@components.BannerText("Craving Something Specific?")
- @components.SearchBar(domainRecipe.SearchFilters{}, true, false)
-
+
+ @components.SearchBar(domainRecipe.SearchFilters{}, true, false)
+
+
}
diff --git a/internal/templates/pages/home_templ.go b/internal/templates/pages/home_templ.go
index 2490417..4e3c435 100644
--- a/internal/templates/pages/home_templ.go
+++ b/internal/templates/pages/home_templ.go
@@ -70,11 +70,15 @@ func searchSection() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
templ_7745c5c3_Err = components.SearchBar(domainRecipe.SearchFilters{}, true, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -103,7 +107,7 @@ func highlightSection(liked bool) templ.Component {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -111,7 +115,7 @@ func highlightSection(liked bool) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "Our 'Recipe of the Week' is the cream of the crop! We handpick it by looking at what recipes our community loves most. This isn't just about how many people view a recipe; it's also about how many times it's been made, liked, reviewed, and its average rating, all combined to find the true fan favorite of the week. It's our way of highlighting the best recipes that truly resonate with our users!
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
Our 'Recipe of the Week' is the cream of the crop! We handpick it by looking at what recipes our community loves most. This isn't just about how many people view a recipe; it's also about how many times it's been made, liked, reviewed, and its average rating, all combined to find the true fan favorite of the week. It's our way of highlighting the best recipes that truly resonate with our users!
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -119,7 +123,7 @@ func highlightSection(liked bool) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -148,7 +152,7 @@ func listsSection() templ.Component {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -156,7 +160,7 @@ func listsSection() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "Recently viewed ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
Recently viewed ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -184,7 +188,7 @@ func listsSection() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
Make again ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
Make again ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -212,7 +216,7 @@ func listsSection() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -241,7 +245,7 @@ func ctaSection() templ.Component {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" class=\"flex items-center justify-center\n bg-gradient-to-r from-blue-400 to-blue-600 text-white\n px-12 py-5 rounded-full shadow-sm hover:shadow-md\n transition-all duration-300 ease-in-out shadow-blue-700\n text-lg md:text-2xl font-bold uppercase tracking-wide\">Create Your Recipe! ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -283,7 +287,7 @@ func HomePage() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -307,7 +311,7 @@ func HomePage() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/internal/templates/pages/list.templ b/internal/templates/pages/list.templ
index 9e8acfd..1fcfc39 100644
--- a/internal/templates/pages/list.templ
+++ b/internal/templates/pages/list.templ
@@ -7,7 +7,7 @@ templ ListPage() {
-
Page Under Construction
+
Page Under Construction
Sit tight, this page is coming soon!
diff --git a/internal/templates/pages/list_templ.go b/internal/templates/pages/list_templ.go
index 5f800d5..003bea5 100644
--- a/internal/templates/pages/list_templ.go
+++ b/internal/templates/pages/list_templ.go
@@ -35,7 +35,7 @@ func ListPage() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Page Under Construction Sit tight, this page is coming soon!
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Page Under Construction Sit tight, this page is coming soon!
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/internal/templates/pages/search.templ b/internal/templates/pages/search.templ
index b40f5fd..aab9696 100644
--- a/internal/templates/pages/search.templ
+++ b/internal/templates/pages/search.templ
@@ -48,7 +48,7 @@ templ searchResult(recipe domain.Recipe, odd bool) {
- { recipe.Title } { recipe.Category }
+ { recipe.Title } { recipe.Category }
diff --git a/internal/templates/pages/search_templ.go b/internal/templates/pages/search_templ.go
index fcaf4e0..e2c4bdd 100644
--- a/internal/templates/pages/search_templ.go
+++ b/internal/templates/pages/search_templ.go
@@ -177,14 +177,14 @@ func searchResult(recipe domain.Recipe, odd bool) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 51, Col: 72}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 51, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
diff --git a/web/static/css/tailwind.css b/web/static/css/tailwind.css
index 8bd1054..b738e44 100644
--- a/web/static/css/tailwind.css
+++ b/web/static/css/tailwind.css
@@ -28,7 +28,6 @@
--color-gray-600: oklch(44.6% 0.03 256.802);
--color-gray-700: oklch(37.3% 0.034 259.733);
--color-gray-800: oklch(27.8% 0.033 256.848);
- --color-gray-900: oklch(21% 0.034 264.665);
--color-black: #000;
--color-white: #fff;
--spacing: 0.25rem;
@@ -423,6 +422,9 @@
.w-1 {
width: calc(var(--spacing) * 1);
}
+ .w-1\/2 {
+ width: calc(1/2 * 100%);
+ }
.w-1\/3 {
width: calc(1/3 * 100%);
}
@@ -462,9 +464,21 @@
.w-full {
width: 100%;
}
+ .max-w-1 {
+ max-width: calc(var(--spacing) * 1);
+ }
+ .max-w-1\/2 {
+ max-width: calc(1/2 * 100%);
+ }
.max-w-2xl {
max-width: var(--container-2xl);
}
+ .max-w-4 {
+ max-width: calc(var(--spacing) * 4);
+ }
+ .max-w-4\/5 {
+ max-width: calc(4/5 * 100%);
+ }
.flex-shrink {
flex-shrink: 1;
}
@@ -477,6 +491,9 @@
.flex-grow {
flex-grow: 1;
}
+ .flex-grow-0 {
+ flex-grow: 0;
+ }
.border-collapse {
border-collapse: collapse;
}
@@ -701,9 +718,6 @@
--tw-gradient-position: to right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops));
}
- .bg-none {
- background-image: none;
- }
.from-blue-100 {
--tw-gradient-from: var(--color-blue-100);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
@@ -1219,6 +1233,11 @@
animation: var(--animate-bounce);
}
}
+ .sm\:hidden {
+ @media (width >= 40rem) {
+ display: none;
+ }
+ }
.sm\:w-3\/4 {
@media (width >= 40rem) {
width: calc(3/4 * 100%);
@@ -1274,6 +1293,16 @@
display: none;
}
}
+ .md\:inline {
+ @media (width >= 48rem) {
+ display: inline;
+ }
+ }
+ .md\:inline-block {
+ @media (width >= 48rem) {
+ display: inline-block;
+ }
+ }
.md\:size-12 {
@media (width >= 48rem) {
width: calc(var(--spacing) * 12);
@@ -1323,6 +1352,11 @@
width: calc(2/5 * 100%);
}
}
+ .md\:w-3\/4 {
+ @media (width >= 48rem) {
+ width: calc(3/4 * 100%);
+ }
+ }
.md\:w-32 {
@media (width >= 48rem) {
width: calc(var(--spacing) * 32);
From d29426290df136dc13d53b6612011760e2f787ca Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Mon, 14 Jul 2025 21:00:28 -0700
Subject: [PATCH 04/10] (FEAT): Created engagement buttons, and wired most of
them up!
The make button is pretty much done, just need to finish up by
rate limiting it. That will take place in the engagement repository.
Sharing works as well, just a UI change, there is no backend yet, maybe
there should be an engagement type for sharing recipes. But not totally
sure.
The viewing is also in a semi-working state. It does not create requests
for users that aren't signed in, which needs to come. With that update
there is a need to update HOW the requests come in, we don't need it
every time we load the page. Maybe just when we click to it, from
somewhere else?
Finally, the favoriting does not totally work. The entry into the
engagement table is complete, but the actual favorites table, favorite
creation, button toggling AND button rendering is not implemented yet.
---
internal/app/handlers/engagement_handler.go | 80 +++
internal/app/server/server.go | 5 +
internal/app/service/engagement_service.go | 14 +
internal/domain/engagement/service.go | 1 +
internal/domain/server/routes.go | 4 +
.../repository/engagement_repository.go | 2 +
internal/templates/pages/favorites.templ | 18 +-
internal/templates/pages/home.templ | 174 +++--
internal/templates/pages/list.templ | 19 +-
internal/templates/pages/recipe.templ | 269 +++++--
internal/templates/pages/recipe_templ.go | 669 ++++++++++++------
internal/templates/pages/search.templ | 12 +-
internal/templates/pages/search_templ.go | 55 +-
web/static/css/tailwind.css | 70 +-
14 files changed, 931 insertions(+), 461 deletions(-)
create mode 100644 internal/app/handlers/engagement_handler.go
diff --git a/internal/app/handlers/engagement_handler.go b/internal/app/handlers/engagement_handler.go
new file mode 100644
index 0000000..36bcddc
--- /dev/null
+++ b/internal/app/handlers/engagement_handler.go
@@ -0,0 +1,80 @@
+package handlers
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ domain "github.com/haydenhargreaves/Potion/internal/domain/server"
+)
+
+
+
+func EngagementViewRecipe(ctx *gin.Context) {
+ deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
+ id := ctx.Param("id")
+
+ if !domain.IsLoggedIn(ctx) {
+ // TODO: Anon view
+ ctx.Status(http.StatusNoContent)
+ return
+ }
+
+ recipeId, _ := strconv.Atoi(id)
+ userId := ctx.MustGet("userId").(int)
+
+ if _, err := deps.EngagementService.UserViewRecipe(userId, recipeId); err != nil {
+ ctx.JSON(http.StatusInternalServerError, gin.H{
+ "status": http.StatusInternalServerError,
+ "message": err.Error(),
+ })
+ } else {
+ ctx.Status(http.StatusNoContent)
+ }
+}
+
+func EngagementLikeRecipe(ctx *gin.Context) {
+ deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
+
+ if !domain.IsLoggedIn(ctx) {
+ ctx.Header("HX-Redirect", domain.WEB_LOGIN)
+ ctx.Status(http.StatusOK)
+ return
+ }
+
+ id := ctx.Param("id")
+ recipeId, _ := strconv.Atoi(id)
+ userId := ctx.MustGet("userId").(int)
+
+ if _, err := deps.EngagementService.UserLikeRecipe(userId, recipeId); err != nil {
+ ctx.JSON(http.StatusInternalServerError, gin.H{
+ "status": http.StatusInternalServerError,
+ "message": err.Error(),
+ })
+ } else {
+ ctx.Status(http.StatusNoContent)
+ }
+}
+
+func EngagementMakeRecipe(ctx *gin.Context) {
+ deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
+
+ if !domain.IsLoggedIn(ctx) {
+ ctx.Header("HX-Redirect", domain.WEB_LOGIN)
+ ctx.Status(http.StatusOK)
+ return
+ }
+
+ id := ctx.Param("id")
+ recipeId, _ := strconv.Atoi(id)
+ userId := ctx.MustGet("userId").(int)
+
+ if _, err := deps.EngagementService.UserMakeRecipe(userId, recipeId); err != nil {
+ ctx.JSON(http.StatusInternalServerError, gin.H{
+ "status": http.StatusInternalServerError,
+ "message": err.Error(),
+ })
+ } else {
+ ctx.Status(http.StatusNoContent)
+ }
+}
diff --git a/internal/app/server/server.go b/internal/app/server/server.go
index 33865f9..fee14fd 100644
--- a/internal/app/server/server.go
+++ b/internal/app/server/server.go
@@ -186,6 +186,11 @@ func (s *Server) Setup() *Server {
router_api.POST("/recipe/search", handlers.SearchRecipes)
router_api.GET("/user/recipes", handlers.GetUserRecipes)
+ // Engagement endpoints
+ router_api.POST("/engagement/view/:id", handlers.EngagementViewRecipe)
+ router_api.POST("/engagement/like/:id", handlers.EngagementLikeRecipe)
+ router_api.POST("/engagement/make/:id", handlers.EngagementMakeRecipe)
+
// Catch un-routed URLS
s.Router.NoRoute(func(ctx *gin.Context) {
path := ctx.Request.URL.Path
diff --git a/internal/app/service/engagement_service.go b/internal/app/service/engagement_service.go
index 1ff43fb..27f0971 100644
--- a/internal/app/service/engagement_service.go
+++ b/internal/app/service/engagement_service.go
@@ -53,6 +53,20 @@ func (s *EngagementService) UserLikeRecipe(userId, recipeId int) (domain.Engagem
return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementLiked)
}
+// UserLikeRecipe requires a user ID and a recipe ID to create an engagement record in the database.
+// A message will be generated using the recipe data and then used to add a like engagement to the
+// database.
+func (s *EngagementService) UserMakeRecipe(userId, recipeId int) (domain.Engagement, error) {
+ recipe, err := s.recipeRepository.GetRecipe(recipeId)
+ if err != nil {
+ return domain.Engagement{}, err
+ }
+
+ message := fmt.Sprintf("Made \"%s\"", recipe.Title)
+
+ return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementMade)
+}
+
// GetUserEngagement returns a list of the users most recent engagement entries. The number of records
// is determined by the limit passed into this function. The results are sorted, newest-to-oldest.
func (s *EngagementService) GetUserEngagement(userId, limit int) ([]domain.Engagement, error) {
diff --git a/internal/domain/engagement/service.go b/internal/domain/engagement/service.go
index a4884b4..2643e91 100644
--- a/internal/domain/engagement/service.go
+++ b/internal/domain/engagement/service.go
@@ -3,5 +3,6 @@ package domain
type EngagementService interface {
UserViewRecipe(userId, recipeId int) (Engagement, error)
UserLikeRecipe(userId, recipeId int) (Engagement, error)
+ UserMakeRecipe(userId, recipeId int) (Engagement, error)
GetUserEngagement(userId, limit int) ([]Engagement, error)
}
diff --git a/internal/domain/server/routes.go b/internal/domain/server/routes.go
index 194940e..221e1fa 100644
--- a/internal/domain/server/routes.go
+++ b/internal/domain/server/routes.go
@@ -25,6 +25,10 @@ const API_AUTH_LOGOUT = VERSION + API + "/auth/logout"
const API_CREATE_RECIPE = VERSION + API + "/recipe"
const API_SEARCH_RECIPES = VERSION + API + "/recipe/search"
+const API_ENGAGEMENT_VIEW = VERSION + API + "/engagement/view/%d"
+const API_ENGAGEMENT_LIKE = VERSION + API + "/engagement/like/%d"
+const API_ENGAGEMENT_MAKE = VERSION + API + "/engagement/make/%d"
+
// State prefixed routes
const STATE_TAGS_CREATE = VERSION + WEB + STATE + "/tags"
const STATE_TAGS_DELETE = VERSION + WEB + STATE + "/tags/delete"
diff --git a/internal/infrastructure/database/repository/engagement_repository.go b/internal/infrastructure/database/repository/engagement_repository.go
index 4d9faf8..50ab3a4 100644
--- a/internal/infrastructure/database/repository/engagement_repository.go
+++ b/internal/infrastructure/database/repository/engagement_repository.go
@@ -68,6 +68,8 @@ func (r *EngagementRepository) AddUserEngagement(userId int, message string, eng
// entity. The message should be provided, but a blank string ("") is acceptable. The engagement
// type parameter determines the labeling of the engagement in the database. Any errors will be
// bubbled to the caller.
+//
+// TODO: Disallow users to "make" the same recipe more than once a day
func (r *EngagementRepository) AddUserEntityEngagement(userId, entityId int, message string, engagementType domain.EngagementType) (domain.Engagement, error) {
tx, err := r.db.Begin()
if err != nil {
diff --git a/internal/templates/pages/favorites.templ b/internal/templates/pages/favorites.templ
index 8d0e238..853262f 100644
--- a/internal/templates/pages/favorites.templ
+++ b/internal/templates/pages/favorites.templ
@@ -3,13 +3,13 @@ package templates
import "github.com/haydenhargreaves/Potion/internal/templates/components"
templ FavoritesPage() {
- @components.Navbar("favorites")
-
-
-
-
Page Under Construction
-
Sit tight, this page is coming soon!
-
-
-
+@components.Navbar("favorites")
+
+
+
+
Page Under Construction
+
Sit tight, this page is coming soon!
+
+
+
}
diff --git a/internal/templates/pages/home.templ b/internal/templates/pages/home.templ
index 3cc3a4b..09bf1c3 100644
--- a/internal/templates/pages/home.templ
+++ b/internal/templates/pages/home.templ
@@ -5,112 +5,106 @@ import "github.com/haydenhargreaves/Potion/internal/domain/server"
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
templ introSection() {
-
-
-
-
-
-
- Discover Your Next Favorite Meal
-
-
-
- Welcome to your ultimate recipe hub! Whether you're a seasoned chef or just starting your culinary adventure,
- we're here to inspire. Explore thousands of delicious recipes, from quick weeknight dinners to gourmet delights,
- all at your fingertips. Find exactly what you're craving with our powerful search and intuitive filters, or
- browse our trending dishes for fresh ideas.
-
-
+
+
+
+
+
+
+ Discover Your Next Favorite Meal
+
+
+
+ Welcome to your ultimate recipe hub! Whether you're a seasoned chef or just starting your culinary adventure,
+ we're here to inspire. Explore thousands of delicious recipes, from quick weeknight dinners to gourmet delights,
+ all at your fingertips. Find exactly what you're craving with our powerful search and intuitive filters, or
+ browse our trending dishes for fresh ideas.
+
+
}
templ searchSection() {
-
- @components.BannerText("Craving Something Specific?")
-
- @components.SearchBar(domainRecipe.SearchFilters{}, true, false)
-
-
-
+
+ @components.BannerText("Craving Something Specific?")
+
+ @components.SearchBar(domainRecipe.SearchFilters{}, true, false)
+
+
+
}
templ highlightSection(liked bool) {
-
- @components.BannerText("Recipe of the Week!")
-
- Our 'Recipe of the Week' is the cream of the crop! We handpick it by looking at what recipes
- our community loves most. This isn't just about how many people view a recipe; it's also about
- how many times it's been made, liked, reviewed, and its average rating, all combined to find
- the true fan favorite of the week. It's our way of highlighting the best recipes that truly
- resonate with our users!
-
-
- @components.RecipeCardLarge(false)
-
-
+
+ @components.BannerText("Recipe of the Week!")
+
+ Our 'Recipe of the Week' is the cream of the crop! We handpick it by looking at what recipes
+ our community loves most. This isn't just about how many people view a recipe; it's also about
+ how many times it's been made, liked, reviewed, and its average rating, all combined to find
+ the true fan favorite of the week. It's our way of highlighting the best recipes that truly
+ resonate with our users!
+
+
+ @components.RecipeCardLarge(false)
+
+
}
templ listsSection() {
-
- @components.BannerText("Take Another Look.")
-
-
Recently viewed
-
- @components.RecipeCardSmall("Avocado Toast", "Breakfast - 15 min", "Hayden Hargreaves", true)
- @components.RecipeCardSmall("Fried Chicken", "Dinner - 120 min", "Hayden Hargreaves", false)
- @components.RecipeCardSmall("Classic Butter Chicken", "Dinner - 60 min", "Hayden Hargreaves", false)
- @components.RecipeCardSmall("Avocado Toast", "Breakfast - 15 min", "Hayden Hargreaves", true)
- @components.RecipeCardSmall("Fried Chicken", "Dinner - 120 min", "Hayden Hargreaves", false)
- @components.RecipeCardSmall("Classic Butter Chicken", "Dinner - 60 min", "Hayden Hargreaves", false)
-
-
Make again
-
- @components.RecipeCardSmall("Avocado Toast", "Breakfast - 15 min", "Hayden Hargreaves", true)
- @components.RecipeCardSmall("Fried Chicken", "Dinner - 120 min", "Hayden Hargreaves", false)
- @components.RecipeCardSmall("Classic Butter Chicken", "Dinner - 60 min", "Hayden Hargreaves", false)
- @components.RecipeCardSmall("Avocado Toast", "Breakfast - 15 min", "Hayden Hargreaves", true)
- @components.RecipeCardSmall("Fried Chicken", "Dinner - 120 min", "Hayden Hargreaves", false)
- @components.RecipeCardSmall("Classic Butter Chicken", "Dinner - 60 min", "Hayden Hargreaves", false)
-
-
-
+
+ @components.BannerText("Take Another Look.")
+
+
Recently viewed
+
+ @components.RecipeCardSmall("Avocado Toast", "Breakfast - 15 min", "Hayden Hargreaves", true)
+ @components.RecipeCardSmall("Fried Chicken", "Dinner - 120 min", "Hayden Hargreaves", false)
+ @components.RecipeCardSmall("Classic Butter Chicken", "Dinner - 60 min", "Hayden Hargreaves", false)
+ @components.RecipeCardSmall("Avocado Toast", "Breakfast - 15 min", "Hayden Hargreaves", true)
+ @components.RecipeCardSmall("Fried Chicken", "Dinner - 120 min", "Hayden Hargreaves", false)
+ @components.RecipeCardSmall("Classic Butter Chicken", "Dinner - 60 min", "Hayden Hargreaves", false)
+
+
Make again
+
+ @components.RecipeCardSmall("Avocado Toast", "Breakfast - 15 min", "Hayden Hargreaves", true)
+ @components.RecipeCardSmall("Fried Chicken", "Dinner - 120 min", "Hayden Hargreaves", false)
+ @components.RecipeCardSmall("Classic Butter Chicken", "Dinner - 60 min", "Hayden Hargreaves", false)
+ @components.RecipeCardSmall("Avocado Toast", "Breakfast - 15 min", "Hayden Hargreaves", true)
+ @components.RecipeCardSmall("Fried Chicken", "Dinner - 120 min", "Hayden Hargreaves", false)
+ @components.RecipeCardSmall("Classic Butter Chicken", "Dinner - 60 min", "Hayden Hargreaves", false)
+
+
+
}
templ ctaSection() {
-
+ text-lg md:text-2xl font-bold uppercase tracking-wide">
+ Create Your Recipe!
+
+
}
templ HomePage() {
- @components.Navbar("home")
-
-
- @introSection()
- @searchSection()
- @highlightSection(false)
- @listsSection()
- @ctaSection()
-
-
+@components.Navbar("home")
+
+
+ @introSection()
+ @searchSection()
+ @highlightSection(false)
+ @listsSection()
+ @ctaSection()
+
+
}
diff --git a/internal/templates/pages/list.templ b/internal/templates/pages/list.templ
index 1fcfc39..428e743 100644
--- a/internal/templates/pages/list.templ
+++ b/internal/templates/pages/list.templ
@@ -3,14 +3,13 @@ package templates
import "github.com/haydenhargreaves/Potion/internal/templates/components"
templ ListPage() {
- @components.Navbar("list")
-
-
-
-
Page Under Construction
-
Sit tight, this page is coming soon!
-
-
-
+@components.Navbar("list")
+
+
+
+
Page Under Construction
+
Sit tight, this page is coming soon!
+
+
+
}
-
diff --git a/internal/templates/pages/recipe.templ b/internal/templates/pages/recipe.templ
index 21bff85..37e51f5 100644
--- a/internal/templates/pages/recipe.templ
+++ b/internal/templates/pages/recipe.templ
@@ -1,7 +1,9 @@
package templates
import (
+ "fmt"
domain "github.com/haydenhargreaves/Potion/internal/domain/recipe"
+ domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
"github.com/haydenhargreaves/Potion/internal/templates/components"
"time"
@@ -63,28 +65,6 @@ templ starIcon(filled bool) {
}
}
-templ RecipePage(recipe domain.Recipe, user domainUser.User) {
- @components.Navbar("")
-
-
-
-
-
{ recipe.Title }
-
Author: { user.Name }
-
Category: { recipe.Category }
-
- @metadataSection(recipe)
-
-
About this recipe
-
{ recipe.Description }
-
- @ingredientList(recipe.Ingredients)
- @instructionList(recipe.Instructions)
- @tagList(recipe.Tags, recipe.Created, recipe.Modified)
-
-
-}
-
templ metadataSection(recipe domain.Recipe) {
Ingredients
- for i, ingredient := range ingredients {
- @ingredientListItem(ingredient.Name, ingredient.Quantity, i%2 == 1)
+ for _, ingredient := range ingredients {
+ @ingredientListItem(ingredient.Name, ingredient.Quantity)
}
@@ -152,15 +132,15 @@ templ instructionList(instructions []string) {
templ tagList(tags []domain.Tag, created time.Time, modified *time.Time) {
- if len(tags) > 0 {
-
Tags
-
-
- for _, tag := range tags {
- @tagListItem(tag.Name)
- }
-
- }
+ if len(tags) > 0 {
+
Tags
+
+
+ for _, tag := range tags {
+ @tagListItem(tag.Name)
+ }
+
+ }
Created: { created.Format("January 2, 2006") }
if modified != nil {
@@ -169,13 +149,9 @@ templ tagList(tags []domain.Tag, created time.Time, modified *time.Time) {
}
-templ ingredientListItem(name, quantity string, odd bool) {
+templ ingredientListItem(name, quantity string) {
@@ -193,17 +169,11 @@ templ ingredientListItem(name, quantity string, odd bool) {
}
templ instructionListItem(num int, content string) {
-
-
-
{ num }
+
+
+
{ num }
- { content }
+ { content }
}
@@ -212,3 +182,204 @@ templ tagListItem(content string) {
{ content }
}
+
+templ favoriteButton(favorited bool, id int) {
+ if favorited {
+
+
+
+
+ Unfavorite
+
+ } else {
+
+
+
+
+ Favorite
+
+ }
+}
+
+templ madeButton(id int) {
+
+
+
+
+
+
+ Made This!
+
+}
+
+templ shareButton() {
+
+
+
+
+ Share
+
+}
+
+templ buttonSection(favorited bool, id int) {
+
+ @favoriteButton(favorited, id)
+ @madeButton(id)
+ @shareButton()
+
+}
+
+templ RecipePage(recipe domain.Recipe, user domainUser.User) {
+ @components.Navbar("")
+
+
+
+
+
{ recipe.Title }
+
Author: { user.Name }
+
Category: { recipe.Category }
+
+ @metadataSection(recipe)
+ @buttonSection(false, recipe.Id)
+
+
About this recipe
+
{ recipe.Description }
+
+ @ingredientList(recipe.Ingredients)
+ @instructionList(recipe.Instructions)
+ @tagList(recipe.Tags, recipe.Created, recipe.Modified)
+
+
+ @scripts(recipe.Id)
+}
+
+templ scripts(id int) {
+
+}
diff --git a/internal/templates/pages/recipe_templ.go b/internal/templates/pages/recipe_templ.go
index 3164ecb..d4ad7cb 100644
--- a/internal/templates/pages/recipe_templ.go
+++ b/internal/templates/pages/recipe_templ.go
@@ -9,7 +9,9 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
+ "fmt"
domain "github.com/haydenhargreaves/Potion/internal/domain/recipe"
+ domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
"github.com/haydenhargreaves/Potion/internal/templates/components"
"time"
@@ -109,7 +111,7 @@ func starIcon(filled bool) templ.Component {
})
}
-func RecipePage(recipe domain.Recipe, user domainUser.User) templ.Component {
+func metadataSection(recipe domain.Recipe) 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 {
@@ -130,116 +132,7 @@ func RecipePage(recipe domain.Recipe, user domainUser.User) templ.Component {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = components.Navbar("").Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var5 string
- templ_7745c5c3_Var5, 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: 72, Col: 75}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " Author: ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var6 string
- templ_7745c5c3_Var6, 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: 73, Col: 66}
- }
- _, 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, 7, "
Category: ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var7 string
- templ_7745c5c3_Var7, 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: 74, Col: 69}
- }
- _, 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, 8, "
")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = metadataSection(recipe).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
About this recipe ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var8 string
- templ_7745c5c3_Var8, 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: 79, Col: 49}
- }
- _, 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, 10, "
")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = ingredientList(recipe.Ingredients).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = instructionList(recipe.Instructions).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = tagList(recipe.Tags, recipe.Created, recipe.Modified).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return nil
- })
-}
-
-func metadataSection(recipe domain.Recipe) 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_Var9 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var9 == nil {
- templ_7745c5c3_Var9 = templ.NopComponent
- }
- ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -247,33 +140,33 @@ func metadataSection(recipe domain.Recipe) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
Prep: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
Prep: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var10 string
- templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Prep)
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Prep)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 95, Col: 34}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 75, Col: 34}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " min
Cook: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " min
Cook: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var11 string
- templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Cook)
+ var templ_7745c5c3_Var6 string
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Cook)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 96, Col: 34}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 76, Col: 34}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
+ _, 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, 15, " min
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " min
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -289,38 +182,38 @@ func metadataSection(recipe domain.Recipe) templ.Component {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
switch recipe.Difficulty {
case 1:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
Beginner
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
Beginner
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 2:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
Easy
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
Easy
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 3:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
Intermediate
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
Intermediate
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 4:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
Challenging
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
Challenging
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 5:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
Extreme
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
Extreme
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -328,20 +221,20 @@ func metadataSection(recipe domain.Recipe) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
Serves ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
Serves ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var12 string
- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Serves)
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Serves)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 124, Col: 28}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 104, Col: 28}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
+ _, 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, 24, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -365,22 +258,22 @@ func ingredientList(ingredients []domain.RecipeIngredient) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var13 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var13 == nil {
- templ_7745c5c3_Var13 = templ.NopComponent
+ templ_7745c5c3_Var8 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var8 == nil {
+ templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
Ingredients ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "Ingredients ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- for i, ingredient := range ingredients {
- templ_7745c5c3_Err = ingredientListItem(ingredient.Name, ingredient.Quantity, i%2 == 1).Render(ctx, templ_7745c5c3_Buffer)
+ for _, ingredient := range ingredients {
+ templ_7745c5c3_Err = ingredientListItem(ingredient.Name, ingredient.Quantity).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -404,12 +297,12 @@ func instructionList(instructions []string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var14 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var14 == nil {
- templ_7745c5c3_Var14 = templ.NopComponent
+ templ_7745c5c3_Var9 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var9 == nil {
+ templ_7745c5c3_Var9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
Instructions ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "Instructions ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -419,7 +312,7 @@ func instructionList(instructions []string) templ.Component {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -443,17 +336,17 @@ func tagList(tags []domain.Tag, created time.Time, modified *time.Time) templ.Co
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var15 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var15 == nil {
- templ_7745c5c3_Var15 = templ.NopComponent
+ templ_7745c5c3_Var10 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var10 == nil {
+ templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(tags) > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
Tags ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "Tags ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -463,48 +356,48 @@ func tagList(tags []domain.Tag, created time.Time, modified *time.Time) templ.Co
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
Created: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
Created: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var16 string
- templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(created.Format("January 2, 2006"))
+ var templ_7745c5c3_Var11 string
+ templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(created.Format("January 2, 2006"))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 165, Col: 91}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 145, Col: 91}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
+ _, 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, 33, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if modified != nil {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
Last Modified: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
Last Modified: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var17 string
- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(modified.Format("January 2, 2006"))
+ var templ_7745c5c3_Var12 string
+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(modified.Format("January 2, 2006"))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 167, Col: 92}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 147, Col: 92}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -512,7 +405,7 @@ func tagList(tags []domain.Tag, created time.Time, modified *time.Time) templ.Co
})
}
-func ingredientListItem(name, quantity string, odd bool) templ.Component {
+func ingredientListItem(name, quantity 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 {
@@ -528,53 +421,38 @@ func ingredientListItem(name, quantity string, odd bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var18 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var18 == nil {
- templ_7745c5c3_Var18 = templ.NopComponent
+ templ_7745c5c3_Var13 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var13 == nil {
+ templ_7745c5c3_Var13 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- if odd {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " class=\"p-2 hover:bg-gray-100 transition-all duration-300 rounded-sm flex items-center justify-start bg-[#f8f8f8]\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- } else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " class=\"p-2 hover:bg-gray-100 transition-all duration-300 rounded-sm flex items-center justify-start\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
+ var templ_7745c5c3_Var14 string
+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(quantity)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 167, Col: 45}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "> ")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var19 string
- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(quantity)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 191, Col: 45}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, ": ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, ": ")
+ var templ_7745c5c3_Var15 string
+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(name)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 167, Col: 63}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var20 string
- templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(name)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 191, Col: 63}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -598,53 +476,38 @@ func instructionListItem(num int, content string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var21 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var21 == nil {
- templ_7745c5c3_Var21 = templ.NopComponent
+ templ_7745c5c3_Var16 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var16 == nil {
+ templ_7745c5c3_Var16 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- if num%2 == 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " class=\"p-4 flex items-start gap-x-4 bg-[#f8f8f8]\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- } else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " class=\"p-4 flex items-start gap-x-4\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
+ var templ_7745c5c3_Var17 string
+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(num)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 174, Col: 69}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, ">
")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var22 string
- templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(num)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 204, Col: 48}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
+ 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, 47, "
")
+ 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}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var23 string
- templ_7745c5c3_Var23, 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: 206, Col: 23}
- }
- _, 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, 48, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -653,6 +516,110 @@ func instructionListItem(num int, content string) templ.Component {
}
func tagListItem(content 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_Var19 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var19 == nil {
+ templ_7745c5c3_Var19 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var20 string
+ templ_7745c5c3_Var20, 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: 182, Col: 11}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func favoriteButton(favorited bool, id int) 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)
+ if favorited {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
Unfavorite")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
Favorite")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ return nil
+ })
+}
+
+func madeButton(id int) 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 {
@@ -673,20 +640,252 @@ func tagListItem(content string) templ.Component {
templ_7745c5c3_Var24 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\" hx-trigger=\"click\" hx-swap=\"none\" id=\"make-button\" hx-on:click=\"makeButtonHandler();\" 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
+ }
+ return nil
+ })
+}
+
+func shareButton() 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_Var26 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var26 == nil {
+ templ_7745c5c3_Var26 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " Share ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func buttonSection(favorited bool, id int) 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_Var27 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var27 == nil {
+ templ_7745c5c3_Var27 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = favoriteButton(favorited, id).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = madeButton(id).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = shareButton().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func RecipePage(recipe domain.Recipe, user domainUser.User) 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_Var28 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var28 == nil {
+ templ_7745c5c3_Var28 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = components.Navbar("").Render(ctx, templ_7745c5c3_Buffer)
+ 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
+ }
+ 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}
+ }
+ _, 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: ")
+ 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}
+ }
+ _, 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: ")
+ 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}
+ }
+ _, 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, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = metadataSection(recipe).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = buttonSection(false, recipe.Id).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 ")
+ 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}
+ }
+ _, 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, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = ingredientList(recipe.Ingredients).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = instructionList(recipe.Instructions).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = tagList(recipe.Tags, recipe.Created, recipe.Modified).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = scripts(recipe.Id).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func scripts(id int) 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_Var33 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var33 == nil {
+ templ_7745c5c3_Var33 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/internal/templates/pages/search.templ b/internal/templates/pages/search.templ
index aab9696..693c2a4 100644
--- a/internal/templates/pages/search.templ
+++ b/internal/templates/pages/search.templ
@@ -25,8 +25,8 @@ templ SearchPage(filters domainRecipe.SearchFilters, searchOnLoad bool) {
templ ResultList(recipes []domain.Recipe) {
- for i, recipe := range recipes {
- @searchResult(recipe, i%2 == 1)
+ for _, recipe := range recipes {
+ @searchResult(recipe)
}
if len(recipes) == 0 || recipes == nil {
No results
@@ -36,14 +36,10 @@ templ ResultList(recipes []domain.Recipe) {
}
-templ searchResult(recipe domain.Recipe, odd bool) {
+templ searchResult(recipe domain.Recipe) {
diff --git a/internal/templates/pages/search_templ.go b/internal/templates/pages/search_templ.go
index e2c4bdd..aba17cd 100644
--- a/internal/templates/pages/search_templ.go
+++ b/internal/templates/pages/search_templ.go
@@ -94,8 +94,8 @@ func ResultList(recipes []domain.Recipe) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- for i, recipe := range recipes {
- templ_7745c5c3_Err = searchResult(recipe, i%2 == 1).Render(ctx, templ_7745c5c3_Buffer)
+ for _, recipe := range recipes {
+ templ_7745c5c3_Err = searchResult(recipe).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -119,7 +119,7 @@ func ResultList(recipes []domain.Recipe) templ.Component {
})
}
-func searchResult(recipe domain.Recipe, odd bool) templ.Component {
+func searchResult(recipe domain.Recipe) 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 {
@@ -149,48 +149,33 @@ func searchResult(recipe domain.Recipe, odd bool) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if odd {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " class=\"w-full p-2 border-b border-gray-200 hover:bg-gray-100 duration-200 flex items-center flex-col md:flex-row bg-[#f8f8f8]\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- } else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " class=\"w-full p-2 border-b border-gray-200 hover:bg-gray-100 duration-200 flex items-center flex-col md:flex-row\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, ">
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" class=\"w-full p-2 border-b border-gray-200 hover:bg-gray-100 duration-200 flex items-center flex-col md:flex-row even:bg-[#f8f8f8]\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 51, Col: 18}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 47, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 51, Col: 89}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 47, Col: 89}
}
_, 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, 14, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -201,13 +186,13 @@ func searchResult(recipe domain.Recipe, odd bool) templ.Component {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Total)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 56, Col: 28}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 52, Col: 28}
}
_, 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, 15, " min ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " min ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -223,7 +208,7 @@ func searchResult(recipe domain.Recipe, odd bool) templ.Component {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -231,33 +216,33 @@ func searchResult(recipe domain.Recipe, odd bool) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "Serves ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "Serves ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Serves)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 68, Col: 27}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 64, Col: 27}
}
_, 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, 18, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Description)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 71, Col: 72}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 67, Col: 72}
}
_, 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, 19, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -286,7 +271,7 @@ func servingIconSm() templ.Component {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -315,7 +300,7 @@ func timeIconSm() templ.Component {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -345,12 +330,12 @@ func starIconSm(filled bool) templ.Component {
}
ctx = templ.ClearChildren(ctx)
if filled {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/web/static/css/tailwind.css b/web/static/css/tailwind.css
index b738e44..fc8d34e 100644
--- a/web/static/css/tailwind.css
+++ b/web/static/css/tailwind.css
@@ -9,6 +9,9 @@
monospace;
--color-red-100: oklch(93.6% 0.032 17.717);
--color-red-500: oklch(63.7% 0.237 25.331);
+ --color-red-600: oklch(57.7% 0.245 27.325);
+ --color-green-300: oklch(87.1% 0.15 154.449);
+ --color-green-500: oklch(72.3% 0.219 149.579);
--color-blue-50: oklch(97% 0.014 254.604);
--color-blue-100: oklch(93.2% 0.032 255.585);
--color-blue-200: oklch(88.2% 0.059 254.128);
@@ -367,6 +370,10 @@
width: calc(var(--spacing) * 5);
height: calc(var(--spacing) * 5);
}
+ .size-8 {
+ width: calc(var(--spacing) * 8);
+ height: calc(var(--spacing) * 8);
+ }
.size-10 {
width: calc(var(--spacing) * 10);
height: calc(var(--spacing) * 10);
@@ -422,9 +429,6 @@
.w-1 {
width: calc(var(--spacing) * 1);
}
- .w-1\/2 {
- width: calc(1/2 * 100%);
- }
.w-1\/3 {
width: calc(1/3 * 100%);
}
@@ -464,21 +468,9 @@
.w-full {
width: 100%;
}
- .max-w-1 {
- max-width: calc(var(--spacing) * 1);
- }
- .max-w-1\/2 {
- max-width: calc(1/2 * 100%);
- }
.max-w-2xl {
max-width: var(--container-2xl);
}
- .max-w-4 {
- max-width: calc(var(--spacing) * 4);
- }
- .max-w-4\/5 {
- max-width: calc(4/5 * 100%);
- }
.flex-shrink {
flex-shrink: 1;
}
@@ -491,9 +483,6 @@
.flex-grow {
flex-grow: 1;
}
- .flex-grow-0 {
- flex-grow: 0;
- }
.border-collapse {
border-collapse: collapse;
}
@@ -668,6 +657,12 @@
.border-gray-300 {
border-color: var(--color-gray-300);
}
+ .border-green-300 {
+ border-color: var(--color-green-300);
+ }
+ .border-green-500 {
+ border-color: var(--color-green-500);
+ }
.border-red-500 {
border-color: var(--color-red-500);
}
@@ -766,6 +761,9 @@
.px-5 {
padding-inline: calc(var(--spacing) * 5);
}
+ .px-6 {
+ padding-inline: calc(var(--spacing) * 6);
+ }
.px-8 {
padding-inline: calc(var(--spacing) * 8);
}
@@ -914,6 +912,9 @@
.text-gray-800 {
color: var(--color-gray-800);
}
+ .text-green-500 {
+ color: var(--color-green-500);
+ }
.text-red-500 {
color: var(--color-red-500);
}
@@ -1080,6 +1081,16 @@
color: var(--color-blue-700);
}
}
+ .odd\:bg-\[\#f8f8f8\] {
+ &:nth-child(odd) {
+ background-color: #f8f8f8;
+ }
+ }
+ .even\:bg-\[\#f8f8f8\] {
+ &:nth-child(even) {
+ background-color: #f8f8f8;
+ }
+ }
.even\:bg-gray-50 {
&:nth-child(even) {
background-color: var(--color-gray-50);
@@ -1107,6 +1118,13 @@
}
}
}
+ .hover\:border-blue-300 {
+ &:hover {
+ @media (hover: hover) {
+ border-color: var(--color-blue-300);
+ }
+ }
+ }
.hover\:border-blue-400 {
&:hover {
@media (hover: hover) {
@@ -1233,11 +1251,6 @@
animation: var(--animate-bounce);
}
}
- .sm\:hidden {
- @media (width >= 40rem) {
- display: none;
- }
- }
.sm\:w-3\/4 {
@media (width >= 40rem) {
width: calc(3/4 * 100%);
@@ -1298,9 +1311,10 @@
display: inline;
}
}
- .md\:inline-block {
+ .md\:size-10 {
@media (width >= 48rem) {
- display: inline-block;
+ width: calc(var(--spacing) * 10);
+ height: calc(var(--spacing) * 10);
}
}
.md\:size-12 {
@@ -1476,6 +1490,12 @@
line-height: var(--tw-leading, var(--text-sm--line-height));
}
}
+ .md\:text-xl {
+ @media (width >= 48rem) {
+ font-size: var(--text-xl);
+ line-height: var(--tw-leading, var(--text-xl--line-height));
+ }
+ }
.lg\:flex {
@media (width >= 64rem) {
display: flex;
From bebeb254923368073a33b680b20965450abd47e9 Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Mon, 14 Jul 2025 21:30:45 -0700
Subject: [PATCH 05/10] (DB/FEAT): Implemented toggle favorite in the backend.
The frontend is half wired up, just need to update the button. I also
want to update the recipe methods to return the favorite status. This
will follow very similar to the way I updated the tags. Another method
which can be called to attach the favorite state.
---
doc/TechnicalSpecification.md | 10 ++--
internal/app/handlers/engagement_handler.go | 4 +-
internal/app/server/server.go | 2 +-
internal/app/service/engagement_service.go | 23 +++++--
internal/domain/engagement/repository.go | 1 +
internal/domain/engagement/service.go | 2 +-
internal/domain/server/routes.go | 2 +-
.../migrations/008_create_favorites_table.sql | 14 +++++
.../repository/engagement_repository.go | 60 +++++++++++++++++++
internal/templates/pages/recipe.templ | 2 +-
internal/templates/pages/recipe_templ.go | 6 +-
11 files changed, 107 insertions(+), 19 deletions(-)
create mode 100644 internal/infrastructure/database/migrations/008_create_favorites_table.sql
diff --git a/doc/TechnicalSpecification.md b/doc/TechnicalSpecification.md
index 8af89c5..4f03d6d 100644
--- a/doc/TechnicalSpecification.md
+++ b/doc/TechnicalSpecification.md
@@ -299,11 +299,11 @@ found in **OTHER** section.
- [x] UserId (FK: User.Id) Serial, optional for not logged in users
- [x] Created (Required) date/time stamp
-- [ ] Likes: **Many-to-many** table to represent a list of recipes liked by a user.
- - [ ] ID (PK) *Composite key***
- - [ ] UserId (FK: User.Id, Required) Serial
- - [ ] RecipeId (FK: Recipe.Id, Required) Serial
- - [ ] Created (Required) date/time stamp
+- [x] Favorites: **Many-to-many** table to represent a list of recipes favorites by a user.
+ - [x] ID (PK) *Composite key***
+ - [x] UserId (FK: User.Id, Required) Serial
+ - [x] RecipeId (FK: Recipe.Id, Required) Serial
+ - [x] Created (Required) date/time stamp
- [x] Tags: Represents a single tag that can be had by many recipes.
- [x] ID (PK) Serial
diff --git a/internal/app/handlers/engagement_handler.go b/internal/app/handlers/engagement_handler.go
index 36bcddc..d57bc6b 100644
--- a/internal/app/handlers/engagement_handler.go
+++ b/internal/app/handlers/engagement_handler.go
@@ -33,7 +33,7 @@ func EngagementViewRecipe(ctx *gin.Context) {
}
}
-func EngagementLikeRecipe(ctx *gin.Context) {
+func EngagementFavoriteRecipe(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
if !domain.IsLoggedIn(ctx) {
@@ -46,7 +46,7 @@ func EngagementLikeRecipe(ctx *gin.Context) {
recipeId, _ := strconv.Atoi(id)
userId := ctx.MustGet("userId").(int)
- if _, err := deps.EngagementService.UserLikeRecipe(userId, recipeId); err != nil {
+ if _, err := deps.EngagementService.UserFavoriteRecipe(userId, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": err.Error(),
diff --git a/internal/app/server/server.go b/internal/app/server/server.go
index fee14fd..226aa43 100644
--- a/internal/app/server/server.go
+++ b/internal/app/server/server.go
@@ -188,7 +188,7 @@ func (s *Server) Setup() *Server {
// Engagement endpoints
router_api.POST("/engagement/view/:id", handlers.EngagementViewRecipe)
- router_api.POST("/engagement/like/:id", handlers.EngagementLikeRecipe)
+ router_api.POST("/engagement/favorite/:id", handlers.EngagementFavoriteRecipe)
router_api.POST("/engagement/make/:id", handlers.EngagementMakeRecipe)
// Catch un-routed URLS
diff --git a/internal/app/service/engagement_service.go b/internal/app/service/engagement_service.go
index 27f0971..6aa2c8e 100644
--- a/internal/app/service/engagement_service.go
+++ b/internal/app/service/engagement_service.go
@@ -39,22 +39,35 @@ func (s *EngagementService) UserViewRecipe(userId, recipeId int) (domain.Engagem
return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementViewed)
}
-// UserLikeRecipe requires a user ID and a recipe ID to create an engagement record in the database.
+// UserFavoriteRecipe requires a user ID and a recipe ID to create an engagement record in the database.
// A message will be generated using the recipe data and then used to add a like engagement to the
// database.
-func (s *EngagementService) UserLikeRecipe(userId, recipeId int) (domain.Engagement, error) {
+func (s *EngagementService) UserFavoriteRecipe(userId, recipeId int) (domain.Engagement, error) {
recipe, err := s.recipeRepository.GetRecipe(recipeId)
if err != nil {
return domain.Engagement{}, err
}
- message := fmt.Sprintf("Liked \"%s\"", recipe.Title)
+ // Update the favorites DB
+ liked, err := s.engagementRepository.UserFavoriteRecipeToggle(userId, recipeId)
+ if err != nil {
+ return domain.Engagement{}, err
+ }
+
+ // Determine if this like is a saving or unsaving
+ var message string
+ if liked {
+ message = fmt.Sprintf("Saved \"%s\"", recipe.Title)
+ } else {
+ message = fmt.Sprintf("Unsaved \"%s\"", recipe.Title)
+ }
return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementLiked)
+
}
-// UserLikeRecipe requires a user ID and a recipe ID to create an engagement record in the database.
-// A message will be generated using the recipe data and then used to add a like engagement to the
+// UserMakeRecipe requires a user ID and a recipe ID to create an engagement record in the database.
+// A message will be generated using the recipe data and then used to add a make engagement to the
// database.
func (s *EngagementService) UserMakeRecipe(userId, recipeId int) (domain.Engagement, error) {
recipe, err := s.recipeRepository.GetRecipe(recipeId)
diff --git a/internal/domain/engagement/repository.go b/internal/domain/engagement/repository.go
index 204682a..906a08e 100644
--- a/internal/domain/engagement/repository.go
+++ b/internal/domain/engagement/repository.go
@@ -4,4 +4,5 @@ type EngagementRepository interface {
AddUserEngagement(userId int, message string, engagementType EngagementType) (Engagement, error)
AddUserEntityEngagement(userId, entityId int, message string, engagementType EngagementType) (Engagement, error)
GetUserEngagement(userId, limit int) ([]Engagement, error)
+ UserFavoriteRecipeToggle(userId, recipeId int) (bool, error)
}
diff --git a/internal/domain/engagement/service.go b/internal/domain/engagement/service.go
index 2643e91..815ac47 100644
--- a/internal/domain/engagement/service.go
+++ b/internal/domain/engagement/service.go
@@ -2,7 +2,7 @@ package domain
type EngagementService interface {
UserViewRecipe(userId, recipeId int) (Engagement, error)
- UserLikeRecipe(userId, recipeId int) (Engagement, error)
+ UserFavoriteRecipe(userId, recipeId int) (Engagement, error)
UserMakeRecipe(userId, recipeId int) (Engagement, error)
GetUserEngagement(userId, limit int) ([]Engagement, error)
}
diff --git a/internal/domain/server/routes.go b/internal/domain/server/routes.go
index 221e1fa..a6f2fab 100644
--- a/internal/domain/server/routes.go
+++ b/internal/domain/server/routes.go
@@ -26,7 +26,7 @@ const API_CREATE_RECIPE = VERSION + API + "/recipe"
const API_SEARCH_RECIPES = VERSION + API + "/recipe/search"
const API_ENGAGEMENT_VIEW = VERSION + API + "/engagement/view/%d"
-const API_ENGAGEMENT_LIKE = VERSION + API + "/engagement/like/%d"
+const API_ENGAGEMENT_FAVORITE = VERSION + API + "/engagement/favorite/%d"
const API_ENGAGEMENT_MAKE = VERSION + API + "/engagement/make/%d"
// State prefixed routes
diff --git a/internal/infrastructure/database/migrations/008_create_favorites_table.sql b/internal/infrastructure/database/migrations/008_create_favorites_table.sql
new file mode 100644
index 0000000..8997128
--- /dev/null
+++ b/internal/infrastructure/database/migrations/008_create_favorites_table.sql
@@ -0,0 +1,14 @@
+-- Author: Hayden Hargreaves (hhargreaves2006@gmail.com)
+-- Desc: Create the favorites table.
+-- Date: 07/14/2025
+
+BEGIN;
+
+CREATE TABLE IF NOT EXISTS Favorites (
+ Id SERIAL PRIMARY KEY NOT NULL,
+ UserId INTEGER NOT NULL REFERENCES users(id),
+ RecipeId INTEGER NOT NULL REFERENCES recipes(id),
+ Created TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+COMMIT;
diff --git a/internal/infrastructure/database/repository/engagement_repository.go b/internal/infrastructure/database/repository/engagement_repository.go
index 50ab3a4..e869262 100644
--- a/internal/infrastructure/database/repository/engagement_repository.go
+++ b/internal/infrastructure/database/repository/engagement_repository.go
@@ -2,6 +2,7 @@ package repository
import (
"database/sql"
+ "errors"
"fmt"
"time"
@@ -153,3 +154,62 @@ func (r *EngagementRepository) GetUserEngagement(userId, limit int) ([]domain.En
return engagements, err
}
+
+// UserFavoriteRecipeToggle toggles the status of a users favorite of a recipe. If the user has already
+// favorited the provided recipe, the database entry will be delete, hence removing the favorite. Otherwise,
+// an entry will be created. The NEW status of the users favorite will be returned as the boolean. Any
+// errors will be bubbled to the caller.
+func (r *EngagementRepository) UserFavoriteRecipeToggle(userId, recipeId int) (bool, error) {
+ tx, err := r.db.Begin()
+ if err != nil {
+ tx.Rollback()
+ return false, err
+ }
+
+ query := `
+ SELECT id
+ FROM favorites
+ WHERE userid = $1 AND recipeid = $2
+ `
+
+ var id int
+
+ err = tx.QueryRow(query, userId, recipeId).Scan(&id)
+ if err != nil {
+ if !errors.Is(err, sql.ErrNoRows) {
+ tx.Rollback()
+ return false, fmt.Errorf("Failed to get recipe favorite. %s", err.Error())
+ }
+ }
+
+ // Means we should create
+ var success bool
+ if id == 0 {
+ createQuery := "INSERT INTO favorites (userid, recipeid, created) VALUES ($1, $2, $3);"
+
+ if result, err := tx.Exec(createQuery, userId, recipeId, time.Now()); err != nil {
+ tx.Rollback()
+ return false, fmt.Errorf("Failed to create recipe favorite. %s", err.Error())
+ } else {
+ rows, _ := result.RowsAffected()
+ success = rows == 1
+ }
+ } else {
+ deleteQuery := "DELETE FROM favorites WHERE id = $1;"
+
+ if result, err := tx.Exec(deleteQuery, id); err != nil {
+ tx.Rollback()
+ return false, fmt.Errorf("Failed to remove recipe favorite. %s", err.Error())
+ } else {
+ rows, _ := result.RowsAffected()
+ success = rows == 1
+ }
+ }
+
+ if err := tx.Commit(); err != nil {
+ tx.Rollback()
+ return false, err
+ }
+
+ return success, nil
+}
diff --git a/internal/templates/pages/recipe.templ b/internal/templates/pages/recipe.templ
index 37e51f5..1d8e17f 100644
--- a/internal/templates/pages/recipe.templ
+++ b/internal/templates/pages/recipe.templ
@@ -228,7 +228,7 @@ templ madeButton(id int) {
hx-trigger="click"
hx-swap="none"
id="make-button"
- hx-on:click="makeButtonHandler();"
+ hx-on:click="makeButtonHandler();"
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"
>
Date: Tue, 15 Jul 2025 19:17:41 -0700
Subject: [PATCH 06/10] (FEAT): Updated recipe repo to include recipe favorite
status.
This means we need to pass the user id into the various methods that
call it. But, since it is a pointer, we can use nil if we don't have a
user to check with (this is noted in the service).
---
internal/app/handlers/page_handler.go | 11 ++-
internal/app/service/engagement_service.go | 10 +--
internal/app/service/recipe_service.go | 4 +-
internal/domain/recipe/recipe.go | 4 +-
internal/domain/recipe/repository.go | 3 +-
internal/domain/recipe/service.go | 2 +-
.../database/repository/recipe_repository.go | 55 ++++++++++++-
internal/templates/pages/recipe.templ | 8 +-
internal/templates/pages/recipe_templ.go | 10 +--
web/static/css/tailwind.css | 81 ++++---------------
10 files changed, 96 insertions(+), 92 deletions(-)
diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go
index a11959f..b0e9f16 100644
--- a/internal/app/handlers/page_handler.go
+++ b/internal/app/handlers/page_handler.go
@@ -104,15 +104,22 @@ func RecipePage(ctx *gin.Context) {
return
}
+ // Get signed in user, if they exist
+ var userId *int = nil
+ if domainServer.IsLoggedIn(ctx) {
+ storeId := ctx.MustGet("userId").(int)
+ userId = &storeId
+ }
+
// Get recipe
- recipe, err := deps.RecipeService.GetRecipe(parsed)
+ recipe, err := deps.RecipeService.GetRecipe(parsed, userId)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
ctx.JSON(400, err.Error())
return
}
- // Get user
+ // Get user (owner)
user, err := deps.UserService.GetUser(recipe.UserId)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
diff --git a/internal/app/service/engagement_service.go b/internal/app/service/engagement_service.go
index 6aa2c8e..362b9ad 100644
--- a/internal/app/service/engagement_service.go
+++ b/internal/app/service/engagement_service.go
@@ -29,7 +29,7 @@ func NewEngagementService(engagementRepository domain.EngagementRepository, reci
// A message will be generated using the recipe data and then used to add a view engagement to the
// database.
func (s *EngagementService) UserViewRecipe(userId, recipeId int) (domain.Engagement, error) {
- recipe, err := s.recipeRepository.GetRecipe(recipeId)
+ recipe, err := s.recipeRepository.GetRecipe(recipeId, &userId)
if err != nil {
return domain.Engagement{}, err
}
@@ -43,17 +43,17 @@ func (s *EngagementService) UserViewRecipe(userId, recipeId int) (domain.Engagem
// A message will be generated using the recipe data and then used to add a like engagement to the
// database.
func (s *EngagementService) UserFavoriteRecipe(userId, recipeId int) (domain.Engagement, error) {
- recipe, err := s.recipeRepository.GetRecipe(recipeId)
+ recipe, err := s.recipeRepository.GetRecipe(recipeId, &userId)
if err != nil {
return domain.Engagement{}, err
}
- // Update the favorites DB
+ // Update the favorites DB
liked, err := s.engagementRepository.UserFavoriteRecipeToggle(userId, recipeId)
if err != nil {
return domain.Engagement{}, err
}
-
+
// Determine if this like is a saving or unsaving
var message string
if liked {
@@ -70,7 +70,7 @@ func (s *EngagementService) UserFavoriteRecipe(userId, recipeId int) (domain.Eng
// A message will be generated using the recipe data and then used to add a make engagement to the
// database.
func (s *EngagementService) UserMakeRecipe(userId, recipeId int) (domain.Engagement, error) {
- recipe, err := s.recipeRepository.GetRecipe(recipeId)
+ recipe, err := s.recipeRepository.GetRecipe(recipeId, &userId)
if err != nil {
return domain.Engagement{}, err
}
diff --git a/internal/app/service/recipe_service.go b/internal/app/service/recipe_service.go
index 870f053..87cdae8 100644
--- a/internal/app/service/recipe_service.go
+++ b/internal/app/service/recipe_service.go
@@ -123,8 +123,8 @@ 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)
-func (s *RecipeService) GetRecipe(id int) (*domain.Recipe, error) {
- recipe, err := s.recipeRepository.GetRecipe(id)
+func (s *RecipeService) GetRecipe(id int, userId *int) (*domain.Recipe, error) {
+ recipe, err := s.recipeRepository.GetRecipe(id, userId)
if recipe == nil {
return nil, fmt.Errorf("Failed to get recipe from database. Nil result.")
diff --git a/internal/domain/recipe/recipe.go b/internal/domain/recipe/recipe.go
index 013a091..bad14a2 100644
--- a/internal/domain/recipe/recipe.go
+++ b/internal/domain/recipe/recipe.go
@@ -55,7 +55,8 @@ type RecipeIngredient struct {
// Recipe is the database model of a recipe. There is no need to map to a different model so
// this will remain in the domain. The Tags field should be loaded from the external Tags table,
-// but is still attached to this domain object.
+// but is still attached to this domain object. The Favorite field should also be loaded from
+// the external favorites table, these are user specific.
type Recipe struct {
Id int
Title string
@@ -70,6 +71,7 @@ type Recipe struct {
Modified *time.Time // Pointer to allow null
Created time.Time
Tags []Tag
+ Favorite bool // Per requesting user
}
// SearchFilters is a model which represents the required filters to complete a recipe search.
diff --git a/internal/domain/recipe/repository.go b/internal/domain/recipe/repository.go
index a5c57d9..d1a3a6e 100644
--- a/internal/domain/recipe/repository.go
+++ b/internal/domain/recipe/repository.go
@@ -2,9 +2,10 @@ package domain
type RecipeRepository interface {
CreateRecipe(recipe *Recipe) error
- GetRecipe(id int) (*Recipe, error)
+ GetRecipe(id int, userId *int) (*Recipe, error)
SearchRecipes(filters SearchFilters) ([]Recipe, error)
CreateRecipeTags(recipe Recipe, tags []string) error
GetUserRecipes(id int) ([]Recipe, error)
GetRecipeTags(recipe *Recipe) error
+ GetRecipeFavorite(recipe *Recipe, userId int) error
}
diff --git a/internal/domain/recipe/service.go b/internal/domain/recipe/service.go
index b3466f4..33d919a 100644
--- a/internal/domain/recipe/service.go
+++ b/internal/domain/recipe/service.go
@@ -4,7 +4,7 @@ import "github.com/gin-gonic/gin"
type RecipeService interface {
CreateRecipe(ctx *gin.Context) (*Recipe, error)
- GetRecipe(id int) (*Recipe, error)
+ GetRecipe(id int, userId *int) (*Recipe, error)
SearchRecipes(filters SearchFilters) ([]Recipe, error)
GetUserRecipes(id int) ([]Recipe, error)
}
diff --git a/internal/infrastructure/database/repository/recipe_repository.go b/internal/infrastructure/database/repository/recipe_repository.go
index e92739a..da5c6f7 100644
--- a/internal/infrastructure/database/repository/recipe_repository.go
+++ b/internal/infrastructure/database/repository/recipe_repository.go
@@ -92,7 +92,7 @@ func (r *RecipeRepository) CreateRecipe(recipe *domain.Recipe) error {
// GetRecipe gets a recipe from the database via its ID. The operation is wrapped in a transaction
// for added safety. The repository will not check for a nil result, instead the service will. Callers
// are responsible for protecting against double nil results. Any errors will be bubbled to the caller.
-func (r *RecipeRepository) GetRecipe(id int) (*domain.Recipe, error) {
+func (r *RecipeRepository) GetRecipe(id int, userId *int) (*domain.Recipe, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
@@ -153,7 +153,18 @@ func (r *RecipeRepository) GetRecipe(id int) (*domain.Recipe, error) {
}
// Add tags
- r.GetRecipeTags(&recipe)
+ if err := r.GetRecipeTags(&recipe); err != nil {
+ fmt.Printf("ERROR getting recipe tags. %s\n", err.Error())
+ }
+
+ // Get favorite status, if user id is provided
+ if userId != nil {
+ if err := r.GetRecipeFavorite(&recipe, *userId); err != nil {
+ fmt.Printf("ERROR getting recipe favorite status. %s\n", err.Error())
+ }
+ } else {
+ recipe.Favorite = false
+ }
if err := tx.Commit(); err != nil {
tx.Rollback()
@@ -368,7 +379,9 @@ func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters) ([]domain
}
// Add tags
- r.GetRecipeTags(&recipe)
+ if err := r.GetRecipeTags(&recipe); err != nil {
+ fmt.Printf("ERROR getting recipe tags. %s\n", err.Error())
+ }
recipes = append(recipes, recipe)
}
@@ -517,7 +530,14 @@ func (r *RecipeRepository) GetUserRecipes(id int) ([]domain.Recipe, error) {
}
// Add tags
- r.GetRecipeTags(&recipe)
+ if err := r.GetRecipeTags(&recipe); err != nil {
+ fmt.Printf("ERROR getting recipe tags. %s\n", err.Error())
+ }
+
+ // Get favorite status
+ if err := r.GetRecipeFavorite(&recipe, id); err != nil {
+ fmt.Printf("ERROR getting recipe favorite status. %s\n", err.Error())
+ }
recipes = append(recipes, recipe)
}
@@ -571,3 +591,30 @@ func (r *RecipeRepository) GetRecipeTags(recipe *domain.Recipe) error {
return nil
}
+
+// GetRecipeFavorite requires a recipe to be filled with at least an ID. This function will use the
+// ID defined in the provided recipe to fill the favorite status of the recipe, based on the provided
+// userId. The recipe is modified in place and is not returned. Any errors will be bubbled to the caller.
+func (r *RecipeRepository) GetRecipeFavorite(recipe *domain.Recipe, userId int) error {
+ tx, err := r.db.Begin()
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+
+ query := `
+ SELECT COUNT(*)
+ FROM favorites
+ WHERE recipeid = $1 AND userid = $2;
+ `
+
+ var count int
+ if err := tx.QueryRow(query, recipe.Id, userId).Scan(&count); err != nil {
+ tx.Rollback()
+ return fmt.Errorf("Failed to get recipe favorite. %s", err.Error())
+ }
+
+ recipe.Favorite = count > 0
+
+ return nil
+}
diff --git a/internal/templates/pages/recipe.templ b/internal/templates/pages/recipe.templ
index 1d8e17f..202ac5d 100644
--- a/internal/templates/pages/recipe.templ
+++ b/internal/templates/pages/recipe.templ
@@ -186,10 +186,10 @@ templ tagListItem(content string) {
templ favoriteButton(favorited bool, id int) {
if favorited {
} else {
Category: { recipe.Category }
@metadataSection(recipe)
- @buttonSection(false, recipe.Id)
+ @buttonSection(recipe.Favorite, recipe.Id)
About this recipe
{ recipe.Description }
diff --git a/internal/templates/pages/recipe_templ.go b/internal/templates/pages/recipe_templ.go
index 1b250d8..0e2b1b1 100644
--- a/internal/templates/pages/recipe_templ.go
+++ b/internal/templates/pages/recipe_templ.go
@@ -1,4 +1,4 @@
- // Code generated by templ - DO NOT EDIT.
+// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.865
package templates
@@ -586,13 +586,13 @@ func favoriteButton(favorited bool, id int) templ.Component {
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(domainServer.API_ENGAGEMENT_FAVORITE, id))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 189, Col: 62}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 189, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
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-gray-300 text-gray-800 px-6 py-3 flex-grow hover:bg-gray-50 hover:border-blue-300 duration-300\">
Unfavorite")
+ 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")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -604,7 +604,7 @@ func favoriteButton(favorited bool, id int) templ.Component {
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(domainServer.API_ENGAGEMENT_FAVORITE, id))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 204, Col: 62}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 204, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
@@ -807,7 +807,7 @@ func RecipePage(recipe domain.Recipe, user domainUser.User) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = buttonSection(false, recipe.Id).Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = buttonSection(recipe.Favorite, recipe.Id).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/web/static/css/tailwind.css b/web/static/css/tailwind.css
index fc8d34e..14b37e1 100644
--- a/web/static/css/tailwind.css
+++ b/web/static/css/tailwind.css
@@ -9,7 +9,6 @@
monospace;
--color-red-100: oklch(93.6% 0.032 17.717);
--color-red-500: oklch(63.7% 0.237 25.331);
- --color-red-600: oklch(57.7% 0.245 27.325);
--color-green-300: oklch(87.1% 0.15 154.449);
--color-green-500: oklch(72.3% 0.219 149.579);
--color-blue-50: oklch(97% 0.014 254.604);
@@ -240,9 +239,6 @@
.static {
position: static;
}
- .top-1 {
- top: calc(var(--spacing) * 1);
- }
.top-1\/2 {
top: calc(1/2 * 100%);
}
@@ -252,9 +248,6 @@
.left-0 {
left: calc(var(--spacing) * 0);
}
- .left-1 {
- left: calc(var(--spacing) * 1);
- }
.left-1\/2 {
left: calc(1/2 * 100%);
}
@@ -426,18 +419,12 @@
.min-h-screen {
min-height: 100vh;
}
- .w-1 {
- width: calc(var(--spacing) * 1);
- }
.w-1\/3 {
width: calc(1/3 * 100%);
}
.w-1\/4 {
width: calc(1/4 * 100%);
}
- .w-3 {
- width: calc(var(--spacing) * 3);
- }
.w-3\/4 {
width: calc(3/4 * 100%);
}
@@ -450,9 +437,6 @@
.w-5 {
width: calc(var(--spacing) * 5);
}
- .w-9 {
- width: calc(var(--spacing) * 9);
- }
.w-9\/10 {
width: calc(9/10 * 100%);
}
@@ -471,9 +455,6 @@
.max-w-2xl {
max-width: var(--container-2xl);
}
- .flex-shrink {
- flex-shrink: 1;
- }
.flex-shrink-0 {
flex-shrink: 0;
}
@@ -483,21 +464,10 @@
.flex-grow {
flex-grow: 1;
}
- .border-collapse {
- border-collapse: collapse;
- }
- .-translate-x-1 {
- --tw-translate-x: calc(var(--spacing) * -1);
- translate: var(--tw-translate-x) var(--tw-translate-y);
- }
.-translate-x-1\/2 {
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
- .-translate-y-1 {
- --tw-translate-y: calc(var(--spacing) * -1);
- translate: var(--tw-translate-x) var(--tw-translate-y);
- }
.-translate-y-1\/2 {
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -510,15 +480,9 @@
--tw-scale-y: 50%;
scale: var(--tw-scale-x) var(--tw-scale-y);
}
- .transform {
- transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
- }
.cursor-pointer {
cursor: pointer;
}
- .resize {
- resize: both;
- }
.resize-none {
resize: none;
}
@@ -657,9 +621,6 @@
.border-gray-300 {
border-color: var(--color-gray-300);
}
- .border-green-300 {
- border-color: var(--color-green-300);
- }
.border-green-500 {
border-color: var(--color-green-500);
}
@@ -669,9 +630,6 @@
.border-white {
border-color: var(--color-white);
}
- .bg-\[\#f8f8f8\] {
- background-color: #f8f8f8;
- }
.bg-black {
background-color: var(--color-black);
}
@@ -1132,6 +1090,20 @@
}
}
}
+ .hover\:border-blue-500 {
+ &:hover {
+ @media (hover: hover) {
+ border-color: var(--color-blue-500);
+ }
+ }
+ }
+ .hover\:bg-blue-100 {
+ &:hover {
+ @media (hover: hover) {
+ background-color: var(--color-blue-100);
+ }
+ }
+ }
.hover\:bg-blue-200 {
&:hover {
@media (hover: hover) {
@@ -1537,26 +1509,6 @@
inherits: false;
initial-value: 1;
}
-@property --tw-rotate-x {
- syntax: "*";
- inherits: false;
-}
-@property --tw-rotate-y {
- syntax: "*";
- inherits: false;
-}
-@property --tw-rotate-z {
- syntax: "*";
- inherits: false;
-}
-@property --tw-skew-x {
- syntax: "*";
- inherits: false;
-}
-@property --tw-skew-y {
- syntax: "*";
- inherits: false;
-}
@property --tw-border-style {
syntax: "*";
inherits: false;
@@ -1766,11 +1718,6 @@
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-scale-z: 1;
- --tw-rotate-x: initial;
- --tw-rotate-y: initial;
- --tw-rotate-z: initial;
- --tw-skew-x: initial;
- --tw-skew-y: initial;
--tw-border-style: solid;
--tw-gradient-position: initial;
--tw-gradient-from: #0000;
From 7e355d5edadae1016a6f45fde6406479030883be Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Tue, 15 Jul 2025 19:44:19 -0700
Subject: [PATCH 07/10] (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 {
}
-templ buttonSection(favorited bool, id int) {
+templ buttonSection(favorited bool, id int, loggedIn bool) {
- @favoriteButton(favorited, id)
- @madeButton(id)
+ @favoriteButton(favorited, id, loggedIn)
+ @madeButton(id, loggedIn)
@shareButton()
}
-templ RecipePage(recipe domain.Recipe, user domainUser.User) {
+templ RecipePage(recipe domain.Recipe, user domainUser.User, loggedIn bool) {
@components.Navbar("")
@@ -289,7 +299,7 @@ templ RecipePage(recipe domain.Recipe, user domainUser.User) {
Category: { recipe.Category }
@metadataSection(recipe)
- @buttonSection(recipe.Favorite, recipe.Id)
+ @buttonSection(recipe.Favorite, recipe.Id, loggedIn)
About this recipe
{ recipe.Description }
@@ -377,9 +387,54 @@ templ scripts(id int) {
Made This!
-
`;
}
+ 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, " Favorite ")
+ 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, " Made This! ")
+ 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, " Share ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " Share ")
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 \n Link Copied!\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 \n Failed!\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
}
From e4c1a575be9133ec4b4dc71d36f0c034b6dbf180 Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Tue, 15 Jul 2025 20:14:32 -0700
Subject: [PATCH 08/10] (FEAT): Implemented anon engagements.
This required some fixing of old repo methods, since the nullable user
id was a bit hard to parse. But it should be working now.
---
internal/app/handlers/page_handler.go | 14 +-
internal/app/service/engagement_service.go | 14 ++
internal/domain/engagement/repository.go | 2 +
internal/domain/engagement/service.go | 1 +
.../repository/engagement_repository.go | 152 +++++++++++++++++-
5 files changed, 173 insertions(+), 10 deletions(-)
diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go
index a5c4ec0..7c5c48d 100644
--- a/internal/app/handlers/page_handler.go
+++ b/internal/app/handlers/page_handler.go
@@ -129,15 +129,21 @@ func RecipePage(ctx *gin.Context) {
}
// Add engagement
- if user != nil {
- if _, err = deps.EngagementService.UserViewRecipe(user.Id, recipe.Id); err != nil {
+ if loggedIn {
+ fmt.Println("CALLING USER VIEW")
+ if _, err = deps.EngagementService.UserViewRecipe(*userId, recipe.Id); err != nil {
+ fmt.Printf("ERROR: %s\n", err.Error())
+ ctx.JSON(400, err.Error())
+ return
+ }
+ } else {
+ fmt.Println("CALLING VIEW")
+ if _, err = deps.EngagementService.ViewRecipe(recipe.Id); err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
ctx.JSON(400, err.Error())
return
}
}
- // TODO: Need handling for anon viewing of the recipe
- // 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, loggedIn)
diff --git a/internal/app/service/engagement_service.go b/internal/app/service/engagement_service.go
index 362b9ad..9e7bf92 100644
--- a/internal/app/service/engagement_service.go
+++ b/internal/app/service/engagement_service.go
@@ -25,6 +25,20 @@ func NewEngagementService(engagementRepository domain.EngagementRepository, reci
}
}
+// ViewRecipe requires a user ID and a recipe ID to create an engagement record in the database.
+// A message will be generated using the recipe data and then used to add a view engagement to the
+// database.
+func (s *EngagementService) ViewRecipe(recipeId int) (domain.Engagement, error) {
+ recipe, err := s.recipeRepository.GetRecipe(recipeId, nil)
+ if err != nil {
+ return domain.Engagement{}, err
+ }
+
+ message := fmt.Sprintf("Viewed \"%s\"", recipe.Title)
+
+ return s.engagementRepository.AddEntityEngagement(recipeId, message, domain.EngagementViewed)
+}
+
// UserViewRecipe requires a user ID and a recipe ID to create an engagement record in the database.
// A message will be generated using the recipe data and then used to add a view engagement to the
// database.
diff --git a/internal/domain/engagement/repository.go b/internal/domain/engagement/repository.go
index 906a08e..294b794 100644
--- a/internal/domain/engagement/repository.go
+++ b/internal/domain/engagement/repository.go
@@ -3,6 +3,8 @@ package domain
type EngagementRepository interface {
AddUserEngagement(userId int, message string, engagementType EngagementType) (Engagement, error)
AddUserEntityEngagement(userId, entityId int, message string, engagementType EngagementType) (Engagement, error)
+ AddEngagement(message string, engagementType EngagementType) (Engagement, error)
+ AddEntityEngagement(entityId int, message string, engagementType EngagementType) (Engagement, error)
GetUserEngagement(userId, limit int) ([]Engagement, error)
UserFavoriteRecipeToggle(userId, recipeId int) (bool, error)
}
diff --git a/internal/domain/engagement/service.go b/internal/domain/engagement/service.go
index 815ac47..a4dd0fc 100644
--- a/internal/domain/engagement/service.go
+++ b/internal/domain/engagement/service.go
@@ -1,6 +1,7 @@
package domain
type EngagementService interface {
+ ViewRecipe(recipeId int) (Engagement, error)
UserViewRecipe(userId, recipeId int) (Engagement, error)
UserFavoriteRecipe(userId, recipeId int) (Engagement, error)
UserMakeRecipe(userId, recipeId int) (Engagement, error)
diff --git a/internal/infrastructure/database/repository/engagement_repository.go b/internal/infrastructure/database/repository/engagement_repository.go
index e869262..16cdc9b 100644
--- a/internal/infrastructure/database/repository/engagement_repository.go
+++ b/internal/infrastructure/database/repository/engagement_repository.go
@@ -44,12 +44,13 @@ func (r *EngagementRepository) AddUserEngagement(userId int, message string, eng
`
var engagement domain.Engagement
+ var engUserId sql.NullInt32
if err := tx.QueryRow(query, engagementType, message, userId, time.Now()).Scan(
&engagement.Id,
&engagement.Type,
&engagement.Message,
&engagement.Entity,
- &engagement.UserId,
+ &engUserId,
&engagement.Created,
); err != nil {
tx.Rollback()
@@ -61,10 +62,15 @@ func (r *EngagementRepository) AddUserEngagement(userId int, message string, eng
return domain.Engagement{}, err
}
+ // Is user is valid
+ if engUserId.Valid {
+ engagement.UserId = int(engUserId.Int32)
+ }
+
return engagement, nil
}
-// AddUserEngagement creates an engagement record in the database with the user ID provided. This
+// AddUserEntityEngagement creates an engagement record in the database with the user ID provided. This
// function requires an entity ID as it should be used when there is a reference to external an
// entity. The message should be provided, but a blank string ("") is acceptable. The engagement
// type parameter determines the labeling of the engagement in the database. Any errors will be
@@ -87,12 +93,13 @@ func (r *EngagementRepository) AddUserEntityEngagement(userId, entityId int, mes
`
var engagement domain.Engagement
+ var engUserId sql.NullInt32
if err := tx.QueryRow(query, engagementType, message, entityId, userId, time.Now()).Scan(
&engagement.Id,
&engagement.Type,
&engagement.Message,
&engagement.Entity,
- &engagement.UserId,
+ &engUserId,
&engagement.Created,
); err != nil {
tx.Rollback()
@@ -104,6 +111,133 @@ func (r *EngagementRepository) AddUserEntityEngagement(userId, entityId int, mes
return domain.Engagement{}, err
}
+ // Is user is valid
+ if engUserId.Valid {
+ engagement.UserId = int(engUserId.Int32)
+ }
+
+ return engagement, nil
+}
+
+// AddEngagement creates an engagement record in the database without any user. This function does
+// not accept an entity ID as it should be used when there is no need to reference an entity or user.
+// The message should be provided, but a blank string ("") is acceptable. The engagement type
+// parameter determines the labeling of the engagement in the database. Any errors will be bubbled
+// to the caller.
+//
+// List of allowed engagements: viewed, shared
+func (r *EngagementRepository) AddEngagement(message string, engagementType domain.EngagementType) (domain.Engagement, error) {
+ // Prevent invalid engagement types
+ switch engagementType {
+ case domain.EngagementViewed:
+ case domain.EngagementShared:
+ break
+ case domain.EngagementMade:
+ case domain.EngagementLiked:
+ case domain.EngagementReviewed:
+ case domain.EngagementRated:
+ return domain.Engagement{}, fmt.Errorf("Attempting to use disallowed anonymous engagement type.")
+ }
+
+ tx, err := r.db.Begin()
+ if err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, err
+ }
+
+ query := `
+ INSERT INTO Engagements (
+ type, message, entity, userid, created
+ ) VALUES (
+ $1, $2, NULL, NULL, $3
+ ) RETURNING *;
+ `
+
+ var engagement domain.Engagement
+ var userId sql.NullInt32
+ if err := tx.QueryRow(query, engagementType, message, time.Now()).Scan(
+ &engagement.Id,
+ &engagement.Type,
+ &engagement.Message,
+ &engagement.Entity,
+ &userId,
+ &engagement.Created,
+ ); err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, fmt.Errorf("Failed to insert engagement into database. %s", err.Error())
+ }
+
+ if err := tx.Commit(); err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, err
+ }
+
+ // Is user is valid
+ if userId.Valid {
+ engagement.UserId = int(userId.Int32)
+ }
+
+ return engagement, nil
+}
+
+// AddEntityEngagement creates an engagement record in the database without any user. This function
+// requires an entity ID as it should be used when there is a reference to external an entity.
+// The message should be provided, but a blank string ("") is acceptable. The engagement type
+// parameter determines the labeling of the engagement in the database. Any errors will be
+// bubbled to the caller.
+//
+// List of allowed engagements: viewed, shared
+func (r *EngagementRepository) AddEntityEngagement(entityId int, message string, engagementType domain.EngagementType) (domain.Engagement, error) {
+ // Prevent invalid engagement types
+ switch engagementType {
+ case domain.EngagementViewed:
+ case domain.EngagementShared:
+ break
+ case domain.EngagementMade:
+ case domain.EngagementLiked:
+ case domain.EngagementReviewed:
+ case domain.EngagementRated:
+ return domain.Engagement{}, fmt.Errorf("Attempting to use disallowed anonymous engagement type.")
+ }
+
+ tx, err := r.db.Begin()
+ if err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, err
+ }
+
+ query := `
+ INSERT INTO Engagements (
+ type, message, entity, userid, created
+ ) VALUES (
+ $1, $2, $3, NULL, $4
+ ) RETURNING *;
+ `
+
+ var engagement domain.Engagement
+ var userId sql.NullInt32
+ if err := tx.QueryRow(query, engagementType, message, entityId, time.Now()).Scan(
+ &engagement.Id,
+ &engagement.Type,
+ &engagement.Message,
+ &engagement.Entity,
+ &userId,
+ &engagement.Created,
+ ); err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, fmt.Errorf("Failed to insert engagement into database. %s", err.Error())
+ }
+
+ if err := tx.Commit(); err != nil {
+ tx.Rollback()
+ return domain.Engagement{}, err
+ }
+
+ // Is user is valid
+ if userId.Valid {
+ engagement.UserId = int(userId.Int32)
+ }
+
return engagement, nil
}
@@ -132,18 +266,24 @@ func (r *EngagementRepository) GetUserEngagement(userId, limit int) ([]domain.En
var engagements []domain.Engagement
for rows.Next() {
var engagement domain.Engagement
+ var engUserId sql.NullInt32
if err := rows.Scan(
&engagement.Id,
&engagement.Type,
&engagement.Message,
&engagement.Entity,
- &engagement.UserId,
+ &engUserId,
&engagement.Created,
); err != nil {
tx.Rollback()
return []domain.Engagement{}, fmt.Errorf("Failed to scan user engagement. %s", err.Error())
}
+ // Add user if valid
+ if engUserId.Valid {
+ engagement.UserId = int(engUserId.Int32)
+ }
+
engagements = append(engagements, engagement)
}
@@ -157,7 +297,7 @@ func (r *EngagementRepository) GetUserEngagement(userId, limit int) ([]domain.En
// UserFavoriteRecipeToggle toggles the status of a users favorite of a recipe. If the user has already
// favorited the provided recipe, the database entry will be delete, hence removing the favorite. Otherwise,
-// an entry will be created. The NEW status of the users favorite will be returned as the boolean. Any
+// an entry will be created. The NEW status of the users favorite will be returned as the boolean. Any
// errors will be bubbled to the caller.
func (r *EngagementRepository) UserFavoriteRecipeToggle(userId, recipeId int) (bool, error) {
tx, err := r.db.Begin()
@@ -173,7 +313,7 @@ func (r *EngagementRepository) UserFavoriteRecipeToggle(userId, recipeId int) (b
`
var id int
-
+
err = tx.QueryRow(query, userId, recipeId).Scan(&id)
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
From 2a33edc8f6ca5eacab05fb8cd2a4dfc9b136ac5b Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Tue, 15 Jul 2025 21:19:47 -0700
Subject: [PATCH 09/10] (FEAT): Implemented API for the share engagement.
This includes user and no user routes! Now wired to the frontend,
however, it will still create an engagement even if it fails...
---
internal/app/handlers/engagement_handler.go | 49 +++++++--
internal/app/handlers/page_handler.go | 2 -
internal/app/server/server.go | 1 +
internal/app/service/engagement_service.go | 28 ++++++
internal/domain/engagement/service.go | 2 +
internal/domain/server/routes.go | 1 +
internal/templates/pages/recipe.templ | 31 +++---
internal/templates/pages/recipe_templ.go | 105 +++++++++++---------
8 files changed, 148 insertions(+), 71 deletions(-)
diff --git a/internal/app/handlers/engagement_handler.go b/internal/app/handlers/engagement_handler.go
index d57bc6b..dcca6d5 100644
--- a/internal/app/handlers/engagement_handler.go
+++ b/internal/app/handlers/engagement_handler.go
@@ -8,24 +8,55 @@ import (
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
)
-
-
func EngagementViewRecipe(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
- id := ctx.Param("id")
+ recipeId, _ := strconv.Atoi(ctx.Param("id"))
if !domain.IsLoggedIn(ctx) {
- // TODO: Anon view
- ctx.Status(http.StatusNoContent)
+ if _, err := deps.EngagementService.ViewRecipe(recipeId); err != nil {
+ ctx.JSON(http.StatusInternalServerError, gin.H{
+ "status": http.StatusInternalServerError,
+ "message": err.Error(),
+ })
+ } else {
+ ctx.Status(http.StatusNoContent)
+ }
return
}
- recipeId, _ := strconv.Atoi(id)
userId := ctx.MustGet("userId").(int)
if _, err := deps.EngagementService.UserViewRecipe(userId, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
- "status": http.StatusInternalServerError,
+ "status": http.StatusInternalServerError,
+ "message": err.Error(),
+ })
+ } else {
+ ctx.Status(http.StatusNoContent)
+ }
+}
+
+func EngagementShareRecipe(ctx *gin.Context) {
+ deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
+ recipeId, _ := strconv.Atoi(ctx.Param("id"))
+
+ if !domain.IsLoggedIn(ctx) {
+ if _, err := deps.EngagementService.ShareRecipe(recipeId); err != nil {
+ ctx.JSON(http.StatusInternalServerError, gin.H{
+ "status": http.StatusInternalServerError,
+ "message": err.Error(),
+ })
+ } else {
+ ctx.Status(http.StatusNoContent)
+ }
+ return
+ }
+
+ userId := ctx.MustGet("userId").(int)
+
+ if _, err := deps.EngagementService.UserShareRecipe(userId, recipeId); err != nil {
+ ctx.JSON(http.StatusInternalServerError, gin.H{
+ "status": http.StatusInternalServerError,
"message": err.Error(),
})
} else {
@@ -48,7 +79,7 @@ func EngagementFavoriteRecipe(ctx *gin.Context) {
if _, err := deps.EngagementService.UserFavoriteRecipe(userId, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
- "status": http.StatusInternalServerError,
+ "status": http.StatusInternalServerError,
"message": err.Error(),
})
} else {
@@ -71,7 +102,7 @@ func EngagementMakeRecipe(ctx *gin.Context) {
if _, err := deps.EngagementService.UserMakeRecipe(userId, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
- "status": http.StatusInternalServerError,
+ "status": http.StatusInternalServerError,
"message": err.Error(),
})
} else {
diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go
index 7c5c48d..3b36eb4 100644
--- a/internal/app/handlers/page_handler.go
+++ b/internal/app/handlers/page_handler.go
@@ -130,14 +130,12 @@ func RecipePage(ctx *gin.Context) {
// Add engagement
if loggedIn {
- fmt.Println("CALLING USER VIEW")
if _, err = deps.EngagementService.UserViewRecipe(*userId, recipe.Id); err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
ctx.JSON(400, err.Error())
return
}
} else {
- fmt.Println("CALLING VIEW")
if _, err = deps.EngagementService.ViewRecipe(recipe.Id); err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
ctx.JSON(400, err.Error())
diff --git a/internal/app/server/server.go b/internal/app/server/server.go
index 226aa43..091e464 100644
--- a/internal/app/server/server.go
+++ b/internal/app/server/server.go
@@ -188,6 +188,7 @@ func (s *Server) Setup() *Server {
// Engagement endpoints
router_api.POST("/engagement/view/:id", handlers.EngagementViewRecipe)
+ router_api.POST("/engagement/share/:id", handlers.EngagementShareRecipe)
router_api.POST("/engagement/favorite/:id", handlers.EngagementFavoriteRecipe)
router_api.POST("/engagement/make/:id", handlers.EngagementMakeRecipe)
diff --git a/internal/app/service/engagement_service.go b/internal/app/service/engagement_service.go
index 9e7bf92..54ec6a9 100644
--- a/internal/app/service/engagement_service.go
+++ b/internal/app/service/engagement_service.go
@@ -39,6 +39,20 @@ func (s *EngagementService) ViewRecipe(recipeId int) (domain.Engagement, error)
return s.engagementRepository.AddEntityEngagement(recipeId, message, domain.EngagementViewed)
}
+// ShareRecipe requires a user ID and a recipe ID to create an engagement record in the database.
+// A message will be generated using the recipe data and then used to add a view engagement to the
+// database.
+func (s *EngagementService) ShareRecipe(recipeId int) (domain.Engagement, error) {
+ recipe, err := s.recipeRepository.GetRecipe(recipeId, nil)
+ if err != nil {
+ return domain.Engagement{}, err
+ }
+
+ message := fmt.Sprintf("Shared \"%s\"", recipe.Title)
+
+ return s.engagementRepository.AddEntityEngagement(recipeId, message, domain.EngagementShared)
+}
+
// UserViewRecipe requires a user ID and a recipe ID to create an engagement record in the database.
// A message will be generated using the recipe data and then used to add a view engagement to the
// database.
@@ -94,6 +108,20 @@ func (s *EngagementService) UserMakeRecipe(userId, recipeId int) (domain.Engagem
return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementMade)
}
+// UserShareRecipe requires a user ID and a recipe ID to create an engagement record in the database.
+// A message will be generated using the recipe data and then used to add a make engagement to the
+// database.
+func (s *EngagementService) UserShareRecipe(userId, recipeId int) (domain.Engagement, error) {
+ recipe, err := s.recipeRepository.GetRecipe(recipeId, &userId)
+ if err != nil {
+ return domain.Engagement{}, err
+ }
+
+ message := fmt.Sprintf("Shared \"%s\"", recipe.Title)
+
+ return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementShared)
+}
+
// GetUserEngagement returns a list of the users most recent engagement entries. The number of records
// is determined by the limit passed into this function. The results are sorted, newest-to-oldest.
func (s *EngagementService) GetUserEngagement(userId, limit int) ([]domain.Engagement, error) {
diff --git a/internal/domain/engagement/service.go b/internal/domain/engagement/service.go
index a4dd0fc..4c3a0ff 100644
--- a/internal/domain/engagement/service.go
+++ b/internal/domain/engagement/service.go
@@ -2,8 +2,10 @@ package domain
type EngagementService interface {
ViewRecipe(recipeId int) (Engagement, error)
+ ShareRecipe(recipeId int) (Engagement, error)
UserViewRecipe(userId, recipeId int) (Engagement, error)
UserFavoriteRecipe(userId, recipeId int) (Engagement, error)
UserMakeRecipe(userId, recipeId int) (Engagement, error)
+ UserShareRecipe(userId, recipeId int) (Engagement, error)
GetUserEngagement(userId, limit int) ([]Engagement, error)
}
diff --git a/internal/domain/server/routes.go b/internal/domain/server/routes.go
index a6f2fab..b6823da 100644
--- a/internal/domain/server/routes.go
+++ b/internal/domain/server/routes.go
@@ -26,6 +26,7 @@ const API_CREATE_RECIPE = VERSION + API + "/recipe"
const API_SEARCH_RECIPES = VERSION + API + "/recipe/search"
const API_ENGAGEMENT_VIEW = VERSION + API + "/engagement/view/%d"
+const API_ENGAGEMENT_SHARE = VERSION + API + "/engagement/share/%d"
const API_ENGAGEMENT_FAVORITE = VERSION + API + "/engagement/favorite/%d"
const API_ENGAGEMENT_MAKE = VERSION + API + "/engagement/make/%d"
diff --git a/internal/templates/pages/recipe.templ b/internal/templates/pages/recipe.templ
index f853b55..3ce0723 100644
--- a/internal/templates/pages/recipe.templ
+++ b/internal/templates/pages/recipe.templ
@@ -189,11 +189,11 @@ templ favoriteButton(favorited bool, id int, loggedIn bool) {
hx-post={ fmt.Sprintf(domainServer.API_ENGAGEMENT_FAVORITE, id) }
hx-trigger="click"
hx-swap="none"
- if loggedIn {
- hx-on:click="favoriteButtonHandler();"
- }
+ if loggedIn {
+ hx-on:click="favoriteButtonHandler();"
+ }
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"
+ id="favorite-button"
>
}
-templ shareButton() {
+templ shareButton(id int) {
@@ -284,7 +287,7 @@ templ buttonSection(favorited bool, id int, loggedIn bool) {
@favoriteButton(favorited, id, loggedIn)
@madeButton(id, loggedIn)
- @shareButton()
+ @shareButton(id)
}
diff --git a/internal/templates/pages/recipe_templ.go b/internal/templates/pages/recipe_templ.go
index e26ba3f..3f81041 100644
--- a/internal/templates/pages/recipe_templ.go
+++ b/internal/templates/pages/recipe_templ.go
@@ -691,7 +691,7 @@ func madeButton(id int, loggedIn bool) templ.Component {
})
}
-func shareButton() templ.Component {
+func shareButton(id int) 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 {
@@ -712,7 +712,20 @@ func shareButton() templ.Component {
templ_7745c5c3_Var26 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " Share ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " Share ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -736,12 +749,12 @@ func buttonSection(favorited bool, id int, loggedIn bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var27 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var27 == nil {
- templ_7745c5c3_Var27 = templ.NopComponent
+ templ_7745c5c3_Var28 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var28 == nil {
+ templ_7745c5c3_Var28 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -753,11 +766,11 @@ func buttonSection(favorited bool, id int, loggedIn bool) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = shareButton().Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = shareButton(id).Render(ctx, templ_7745c5c3_Buffer)
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, 53, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -781,55 +794,55 @@ func RecipePage(recipe domain.Recipe, user domainUser.User, loggedIn bool) templ
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var28 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var28 == nil {
- templ_7745c5c3_Var28 = templ.NopComponent
+ templ_7745c5c3_Var29 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var29 == nil {
+ templ_7745c5c3_Var29 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = components.Navbar("").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- 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: 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, 54, " Author: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var30 string
- templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
+ templ_7745c5c3_Var30, 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: 298, Col: 66}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 300, Col: 75}
}
_, 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, 55, " Category: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "
Author: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var31 string
- templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
+ templ_7745c5c3_Var31, 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: 299, Col: 69}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 301, Col: 66}
}
_, 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, 56, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "
Category: ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var32 string
+ templ_7745c5c3_Var32, 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: 302, Col: 69}
+ }
+ _, 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, 57, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -841,20 +854,20 @@ func RecipePage(recipe domain.Recipe, user domainUser.User, loggedIn bool) templ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "
About this recipe ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
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)
+ var templ_7745c5c3_Var33 string
+ templ_7745c5c3_Var33, 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: 305, Col: 49}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/recipe.templ`, Line: 308, Col: 49}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
+ _, 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, 58, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -870,7 +883,7 @@ func RecipePage(recipe domain.Recipe, user domainUser.User, loggedIn bool) templ
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -898,24 +911,24 @@ func scripts(id int) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var33 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var33 == nil {
- templ_7745c5c3_Var33 = templ.NopComponent
+ templ_7745c5c3_Var34 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var34 == nil {
+ templ_7745c5c3_Var34 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "\"\n navigator.clipboard.writeText(url).then(() => {\n button.outerHTML = `\n \n \n \n \n Link Copied!\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 \n Failed!\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
}
From 15cb03589c8b40506cbe9bb7bccafabd79783813 Mon Sep 17 00:00:00 2001
From: Hayden Hargreaves
Date: Tue, 15 Jul 2025 21:44:20 -0700
Subject: [PATCH 10/10] (UI/FIX): Fixed the view engagement to only run on
clicks.
But that means we have to redirect from the handler. I didn't want to,
but I guess that makes it easier when more pages direct to the recipe
page.
---
internal/app/handlers/engagement_handler.go | 7 ++-
internal/app/handlers/page_handler.go | 27 +++++------
internal/app/service/recipe_service.go | 4 +-
.../database/repository/recipe_repository.go | 2 -
internal/templates/pages/profile.templ | 14 +++---
internal/templates/pages/profile_templ.go | 45 ++++++++++---------
internal/templates/pages/recipe.templ | 1 +
internal/templates/pages/recipe_templ.go | 4 +-
internal/templates/pages/search.templ | 10 +++--
internal/templates/pages/search_templ.go | 24 +++++-----
10 files changed, 76 insertions(+), 62 deletions(-)
diff --git a/internal/app/handlers/engagement_handler.go b/internal/app/handlers/engagement_handler.go
index dcca6d5..1f80b7b 100644
--- a/internal/app/handlers/engagement_handler.go
+++ b/internal/app/handlers/engagement_handler.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "fmt"
"net/http"
"strconv"
@@ -19,7 +20,8 @@ func EngagementViewRecipe(ctx *gin.Context) {
"message": err.Error(),
})
} else {
- ctx.Status(http.StatusNoContent)
+ ctx.Header("HX-Redirect", fmt.Sprintf(domain.WEB_RECIPE, recipeId))
+ ctx.Status(http.StatusOK)
}
return
}
@@ -32,7 +34,8 @@ func EngagementViewRecipe(ctx *gin.Context) {
"message": err.Error(),
})
} else {
- ctx.Status(http.StatusNoContent)
+ ctx.Header("HX-Redirect", fmt.Sprintf(domain.WEB_RECIPE, recipeId))
+ ctx.Status(http.StatusOK)
}
}
diff --git a/internal/app/handlers/page_handler.go b/internal/app/handlers/page_handler.go
index 3b36eb4..cadcee1 100644
--- a/internal/app/handlers/page_handler.go
+++ b/internal/app/handlers/page_handler.go
@@ -129,19 +129,20 @@ func RecipePage(ctx *gin.Context) {
}
// Add engagement
- if loggedIn {
- if _, err = deps.EngagementService.UserViewRecipe(*userId, recipe.Id); err != nil {
- fmt.Printf("ERROR: %s\n", err.Error())
- ctx.JSON(400, err.Error())
- return
- }
- } else {
- if _, err = deps.EngagementService.ViewRecipe(recipe.Id); err != nil {
- fmt.Printf("ERROR: %s\n", err.Error())
- ctx.JSON(400, err.Error())
- return
- }
- }
+ // BUG: Don't want to do this here
+ // if loggedIn {
+ // if _, err = deps.EngagementService.UserViewRecipe(*userId, recipe.Id); err != nil {
+ // fmt.Printf("ERROR: %s\n", err.Error())
+ // ctx.JSON(400, err.Error())
+ // return
+ // }
+ // } else {
+ // if _, err = deps.EngagementService.ViewRecipe(recipe.Id); err != nil {
+ // fmt.Printf("ERROR: %s\n", err.Error())
+ // ctx.JSON(400, err.Error())
+ // return
+ // }
+ // }
title := "Potion - View Recipe"
page := pages.RecipePage(*recipe, *user, loggedIn)
diff --git a/internal/app/service/recipe_service.go b/internal/app/service/recipe_service.go
index 0f5a68b..be4ba73 100644
--- a/internal/app/service/recipe_service.go
+++ b/internal/app/service/recipe_service.go
@@ -33,7 +33,7 @@ func NewRecipeService(recipeRepository domain.RecipeRepository) domain.RecipeSer
// occur.
//
// TODO: Implement validation in the API.
-// TODO: Implement image creation and tag creation.
+// TODO: Implement image creation.
func (s *RecipeService) CreateRecipe(ctx *gin.Context) (*domain.Recipe, error) {
// Ensure user is logged in
if !domainServer.IsLoggedIn(ctx) {
@@ -110,7 +110,7 @@ func (s *RecipeService) CreateRecipe(ctx *gin.Context) (*domain.Recipe, error) {
if image != nil {
}
- // TODO: Create the tags in the database
+ // Create the tags
if len(tags) > 0 {
if err := s.recipeRepository.CreateRecipeTags(recipe, tags); err != nil {
return &recipe, fmt.Errorf("Failed to attach/create tags. %s\n", err.Error())
diff --git a/internal/infrastructure/database/repository/recipe_repository.go b/internal/infrastructure/database/repository/recipe_repository.go
index da5c6f7..a0a578d 100644
--- a/internal/infrastructure/database/repository/recipe_repository.go
+++ b/internal/infrastructure/database/repository/recipe_repository.go
@@ -251,8 +251,6 @@ func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters) ([]domain
}
}
- // TODO: Title search somehow...
-
// Merge condition strings
mealString := fmt.Sprintf("(%s)", strings.Join(mealConditions, " OR "))
timeString := fmt.Sprintf("(%s)", strings.Join(timeConditions, " OR "))
diff --git a/internal/templates/pages/profile.templ b/internal/templates/pages/profile.templ
index 1dcf8a2..c13d33e 100644
--- a/internal/templates/pages/profile.templ
+++ b/internal/templates/pages/profile.templ
@@ -44,8 +44,7 @@ templ userDetailsSection(user domainUser.User, recipeCount int) {
} else {
}
@@ -67,7 +66,7 @@ templ recipesSection(recipes []domainRecipe.Recipe) {
My Recipes
if len(recipes) <= 4 {
- for _, recipe :=range recipes {
+ for _, recipe := range recipes {
@recipeListItem(recipe)
}
} else {
@@ -114,10 +113,13 @@ templ activitySection(engagement []domainEngagement.Engagement) {
templ recipeListItem(recipe domainRecipe.Recipe) {
-
-
+
{ recipe.Title }
-
Difficulty: { displayDifficulty(recipe.Difficulty) }
diff --git a/internal/templates/pages/profile_templ.go b/internal/templates/pages/profile_templ.go
index 6e31303..88ed43b 100644
--- a/internal/templates/pages/profile_templ.go
+++ b/internal/templates/pages/profile_templ.go
@@ -90,10 +90,9 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
- templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("https://ui-avatars.com/api/?name=%s+%s&size=150", strings.Split(user.Name, " ")[0],
- strings.Split(user.Name, " ")[1]))
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("https://ui-avatars.com/api/?name=%s+%s&size=150", strings.Split(user.Name, " ")[0], strings.Split(user.Name, " ")[1]))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 48, Col: 40}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 47, Col: 143}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -111,7 +110,7 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 53, Col: 62}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 52, Col: 62}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -124,7 +123,7 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 54, Col: 47}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 53, Col: 47}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@@ -137,7 +136,7 @@ func userDetailsSection(user domainUser.User, recipeCount int) templ.Component {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(recipeCount)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 57, Col: 72}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 56, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@@ -288,36 +287,40 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" hx-trigger=\"click\" hx-swap=\"none\" class=\"text-base md:text-lg hover:text-blue-600 duration-100 cursor-pointer\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 119, Col: 18}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 122, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
Difficulty: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
Difficulty: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(displayDifficulty(recipe.Difficulty))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 123, Col: 81}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 125, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
@@ -330,7 +333,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Total)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 124, Col: 66}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 126, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
@@ -343,7 +346,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 125, Col: 60}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 127, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
@@ -356,7 +359,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(displayDifficulty(recipe.Difficulty))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 128, Col: 81}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 130, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
@@ -369,7 +372,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Duration.Total)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 131, Col: 64}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 133, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
@@ -382,7 +385,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 134, Col: 58}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 136, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
@@ -400,7 +403,7 @@ func recipeListItem(recipe domainRecipe.Recipe) templ.Component {
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(displayTags(recipe.Tags))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 138, Col: 36}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 140, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
@@ -447,7 +450,7 @@ func activityListItem(engagement domainEngagement.Engagement) templ.Component {
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(engagement.Message)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 149, Col: 23}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 151, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
@@ -460,7 +463,7 @@ func activityListItem(engagement domainEngagement.Engagement) templ.Component {
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(engagement.Created.Format("01/02/2006"))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 152, Col: 44}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 154, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
diff --git a/internal/templates/pages/recipe.templ b/internal/templates/pages/recipe.templ
index 3ce0723..c3ea191 100644
--- a/internal/templates/pages/recipe.templ
+++ b/internal/templates/pages/recipe.templ
@@ -322,6 +322,7 @@ templ scripts(id int) {
const before = button.outerHTML;
if (navigator.clipboard && navigator.clipboard.writeText) {
+ // TODO: Fix this to use the real domain somehow
const url = "http://localhost:7331/v1/web/recipe/{{ id }}"
navigator.clipboard.writeText(url).then(() => {
button.outerHTML = `
diff --git a/internal/templates/pages/recipe_templ.go b/internal/templates/pages/recipe_templ.go
index 3f81041..616ed96 100644
--- a/internal/templates/pages/recipe_templ.go
+++ b/internal/templates/pages/recipe_templ.go
@@ -916,13 +916,13 @@ func scripts(id int) templ.Component {
templ_7745c5c3_Var34 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "