Merge pull request '(FIX): Working on the nil derefs, removed tx's from select queries.' (#37) from dev into master
All checks were successful
Deploy application with Docker / build_and_deploy (push) Successful in 41s

Reviewed-on: #37
This commit is contained in:
Hayden Hargreaves 2025-07-27 14:12:08 -07:00
commit b1dc5e67c0
20 changed files with 689 additions and 478 deletions

View File

@ -36,18 +36,7 @@ func GoogleCallback(ctx *gin.Context) {
if jwt, dbUser, googleUserInfo, err := deps.AuthService.GoogleAuthSuccess(state, code); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
} else {
// TODO: Update these values when using a real domain. Maybe an ENV?
domain.SetCookie(ctx, "jwt_token", jwt, time.Hour*24*7)
// ctx.SetCookie(
// "jwt_token",
// jwt,
// int(time.Now().Add(7*24*time.Hour).Sub(time.Now()).Seconds()),
// "/",
// "", // TODO: Real live domain
// false, // TODO: True in prod
// true,
// )
// ctx.JSON(http.StatusOK, gin.H{"jwt": jwt, "googleUserInfo": googleUserInfo, "dbUser": dbUser})
_ = dbUser
_ = googleUserInfo
@ -60,11 +49,7 @@ func GoogleCallback(ctx *gin.Context) {
// require authentication will require the user to sign back in before accessing them again.
// This route will direct the user back to the home page.
func Logout(ctx *gin.Context) {
// TODO: Use same values as the GoogleCallback function
domain.SetCookie(ctx, "jwt_token", "", -1)
domain.SetCookie(ctx, "search-filters", "", -1)
// ctx.SetCookie("jwt_token", "", -1, "/", "", false, true) // TODO: Update settings
// ctx.SetCookie("search-filters", "", -1, "/", "", false, true)
ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME)
}

View File

@ -13,7 +13,15 @@ func EngagementViewRecipe(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
recipeId, _ := strconv.Atoi(ctx.Param("id"))
if !domain.IsLoggedIn(ctx) {
// Ensure user is logged in with a valid account
user := deps.UserService.GetAuthenicatedUser(ctx)
if user == nil {
// Log (stale) user out
domain.SetCookie(ctx, "jwt_token", "", -1)
domain.SetCookie(ctx, "search-filters", "", -1)
}
if !domain.IsLoggedIn(ctx) || user == nil {
if _, err := deps.EngagementService.ViewRecipe(recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
@ -26,9 +34,8 @@ func EngagementViewRecipe(ctx *gin.Context) {
return
}
userId := ctx.MustGet("userId").(int)
if _, err := deps.EngagementService.UserViewRecipe(userId, recipeId); err != nil {
// We caught nil already, we can assume the user exists
if _, err := deps.EngagementService.UserViewRecipe(user.Id, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": err.Error(),
@ -43,7 +50,15 @@ func EngagementShareRecipe(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
recipeId, _ := strconv.Atoi(ctx.Param("id"))
if !domain.IsLoggedIn(ctx) {
// Ensure user is logged in with a valid account
user := deps.UserService.GetAuthenicatedUser(ctx)
if user == nil {
// Log (stale) user out
domain.SetCookie(ctx, "jwt_token", "", -1)
domain.SetCookie(ctx, "search-filters", "", -1)
}
if !domain.IsLoggedIn(ctx) || user == nil {
if _, err := deps.EngagementService.ShareRecipe(recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
@ -55,9 +70,7 @@ func EngagementShareRecipe(ctx *gin.Context) {
return
}
userId := ctx.MustGet("userId").(int)
if _, err := deps.EngagementService.UserShareRecipe(userId, recipeId); err != nil {
if _, err := deps.EngagementService.UserShareRecipe(user.Id, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": err.Error(),
@ -70,7 +83,15 @@ func EngagementShareRecipe(ctx *gin.Context) {
func EngagementFavoriteRecipe(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
if !domain.IsLoggedIn(ctx) {
// Ensure user is logged in with a valid account
user := deps.UserService.GetAuthenicatedUser(ctx)
if user == nil {
// Log (stale) user out
domain.SetCookie(ctx, "jwt_token", "", -1)
domain.SetCookie(ctx, "search-filters", "", -1)
}
if !domain.IsLoggedIn(ctx) || user == nil {
ctx.Header("HX-Redirect", domain.WEB_LOGIN)
ctx.Status(http.StatusOK)
return
@ -78,9 +99,8 @@ func EngagementFavoriteRecipe(ctx *gin.Context) {
id := ctx.Param("id")
recipeId, _ := strconv.Atoi(id)
userId := ctx.MustGet("userId").(int)
if _, err := deps.EngagementService.UserFavoriteRecipe(userId, recipeId); err != nil {
if _, err := deps.EngagementService.UserFavoriteRecipe(user.Id, recipeId); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": err.Error(),

75
internal/app/handlers/page_handler.go Normal file → Executable file
View File

@ -28,6 +28,14 @@ func HomePage(ctx *gin.Context) {
loggedIn := domain.IsLoggedIn(ctx)
// Ensure user is logged in with a valid account
if user := deps.UserService.GetAuthenicatedUser(ctx); user == nil {
// Log (stale) user out
domain.SetCookie(ctx, "jwt_token", "", -1)
domain.SetCookie(ctx, "search-filters", "", -1)
loggedIn = false
}
var page templ.Component
if loggedIn {
userId := ctx.MustGet("userId").(int)
@ -58,7 +66,18 @@ func HomePage(ctx *gin.Context) {
return
}
page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek)
if bytes, err := ctx.Cookie("search-filters"); err != nil {
fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error())
page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek, nil)
} else {
var filters domainRecipe.SearchFilters
if err := json.Unmarshal([]byte(bytes), &filters); err != nil {
fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error())
page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek, nil)
} else {
page = templates.HomePage(true, viewedRecipes, madeRecipes, recipeOfTheWeek, &filters)
}
}
} else {
// Get the recipe of the week
recipeOfTheWeek, err := deps.RecipeService.GetRecipeOfTheWeek(nil)
@ -70,7 +89,18 @@ func HomePage(ctx *gin.Context) {
return
}
page = templates.HomePage(false, nil, nil, recipeOfTheWeek)
if bytes, err := ctx.Cookie("search-filters"); err != nil {
fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error())
page = templates.HomePage(false, nil, nil, recipeOfTheWeek, nil)
} else {
var filters domainRecipe.SearchFilters
if err := json.Unmarshal([]byte(bytes), &filters); err != nil {
fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error())
page = templates.HomePage(false, nil, nil, recipeOfTheWeek, nil)
} else {
page = templates.HomePage(false, nil, nil, recipeOfTheWeek, &filters)
}
}
}
title := "Potion - Home"
@ -91,14 +121,14 @@ func FavoritesPage(ctx *gin.Context) {
// Get filters from cookies
if bytes, err := ctx.Cookie("search-filters"); err != nil {
fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error())
page = pages.FavoritesPage(domainRecipe.SearchFilters{})
page = pages.FavoritesPage(nil)
} else {
var filters domainRecipe.SearchFilters
if err := json.Unmarshal([]byte(bytes), &filters); err != nil {
fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error())
page = pages.FavoritesPage(domainRecipe.SearchFilters{})
page = pages.FavoritesPage(nil)
} else {
page = pages.FavoritesPage(filters)
page = pages.FavoritesPage(&filters)
}
}
@ -106,12 +136,24 @@ func FavoritesPage(ctx *gin.Context) {
}
func CreatePage(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies)
// If not logged in, direct to the login page
if !domainServer.IsLoggedIn(ctx) {
ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN)
return
}
// Ensure user is logged in with a valid account
if user := deps.UserService.GetAuthenicatedUser(ctx); user == nil {
// Log (stale) user out
domain.SetCookie(ctx, "jwt_token", "", -1)
domain.SetCookie(ctx, "search-filters", "", -1)
ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN)
return
}
title := "Potion - Create"
page := pages.CreatePage()
@ -128,6 +170,12 @@ func ProfilePage(ctx *gin.Context) {
// Else, get the user data
deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies)
user := deps.UserService.GetAuthenicatedUser(ctx)
if user == nil {
// User is failing to be found, direct to the login page
ctx.Redirect(http.StatusSeeOther, domainServer.WEB_LOGIN)
return
}
recipes, err := deps.RecipeService.GetUserRecipes(user.Id)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
@ -157,7 +205,7 @@ func ProfilePage(ctx *gin.Context) {
}
title := "Potion - Profile"
page := pages.ProfilePage(user, recipes, favorites, engagements)
page := pages.ProfilePage(*user, recipes, favorites, engagements)
ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page))
}
@ -186,6 +234,15 @@ func RecipePage(ctx *gin.Context) {
// Get signed in user, if they exist
var userId *int = nil
var loggedIn = domainServer.IsLoggedIn(ctx)
// Ensure user is logged in with a valid account
if user := deps.UserService.GetAuthenicatedUser(ctx); user == nil {
// Log (stale) user out
domain.SetCookie(ctx, "jwt_token", "", -1)
domain.SetCookie(ctx, "search-filters", "", -1)
loggedIn = false
}
if loggedIn {
storeId := ctx.MustGet("userId").(int)
userId = &storeId
@ -218,14 +275,14 @@ func SearchPage(ctx *gin.Context) {
// Get filters from cookies
if bytes, err := ctx.Cookie("search-filters"); err != nil {
fmt.Printf("ERROR: Failed to get search-filter cookie. %s\n", err.Error())
page = pages.SearchPage(domainRecipe.SearchFilters{}, false)
page = pages.SearchPage(nil, false)
} else {
var filters domainRecipe.SearchFilters
if err := json.Unmarshal([]byte(bytes), &filters); err != nil {
fmt.Printf("ERROR: Failed to unmarshal search-filter cookie. %s\n", err.Error())
page = pages.SearchPage(domainRecipe.SearchFilters{}, false)
page = pages.SearchPage(nil, false)
} else {
page = pages.SearchPage(filters, true)
page = pages.SearchPage(&filters, true)
}
}

View File

@ -84,6 +84,8 @@ func SearchRecipes(ctx *gin.Context) {
userId = &id
}
// TODO: Not sure if we need to ensure the user is valid here
// We don't care about favorite status, so use false
recipes, err := deps.RecipeService.SearchRecipes(filters, userId, false)
if err != nil {

View File

@ -11,8 +11,16 @@ import (
func GetUserRecipes(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
// Ensure user is logged in with a valid account
user := deps.UserService.GetAuthenicatedUser(ctx)
if user == nil {
// Log (stale) user out
domain.SetCookie(ctx, "jwt_token", "", -1)
domain.SetCookie(ctx, "search-filters", "", -1)
}
// Ensure logged in
if !domain.IsLoggedIn(ctx) {
if !domain.IsLoggedIn(ctx) || user == nil {
ctx.JSON(http.StatusUnauthorized, gin.H{
"status": http.StatusUnauthorized,
"message": "User is not authorized to access this endpoint. Please login to continue.",
@ -21,17 +29,7 @@ func GetUserRecipes(ctx *gin.Context) {
return
}
userId, ok := ctx.MustGet("userId").(int)
if !ok {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": "Unable to access user id from store.",
"recipes": nil,
})
return
}
recipes, err := deps.RecipeService.GetUserRecipes(userId)
recipes, err := deps.RecipeService.GetUserRecipes(user.Id)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{
"status": http.StatusBadRequest,
@ -51,8 +49,16 @@ func GetUserRecipes(ctx *gin.Context) {
func GetUserFavoriteRecipes(ctx *gin.Context) {
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
// Ensure user is logged in with a valid account
user := deps.UserService.GetAuthenicatedUser(ctx)
if user == nil {
// Log (stale) user out
domain.SetCookie(ctx, "jwt_token", "", -1)
domain.SetCookie(ctx, "search-filters", "", -1)
}
// Ensure logged in
if !domain.IsLoggedIn(ctx) {
if !domain.IsLoggedIn(ctx) || user == nil {
ctx.JSON(http.StatusUnauthorized, gin.H{
"status": http.StatusUnauthorized,
"message": "User is not authorized to access this endpoint. Please login to continue.",
@ -61,17 +67,7 @@ func GetUserFavoriteRecipes(ctx *gin.Context) {
return
}
userId, ok := ctx.MustGet("userId").(int)
if !ok {
ctx.JSON(http.StatusInternalServerError, gin.H{
"status": http.StatusInternalServerError,
"message": "Unable to access user id from store.",
"recipes": nil,
})
return
}
recipes, err := deps.RecipeService.GetUserFavoriteRecipes(userId)
recipes, err := deps.RecipeService.GetUserFavoriteRecipes(user.Id)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{
"status": http.StatusBadRequest,

View File

@ -25,19 +25,19 @@ func NewUserService(userRepository domain.UserRepository) domain.UserService {
// user is actually logged in, if not, a blank user will be returned. To ensure success, call the
// `domain.IsLoggedIn()` function first to ensure the user is logged in. If that passes, this
// function should yield a result.
func (s *UserService) GetAuthenicatedUser(ctx *gin.Context) domain.User {
func (s *UserService) GetAuthenicatedUser(ctx *gin.Context) *domain.User {
val, ok := ctx.Get("userId")
if !ok {
return domain.User{}
return nil
}
id := val.(int)
user, err := s.userRepository.GetUser(id)
if err != nil {
return domain.User{}
return nil
}
return *user
return user
}
// GetUser will get a user from the database via its ID. This is not related to the Google ID in

View File

@ -3,6 +3,6 @@ package domain
import "github.com/gin-gonic/gin"
type UserService interface {
GetAuthenicatedUser(ctx *gin.Context) User
GetAuthenicatedUser(ctx *gin.Context) *User
GetUser(id int) (*User, error)
}

View File

@ -244,21 +244,14 @@ func (r *EngagementRepository) AddEntityEngagement(entityId int, message string,
// 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)
rows, err := r.db.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()
@ -275,7 +268,6 @@ func (r *EngagementRepository) GetUserEngagement(userId, limit int) ([]domain.En
&engUserId,
&engagement.Created,
); err != nil {
tx.Rollback()
return []domain.Engagement{}, fmt.Errorf("Failed to scan user engagement. %s", err.Error())
}
@ -287,11 +279,6 @@ func (r *EngagementRepository) GetUserEngagement(userId, limit int) ([]domain.En
engagements = append(engagements, engagement)
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return []domain.Engagement{}, err
}
return engagements, err
}
@ -299,12 +286,6 @@ func (r *EngagementRepository) GetUserEngagement(userId, limit int) ([]domain.En
// type. The number of records is determined by the limit passed into this function. The results are
// sorted, newest-to-oldest. Only results of the provided engagementType will be returned.
func (r *EngagementRepository) GetUserEngagementFiltered(userId, limit int, engagementType domain.EngagementType) ([]domain.Engagement, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return []domain.Engagement{}, err
}
query := `
SELECT id, type, message, entity, userid, created
FROM (
@ -319,9 +300,8 @@ func (r *EngagementRepository) GetUserEngagementFiltered(userId, limit int, enga
LIMIT $3;
`
rows, err := tx.Query(query, userId, engagementType, limit)
rows, err := r.db.Query(query, userId, engagementType, limit)
if err != nil {
tx.Rollback()
return []domain.Engagement{}, fmt.Errorf("Failed to get user engagements. %s", err.Error())
}
defer rows.Close()
@ -338,7 +318,6 @@ func (r *EngagementRepository) GetUserEngagementFiltered(userId, limit int, enga
&engUserId,
&engagement.Created,
); err != nil {
tx.Rollback()
return []domain.Engagement{}, fmt.Errorf("Failed to scan user engagement. %s", err.Error())
}
@ -350,13 +329,7 @@ func (r *EngagementRepository) GetUserEngagementFiltered(userId, limit int, enga
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

View File

@ -94,12 +94,6 @@ func (r *RecipeRepository) CreateRecipe(recipe *domain.Recipe) error {
// 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, userId *int) (*domain.Recipe, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return nil, err
}
query := `
SELECT
id, title, description, instructions, serves, difficulty, duration, category, ingredients,
@ -112,7 +106,7 @@ func (r *RecipeRepository) GetRecipe(id int, userId *int) (*domain.Recipe, error
var ingredientBytes []byte
var recipe domain.Recipe
if err := tx.QueryRow(query, id).Scan(
if err := r.db.QueryRow(query, id).Scan(
&recipe.Id,
&recipe.Title,
&recipe.Description,
@ -167,11 +161,6 @@ func (r *RecipeRepository) GetRecipe(id int, userId *int) (*domain.Recipe, error
recipe.Favorite = false
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
return &recipe, nil
}
@ -180,12 +169,6 @@ func (r *RecipeRepository) GetRecipe(id int, userId *int) (*domain.Recipe, error
// will. Callers are responsible for protecting against double nil results. Any errors will be bubbled
// to the caller.
func (r *RecipeRepository) GetRecipes(ids []int, userId *int) ([]domain.Recipe, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return nil, err
}
query := `
SELECT
id, title, description, instructions, serves, difficulty, duration, category, ingredients, userid, modified, created
@ -196,9 +179,8 @@ func (r *RecipeRepository) GetRecipes(ids []int, userId *int) ([]domain.Recipe,
var recipes []domain.Recipe
rows, err := tx.Query(query, pq.Array(ids))
rows, err := r.db.Query(query, pq.Array(ids))
if err != nil {
tx.Rollback()
return nil, fmt.Errorf("Failed to get recipes. %s", err.Error())
}
defer rows.Close()
@ -266,11 +248,6 @@ func (r *RecipeRepository) GetRecipes(ids []int, userId *int) ([]domain.Recipe,
recipes = append(recipes, recipe)
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
return recipes, nil
}
@ -288,12 +265,6 @@ func isBitActive(bits, pos int) bool {
//
// TODO: Pagination is required, to provide infinite scroll.
func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters, userId *int, favorites bool) ([]domain.Recipe, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return nil, err
}
// Compute meals type filters (there are 7 bits)
var mealConditions []string
for i := range 7 {
@ -442,7 +413,7 @@ func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters, userId *i
query += ";"
// Execute the query
rows, err := tx.Query(query)
rows, err := r.db.Query(query)
if err != nil {
return nil, fmt.Errorf("failed to query recipes: %w", err)
}
@ -513,11 +484,6 @@ func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters, userId *i
recipes = append(recipes, recipe)
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
return recipes, nil
}
@ -584,12 +550,6 @@ func (r *RecipeRepository) CreateRecipeTags(recipe domain.Recipe, tags []string)
// authenticated or exists. If nothing is found, a blank slice will be returned. The resulting list
// is sorted by the created dates, newest first. Any errors will be bubbled to the caller.
func (r *RecipeRepository) GetUserRecipes(id int) ([]domain.Recipe, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return nil, err
}
query := `
SELECT id, title, description, instructions, serves, difficulty, duration, category, ingredients,
userid, modified, created
@ -598,7 +558,7 @@ func (r *RecipeRepository) GetUserRecipes(id int) ([]domain.Recipe, error) {
ORDER BY created DESC;
`
rows, err := tx.Query(query, id)
rows, err := r.db.Query(query, id)
if err != nil {
return nil, fmt.Errorf("Failed to query DB for user recipes. %s\n", err.Error())
}
@ -669,11 +629,6 @@ func (r *RecipeRepository) GetUserRecipes(id int) ([]domain.Recipe, error) {
recipes = append(recipes, recipe)
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
return recipes, nil
}
@ -681,12 +636,6 @@ func (r *RecipeRepository) GetUserRecipes(id int) ([]domain.Recipe, error) {
// authenticated or exists. If nothing is found, a blank slice will be returned. The resulting list
// is sorted by the created dates, newest first. Any errors will be bubbled to the caller.
func (r *RecipeRepository) GetUserFavoriteRecipes(id int) ([]domain.Recipe, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return nil, err
}
query := `
SELECT r.id, r.title, r.description, r.instructions, r.serves, r.difficulty, r.duration, r.category, r.ingredients, r.
userid, r.modified, r.created
@ -695,7 +644,7 @@ func (r *RecipeRepository) GetUserFavoriteRecipes(id int) ([]domain.Recipe, erro
WHERE f.userid = $1
ORDER BY f.created DESC;
`
rows, err := tx.Query(query, id)
rows, err := r.db.Query(query, id)
if err != nil {
return nil, fmt.Errorf("Failed to query DB for user recipes. %s\n", err.Error())
}
@ -760,11 +709,6 @@ func (r *RecipeRepository) GetUserFavoriteRecipes(id int) ([]domain.Recipe, erro
recipes = append(recipes, recipe)
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
return recipes, nil
}
@ -777,12 +721,6 @@ func (r *RecipeRepository) GetRecipeTags(recipe *domain.Recipe) error {
return nil
}
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return err
}
recipe.Tags = []domain.Tag{}
query := `
@ -790,7 +728,7 @@ func (r *RecipeRepository) GetRecipeTags(recipe *domain.Recipe) error {
JOIN recipetags rt ON rt.tagid = t.id
WHERE rt.recipeid = $1;
`
rows, err := tx.Query(query, recipe.Id)
rows, err := r.db.Query(query, recipe.Id)
if err != nil {
return fmt.Errorf("Failed to get tags for recipe. %s\n", err.Error())
}
@ -807,11 +745,6 @@ func (r *RecipeRepository) GetRecipeTags(recipe *domain.Recipe) error {
recipe.Tags = append(recipe.Tags, tag)
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return err
}
return nil
}
@ -824,12 +757,6 @@ func (r *RecipeRepository) GetRecipeFavorite(recipe *domain.Recipe, userId int)
return nil
}
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return err
}
query := `
SELECT COUNT(*)
FROM favorites
@ -837,8 +764,7 @@ func (r *RecipeRepository) GetRecipeFavorite(recipe *domain.Recipe, userId int)
`
var count int
if err := tx.QueryRow(query, recipe.Id, userId).Scan(&count); err != nil {
tx.Rollback()
if err := r.db.QueryRow(query, recipe.Id, userId).Scan(&count); err != nil {
return fmt.Errorf("Failed to get recipe favorite. %s", err.Error())
}
@ -852,12 +778,6 @@ func (r *RecipeRepository) GetRecipeFavorite(recipe *domain.Recipe, userId int)
// table and return it. If there is no entry, nil will be returned Any errors will be bubbled to
// the caller.
func (r *RecipeRepository) GetRecipeOfTheWeek(userId *int) (*domain.Recipe, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return nil, err
}
query := `
SELECT
r.id, r.title, r.description, r.instructions, r.serves, r.difficulty, r.duration, r.category,
@ -872,7 +792,7 @@ func (r *RecipeRepository) GetRecipeOfTheWeek(userId *int) (*domain.Recipe, erro
var ingredientBytes []byte
var recipe domain.Recipe
if err := tx.QueryRow(query).Scan(
if err := r.db.QueryRow(query).Scan(
&recipe.Id,
&recipe.Title,
&recipe.Description,
@ -930,10 +850,5 @@ func (r *RecipeRepository) GetRecipeOfTheWeek(userId *int) (*domain.Recipe, erro
recipe.Favorite = false
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
return &recipe, nil
}

View File

@ -77,16 +77,10 @@ func (r *UserRepository) CreateGoogleUser(googleUserInfo *domain.GoogleUserInfo,
// function is used when a user logs in with Google to prevent duplicate entries from being made. If
// no user is found, this function will return a null pointer but not an error.
func (r *UserRepository) GetGoogleUser(googleId string) (*domain.User, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return nil, err
}
var user domain.User
query := `SELECT * FROM users WHERE GoogleId = $1`
if err := tx.QueryRow(query, googleId).Scan(
if err := r.db.QueryRow(query, googleId).Scan(
&user.Id,
&user.GoogleId,
&user.Name,
@ -99,12 +93,6 @@ func (r *UserRepository) GetGoogleUser(googleId string) (*domain.User, error) {
if err == sql.ErrNoRows {
return nil, nil
}
tx.Rollback()
return nil, err
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
@ -116,16 +104,10 @@ func (r *UserRepository) GetGoogleUser(googleId string) (*domain.User, error) {
// Callers are responsible for protecting against double nil results. Any errors will be bubbled
// to the caller.
func (r *UserRepository) GetUser(id int) (*domain.User, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return nil, err
}
query := "SELECT * FROM users WHERE id = $1"
var user domain.User
if err := tx.QueryRow(query, id).Scan(
if err := r.db.QueryRow(query, id).Scan(
&user.Id,
&user.GoogleId,
&user.Name,
@ -138,15 +120,8 @@ func (r *UserRepository) GetUser(id int) (*domain.User, error) {
if err == sql.ErrNoRows {
return nil, nil
}
tx.Rollback()
return nil, err
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
return &user, nil
}

View File

@ -20,7 +20,7 @@ templ dropdownButton(content, name, value string, selected bool) {
</label>
}
templ FilterDropdown(filters domainRecipe.SearchFilters) {
templ FilterDropdown(filters *domainRecipe.SearchFilters) {
<script>
function toggleDropdown() {
const menu = document.getElementById("filter-dropdown-menu");
@ -41,13 +41,23 @@ templ FilterDropdown(filters domainRecipe.SearchFilters) {
Meal
</h3>
<div class="flex text-xs flex-wrap gap-1 gap-y-3">
@dropdownButton("Breakfast", "meal", "1", isBitActive(filters.MealType, 0))
@dropdownButton("Lunch", "meal", "2", isBitActive(filters.MealType, 1))
@dropdownButton("Dinner", "meal", "4", isBitActive(filters.MealType, 2))
@dropdownButton("Desert", "meal", "8", isBitActive(filters.MealType, 3))
@dropdownButton("Snack", "meal", "16", isBitActive(filters.MealType, 4))
@dropdownButton("Side", "meal", "32", isBitActive(filters.MealType, 5))
@dropdownButton("Other", "meal", "64", isBitActive(filters.MealType, 6))
if filters != nil {
@dropdownButton("Breakfast", "meal", "1", isBitActive(filters.MealType, 0))
@dropdownButton("Lunch", "meal", "2", isBitActive(filters.MealType, 1))
@dropdownButton("Dinner", "meal", "4", isBitActive(filters.MealType, 2))
@dropdownButton("Desert", "meal", "8", isBitActive(filters.MealType, 3))
@dropdownButton("Snack", "meal", "16", isBitActive(filters.MealType, 4))
@dropdownButton("Side", "meal", "32", isBitActive(filters.MealType, 5))
@dropdownButton("Other", "meal", "64", isBitActive(filters.MealType, 6))
} else {
@dropdownButton("Breakfast", "meal", "1", false)
@dropdownButton("Lunch", "meal", "2", false)
@dropdownButton("Dinner", "meal", "4", false)
@dropdownButton("Desert", "meal", "8", false)
@dropdownButton("Snack", "meal", "16", false)
@dropdownButton("Side", "meal", "32", false)
@dropdownButton("Other", "meal", "64", false)
}
</div>
</div>
<div class="w-full border-b border-gray-300 py-2">
@ -55,11 +65,19 @@ templ FilterDropdown(filters domainRecipe.SearchFilters) {
Cook Time
</h3>
<div class="flex text-xs flex-wrap gap-1 gap-y-3">
@dropdownButton("< 15 min", "time" , "1" , isBitActive(filters.Time, 0))
@dropdownButton("15 to 30 min", "time", "2" , isBitActive(filters.Time, 1))
@dropdownButton("30 to 60 min", "time" , "4" , isBitActive(filters.Time, 2))
@dropdownButton("60 to 120 min", "time" , "8" , isBitActive(filters.Time, 3))
@dropdownButton("+120 min", "time" , "16" , isBitActive(filters.Time, 4))
if filters != nil {
@dropdownButton("< 15 min", "time" , "1" , isBitActive(filters.Time, 0))
@dropdownButton("15 to 30 min", "time", "2" , isBitActive(filters.Time, 1))
@dropdownButton("30 to 60 min", "time" , "4" , isBitActive(filters.Time, 2))
@dropdownButton("60 to 120 min", "time" , "8" , isBitActive(filters.Time, 3))
@dropdownButton("+120 min", "time" , "16" , isBitActive(filters.Time, 4))
} else {
@dropdownButton("< 15 min", "time" , "1" , false)
@dropdownButton("15 to 30 min", "time", "2" , false)
@dropdownButton("30 to 60 min", "time" , "4" , false)
@dropdownButton("60 to 120 min", "time" , "8" , false)
@dropdownButton("+120 min", "time" , "16" , false)
}
</div>
</div>
<div class="w-full border-b border-gray-300 py-2">
@ -67,11 +85,19 @@ templ FilterDropdown(filters domainRecipe.SearchFilters) {
Difficulty
</h3>
<div class="flex text-xs flex-wrap gap-1 gap-y-3">
@dropdownButton("Beginner", "difficulty", "1", isBitActive(filters.Difficulty, 0))
@dropdownButton("Easy", "difficulty", "2", isBitActive(filters.Difficulty, 1))
@dropdownButton("Intermediate", "difficulty", "4", isBitActive(filters.Difficulty, 2))
@dropdownButton("Challenging", "difficulty", "8", isBitActive(filters.Difficulty, 3))
@dropdownButton("Extreme", "difficulty", "16", isBitActive(filters.Difficulty, 4))
if filters != nil {
@dropdownButton("Beginner", "difficulty", "1", isBitActive(filters.Difficulty, 0))
@dropdownButton("Easy", "difficulty", "2", isBitActive(filters.Difficulty, 1))
@dropdownButton("Intermediate", "difficulty", "4", isBitActive(filters.Difficulty, 2))
@dropdownButton("Challenging", "difficulty", "8", isBitActive(filters.Difficulty, 3))
@dropdownButton("Extreme", "difficulty", "16", isBitActive(filters.Difficulty, 4))
} else {
@dropdownButton("Beginner", "difficulty", "1", false)
@dropdownButton("Easy", "difficulty", "2", false)
@dropdownButton("Intermediate", "difficulty", "4", false)
@dropdownButton("Challenging", "difficulty", "8", false)
@dropdownButton("Extreme", "difficulty", "16", false)
}
</div>
</div>
<div class="w-full border-b border-gray-300 py-2">
@ -79,11 +105,19 @@ templ FilterDropdown(filters domainRecipe.SearchFilters) {
Serving Size
</h3>
<div class="flex text-xs flex-wrap gap-1 gap-y-3">
@dropdownButton("1 to 2", "serving", "1", isBitActive(filters.ServingSize, 0))
@dropdownButton("2 to 4", "serving", "2", isBitActive(filters.ServingSize, 1))
@dropdownButton("4 to 6", "serving", "4", isBitActive(filters.ServingSize, 2))
@dropdownButton("6 to 8", "serving", "8", isBitActive(filters.ServingSize, 3))
@dropdownButton("8+", "serving", "16", isBitActive(filters.ServingSize, 4))
if filters != nil {
@dropdownButton("1 to 2", "serving", "1", isBitActive(filters.ServingSize, 0))
@dropdownButton("2 to 4", "serving", "2", isBitActive(filters.ServingSize, 1))
@dropdownButton("4 to 6", "serving", "4", isBitActive(filters.ServingSize, 2))
@dropdownButton("6 to 8", "serving", "8", isBitActive(filters.ServingSize, 3))
@dropdownButton("8+", "serving", "16", isBitActive(filters.ServingSize, 4))
} else {
@dropdownButton("1 to 2", "serving", "1", false)
@dropdownButton("2 to 4", "serving", "2", false)
@dropdownButton("4 to 6", "serving", "4", false)
@dropdownButton("6 to 8", "serving", "8", false)
@dropdownButton("8+", "serving", "16", false)
}
</div>
</div>
<div class="w-full pt-2 flex justify-end items-end">

View File

@ -93,7 +93,7 @@ func dropdownButton(content, name, value string, selected bool) templ.Component
})
}
func FilterDropdown(filters domainRecipe.SearchFilters) templ.Component {
func FilterDropdown(filters *domainRecipe.SearchFilters) 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 {
@ -118,107 +118,351 @@ func FilterDropdown(filters domainRecipe.SearchFilters) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Breakfast", "meal", "1", isBitActive(filters.MealType, 0)).Render(ctx, templ_7745c5c3_Buffer)
if filters != nil {
templ_7745c5c3_Err = dropdownButton("Breakfast", "meal", "1", isBitActive(filters.MealType, 0)).Render(ctx, templ_7745c5c3_Buffer)
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 = dropdownButton("Lunch", "meal", "2", isBitActive(filters.MealType, 1)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Dinner", "meal", "4", isBitActive(filters.MealType, 2)).Render(ctx, templ_7745c5c3_Buffer)
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 = dropdownButton("Desert", "meal", "8", isBitActive(filters.MealType, 3)).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
}
templ_7745c5c3_Err = dropdownButton("Snack", "meal", "16", isBitActive(filters.MealType, 4)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Side", "meal", "32", isBitActive(filters.MealType, 5)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Other", "meal", "64", isBitActive(filters.MealType, 6)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = dropdownButton("Breakfast", "meal", "1", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Lunch", "meal", "2", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Dinner", "meal", "4", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Desert", "meal", "8", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Snack", "meal", "16", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Side", "meal", "32", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Other", "meal", "64", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div></div><div class=\"w-full border-b border-gray-300 py-2\"><h3 class=\"mb-2\">Cook Time</h3><div class=\"flex text-xs flex-wrap gap-1 gap-y-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Lunch", "meal", "2", isBitActive(filters.MealType, 1)).Render(ctx, templ_7745c5c3_Buffer)
if filters != nil {
templ_7745c5c3_Err = dropdownButton("< 15 min", "time", "1", isBitActive(filters.Time, 0)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("15 to 30 min", "time", "2", isBitActive(filters.Time, 1)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("30 to 60 min", "time", "4", isBitActive(filters.Time, 2)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("60 to 120 min", "time", "8", isBitActive(filters.Time, 3)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("+120 min", "time", "16", isBitActive(filters.Time, 4)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = dropdownButton("< 15 min", "time", "1", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("15 to 30 min", "time", "2", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("30 to 60 min", "time", "4", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("60 to 120 min", "time", "8", false).Render(ctx, templ_7745c5c3_Buffer)
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
}
templ_7745c5c3_Err = dropdownButton("+120 min", "time", "16", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</div></div><div class=\"w-full border-b border-gray-300 py-2\"><h3 class=\"mb-2\">Difficulty</h3><div class=\"flex text-xs flex-wrap gap-1 gap-y-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Dinner", "meal", "4", isBitActive(filters.MealType, 2)).Render(ctx, templ_7745c5c3_Buffer)
if filters != nil {
templ_7745c5c3_Err = dropdownButton("Beginner", "difficulty", "1", isBitActive(filters.Difficulty, 0)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Easy", "difficulty", "2", isBitActive(filters.Difficulty, 1)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Intermediate", "difficulty", "4", isBitActive(filters.Difficulty, 2)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Challenging", "difficulty", "8", isBitActive(filters.Difficulty, 3)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Extreme", "difficulty", "16", isBitActive(filters.Difficulty, 4)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = dropdownButton("Beginner", "difficulty", "1", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Easy", "difficulty", "2", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Intermediate", "difficulty", "4", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Challenging", "difficulty", "8", false).Render(ctx, templ_7745c5c3_Buffer)
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
}
templ_7745c5c3_Err = dropdownButton("Extreme", "difficulty", "16", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</div></div><div class=\"w-full border-b border-gray-300 py-2\"><h3 class=\"mb-2\">Serving Size</h3><div class=\"flex text-xs flex-wrap gap-1 gap-y-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Desert", "meal", "8", isBitActive(filters.MealType, 3)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
if filters != nil {
templ_7745c5c3_Err = dropdownButton("1 to 2", "serving", "1", isBitActive(filters.ServingSize, 0)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("2 to 4", "serving", "2", isBitActive(filters.ServingSize, 1)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("4 to 6", "serving", "4", isBitActive(filters.ServingSize, 2)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("6 to 8", "serving", "8", isBitActive(filters.ServingSize, 3)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("8+", "serving", "16", isBitActive(filters.ServingSize, 4)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = dropdownButton("1 to 2", "serving", "1", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("2 to 4", "serving", "2", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("4 to 6", "serving", "4", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("6 to 8", "serving", "8", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("8+", "serving", "16", false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = dropdownButton("Snack", "meal", "16", isBitActive(filters.MealType, 4)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Side", "meal", "32", isBitActive(filters.MealType, 5)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Other", "meal", "64", isBitActive(filters.MealType, 6)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div><div class=\"w-full border-b border-gray-300 py-2\"><h3 class=\"mb-2\">Cook Time</h3><div class=\"flex text-xs flex-wrap gap-1 gap-y-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("< 15 min", "time", "1", isBitActive(filters.Time, 0)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("15 to 30 min", "time", "2", isBitActive(filters.Time, 1)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("30 to 60 min", "time", "4", isBitActive(filters.Time, 2)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("60 to 120 min", "time", "8", isBitActive(filters.Time, 3)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("+120 min", "time", "16", isBitActive(filters.Time, 4)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div><div class=\"w-full border-b border-gray-300 py-2\"><h3 class=\"mb-2\">Difficulty</h3><div class=\"flex text-xs flex-wrap gap-1 gap-y-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Beginner", "difficulty", "1", isBitActive(filters.Difficulty, 0)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Easy", "difficulty", "2", isBitActive(filters.Difficulty, 1)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Intermediate", "difficulty", "4", isBitActive(filters.Difficulty, 2)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Challenging", "difficulty", "8", isBitActive(filters.Difficulty, 3)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("Extreme", "difficulty", "16", isBitActive(filters.Difficulty, 4)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div></div><div class=\"w-full border-b border-gray-300 py-2\"><h3 class=\"mb-2\">Serving Size</h3><div class=\"flex text-xs flex-wrap gap-1 gap-y-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("1 to 2", "serving", "1", isBitActive(filters.ServingSize, 0)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("2 to 4", "serving", "2", isBitActive(filters.ServingSize, 1)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("4 to 6", "serving", "4", isBitActive(filters.ServingSize, 2)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("6 to 8", "serving", "8", isBitActive(filters.ServingSize, 3)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = dropdownButton("8+", "serving", "16", isBitActive(filters.ServingSize, 4)).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></div><div class=\"w-full pt-2 flex justify-end items-end\"><button type=\"submit\" class=\"w-full text-sm md:text-base text-white rounded-lg py-1.5 md:py-2 bg-blue-600 hover:bg-blue-700 duration-300\">Apply Filters</button></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</div></div><div class=\"w-full pt-2 flex justify-end items-end\"><button type=\"submit\" class=\"w-full text-sm md:text-base text-white rounded-lg py-1.5 md:py-2 bg-blue-600 hover:bg-blue-700 duration-300\">Apply Filters</button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -3,7 +3,7 @@ package components
import domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
templ SearchBar(filters domainRecipe.SearchFilters, redirect, searchOnLoad, favorites bool) {
templ SearchBar(filters *domainRecipe.SearchFilters, redirect, searchOnLoad, favorites bool) {
<form
if favorites {
hx-post={ domainServer.API_SEARCH_FAVORITES }
@ -27,7 +27,9 @@ templ SearchBar(filters domainRecipe.SearchFilters, redirect, searchOnLoad, favo
type="search"
name="search"
placeholder="Search recipes, ingredients..."
value={ filters.Search }
if filters !=nil {
value={ filters.Search }
}
class="w-full pr-4 pl-10 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button type="submit" class="absolute left-3 top-1/2 -translate-y-1/2">

View File

@ -11,7 +11,7 @@ import templruntime "github.com/a-h/templ/runtime"
import domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
func SearchBar(filters domainRecipe.SearchFilters, redirect, searchOnLoad, favorites bool) templ.Component {
func SearchBar(filters *domainRecipe.SearchFilters, redirect, searchOnLoad, favorites 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 {
@ -101,20 +101,30 @@ func SearchBar(filters domainRecipe.SearchFilters, redirect, searchOnLoad, favor
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"> <input type=\"search\" name=\"search\" placeholder=\"Search recipes, ingredients...\" value=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"> <input type=\"search\" name=\"search\" placeholder=\"Search recipes, ingredients...\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(filters.Search)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/search_bar.templ`, Line: 30, Col: 27}
if filters != nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(filters.Search)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/search_bar.templ`, Line: 31, Col: 28}
}
_, 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, 12, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, 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, 11, "\" class=\"w-full pr-4 pl-10 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500\"> <button type=\"submit\" class=\"absolute left-3 top-1/2 -translate-y-1/2\"><svg class=\"h-5 w-5 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path></svg></button></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " class=\"w-full pr-4 pl-10 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500\"> <button type=\"submit\" class=\"absolute left-3 top-1/2 -translate-y-1/2\"><svg class=\"h-5 w-5 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path></svg></button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -122,7 +132,7 @@ func SearchBar(filters domainRecipe.SearchFilters, redirect, searchOnLoad, favor
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -130,7 +140,7 @@ func SearchBar(filters domainRecipe.SearchFilters, redirect, searchOnLoad, favor
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</form>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -159,7 +169,7 @@ func filterButton() templ.Component {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<button type=\"button\" id=\"filter-dropdown-button\" onclick=\"toggleDropdown()\" class=\"text-gray-400 border border-gray-300 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-blue-500\"><svg class=\"h-6\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M6 11.1707L6 4C6 3.44771 5.55228 3 5 3C4.44771 3 4 3.44771 4 4L4 11.1707C2.83481 11.5825 2 12.6938 2 14C2 15.3062 2.83481 16.4175 4 16.8293L4 20C4 20.5523 4.44772 21 5 21C5.55228 21 6 20.5523 6 20L6 16.8293C7.16519 16.4175 8 15.3062 8 14C8 12.6938 7.16519 11.5825 6 11.1707ZM5 13C4.44772 13 4 13.4477 4 14C4 14.5523 4.44772 15 5 15C5.55228 15 6 14.5523 6 14C6 13.4477 5.55228 13 5 13Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M19 21C18.4477 21 18 20.5523 18 20L18 18C18 17.9435 18.0047 17.8881 18.0137 17.8341C16.8414 17.4262 16 16.3113 16 15C16 13.6887 16.8414 12.5738 18.0137 12.1659C18.0047 12.1119 18 12.0565 18 12L18 4C18 3.44771 18.4477 3 19 3C19.5523 3 20 3.44771 20 4L20 12C20 12.0565 19.9953 12.1119 19.9863 12.1659C21.1586 12.5738 22 13.6887 22 15C22 16.3113 21.1586 17.4262 19.9863 17.8341C19.9953 17.8881 20 17.9435 20 18V20C20 20.5523 19.5523 21 19 21ZM18 15C18 14.4477 18.4477 14 19 14C19.5523 14 20 14.4477 20 15C20 15.5523 19.5523 16 19 16C18.4477 16 18 15.5523 18 15Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9 9C9 7.69378 9.83481 6.58254 11 6.17071V4C11 3.44772 11.4477 3 12 3C12.5523 3 13 3.44772 13 4V6.17071C14.1652 6.58254 15 7.69378 15 9C15 10.3113 14.1586 11.4262 12.9863 11.8341C12.9953 11.8881 13 11.9435 13 12L13 20C13 20.5523 12.5523 21 12 21C11.4477 21 11 20.5523 11 20L11 12C11 11.9435 11.0047 11.8881 11.0137 11.8341C9.84135 11.4262 9 10.3113 9 9ZM11 9C11 8.44772 11.4477 8 12 8C12.5523 8 13 8.44772 13 9C13 9.55229 12.5523 10 12 10C11.4477 10 11 9.55229 11 9Z\" fill=\"currentColor\"></path></svg></button>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<button type=\"button\" id=\"filter-dropdown-button\" onclick=\"toggleDropdown()\" class=\"text-gray-400 border border-gray-300 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-blue-500\"><svg class=\"h-6\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M6 11.1707L6 4C6 3.44771 5.55228 3 5 3C4.44771 3 4 3.44771 4 4L4 11.1707C2.83481 11.5825 2 12.6938 2 14C2 15.3062 2.83481 16.4175 4 16.8293L4 20C4 20.5523 4.44772 21 5 21C5.55228 21 6 20.5523 6 20L6 16.8293C7.16519 16.4175 8 15.3062 8 14C8 12.6938 7.16519 11.5825 6 11.1707ZM5 13C4.44772 13 4 13.4477 4 14C4 14.5523 4.44772 15 5 15C5.55228 15 6 14.5523 6 14C6 13.4477 5.55228 13 5 13Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M19 21C18.4477 21 18 20.5523 18 20L18 18C18 17.9435 18.0047 17.8881 18.0137 17.8341C16.8414 17.4262 16 16.3113 16 15C16 13.6887 16.8414 12.5738 18.0137 12.1659C18.0047 12.1119 18 12.0565 18 12L18 4C18 3.44771 18.4477 3 19 3C19.5523 3 20 3.44771 20 4L20 12C20 12.0565 19.9953 12.1119 19.9863 12.1659C21.1586 12.5738 22 13.6887 22 15C22 16.3113 21.1586 17.4262 19.9863 17.8341C19.9953 17.8881 20 17.9435 20 18V20C20 20.5523 19.5523 21 19 21ZM18 15C18 14.4477 18.4477 14 19 14C19.5523 14 20 14.4477 20 15C20 15.5523 19.5523 16 19 16C18.4477 16 18 15.5523 18 15Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9 9C9 7.69378 9.83481 6.58254 11 6.17071V4C11 3.44772 11.4477 3 12 3C12.5523 3 13 3.44772 13 4V6.17071C14.1652 6.58254 15 7.69378 15 9C15 10.3113 14.1586 11.4262 12.9863 11.8341C12.9953 11.8881 13 11.9435 13 12L13 20C13 20.5523 12.5523 21 12 21C11.4477 21 11 20.5523 11 20L11 12C11 11.9435 11.0047 11.8881 11.0137 11.8341C9.84135 11.4262 9 10.3113 9 9ZM11 9C11 8.44772 11.4477 8 12 8C12.5523 8 13 8.44772 13 9C13 9.55229 12.5523 10 12 10C11.4477 10 11 9.55229 11 9Z\" fill=\"currentColor\"></path></svg></button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -65,7 +65,7 @@ templ favoriteResult(recipe domain.Recipe) {
</div>
}
templ FavoritesPage(filters domain.SearchFilters) {
templ FavoritesPage(filters *domain.SearchFilters) {
@components.Navbar("favorites")
<div class="w-full flex justify-center">
<div class="mx-2 md:mx-0 w-full md:w-1/2 md:pt-14 min-h-screen border-l border-r border-gray-300 bg-white">

View File

@ -198,7 +198,7 @@ func favoriteResult(recipe domain.Recipe) templ.Component {
})
}
func FavoritesPage(filters domain.SearchFilters) templ.Component {
func FavoritesPage(filters *domain.SearchFilters) 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 {

View File

@ -5,132 +5,126 @@ import "github.com/haydenhargreaves/Potion/internal/domain/server"
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
templ introSection() {
<section class="w-full h-fit mb-16">
<div class="relative">
<video class="" autoplay loop muted playsinline>
<source src="/v1/web/static/img/salmon_video.mp4" type="video/mp4"/>
</video>
<h1
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center
text-white text-3xl w-4/5 font-bold z-10"
>
Discover Your Next Favorite Meal
</h1>
</div>
<p class="leading-relaxed p-4 my-8">
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.
</p>
</section>
<section class="w-full h-fit mb-16">
<div class="relative">
<video class="" autoplay loop muted playsinline>
<source src="/v1/web/static/img/salmon_video.mp4" type="video/mp4" />
</video>
<h1 class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center
text-white text-3xl w-4/5 font-bold z-10">
Discover Your Next Favorite Meal
</h1>
</div>
<p class="leading-relaxed p-4 my-8">
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.
</p>
</section>
}
templ searchSection() {
<section class="w-full flex flex-col items-center justify-center my-8 py-4">
@components.BannerText("Craving Something Specific?")
<div class="w-full md:w-3/4">
@components.SearchBar(domainRecipe.SearchFilters{}, true, false, false)
</div>
<div class="hidden" id="result-list"></div>
</section>
templ searchSection(filters *domainRecipe.SearchFilters) {
<section class="w-full flex flex-col items-center justify-center my-8 py-4">
@components.BannerText("Craving Something Specific?")
<div class="w-full md:w-3/4">
@components.SearchBar(filters, true, false, false)
</div>
<div class="hidden" id="result-list"></div>
</section>
}
templ highlightSection(recipeOfTheWeek *domainRecipe.Recipe) {
<section class="w-full flex flex-col items-center justify-center my-8 py-4">
@components.BannerText("Recipe of the Week!")
<p class="leading-relaxed p-4 my-8">
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!
</p>
<div class="flex items-center justify-center w-full">
@components.RecipeCardLarge(recipeOfTheWeek)
</div>
</section>
<section class="w-full flex flex-col items-center justify-center my-8 py-4">
@components.BannerText("Recipe of the Week!")
<p class="leading-relaxed p-4 my-8">
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!
</p>
<div class="flex items-center justify-center w-full">
@components.RecipeCardLarge(recipeOfTheWeek)
</div>
</section>
}
templ listsSection(loggedIn bool, viewed, made []domainRecipe.Recipe) {
<section class="w-full flex flex-col items-center justify-center my-8 py-4">
@components.BannerText("Take Another Look.")
<div class="w-full">
<h3 class="text-lg mt-8 mx-4">Recently viewed</h3>
if loggedIn {
<div class="flex overflow-x-auto gap-x-4 mx-4 my-4">
if len(viewed) > 0 {
for _, recipe := range viewed {
@components.RecipeCardSmall(recipe)
}
@components.ContentCardSmall("View full history...", "/v1/web/history")
} else {
<p class="text-sm">You have not viewed any recipes. There is nothing to show.</p>
}
</div>
} else {
<div class="my-2 mx-4 text-gray-800">
<a class="underline" href={ domain.WEB_LOGIN }>
<p class="text-sm">Log in to view metrics.</p>
</a>
</div>
}
<h3 class="text-lg mt-8 mx-4">Make again</h3>
if loggedIn {
<div class="flex overflow-x-auto gap-x-4 mx-4 my-4">
if len(made) > 0 {
for _, recipe := range made {
@components.RecipeCardSmall(recipe)
}
@components.ContentCardSmall("View full history...", "/v1/web/history")
} else {
<p class="text-sm">You have not made any recipes. There is nothing to show.</p>
}
</div>
} else {
<div class="my-2 mx-4 text-gray-800">
<a class="underline" href={ domain.WEB_LOGIN }>
<p class="text-sm">Log in to view metrics.</p>
</a>
</div>
}
</div>
</section>
<section class="w-full flex flex-col items-center justify-center my-8 py-4">
@components.BannerText("Take Another Look.")
<div class="w-full">
<h3 class="text-lg mt-8 mx-4">Recently viewed</h3>
if loggedIn {
<div class="flex overflow-x-auto gap-x-4 mx-4 my-4">
if len(viewed) > 0 {
for _, recipe := range viewed {
@components.RecipeCardSmall(recipe)
}
@components.ContentCardSmall("View full history...", "/v1/web/history")
} else {
<p class="text-sm">You have not viewed any recipes. There is nothing to show.</p>
}
</div>
} else {
<div class="my-2 mx-4 text-gray-800">
<a class="underline" href={ domain.WEB_LOGIN }>
<p class="text-sm">Log in to view metrics.</p>
</a>
</div>
}
<h3 class="text-lg mt-8 mx-4">Make again</h3>
if loggedIn {
<div class="flex overflow-x-auto gap-x-4 mx-4 my-4">
if len(made) > 0 {
for _, recipe := range made {
@components.RecipeCardSmall(recipe)
}
@components.ContentCardSmall("View full history...", "/v1/web/history")
} else {
<p class="text-sm">You have not made any recipes. There is nothing to show.</p>
}
</div>
} else {
<div class="my-2 mx-4 text-gray-800">
<a class="underline" href={ domain.WEB_LOGIN }>
<p class="text-sm">Log in to view metrics.</p>
</a>
</div>
}
</div>
</section>
}
templ ctaSection() {
<section
class="w-full flex flex-col items-center justify-center mt-16 py-8 md:py-12 bg-gradient-to-br from-blue-100 to-purple-100 text-center"
>
<h2 class="text-2xl md:text-3xl font-extrabold text-gray-800 mb-6 px-4">
Unleash Your Inner Chef!
</h2>
<p class="text-md md:text-lg text-gray-700 max-w-2xl mb-10 px-4 leading-relaxed">
Have a unique recipe idea? Want to share your culinary masterpiece with the world?
It's time to bring your creations to life!
</p>
<a
href={ domain.WEB_CREATE }
class="flex items-center justify-center
<section
class="w-full flex flex-col items-center justify-center mt-16 py-8 md:py-12 bg-gradient-to-br from-blue-100 to-purple-100 text-center">
<h2 class="text-2xl md:text-3xl font-extrabold text-gray-800 mb-6 px-4">
Unleash Your Inner Chef!
</h2>
<p class="text-md md:text-lg text-gray-700 max-w-2xl mb-10 px-4 leading-relaxed">
Have a unique recipe idea? Want to share your culinary masterpiece with the world?
It's time to bring your creations to life!
</p>
<a href={ domain.WEB_CREATE } class="flex items-center justify-center
bg-gradient-to-r from-blue-400 to-blue-600 text-white
px-12 py-5 rounded-full shadow-sm hover:shadow-md
transition-all duration-300 ease-in-out shadow-blue-700
text-lg md:text-2xl font-bold uppercase tracking-wide"
>
Create Your Recipe!
</a>
</section>
text-lg md:text-2xl font-bold uppercase tracking-wide">
Create Your Recipe!
</a>
</section>
}
templ HomePage(loggedIn bool, viewed, made []domainRecipe.Recipe, recipeOfTheWeek *domainRecipe.Recipe) {
@components.Navbar("home")
<div class="w-full h-fit flex justify-center">
<div class="mx-2 md:mx-0 w-full md:w-1/2 md:pt-14 h-full border-l border-r border-gray-300 bg-white">
@introSection()
@searchSection()
@highlightSection(recipeOfTheWeek)
@listsSection(loggedIn, viewed, made)
@ctaSection()
</div>
</div>
templ HomePage(loggedIn bool, viewed, made []domainRecipe.Recipe, recipeOfTheWeek *domainRecipe.Recipe, filters *domainRecipe.SearchFilters) {
@components.Navbar("home")
<div class="w-full h-fit flex justify-center">
<div class="mx-2 md:mx-0 w-full md:w-1/2 md:pt-14 h-full border-l border-r border-gray-300 bg-white">
@introSection()
@searchSection(filters)
@highlightSection(recipeOfTheWeek)
@listsSection(loggedIn, viewed, made)
@ctaSection()
</div>
</div>
}

View File

@ -41,7 +41,7 @@ func introSection() templ.Component {
})
}
func searchSection() templ.Component {
func searchSection(filters *domainRecipe.SearchFilters) 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 {
@ -74,7 +74,7 @@ func searchSection() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.SearchBar(domainRecipe.SearchFilters{}, true, false, false).Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = components.SearchBar(filters, true, false, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -304,7 +304,7 @@ func ctaSection() templ.Component {
})
}
func HomePage(loggedIn bool, viewed, made []domainRecipe.Recipe, recipeOfTheWeek *domainRecipe.Recipe) templ.Component {
func HomePage(loggedIn bool, viewed, made []domainRecipe.Recipe, recipeOfTheWeek *domainRecipe.Recipe, filters *domainRecipe.SearchFilters) 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 {
@ -337,7 +337,7 @@ func HomePage(loggedIn bool, viewed, made []domainRecipe.Recipe, recipeOfTheWeek
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = searchSection().Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = searchSection(filters).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -8,7 +8,7 @@ import (
"github.com/haydenhargreaves/Potion/internal/templates/components"
)
templ SearchPage(filters domainRecipe.SearchFilters, searchOnLoad bool) {
templ SearchPage(filters *domainRecipe.SearchFilters, searchOnLoad bool) {
@components.Navbar("")
<div class="w-full flex justify-center">
<div
@ -43,7 +43,11 @@ templ searchResult(recipe domain.Recipe) {
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"
>
<img class="bg-gray-50 size-56 md:size-40 rounded-md border-0" src="/v1/web/static/img/recipe_placeholder.png" type="image/png" />
<img
class="bg-gray-50 size-56 md:size-40 rounded-md border-0"
src="/v1/web/static/img/recipe_placeholder.png"
type="image/png"
/>
<div class="text-gray-700 p-4 flex flex-col items-center md:items-start w-full">
<div class="flex flex-col md:flex-row items-center md:items-start justify-between w-full">
<div class="flex flex-col items-center md:items-start">
@ -70,14 +74,14 @@ templ searchResult(recipe domain.Recipe) {
</div>
</div>
<div class="mb-2 mt-4 md:my-0 hidden md:block">
if recipe.Favorite {
<svg class="h-6 text-red-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2 9.1371C2 14 6.01943 16.5914 8.96173 18.9109C10 19.7294 11 20.5 12 20.5C13 20.5 14 19.7294 15.0383 18.9109C17.9806 16.5914 22 14 22 9.1371C22 4.27416 16.4998 0.825464 12 5.50063C7.50016 0.825464 2 4.27416 2 9.1371Z"
fill="currentColor"
></path>
</svg>
}
if recipe.Favorite {
<svg class="h-6 text-red-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2 9.1371C2 14 6.01943 16.5914 8.96173 18.9109C10 19.7294 11 20.5 12 20.5C13 20.5 14 19.7294 15.0383 18.9109C17.9806 16.5914 22 14 22 9.1371C22 4.27416 16.4998 0.825464 12 5.50063C7.50016 0.825464 2 4.27416 2 9.1371Z"
fill="currentColor"
></path>
</svg>
}
</div>
</div>
<p class="text-sm py-2 text-center md:text-left">{ recipe.Description }</p>

View File

@ -16,7 +16,7 @@ import (
"github.com/haydenhargreaves/Potion/internal/templates/components"
)
func SearchPage(filters domainRecipe.SearchFilters, searchOnLoad bool) templ.Component {
func SearchPage(filters *domainRecipe.SearchFilters, searchOnLoad 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 {
@ -160,7 +160,7 @@ func searchResult(recipe domain.Recipe) templ.Component {
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: 20}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 55, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@ -173,7 +173,7 @@ func searchResult(recipe domain.Recipe) templ.Component {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(recipe.Category)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 51, Col: 91}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 55, Col: 91}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@ -190,7 +190,7 @@ func searchResult(recipe domain.Recipe) 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: 30}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 60, Col: 30}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
@ -227,7 +227,7 @@ func searchResult(recipe domain.Recipe) templ.Component {
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: 29}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 72, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@ -250,7 +250,7 @@ func searchResult(recipe domain.Recipe) templ.Component {
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: 83, Col: 72}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/search.templ`, Line: 87, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {