├── postcss.config.js ├── tailwind.config.js ├── public └── index.html ├── .gitignore ├── src ├── index.js ├── components │ ├── Footer.jsx │ ├── FryingPan.jsx │ ├── Favourites.jsx │ ├── NotFound.jsx │ ├── Home.jsx │ ├── Recipe.jsx │ ├── Navbar.jsx │ ├── RecipeItem.jsx │ └── fryingPanStyle.css ├── index.css ├── hooks │ └── useFetch.js └── App.js ├── package.json └── README.md /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: ["./src/**/*.{js,jsx,ts,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Foodverse 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /.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 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /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/Footer.jsx: -------------------------------------------------------------------------------- 1 | const Footer = () => { 2 | return ( 3 | 9 | ); 10 | }; 11 | 12 | export default Footer; 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/FryingPan.jsx: -------------------------------------------------------------------------------- 1 | import "./fryingPanStyle.css"; 2 | const FryingPan = () => { 3 | return ( 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ); 21 | }; 22 | export default FryingPan; 23 | -------------------------------------------------------------------------------- /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; 22 | -------------------------------------------------------------------------------- /src/components/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import FryingPan from "./FryingPan"; 3 | 4 | const NotFound = () => { 5 | return ( 6 |
7 |

8 | Page not found! 9 |

10 | 14 | Go home 15 | 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default NotFound; 22 | -------------------------------------------------------------------------------- /src/components/Home.jsx: -------------------------------------------------------------------------------- 1 | import FryingPan from "./FryingPan"; 2 | import Recipe from "./Recipe"; 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; 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foodverse", 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-router-dom": "^6.5.0", 12 | "react-scripts": "5.0.1", 13 | "web-vitals": "^2.1.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | }, 39 | "devDependencies": { 40 | "autoprefixer": "^10.4.13", 41 | "postcss": "^8.4.20", 42 | "tailwindcss": "^3.2.4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # foodverse 2 | 3 | Foodverse GitHub repository - Source code for the food-focused website Foodverse.com. The site showcases recipes, cooking tips, and engaging food-related articles, all designed to provide a comprehensive and user-friendly experience. Built with HTML, CSS, and JavaScript for a responsive design on all devices. 4 | 5 | # Features 6 | - Foodverse is an innovative recipe web application catering to diverse culinary preferences 7 | - With an extensive collection of recipes for various cuisines and dietary needs. 8 | - Users can easily search, access ingredients, and save their favorite recipes. 9 | - Seamlessly plan meals, explore trending dishes, your ultimate recipe companion. 10 | 11 | # Tools and Technologies 12 | - React for building the user interface. 13 | - Tailwind Css for styling. 14 | 15 | # Installation 16 | 1. Clone the repository:`https://github.com/Shm-Rsuf/foodverse` 17 | 2. Navigate to the client directory: `cd foodverse` 18 | 3. Install dependencies: `npm install` 19 | 4. Start the client: `npm start` 20 | 5. Access the client in your browser at: `https://localhost:3000` 21 | 22 | # Links 23 | - [Live-Link](https://foodverse-a3.netlify.app/) 24 | - [Front-End-Link](https://github.com/Shm-Rsuf/foodverse) 25 | -------------------------------------------------------------------------------- /src/components/Recipe.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | const Recipe = ({ recipe }) => { 4 | return ( 5 |
6 |
7 | {recipe.title} 12 |
13 | 14 |
15 | 16 | {recipe.publisher} 17 | 18 |

19 | {recipe.title} 20 |

21 | 25 | View recipe 26 | 27 |
28 |
29 | ); 30 | }; 31 | 32 | export default Recipe; 33 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { NavLink } from "react-router-dom"; 2 | 3 | const Navbar = ({ 4 | searchQuery, 5 | setSearchQuery, 6 | searchHandler, 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 |
22 | setSearchQuery(e.target.value)} 29 | required 30 | /> 31 |
32 | 57 |
58 | ); 59 | }; 60 | 61 | export default Navbar; 62 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import { Route, Routes, useNavigate } from "react-router-dom"; 3 | 4 | import Home from "./components/Home"; 5 | import Navbar from "./components/Navbar"; 6 | import Footer from "./components/Footer"; 7 | import Favourites from "./components/Favourites"; 8 | import NotFound from "./components/NotFound"; 9 | import RecipeItem from "./components/RecipeItem"; 10 | 11 | function App() { 12 | const [searchQuery, setSearchQuery] = useState(""); 13 | const [recipes, setRecipes] = useState([]); 14 | const [loading, setLoading] = useState(false); 15 | const [error, setError] = useState(""); 16 | const [savedItems, setSavedItems] = useState(() => { 17 | const localData = localStorage.getItem("recipes"); 18 | return localData ? JSON.parse(localData) : []; 19 | }); 20 | 21 | const inputField = useRef(null); 22 | const navigate = useNavigate(); 23 | 24 | const searchHandler = (e) => { 25 | e.preventDefault(); 26 | getData(searchQuery); 27 | 28 | setSearchQuery(""); 29 | inputField.current.blur(); 30 | setRecipes([]); 31 | setError(""); 32 | navigate("/"); 33 | }; 34 | 35 | const getData = async (searchQuery) => { 36 | try { 37 | setLoading(true); 38 | const res = await fetch( 39 | `https://forkify-api.herokuapp.com/api/v2/recipes?search=${searchQuery}` 40 | ); 41 | if (!res.ok) throw new Error("something went wrong!"); 42 | 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 (error) { 48 | setError(error.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 |