This commit is contained in:
parent
7258d283ed
commit
31f5bdc254
42 changed files with 21523 additions and 1040 deletions
6
frontend/package-lock.json
generated
6
frontend/package-lock.json
generated
|
|
@ -2196,9 +2196,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ const RecipeForm: React.FC<RecipeFormProps> = ({ onSubmit, initialData }) => {
|
|||
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
setRecipeName(initialData.details.name || "");
|
||||
setRecipeCuisine(initialData.details.cuisine || "");
|
||||
setAuthor(initialData.details.author || "");
|
||||
setStars(initialData.details.stars || 0);
|
||||
setPrepMinutes(initialData.details.prep_minutes || 5);
|
||||
setCookMinutes(initialData.details.cook_minutes || 5);
|
||||
setIngredients(initialData.ingredients);
|
||||
setSteps(initialData.steps);
|
||||
setRecipeName(initialData.name || "");
|
||||
setRecipeCuisine(initialData.cuisine || "");
|
||||
setAuthor(initialData.author || "");
|
||||
setStars(initialData.stars || 0);
|
||||
setPrepMinutes(initialData.prep_minutes || 5);
|
||||
setCookMinutes(initialData.cook_minutes || 5);
|
||||
setIngredients(initialData.recipeIngredients);
|
||||
setSteps(initialData.recipeSteps);
|
||||
}
|
||||
}, [initialData]);
|
||||
|
||||
|
|
@ -53,19 +53,17 @@ const RecipeForm: React.FC<RecipeFormProps> = ({ onSubmit, initialData }) => {
|
|||
}
|
||||
|
||||
const recipeData = {
|
||||
details: {
|
||||
name: recipeName,
|
||||
cuisine: recipeCuisine.toLowerCase(),
|
||||
author: author,
|
||||
prep_minutes: prepMinutes,
|
||||
cook_minutes: cookMinutes,
|
||||
stars: stars,
|
||||
},
|
||||
ingredients: ingredients,
|
||||
steps: steps.map((step) => ({
|
||||
name: recipeName,
|
||||
cuisine: recipeCuisine.toLowerCase(),
|
||||
author: author,
|
||||
prep_minutes: prepMinutes,
|
||||
cook_minutes: cookMinutes,
|
||||
stars: stars,
|
||||
recipeIngredients: ingredients.map((ing) => ing.trim()),
|
||||
recipeSteps: steps.map((step) => ({
|
||||
id: undefined,
|
||||
step_number: step.step_number,
|
||||
instruction: step.instruction,
|
||||
instruction: step.instruction.trim(),
|
||||
})),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,58 @@
|
|||
function About() {
|
||||
|
||||
return (
|
||||
<div className="about page-outer">
|
||||
<div>
|
||||
<h2 className="text-xl text-[var(--color-secondaryTextDark)]">This app uses the following components:</h2>
|
||||
<h2 className="mt-4 font-bold text-xl text-[var(--color-secondaryTextDark)]">Frontend:</h2>
|
||||
<ul><li>React</li><li>TypeScript</li></ul>
|
||||
<h2 className="mt-4 font-bold text-xl text-[var(--color-secondaryTextDark)]">Backend:</h2>
|
||||
<ul><li>Node.js & Express</li><li>PostgreSQL</li><li>Prisma</li></ul>
|
||||
<h2 className="mt-4 font-bold text-xl text-[var(--color-secondaryTextDark)]">Containerization:</h2>
|
||||
<ul><li>Docker</li></ul>
|
||||
<h2 className="mt-4 font-bold text-xl text-[var(--color-secondaryTextDark)]">Styling/UI:</h2>
|
||||
<ul><li>Tailwind CSS</li></ul>
|
||||
<p className="mt-4 text-[var(--color-secondaryTextDark)]">More about me <a className="text-[var(--color-textLink)]" target="_blank" href="https://fredzernia.com">here</a> |
|
||||
Code for this app <a className="text-[var(--color-textLink)]" target="_blank" href="https://forgejo.fredzernia.com/fred/recipe_app">here</a></p>
|
||||
<h2 className="text-xl text-[var(--color-secondaryTextDark)]">
|
||||
This app uses the following components:
|
||||
</h2>
|
||||
<h2 className="mt-4 font-bold text-xl text-[var(--color-secondaryTextDark)]">
|
||||
Frontend:
|
||||
</h2>
|
||||
<ul>
|
||||
<li>React</li>
|
||||
<li>TypeScript</li>
|
||||
</ul>
|
||||
<h2 className="mt-4 font-bold text-xl text-[var(--color-secondaryTextDark)]">
|
||||
Backend:
|
||||
</h2>
|
||||
<ul>
|
||||
<li>NestJS</li>
|
||||
<li>PostgreSQL</li>
|
||||
<li>Prisma</li>
|
||||
</ul>
|
||||
<h2 className="mt-4 font-bold text-xl text-[var(--color-secondaryTextDark)]">
|
||||
Containerization:
|
||||
</h2>
|
||||
<ul>
|
||||
<li>Docker</li>
|
||||
</ul>
|
||||
<h2 className="mt-4 font-bold text-xl text-[var(--color-secondaryTextDark)]">
|
||||
Styling/UI:
|
||||
</h2>
|
||||
<ul>
|
||||
<li>Tailwind CSS</li>
|
||||
</ul>
|
||||
<p className="mt-4 text-[var(--color-secondaryTextDark)]">
|
||||
More about me{" "}
|
||||
<a
|
||||
className="text-[var(--color-textLink)]"
|
||||
target="_blank"
|
||||
href="https://fredzernia.com"
|
||||
>
|
||||
here
|
||||
</a>{" "}
|
||||
| Code for this app{" "}
|
||||
<a
|
||||
className="text-[var(--color-textLink)]"
|
||||
target="_blank"
|
||||
href="https://forgejo.fredzernia.com/fred/recipe_app"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default About
|
||||
export default About;
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ import TimeDisplay from "../components/TimeDisplay.tsx";
|
|||
|
||||
function RecipePage() {
|
||||
const [recipe, setRecipe] = useState<Recipe>({
|
||||
details: {},
|
||||
ingredients: [],
|
||||
steps: [],
|
||||
recipeIngredients: [],
|
||||
recipeSteps: [],
|
||||
});
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
|
@ -23,8 +22,8 @@ function RecipePage() {
|
|||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||
const { id } = useParams();
|
||||
const isWebSource =
|
||||
recipe && recipe.details && recipe.details.author
|
||||
? /http|com/.test(recipe.details.author) //etc
|
||||
recipe && recipe.author
|
||||
? /http|com/.test(recipe.author) //etc
|
||||
: false;
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
|
@ -36,7 +35,7 @@ function RecipePage() {
|
|||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
handleDelete(recipe.details.id);
|
||||
handleDelete(recipe.id);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
|
|
@ -44,12 +43,12 @@ function RecipePage() {
|
|||
const loadRecipe = async () => {
|
||||
try {
|
||||
const recipe = await getRecipeById(id);
|
||||
if (!recipe.details) {
|
||||
if (!recipe.name) {
|
||||
setError("Sorry, this recipe no longer exists");
|
||||
} else {
|
||||
setRecipe(recipe);
|
||||
setStars(recipe.details?.stars ?? 0);
|
||||
setInitialStars(recipe.details?.stars ?? 0);
|
||||
setStars(recipe.stars ?? 0);
|
||||
setInitialStars(recipe.stars ?? 0);
|
||||
if (process.env.NODE_ENV === "dev") {
|
||||
console.log(recipe);
|
||||
}
|
||||
|
|
@ -119,11 +118,11 @@ function RecipePage() {
|
|||
</button>
|
||||
</div>
|
||||
<h3 className="text-center max-w-lg px-4 text-2xl lg:text-3xl font-bold text-[var(--color-textDark)]">
|
||||
{recipe.details.name}
|
||||
{recipe.name}
|
||||
</h3>
|
||||
<div className="modify-buttons">
|
||||
<button
|
||||
onClick={() => navigate(`/edit-recipe/${recipe.details.id}`)}
|
||||
onClick={() => navigate(`/edit-recipe/${recipe.id}`)}
|
||||
className="ar-button bg-[var(--color-buttonBg)] text-[var(--color-textLight)] py-1 px-1 rounded hover:bg-[var(--color-buttonBgHover)] mr-2 self-start"
|
||||
>
|
||||
🔧
|
||||
|
|
@ -138,12 +137,12 @@ function RecipePage() {
|
|||
</div>
|
||||
<div className="mt-1">
|
||||
<p className="text-[var(--color-textDark)] italic text-lg">
|
||||
{recipe.details.cuisine}
|
||||
{recipe.cuisine}
|
||||
</p>
|
||||
<p>
|
||||
prep: <TimeDisplay minutes={recipe.details.prep_minutes ?? 0} />{" "}
|
||||
prep: <TimeDisplay minutes={recipe.prep_minutes ?? 0} />{" "}
|
||||
| cook:{" "}
|
||||
<TimeDisplay minutes={recipe.details.cook_minutes ?? 0} />
|
||||
<TimeDisplay minutes={recipe.cook_minutes ?? 0} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -154,7 +153,7 @@ function RecipePage() {
|
|||
Ingredients:
|
||||
</h4>
|
||||
<ul className="space-y-2">
|
||||
{recipe.ingredients.map((ingredient: string, index) => (
|
||||
{recipe.recipeIngredients.map((ingredient: string, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="text-[var(--color-secondaryTextDark)] flex items-start"
|
||||
|
|
@ -171,17 +170,17 @@ function RecipePage() {
|
|||
Instructions:
|
||||
</h4>
|
||||
<ol className="space-y-3">
|
||||
{recipe.steps &&
|
||||
Object.keys(recipe.steps || {}).map((stepNumber) => (
|
||||
{recipe.recipeSteps &&
|
||||
Object.keys(recipe.recipeSteps || {}).map((stepNumber) => (
|
||||
<li
|
||||
key={stepNumber}
|
||||
className="text-[var(--color-secondaryTextDark)] flex items-start"
|
||||
>
|
||||
<span className="bg-[var(--color-buttonBg)] text-[var(--color-textLight)] 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}
|
||||
{recipe.recipeSteps[parseInt(stepNumber)].step_number}
|
||||
</span>
|
||||
<span className="leading-relaxed text-left">
|
||||
{recipe.steps[parseInt(stepNumber)].instruction}
|
||||
{recipe.recipeSteps[parseInt(stepNumber)].instruction}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -192,9 +191,9 @@ function RecipePage() {
|
|||
<div className="border-t-2 border-[var(--color-primaryBorder)] pt-4">
|
||||
<div className="flex justify-between items-center text-sm text-[var(--color-textDark)]">
|
||||
{isWebSource ? (
|
||||
<span>Source: {recipe.details.author}</span>
|
||||
<span>Source: {recipe.author}</span>
|
||||
) : (
|
||||
<span>From the kitchen of {recipe.details.author}</span>
|
||||
<span>From the kitchen of {recipe.author}</span>
|
||||
)}
|
||||
<span>
|
||||
<StarRating
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ function RecipeSteps() {
|
|||
};
|
||||
loadRecipeSteps();
|
||||
}, []);
|
||||
console.log(recipeSteps)
|
||||
// console.log(recipeSteps)
|
||||
return (
|
||||
<div className='page-outer'>
|
||||
{loading ? (
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ const baseUrl =
|
|||
process.env.NODE_ENV !== "dev" ? "/api" : "http://localhost:3000/api";
|
||||
|
||||
export const getRecipes = async () => {
|
||||
console.log(`${baseUrl}/recipes`);
|
||||
const response = await fetch(`${baseUrl}/recipes`);
|
||||
// console.log(`${baseUrl}/recipe`);
|
||||
const response = await fetch(`${baseUrl}/recipe`);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
|
@ -23,30 +23,36 @@ export const getRecipeIngredients = async () => {
|
|||
export const getRecipeById = async (id) => {
|
||||
const response = await fetch(`${baseUrl}/recipe/${id}`);
|
||||
const data = await response.json();
|
||||
if (!data || !data.details) {
|
||||
if (!data || !data.name) {
|
||||
return { details: null };
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const addRecipe = async (recipeData) => {
|
||||
console.log(JSON.stringify(recipeData));
|
||||
recipeData.recipeIngredients = recipeData.recipeIngredients.map((str) => ({
|
||||
raw: str,
|
||||
}));
|
||||
// console.log(JSON.stringify(recipeData));
|
||||
// return
|
||||
const response = await fetch(`${baseUrl}/add-recipe`, {
|
||||
const response = await fetch(`${baseUrl}/recipe`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(recipeData),
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
// console.log(data);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateRecipe = async (id, recipeData) => {
|
||||
console.log("updateRecipe");
|
||||
console.log(recipeData);
|
||||
const response = await fetch(`${baseUrl}/update-recipe/${id}`, {
|
||||
method: "POST",
|
||||
// console.log("updateRecipe");
|
||||
// console.log(recipeData);
|
||||
recipeData.recipeIngredients = recipeData.recipeIngredients.map((str) => ({
|
||||
raw: str,
|
||||
}));
|
||||
const response = await fetch(`${baseUrl}/recipe/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(recipeData),
|
||||
});
|
||||
|
|
@ -55,22 +61,22 @@ export const updateRecipe = async (id, recipeData) => {
|
|||
};
|
||||
|
||||
export const setDBStars = async (id, stars) => {
|
||||
console.log(JSON.stringify({ id: id, stars: stars }));
|
||||
// console.log(JSON.stringify({ id: id, stars: stars }));
|
||||
// return
|
||||
const response = await fetch(`${baseUrl}/set-stars`, {
|
||||
method: "POST",
|
||||
const response = await fetch(`${baseUrl}/recipe/${id}/stars`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ id: id, stars: stars }),
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
// console.log(data);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteRecipe = async (id) => {
|
||||
console.log(id);
|
||||
// console.log(id);
|
||||
// return
|
||||
const response = await fetch(`${baseUrl}/delete-recipe`, {
|
||||
const response = await fetch(`${baseUrl}/recipe/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ id }),
|
||||
|
|
|
|||
|
|
@ -5,17 +5,15 @@ interface Step {
|
|||
}
|
||||
|
||||
interface Recipe {
|
||||
details: {
|
||||
id?: number;
|
||||
name?: string;
|
||||
author?: string;
|
||||
stars?: number;
|
||||
cuisine?: string;
|
||||
prep_minutes?: number;
|
||||
cook_minutes?: number;
|
||||
};
|
||||
ingredients: string[];
|
||||
steps: Step[];
|
||||
id?: number;
|
||||
name?: string;
|
||||
author?: string;
|
||||
stars?: number;
|
||||
cuisine?: string;
|
||||
prep_minutes?: number;
|
||||
cook_minutes?: number;
|
||||
recipeIngredients: string[];
|
||||
recipeSteps: Step[];
|
||||
}
|
||||
|
||||
// smaller Recipe type returned by backend at /recipes for all
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue