Skip to content

Commit c7b464c

Browse files
committed
foodverse done
1 parent 9c978e5 commit c7b464c

File tree

9 files changed

+421
-31
lines changed

9 files changed

+421
-31
lines changed

src/App.js

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Route, Routes } from "react-router-dom";
2-
import { useRef, useState } from "react";
1+
import { useEffect, useRef, useState } from "react";
2+
import { Route, Routes, useNavigate } from "react-router-dom";
33

44
import Home from "./components/Home";
55
import Navbar from "./components/Navbar";
@@ -13,8 +13,13 @@ function App() {
1313
const [recipes, setRecipes] = useState([]);
1414
const [loading, setLoading] = useState(false);
1515
const [error, setError] = useState("");
16+
const [savedItems, setSavedItems] = useState(() => {
17+
const localData = localStorage.getItem("recipes");
18+
return localData ? JSON.parse(localData) : [];
19+
});
1620

1721
const inputField = useRef(null);
22+
const navigate = useNavigate();
1823

1924
const searchHandler = (e) => {
2025
e.preventDefault();
@@ -23,6 +28,8 @@ function App() {
2328
setSearchQuery("");
2429
inputField.current.blur();
2530
setRecipes([]);
31+
setError("");
32+
navigate("/");
2633
};
2734

2835
const getData = async (searchQuery) => {
@@ -42,6 +49,30 @@ function App() {
4249
}
4350
};
4451

52+
const checkLocalData = (data) => {
53+
const localData = JSON.parse(localStorage.getItem("recipes"));
54+
const existedData = localData?.some((item) => item.id === data.id);
55+
56+
if (!existedData) {
57+
setSavedItems([...savedItems, data]);
58+
} else {
59+
const filteredData = localData.filter((item) => item.id !== data.id);
60+
setSavedItems(filteredData);
61+
}
62+
};
63+
64+
const favouriteHandler = (id) => {
65+
fetch(`https://forkify-api.herokuapp.com/api/v2/recipes/${id}`)
66+
.then((res) => res.json())
67+
.then((data) => checkLocalData(data.data.recipe));
68+
69+
navigate("/favourites");
70+
};
71+
72+
useEffect(() => {
73+
localStorage.setItem("recipes", JSON.stringify(savedItems));
74+
}, [savedItems]);
75+
4576
return (
4677
<>
4778
<div className="app min-h-screen bg-rose-50 text-gray-600 text-lg">
@@ -50,14 +81,26 @@ function App() {
5081
setSearchQuery={setSearchQuery}
5182
searchHandler={searchHandler}
5283
inputField={inputField}
84+
savedItems={savedItems}
5385
/>
5486
<Routes>
5587
<Route
5688
path="/"
5789
element={<Home recipes={recipes} loading={loading} error={error} />}
5890
/>
59-
<Route path="/favourites" element={<Favourites />} />
60-
<Route path="/recipe-item/:id" element={<RecipeItem />} />
91+
<Route
92+
path="/favourites"
93+
element={<Favourites savedItems={savedItems} />}
94+
/>
95+
<Route
96+
path="/recipe-item/:id"
97+
element={
98+
<RecipeItem
99+
favouriteHandler={favouriteHandler}
100+
savedItems={savedItems}
101+
/>
102+
}
103+
/>
61104
<Route path="*" element={<NotFound />} />
62105
</Routes>
63106
</div>

src/components/Favourites.jsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1-
const Favourites = () => {
2-
return <div>Favourites</div>;
1+
import Recipe from "./Recipe";
2+
3+
const Favourites = ({ savedItems }) => {
4+
return (
5+
<div className="favourite">
6+
{savedItems.length === 0 && (
7+
<p className="text-rose-300 text-2xl lg:text-3xl font-semibold text-center pt-10">
8+
Favourite list is empty!
9+
</p>
10+
)}
11+
12+
<div className="favourite-items container mx-auto py-10 flex flex-wrap gap-10 justify-center">
13+
{savedItems.map((recipe) => (
14+
<Recipe key={recipe.id} recipe={recipe} />
15+
))}
16+
</div>
17+
</div>
18+
);
319
};
420

521
export default Favourites;

src/components/Footer.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
const Footer = () => {
2-
return <div>Footer</div>;
2+
return (
3+
<footer className="py-8 flex flex-col gap-3 items-center bg-rose-200 opacity-75">
4+
<h2 className="text-2xl font-bold lowercase italic">
5+
Food<span className="text-rose-500">verse</span>
6+
</h2>
7+
<p>&copy; {new Date().getFullYear()} foodverse. All rights reserved.</p>
8+
</footer>
9+
);
310
};
411

512
export default Footer;

src/components/FryingPan.jsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
1+
import "./fryingPanStyle.css";
12
const FryingPan = () => {
2-
return <div>FryingPan</div>;
3+
return (
4+
<div id="cooking">
5+
<div className="bubble"></div>
6+
<div className="bubble"></div>
7+
<div className="bubble"></div>
8+
<div className="bubble"></div>
9+
<div className="bubble"></div>
10+
<div id="area">
11+
<div id="sides">
12+
<div id="pan"></div>
13+
<div id="handle"></div>
14+
</div>
15+
<div id="pancake">
16+
<div id="pastry"></div>
17+
</div>
18+
</div>
19+
</div>
20+
);
321
};
4-
522
export default FryingPan;

src/components/Home.jsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
import FryingPan from "./FryingPan";
12
import Recipe from "./Recipe";
23

34
const Home = ({ recipes, loading, error }) => {
45
return (
56
<div className="home container mx-auto py-10 flex flex-wrap gap-10 justify-center">
67
{!loading && !error && recipes.length === 0 ? (
7-
<p className="text-rose-300 text-2xl lg:text-3xl font-semibold">
8-
Nothing to show, please search something!
9-
</p>
8+
<div>
9+
<p className="text-rose-300 text-2xl lg:text-3xl font-semibold">
10+
Nothing to show, please search something!
11+
</p>
12+
<FryingPan />
13+
</div>
1014
) : null}
1115

1216
{loading && <p>{error ? error : "loading..."}</p>}

src/components/Navbar.jsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { NavLink } from "react-router-dom";
22

3-
const Navbar = ({ searchQuery, setSearchQuery, searchHandler, inputField }) => {
3+
const Navbar = ({
4+
searchQuery,
5+
setSearchQuery,
6+
searchHandler,
7+
inputField,
8+
savedItems,
9+
}) => {
410
const navActive = ({ isActive }) => {
511
return {
612
color: isActive ? "#f43f5e" : null,
@@ -43,7 +49,7 @@ const Navbar = ({ searchQuery, setSearchQuery, searchHandler, inputField }) => {
4349
>
4450
Favourites{" "}
4551
<span className="favourites-count font-semibold text-sky-400">
46-
(0)
52+
({savedItems.length})
4753
</span>
4854
</NavLink>
4955
</li>

src/components/NotFound.jsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1+
import { Link } from "react-router-dom";
2+
import FryingPan from "./FryingPan";
3+
14
const NotFound = () => {
2-
return <div>NotFound</div>;
5+
return (
6+
<div className="not-found container mx-auto py-8 flex flex-col items-center gap-5">
7+
<p className="text-2xl lg:text-3xl text-center font-semibold text-rose-300 leading-normal">
8+
Page not found!
9+
</p>
10+
<Link
11+
to="/"
12+
className="bg-sky-400 text-sky-50 p-3 px-8 rounded-full uppercase shadow-lg shadow-sky-200 hover:bg-rose-600 hover:text-rose-50 hover:shadow-rose-300 duration-300"
13+
>
14+
Go home
15+
</Link>
16+
<FryingPan />
17+
</div>
18+
);
319
};
420

521
export default NotFound;

src/components/RecipeItem.jsx

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { useParams } from "react-router-dom";
1+
import { useEffect, useState } from "react";
2+
import { Link, useParams } from "react-router-dom";
23
import { useFetch } from "../hooks/useFetch";
34

4-
const RecipeItem = () => {
5+
const RecipeItem = ({ favouriteHandler, savedItems }) => {
6+
const [itemSavedStatus, setItemSavedStatus] = useState(null);
57
const { id } = useParams();
68
const { data: recipe, loading, error } = useFetch(id);
79

@@ -22,30 +24,42 @@ const RecipeItem = () => {
2224
}
2325
};
2426

27+
useEffect(() => {
28+
if (!recipe) return;
29+
30+
setItemSavedStatus(savedItems.some((item) => item.id === recipe.id));
31+
}, [recipe]);
32+
2533
return (
26-
<div className="recipe-item-secton container mx-auto py-10 grid grid-cols-1 lg:grid-cols-2 gap-10">
27-
<div className="left">
28-
<div className="img">
29-
<img src={recipe?.image_url} alt={recipe?.title} />
34+
<div className="recipe-item-section container mx-auto py-20 grid grid-cols-1 lg:grid-cols-2 gap-10">
35+
<div className="left row-start-2 lg:row-start-auto">
36+
<div className="img overflow-hidden rounded-xl border shadow-md group">
37+
<img
38+
src={recipe?.image_url}
39+
alt={recipe?.title}
40+
className="h-full w-full object-cover group-hover:scale-105 duration-300"
41+
/>
3042
</div>
31-
32-
<div className="ings">
33-
<span className="ing-title">Ingredients</span>
34-
<ul>
43+
<div className="ings mt-10">
44+
<span className="ing-title text-3xl font-medium mb-5 inline-block">
45+
Ingredients:
46+
</span>
47+
<ul className="flex flex-col gap-2">
3548
{recipe?.ingredients?.map((ing, i) => (
3649
<li key={i}>
37-
{ing.quantity}
50+
{ing.quantity}
3851
{ing.unit} {ing.description}
3952
</li>
4053
))}
4154
</ul>
4255
</div>
4356
</div>
44-
<div className="right">
45-
<span className="publisher">{recipe?.publisher}</span>
46-
<h3 className="title">{recipe?.title}</h3>
47-
48-
<div className="servings-cooking-time">
57+
<div className="right flex flex-col gap-5">
58+
<span className="publisher uppercase tracking-widest font-semibold text-sky-400">
59+
{recipe?.publisher}
60+
</span>
61+
<h2 className="title text-5xl">{recipe?.title}</h2>
62+
<div className="servings-cooking-time flex gap-5 uppercase tracking-widest font-semibold text-rose-500">
4963
<div className="servings">Servings: {recipe?.servings} people</div>
5064
<div className="cooking-time">
5165
Cooking time:{" "}
@@ -54,6 +68,34 @@ const RecipeItem = () => {
5468
: durationCalc(recipe?.cooking_time / 60)}
5569
</div>
5670
</div>
71+
<div className="btns flex gap-5">
72+
<button
73+
onClick={() => favouriteHandler(recipe?.id)}
74+
className={`bg-gradient-to-br p-3 px-8 rounded-lg text-sm uppercase font-medium tracking-wider mt-2 inline-block shadow-md hover:shadow-lg duration-300 ${
75+
itemSavedStatus
76+
? "from-orange-400 to-orange-600 text-orange-50 shadow-orange-200 hover:shadow-orange-300"
77+
: "from-sky-400 to-sky-600 text-sky-50 shadow-sky-200 hover:shadow-sky-300"
78+
}`}
79+
>
80+
{itemSavedStatus
81+
? "- Remove from favourites"
82+
: "+ Save as favourite"}
83+
</button>
84+
<a
85+
href={recipe?.source_url}
86+
target="_blank"
87+
rel="noreferrer"
88+
className="bg-gradient-to-br from-purple-400 to-purple-600 text-purple-50 p-3 px-8 rounded-lg text-sm uppercase font-medium tracking-wider mt-2 inline-block shadow-md shadow-purple-200 hover:shadow-lg hover:shadow-purple-300 duration-300"
89+
>
90+
Get directions
91+
</a>
92+
<Link
93+
to="/"
94+
className="bg-gradient-to-br from-rose-400 to-rose-600 text-rose-50 p-3 px-8 rounded-lg text-sm uppercase font-medium tracking-wider mt-2 inline-block shadow-md shadow-rose-200 hover:shadow-lg hover:shadow-rose-300 duration-300"
95+
>
96+
Back to home
97+
</Link>
98+
</div>
5799
</div>
58100
</div>
59101
);

0 commit comments

Comments
 (0)