(FEAT): Orm work, not complete, but editing needs to be merged.
This commit is contained in:
parent
e81f2ec513
commit
6e28ebfe80
@ -131,7 +131,7 @@ func (s *RecipeService) DeleteRecipe(userId, recipeId int) error {
|
|||||||
return fmt.Errorf("User id does not match. Do you own the target recipe?")
|
return fmt.Errorf("User id does not match. Do you own the target recipe?")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.recipeRepository.DeleteRecipe(recipeId)
|
return s.recipeRepository.DeleteRecipe(recipeId, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRecipe will get a recipe via its ID. Any errors will be bubbled to the caller. Furthermore,
|
// GetRecipe will get a recipe via its ID. Any errors will be bubbled to the caller. Furthermore,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package domain
|
|||||||
type RecipeRepository interface {
|
type RecipeRepository interface {
|
||||||
CreateRecipe(recipe *Recipe) error
|
CreateRecipe(recipe *Recipe) error
|
||||||
EditRecipe(recipe *Recipe, userId int) error
|
EditRecipe(recipe *Recipe, userId int) error
|
||||||
DeleteRecipe(recipeId int) error
|
DeleteRecipe(recipeId, userId int) error
|
||||||
GetRecipe(id int, userId *int) (*Recipe, error)
|
GetRecipe(id int, userId *int) (*Recipe, error)
|
||||||
GetRecipes(ids []int, userId *int) ([]Recipe, error)
|
GetRecipes(ids []int, userId *int) ([]Recipe, error)
|
||||||
SearchRecipes(filters SearchFilters, userId *int, favorites bool) ([]int, error)
|
SearchRecipes(filters SearchFilters, userId *int, favorites bool) ([]int, error)
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -34,26 +33,7 @@ func NewRecipeRepository(db *sqlx.DB) domain.RecipeRepository {
|
|||||||
// be bubbled to the caller. The recipe parameter is passed by reference and will therefore be updated
|
// be bubbled to the caller. The recipe parameter is passed by reference and will therefore be updated
|
||||||
// directly and the new fields (ID, created) can be accessed upon success.
|
// directly and the new fields (ID, created) can be accessed upon success.
|
||||||
func (r *RecipeRepository) CreateRecipe(recipe *domain.Recipe) error {
|
func (r *RecipeRepository) CreateRecipe(recipe *domain.Recipe) error {
|
||||||
tx, err := r.db.Begin()
|
// Convert data into a readable format
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
query := `INSERT INTO recipes (
|
|
||||||
title, description, instructions, serves, difficulty,
|
|
||||||
duration, category, ingredients, userid, modified, created
|
|
||||||
) VALUES (
|
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11
|
|
||||||
) RETURNING id;`
|
|
||||||
|
|
||||||
// NOTE: Data steps
|
|
||||||
// cast duration to JSON
|
|
||||||
// convert ingredients to store type
|
|
||||||
// cast store type to JSON
|
|
||||||
// extract string instructions from type
|
|
||||||
// cast category to string
|
|
||||||
// use nil for the modified time
|
|
||||||
|
|
||||||
durationJSON, err := json.Marshal(recipe.Duration)
|
durationJSON, err := json.Marshal(recipe.Duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -74,27 +54,46 @@ func (r *RecipeRepository) CreateRecipe(recipe *domain.Recipe) error {
|
|||||||
instructions[i] = instruction.Content
|
instructions[i] = instruction.Content
|
||||||
}
|
}
|
||||||
|
|
||||||
var id int
|
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||||
if err = tx.QueryRow(
|
|
||||||
query,
|
query := psql.
|
||||||
recipe.Title,
|
Insert("recipes").
|
||||||
recipe.Description,
|
Columns(
|
||||||
pq.Array(instructions),
|
"title",
|
||||||
recipe.Serves,
|
"description",
|
||||||
recipe.Difficulty,
|
"instructions",
|
||||||
durationJSON,
|
"serves",
|
||||||
string(recipe.Category),
|
"difficulty",
|
||||||
ingredientsJSON,
|
"duration",
|
||||||
recipe.UserId,
|
"category",
|
||||||
nil,
|
"ingredients",
|
||||||
recipe.Created,
|
"userid",
|
||||||
).Scan(&id); err != nil {
|
"modified",
|
||||||
tx.Rollback()
|
"created",
|
||||||
return err
|
).
|
||||||
|
Values(
|
||||||
|
recipe.Title,
|
||||||
|
recipe.Description,
|
||||||
|
pq.Array(instructions),
|
||||||
|
recipe.Serves,
|
||||||
|
recipe.Difficulty,
|
||||||
|
durationJSON,
|
||||||
|
string(recipe.Category),
|
||||||
|
ingredientsJSON,
|
||||||
|
recipe.UserId,
|
||||||
|
nil,
|
||||||
|
recipe.Created,
|
||||||
|
).
|
||||||
|
Suffix("RETURNING id")
|
||||||
|
|
||||||
|
_sql, args, err := query.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to construct query: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
var id int
|
||||||
return err
|
if err := r.db.Get(&id, _sql, args...); err != nil {
|
||||||
|
return fmt.Errorf("Failed to create recipe: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the new ID
|
// Set the new ID
|
||||||
@ -107,36 +106,9 @@ func (r *RecipeRepository) CreateRecipe(recipe *domain.Recipe) error {
|
|||||||
// function will fail - it will not know what recipe to edit.
|
// function will fail - it will not know what recipe to edit.
|
||||||
func (r *RecipeRepository) EditRecipe(recipe *domain.Recipe, userId int) error {
|
func (r *RecipeRepository) EditRecipe(recipe *domain.Recipe, userId int) error {
|
||||||
if recipe.Id <= 0 {
|
if recipe.Id <= 0 {
|
||||||
return fmt.Errorf("[ERROR] Recipe must contain an ID. Cannot edit unknown recipe.")
|
return fmt.Errorf("Recipe must contain an ID. Cannot edit unknown recipe.")
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := r.db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// This query will ensure the userId matches the owner.
|
|
||||||
query := `UPDATE recipes SET
|
|
||||||
title = $1,
|
|
||||||
description = $2,
|
|
||||||
instructions = $3,
|
|
||||||
serves = $4,
|
|
||||||
difficulty = $5,
|
|
||||||
duration = $6,
|
|
||||||
category = $7,
|
|
||||||
ingredients = $8,
|
|
||||||
modified = $9
|
|
||||||
WHERE id = $10
|
|
||||||
AND userid = $11;`
|
|
||||||
|
|
||||||
// NOTE: Data steps
|
|
||||||
// cast duration to JSON
|
|
||||||
// convert ingredients to store type
|
|
||||||
// cast store type to JSON
|
|
||||||
// extract string instructions from type
|
|
||||||
// cast category to string
|
|
||||||
// use nil for the modified time
|
|
||||||
|
|
||||||
durationJSON, err := json.Marshal(recipe.Duration)
|
durationJSON, err := json.Marshal(recipe.Duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -157,38 +129,38 @@ func (r *RecipeRepository) EditRecipe(recipe *domain.Recipe, userId int) error {
|
|||||||
instructions[i] = instruction.Content
|
instructions[i] = instruction.Content
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := tx.Exec(
|
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||||
query,
|
|
||||||
recipe.Title,
|
query := psql.
|
||||||
recipe.Description,
|
Update("recipes").
|
||||||
pq.Array(instructions),
|
Set("title", recipe.Title).
|
||||||
recipe.Serves,
|
Set("description", recipe.Description).
|
||||||
recipe.Difficulty,
|
Set("instructions", pq.Array(instructions)).
|
||||||
durationJSON,
|
Set("serves", recipe.Serves).
|
||||||
string(recipe.Category),
|
Set("difficulty", recipe.Difficulty).
|
||||||
ingredientsJSON,
|
Set("duration", durationJSON).
|
||||||
time.Now().UTC(),
|
Set("category", string(recipe.Category)).
|
||||||
recipe.Id,
|
Set("ingredients", ingredientsJSON).
|
||||||
userId,
|
Set("modified", time.Now().UTC()).
|
||||||
)
|
Where(sq.Eq{
|
||||||
|
"id": recipe.Id,
|
||||||
|
"userid": userId,
|
||||||
|
})
|
||||||
|
|
||||||
|
_sql, args, err := query.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
return fmt.Errorf("Failed to construct query: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := result.RowsAffected()
|
result, err := r.db.Exec(_sql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
return fmt.Errorf("Failed to update recipe: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows != 1 {
|
if rows, err := result.RowsAffected(); err != nil {
|
||||||
tx.Rollback()
|
|
||||||
return fmt.Errorf("[ERROR] Modified an unexpected number of rows. Expected 1, modified %d.", rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
|
} else if rows != 1 {
|
||||||
|
return fmt.Errorf("Modified an unexpected number of rows. Expected 1, modified %d.", rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -197,17 +169,35 @@ func (r *RecipeRepository) EditRecipe(recipe *domain.Recipe, userId int) error {
|
|||||||
// DeleteRecipe deletes a recipe in the database. This is done by setting the deleted field to true.
|
// DeleteRecipe deletes a recipe in the database. This is done by setting the deleted field to true.
|
||||||
// This will create a "soft delete" effect. This function does not validate that the user is the owner,
|
// This will create a "soft delete" effect. This function does not validate that the user is the owner,
|
||||||
// so the caller should validate the owner. If any errors occur, they will be returned to the caller.
|
// so the caller should validate the owner. If any errors occur, they will be returned to the caller.
|
||||||
func (r *RecipeRepository) DeleteRecipe(recipeId int) error {
|
func (r *RecipeRepository) DeleteRecipe(recipeId, userId int) error {
|
||||||
query := "UPDATE recipes SET deleted = TRUE WHERE id = $1"
|
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||||
|
|
||||||
result, err := r.db.Exec(query, recipeId)
|
query := psql.
|
||||||
|
Update("recipes").
|
||||||
|
Set("deleted", true).
|
||||||
|
Set("modified", time.Now().UTC()).
|
||||||
|
Where(sq.Eq{
|
||||||
|
"id": recipeId,
|
||||||
|
"userid": userId,
|
||||||
|
"deleted": false,
|
||||||
|
})
|
||||||
|
|
||||||
|
sql, args, err := query.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("Failed to build delete query: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, _ := result.RowsAffected()
|
result, err := r.db.Exec(sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to delete recipe: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to get rows affects: %w", err)
|
||||||
|
}
|
||||||
if rows != 1 {
|
if rows != 1 {
|
||||||
return fmt.Errorf("[ERROR] Incorrect number of rows modified. Expected 1, received %d.", rows)
|
return fmt.Errorf("Incorrect number of rows modified. Expected 1, received %d.", rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -646,28 +636,26 @@ func (r *RecipeRepository) UpdateRecipeTags(recipe domain.Recipe, tags []string)
|
|||||||
//
|
//
|
||||||
// This function will only return recipes that are not deleted. Any recipes marked deleted will be ignored
|
// This function will only return recipes that are not deleted. Any recipes marked deleted will be ignored
|
||||||
// and the standard "not-found" error will be returned.
|
// and the standard "not-found" error will be returned.
|
||||||
func (r *RecipeRepository) GetUserRecipesIds(user_id int) ([]int, error) {
|
func (r *RecipeRepository) GetUserRecipesIds(userId int) ([]int, error) {
|
||||||
query := `
|
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||||
SELECT id
|
|
||||||
FROM recipes
|
|
||||||
WHERE userid = $1 AND deleted = false
|
|
||||||
ORDER BY created DESC;
|
|
||||||
`
|
|
||||||
|
|
||||||
rows, err := r.db.Query(query, user_id)
|
query := psql.
|
||||||
|
Select("id").
|
||||||
|
From("recipes").
|
||||||
|
Where(sq.Eq{
|
||||||
|
"userid": userId,
|
||||||
|
"deleted": false,
|
||||||
|
}).
|
||||||
|
OrderBy("created DESC")
|
||||||
|
|
||||||
|
_sql, args, err := query.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to query DB for user recipes. %s\n", err.Error())
|
return []int{}, fmt.Errorf("Failed to construct SQL query: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var ids []int
|
var ids []int
|
||||||
for rows.Next() {
|
if err := r.db.Select(&ids, _sql, args...); err != nil {
|
||||||
var r_id int
|
return []int{}, fmt.Errorf("Failed to get user recipes: %w", err)
|
||||||
if err := rows.Scan(&r_id); err != nil {
|
|
||||||
return []int{}, fmt.Errorf("Failed to scan ID from db. %s\n", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
ids = append(ids, r_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ids, nil
|
return ids, nil
|
||||||
@ -681,28 +669,29 @@ func (r *RecipeRepository) GetUserRecipesIds(user_id int) ([]int, error) {
|
|||||||
//
|
//
|
||||||
// This function will only return recipes that are not deleted. Any recipes marked deleted will be ignored
|
// This function will only return recipes that are not deleted. Any recipes marked deleted will be ignored
|
||||||
// and the standard "not-found" error will be returned.
|
// and the standard "not-found" error will be returned.
|
||||||
func (r *RecipeRepository) GetUserFavoriteRecipesIds(id int) ([]int, error) {
|
func (r *RecipeRepository) GetUserFavoriteRecipesIds(userId int) ([]int, error) {
|
||||||
query := `
|
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||||
SELECT r.id
|
|
||||||
FROM favorites f
|
query := psql.
|
||||||
JOIN recipes r ON r.id = f.recipeid
|
Select("r.id").
|
||||||
WHERE f.userid = $1 AND deleted = false
|
From("favorites f").
|
||||||
ORDER BY f.created DESC;
|
Join("recipes r on r.id = f.recipeid").
|
||||||
`
|
Where(sq.Eq{
|
||||||
rows, err := r.db.Query(query, id)
|
"f.userid": userId,
|
||||||
|
"deleted": false,
|
||||||
|
}).
|
||||||
|
OrderBy("f.created DESC")
|
||||||
|
|
||||||
|
_sql, args, err := query.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to query DB for user recipes. %s\n", err.Error())
|
return []int{}, fmt.Errorf("Failed to construct SQL query: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
|
||||||
|
fmt.Println(_sql)
|
||||||
|
|
||||||
var ids []int
|
var ids []int
|
||||||
for rows.Next() {
|
if err := r.db.Select(&ids, _sql, args...); err != nil {
|
||||||
var r_id int
|
return []int{}, fmt.Errorf("Failed to get users' favorite recipes: %w", err)
|
||||||
if err := rows.Scan(&r_id); err != nil {
|
|
||||||
return []int{}, fmt.Errorf("Failed to scan ID from db. %s\n", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
ids = append(ids, r_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ids, nil
|
return ids, nil
|
||||||
@ -753,15 +742,24 @@ func (r *RecipeRepository) GetRecipeFavorite(recipe *domain.Recipe, userId int)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
query := `
|
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM favorites
|
query := psql.
|
||||||
WHERE recipeid = $1 AND userid = $2;
|
Select("COUNT(*)").
|
||||||
`
|
From("favorites").
|
||||||
|
Where(sq.Eq{
|
||||||
|
"recipeid": recipe.Id,
|
||||||
|
"userid": userId,
|
||||||
|
})
|
||||||
|
|
||||||
|
_sql, args, err := query.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to construct SQL query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var count int
|
var count int
|
||||||
if err := r.db.QueryRow(query, recipe.Id, userId).Scan(&count); err != nil {
|
if err := r.db.Get(&count, _sql, args...); err != nil {
|
||||||
return fmt.Errorf("Failed to get recipe favorite. %s", err.Error())
|
return fmt.Errorf("Failed to get recipe favorite status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
recipe.Favorite = count > 0
|
recipe.Favorite = count > 0
|
||||||
@ -774,41 +772,56 @@ 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
|
// table and return it. If there is no entry, nil will be returned. Any errors will be bubbled to
|
||||||
// the caller. All that is returned is the recipe ID, that way the caller can handle the fetching.
|
// the caller. All that is returned is the recipe ID, that way the caller can handle the fetching.
|
||||||
func (r *RecipeRepository) GetRecipeOfTheWeekId(userId *int) (*int, error) {
|
func (r *RecipeRepository) GetRecipeOfTheWeekId(userId *int) (*int, error) {
|
||||||
query := `
|
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||||
SELECT
|
|
||||||
r.id
|
|
||||||
FROM recipes r
|
|
||||||
JOIN recipeoftheweek rw ON rw.recipeid = r.id
|
|
||||||
WHERE r.deleted = false
|
|
||||||
ORDER BY rw.created DESC
|
|
||||||
LIMIT 1;
|
|
||||||
`
|
|
||||||
|
|
||||||
var id int
|
query := psql.
|
||||||
if err := r.db.QueryRow(query).Scan(&id); err != nil {
|
Select("r.id").
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
From("recipes r").
|
||||||
|
Join("recipeoftheweek rw ON rw.recipeid = r.id").
|
||||||
|
Where(sq.Eq{"r.deleted": false}).
|
||||||
|
OrderBy("rw.created DESC").
|
||||||
|
Limit(1)
|
||||||
|
|
||||||
|
_sql, args, err := query.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to build SQL query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var recipeId int
|
||||||
|
if err := r.db.Get(&recipeId, _sql, args...); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Failed to locate recipe in database: %s", err.Error())
|
return nil, fmt.Errorf("Failed to locate recipe in database: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &id, nil
|
return &recipeId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRecipeOwner takes two required arguments: a user id and a recipe id. This function queries the DB
|
// IsRecipeOwner takes two required arguments: a user id and a recipe id. This function queries the DB
|
||||||
// to check if the user is the owner of the provided recipe. Any error will be bubbled to the caller.
|
// to check if the user is the owner of the provided recipe. Any error will be bubbled to the caller.
|
||||||
func (r *RecipeRepository) IsRecipeOwner(userId, recipeId int) (bool, error) {
|
func (r *RecipeRepository) IsRecipeOwner(userId, recipeId int) (bool, error) {
|
||||||
query := `
|
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||||
SELECT
|
|
||||||
userid
|
query := psql.
|
||||||
FROM recipes
|
Select("userid").
|
||||||
WHERE deleted = false
|
From("recipes").
|
||||||
AND id = $1;
|
Where(sq.Eq{
|
||||||
`
|
"id": recipeId,
|
||||||
|
"deleted": false,
|
||||||
|
})
|
||||||
|
|
||||||
|
_sql, args, err := query.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
var recipeOwnerId int
|
var recipeOwnerId int
|
||||||
if err := r.db.QueryRow(query, recipeId).Scan(&recipeOwnerId); err != nil {
|
if err := r.db.Get(&recipeOwnerId, _sql, args...); err != nil {
|
||||||
return false, fmt.Errorf("Failed to get recipe owner id: %s", err.Error())
|
if err == sql.ErrNoRows {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("Failed to get recipe owner id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return recipeOwnerId == userId, nil
|
return recipeOwnerId == userId, nil
|
||||||
|
|||||||
@ -127,8 +127,12 @@ export default function RecipePage() {
|
|||||||
<MadeButton id={recipe.Id} />
|
<MadeButton id={recipe.Id} />
|
||||||
<ShareButton id={recipe.Id} />
|
<ShareButton id={recipe.Id} />
|
||||||
|
|
||||||
{isAuthor && <DeleteButton clickHandler={deleteHandler} />}
|
{isAuthor && (
|
||||||
{isAuthor && <EditButton clickHandler={editHandler} />}
|
<>
|
||||||
|
<DeleteButton clickHandler={deleteHandler} />
|
||||||
|
<EditButton clickHandler={editHandler} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
<div className="px-4 py-8 md:px-8">
|
<div className="px-4 py-8 md:px-8">
|
||||||
<h3 className="text-xl text-gray-800 font-semibold mb-2">About this recipe</h3>
|
<h3 className="text-xl text-gray-800 font-semibold mb-2">About this recipe</h3>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user