Project Structure(Frontend)
Frontend dependencies:
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Example: Create the required files and add the following code.
Javascript
//index.js (update previous index.js) import React from 'react' ; import ReactDOM from 'react-dom/client' ; import './index.css' ; import App from './App' ; import reportWebVitals from './reportWebVitals' ; import { RestaurantProvider } from './contexts/RestaurantContext' ; const root = ReactDOM.createRoot(document.getElementById( 'root' )); root.render( <RestaurantProvider> <App /> </RestaurantProvider> ); reportWebVitals(); |
Javascript
// App.js import React, { useContext } from "react" ; import RestaurantList from "./components/RestaurantList" ; import DishesMenu from "./components/DishesMenu" ; import Cart from "./components/Cart" ; import { RestaurantContext } from "./contexts/RestaurantContext" ; import "./App.css" ; // Import the CSS file const App = () => { const { selectedRestaurant } = useContext(RestaurantContext); return ( <> <div className= "container" > <h1 className= "header" >GFG Food Delivery App</h1> <Cart style={{ position: "absolute" , right: "20px" , top: "20px" }} /> <RestaurantList /> {selectedRestaurant && <DishesMenu />} </div> </> ); }; export default App; |
Javascript
//RestaurantContext.js import React, { createContext, useState, useEffect } from "react" ; import axios from "axios" ; const RestaurantContext = createContext(); const RestaurantProvider = ({ children }) => { const [restaurants, setRestaurants] = useState([]); const [selectedRestaurant, setSelectedRestaurant] = useState( null ); const [cartItems, setCartItems] = useState([]); const [totalPrice, setTotalPrice] = useState(0); useEffect(() => { const fetchRestaurants = async () => { try { const response = await axios.get( "http://localhost:5000/restaurants" ); setRestaurants(response.data); } catch (error) { console.error( "Error fetching restaurants:" , error.message); } }; fetchRestaurants(); }, []); const handleAddItems = (dish) => { console.log( "Dish:" , dish); // Check if the dish already exists in the cart const existingItemIndex = cartItems.findIndex( (item) => item._id === dish._id ); if (existingItemIndex !== -1) { // If the dish already exists, update // the quantity or any other logic console.log( "Dish already exists in the cart. You may want to update the quantity." ); // Example: Increment the quantity const updatedCartItems = [...cartItems]; updatedCartItems[existingItemIndex] = { ...updatedCartItems[existingItemIndex], quantity: updatedCartItems[existingItemIndex].quantity + 1, }; // console.log('cart',cartItems.length); // setTotalPrice(prev=>prev-dish.price) setCartItems(updatedCartItems); } else { // If the dish is not in the cart, add it console.log( "Dish does not exist in the cart. Adding to the cart." ); console.log( "cart" , cartItems.length); // setTotalPrice(prev=>prev-dish.price) setCartItems([...cartItems, { ...dish, quantity: 1 }]); } setTotalPrice((prev) => prev + dish.price); }; const handleRemoveItems = (dish) => { console.log( "Dish ID to remove:" , dish); // Check if the dish exists in the cart const existingItemIndex = cartItems.findIndex( (item) => item._id === dish._id ); if (existingItemIndex !== -1) { // If the dish exists, decrement the // quantity or remove it from the cart console.log( "Dish exists in the cart. You may want to decrease the quantity or remove it." ); const updatedCartItems = [...cartItems]; if (updatedCartItems[existingItemIndex].quantity > 1) { // If the quantity is greater than 1, decrement the quantity updatedCartItems[existingItemIndex] = { ...updatedCartItems[existingItemIndex], quantity: updatedCartItems[existingItemIndex].quantity - 1, }; setTotalPrice(totalPrice - cartItems[existingItemIndex].price); } else { // If the quantity is 1, remove the dish from the cart updatedCartItems.splice(existingItemIndex, 1); setTotalPrice(totalPrice - cartItems[existingItemIndex].price); } setCartItems(updatedCartItems); } else { // If the dish is not in the cart, // log a message or handle accordingly console.log( "Dish does not exist in the cart." ); } }; const emptyCart = () => { setCartItems([]); setTotalPrice(0); }; const value = { restaurants, selectedRestaurant, setSelectedRestaurant, handleAddItems, handleRemoveItems, totalPrice, emptyCart, }; return ( <RestaurantContext.Provider value={value}> {children} </RestaurantContext.Provider> ); }; export { RestaurantContext, RestaurantProvider }; |
Javascript
//Cart.js import React, { useContext, useState } from "react" ; import axios from "axios" ; import { RestaurantContext } from "../contexts/RestaurantContext" ; const Cart = () => { const { totalPrice, emptyCart } = useContext(RestaurantContext); const [isCheckingOut, setIsCheckingOut] = useState( false ); const generateOrderId = () => { // Generate a unique order ID // (you can use a library like uuid for a more robust solution) return `${Math.floor(Math.random() * 1000)}`; }; const handleCheckout = async () => { try { setIsCheckingOut( true ); const orderId = generateOrderId(); // Assuming you have a backend endpoint to handle the checkout const response = await axios.post( "http://localhost:5000/previousOrders" , { orderId, dateOfOrder: new Date(), amount: totalPrice, } ); console.log(response.data); emptyCart(); } catch (error) { console.error( "Error during checkout:" , error.message); } finally { setIsCheckingOut( false ); } }; return ( <div className= "cart-container" > <h2>Cart</h2> <div className= "cart-content" > <span style={{ color: "brown" }}>Total Price: </span> $ {totalPrice} <button onClick={handleCheckout} disabled={isCheckingOut}> {isCheckingOut ? "Checking out..." : "Checkout" } </button> </div> </div> ); }; export default Cart; |
Javascript
//DishCard.js import React, { useContext } from "react" ; import { RestaurantContext } from "../contexts/RestaurantContext" ; const DishCard = ({ dish }) => { const { handleAddItems, handleRemoveItems } = useContext(RestaurantContext); const handleAdd = () => { handleAddItems(dish); }; const handleRemove = () => { handleRemoveItems(dish); }; return ( <div className= "dish-card" > <h3>{dish.name}</h3> <img src={dish.image} alt= "" /> <p>Price: ${dish.price}</p> <div style={{ width: "40%" , display: "flex" , justifyContent: "space-between" , alignItems: "center" , }} > <button onClick={handleAdd}>+</button> <button onClick={handleRemove}>-</button> </div> </div> ); }; export default DishCard; |
Javascript
//DishesMenu.js import React, { useContext } from 'react' ; import DishCard from './DishCard' ; import { RestaurantContext } from '../contexts/RestaurantContext' ; const DishesMenu = () => { const { selectedRestaurant } = useContext(RestaurantContext); return ( <div> <h2>Menu</h2> {selectedRestaurant && ( <div style={{ display: 'flex' , flexWrap: 'wrap' }}> {selectedRestaurant.menu.map((dish) => ( <DishCard key={dish.name} dish={dish} /> ))} </div> )} </div> ); }; export default DishesMenu; |
Javascript
//PreviousOrders.js import React, { useState, useEffect } from 'react' ; import axios from 'axios' ; const PreviousOrders = ({ handleShow }) => { const [orders, setOrders] = useState([]); useEffect(() => { const fetchOrders = async () => { try { const response = await axios.get( 'http://localhost:5000/previousOrders' ); setOrders(response.data); } catch (error) { console.error( 'Error fetching orders:' , error.message); } }; fetchOrders(); }, []); return ( <div className= "previous-orders-container" > <h2>Your Previous Orders</h2> <button style={{ backgroundColor: "white" , color: "red" }} onClick={handleShow}>Close</button> <ul className= "orders-list" > {orders.map(order => ( <li key={order.orderId} className= "order-card" > <h3>Order #{order.orderId}</h3> <div className= "order-details" > <div>Items: 1</div> <div>Total Amount: ${order.amount.toFixed(2)}</div> </div> <div>Ordered on: { new Date(order.dateOfOrder).toLocaleDateString()}</div> </li> ))} </ul> </div> ); }; export default PreviousOrders; |
Javascript
//RestaurantCard.js import React from 'react' ; const RestaurantCard = ({ restaurant, onClick }) => { return ( <div className= "card" onClick={onClick}> <h3>{restaurant.name}</h3> <div className= "image-container" > <img className= "restaurant-image" src={restaurant.image} alt={restaurant.name} /> </div> <p>Rating: {restaurant.rating}</p> </div> ); }; export default RestaurantCard; |
Javascript
//RestaurantList.js import React, { useContext, useState, useEffect } from 'react' ; import RestaurantCard from './RestaurantCard' ; import { RestaurantContext } from '../contexts/RestaurantContext' ; import PreviousOrders from './PreviousOders' ; const RestaurantList = () => { const { restaurants, setSelectedRestaurant } = useContext(RestaurantContext); const [filteredRestaurants, setFilteredRestaurants] = useState([...restaurants]); const [ratingFilter, setRatingFilter] = useState( '' ); const [searchTerm, setSearchTerm] = useState( '' ); const [showOrder, setShowOrder] = useState( false ) useEffect(() => { filterRestaurants(); }, [ratingFilter, searchTerm, restaurants]); const handleRestaurantClick = (restaurantId) => { setSelectedRestaurant(restaurants.find((restaurant) => restaurant._id === restaurantId)); }; const handleRatingChange = (e) => { setRatingFilter(e.target.value); }; const handleSearchChange = (e) => { setSearchTerm(e.target.value); }; const filterRestaurants = () => { let filtered = restaurants; if (ratingFilter) { filtered = filtered.filter((restaurant) => restaurant.rating >= parseFloat(ratingFilter)); } if (searchTerm) { const searchLower = searchTerm.toLowerCase(); filtered = filtered.filter((restaurant) => restaurant.name.toLowerCase().includes(searchLower) ); } setFilteredRestaurants(filtered); }; const handleShow = () => { setShowOrder(!showOrder) } return ( <div className= "container" > <h2 className= "header" >Restaurant List</h2> <div className= "filter-container" > <label htmlFor= "rating" className= "filter-label" > Filter by Rating: </label> <input type= "number" id= "rating" value={ratingFilter} onChange={handleRatingChange} className= "filter-input" /> <label htmlFor= "search" className= "filter-label" > Search by Name: </label> <input type= "text" id= "search" value={searchTerm} onChange={handleSearchChange} className= "filter-input" /> <p id= 'pre-orders' onClick={handleShow}> Previous Orders </p> </div> <div className= "restaurant-card-container" > {filteredRestaurants.map((restaurant) => ( <RestaurantCard key={restaurant._id} restaurant={restaurant} onClick={() => handleRestaurantClick(restaurant._id)} /> ))} </div> {showOrder && <PreviousOrders handleShow={handleShow} />} </div> ); }; export default RestaurantList; |
CSS
/* App.css */ .container { font-family : 'Arial, sans-serif' ; padding : 20px ; display : flex; flex- direction : column; align-items: center ; } .header { font-size : 24px ; margin-bottom : 10px ; border-radius: 15px ; } /* Styles to Resturant List */ /* RestaurantList.css */ .container { font-family : 'Arial, sans-serif' ; padding : 20px ; display : flex; flex- direction : column; align-items: center ; } .header { font-size : 32px ; margin-bottom : 20px ; background-color : #fc0671 ; color : white ; padding : 10px ; } .filter-container { display : flex; justify- content : center ; align-items: center ; gap: 20px ; margin-bottom : 20px ; } .filter-container label { font-size : 18px ; color : #555 ; } .filter-input { padding : 8px ; font-size : 16px ; } .restaurant-card-container { display : flex; flex-wrap: wrap; gap: 20px ; justify- content : center ; } /* RestaurantCard.css */ .card { border : 1px solid #ccc ; border-radius: 8px ; padding : 12px ; margin : 10px ; width : 200px ; cursor : pointer ; transition: transform 0.3 s ease-in-out; } .card:hover { transform: scale( 1.05 ); } .image-container { overflow : hidden ; border-radius: 8px ; margin-bottom : 10px ; height : 150px ; /* Set a fixed height for the image container */ } .restaurant-image { width : 100% ; height : 100% ; object-fit: cover; /* Maintain the aspect ratio and cover the container */ border-radius: 8px ; } /* Responsive Styles */ @media screen and ( max-width : 600px ) { .card { width : 100% ; } } /* Dish Card */ .dish-card { border : 1px solid #ccc ; border-radius: 8px ; padding : 12px ; margin : 10px ; width : 200px ; display : flex; flex- direction : column; align-items: center ; text-align : center ; transition: transform 0.3 s ease-in-out; } .dish-card:hover { transform: scale( 1.05 ); } img { width : 100% ; /* Set the width to fill the container */ height : 100px ; /* Set the fixed height for the image */ object-fit: cover; /* Maintain the aspect ratio and cover the container */ border-radius: 8px ; margin-bottom : 10px ; } h 3 { margin-bottom : 8px ; } p { margin-bottom : 8px ; } button { margin-top : 8px ; padding : 8px ; cursor : pointer ; background-color : #4caf50 ; color : white ; border : none ; border-radius: 4px ; } /* Responsive Styles */ @media screen and ( max-width : 600px ) { .dish-card { width : 100% ; } } /* CART */ .cart-container { position : fixed ; top : 10px ; right : 10px ; width : 200px ; border : 2px solid #fc0671 ; border-radius: 10px ; height : fit-content; padding : 5px 10px ; background-color : #fff ; box-shadow: 0 0 10px rgba( 0 , 0 , 0 , 0.1 ); overflow-y: auto ; z-index : 1000 ; } .cart-container button { background-color : #fc0671 ; color : white ; border : none ; border-radius: 10px ; } h 2 { margin-bottom : 10px ; margin-left : 20px ; } .cart-content { padding : 16px ; display : flex; flex- direction : column; justify- content : center ; align-items: flex-start; } #pre-orders { background-color : #fc0671 ; color : aliceblue; font-size : 20px ; padding : 5px 10px ; border-radius: 10px ; cursor : pointer ; } /* Previous orders */ /* PreviousOrders.css */ .previous-orders-container { max-width : 600px ; margin : 20px auto ; padding : 20px ; position : absolute ; background-color : #fc0671 ; color : white ; border-radius: 10px ; } .orders-list { list-style : none ; padding : 0 ; } .order-card { background-color : #fff ; border : 1px solid #ddd ; border-radius: 8px ; padding : 15px ; margin-bottom : 15px ; box-shadow: 0 4px 8px rgba( 0 , 0 , 0 , 0.1 ); transition: transform 0.3 s ease-in-out; } .order-card:hover { transform: scale( 1.02 ); } .order-card h 3 { color : #333 ; margin-bottom : 10px ; } .order-details { display : flex; justify- content : space-between; font-size : 14px ; color : #666 ; } |
Step 7: To start the frontend run the following command.
npm start
Output:
Food Delivery Application using MERN Stack
In the fast world where there is no time for people to cook food or go to restaurants to eat, Food Delivery applications are one of the best options for them. In this tutorial, you will learn how to create a simple food delivery app using MERN stack. Our application will allow users to browse through a list of restaurants, view their menus, and add items to a shopping cart.
Preview of final output: Let us have a look at how the final output will look like