-
-
+
);
-}
+};
export default CookbookRecipeTile;
diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx
deleted file mode 100644
index f79021a..0000000
--- a/frontend/src/components/NavBar.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Link } from "react-router-dom";
-import "../css/Navbar.css";
-
-function NavBar() {
- return (
-
- );
-}
-
-export default NavBar;
diff --git a/frontend/src/components/RecipeBookTabs.tsx b/frontend/src/components/RecipeBookTabs.tsx
new file mode 100644
index 0000000..472f6cd
--- /dev/null
+++ b/frontend/src/components/RecipeBookTabs.tsx
@@ -0,0 +1,66 @@
+import React, { useState } from 'react';
+import { Link, useLocation } from 'react-router-dom';
+
+const RecipeBookTabs = () => {
+ const location = useLocation();
+
+ const tabs = [
+ { id: '/', label: 'All Recipes', icon: '📚' },
+ { id: '/recipe/', label: 'Recipe', icon: '🥗' },
+ { id: '/search', label: 'Search', icon: '🔎' },
+ { id: '/add-recipe', label: 'Add Recipe', icon: '➕' },
+ { id: '/about', label: 'About', icon: '🍽️' },
+ ];
+
+ return (
+
+ {/* Navigation Tabs */}
+
+ {/* Tab Background Line */}
+
+
+ {/* Tabs Container */}
+
+ {tabs.map((tab) => {
+ const isActive = location.pathname === tab.id || (location.pathname.startsWith(tab.id) && tab.id === "/recipe/");
+
+ return (
+
+
+ {tab.icon}
+ {tab.label}
+
+
+ {/* Tab Shadow Effect */}
+ {isActive && (
+ <>
+
+
+ >
+ )}
+
+ );
+ })}
+
+
+
+ );
+};
+
+export default RecipeBookTabs;
diff --git a/frontend/src/components/RecipeCard.tsx b/frontend/src/components/RecipeCard.tsx
deleted file mode 100644
index 9ecf166..0000000
--- a/frontend/src/components/RecipeCard.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { type Recipe, type Ingredient } from "../types/Recipe"
-
-function RecipeCard({ recipe }: { recipe: Recipe }) {
- return (
-
-
-
{recipe.details.name}
-
{recipe.details.cuisine}
-
Ingredients:
-
- {recipe.ingredients.map((ingredient: Ingredient, index) => (
- - {ingredient.quantity} {ingredient.unit} {ingredient.name}
- ))}
-
-
Steps:
-
- {recipe.steps && Object.keys(recipe.steps || {}).map((stepNumber) => (
- - {recipe.steps?.[parseInt(stepNumber)]}
- ))}
-
-
-
- );
-}
-
-export default RecipeCard;
diff --git a/frontend/src/components/RecipesContainer.tsx b/frontend/src/components/RecipesContainer.tsx
new file mode 100644
index 0000000..30b4e20
--- /dev/null
+++ b/frontend/src/components/RecipesContainer.tsx
@@ -0,0 +1,23 @@
+import CookbookRecipeTile from "../components/CookbookRecipeTile.tsx"
+import { useState, useEffect } from "react";
+import { getRecipes, deleteRecipe } from "../services/frontendApi.js";
+import { type Recipe } from "../types/Recipe"
+
+
+
+function RecipesContainer({ recipes }) => {
+
+ return (
+
+
Recipe Index
+
+ {recipes.map((recipe) => (
+
+ ))}
+
+
+
+ )
+}
+
+return default RecipesContainer
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 08a3ac9..2f9f104 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,10 +1,10 @@
:root {
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
+ /* font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; */
+ /* line-height: 1.5; */
+ /* font-weight: 400; */
+ /**/
+ /* color-scheme: light dark; */
+ /* color: rgba(255, 255, 255, 0.87); */
background-color: #242424;
font-synthesis: none;
@@ -13,15 +13,6 @@
-moz-osx-font-smoothing: grayscale;
}
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
body {
margin: 0;
display: flex;
@@ -29,40 +20,3 @@ body {
min-width: 320px;
min-height: 100vh;
}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
diff --git a/frontend/src/pages/About.tsx b/frontend/src/pages/About.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/pages/AddRecipe.tsx b/frontend/src/pages/AddRecipe.tsx
index 7219803..b878df6 100644
--- a/frontend/src/pages/AddRecipe.tsx
+++ b/frontend/src/pages/AddRecipe.tsx
@@ -43,33 +43,51 @@ function AddRecipe() {
}, [newRecipeId, navigate]);
return (
-
+
-
diff --git a/frontend/src/pages/Cookbook.tsx b/frontend/src/pages/Cookbook.tsx
index af48a40..e142215 100644
--- a/frontend/src/pages/Cookbook.tsx
+++ b/frontend/src/pages/Cookbook.tsx
@@ -38,16 +38,17 @@ function Cookbook() {
return (
-
{error &&
{error}
}
-
{loading ? (
Loading...
) : (
-
- {recipes.map((recipe: Recipe) => (
-
- ))}
+
+
Recipe Index
+
+ {recipes.map((recipe) => (
+
+ ))}
+
)}
diff --git a/frontend/src/pages/RecipePage.tsx b/frontend/src/pages/RecipePage.tsx
index 88c5a4c..7be4a34 100644
--- a/frontend/src/pages/RecipePage.tsx
+++ b/frontend/src/pages/RecipePage.tsx
@@ -1,8 +1,7 @@
-import RecipeCard from "../components/RecipeCard.tsx"
import { useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import { getRecipeById } from "../services/frontendApi.js";
-import { type Recipe } from "../types/Recipe"
+import { type Recipe, type Ingredient } from "../types/Recipe"
function RecipePage() {
const [recipe, setRecipe] = useState
({
@@ -29,7 +28,7 @@ function RecipePage() {
};
loadRecipe();
}, [id]);
-
+ console.log(recipe)
return (
@@ -38,8 +37,53 @@ function RecipePage() {
{loading ? (
Loading...
) : (
-
-
+
+
+
+
{recipe.details.name}
+
{recipe.details.cuisine}
+
+
+
+
+
+
+ Ingredients:
+
+
+ {recipe.ingredients.map((ingredient: Ingredient, index) => (
+ -
+
+ {ingredient.quantity} {ingredient.unit} {ingredient.name}
+
+ ))}
+
+
+
+
+
+
+ Instructions:
+
+
+ {recipe.steps && Object.keys(recipe.steps || {}).map((stepNumber) => (
+ -
+
+ {stepNumber}
+
+ {recipe.steps[parseInt(stepNumber)]}
+
+ ))}
+
+
+
+
+
+
+ From the Kitchen of
+ ★ ★ ★
+
+
)}
diff --git a/frontend/src/pages/Search.tsx b/frontend/src/pages/Search.tsx
new file mode 100644
index 0000000..fb2bfe0
--- /dev/null
+++ b/frontend/src/pages/Search.tsx
@@ -0,0 +1,72 @@
+import { useState, useEffect } from "react";
+import CookbookRecipeTile from "../components/CookbookRecipeTile.tsx"
+import { getRecipes, deleteRecipe } from "../services/frontendApi.js";
+import { type Recipe } from "../types/Recipe"
+
+
+
+
+function Search() {
+ const [searchQuery, setSearchQuery] = useState("");
+ const [recipes, setRecipes] = useState([]);
+ const [error, setError] = useState
(null);
+ const [loading, setLoading] = useState(true);
+ const [shouldFetchRecipes, setShouldFetchRecipes] = useState(true);
+
+ useEffect(() => {
+ const loadRecipes = async () => {
+ try {
+ const recipes = await getRecipes();
+ setRecipes(recipes);
+ } catch (error) {
+ console.log(error);
+ setError("Failed to load recipes...");
+ } finally {
+ setLoading(false);
+ }
+ };
+ if (shouldFetchRecipes) {
+ loadRecipes().then(() => setShouldFetchRecipes(false));
+ }
+ }, [shouldFetchRecipes]);
+
+ const handleDelete = async (id: number | void) => {
+ try {
+ await deleteRecipe(id);
+ setShouldFetchRecipes(true);
+ } catch (error) {
+ console.error("Error deleting recipe:", error);
+ }
+ };
+
+ return (
+
+
+ {error &&
{error}
}
+ {loading ? (
+
Loading...
+ ) : (
+
+
+ {recipes.map((recipe) => (
+ recipe.name.toLowerCase().startsWith(searchQuery) &&
+ ))}
+
+
+ )}
+
+ );
+}
+
+export default Search
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
new file mode 100644
index 0000000..d37737f
--- /dev/null
+++ b/frontend/tailwind.config.js
@@ -0,0 +1,12 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
+