├── images
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── src
├── Redux
│ ├── store.js
│ ├── RecipeTypes.js
│ ├── RecipeReducer.js
│ └── RecipeActions.js
├── setupTests.js
├── App.test.js
├── Components
│ ├── Loader.js
│ ├── Main.css
│ ├── Main.js
│ ├── Alert.js
│ ├── SearchListAlert.js
│ ├── Favourite.js
│ ├── Home.js
│ ├── NavBar.js
│ ├── SearchList.js
│ └── RecipeInstruction.js
├── App.js
├── index.css
├── reportWebVitals.js
└── index.js
├── .gitignore
├── package.json
└── README.md
/images:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jupiter-Github/Recipe-Finder/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jupiter-Github/Recipe-Finder/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jupiter-Github/Recipe-Finder/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/Redux/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from "redux";
2 | import { thunk } from "redux-thunk";
3 | import { RecipeReducer } from "./RecipeReducer";
4 |
5 | const store = createStore(RecipeReducer, applyMiddleware(thunk));
6 | export { store };
7 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/Components/Loader.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box, LinearProgress } from "@mui/material";
3 | const Loader = () => {
4 | return (
5 | <>
6 |
7 |
8 |
9 | >
10 | );
11 | };
12 |
13 | export default Loader;
14 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Main from "./Components/Main";
3 | import {Provider} from "react-redux"
4 | import { store } from "./Redux/store";
5 | function App() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/.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 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/Redux/RecipeTypes.js:
--------------------------------------------------------------------------------
1 | const SEND_API_REQUEST = "SEND_API_REQUEST";
2 | const FETCH_RECIPE_INSTRUCTION = "FETCH_RECIPE_INSTRUCTION";
3 | const SET_RECIPE_ITEM = "SET_RECIPE_ITEM";
4 | const SET_RECIPE_DATA = "SET_RECIPE_DATA";
5 | const ADD_TO_FAVOURITE = "ADD_TO_FAVOURITE";
6 | const REMOVE_FROM_FAVOURITE = "REMOVE_FROM_FAVOURITE";
7 | const SET_SEARCH_ITEM = "SET_SEARCH_ITEM";
8 | export {
9 | SEND_API_REQUEST,
10 | SET_RECIPE_DATA,
11 | ADD_TO_FAVOURITE,
12 | REMOVE_FROM_FAVOURITE,
13 | FETCH_RECIPE_INSTRUCTION,
14 | SET_RECIPE_ITEM,
15 | SET_SEARCH_ITEM,
16 | };
17 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import "./index.css";
4 | import App from "./App";
5 | import { BrowserRouter } from "react-router-dom";
6 |
7 | import reportWebVitals from "./reportWebVitals";
8 |
9 | const root = ReactDOM.createRoot(document.getElementById("root"));
10 | root.render(
11 |
12 |
13 |
14 | );
15 |
16 | // If you want to start measuring performance in your app, pass a function
17 | // to log results (for example: reportWebVitals(console.log))
18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19 | reportWebVitals();
20 |
--------------------------------------------------------------------------------
/src/Components/Main.css:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | .displayImage {
8 | height: 100%;
9 | width: 100%;
10 | }
11 | .displayCardMiddle {
12 | width: 390px;
13 | display: flex;
14 | /* border: 2px solid black; */
15 | justify-content: space-between;
16 | }
17 | .displayCard {
18 | padding-left: 5px;
19 | width: 500px;
20 | /* border: 2px solid black; */
21 | }
22 | @media (max-width: 426px) {
23 | .displayCardMiddle {
24 | flex-direction: column;
25 | margin: 5px 0;
26 | width: 100%;
27 | }
28 | .displayCard {
29 | width: 100%;
30 | }
31 | }
32 | @media (max-width: 770px) {
33 | .displayCardMiddle {
34 | margin: 10px 0;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Components/Main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Main.css";
3 | import NavBar from "./NavBar";
4 | import Home from "./Home";
5 | import RecipeInstruction from "./RecipeInstruction";
6 | import { Routes, Route } from "react-router-dom";
7 | import Favourite from "./Favourite";
8 | import SearchList from "./SearchList";
9 |
10 | const Main = () => {
11 | return (
12 | <>
13 |
14 |
15 |
16 | } />
17 | } />
18 | } />
19 | } />
20 |
21 | >
22 | );
23 | };
24 |
25 | export default Main;
26 |
--------------------------------------------------------------------------------
/src/Components/Alert.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Slide from "@mui/material/Slide";
3 | import Snackbar from "@mui/material/Snackbar";
4 | const Alert = ({ open, setOpen }) => {
5 | const handleClose = (event, reason) => {
6 | if (reason === "clickaway") {
7 | return;
8 | }
9 | setOpen(false);
10 | };
11 | function SlideTransition(props) {
12 | return ;
13 | }
14 |
15 | return (
16 | <>
17 |
18 |
30 |
31 | >
32 | );
33 | };
34 |
35 | export default Alert;
36 |
--------------------------------------------------------------------------------
/src/Components/SearchListAlert.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import { Button, Snackbar, Slide, IconButton } from "@mui/material";
4 |
5 | import { Close } from "@mui/icons-material";
6 | const SearchListAlert = () => {
7 | const [open, setOpen] = useState(true);
8 |
9 | const handleClose = (event, reason) => {
10 | if (reason === "clickaway") {
11 | return;
12 | }
13 |
14 | setOpen(false);
15 | };
16 | function SlideTransition(props) {
17 | return ;
18 | }
19 | const action = (
20 | <>
21 |
25 |
28 |
29 |
35 |
36 |
37 | >
38 | );
39 | return (
40 | <>
41 |
50 | >
51 | );
52 | };
53 | export default SearchListAlert;
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "project3",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.11.4",
7 | "@emotion/styled": "^11.11.0",
8 | "@mui/icons-material": "^5.15.12",
9 | "@mui/material": "^5.15.12",
10 | "@testing-library/jest-dom": "^5.17.0",
11 | "@testing-library/react": "^13.4.0",
12 | "@testing-library/user-event": "^13.5.0",
13 | "bootstrap": "^5.3.3",
14 | "notistack": "^3.0.1",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-redux": "^9.1.0",
18 | "react-router-dom": "^6.22.3",
19 | "react-scripts": "5.0.1",
20 | "reactstrap": "^9.2.2",
21 | "redux": "^5.0.1",
22 | "redux-thunk": "^3.1.0",
23 | "semantic-ui-css": "^2.5.0",
24 | "semantic-ui-react": "^2.1.5",
25 | "sweetalert2": "^11.10.6",
26 | "sweetalert2-react-content": "^5.0.7",
27 | "web-vitals": "^2.1.4"
28 | },
29 | "scripts": {
30 | "start": "react-scripts start",
31 | "build": "react-scripts build",
32 | "test": "react-scripts test",
33 | "eject": "react-scripts eject"
34 | },
35 | "eslintConfig": {
36 | "extends": [
37 | "react-app",
38 | "react-app/jest"
39 | ]
40 | },
41 | "browserslist": {
42 | "production": [
43 | ">0.2%",
44 | "not dead",
45 | "not op_mini all"
46 | ],
47 | "development": [
48 | "last 1 chrome version",
49 | "last 1 firefox version",
50 | "last 1 safari version"
51 | ]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # Recipe Finder Web Application
3 |
4 | Recipe Finder is a recipe web application, where a user can search for a variety of recipes and find the ingredients and quantities needed to make each recipe item. Also, favorite recipe items can be saved in the browser's local storage. It is easy to find different recipes for vegetarians and meat eaters, as well as those who want to lose weight.
5 |
6 | You're one-stop for recipes, It is the best app for any type of cooking and cooking level. Browse what's trending, plan your meals with our grocery list tool, and browse recipes by ingredient. There's nothing like a well-prepared meal to make you feel like a culinary genius!
7 |
8 | ## Features
9 |
10 | - Users can search different kinds of recipes and get the ingredients for these
11 | - Users can save specific recipes as the favorites
12 | - Favorite recipes are also stored in the local storage of the client's browser
13 | - It is a fully responsive web application, so you can easily view it on your mobile device as well.
14 |
15 |
16 | ## View the app (Hosted on Vercel):
17 | https://recipe-finder-rho-gilt.vercel.app/
18 |
19 | ## Tools
20 | - React
21 | - Redux
22 | - React-Router
23 | - Material -UI
24 |
25 | ## How to use this repo?
26 | Download or clone this repo and run the following command in the terminal:
27 |
28 | ```bash
29 | npm install
30 | or
31 | npm i
32 | ```
33 |
34 | ## Conclusion:
35 | A recipe web application based not only on a list of recipes with ingredients but a database created by food lovers, for food lovers. Search through thousands of the best recipes worldwide and find what you're looking for without leaving your daily routine Give it a go!
36 |
37 | ## Thanks for visiting this repo!
38 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Recipe Finder
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/Redux/RecipeReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_TO_FAVOURITE,
3 | SEND_API_REQUEST,
4 | REMOVE_FROM_FAVOURITE,
5 | SET_RECIPE_DATA,
6 | SET_RECIPE_ITEM,
7 | SET_SEARCH_ITEM,
8 | } from "./RecipeTypes";
9 |
10 | const initialState = {
11 | allRecipeData: [],
12 | favouriteRecipe: JSON.parse(localStorage.getItem("favouriteRecipe")) || [],
13 | loading: false,
14 | recipeInstruction: [],
15 | searchItem: "",
16 | };
17 |
18 | const RecipeReducer = (state = initialState, action) => {
19 | switch (action.type) {
20 | case SEND_API_REQUEST:
21 | return {
22 | ...state,
23 | loading: true,
24 | };
25 | case SET_RECIPE_DATA:
26 | return {
27 | ...state,
28 | loading: false,
29 | allRecipeData: action.payload,
30 | };
31 | case SET_RECIPE_ITEM:
32 | return {
33 | ...state,
34 | recipeInstruction: action.payload,
35 | };
36 | case REMOVE_FROM_FAVOURITE:
37 | const updatedFavouriteRemove = state.favouriteRecipe.filter(
38 | (value) => !(value.id === action.payload)
39 | );
40 | localStorage.setItem(
41 | "favouriteRecipe",
42 | JSON.stringify(updatedFavouriteRemove)
43 | );
44 | return {
45 | ...state,
46 | favouriteRecipe: updatedFavouriteRemove,
47 | };
48 | case ADD_TO_FAVOURITE:
49 | const updatedFavouriteAdd = [...state.favouriteRecipe, action.payload];
50 | localStorage.setItem(
51 | "favouriteRecipe",
52 | JSON.stringify(updatedFavouriteAdd)
53 | );
54 | return {
55 | ...state,
56 | favouriteRecipe: updatedFavouriteAdd,
57 | };
58 | case SET_SEARCH_ITEM:
59 | return {
60 | ...state,
61 | searchItem: action.payload,
62 | };
63 |
64 | default:
65 | return state;
66 | }
67 | };
68 |
69 | export { RecipeReducer };
70 |
--------------------------------------------------------------------------------
/src/Redux/RecipeActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | SEND_API_REQUEST,
3 | SET_RECIPE_DATA,
4 | SET_RECIPE_ITEM,
5 | ADD_TO_FAVOURITE,
6 | REMOVE_FROM_FAVOURITE,
7 | SET_SEARCH_ITEM,
8 | } from "./RecipeTypes";
9 |
10 | const setRecipeData = (data) => {
11 | return {
12 | type: SET_RECIPE_DATA,
13 | payload: data.data.recipes,
14 | };
15 | };
16 |
17 | const sendApiRequest = () => {
18 | return {
19 | type: SEND_API_REQUEST,
20 | };
21 | };
22 |
23 | const setRecipeItem = (data) => {
24 | return {
25 | type: SET_RECIPE_ITEM,
26 | payload: data.data.recipe,
27 | };
28 | };
29 |
30 | const addToFavourite = (recipe) => {
31 | return {
32 | type: ADD_TO_FAVOURITE,
33 | payload: recipe,
34 | };
35 | };
36 |
37 | const removeFromFavourite = (id) => {
38 | return {
39 | type: REMOVE_FROM_FAVOURITE,
40 | payload: id,
41 | };
42 | };
43 |
44 | const setSearchItem = (searchItem) => {
45 | return {
46 | type: SET_SEARCH_ITEM,
47 | payload: searchItem,
48 | };
49 | };
50 |
51 | const fetchRecipe = (item) => {
52 | return (dispatch) => {
53 | dispatch(sendApiRequest());
54 | fetch(`https://forkify-api.herokuapp.com/api/v2/recipes?search=${item}`, {
55 | method: "GET",
56 | headers: { "Content-Type": "application/json" },
57 | })
58 | .then((response) => response.json())
59 | .then((data) => dispatch(setRecipeData(data)));
60 | };
61 | };
62 |
63 | const fetchRecipeItem = (id) => {
64 | return (dispatch) => {
65 | fetch(`https://forkify-api.herokuapp.com/api/v2/recipes/${id}`, {
66 | method: "GET",
67 | headers: { "Content-Type": "application/json" },
68 | })
69 | .then((response) => response.json())
70 | .then((data) => dispatch(setRecipeItem(data)));
71 | };
72 | };
73 |
74 | export {
75 | setRecipeData,
76 | fetchRecipe,
77 | fetchRecipeItem,
78 | addToFavourite,
79 | removeFromFavourite,
80 | sendApiRequest,
81 | setSearchItem,
82 | };
83 |
--------------------------------------------------------------------------------
/src/Components/Favourite.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Box,
4 | Grid,
5 | Card,
6 | CardContent,
7 | Container,
8 | CardMedia,
9 | Typography,
10 | Button,
11 | CardActionArea,
12 | CardActions,
13 | } from "@mui/material";
14 |
15 | import { useSelector, useDispatch } from "react-redux";
16 | import { removeFromFavourite } from "../Redux/RecipeActions";
17 | import { Link } from "react-router-dom";
18 |
19 | const Favourite = () => {
20 | const dispatch = useDispatch();
21 | const favouriteRecipe = useSelector((state) => state.favouriteRecipe);
22 |
23 | const handleClick = (id) => {
24 | dispatch(removeFromFavourite(id));
25 | };
26 |
27 | return (
28 | <>
29 |
30 | {favouriteRecipe.length > 0 ? (
31 |
32 | {favouriteRecipe.map((value) => (
33 |
34 |
35 |
39 |
40 |
41 |
42 |
51 | {value.title}
52 |
53 |
54 | {value.publisher}
55 |
56 |
57 |
58 |
59 |
60 |
68 |
69 |
70 |
71 | ))}
72 |
73 | ) : (
74 |
75 |
76 | Your favourite list is empty!
77 |
78 |
79 | )}
80 |
81 | >
82 | );
83 | };
84 |
85 | export default Favourite;
86 |
--------------------------------------------------------------------------------
/src/Components/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | Box,
4 | Grid,
5 | Card,
6 | CardContent,
7 | Container,
8 | CardMedia,
9 | Typography,
10 | Button,
11 | CardActionArea,
12 | CardActions,
13 | } from "@mui/material";
14 |
15 | import { useSelector, useDispatch } from "react-redux";
16 | import { addToFavourite } from "../Redux/RecipeActions";
17 | import { Link } from "react-router-dom";
18 | import Loader from "./Loader";
19 | import Alert from "./Alert";
20 | import SearchListAlert from "./SearchListAlert";
21 |
22 | const Home = () => {
23 | const dispatch = useDispatch();
24 | const allRecipes = useSelector((state) => state.allRecipeData);
25 | const loading = useSelector((state) => state.loading);
26 | const favouriteRecipe = useSelector((state) => state.favouriteRecipe);
27 | const [showalert, setShowAlert] = useState(false);
28 |
29 | const handleAddClick = (recipe) => {
30 | const existingItem = favouriteRecipe.find(
31 | (value) => value.id === recipe.id
32 | );
33 | if (existingItem) setShowAlert(true);
34 | else dispatch(addToFavourite(recipe));
35 | };
36 |
37 | return (
38 | <>
39 | {loading ? (
40 |
41 | ) : (
42 |
48 | {allRecipes.length > 0 ? (
49 |
50 | {allRecipes.map((value) => (
51 |
52 |
53 |
57 |
58 |
62 |
63 |
72 | {value.title}
73 |
74 |
75 | {value.publisher}
76 |
77 |
78 |
79 |
80 |
81 |
89 |
90 |
91 |
92 |
93 | ))}
94 |
95 | ) : (
96 |
97 |
98 | Nothing to show, please search something!
99 |
100 |
101 |
102 | )}
103 |
104 | )}
105 | >
106 | );
107 | };
108 |
109 | export default Home;
110 |
--------------------------------------------------------------------------------
/src/Components/NavBar.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import {
4 | styled,
5 | alpha,
6 | AppBar,
7 | Box,
8 | Toolbar,
9 | IconButton,
10 | Typography,
11 | InputBase,
12 | Badge,
13 | } from "@mui/material";
14 | import { Restaurant, Home, Favorite, Search } from "@mui/icons-material";
15 | import { Link } from "react-router-dom";
16 | import { useSelector, useDispatch } from "react-redux";
17 | import { fetchRecipe } from "../Redux/RecipeActions";
18 |
19 | const SearchDiv = styled("form")(({ theme }) => ({
20 | position: "relative",
21 | borderRadius: theme.shape.borderRadius,
22 | backgroundColor: alpha(theme.palette.common.white, 0.15),
23 | "&:hover": {
24 | backgroundColor: alpha(theme.palette.common.white, 0.25),
25 | },
26 | marginLeft: 0,
27 | width: "100%",
28 | [theme.breakpoints.up("sm")]: {
29 | marginLeft: theme.spacing(1),
30 | width: "auto",
31 | },
32 | }));
33 |
34 | const SearchIconWrapper = styled("div")(({ theme }) => ({
35 | padding: theme.spacing(0, 2),
36 | height: "100%",
37 | position: "absolute",
38 | pointerEvents: "none",
39 | display: "flex",
40 | alignItems: "center",
41 | justifyContent: "center",
42 | }));
43 |
44 | const StyledInputBase = styled(InputBase)(({ theme }) => ({
45 | color: "inherit",
46 | width: "100%",
47 | "& .MuiInputBase-input": {
48 | padding: theme.spacing(1, 1, 1, 0),
49 | // vertical padding + font size from searchIcon
50 | paddingLeft: `calc(1em + ${theme.spacing(4)})`,
51 | transition: theme.transitions.create("width"),
52 | [theme.breakpoints.up("sm")]: {
53 | width: "12ch",
54 | "&:focus": {
55 | width: "20ch",
56 | },
57 | },
58 | },
59 | }));
60 |
61 | const NavBar = () => {
62 | let searchItem = useSelector((state) => state.searchItem);
63 | const [recipeName, setRecipe] = useState("");
64 | const favouriteRecipe = useSelector((state) => state.favouriteRecipe);
65 | const [badgeValue, setBadgeValue] = useState(0);
66 | const navigate = useNavigate();
67 |
68 | useEffect(() => {
69 | setRecipe(searchItem);
70 |
71 | }, [searchItem]);
72 |
73 | useEffect(() => {
74 | setBadgeValue(favouriteRecipe.length);
75 | }, [favouriteRecipe]);
76 |
77 | const dispatch = useDispatch();
78 | const handleSubmit = (e) => {
79 | e.preventDefault();
80 | dispatch(fetchRecipe(recipeName));
81 | setRecipe("");
82 | navigate("/");
83 | };
84 |
85 | return (
86 | <>
87 |
88 |
89 |
90 |
91 |
97 | Recipe Finder
98 |
99 |
100 |
101 |
102 |
103 | {
108 | setRecipe(e.target.value);
109 | }}
110 | />
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | >
129 | );
130 | };
131 |
132 | export default NavBar;
133 |
--------------------------------------------------------------------------------
/src/Components/SearchList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box, Grid, Chip, Typography } from "@mui/material";
3 | import { createTheme, ThemeProvider } from "@mui/material/styles";
4 | import { fetchRecipe, setSearchItem } from "../Redux/RecipeActions";
5 | import { useDispatch } from "react-redux";
6 | import { useNavigate } from "react-router-dom";
7 | const SearchList = () => {
8 | const searchList = [
9 | "carrot",
10 | "broccoli",
11 | "asparagus",
12 | "cauliflower",
13 | "corn",
14 | "cucumber",
15 | "green pepper",
16 | "lettuce",
17 | "mushrooms",
18 | "onion",
19 | "potato",
20 | "pumpkin",
21 | "red pepper",
22 | "tomato",
23 | "beetroot",
24 | "brussel sprouts",
25 | "peas",
26 | "zucchini",
27 | "radish",
28 | "sweet potato",
29 | "artichoke",
30 | "leek",
31 | "cabbage",
32 | "celery",
33 | "chili",
34 | "garlic",
35 | "basil",
36 | "coriander",
37 | "parsley",
38 | "dill",
39 | "rosemary",
40 | "oregano",
41 | "cinnamon",
42 | "saffron",
43 | "green bean",
44 | "bean",
45 | "chickpea",
46 | "lentil",
47 | "apple",
48 | "apricot",
49 | "avocado",
50 | "banana",
51 | "blackberry",
52 | "blackcurrant",
53 | "blueberry",
54 | "boysenberry",
55 | "cherry",
56 | "coconut",
57 | "fig",
58 | "grape",
59 | "grapefruit",
60 | "kiwifruit",
61 | "lemon",
62 | "lime",
63 | "lychee",
64 | "mandarin",
65 | "mango",
66 | "melon",
67 | "nectarine",
68 | "orange",
69 | "papaya",
70 | "passion fruit",
71 | "peach",
72 | "pear",
73 | "pineapple",
74 | "plum",
75 | "pomegranate",
76 | "quince",
77 | "raspberry",
78 | "strawberry",
79 | "watermelon",
80 | "salad",
81 | "pizza",
82 | "pasta",
83 | "popcorn",
84 | "lobster",
85 | "steak",
86 | "bbq",
87 | "pudding",
88 | "hamburger",
89 | "pie",
90 | "cake",
91 | "sausage",
92 | "tacos",
93 | "kebab",
94 | "poutine",
95 | "seafood",
96 | "chips",
97 | "fries",
98 | "masala",
99 | "paella",
100 | "som tam",
101 | "chicken",
102 | "toast",
103 | "marzipan",
104 | "tofu",
105 | "ketchup",
106 | "hummus",
107 | "chili",
108 | "maple syrup",
109 | "parma ham",
110 | "fajitas",
111 | "champ",
112 | "lasagna",
113 | "poke",
114 | "chocolate",
115 | "croissant",
116 | "arepas",
117 | "bunny chow",
118 | "pierogi",
119 | "donuts",
120 | "rendang",
121 | "sushi",
122 | "ice cream",
123 | "duck",
124 | "curry",
125 | "beef",
126 | "goat",
127 | "lamb",
128 | "turkey",
129 | "pork",
130 | "fish",
131 | "crab",
132 | "bacon",
133 | "ham",
134 | "pepperoni",
135 | "salami",
136 | "ribs",
137 | ];
138 | const navigate = useNavigate();
139 | const dispatch = useDispatch();
140 | const theme = createTheme({
141 | breakpoints: {
142 | values: {
143 | xs: 0,
144 | sm: 425,
145 | md: 768,
146 | lg: 1024,
147 | xl: 1440,
148 | },
149 | },
150 | });
151 |
152 | const handleClick = (value) => {
153 | dispatch(setSearchItem(value));
154 | dispatch(fetchRecipe(value));
155 | navigate("/");
156 | };
157 | return (
158 | <>
159 |
160 |
161 |
162 | Available search queries
163 |
164 |
165 | {searchList.map((value) => (
166 |
167 | {
170 | handleClick(value);
171 | }}
172 | />
173 |
174 | ))}
175 |
176 |
177 |
178 | >
179 | );
180 | };
181 |
182 | export default SearchList;
183 |
--------------------------------------------------------------------------------
/src/Components/RecipeInstruction.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useParams } from "react-router-dom";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import Alert from "./Alert";
5 | import {
6 | Typography,
7 | Toolbar,
8 | Button,
9 | Paper,
10 | Box,
11 | Container,
12 | CssBaseline,
13 | Stack,
14 | } from "@mui/material";
15 | import { Person, AccessTime } from "@mui/icons-material";
16 | import { fetchRecipeItem, addToFavourite } from "../Redux/RecipeActions";
17 |
18 | const RecipeInstruction = () => {
19 | const { id } = useParams();
20 | const dispatch = useDispatch();
21 | const recipeInstruction = useSelector((state) => state.recipeInstruction);
22 | const favouriteRecipe = useSelector((state) => state.favouriteRecipe);
23 |
24 | const {
25 | publisher,
26 | source_url,
27 | image_url,
28 | title,
29 | servings,
30 | cooking_time,
31 | ingredients,
32 | } = recipeInstruction;
33 |
34 | const [showalert, setShowAlert] = useState(false);
35 |
36 | useEffect(() => {
37 | dispatch(fetchRecipeItem(id));
38 | }, [id, dispatch]);
39 |
40 | const handleAddClick = () => {
41 | const existingItem = favouriteRecipe.find((value) => value.id === id);
42 | if (existingItem) setShowAlert(true);
43 | else dispatch(addToFavourite({ image_url, publisher, title, id }));
44 | };
45 |
46 | return (
47 | <>
48 | {" "}
49 |
50 |
56 |
64 |
74 |
75 |
76 |
77 |
{title}
78 |
79 | {publisher}
80 |
81 |
82 |
88 |
89 |
90 | Serving size:{servings}
91 |
92 |
93 |
99 |
100 |
101 | Cooking time:{cooking_time}mins
102 |
103 |
104 |
105 |
106 |
111 | {" "}
112 |
115 |
118 |
119 |
120 |
121 |
122 |
123 |
129 |
130 | Ingredients
131 |
132 |
133 |
134 | {ingredients?.map((value, index) => (
135 | -
136 |
137 | {value.quantity}
138 | {value.unit} {""}
139 | {value.description}
140 |
141 |
142 | ))}
143 |
144 |
145 |
146 | >
147 | );
148 | };
149 |
150 | export default RecipeInstruction;
151 |
--------------------------------------------------------------------------------