├── postcss.config.js
├── tailwind.config.js
├── src
├── components
│ ├── NotFound.jsx
│ ├── FryingPan.jsx
│ ├── Favourites.jsx
│ ├── Home.jsx
│ ├── Footer.jsx
│ ├── Recipe.jsx
│ ├── Navbar.jsx
│ ├── RecipeItem.jsx
│ └── FryingPanStyles.css
├── index.js
├── index.css
├── hooks
│ └── useFetch.js
└── App.jsx
├── public
└── index.html
├── .gitignore
├── package.json
└── note.txt
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./src/**/*.{js,jsx,ts,tsx}",
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
--------------------------------------------------------------------------------
/src/components/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFound = () => {
4 | return (
5 |
6 |
404 Not Found
7 |
8 | );
9 | };
10 |
11 | export default NotFound;
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Food Verse Project
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import App from './App';
5 | import './index.css';
6 |
7 | const root = ReactDOM.createRoot(document.getElementById('root'));
8 | root.render(
9 |
10 |
11 |
12 |
13 |
14 | );
15 |
--------------------------------------------------------------------------------
/src/components/FryingPan.jsx:
--------------------------------------------------------------------------------
1 | import "./fryingPanStyles.css";
2 |
3 | const FryingPan = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
21 | );
22 | };
23 |
24 | export default FryingPan;
--------------------------------------------------------------------------------
/src/components/Favourites.jsx:
--------------------------------------------------------------------------------
1 | import Recipe from "./Recipe";
2 |
3 | const Favourites = ({ savedItems }) => {
4 | return (
5 |
6 | {savedItems.length === 0 && (
7 |
8 | Favourite list is empty!
9 |
10 | )}
11 |
12 |
13 | {savedItems.map((recipe) => (
14 |
15 | ))}
16 |
17 |
18 | );
19 | };
20 |
21 | export default Favourites;
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url('https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;600;700;800;900&display=swap');
6 |
7 | body{
8 | font-family: 'Rubik', sans-serif;
9 | }
10 |
11 | ::selection {
12 | background-color: #0ea5e9;
13 | color: #f0f9ff;
14 | }
15 |
16 | ::-webkit-scrollbar {
17 | width: 10px;
18 | }
19 |
20 | ::-webkit-scrollbar-thumb{
21 | height: 10%;
22 | background-color: #f43f5e;
23 | border-radius: 50px;
24 | }
25 |
26 | ::-webkit-scrollbar-track{
27 | background-color: #fff1f2;
28 | }
29 | .site-footer{
30 | border-radius: 50px 50px 0 0px;
31 | padding: 50px;
32 | }
33 | .text-left p{
34 | font-size: 22px;
35 | }
--------------------------------------------------------------------------------
/src/components/Home.jsx:
--------------------------------------------------------------------------------
1 | import Recipe from "./Recipe";
2 | import FryingPan from "./FryingPan";
3 |
4 | const Home = ({ recipes, loading, error }) => {
5 | return (
6 |
7 | {!loading && !error && recipes.length === 0 ? (
8 |
9 |
10 | Nothing to show, please search something!
11 |
12 |
13 |
14 | ) : null}
15 |
16 | {loading &&
{error ? error : "Loading..."}
}
17 |
18 | {recipes?.length > 0 &&
19 | recipes.map((recipe) =>
)}
20 |
21 | );
22 | };
23 |
24 | export default Home;
--------------------------------------------------------------------------------
/src/hooks/useFetch.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export const useFetch = (id) => {
4 | const [data, setData] = useState({});
5 | const [loading, setLoading] = useState(false);
6 | const [error, setError] = useState("");
7 |
8 | useEffect(() => {
9 | const getRecipeItemData = async () => {
10 | try {
11 | setLoading(true);
12 | const res = await fetch(
13 | `https://forkify-api.herokuapp.com/api/v2/recipes/${id}`
14 | );
15 | if (!res.ok)
16 | throw new Error("Something went wrong, please try again later!");
17 | const data = await res.json();
18 | setData(data?.data?.recipe);
19 | setLoading(false);
20 | } catch (err) {
21 | setError(err.message);
22 | }
23 | };
24 |
25 | getRecipeItemData();
26 | }, []);
27 |
28 | return { data, loading, error };
29 | };
30 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Footer = () => {
4 | return (
5 |
6 |
7 |
8 |
9 | Foodverse
10 |
11 |
12 | Foodverse by Nature
13 |
14 | "9609 Sideroad 17"
15 |
16 | "Erin, Ontario"
17 |
18 | "N0B 1T0, Canada"
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export default Footer;
--------------------------------------------------------------------------------
/src/components/Recipe.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Recipe = ({ recipe }) => {
4 | return (
5 |
6 |
11 |
12 |
13 | {recipe.publisher}
14 |
15 |
16 | {recipe.title}{" "}
17 |
18 |
22 | View recipe
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export default Recipe;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "18",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.5",
7 | "@testing-library/react": "^13.4.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "react": "^18.2.0",
10 | "react-dom": "^18.2.0",
11 | "react-redux": "^8.0.5",
12 | "react-router-dom": "^6.5.0",
13 | "react-scripts": "5.0.1",
14 | "redux": "^4.2.0",
15 | "web-vitals": "^2.1.4"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test",
21 | "eject": "react-scripts eject"
22 | },
23 | "eslintConfig": {
24 | "extends": [
25 | "react-app",
26 | "react-app/jest"
27 | ]
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | },
41 | "devDependencies": {
42 | "autoprefixer": "^10.4.13",
43 | "postcss": "^8.4.20",
44 | "tailwindcss": "^3.2.4"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import { NavLink } from "react-router-dom";
2 |
3 | const Navbar = ({
4 | searchHandler,
5 | searchQuery,
6 | setSearchQuery,
7 | inputField,
8 | savedItems,
9 | }) => {
10 | const navActive = ({ isActive }) => {
11 | return {
12 | color: isActive ? "#f43f5e" : null,
13 | };
14 | };
15 |
16 | return (
17 |
18 |
19 | Foodverse
20 |
21 |
32 |
33 |
34 |
40 | Home
41 |
42 |
43 |
44 |
49 | Favourites{" "}
50 |
51 | ({savedItems.length})
52 |
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default Navbar;
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 | import { Route, Routes, useNavigate } from "react-router-dom";
3 | import Home from "./components/Home";
4 | import Navbar from "./components/Navbar";
5 | import Footer from "./components/Footer";
6 | import Favourites from "./components/Favourites";
7 | import NotFound from "./components/NotFound";
8 | import RecipeItem from "./components/RecipeItem";
9 |
10 | const App = () => {
11 | const [searchQuery, setSearchQuery] = useState("");
12 | const [recipes, setRecipes] = useState([]);
13 | const [loading, setLoading] = useState(false);
14 | const [error, setError] = useState("");
15 | const [savedItems, setSavedItems] = useState(() => {
16 | const localData = localStorage.getItem("recipes");
17 | return localData ? JSON.parse(localData) : [];
18 | });
19 |
20 | const inputField = useRef(null);
21 |
22 | const navigate = useNavigate();
23 |
24 | const searchHandler = (e) => {
25 | e.preventDefault();
26 |
27 | getData(searchQuery);
28 |
29 | setSearchQuery("");
30 | inputField.current.blur();
31 | setRecipes([]);
32 | setError("");
33 | navigate("/");
34 | };
35 |
36 | const getData = async (searchQuery) => {
37 | try {
38 | setLoading(true);
39 | const res = await fetch(
40 | `https://forkify-api.herokuapp.com/api/v2/recipes?search=${searchQuery}`
41 | );
42 | if (!res.ok) throw new Error("Something went wrong!");
43 | const data = await res.json();
44 | if (data.results === 0) throw new Error("No recipe found!");
45 | setRecipes(data?.data?.recipes);
46 | setLoading(false);
47 | } catch (err) {
48 | setError(err.message);
49 | }
50 | };
51 |
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 |
76 | return (
77 | <>
78 |
79 |
86 |
87 | }
90 | />
91 | }
94 | />
95 |
102 | }
103 | />
104 | } />
105 |
106 |
107 |
108 | >
109 | );
110 | };
111 |
112 | export default App;
--------------------------------------------------------------------------------
/src/components/RecipeItem.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { Link, useParams } from "react-router-dom";
3 | import { useFetch } from "../hooks/useFetch";
4 |
5 | const RecipeItem = ({ favouriteHandler, savedItems }) => {
6 | const [itemSavedStatus, setItemSavedStatus] = useState(null);
7 | const { id } = useParams();
8 |
9 | const { data: recipe, loading, error } = useFetch(id);
10 |
11 | const durationCalc = (duration) => {
12 | if (!duration) return;
13 |
14 | if (!String(duration).includes(".")) {
15 | return duration + "h";
16 | }
17 |
18 | if (String(duration).includes(".")) {
19 | const splittedDuration = String(duration).split(".");
20 | const hour = splittedDuration[0] + "h";
21 | const splitterMinutes = "." + splittedDuration[1];
22 | const minutes = +splitterMinutes * 60 + "min";
23 |
24 | return hour + minutes;
25 | }
26 | };
27 |
28 | useEffect(() => {
29 | if (!recipe) return;
30 |
31 | setItemSavedStatus(savedItems.some((item) => item.id === recipe.id));
32 | }, [recipe]);
33 |
34 | return (
35 |
36 |
37 |
38 |
43 |
44 |
45 |
46 | Ingredients:
47 |
48 |
49 | {recipe?.ingredients?.map((ing, i) => (
50 |
51 | ✓ {ing.quantity}
52 | {ing.unit} {ing.description}
53 |
54 | ))}
55 |
56 |
57 |
58 |
59 |
60 | {recipe?.publisher}
61 |
62 |
{recipe?.title}
63 |
64 |
Servings: {recipe?.servings} people
65 |
66 | Cooking time:{" "}
67 | {recipe?.cooking_time < 60
68 | ? String(recipe?.cooking_time) + "min"
69 | : durationCalc(recipe?.cooking_time / 60)}
70 |
71 |
72 |
73 |
favouriteHandler(recipe?.id)}
75 | 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 ${
76 | itemSavedStatus
77 | ? "from-orange-400 to-orange-600 text-orange-50 shadow-orange-200 hover:shadow-orange-300"
78 | : "from-sky-400 to-sky-600 text-sky-50 shadow-sky-200 hover:shadow-sky-300"
79 | }`}
80 | >
81 | {itemSavedStatus
82 | ? "- Remove from favourites"
83 | : "+ Save as favourite"}
84 |
85 |
91 | Get directions
92 |
93 |
97 | Back to home
98 |
99 |
100 |
101 |
102 | );
103 | };
104 |
105 | export default RecipeItem;
--------------------------------------------------------------------------------
/src/components/FryingPanStyles.css:
--------------------------------------------------------------------------------
1 | #cooking {
2 | position: relative;
3 | margin: 0 auto;
4 | top: 0;
5 | width: 50vh;
6 | height: 50vh;
7 | overflow: hidden;
8 | }
9 |
10 | #cooking #area {
11 | position: absolute;
12 | bottom: 0;
13 | right: 0;
14 | width: 50%;
15 | height: 50%;
16 | background-color: transparent;
17 | transform-origin: 15% 60%;
18 | animation: flip 2.1s ease-in-out infinite;
19 | }
20 |
21 | #cooking #area #sides {
22 | position: absolute;
23 | width: 100%;
24 | height: 100%;
25 | transform-origin: 15% 60%;
26 | animation: switchSide 2.1s ease-in-out infinite;
27 | }
28 |
29 | #cooking #area #sides #handle {
30 | position: absolute;
31 | bottom: 18%;
32 | right: 80%;
33 | width: 35%;
34 | height: 20%;
35 | background-color: transparent;
36 | border-top: 1vh solid #374151;
37 | border-left: 1vh solid transparent;
38 | border-radius: 100%;
39 | transform: rotate(20deg) rotateX(0deg) scale(1.3, 0.9);
40 | }
41 |
42 | #cooking #area #sides #pan {
43 | position: absolute;
44 | bottom: 20%;
45 | right: 30%;
46 | width: 50%;
47 | height: 8%;
48 | background-color: #374151;
49 | border-radius: 0 0 1.4em 1.4em;
50 | transform-origin: -15% 0;
51 | }
52 |
53 | #cooking #area #pancake {
54 | position: absolute;
55 | top: 24%;
56 | width: 100%;
57 | height: 100%;
58 | transform: rotateX(85deg);
59 | animation: jump 2.1s ease-in-out infinite;
60 | }
61 |
62 | #cooking #area #pancake #pastry {
63 | position: absolute;
64 | bottom: 26%;
65 | right: 37%;
66 | width: 40%;
67 | height: 45%;
68 | background-color: #fca5a5;
69 | box-shadow: 0 0 3px 0 #fca5a5;
70 | border-radius: 100%;
71 | transform-origin: -20% 0;
72 | animation: fly 2.1s ease-in-out infinite;
73 | }
74 |
75 | #cooking .bubble {
76 | position: absolute;
77 | border-radius: 100%;
78 | box-shadow: 0 0 0.25vh #e5e7eb;
79 | opacity: 0;
80 | }
81 |
82 | #cooking .bubble:nth-child(1) {
83 | margin-top: 2.5vh;
84 | left: 58%;
85 | width: 2.5vh;
86 | height: 2.5vh;
87 | background-color: #d1d5db;
88 | animation: bubble 2s cubic-bezier(0.53, 0.16, 0.39, 0.96) infinite;
89 | }
90 |
91 | #cooking .bubble:nth-child(2) {
92 | margin-top: 3vh;
93 | left: 52%;
94 | width: 2vh;
95 | height: 2vh;
96 | background-color: #9ca3af;
97 | animation: bubble 2s ease-in-out 0.35s infinite;
98 | }
99 |
100 | #cooking .bubble:nth-child(3) {
101 | margin-top: 1.8vh;
102 | left: 50%;
103 | width: 1.5vh;
104 | height: 1.5vh;
105 | background-color: #6b7280;
106 | animation: bubble 1.5s cubic-bezier(0.53, 0.16, 0.39, 0.96) 0.55s infinite;
107 | }
108 |
109 | #cooking .bubble:nth-child(4) {
110 | margin-top: 2.7vh;
111 | left: 56%;
112 | width: 1.2vh;
113 | height: 1.2vh;
114 | background-color: #4b5563;
115 | animation: bubble 1.8s cubic-bezier(0.53, 0.16, 0.39, 0.96) 0.55s infinite;
116 | }
117 |
118 | #cooking .bubble:nth-child(5) {
119 | margin-top: 2.7vh;
120 | left: 63%;
121 | width: 1.1vh;
122 | height: 1.1vh;
123 | background-color: #374151;
124 | animation: bubble 1.6s ease-in-out 1s infinite;
125 | }
126 |
127 | @keyframes pulse {
128 | 0% {
129 | transform: scale(1, 1);
130 | opacity: 0.25;
131 | }
132 | 50% {
133 | transform: scale(1.2, 1);
134 | opacity: 1;
135 | }
136 | 100% {
137 | transform: scale(1, 1);
138 | opacity: 0.25;
139 | }
140 | }
141 |
142 | @keyframes flip {
143 | 0% {
144 | transform: rotate(0deg);
145 | }
146 | 5% {
147 | transform: rotate(-27deg);
148 | }
149 | 30%,
150 | 50% {
151 | transform: rotate(0deg);
152 | }
153 | 55% {
154 | transform: rotate(27deg);
155 | }
156 | 83.3% {
157 | transform: rotate(0deg);
158 | }
159 | 100% {
160 | transform: rotate(0deg);
161 | }
162 | }
163 |
164 | @keyframes switchSide {
165 | 0% {
166 | transform: rotateY(0deg);
167 | }
168 | 50% {
169 | transform: rotateY(180deg);
170 | }
171 | 100% {
172 | transform: rotateY(0deg);
173 | }
174 | }
175 |
176 | @keyframes jump {
177 | 0% {
178 | top: 24;
179 | transform: rotateX(85deg);
180 | }
181 | 25% {
182 | top: 10%;
183 | transform: rotateX(0deg);
184 | }
185 | 50% {
186 | top: 30%;
187 | transform: rotateX(85deg);
188 | }
189 | 75% {
190 | transform: rotateX(0deg);
191 | }
192 | 100% {
193 | transform: rotateX(85deg);
194 | }
195 | }
196 |
197 | @keyframes fly {
198 | 0% {
199 | bottom: 26%;
200 | transform: rotate(0deg);
201 | }
202 | 10% {
203 | bottom: 40%;
204 | }
205 | 50% {
206 | bottom: 26%;
207 | transform: rotate(-190deg);
208 | }
209 | 80% {
210 | bottom: 40%;
211 | }
212 | 100% {
213 | bottom: 26%;
214 | transform: rotate(0deg);
215 | }
216 | }
217 |
218 | @keyframes bubble {
219 | 0% {
220 | transform: scale(0.15, 0.15);
221 | top: 80%;
222 | opacity: 0;
223 | }
224 | 50% {
225 | transform: scale(1.1, 1.1);
226 | opacity: 1;
227 | }
228 | 100% {
229 | transform: scale(0.33, 0.33);
230 | top: 60%;
231 | opacity: 0;
232 | }
233 | }
234 |
235 | @media screen and (max-width: 768px) {
236 | #cooking {
237 | display: none;
238 | }
239 | }
--------------------------------------------------------------------------------
/note.txt:
--------------------------------------------------------------------------------
1 | food verse website
2 |
3 | 1. first install tailwind css in your project
4 | 2. select Rubik google font family and set up your project
5 | 3. copy the import link of this fonts and past css file and also body font-family
6 | 4. project start korar age css file kichu starter pack starter css likte hobe css file
7 | body(font-family),
8 | ::selection(),
9 | ::-webkit-scrollbar(width),
10 | ::-webkit-scrollbar-thumb(height, background-color, border-radius),
11 | ::-webkit-scrollbar-track(background-color)
12 |
13 | 5. website er jonno kichu componnet create korte hobe
14 | -Home.jsx
15 | -Navbar.jsx
16 | -Favourites.jsx
17 | -Receipe.jsx
18 | -ReceipeItem.jsx
19 | -fryingPen.jsx
20 | -fryingPenStyle.css
21 | -NotFound.jsx
22 | -Footer.jsx
23 | sob gula functional component hobe
24 |
25 | 6. setup router and install it (npm i react-router-dom)
26 | index.js jete hobe and then modde React.StrictMode ta dukate hobe
27 |
28 | 7. app.js e jete hobe and sekhane apnake div er bitor e route set korte hobe
29 | 8. app.js Footer component ke set korte hobe jate footer ta alltime niche e thake content thakok r nai thakok tar jonno div er bahire Footer ta ke set rakte hobe but jokon rakben tokon abar error show korbe tai take fragment diye rakte and then er bitor footer rakte hobe and div er bitore ekta class dite hobe (min-h-screen) tahole footer ekdom niche chole jabe
30 |
31 | //first step done starter pack o bolte paren ato kon ja korano holo
32 |
33 | //lets deign the page with tailwind css
34 |
35 | 9. first Navbar component ke design korte hobe. amader design kora navbar e 3 ta jinish thakbe (logo, searchbar and menu )
36 |
37 | 10. navbar name ekta class div nite and then oitar bitor logo search menu thakbe
38 | - first h2 diye logo tar name likte hobe r jehot logo ta 2 colors tai h2 er bitor baki color er onghso toko ke span tag er bitor diye dite hobe.
39 | - search-bar name class form nite hobe jate kew search korle submit korte pare . and oitar bitor ekta input nite hobe jar type hobe search and placholder jekono ekta dile hobe apnr design moto
40 | - menu class name ul tag nite hobe er bitor thakbe Li and li er bitor a ke dite hobe jehoto amra router use korchi tai navlink or link use korte pare a er bodli ei doi tai a ke represent kore abar navlink use korle er bitor (en to="/") use korte hobe
41 |
42 | Home
43 |
44 |
45 | Favourites {}
46 |
47 |
48 |
49 | 10. navbar class div jeita okhane flex boshate hobe justify-between , items-center dite hobe (flex justify-between items-center container mx-auto py-10 flex-col lg:flex-row gap-5)
50 |
51 | 11. h2 er logo ke ekon style korte hobe h2 er bitor ja ja dite hobe
52 | (text-2xl font-bold lowercase italic) and span er bitor dite hobe text-rose-500
53 |
54 |
55 | 12. abar app.js e jete hobe and app class e abr kichu css dite hobe
56 | (bg-rose-50 text-gray-600 text-lg)
57 |
58 | 13. tarpor search er inpute field ke design korte hobe
59 | (bg-white/75 p-3 px-8 lg:w-96 rounded-full outline-none shadow-lg shadow-rose-100 focus:shadow-rose-200 duration-300)
60 |
61 | 14. ekon ul menu ke design korte hobe
62 | (flex gap-5)
63 | - navlink ke deign korte hobe
64 | (text-gray-400 hover:text-gray-600 duration-300)
65 | navlink er bitor je span tag ache oita ke o design korte hobe
66 | (text-bold text-sky-400)
67 |
68 | - active class dite navlink er bitore - navActive
69 | - return er upore navActive name ekta arrow function nite hobe and parameter hisebe ekta object nibe object er name hocche isActive click korle isActive ta trigger hobe
70 | - ei function ta return korbe r ekta object
71 | color: isActive ? "#f43f5e": null,
72 | - ekhane jokon e click kora tokon isActive ti trigger hobe and color ta show korbe otherwise color ta null dekabe
73 |
74 |
75 | 15. ekon input er jonno state nite hobe cz input e kono kichu like jeno sei data ta pawa jai tai nite hobe
76 | const [searchQuery, setSearchQuery] = useState('');
77 |
78 | - tarpor input er bitor value dite hobe
79 | value = {searchQuery}
80 | - abr onChange() name ekta function o dite hobe jate kono kichu likar shate value ta change hoi setSearchQuery er maddome
81 | onChange(e) => setSearchQuery(e.target.value);
82 |
83 | - form er modde searchHandler name on submit add korte hobe jate inter dile form ta trigger hoi
84 |
85 | const searchHandler = (e) => {
86 | e.preventDefault();
87 |
88 | console.log(searchQuery)
89 | }
90 |
91 | - input e ekta refrence nite hobe jate submit korle lika ta chole jai
92 | const inputFeildRef = useRef(null);
93 | ref={inputFeildRef}
94 |
95 | - setSearchQuery("");
96 | inputFeildRef.current.blur();
97 |
98 | r ei doi ta searchHandler er bitor add kore dite hobe
99 |
100 | 16. searchQuery jei value ta ache ekon sei ta navabr er bitor ache so amader ke ei searchQuery value ta ke home er bitor nite hobe
101 | - navabr e searchQuery jei state ta ache sei ta ke tule niye app.jsx e patate jate kore okhane theke navbar o home doi tai use kora jai mane stae ta ke top e niye jete hobe . abr eki bhave search handler ta ke o niye jete hobe . ekon niya jawar age amader ke ekta way follow korte hobe
102 | - first jeigula niye jaben sei gula ke props akare navabr paramater hisebe diye rakte hobe
103 |
104 |
105 | 17. getData name e ekta async function nite hobe jar parameter hisebe searchQuery ta nibe jar bitore data gula ke fetch korte hobe
106 |
107 | const getData = async (searchQuery) => {
108 | const res = await fetch(`https://forkify-api.herokuapp.com/api/search?q=${searchQuery}`);
109 | const data = await res.json();
110 | console.log(data.recipes)
111 | }
112 |
113 | 18. fecth korar por jei data paichi tar jonno abar r ekta state nite hobe
114 | const [recipes, setRecipes] = useState([]);
115 |
116 | 19. jodi response property correct na thake tahole ekta error throw korte hobe
117 | if(!res.ok) throw new Error ('No receipe Found!');
118 |
119 | 20. ato kon fetch kore jei data peyechi aita ke try{} er bitor rakte hobe
120 | const res = await fetch(`https://forkify-api.herokuapp.com/api/search?q=${searchQuery}`);
121 | if(!res.ok) throw new Error ('No reciepe Found');
122 | const data = await res.json();
123 | setRecipes(data.recipes)
124 |
125 |
126 | 21. try{} er niche obossoi catch(err){} use korte hobe . so erjonno abr 2 ta state extra add korte hobe
127 | const [loading, setLoading] = useState(false);
128 | const [error, setError] = useState('');
129 |
130 | 22. ekon try er bitor setLoading(true) kore dite hobe . abar data set kore pore setLoading(flase) false kore dite hobe
131 |
132 | 23. ekon jodi error khai tai tahole catch block setError er message ta set kore dite hobe
133 |
134 | 24.
135 | const getData = async (searchQuery) => {
136 | try{
137 | setLoading(true);
138 | const res = await fetch(`https://forkify-api.herokuapp.com/api/search?q=${searchQuery}`);
139 | if(!res.ok) throw new Error ('No reciepe Found');
140 | const data = await res.json();
141 | setRecipes(data.recipes);
142 | setLoading(false);
143 | } catch (err) {
144 | setError(err.message)
145 | }
146 | }
147 |
148 | ei conding ta ke ekta hook baniye tarpor reke dite hobe
--------------------------------------------------------------------------------