migrate from knex to prisma on backend

Co-authored-by: fred <>
Reviewed-on: #1
This commit is contained in:
fred 2025-08-15 20:52:38 +00:00
parent 24281a6322
commit 0a41568a2e
31 changed files with 1577 additions and 472 deletions

View file

@ -1,10 +1,8 @@
import { getRecipeIngredients } from "../services/frontendApi.js";
import { useState, useEffect } from "react";
import { type Ingredient } from "../types/Recipe.ts"
function RecipeIngredients() {
const [recipeIngredients, setRecipeIngredients] = useState<Ingredient[]>([]);
const [recipeIngredients, setRecipeIngredients] = useState<string[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
@ -13,36 +11,32 @@ function RecipeIngredients() {
try {
const recipeIngredients = await getRecipeIngredients();
setRecipeIngredients(recipeIngredients);
console.log(recipeIngredients)
console.log(recipeIngredients);
} catch (err) {
console.log(err);
setError("Failed to load recipe ingredients...");
console.log(error)
console.log(error);
} finally {
setLoading(false);
}
};
loadRecipeIngredients();
}, []);
console.log(recipeIngredients)
console.log(recipeIngredients);
return (
// should this be a string[]? only if we are only returning raw. otherwise i will need to type and return the ingredient object. This template shoudl work for steps though, so maybe setting that up is a good first step
<div className='page-outer'>
<div className="page-outer">
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="recipe-outer bg-amber-100 p-4 md:p-8 lg:p-12">
<div className="recipes-grid grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
{recipeIngredients.map(ing => (
<li key={ing.id}>
{ing.raw}
</li>
{recipeIngredients.map((ing, idx) => (
<li key={idx}>{ing}</li>
))}
</div>
</div>
)}
</div>
)
);
}
export default RecipeIngredients
export default RecipeIngredients;

View file

@ -1,17 +1,21 @@
import { useParams, useNavigate, Link } from "react-router-dom";
import { useState, useEffect } from "react";
import { getRecipeById, deleteRecipe, setDBStars } from "../services/frontendApi.js";
import { type Recipe, type Ingredient } from "../types/Recipe"
import Modal from '../components/Modal.tsx'
import DemoModal from '../components/DemoModal.tsx'
import StarRating from "../components/StarRating.tsx"
import TimeDisplay from '../components/TimeDisplay.tsx'
import {
getRecipeById,
deleteRecipe,
setDBStars,
} from "../services/frontendApi.js";
import { type Recipe } from "../types/Recipe";
import Modal from "../components/Modal.tsx";
import DemoModal from "../components/DemoModal.tsx";
import StarRating from "../components/StarRating.tsx";
import TimeDisplay from "../components/TimeDisplay.tsx";
function RecipePage() {
const [recipe, setRecipe] = useState<Recipe>({
details: {},
ingredients: [],
steps: []
steps: [],
});
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
@ -20,16 +24,21 @@ function RecipePage() {
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 isWebSource =
recipe && recipe.details && recipe.details.author
? /http|com/.test(recipe.details.author) //etc
: false;
const navigate = useNavigate();
const openModal = () => { setShowConfirmModal(true) };
const closeModal = () => { setShowConfirmModal(false) };
const openModal = () => {
setShowConfirmModal(true);
};
const closeModal = () => {
setShowConfirmModal(false);
};
const confirmDelete = () => {
if (process.env.NODE_ENV === 'demo') {
if (process.env.NODE_ENV === "demo") {
closeModal();
setShowDemoModal(true);
} else {
@ -43,13 +52,13 @@ function RecipePage() {
try {
const recipe = await getRecipeById(id);
if (!recipe.details) {
setError("Sorry, this recipe no longer exists")
setError("Sorry, this recipe no longer exists");
} else {
setRecipe(recipe);
setStars(recipe.details?.stars ?? 0)
setStars(recipe.details?.stars ?? 0);
setInitialStars(recipe.details?.stars ?? 0);
if (process.env.NODE_ENV === 'dev') {
console.log(recipe)
if (process.env.NODE_ENV === "dev") {
console.log(recipe);
}
}
} catch (error) {
@ -77,38 +86,56 @@ function RecipePage() {
const handleDelete = async (id: number | void) => {
try {
await deleteRecipe(id);
navigate('/')
navigate("/");
} catch (error) {
console.error("Error deleting recipe:", error);
}
};
return (
<div className="recipe page-outer">
{loading ? (
<div className="loading">Loading...</div>
) : error ? (
<div>
<div className="error-message text-lg">{error}</div>
<div className="m-2">
<Link to="/" className="ar-button bg-amber-600 text-white py-2 px-4 rounded hover:bg-amber-700">
<Link
to="/"
className="ar-button bg-amber-600 text-white py-2 px-4 rounded hover:bg-amber-700"
>
Return to Cookbook
</Link>
</div>
</div>
) : (
<div className="border-b-2 border-amber-300 pb-4">
<div className="recipe-card">
<div className="flex relative justify-between">
<button onClick={() => { }} className="invisible ar-button py-1 px-1 rounded self-start">🗑</button>
<h3 className="text-center max-w-lg px-4 text-2xl lg:text-3xl font-bold text-amber-900">{recipe.details.name}</h3>
<button onClick={openModal} className="ar-button bg-amber-500 text-white py-1 px-1 rounded hover:bg-amber-600 self-start">🗑</button>
<button
onClick={() => {}}
className="invisible ar-button py-1 px-1 rounded self-start"
>
🗑
</button>
<h3 className="text-center max-w-lg px-4 text-2xl lg:text-3xl font-bold text-amber-900">
{recipe.details.name}
</h3>
<button
onClick={openModal}
className="ar-button bg-amber-500 text-white py-1 px-1 rounded hover:bg-amber-600 self-start"
>
🗑
</button>
</div>
<div className="mt-1">
<p className="text-amber-700 italic text-lg">{recipe.details.cuisine}</p>
<p>prep: <TimeDisplay minutes={recipe.details.prep_minutes ?? 0} /> | cook: <TimeDisplay minutes={recipe.details.cook_minutes ?? 0} /></p>
<p className="text-amber-700 italic text-lg">
{recipe.details.cuisine}
</p>
<p>
prep: <TimeDisplay minutes={recipe.details.prep_minutes ?? 0} />{" "}
| cook:{" "}
<TimeDisplay minutes={recipe.details.cook_minutes ?? 0} />
</p>
</div>
</div>
@ -118,10 +145,10 @@ function RecipePage() {
Ingredients:
</h4>
<ul className="space-y-2">
{recipe.ingredients.map((ingredient: Ingredient, index) => (
{recipe.ingredients.map((ingredient: string, index) => (
<li key={index} className="text-gray-700 flex items-start">
<span className="w-1.5 h-1.5 bg-amber-400 rounded-full mt-2 mr-3 flex-shrink-0"></span>
<span className="font-medium text-left">{ingredient.raw}</span>
<span className="font-medium text-left">{ingredient}</span>
</li>
))}
</ul>
@ -132,14 +159,20 @@ function RecipePage() {
Instructions:
</h4>
<ol className="space-y-3">
{recipe.steps && Object.keys(recipe.steps || {}).map((stepNumber) => (
<li key={stepNumber} className="text-gray-700 flex items-start">
<span className="bg-amber-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold mr-3 mt-0.5 flex-shrink-0">
{recipe.steps[parseInt(stepNumber)].step_number}
</span>
<span className="leading-relaxed text-left">{recipe.steps[parseInt(stepNumber)].instruction}</span>
</li>
))}
{recipe.steps &&
Object.keys(recipe.steps || {}).map((stepNumber) => (
<li
key={stepNumber}
className="text-gray-700 flex items-start"
>
<span className="bg-amber-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold mr-3 mt-0.5 flex-shrink-0">
{recipe.steps[parseInt(stepNumber)].step_number}
</span>
<span className="leading-relaxed text-left">
{recipe.steps[parseInt(stepNumber)].instruction}
</span>
</li>
))}
</ol>
</div>
</div>
@ -152,13 +185,15 @@ function RecipePage() {
<span>From the kitchen of {recipe.details.author}</span>
)}
<span>
<StarRating rating={stars} onRatingChange={(newRating: number) => setStars(newRating)} />
<StarRating
rating={stars}
onRatingChange={(newRating: number) => setStars(newRating)}
/>
</span>
</div>
</div>
</div>
)
}
)}
<Modal
isOpen={showConfirmModal}
onClose={closeModal}
@ -173,7 +208,7 @@ function RecipePage() {
closeModal={() => setShowDemoModal(false)}
/>
)}
</div >
</div>
);
}