munch-ease-frontend/src/components/recipes/EditRecipePage.vue

171 lines
4.3 KiB
Vue
Raw Normal View History

2024-01-13 02:00:15 +00:00
<template>
2025-10-18 02:08:22 +00:00
<div>
2024-01-13 08:43:15 +00:00
<div v-if="!id && !recipe">
2025-10-18 02:08:22 +00:00
<input class="recipe-link" type="text" v-model="link" placeholder="Link to Recipe" /> <br />
<button @click="parseLink">Parse</button>
<button @click="createFromScratch">Create from Scratch</button>
2024-01-13 02:00:15 +00:00
</div>
2024-01-13 08:43:15 +00:00
2024-01-13 03:22:10 +00:00
<div v-if="parse_failed">
2025-10-18 02:08:22 +00:00
<p>Recipe not found</p>
2024-01-13 02:00:15 +00:00
</div>
2024-01-13 03:22:10 +00:00
<div v-if="!parse_failed && recipe">
2025-10-18 02:08:22 +00:00
<div class="image-container" v-if="image_styling" :style="image_styling"></div>
<h1><input class="recipe-name" type="text" v-model="recipe.name" /></h1>
<label for="recipe-serves">Number of serves: </label>
<input type="number" v-model="recipe.serves" />
<h3 class="recipe-link"><a :href="recipe.link">View Recipe</a></h3>
<h2>Ingredients</h2>
<editable-ingredients-panel
:ingredients="recipe.ingredients"
:edit-only="true"
@on-add="addIngredient"
@on-delete="deleteIngredient"
@on-update-ingredient="updateIngredient"
/>
2024-05-04 02:00:05 +00:00
2025-10-18 02:08:22 +00:00
<div>
<button v-if="recipe.id" class="delete-btn" @click="deleteRecipe">Delete</button>
<button class="submit-btn" @click="saveRecipe">{{ recipe.id ? 'Save' : 'Create' }}</button>
</div>
2024-01-13 02:00:15 +00:00
</div>
2025-10-18 02:08:22 +00:00
</div>
2024-01-13 02:00:15 +00:00
</template>
<style scoped>
input {
2025-10-18 02:08:22 +00:00
border: 0;
border-bottom: 1px solid #ccc;
font-size: large;
2024-01-13 02:00:15 +00:00
}
input.recipe-link {
2025-10-18 02:08:22 +00:00
width: 80%;
2024-01-13 02:00:15 +00:00
}
2024-01-13 08:43:15 +00:00
.image-container {
2025-10-18 02:08:22 +00:00
max-height: 20vh;
min-height: 20vh;
display: flex;
flex-direction: column;
2024-01-13 08:43:15 +00:00
}
2024-01-13 02:00:15 +00:00
input.recipe-name {
2025-10-18 02:08:22 +00:00
width: 100%;
font-weight: bold;
font-size: larger;
2024-01-13 08:43:15 +00:00
}
.recipe-link {
2025-10-18 02:08:22 +00:00
color: #0000ee;
text-decoration: none;
2024-01-13 02:00:15 +00:00
}
</style>
2025-10-18 02:57:40 +00:00
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
2024-09-22 02:58:25 +00:00
import alert from '@/alert.js'
2025-10-18 02:57:40 +00:00
import { getRecipe, parseRecipe, saveRecipe as saveRecipeApi, deleteRecipe as deleteRecipeApi } from '@/api/recipes'
2024-05-12 07:00:26 +00:00
import EditableIngredientsPanel from '@/components/ingredients/EditableIngredientsPanel.vue'
2024-01-13 02:00:15 +00:00
2025-10-18 02:57:40 +00:00
const props = defineProps({
id: { type: Number, required: false },
})
const router = useRouter()
const route = useRoute()
const link = ref(route.query.url ?? '')
const parse_failed = ref(false)
const recipe = ref(null)
const image_styling = computed(() => {
if (recipe.value?.image_urls && recipe.value.image_urls[0]) {
const image = recipe.value.image_urls[0]
2025-10-18 02:08:22 +00:00
return {
2025-10-18 02:57:40 +00:00
background: `linear-gradient(to bottom, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 100%), url('${image}') center/cover no-repeat`,
2025-10-18 02:08:22 +00:00
}
2025-10-18 02:57:40 +00:00
}
return null
})
function parseLink() {
router.push({ path: '/recipes/add', query: { url: link.value } })
refreshRecipe()
}
async function refreshRecipe() {
if (props.id >= 0) {
recipe.value = await getRecipe(props.id)
link.value = recipe.value.link
return
} else if (link.value) {
recipe.value = await parseRecipe(link.value)
parse_failed.value = !recipe.value
} else {
recipe.value = null
}
}
async function updateIngredient(ingredient, newIngredient) {
recipe.value.ingredients = recipe.value.ingredients.map((i) =>
i == ingredient ? newIngredient : i
)
}
function deleteIngredient(ingredient) {
recipe.value.ingredients = recipe.value.ingredients.filter((i) => i != ingredient)
}
async function saveRecipe() {
const saved = await saveRecipeApi(recipe.value)
if (saved?.id >= 0) {
alert.show({ heading: 'Recipe saved', message: 'Your recipe has been saved', type: 'success' })
router.push(`/recipes/${saved.id}`)
return
}
alert.show({ heading: 'Error saving recipe', message: 'There was an error saving your recipe', type: 'error' })
}
function createFromScratch() {
recipe.value = {
id: -1,
name: 'My new recipe',
created_by_id: -1,
link: '',
ingredients: [],
image_urls: [],
}
2024-01-13 02:00:15 +00:00
}
2025-10-18 02:57:40 +00:00
function addIngredient() {
recipe.value.ingredients = [{ line: '', product: null }, ...recipe.value.ingredients]
}
async function deleteRecipe() {
if (confirm('Are you sure you want to delete this recipe?')) {
await deleteRecipeApi(recipe.value.id)
router.push('/recipes')
}
}
onMounted(() => {
refreshRecipe()
})
// Keep recipe in sync if link query changes while on page
watch(
() => route.query.url,
(newUrl) => {
if (typeof newUrl === 'string') {
link.value = newUrl
refreshRecipe()
}
}
)
// expose functions for template binding names (automatic in <script setup>)
2025-10-18 02:08:22 +00:00
</script>