Load and save recipes from backend
This commit is contained in:
parent
38a5707622
commit
568606213d
5 changed files with 102 additions and 16 deletions
|
|
@ -12,22 +12,35 @@ app.use(express.json());
|
|||
|
||||
let recipeList = recipes;
|
||||
// Routes
|
||||
app.get("/recipe", (req, res) => res.json(recipeList));
|
||||
app.get("/recipe", (req, res) => {
|
||||
console.log("GET /recipe")
|
||||
res.json(recipeList);
|
||||
});
|
||||
|
||||
app.get("/recipe/:id", (req, res) => {
|
||||
let recipeId : string = req.params.id;
|
||||
console.log("GET /recipe/", recipeId);
|
||||
const recipe = recipeList.find(r => r.id === req.params.id);
|
||||
console.log(recipe ? "SUCCESS" : "404")
|
||||
recipe ? res.json(recipe) : res.status(404).send("Recipe not found");
|
||||
});
|
||||
|
||||
app.post("/recipe", (req, res) => {
|
||||
console.log("POST /recipe")
|
||||
const newRecipe: Recipe = { id: uuidv4(), ...req.body };
|
||||
recipeList.push(newRecipe);
|
||||
res.status(201).json(newRecipe);
|
||||
});
|
||||
|
||||
app.put("/recipe/:id", (req, res) => {
|
||||
const index = recipes.findIndex(r => r.id === req.params.id);
|
||||
if (index === -1) return res.status(404).send("Recipe not found");
|
||||
let recipeId : string = req.params.id;
|
||||
console.log("PUT /recipe/", recipeId)
|
||||
const index = recipes.findIndex(r => r.id === recipeId);
|
||||
if (index === -1) {
|
||||
console.log("404")
|
||||
return res.status(404).send("Recipe not found");
|
||||
}
|
||||
console.log("SUCCESS");
|
||||
recipeList[index] = { ...recipeList[index], ...req.body };
|
||||
res.json(recipeList[index]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,8 +4,16 @@ import { API_BASE_URL } from "../config/api"
|
|||
/**
|
||||
* Util for handling the recipe api
|
||||
*/
|
||||
/**
|
||||
* URL for handling recipes
|
||||
*/
|
||||
const RECIPE_URL = `${API_BASE_URL}/recipe`
|
||||
|
||||
/**
|
||||
* Load a single recipe
|
||||
* @param id ID of the recipe to load
|
||||
* @returns A single recipe
|
||||
*/
|
||||
export async function fetchRecipe(id: string): Promise<Recipe> {
|
||||
const res = await fetch(`${RECIPE_URL}/${id}`)
|
||||
if (!res.ok) {
|
||||
|
|
@ -14,6 +22,23 @@ export async function fetchRecipe(id: string): Promise<Recipe> {
|
|||
return res.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* Load list of all recipes
|
||||
* @returns Array of recipe
|
||||
*/
|
||||
export async function fetchRecipeList(): Promise<Recipe[]> {
|
||||
const res = await fetch(`${RECIPE_URL}/`)
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch recipe list`)
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new Recipe
|
||||
* @param recipe Recipe to create
|
||||
* @returns Saved recipe
|
||||
*/
|
||||
export async function createRecipe(recipe: Recipe): Promise<Recipe> {
|
||||
const res = await fetch(RECIPE_URL, {
|
||||
method: "POST",
|
||||
|
|
@ -26,6 +51,11 @@ export async function createRecipe(recipe: Recipe): Promise<Recipe> {
|
|||
return res.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an existing recipe
|
||||
* @param recipe Recipe to save. This recipe must have an ID!
|
||||
* @returns Saved recipe
|
||||
*/
|
||||
export async function updateRecipe(recipe: Recipe): Promise<Recipe> {
|
||||
const res = await fetch(`${RECIPE_URL}/${recipe.id}`, {
|
||||
method: "PUT",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useParams, Link } from "react-router-dom"
|
||||
import { recipes } from "../../mock_data/recipes"
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
import { useState } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { fetchRecipe } from "../../api/recipePoint"
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -11,22 +11,43 @@ import { useState } from "react"
|
|||
export default function RecipeDetailPage() {
|
||||
// Extract recipe ID from route params
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const recipe = recipes.find((r) => r.id === id)
|
||||
// the recipe loaded from the backend, don't change this! it's required for scaling
|
||||
const [recipe, setRecipe] = useState<Recipe | null>(null)
|
||||
// Working copy for re-calculating ingredients
|
||||
const [recipeWorkingCopy, setRecipeWorkingCopy] = useState<Recipe|null>(null)
|
||||
// load recipe data whenever id changes
|
||||
useEffect(() => {
|
||||
const loadRecipe = async () => {
|
||||
if (id) {
|
||||
try {
|
||||
// Fetch recipe data when editing an existing one
|
||||
console.log("loading recipe with id", id)
|
||||
const data = await fetchRecipe(id)
|
||||
setRecipe(data)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadRecipe()
|
||||
}, [id])
|
||||
|
||||
// set original recipe data and working copy when recipe changes
|
||||
useEffect( ()=> {
|
||||
setRecipeWorkingCopy(recipe);
|
||||
}, [recipe])
|
||||
|
||||
|
||||
if (!recipe) {
|
||||
if (!recipe || !recipeWorkingCopy) {
|
||||
return <p className="p-6">Recipe not found.</p>
|
||||
}
|
||||
|
||||
// Working copy for re-calculating ingredients
|
||||
const [recipeWorkingCopy, updateRecipeWorkingCopy] = useState<Recipe>(recipe)
|
||||
// Keep original immutable for scaling
|
||||
const [originalRecipe] = useState<Recipe>(recipe)
|
||||
|
||||
/** recalculate ingredients based on the amount of servings */
|
||||
const recalculateIngredients = (newAmount: number) => {
|
||||
// Always calculate factor from the *original recipe*, not the working copy
|
||||
const factor = newAmount / originalRecipe.servings.amount
|
||||
const factor = newAmount / recipe.servings.amount
|
||||
|
||||
// Create a new ingredient list with updated amounts
|
||||
const updatedIngredientGroupList = recipe.ingredientGroupList.map((ingGrp) => ({
|
||||
|
|
@ -38,7 +59,7 @@ export default function RecipeDetailPage() {
|
|||
}))
|
||||
|
||||
// Update working copy with new servings + recalculated ingredients
|
||||
updateRecipeWorkingCopy({
|
||||
setRecipeWorkingCopy({
|
||||
...recipeWorkingCopy,
|
||||
servings: {
|
||||
...recipeWorkingCopy.servings,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import RecipeEditor from "./RecipeEditor"
|
|||
import { fetchRecipe, createRecipe, updateRecipe } from "../../api/recipePoint"
|
||||
|
||||
export default function RecipeEditPage() {
|
||||
// Extract recipe ID from route params
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const [recipe, setRecipe] = useState<Recipe | null>(null)
|
||||
const navigate = useNavigate()
|
||||
|
|
|
|||
|
|
@ -1,17 +1,38 @@
|
|||
import { recipes } from "../../mock_data/recipes"
|
||||
import { useEffect, useState } from "react"
|
||||
import RecipeListItem from "./RecipeListItem"
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
import { fetchRecipeList } from "../../api/recipePoint"
|
||||
|
||||
/**
|
||||
* Displays a list of recipes in a sidebar layout.
|
||||
* Each recipe link fills the available width.
|
||||
*/
|
||||
export default function RecipeListPage() {
|
||||
|
||||
const [recipeList, setRecipeList] = useState<Recipe[]|null>(null)
|
||||
// load recipes once on render
|
||||
useEffect(() => {
|
||||
const loadRecipeList = async () => {
|
||||
try {
|
||||
// Fetch recipe data when editing an existing one
|
||||
console.log("loading recipe list")
|
||||
const data = await fetchRecipeList()
|
||||
setRecipeList(data)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
loadRecipeList()
|
||||
}, [])
|
||||
|
||||
if(!recipeList) { return <div>Unable to load recipes1</div>}
|
||||
// @todo find a better representation than an oldfashioned sidebar
|
||||
return (
|
||||
<div className="sidebar">
|
||||
<h1 className="sidebar-title">Recipes</h1>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{recipes.map((recipe) => (
|
||||
{recipeList.map((recipe) => (
|
||||
<RecipeListItem
|
||||
key={recipe.id}
|
||||
title = {recipe.title}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue