From 01f42c22d6d0fd2f749791d68a3fedb0937bcf13 Mon Sep 17 00:00:00 2001
From: chloe
Date: Wed, 11 Mar 2026 05:04:48 +0000
Subject: [PATCH] UI + backend update
---
.../Controllers/RecipeController.cs | 27 ++-
Seasoned.Backend/DTOs/RecipeResponseDto.cs | 2 +-
Seasoned.Backend/Models/Recipe.cs | 2 +-
Seasoned.Backend/Services/RecipeService.cs | 35 ++-
.../app/assets/css/app-theme.css | 27 ++-
Seasoned.Frontend/app/assets/css/gallery.css | 74 ++++++
Seasoned.Frontend/app/pages/gallery.vue | 223 +++++++++++++++++-
7 files changed, 346 insertions(+), 44 deletions(-)
diff --git a/Seasoned.Backend/Controllers/RecipeController.cs b/Seasoned.Backend/Controllers/RecipeController.cs
index 3733a4e..b2dc399 100644
--- a/Seasoned.Backend/Controllers/RecipeController.cs
+++ b/Seasoned.Backend/Controllers/RecipeController.cs
@@ -5,7 +5,6 @@ using Seasoned.Backend.Data;
using System.Security.Claims;
using Seasoned.Backend.Models;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace Seasoned.Backend.Controllers;
@@ -49,7 +48,7 @@ public class RecipeController : ControllerBase
var recipe = new Recipe
{
Title = recipeDto.Title,
- Description = recipeDto.Description,
+ Icon = recipeDto.Icon,
Ingredients = recipeDto.Ingredients,
Instructions = recipeDto.Instructions,
CreatedAt = DateTime.UtcNow,
@@ -62,6 +61,29 @@ public class RecipeController : ControllerBase
return Ok(new { message = "Recipe saved to your collection!" });
}
+ [Authorize]
+ [HttpPut("update/{id}")]
+ public async Task UpdateRecipe(int id, [FromBody] Recipe updatedRecipe)
+ {
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+
+ var existingRecipe = await _context.Recipes
+ .FirstOrDefaultAsync(r => r.Id == id && r.UserId == userId);
+
+ if (existingRecipe == null)
+ {
+ return NotFound("Recipe not found or you do not have permission to edit it.");
+ }
+
+ existingRecipe.Title = updatedRecipe.Title;
+ existingRecipe.Ingredients = updatedRecipe.Ingredients;
+ existingRecipe.Instructions = updatedRecipe.Instructions;
+
+ await _context.SaveChangesAsync();
+
+ return Ok(new { message = "Recipe updated successfully!" });
+ }
+
[Authorize]
[HttpGet("my-collection")]
public async Task>> GetMyRecipes()
@@ -75,5 +97,4 @@ public class RecipeController : ControllerBase
return Ok(myRecipes);
}
-
}
\ No newline at end of file
diff --git a/Seasoned.Backend/DTOs/RecipeResponseDto.cs b/Seasoned.Backend/DTOs/RecipeResponseDto.cs
index 1d2b8d5..dd81672 100644
--- a/Seasoned.Backend/DTOs/RecipeResponseDto.cs
+++ b/Seasoned.Backend/DTOs/RecipeResponseDto.cs
@@ -3,7 +3,7 @@ namespace Seasoned.Backend.DTOs;
public class RecipeResponseDto
{
public string Title { get; set; } = string.Empty;
- public string Description { get; set; } = string.Empty;
+ public string Icon { get; set; } = "mdi-silverware-fork-knife";
public List Ingredients { get; set; } = new();
public List Instructions { get; set; } = new();
}
\ No newline at end of file
diff --git a/Seasoned.Backend/Models/Recipe.cs b/Seasoned.Backend/Models/Recipe.cs
index 38825ad..ec0eeff 100644
--- a/Seasoned.Backend/Models/Recipe.cs
+++ b/Seasoned.Backend/Models/Recipe.cs
@@ -3,7 +3,7 @@ namespace Seasoned.Backend.Models;
public class Recipe {
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
- public string? Description { get; set; }
+ public string Icon { get; set; } = "mdi-silverware-fork-knife";
public List Ingredients { get; set; } = new();
public List Instructions { get; set; } = new();
public DateTime CreatedAt { get; set; }
diff --git a/Seasoned.Backend/Services/RecipeService.cs b/Seasoned.Backend/Services/RecipeService.cs
index 705304d..25396a4 100644
--- a/Seasoned.Backend/Services/RecipeService.cs
+++ b/Seasoned.Backend/Services/RecipeService.cs
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
+
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
@@ -18,6 +19,7 @@ public class RecipeService : IRecipeService
public async Task ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
+
var model = googleAI.GenerativeModel("gemini-3.1-flash-lite-preview");
using var ms = new MemoryStream();
@@ -25,16 +27,19 @@ public class RecipeService : IRecipeService
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = @"Extract the recipe details from this image.
+
+ RULES FOR THE 'icon' FIELD:
+ 1. Select a valid 'Material Design Icon' name (e.g., 'mdi-pasta', 'mdi-bread-slice', 'mdi-muffin', 'mdi-pizza').
+ 2. If the recipe type is ambiguous or you cannot find a specific matching icon, you MUST return 'mdi-silverware-fork-knife'.
+
IMPORTANT: Return ONLY a raw JSON string.
- DO NOT include markdown formatting (no ```json).
- DO NOT include any text before or after the JSON.
- All property names and string values MUST be enclosed in double quotes.
+ DO NOT include markdown formatting.
JSON structure:
{
- ""title"": ""string"",
- ""description"": ""string"",
- ""ingredients"": [""string"", ""string""],
- ""instructions"": [""string"", ""string""]
+ ""title"": ""string"",
+ ""icon"": ""string"",
+ ""ingredients"": [""string"", ""string""],
+ ""instructions"": [""string"", ""string""]
}";
var config = new GenerationConfig {
@@ -43,7 +48,8 @@ public class RecipeService : IRecipeService
};
var request = new GenerateContentRequest(prompt, config);
- await Task.Run(() => request.AddMedia(base64Image, "image/png"));
+
+ await Task.Run(() => request.AddMedia(base64Image, image.ContentType ?? "image/png"));
var response = await model.GenerateContent(request);
string rawText = response.Text?.Trim() ?? "";
@@ -53,7 +59,7 @@ public class RecipeService : IRecipeService
if (start == -1 || end == -1)
{
- return new RecipeResponseDto { Title = "Error", Description = "AI failed to generate a valid JSON block." };
+ return new RecipeResponseDto { Title = "Error" };
}
string cleanJson = rawText.Substring(start, (end - start) + 1);
@@ -62,6 +68,12 @@ public class RecipeService : IRecipeService
{
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var result = JsonSerializer.Deserialize(cleanJson, options);
+
+ if (result != null && string.IsNullOrEmpty(result.Icon))
+ {
+ result.Icon = "mdi-silverware-fork-knife";
+ }
+
return result ?? new RecipeResponseDto { Title = "Empty Response" };
}
catch (JsonException ex)
@@ -69,10 +81,7 @@ public class RecipeService : IRecipeService
Console.WriteLine($"Raw AI Output: {rawText}");
Console.WriteLine($"Failed to parse JSON: {ex.Message}");
- return new RecipeResponseDto {
- Title = "Parsing Error",
- Description = "The AI response was malformed. Check logs."
- };
+ return new RecipeResponseDto { Title = "Parsing Error" };
}
}
}
\ No newline at end of file
diff --git a/Seasoned.Frontend/app/assets/css/app-theme.css b/Seasoned.Frontend/app/assets/css/app-theme.css
index 2cbf2c8..56771a6 100644
--- a/Seasoned.Frontend/app/assets/css/app-theme.css
+++ b/Seasoned.Frontend/app/assets/css/app-theme.css
@@ -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));
}
\ No newline at end of file
diff --git a/Seasoned.Frontend/app/assets/css/gallery.css b/Seasoned.Frontend/app/assets/css/gallery.css
index b01ec44..d20e705 100644
--- a/Seasoned.Frontend/app/assets/css/gallery.css
+++ b/Seasoned.Frontend/app/assets/css/gallery.css
@@ -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;
}
\ No newline at end of file
diff --git a/Seasoned.Frontend/app/pages/gallery.vue b/Seasoned.Frontend/app/pages/gallery.vue
index 04ed1ef..530948f 100644
--- a/Seasoned.Frontend/app/pages/gallery.vue
+++ b/Seasoned.Frontend/app/pages/gallery.vue
@@ -3,7 +3,7 @@
- The Collection
+ Your Collection
Hand-Picked & Seasoned
@@ -23,7 +23,7 @@
- Opening the Ledger...
+ Opening Collection...
@@ -37,7 +37,7 @@
style="border: 1px solid #e8e2d6;"
>
@@ -48,14 +48,24 @@
-
- Open Recipe
-
+
+
+ Open
+
+
+ Edit
+
+
@@ -64,22 +74,129 @@
Your collection is empty.
- Return to kitchen to add some
+ Return Home to add some
+
+
+
+
+
+
+
+ {{ selectedRecipe.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ ing }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ index + 1 }}
+
{{ step }}
+
+
+
+
+
+
+ Save Changes
+
+
+
+ Cancel
+
+
+
+
+
+
+
+
\ No newline at end of file