├── src ├── components │ ├── Sum.js │ ├── __tests__ │ │ ├── Sum.test.js │ │ └── Header.test.js │ ├── Contact.jsx │ ├── Instamart.jsx │ ├── About.jsx │ ├── Error.jsx │ ├── NotFound.jsx │ ├── Profile.jsx │ ├── Cart.jsx │ ├── Header.jsx │ ├── Shimmer.jsx │ ├── RestaurentCard.jsx │ ├── Footer.jsx │ ├── Body.jsx │ └── RestaurentMenu.jsx ├── index.css ├── Utils │ ├── UserContext.js │ ├── Helper.js │ ├── store.js │ ├── CartSlice.js │ ├── useRestaurant.js │ └── useOnline.js ├── App.js └── Config.js ├── assets ├── hiking.png ├── offer.png └── offer2.png ├── tailwind.config.js ├── README.md ├── index.html ├── package.json └── jest.config.js /src/components/Sum.js: -------------------------------------------------------------------------------- 1 | export const Sum = (num1,num2)=>num1 + num2; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /assets/hiking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chhatrapati295/FoodPanda-food-app/HEAD/assets/hiking.png -------------------------------------------------------------------------------- /assets/offer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chhatrapati295/FoodPanda-food-app/HEAD/assets/offer.png -------------------------------------------------------------------------------- /assets/offer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chhatrapati295/FoodPanda-food-app/HEAD/assets/offer2.png -------------------------------------------------------------------------------- /src/components/__tests__/Sum.test.js: -------------------------------------------------------------------------------- 1 | import { Sum } from "../Sum" 2 | 3 | test('For sum of two numbers',()=>{ 4 | expect(Sum(2,3)).toBe(5); 5 | }) -------------------------------------------------------------------------------- /src/components/Contact.jsx: -------------------------------------------------------------------------------- 1 | const Contact = ()=>{ 2 | return( 3 |
4 |

This is Contact Page

5 |
6 | ) 7 | } 8 | export default Contact; -------------------------------------------------------------------------------- /src/components/Instamart.jsx: -------------------------------------------------------------------------------- 1 | const Instamart = ()=>{ 2 | return( 3 |
4 |

This is Instamart Page

5 |
6 | ) 7 | } 8 | export default Instamart; -------------------------------------------------------------------------------- /src/Utils/UserContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const UserContext = createContext({ 4 | name : 'Chhatrapati Chauhan', 5 | email : 'chhatrapati1511@gmail.com' 6 | }) 7 | export default UserContext; -------------------------------------------------------------------------------- /src/Utils/Helper.js: -------------------------------------------------------------------------------- 1 | export function filterData (searchText,allRestaurents){ 2 | return (allRestaurents.filter((restaurent)=>{ 3 | return restaurent?.data?.name.toLowerCase()?.includes(searchText.toLowerCase()) 4 | })) 5 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{html,js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/Utils/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import CartSlice from "./CartSlice"; 3 | 4 | const store = configureStore({ 5 | reducer : { 6 | cart : CartSlice 7 | } 8 | }) 9 | export default store; -------------------------------------------------------------------------------- /src/components/About.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | 3 | const About = ()=>{ 4 | return( 5 |
6 |

This is About Page

7 | 8 |
9 | ) 10 | } 11 | export default About; -------------------------------------------------------------------------------- /src/components/Error.jsx: -------------------------------------------------------------------------------- 1 | import { useRouteError } from "react-router-dom"; 2 | const Error = ()=>{ 3 | const err = useRouteError() 4 | return( 5 |
6 |

You made a mistake

7 |

{err.status} : {err.statusText}

8 |
9 | ) 10 | } 11 | export default Error; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FoodPanda-food-app 2 | In this project I have used Swiggy live API to fetch all the data through CORS. 3 | 4 | 🚀 React js as a UI library. 5 | Redux toolkit for state management. 6 | Parcel for bundling. 7 | Babel as a JavaScript transpiler. 8 | React router-V6 for routing. 9 | Tailwind CSS for styling. 10 | Completely responsive. 11 | React testing library with jest for unit testing and integration testing. 12 | -------------------------------------------------------------------------------- /src/components/NotFound.jsx: -------------------------------------------------------------------------------- 1 | const NotFound = ()=>{ 2 | return( 3 |
4 | 5 |

Internet is dissconnected

6 |
7 | ) 8 | } 9 | export default NotFound; -------------------------------------------------------------------------------- /src/Utils/CartSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const CartSlice = createSlice({ 4 | name : 'cart', 5 | initialState : { 6 | items : [] 7 | }, 8 | reducers : { 9 | addItem : (state,action)=>{ 10 | state.items.push(action.payload) 11 | }, 12 | removeItem : (state,action)=>{ 13 | state.items.pop() 14 | }, 15 | clearItem : (state,action)=>{ 16 | state.items = [] 17 | } 18 | } 19 | }) 20 | 21 | export const{addItem , removeItem , clearItem} = CartSlice.actions; 22 | export default CartSlice.reducer; -------------------------------------------------------------------------------- /src/Utils/useRestaurant.js: -------------------------------------------------------------------------------- 1 | import { useState , useEffect } from "react" 2 | 3 | const useRestaurant = (resId)=>{ 4 | const[resMenu , setResMenu] = useState(null) 5 | useEffect(()=>{ 6 | getMenu() 7 | },[]) 8 | async function getMenu(){ 9 | const URL = await fetch(`https://corsproxy.io/?https://www.swiggy.com/dapi/menu/pl?page-type=REGULAR_MENU&complete-menu=true&lat=19.0759837&lng=72.8776559&restaurantId=${resId}&submitAction=ENTER`) 10 | const json = await URL.json() 11 | console.log(json?.data?.cards[2]?.groupedCard?.cardGroupMap?.REGULAR?.cards[2]) 12 | setResMenu(json?.data) 13 | } 14 | return resMenu; 15 | } 16 | export default useRestaurant; -------------------------------------------------------------------------------- /src/Utils/useOnline.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | 3 | const UseOnline = ()=>{ 4 | const[isOnline,setIsOnline]= useState(true) 5 | useEffect(()=>{ 6 | const handleOnline = ()=>{ 7 | setIsOnline(true) 8 | } 9 | const handleOffline = ()=>{ 10 | setIsOnline(false) 11 | } 12 | window.addEventListener('online',handleOnline) 13 | window.addEventListener('offline',handleOffline) 14 | return (()=>{ 15 | window.removeEventListener('online',handleOnline) 16 | window.removeEventListener('offline',handleOffline) 17 | }) 18 | },[]) 19 | return isOnline; 20 | } 21 | export default UseOnline; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | FoodPanda | order food from best restaurants near you 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foodpanda-main", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "jest", 7 | "start": "parcel index.html", 8 | "build": "parcel build index.html" 9 | }, 10 | "author": "Chhatrapati chauhan", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@reduxjs/toolkit": "^1.9.3", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-loading-skeleton": "^3.2.0", 17 | "react-redux": "^8.0.5", 18 | "react-router-dom": "^6.10.0" 19 | }, 20 | "devDependencies": { 21 | "@testing-library/react": "^14.0.0", 22 | "jest": "^29.5.0", 23 | "jest-environment-jsdom": "^29.5.0", 24 | "parcel": "^2.8.3", 25 | "postcss": "^8.4.21", 26 | "process": "^0.11.10", 27 | "tailwindcss": "^3.3.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/__tests__/Header.test.js: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/react" 2 | import Header from '../Header' 3 | import { Provider } from "react-redux" 4 | import store from "../../Utils/store" 5 | import {StaticRouter} from 'react-router-dom/server' 6 | 7 | test('To load header when rendering first time',()=>{ 8 | const header = render( 9 | 10 | 11 |
12 | 13 | 14 | ); 15 | // console.log(header) 16 | const logo = header.getByTestId('logo') 17 | expect(logo.src).toBe("https://th.bing.com/th/id/R.f6bc2b2712b5a390fee444e60d312b69?rik=qTqjtW7Nn4UiJw&riu=http%3a%2f%2fwww.gadgetsmagazine.com.ph%2fwp-content%2fuploads%2f2017%2f09%2ffoodpanda-logo.png&ehk=Jy3z435XdWQADxTy4KoiuJvyN6HyGj9DOZIzHuitbEI%3d&risl=&pid=ImgRaw&r=0") 18 | }) 19 | 20 | test('Cart item should be 0 in intital render',()=>{ 21 | const header = render( 22 | 23 | 24 |
25 | 26 | 27 | ) 28 | const cart = header.getByTestId('cart') 29 | expect(cart.innerHTML).toBe('0') 30 | }) -------------------------------------------------------------------------------- /src/components/Profile.jsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | // import UserContext from "../Utils/UserContext"; 3 | 4 | class Profile extends Component{ 5 | constructor(props){ 6 | super(props) 7 | this.state = { 8 | name : 'What', 9 | loc : 'where' 10 | } 11 | } 12 | async componentDidMount(){ 13 | const url = await fetch('https://api.github.com/users/chhatrapati295') 14 | const data = await url.json() 15 | console.log(data) 16 | this.setState({ 17 | name : data.name, 18 | loc : data.location 19 | }) 20 | 21 | } 22 | render(){ 23 | console.log('render') 24 | return( 25 | <> 26 |

hello

27 | {/* 28 | { 29 | ({user})=>( 30 |

{user.name} : {user.email}

31 | ) 32 | } 33 |
*/} 34 |

{this.state.name}

35 |

{this.state.loc}

36 | 37 | ) 38 | } 39 | } 40 | export default Profile; -------------------------------------------------------------------------------- /src/components/Cart.jsx: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from "react-redux"; 2 | import { IMG_CDN } from "../Config"; 3 | import { clearItem } from "../Utils/CartSlice"; 4 | 5 | const Cart = ()=>{ 6 | const cartItems = useSelector((store)=> store.cart.items) 7 | console.log(cartItems) 8 | const dispatch = useDispatch() 9 | function clearItemFunc (){ 10 | dispatch(clearItem()) 11 | } 12 | return( 13 |
14 |
15 |

Cart ({cartItems.length})

16 | 17 |
18 |
19 | {cartItems.map(item=>{ 20 | return 21 | })} 22 |
23 |
24 | ) 25 | } 26 | const CartItem = ({name,imageId,price,description})=>{ 27 | return( 28 |
29 | 36 |
37 | {name} 38 | ₹{!price ? '250' : price/100} 39 | {description} 40 |
41 |
42 | ) 43 | } 44 | export default Cart; -------------------------------------------------------------------------------- /src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | // import UseOnline from "../Utils/useOnline"; 3 | import { useContext } from "react"; 4 | import UserContext from "../Utils/UserContext"; 5 | import { useSelector } from "react-redux"; 6 | // import store from "../Utils/store"; 7 | 8 | const Header = ()=>{ 9 | // const isOnline = UseOnline() 10 | const myInfo = useContext(UserContext) 11 | const cartItems = useSelector(store => store.cart.items) 12 | return( 13 |
14 | 15 | 16 | 17 |
    18 |
  • Home
  • 19 |
  • About
  • 20 |
  • Contact
  • 21 |
  • {cartItems.length}
  • 22 | {/*
  • Instamart
  • */} 23 |
24 | {/*
{isOnline ? '🟢' : '🔴'}
*/} 25 | {/* {myInfo.name} */} 26 |
27 | ) 28 | } 29 | export default Header; -------------------------------------------------------------------------------- /src/components/Shimmer.jsx: -------------------------------------------------------------------------------- 1 | import Skeleton from "react-loading-skeleton"; 2 | import "react-loading-skeleton/dist/skeleton.css"; 3 | 4 | const Shimmer = () => { 5 | return ( 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | ); 17 | }; 18 | const ShimmerCard = () => { 19 | return ( 20 |
21 | 22 | 23 | 24 | 25 |
26 | ); 27 | }; 28 | export const ShimmerMenu = () => { 29 | return ( 30 |
31 |
32 |
33 | 34 | 35 | 36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 |
45 | 46 | 47 |
48 | 49 |
50 |
51 |
52 | 53 | 54 |
55 | 56 |
57 |
58 | ); 59 | }; 60 | export default Shimmer; 61 | -------------------------------------------------------------------------------- /src/components/RestaurentCard.jsx: -------------------------------------------------------------------------------- 1 | import { IMG_CDN } from "../Config"; 2 | import offerImg from "../../assets/offer2.png"; 3 | import { useContext } from "react"; 4 | import UserContext from "../Utils/UserContext"; 5 | const RestaurentCard = ({ 6 | name, 7 | cuisines, 8 | avgRating, 9 | cloudinaryImageId, 10 | slaString, 11 | costForTwoString, 12 | aggregatedDiscountInfo, 13 | // user 14 | }) => { 15 | const myInfo= useContext(UserContext) 16 | return ( 17 |
18 | 28 | {name} 29 | {cuisines.join(", ")} 30 |
31 |
32 | 33 | 34 | {avgRating === "--" ? "4.2" : avgRating} 35 | 36 |
37 |
38 | {slaString} 39 |
40 | {costForTwoString} 41 |
42 |
43 | 44 | 45 | {!aggregatedDiscountInfo?.shortDescriptionList[0]?.meta 46 | ? "40% off | Use TRYNEW" 47 | : aggregatedDiscountInfo?.shortDescriptionList[0]?.meta} 48 | 49 |
50 | {/* {myInfo.name} */} 51 |
52 | ); 53 | }; 54 | export default RestaurentCard; 55 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy, useState } from "react" 2 | import ReactDOM from 'react-dom/client' 3 | import Header from "./components/Header" 4 | import Body from "./components/Body" 5 | import { Outlet, RouterProvider, createBrowserRouter } from "react-router-dom" 6 | import Error from "./components/Error" 7 | import About from "./components/About" 8 | import Contact from "./components/Contact" 9 | import Footer from "./components/Footer" 10 | import RestaurentMenu from "./components/RestaurentMenu" 11 | import Profile from "./components/Profile" 12 | import Shimmer from "./components/Shimmer" 13 | import Cart from "./components/Cart" 14 | import UserContext from "./Utils/UserContext" 15 | import { Provider } from "react-redux" 16 | import store from "./Utils/store" 17 | 18 | const Instamart = lazy(()=> import("./components/Instamart")) 19 | 20 | const App = ()=>{ 21 | return( 22 | 23 | 26 |
27 | 28 |