UI + backend update

This commit is contained in:
2026-03-11 05:04:48 +00:00
parent 27bff9535c
commit 01f42c22d6
7 changed files with 346 additions and 44 deletions

View File

@@ -71,25 +71,27 @@
background: transparent !important;
}
.ingredient-item {
border-bottom: 1px dotted #c1b18e;
font-style: italic;
color: #2c2925;
}
.instruction-step {
.instruction-step, .ingredient-item {
display: flex;
font-family: 'Libre Baskerville', serif;
font-size: 1.1rem;
gap: 20px;
line-height: 1.6;
color: #2c2925;
margin: 0 !important;
padding: 0 !important;
flex: 1;
}
.step-number {
font-family: 'Libre Baskerville', serif;
font-weight: bold;
color: #3b4e1e;
font-size: 1.3rem;
min-width: 25px;
font-family: 'Libre Baskerville', serif !important;
font-weight: bold !important;
color: #3b4e1e !important;
font-size: 1.3rem !important;
text-align: center !important;
min-width: 30px;
line-height: 1.4 !important;
padding-top: 16px;
}
.separator {
@@ -237,6 +239,5 @@
}
.brand-icon-container .v-img {
/* Gives it a slightly 'stamped' look on the paper */
filter: drop-shadow(0px 1px 1px rgba(0,0,0,0.1));
}

View File

@@ -47,4 +47,78 @@
.back-to-home-btn:hover {
background-color: #4a3a2a !important;
box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important;
}
.recipe-title-edit .v-field__input {
font-family: 'Libre Baskerville', serif !important;
font-size: 2.4rem !important;
color: #1e1408 !important;
text-align: center !important;
}
.v-textarea .v-field {
background-color: rgba(93, 64, 55, 0.05) !important;
border-radius: 8px !important;
}
.save-btn, .cancel-btn {
font-family: 'Libre Baskerville', serif !important;
text-transform: none !important;
font-weight: bold !important;
font-size: 1.1rem !important;
letter-spacing: 0px !important;
transition: all 0.3s ease;
}
.save-btn {
color: #556b2f !important;
}
.save-btn:hover {
background-color: rgba(85, 107, 47, 0.08) !important;
}
.cancel-btn {
color: #8c4a32 !important;
}
.cancel-btn:hover {
background-color: rgba(140, 74, 50, 0.05) !important;
}
.recipe-title-edit .v-field__input {
font-family: 'Libre Baskerville', serif !important;
font-weight: 700 !important;
color: #1e1408 !important;
font-size: 2.4rem !important;
text-align: center !important;
}
.v-textarea .v-field__input {
font-weight: 500 !important;
color: #2c2925 !important;
line-height: 1.6 !important;
}
.v-field-label {
color: #5d4037 !important;
opacity: 0.6 !important;
}
.v-field__outline {
--v-field-border-opacity: 1 !important;
color: #d1c7b7 !important;
}
.v-field--focused .v-field__outline {
color: #556b2f !important;
}
.recipe-title-edit.v-text-field .v-field__outline__line {
border-bottom-width: 2px !important;
color: #d1c7b7 !important;
}
.recipe-title-edit.v-field--focused .v-field__outline__line {
color: #556b2f !important;
}

View File

@@ -3,7 +3,7 @@
<v-card class="recipe-card pa-10 mx-auto mt-10" max-width="1200" elevation="1">
<header class="text-center mb-10">
<h1 class="brand-title">The Collection</h1>
<h1 class="brand-title">Your Collection</h1>
<p class="brand-subtitle">Hand-Picked & Seasoned</p>
</header>
@@ -23,7 +23,7 @@
<v-row v-if="loading" justify="center" class="py-16">
<v-col cols="12" class="d-flex flex-column align-center">
<v-progress-circular indeterminate color="#556b2f" size="64" width="3"></v-progress-circular>
<p class="brand-subtitle mt-4">Opening the Ledger...</p>
<p class="brand-subtitle mt-4">Opening Collection...</p>
</v-col>
</v-row>
@@ -37,7 +37,7 @@
style="border: 1px solid #e8e2d6;"
>
<v-icon
:icon="getRecipeIcon(recipe.title)"
:icon="getRecipeIcon(recipe)"
size="80"
color="#d1c7b7"
></v-icon>
@@ -48,14 +48,24 @@
</p>
<v-card-actions class="justify-center">
<v-btn
variant="text"
class="view-recipe-btn"
color="#556b2f"
@click="openRecipe(recipe)"
>
Open Recipe
</v-btn>
<v-card-actions class="justify-center">
<v-btn
variant="text"
color="#556b2f"
class="save-btn"
@click="openRecipe(recipe)"
>
Open
</v-btn>
<v-btn
variant="text"
color="#8c4a32"
class="cancel-btn"
@click="editRecipe(recipe)"
>
Edit
</v-btn>
</v-card-actions>
</v-card-actions>
</v-card>
</v-col>
@@ -64,22 +74,129 @@
<v-row v-else justify="center" class="py-10 text-center">
<v-col cols="12">
<p class="brand-subtitle mb-4">Your collection is empty.</p>
<v-btn to="/" variant="text" color="#556b2f">Return to kitchen to add some</v-btn>
<v-btn to="/" variant="text" color="#556b2f">Return Home to add some</v-btn>
</v-col>
</v-row>
</v-card>
<v-dialog v-model="showDetails" max-width="800" persistent>
<v-card v-if="selectedRecipe" class="recipe-card pa-8">
<v-btn
icon="mdi-close"
variant="text"
position="absolute"
style="top: 10px; right: 10px;"
color="#5d4037"
@click="closeDetails"
></v-btn>
<header class="text-center mb-6">
<v-text-field
v-if="isEditing"
v-model="selectedRecipe.title"
variant="underlined"
class="recipe-title-edit"
></v-text-field>
<h2 v-else class="recipe-title">{{ selectedRecipe.title }}</h2>
</header>
<v-divider class="mb-6 separator"></v-divider>
<v-row justify="center" class="px-md-10">
<v-col cols="12" md="5" class="d-flex flex-column align-center">
<div style="width: 100%; max-width: 300px;">
<h3 class="section-header mb-4">
<v-icon icon="mdi-basket-outline" class="mr-2" size="small"></v-icon>
Ingredients
</h3>
<v-textarea
v-if="isEditing"
v-model="selectedRecipe.ingredients"
variant="outlined"
auto-grow
density="comfortable"
bg-color="rgba(255,255,255,0.3)"
></v-textarea>
<v-list v-else class="ingredients-list">
<v-list-item
v-for="(ing, index) in selectedRecipe.ingredients?.split('\n').filter(i => i.trim())"
:key="index"
class="ingredient-item px-0"
>
{{ ing }}
</v-list-item>
</v-list>
</div>
</v-col>
<v-col cols="12" md="7">
<h3 class="section-header mb-4">
<v-icon icon="mdi-chef-hat" class="mr-2" size="small"></v-icon>
Instructions
</h3>
<v-textarea
v-if="isEditing"
v-model="selectedRecipe.instructions"
variant="outlined"
auto-grow
density="comfortable"
bg-color="rgba(255,255,255,0.3)"
></v-textarea>
<div v-else
v-for="(step, index) in selectedRecipe.instructions?.split('\n').filter(s => s.trim())"
:key="index"
class="instruction-step mb-4"
>
<span class="step-number">{{ index + 1 }}</span>
<p>{{ step }}</p>
</div>
</v-col>
</v-row>
<v-card-actions v-if="isEditing" class="justify-center mt-6">
<v-btn
variant="text"
@click="saveChanges"
class="save-btn px-8"
>
Save Changes
</v-btn>
<v-btn
variant="text"
@click="isEditing = false"
class="cancel-btn px-8"
>
Cancel
</v-btn>
</v-card-actions>
<v-divider class="my-6 separator"></v-divider>
<footer class="text-center">
<p class="brand-subtitle" style="font-size: 0.8rem;">
Recorded on {{ new Date(selectedRecipe.createdAt).toLocaleDateString() }}
</p>
</footer>
</v-card>
</v-dialog>
</v-container>
</template>
<script setup>
import '@/assets/css/gallery.css'
const config = useRuntimeConfig()
const recipes = ref([])
const loading = ref(true)
const showDetails = ref(false)
const selectedRecipe = ref(null)
const isEditing = ref(false)
const originalRecipe = ref(null)
onMounted(async () => {
await fetchRecipes()
@@ -108,4 +225,84 @@ const fetchRecipes = async () => {
loading.value = false
}
}
const openRecipe = (recipe) => {
selectedRecipe.value = { ...recipe }
isEditing.value = false
showDetails.value = true
}
const editRecipe = (recipe) => {
selectedRecipe.value = { ...recipe }
originalRecipe.value = { ...recipe }
isEditing.value = true
showDetails.value = true
}
const closeDetails = () => {
showDetails.value = false
isEditing.value = false
}
const saveChanges = async () => {
const token = useCookie('seasoned_token').value
try {
await $fetch(`${config.public.apiBase}api/recipe/update/${selectedRecipe.value.id}`, {
method: 'PUT',
headers: { 'Authorization': `Bearer ${token}` },
body: selectedRecipe.value
})
await fetchRecipes()
isEditing.value = false
showDetails.value = false
} catch (e) {
console.error("Failed to update recipe:", e)
alert("Could not save changes. Please try again.")
}
}
// Mock setup for recipe cards
//const mockData = [
// {
// id: 1,
// title: "Grandma's Secret Bolognese",
// createdAt: new Date().toISOString(),
// ingredients: "2 lbs Ground Beef\n1 Onion, diced\n3 cloves Garlic\n2 cans Crushed Tomatoes\n1 cup Red Wine",
// instructions: "Brown the meat with onions.\nAdd garlic and wine; reduce.\nSimmer with tomatoes for 3 hours."
// },
// {
//id: 2,
//title: "Rustic Sourdough",
//createdAt: new Date().toISOString(),
//ingredients: "500g Flour\n350g Water\n100g Starter\n10g Salt",
//instructions: "Mix and autolyse for 1 hour.\nPerform 4 sets of stretch and folds.\nCold ferment for 12 hours.\nBake at 450°F in a dutch oven."
//}
//]
//recipes.value = mockData
//loading.value = false
//}
//const saveChanges = async () => {
//const index = recipes.value.findIndex(r => r.id === selectedRecipe.value.id)
//if (index !== -1) {
//recipes.value[index] = { ...selectedRecipe.value }
//}
//isEditing.value = false
//showDetails.value = false
//}
const getRecipeIcon = (title) => {
const t = title.toLowerCase()
if (recipe.icon) return recipe.icon
if (t.includes('cake') || t.includes('cookie') || t.includes('dessert')) return 'mdi-cookie'
if (t.includes('soup') || t.includes('stew')) return 'mdi-bowl-mix'
if (t.includes('drink') || t.includes('cocktail')) return 'mdi-glass-cocktail'
return 'mdi-silverware-fork-knife'
}
</script>