Project Structure(Frontend)

Frontend Folder Structure.

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.3s 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.3s 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;
}
 
h3 {
    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;
}
 
h2 {
    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.3s ease-in-out;
}
 
.order-card:hover {
    transform: scale(1.02);
}
 
.order-card h3 {
    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:

Final 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

Similar Reads

Prerequisites:

ReactJS ExpressJS and NodeJs Context API, Prop drilling and Functional components JavaScript MERN Stack...

Approach to create Restaurant App using MERN:

1. Import Statements:...

Steps to create Application:

Step 1: creating the folder for the project...

Project Structure(Backend):

Backend Folder...

Project Structure(Frontend):

...