From 91439cbcfa24abcd1f8261ac5e73374f539272de Mon Sep 17 00:00:00 2001
From: fred <>
Date: Wed, 6 Aug 2025 16:18:06 -0700
Subject: [PATCH] more detail on index; move delete to recipe page
---
backend/backendServer.js | 2 +-
.../src/components/CookbookRecipeTile.tsx | 56 +++----------
frontend/src/components/RecipeBookTabs.tsx | 3 -
frontend/src/components/StarRating.tsx | 2 +-
frontend/src/pages/AddRecipe.tsx | 3 +-
frontend/src/pages/Index.tsx | 17 ++--
frontend/src/pages/RecipePage.tsx | 81 ++++++++++++++++---
frontend/src/services/frontendApi.js | 3 +
frontend/src/types/Recipe.ts | 3 +
9 files changed, 95 insertions(+), 75 deletions(-)
diff --git a/backend/backendServer.js b/backend/backendServer.js
index 919025b..74f3ea6 100644
--- a/backend/backendServer.js
+++ b/backend/backendServer.js
@@ -15,7 +15,7 @@ app.get("/backend/test", async (req, res) => {
// ### GET ALL RECIPES ###
app.get("/backend/recipes", async (req, res) => {
try {
- const recipes = await db("recipes").select("id", "name", "cuisine");
+ const recipes = await db("recipes").select("id", "name", "cuisine", "stars", "prep_minutes", "cook_minutes");
res.json(recipes);
} catch (err) {
console.log(err);
diff --git a/frontend/src/components/CookbookRecipeTile.tsx b/frontend/src/components/CookbookRecipeTile.tsx
index fd2ad97..c090f26 100644
--- a/frontend/src/components/CookbookRecipeTile.tsx
+++ b/frontend/src/components/CookbookRecipeTile.tsx
@@ -1,55 +1,21 @@
-import { useState } from 'react';
import { type RecipeSmall } from "../types/Recipe"
-import Modal from '../components/Modal.tsx'
-import DemoModal from '../components/DemoModal.tsx'
import { Link } from 'react-router-dom';
+import StarRating from '../components/StarRating.tsx'
-interface CookbookRecipeTileProps {
- recipe: RecipeSmall;
- handleDelete: (id: number | undefined) => void;
-}
-
-function CookbookRecipeTile({ recipe, handleDelete }: CookbookRecipeTileProps) {
- const [showConfirmModal, setShowConfirmModal] = useState(false);
- const [showDemoModal, setShowDemoModal] = useState(false);
-
- const openModal = () => { setShowConfirmModal(true) };
- const closeModal = () => { setShowConfirmModal(false) };
- const confirmDelete = () => {
- if (process.env.NODE_ENV === 'demo') {
- closeModal();
- setShowDemoModal(true);
- } else {
- handleDelete(recipe.id);
- closeModal();
- }
- };
-
+function CookbookRecipeTile({ recipe }: { recipe: RecipeSmall }) {
return (
-
+
-
{recipe.name}
-
+
{recipe.name}
+
+ {recipe.cuisine}
+
+
+
+ ⏰ {recipe.prep_minutes + recipe.cook_minutes} min
+ { }} />
-
- {showDemoModal && (
-
setShowDemoModal(false)}
- closeModal={() => setShowDemoModal(false)}
- />
- )}
);
};
diff --git a/frontend/src/components/RecipeBookTabs.tsx b/frontend/src/components/RecipeBookTabs.tsx
index 7f678b1..890e6fb 100644
--- a/frontend/src/components/RecipeBookTabs.tsx
+++ b/frontend/src/components/RecipeBookTabs.tsx
@@ -30,9 +30,6 @@ const RecipeBookTabs = () => {
useEffect(() => {
if (!lastViewedRecipeId) {
loadRandomRecipeId();
- } else {
- console.log('id found', lastViewedRecipeId)
-
}
}, []);
diff --git a/frontend/src/components/StarRating.tsx b/frontend/src/components/StarRating.tsx
index 91af1e0..cd5d5bc 100644
--- a/frontend/src/components/StarRating.tsx
+++ b/frontend/src/components/StarRating.tsx
@@ -13,7 +13,7 @@ const StarRating = ({ rating, onRatingChange }: StarRatingProps) => {
onRatingChange(index)}
- style={{ color: index <= rating ? 'gold' : 'gray', fontSize: '2rem', cursor: 'pointer' }}
+ style={{ color: index <= rating ? '#FFB800' : 'gray', fontSize: '1.5rem', cursor: 'pointer' }}
>
★
diff --git a/frontend/src/pages/AddRecipe.tsx b/frontend/src/pages/AddRecipe.tsx
index 9ce744c..54f61c4 100644
--- a/frontend/src/pages/AddRecipe.tsx
+++ b/frontend/src/pages/AddRecipe.tsx
@@ -5,7 +5,6 @@ import AddBulkIngredients from "../components/AddBulkIngredients.tsx"
import AddBulkSteps from "../components/AddBulkSteps.tsx"
import StarRating from "../components/StarRating.tsx"
import DemoModal from '../components/DemoModal.tsx'
-// import { type Step } from "../types/Recipe";
interface Step {
step_number: number;
@@ -14,7 +13,6 @@ interface Step {
function AddRecipe() {
const [newRecipeId, setNewRecipeId] = useState
(null);
- const navigate = useNavigate();
const [ingredients, setIngredients] = useState([]);
const [steps, setSteps] = useState([]);
const [showBulkForm, setShowBulkForm] = useState(true);
@@ -25,6 +23,7 @@ function AddRecipe() {
const [prepMinutes, setPrepMinutes] = useState(5);
const [cookMinutes, setCookMinutes] = useState(5);
const [showDemoModal, setShowDemoModal] = useState(false);
+ const navigate = useNavigate();
const addRecipeForm = async (event: React.FormEvent) => {
event.preventDefault();
diff --git a/frontend/src/pages/Index.tsx b/frontend/src/pages/Index.tsx
index 3f1c8bb..894492e 100644
--- a/frontend/src/pages/Index.tsx
+++ b/frontend/src/pages/Index.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import CookbookRecipeTile from "../components/CookbookRecipeTile.tsx"
-import { getRecipes, deleteRecipe } from "../services/frontendApi.js";
+import { getRecipes } from "../services/frontendApi.js";
import { type RecipeSmall } from "../types/Recipe.ts"
@@ -18,12 +18,13 @@ function AllRecipes() {
try {
const recipes = await getRecipes();
setRecipes(recipes);
- console.log(recipes)
+ if (process.env.NODE_ENV === 'dev') {
+ console.log(recipes)
+ }
const uniqueCuisines: string[] = recipes.length > 0
? Array.from(new Set(recipes.map((recipe: RecipeSmall) => recipe.cuisine)))
: [];
setCuisines(uniqueCuisines)
- console.log(cuisines)
} catch (error) {
console.log(error);
setError("Failed to load recipes...");
@@ -36,14 +37,6 @@ function AllRecipes() {
}
}, [shouldFetchRecipes]);
- const handleDelete = async (id: number | void) => {
- try {
- await deleteRecipe(id);
- setShouldFetchRecipes(true);
- } catch (error) {
- console.error("Error deleting recipe:", error);
- }
- };
const filteredRecipes = selectedCuisine ? recipes.filter(recipe => recipe.cuisine === selectedCuisine) : recipes;
return (
@@ -74,7 +67,7 @@ function AllRecipes() {
{filteredRecipes.map((recipe) => (
recipe.name.toLowerCase().includes(searchQuery.toLowerCase()) &&
-
+
))}
diff --git a/frontend/src/pages/RecipePage.tsx b/frontend/src/pages/RecipePage.tsx
index 83ff1ad..dbc92de 100644
--- a/frontend/src/pages/RecipePage.tsx
+++ b/frontend/src/pages/RecipePage.tsx
@@ -1,9 +1,11 @@
-import { useParams } from "react-router-dom";
+import { useParams, useNavigate, Link } from "react-router-dom";
import { useState, useEffect } from "react";
-import { getRecipeById } from "../services/frontendApi.js";
+import { getRecipeById, deleteRecipe } from "../services/frontendApi.js";
import { type Recipe, type Ingredient } from "../types/Recipe"
import StarRating from "../components/StarRating.tsx"
import { setDBStars } from "../services/frontendApi.js";
+import Modal from '../components/Modal.tsx'
+import DemoModal from '../components/DemoModal.tsx'
function RecipePage() {
const [recipe, setRecipe] = useState({
@@ -13,21 +15,43 @@ function RecipePage() {
});
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
+ const [stars, setStars] = useState(0);
+ const [initialStars, setInitialStars] = useState(null);
+ const [showConfirmModal, setShowConfirmModal] = useState(false);
+ const [showDemoModal, setShowDemoModal] = useState(false);
const { id } = useParams();
const isWebSource = recipe && recipe.details && recipe.details.author
? /http|com/.test(recipe.details.author) //etc
: false;
- const [stars, setStars] = useState(0);
- const [initialStars, setInitialStars] = useState(null);
+ const navigate = useNavigate();
+
+ const openModal = () => { setShowConfirmModal(true) };
+ const closeModal = () => { setShowConfirmModal(false) };
+
+ const confirmDelete = () => {
+ if (process.env.NODE_ENV === 'demo') {
+ closeModal();
+ setShowDemoModal(true);
+ } else {
+ handleDelete(recipe.details.id);
+ closeModal();
+ }
+ };
useEffect(() => {
const loadRecipe = async () => {
try {
const recipe = await getRecipeById(id);
- setRecipe(recipe);
- setStars(recipe.details?.stars ?? 0)
- setInitialStars(recipe.details?.stars ?? 0);
- console.log(recipe)
+ if (!recipe.details) {
+ setError("Sorry, this recipe no longer exists")
+ } else {
+ setRecipe(recipe);
+ setStars(recipe.details?.stars ?? 0)
+ setInitialStars(recipe.details?.stars ?? 0);
+ if (process.env.NODE_ENV === 'dev') {
+ console.log(recipe)
+ }
+ }
} catch (error) {
console.log(error);
setError("Failed to load recipes...");
@@ -49,16 +73,36 @@ function RecipePage() {
updateStarsInDB();
}, [stars]);
+
+ const handleDelete = async (id: number | void) => {
+ try {
+ await deleteRecipe(id);
+ navigate('/')
+ } catch (error) {
+ console.error("Error deleting recipe:", error);
+ }
+ };
return (
- {error &&
{error}
}
{loading ? (
Loading...
+ ) : error ? (
+
+
{error}
+
+
+ Return to Cookbook
+
+
+
) : (
-
+
+
{recipe.details.name}
{recipe.details.cuisine}
@@ -110,8 +154,23 @@ function RecipePage() {
+ )
+ }
+
+ {showDemoModal && (
+
setShowDemoModal(false)}
+ closeModal={() => setShowDemoModal(false)}
+ />
)}
-
+
);
}
diff --git a/frontend/src/services/frontendApi.js b/frontend/src/services/frontendApi.js
index 4158c4a..69117c9 100644
--- a/frontend/src/services/frontendApi.js
+++ b/frontend/src/services/frontendApi.js
@@ -23,6 +23,9 @@ export const getRecipeIngredients = async () => {
export const getRecipeById = async (id) => {
const response = await fetch(`${baseUrl}backend/recipe/${id}`);
const data = await response.json();
+ if (!data || !data.details) {
+ return { details: null };
+ }
return data;
};
diff --git a/frontend/src/types/Recipe.ts b/frontend/src/types/Recipe.ts
index c988ea7..97298aa 100644
--- a/frontend/src/types/Recipe.ts
+++ b/frontend/src/types/Recipe.ts
@@ -29,6 +29,9 @@ interface RecipeSmall {
id: number;
name: string;
cuisine: string;
+ stars: number;
+ cook_minutes: number;
+ prep_minutes: number;
}