diff --git a/doc/TechnicalSpecification.md b/doc/TechnicalSpecification.md index dce8da4..4f03d6d 100644 --- a/doc/TechnicalSpecification.md +++ b/doc/TechnicalSpecification.md @@ -291,18 +291,19 @@ 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*** - - [ ] 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 @@ -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/engagement_handler.go b/internal/app/handlers/engagement_handler.go new file mode 100644 index 0000000..1f80b7b --- /dev/null +++ b/internal/app/handlers/engagement_handler.go @@ -0,0 +1,114 @@ +package handlers + +import ( + "fmt" + "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) + recipeId, _ := strconv.Atoi(ctx.Param("id")) + + if !domain.IsLoggedIn(ctx) { + if _, err := deps.EngagementService.ViewRecipe(recipeId); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "status": http.StatusInternalServerError, + "message": err.Error(), + }) + } else { + ctx.Header("HX-Redirect", fmt.Sprintf(domain.WEB_RECIPE, recipeId)) + ctx.Status(http.StatusOK) + } + return + } + + 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.Header("HX-Redirect", fmt.Sprintf(domain.WEB_RECIPE, recipeId)) + ctx.Status(http.StatusOK) + } +} + +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 { + ctx.Status(http.StatusNoContent) + } +} + +func EngagementFavoriteRecipe(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.UserFavoriteRecipe(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/handlers/page_handler.go b/internal/app/handlers/page_handler.go index d375b78..cadcee1 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)) } @@ -95,15 +104,23 @@ func RecipePage(ctx *gin.Context) { return } + // Get signed in user, if they exist + var userId *int = nil + var loggedIn = domainServer.IsLoggedIn(ctx) + if loggedIn { + 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()) @@ -111,8 +128,24 @@ func RecipePage(ctx *gin.Context) { return } + // Add engagement + // 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) + page := pages.RecipePage(*recipe, *user, loggedIn) ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) } diff --git a/internal/app/server/server.go b/internal/app/server/server.go index 406169b..091e464 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 @@ -183,6 +186,12 @@ 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/share/:id", handlers.EngagementShareRecipe) + router_api.POST("/engagement/favorite/:id", handlers.EngagementFavoriteRecipe) + 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 new file mode 100644 index 0000000..54ec6a9 --- /dev/null +++ b/internal/app/service/engagement_service.go @@ -0,0 +1,129 @@ +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, + } +} + +// 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) +} + +// 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. +func (s *EngagementService) UserViewRecipe(userId, recipeId int) (domain.Engagement, error) { + recipe, err := s.recipeRepository.GetRecipe(recipeId, &userId) + if err != nil { + return domain.Engagement{}, err + } + + message := fmt.Sprintf("Viewed \"%s\"", recipe.Title) + + return s.engagementRepository.AddUserEntityEngagement(userId, recipeId, message, domain.EngagementViewed) +} + +// 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) UserFavoriteRecipe(userId, recipeId int) (domain.Engagement, error) { + recipe, err := s.recipeRepository.GetRecipe(recipeId, &userId) + if err != nil { + return domain.Engagement{}, err + } + + // 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) + +} + +// 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, &userId) + if err != nil { + return domain.Engagement{}, err + } + + message := fmt.Sprintf("Made \"%s\"", recipe.Title) + + 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) { + return s.engagementRepository.GetUserEngagement(userId, limit) +} diff --git a/internal/app/service/recipe_service.go b/internal/app/service/recipe_service.go index 870f053..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()) @@ -123,8 +123,12 @@ 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) +// +// 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) if recipe == nil { return nil, fmt.Errorf("Failed to get recipe from database. Nil result.") 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..294b794 --- /dev/null +++ b/internal/domain/engagement/repository.go @@ -0,0 +1,10 @@ +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 new file mode 100644 index 0000000..4c3a0ff --- /dev/null +++ b/internal/domain/engagement/service.go @@ -0,0 +1,11 @@ +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/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/domain/server/routes.go b/internal/domain/server/routes.go index 194940e..b6823da 100644 --- a/internal/domain/server/routes.go +++ b/internal/domain/server/routes.go @@ -25,6 +25,11 @@ 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_SHARE = VERSION + API + "/engagement/share/%d" +const API_ENGAGEMENT_FAVORITE = VERSION + API + "/engagement/favorite/%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/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/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 new file mode 100644 index 0000000..16cdc9b --- /dev/null +++ b/internal/infrastructure/database/repository/engagement_repository.go @@ -0,0 +1,355 @@ +package repository + +import ( + "database/sql" + "errors" + "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 + var engUserId sql.NullInt32 + if err := tx.QueryRow(query, engagementType, message, userId, time.Now()).Scan( + &engagement.Id, + &engagement.Type, + &engagement.Message, + &engagement.Entity, + &engUserId, + &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 engUserId.Valid { + engagement.UserId = int(engUserId.Int32) + } + + return engagement, nil +} + +// 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 +// 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 { + 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 + var engUserId sql.NullInt32 + if err := tx.QueryRow(query, engagementType, message, entityId, userId, time.Now()).Scan( + &engagement.Id, + &engagement.Type, + &engagement.Message, + &engagement.Entity, + &engUserId, + &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 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 +} + +// 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 + var engUserId sql.NullInt32 + if err := rows.Scan( + &engagement.Id, + &engagement.Type, + &engagement.Message, + &engagement.Entity, + &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) + } + + if err := tx.Commit(); err != nil { + tx.Rollback() + return []domain.Engagement{}, err + } + + 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/infrastructure/database/repository/recipe_repository.go b/internal/infrastructure/database/repository/recipe_repository.go index e92739a..a0a578d 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() @@ -240,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 ")) @@ -368,7 +377,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 +528,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 +589,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/favorites.templ b/internal/templates/pages/favorites.templ index 70b1267..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/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..09bf1c3 100644 --- a/internal/templates/pages/home.templ +++ b/internal/templates/pages/home.templ @@ -5,110 +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() { -
-

- Unleash Your Inner Chef! -

-

- Have a unique recipe idea? Want to share your culinary masterpiece with the world? - It's time to bring your creations to life! -

- +

+ Unleash Your Inner Chef! +

+

+ Have a unique recipe idea? Want to share your culinary masterpiece with the world? + It's time to bring your creations to life! +

+
- Create Your Recipe! - -
+ 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/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, "

Unleash Your Inner Chef!

Have a unique recipe idea? Want to share your culinary masterpiece with the world? It's time to bring your creations to life!

Unleash Your Inner Chef!

Have a unique recipe idea? Want to share your culinary masterpiece with the world? It's time to bring your creations to life!

Create Your Recipe!
") + 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..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/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/profile.templ b/internal/templates/pages/profile.templ index 00e0a76..c13d33e 100644 --- a/internal/templates/pages/profile.templ +++ b/internal/templates/pages/profile.templ @@ -6,6 +6,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 { @@ -35,18 +36,17 @@ func displayTags(tags []domainRecipe.Tag) string { templ userDetailsSection(user domainUser.User, recipeCount int) {
- if user.ImageUrl != "" { - - } else { - - - } + if user.ImageUrl != "" { + + } else { + + }

{ user.Name }

@@ -66,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 { @@ -92,18 +92,14 @@ templ favoritesSection(recipes []domainRecipe.Recipe) {
} -templ activitySection() { +templ activitySection(engagement []domainEngagement.Engagement) {

Recent Activity

Activity section is under construction!

-
} templ recipeListItem(recipe domainRecipe.Recipe) {
  • -

    - +

    { recipe.Title } -

  • } -templ activityListItem() { +templ activityListItem(engagement domainEngagement.Engagement) {
  • - Rated "Spicy Chicken Wings" + { engagement.Message }

    - 2 days ago + { engagement.Created.Format("01/02/2006") }

  • } @@ -169,14 +167,14 @@ templ logoutSection() { } -templ ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe) { +templ ProfilePage(user domainUser.User, recipes []domainRecipe.Recipe, engagement []domainEngagement.Engagement) { @components.Navbar(" profile")
    @userDetailsSection(user, len(recipes)) @recipesSection(recipes) @favoritesSection(recipes) - @activitySection() + @activitySection(engagement) @logoutSection()
    diff --git a/internal/templates/pages/profile_templ.go b/internal/templates/pages/profile_templ.go index b035f02..88ed43b 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: 143} } _, 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 } @@ -276,130 +287,134 @@ 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, "\" 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: 123, 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, 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: 125, 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: 126, 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: 127, 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: 130, 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: 133, 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: 136, 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: 140, 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 +422,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 +443,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: 151, Col: 23} + } + _, 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: 154, Col: 44} + } + _, 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 +493,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, "
    Logout
    ") + 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 +515,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 +531,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 +556,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 +564,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 } diff --git a/internal/templates/pages/recipe.templ b/internal/templates/pages/recipe.templ index 21bff85..c3ea191 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
    @@ -152,15 +132,15 @@ templ instructionList(instructions []string) { templ tagList(tags []domain.Tag, created time.Time, modified *time.Time) {
    - if len(tags) > 0 { -

    Tags

    -
    - - } + if len(tags) > 0 { +

    Tags

    +
    + + }

    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,263 @@ templ tagListItem(content string) { { content } } + +templ favoriteButton(favorited bool, id int, loggedIn bool) { + if favorited { + + } else { + + } +} + +templ madeButton(id int, loggedIn bool) { + +} + +templ shareButton(id int) { + +} + +templ buttonSection(favorited bool, id int, loggedIn bool) { +
    + @favoriteButton(favorited, id, loggedIn) + @madeButton(id, loggedIn) + @shareButton(id) +
    +} + +templ RecipePage(recipe domain.Recipe, user domainUser.User, loggedIn bool) { + @components.Navbar("") +
    +
    + +
    +

    { recipe.Title }

    +

    Author: { user.Name }

    +

    Category: { recipe.Category }

    +
    + @metadataSection(recipe) + @buttonSection(recipe.Favorite, recipe.Id, loggedIn) +
    +

    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..616ed96 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


    ") 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


    ") 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


    ") 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: 32} + } + _, 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,130 @@ 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, 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 { + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +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 { @@ -673,20 +660,275 @@ 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, 46, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +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 { + 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, 50, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +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 { + 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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, loggedIn).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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, 53, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +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 { + 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_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, 54, "
    \"\"

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 string + 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: 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, "

    Author: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var31 string + 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: 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, "

    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 + } + templ_7745c5c3_Err = metadataSection(recipe).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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, 58, "

    About this recipe

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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: 308, Col: 49} + } + _, 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, 59, "

    ") + 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, 60, "
    ") + 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_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, 61, "") 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..58606fd 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,19 +36,17 @@ templ ResultList(recipes []domain.Recipe) {
    } -templ searchResult(recipe domain.Recipe, odd bool) { -

    - { recipe.Title } { recipe.Category } + { recipe.Title }

    @@ -70,7 +68,7 @@ templ searchResult(recipe domain.Recipe, odd bool) {

    { recipe.Description }

    -
    + } templ servingIconSm() { diff --git a/internal/templates/pages/search_templ.go b/internal/templates/pages/search_templ.go index fcaf4e0..4402b67 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 { @@ -140,57 +140,46 @@ func searchResult(recipe domain.Recipe, odd bool) templ.Component { templ_7745c5c3_Var3 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" hx-trigger=\"click\" hx-swap=\"none\" 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] cursor-pointer\">

    ") 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: 49, 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, "

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

    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -201,13 +190,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: 54, 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 +212,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 +220,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: 66, 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: 69, 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 +275,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 +304,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 +334,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 8bd1054..14b37e1 100644 --- a/web/static/css/tailwind.css +++ b/web/static/css/tailwind.css @@ -9,6 +9,8 @@ monospace; --color-red-100: oklch(93.6% 0.032 17.717); --color-red-500: oklch(63.7% 0.237 25.331); + --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); @@ -28,7 +30,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; @@ -238,9 +239,6 @@ .static { position: static; } - .top-1 { - top: calc(var(--spacing) * 1); - } .top-1\/2 { top: calc(1/2 * 100%); } @@ -250,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%); } @@ -368,6 +363,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); @@ -420,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%); } @@ -444,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%); } @@ -465,9 +455,6 @@ .max-w-2xl { max-width: var(--container-2xl); } - .flex-shrink { - flex-shrink: 1; - } .flex-shrink-0 { flex-shrink: 0; } @@ -477,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); @@ -504,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; } @@ -651,15 +621,15 @@ .border-gray-300 { border-color: var(--color-gray-300); } + .border-green-500 { + border-color: var(--color-green-500); + } .border-red-500 { border-color: var(--color-red-500); } .border-white { border-color: var(--color-white); } - .bg-\[\#f8f8f8\] { - background-color: #f8f8f8; - } .bg-black { background-color: var(--color-black); } @@ -701,9 +671,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)); @@ -752,6 +719,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); } @@ -900,6 +870,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); } @@ -1066,6 +1039,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); @@ -1093,6 +1076,13 @@ } } } + .hover\:border-blue-300 { + &:hover { + @media (hover: hover) { + border-color: var(--color-blue-300); + } + } + } .hover\:border-blue-400 { &:hover { @media (hover: hover) { @@ -1100,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) { @@ -1274,6 +1278,17 @@ display: none; } } + .md\:inline { + @media (width >= 48rem) { + display: inline; + } + } + .md\:size-10 { + @media (width >= 48rem) { + width: calc(var(--spacing) * 10); + height: calc(var(--spacing) * 10); + } + } .md\:size-12 { @media (width >= 48rem) { width: calc(var(--spacing) * 12); @@ -1323,6 +1338,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); @@ -1442,6 +1462,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; @@ -1483,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; @@ -1712,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;