├── cod_icon.png ├── src ├── redux │ ├── actions │ │ ├── index.ts │ │ ├── shoppingAction.ts │ │ └── userActions.ts │ ├── index.ts │ ├── store.ts │ ├── reducers │ │ ├── index.ts │ │ ├── shoppingReducer.ts │ │ └── userReducer.ts │ └── models │ │ └── index.ts ├── images │ ├── email.png │ ├── home.png │ ├── lunch.jpg │ ├── offer.jpg │ ├── pizza.jpg │ ├── avatar.png │ ├── burger.jpg │ ├── coffee.jpeg │ ├── cusin_1.jpg │ ├── cusin_2.jpg │ ├── cusin_3.jpg │ ├── hambar.png │ ├── orders.png │ ├── search.png │ ├── arrow_icon.png │ ├── back_arrow.png │ ├── call_icon.png │ ├── cart_icon.png │ ├── edit_icon.png │ ├── home_icon.png │ ├── offer_icon.png │ ├── starIcon.png │ ├── account_icon.png │ ├── burger_icon.jpg │ ├── cart_n_icon.png │ ├── home_focused.png │ ├── home_n_icon.png │ ├── offer_n_icon.png │ ├── warning-icon.png │ ├── account_n_icon.png │ ├── delivery_icon.png │ ├── order_process.png │ ├── best_restaurent_1.jpg │ └── best_restaurent_2.jpg ├── utils │ ├── index.tsx │ ├── useNavigation.tsx │ └── CartHelper.tsx ├── components │ ├── index.tsx │ ├── ButtonWithIcon.tsx │ ├── CategoryCard.tsx │ ├── RestaurantCard.tsx │ ├── TextField.tsx │ ├── ButtonWithTitle.tsx │ ├── SearchBar.tsx │ ├── ButtonAddRemove.tsx │ ├── FoodCartInfo.tsx │ └── FoodCard.tsx └── screens │ ├── FoodDetailScreen.tsx │ ├── SearchScreen.tsx │ ├── RestaurantScreen.tsx │ ├── LoginScreen.tsx │ ├── LandingScreen.tsx │ ├── CartScreen.tsx │ └── HomeScreen.tsx ├── assets ├── icon.png ├── splash.png └── favicon.png ├── card_icon.png ├── verify_otp.png ├── stripe_server.zip ├── .expo-shared └── assets.json ├── .gitignore ├── babel.config.js ├── backend ├── docker-compose.yml ├── readme.md └── Online_Food_Order_App_Backend_API.postman_collection.json ├── tsconfig.json ├── app.json ├── package.json ├── Readme.md └── App.tsx /cod_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/cod_icon.png -------------------------------------------------------------------------------- /src/redux/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './userActions' 2 | export * from './shoppingAction' -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/assets/icon.png -------------------------------------------------------------------------------- /card_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/card_icon.png -------------------------------------------------------------------------------- /verify_otp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/verify_otp.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/assets/splash.png -------------------------------------------------------------------------------- /stripe_server.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/stripe_server.zip -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /src/images/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/email.png -------------------------------------------------------------------------------- /src/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/home.png -------------------------------------------------------------------------------- /src/images/lunch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/lunch.jpg -------------------------------------------------------------------------------- /src/images/offer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/offer.jpg -------------------------------------------------------------------------------- /src/images/pizza.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/pizza.jpg -------------------------------------------------------------------------------- /src/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/avatar.png -------------------------------------------------------------------------------- /src/images/burger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/burger.jpg -------------------------------------------------------------------------------- /src/images/coffee.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/coffee.jpeg -------------------------------------------------------------------------------- /src/images/cusin_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/cusin_1.jpg -------------------------------------------------------------------------------- /src/images/cusin_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/cusin_2.jpg -------------------------------------------------------------------------------- /src/images/cusin_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/cusin_3.jpg -------------------------------------------------------------------------------- /src/images/hambar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/hambar.png -------------------------------------------------------------------------------- /src/images/orders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/orders.png -------------------------------------------------------------------------------- /src/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/search.png -------------------------------------------------------------------------------- /src/images/arrow_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/arrow_icon.png -------------------------------------------------------------------------------- /src/images/back_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/back_arrow.png -------------------------------------------------------------------------------- /src/images/call_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/call_icon.png -------------------------------------------------------------------------------- /src/images/cart_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/cart_icon.png -------------------------------------------------------------------------------- /src/images/edit_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/edit_icon.png -------------------------------------------------------------------------------- /src/images/home_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/home_icon.png -------------------------------------------------------------------------------- /src/images/offer_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/offer_icon.png -------------------------------------------------------------------------------- /src/images/starIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/starIcon.png -------------------------------------------------------------------------------- /src/images/account_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/account_icon.png -------------------------------------------------------------------------------- /src/images/burger_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/burger_icon.jpg -------------------------------------------------------------------------------- /src/images/cart_n_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/cart_n_icon.png -------------------------------------------------------------------------------- /src/images/home_focused.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/home_focused.png -------------------------------------------------------------------------------- /src/images/home_n_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/home_n_icon.png -------------------------------------------------------------------------------- /src/images/offer_n_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/offer_n_icon.png -------------------------------------------------------------------------------- /src/images/warning-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/warning-icon.png -------------------------------------------------------------------------------- /src/images/account_n_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/account_n_icon.png -------------------------------------------------------------------------------- /src/images/delivery_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/delivery_icon.png -------------------------------------------------------------------------------- /src/images/order_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/order_process.png -------------------------------------------------------------------------------- /src/images/best_restaurent_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/best_restaurent_1.jpg -------------------------------------------------------------------------------- /src/images/best_restaurent_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codergogoi/Online_Food_Order_App/HEAD/src/images/best_restaurent_2.jpg -------------------------------------------------------------------------------- /src/redux/index.ts: -------------------------------------------------------------------------------- 1 | export * from './store' 2 | export * from './actions' 3 | export * from './models' 4 | export * from './reducers' -------------------------------------------------------------------------------- /src/utils/index.tsx: -------------------------------------------------------------------------------- 1 | export const BASE_URL = "http://localhost:8888/"; 2 | 3 | export * from "./useNavigation"; 4 | export * from "./CartHelper"; 5 | -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | 12 | # macOS 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | plugins: ["react-native-reanimated/plugin"], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux' 2 | import thunk from 'redux-thunk' 3 | 4 | import { rootReducer } from './reducers' 5 | 6 | const store = createStore(rootReducer, applyMiddleware(thunk)) 7 | 8 | export { store }; -------------------------------------------------------------------------------- /backend/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | food_backend: 4 | image: codewithjay/food_order_backend:latest 5 | ports: 6 | - "8888:8888" # You can change the port number "[YOUR_MACHINE_PORT]:[CONTAINER_PORT(Don't change it)]" 7 | container_name: food_app_backend 8 | -------------------------------------------------------------------------------- /src/components/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './SearchBar' 2 | export * from './ButtonWithIcon' 3 | export * from './CategoryCard' 4 | export * from './RestaurantCard' 5 | export * from './ButtonAddRemove' 6 | export * from './FoodCard' 7 | export * from './FoodCartInfo'; 8 | export * from './ButtonWithIcon'; 9 | export * from './TextField'; -------------------------------------------------------------------------------- /src/utils/useNavigation.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { NavigationScreenProp,NavigationRoute, NavigationContext, NavigationParams } from 'react-navigation' 3 | 4 | 5 | export function useNavigation(){ 6 | 7 | return useContext(NavigationContext) as NavigationScreenProp 8 | 9 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "react-native", 5 | "lib": [ 6 | "dom", 7 | "esnext" 8 | ], 9 | "moduleResolution": "node", 10 | "noEmit": true, 11 | "skipLibCheck": true, 12 | "resolveJsonModule": true, 13 | "strict": true 14 | }, 15 | "extends": "expo/tsconfig.base" 16 | } 17 | -------------------------------------------------------------------------------- /src/redux/reducers/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { combineReducers } from 'redux' 3 | import { ShoppingReducer } from './shoppingReducer' 4 | import { UserReducer } from './userReducer' 5 | 6 | const rootReducer = combineReducers({ 7 | userReducer: UserReducer, 8 | shoppingReducer: ShoppingReducer 9 | }) 10 | 11 | export type ApplicationState = ReturnType 12 | 13 | export { rootReducer} -------------------------------------------------------------------------------- /src/utils/CartHelper.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { FoodModel } from '../redux' 3 | 4 | 5 | 6 | const checkExistence = (item: FoodModel, Cart: [FoodModel]) => { 7 | 8 | if(Array.isArray(Cart)){ 9 | 10 | let currentItem = Cart.filter((cartItem) => cartItem._id == item._id) 11 | 12 | if(currentItem.length > 0){ 13 | return currentItem[0] 14 | } 15 | } 16 | return item; 17 | } 18 | 19 | export { checkExistence }; -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Online_Food_Order_App", 4 | "slug": "Online_Food_Order_App", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": [ 17 | "**/*" 18 | ], 19 | "ios": { 20 | "supportsTablet": true 21 | }, 22 | "web": { 23 | "favicon": "./assets/favicon.png" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/redux/reducers/shoppingReducer.ts: -------------------------------------------------------------------------------- 1 | import { ShoppingAction } from '../actions' 2 | import { FoodAvailability, FoodModel, ShoppingState } from '../models' 3 | 4 | 5 | const initialState = { 6 | availability: {} as FoodAvailability, 7 | availableFoods: {} as [FoodModel] 8 | } 9 | 10 | 11 | 12 | const ShoppingReducer = (state: ShoppingState = initialState, action: ShoppingAction) => { 13 | 14 | switch(action.type){ 15 | case 'ON_AVAILABILITY': 16 | return { 17 | ...state, 18 | availability: action.payload 19 | } 20 | case 'ON_FOODS_SEARCH': 21 | return { 22 | ...state, 23 | availableFoods: action.payload 24 | } 25 | 26 | 27 | default: 28 | return state 29 | } 30 | 31 | 32 | } 33 | 34 | 35 | export { ShoppingReducer} -------------------------------------------------------------------------------- /src/components/ButtonWithIcon.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image, ImageSourcePropType } from 'react-native' 3 | 4 | interface ButtonProps{ 5 | onTap: Function; 6 | width: number; 7 | height: number; 8 | icon: ImageSourcePropType 9 | } 10 | 11 | 12 | const ButtonWithIcon: React.FC = ({ onTap, icon, width, height }) => { 13 | 14 | return ( 15 | onTap()} 17 | > 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | 25 | const styles = StyleSheet.create({ 26 | btn: { display: 'flex', justifyContent: 'center', alignItems: 'center', width: 60, height: 40}, 27 | }) 28 | 29 | export { ButtonWithIcon } -------------------------------------------------------------------------------- /src/components/CategoryCard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image } from 'react-native' 3 | import { Category } from '../redux' 4 | 5 | interface CategoryProps{ 6 | item: Category; 7 | onTap: Function; 8 | 9 | } 10 | const CategoryCard: React.FC = ({ item, onTap }) => { 11 | 12 | 13 | 14 | return ( 15 | 16 | onTap(item)}> 17 | 18 | {item.title} 19 | 20 | ) 21 | } 22 | 23 | 24 | const styles = StyleSheet.create({ 25 | container: { width: 120, height: 140, justifyContent: 'space-around', alignItems: 'center', margin: 5 }, 26 | 27 | }) 28 | 29 | export { CategoryCard } -------------------------------------------------------------------------------- /src/components/RestaurantCard.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect} from 'react' 3 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image, Dimensions } from 'react-native' 4 | import { FoodModel, Restaurant } from '../redux' 5 | 6 | 7 | const screenWidth = Dimensions.get('screen').width; 8 | 9 | interface RestaurantProps{ 10 | item: Restaurant | FoodModel 11 | onTap: Function; 12 | } 13 | 14 | const RestaurantCard: React.FC = ({ item, onTap }) => { 15 | 16 | return ( 17 | onTap(item)}> 18 | 21 | 22 | 23 | )} 24 | 25 | const styles = StyleSheet.create({ 26 | container: { width: screenWidth - 20, height: 230, justifyContent: 'space-around', alignItems: 'center', margin: 10, borderRadius: 20 }, 27 | }) 28 | 29 | export { RestaurantCard } -------------------------------------------------------------------------------- /backend/readme.md: -------------------------------------------------------------------------------- 1 | # Steps to run the Backend API 2 | 3 | As you all know Heroku has stoped free service for running the nodejs app for free. So as a solution for backend API, I have created a docker image to support the tutorial series. Kindly follow the below steps to run the server on your local machine and change the base URL to the below in your mobile app configuration to call APIs. 4 | 5 | ## Install Docker desktop into you machine & run 6 | 7 | Install Docker Desktop [https://www.docker.com/products/docker-desktop/] 8 | 9 | Download the backend directory to your machine and go to your terminal and get inside the directory to run the docker image. 10 | 11 | `$ cd backend` 12 | 13 | Now run the docker compose file to spin APIs in your local machine 14 | 15 | `$backend docker-compose up -d` 16 | 17 | The above command will start the backend api by default running on http://localhost:8888 This is your base URL that is need to replace instead of Heroku URL. 18 | 19 | # Import API endpoints 20 | 21 | I have attached the postman collection for the app tutorial series. All the demonstrate endpoints will work as describe on video. 22 | 23 | Just a side note: If you did not find the precise api look for v1 inside the collection. 24 | 25 | Hope the above will support to continue this tutorial series. 26 | -------------------------------------------------------------------------------- /src/components/TextField.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { 3 | View, 4 | TextInput, 5 | StyleSheet, 6 | TouchableOpacity, 7 | Image, 8 | } from 'react-native'; 9 | 10 | interface TextFieldProps { 11 | placeholder: string; 12 | isSecure?: boolean; 13 | onTextChange: Function; 14 | } 15 | 16 | export const TextField: React.FC = ({ 17 | placeholder, 18 | isSecure = false, 19 | onTextChange, 20 | }) => { 21 | const [isPassword, setIsPassword] = useState(false); 22 | 23 | useEffect(() => { 24 | setIsPassword(isSecure); 25 | }, []); 26 | 27 | return ( 28 | 29 | onTextChange(text)} 34 | style={styles.textField} 35 | /> 36 | 37 | ); 38 | }; 39 | 40 | const styles = StyleSheet.create({ 41 | container: { 42 | flexDirection: 'row', 43 | height: 50, 44 | borderRadius: 30, 45 | backgroundColor: '#DBDBDB', 46 | justifyContent: 'center', 47 | alignItems: 'center', 48 | margin: 10, 49 | marginLeft: 30, 50 | marginRight: 30, 51 | paddingLeft: 20, 52 | paddingRight: 10, 53 | 54 | }, 55 | textField: { 56 | flex: 1, 57 | height: 50, 58 | fontSize: 20, 59 | color: '#000', 60 | }, 61 | }); 62 | -------------------------------------------------------------------------------- /src/redux/models/index.ts: -------------------------------------------------------------------------------- 1 | export interface Address { 2 | city: string; 3 | district: string; 4 | streetNumber: string; 5 | street: string; 6 | country: string; 7 | postalCode: string; 8 | } 9 | 10 | // category 11 | export interface Category { 12 | id: string; 13 | title: String; 14 | icon: String; 15 | } 16 | 17 | // Food Model 18 | export interface FoodModel { 19 | _id: string; 20 | name: string; 21 | description: string; 22 | category: string; 23 | price: number; 24 | readyTime: number; 25 | images: [string]; 26 | unit: number; 27 | } 28 | 29 | //Restaurant Model 30 | export interface Restaurant { 31 | _id: string; 32 | name: string; 33 | foodType: string; 34 | address: string; 35 | phone: string; 36 | images: string; 37 | foods: [FoodModel]; 38 | } 39 | 40 | export interface FoodAvailability { 41 | categories: [Category]; 42 | restaurants: [Restaurant]; 43 | foods: [FoodModel]; 44 | } 45 | 46 | //todo : Modify later 47 | //User Model 48 | export interface UserModel { 49 | firstName: string; 50 | lastName: String; 51 | contactNumber: String; 52 | token: string; 53 | varified: boolean; 54 | } 55 | 56 | export interface UserState { 57 | user: UserModel; 58 | location: Address; 59 | error: string | undefined; 60 | Cart: [FoodModel]; 61 | //orders 62 | } 63 | 64 | export interface ShoppingState { 65 | availability: FoodAvailability; 66 | availableFoods: [FoodModel]; 67 | //other models 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "@react-native-async-storage/async-storage": "1.17.11", 12 | "@react-native-community/masked-view": "0.1.10", 13 | "@types/react-redux": "7.1.9", 14 | "@types/redux-thunk": "2.1.0", 15 | "axios": "0.20.0", 16 | "expo": "48.0.17", 17 | "expo-location": "15.1.1", 18 | "expo-status-bar": "1.4.4", 19 | "moment": "2.28.0", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "react-native": "0.71.8", 23 | "react-native-gesture-handler": "2.9.0", 24 | "react-native-reanimated": "2.14.4", 25 | "react-native-safe-area-context": "4.5.0", 26 | "react-native-screens": "3.20.0", 27 | "react-native-web": "0.18.10", 28 | "react-navigation": "4.4.0", 29 | "react-navigation-stack": "2.8.2", 30 | "react-navigation-tabs": "2.9.0", 31 | "react-redux": "7.2.1", 32 | "redux": "4.0.5", 33 | "redux-thunk": "2.3.0", 34 | "yarn": "^1.22.19" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "7.22.1", 38 | "@types/react": "18.0.27", 39 | "@types/react-native": "0.72.2", 40 | "typescript": "4.9.4" 41 | }, 42 | "private": true, 43 | "resolutions": { 44 | "@types/react": "17.0.14", 45 | "@types/react-dom": "17.0.14" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/ButtonWithTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image, ImageSourcePropType } from 'react-native' 3 | 4 | interface ButtonProps{ 5 | onTap: Function; 6 | width: number; 7 | height: number; 8 | title: string, 9 | isNoBg?: boolean 10 | } 11 | 12 | const ButtonWithTitle: React.FC = ({ onTap, width, height, title, isNoBg = false }) => { 13 | 14 | if(isNoBg){ 15 | return ( 16 | onTap()} 18 | > 19 | {title} 20 | 21 | ) 22 | }else{ 23 | 24 | return ( 25 | onTap()} 27 | > 28 | {title} 29 | 30 | ) 31 | 32 | 33 | } 34 | 35 | } 36 | 37 | 38 | const styles = StyleSheet.create({ 39 | btn: { 40 | flex: 1, 41 | display: 'flex', 42 | maxHeight: 50, 43 | justifyContent: 'center', 44 | alignItems: 'center', 45 | backgroundColor: '#f15b5d', 46 | borderRadius:30, 47 | alignSelf: 'center', 48 | marginTop: 20, 49 | }, 50 | 51 | }) 52 | 53 | export { ButtonWithTitle } -------------------------------------------------------------------------------- /src/redux/reducers/userReducer.ts: -------------------------------------------------------------------------------- 1 | import { UserAction } from "../actions"; 2 | import { UserModel, UserState, FoodModel, Address } from "../models"; 3 | 4 | const initialState: UserState = { 5 | user: {} as UserModel, 6 | location: {} as Address, 7 | error: undefined, 8 | Cart: {} as [FoodModel], 9 | }; 10 | 11 | const UserReducer = (state: UserState = initialState, action: UserAction) => { 12 | const { type, payload } = action; 13 | 14 | switch (type) { 15 | case "ON_UPDATE_LOCATION": 16 | return { 17 | ...state, 18 | location: payload, 19 | }; 20 | case "ON_UPDATE_CART": 21 | if (!Array.isArray(state.Cart)) { 22 | return { 23 | ...state, 24 | Cart: [action.payload], 25 | }; 26 | } 27 | 28 | const existingFoods = state.Cart.filter( 29 | (item) => item._id == action.payload._id 30 | ); 31 | 32 | //Check for Existing Product to update unit 33 | if (existingFoods.length > 0) { 34 | let updatedCart = state.Cart.map((food) => { 35 | if (food._id == action.payload._id) { 36 | food.unit = action.payload.unit; 37 | } 38 | return food; 39 | }); 40 | 41 | return { 42 | ...state, 43 | Cart: updatedCart.filter((item) => item.unit > 0), 44 | }; 45 | } else { 46 | // Add to cart if not added 47 | return { 48 | ...state, 49 | Cart: [...state.Cart, action.payload], 50 | }; 51 | } 52 | case "ON_USER_LOGIN": 53 | console.log("User Token" + action.payload); 54 | 55 | default: 56 | return state; 57 | } 58 | }; 59 | 60 | export { UserReducer }; 61 | -------------------------------------------------------------------------------- /src/components/SearchBar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image } from 'react-native' 3 | 4 | interface SearchBarProps{ 5 | onEndEditing?: any | undefined; 6 | didTouch?: any | undefined; 7 | autoFocus?: boolean | undefined; 8 | onTextChange: Function; 9 | } 10 | 11 | 12 | const SearchBar: React.FC = ({ onEndEditing, didTouch, autoFocus = false, onTextChange }) => { 13 | 14 | 15 | 16 | return ( 17 | 18 | 19 | onTextChange(text)} 25 | onEndEditing={onEndEditing} 26 | /> 27 | 28 | 29 | 30 | )} 31 | 32 | 33 | const styles = StyleSheet.create({ 34 | container: { 35 | flex: 1, 36 | height: 60, 37 | display: 'flex', 38 | flexDirection: 'row', 39 | justifyContent: 'space-between', 40 | alignContent: 'center', 41 | alignItems: 'center', 42 | paddingLeft: 20, 43 | paddingRight: 20 44 | }, 45 | searchBar: { 46 | display: 'flex', 47 | height: 32, 48 | flex: 1, 49 | flexDirection: 'row', 50 | justifyContent: 'space-between', 51 | backgroundColor: '#ededed', 52 | alignItems: 'center', 53 | borderRadius: 20, 54 | paddingLeft: 10, 55 | paddingRight: 10, 56 | borderColor: '#E5E5E5', 57 | borderWidth: 2 58 | } 59 | 60 | }) 61 | 62 | export { SearchBar } -------------------------------------------------------------------------------- /src/components/ButtonAddRemove.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image } from 'react-native' 3 | 4 | interface ButtonProps{ 5 | onAdd: Function; 6 | qty: number; 7 | onRemove: Function; 8 | } 9 | 10 | 11 | const ButtonAddRemove: React.FC = ({ onAdd, qty, onRemove }) => { 12 | 13 | if(qty > 0){ 14 | return ( 15 | 16 | onAdd()}> 17 | + 18 | 19 | 20 | {qty} 21 | 22 | onRemove()}> 23 | - 24 | 25 | 26 | 27 | 28 | ) 29 | }else{ 30 | return ( 31 | onAdd()}> 32 | Add 33 | 34 | ) 35 | } 36 | 37 | } 38 | 39 | 40 | const styles = StyleSheet.create({ 41 | 42 | btn: { 43 | display: 'flex', 44 | justifyContent: 'center', 45 | alignItems: 'center', width: 80, 46 | height: 40, 47 | alignSelf: 'center', 48 | borderRadius: 30, 49 | backgroundColor: '#f15b5b'}, 50 | 51 | btnPlusMinus: { 52 | display: 'flex', 53 | justifyContent: 'center', 54 | alignItems: 'center', 55 | borderRadius: 10, 56 | borderWidth: 0.5, 57 | borderColor: '#f15b5d', 58 | height: 58, 59 | width: 38}, 60 | 61 | optionsView: { 62 | display: 'flex', 63 | flexDirection: 'row', 64 | justifyContent: 'space-between', 65 | alignItems: 'center', flex: 1}, 66 | 67 | }) 68 | 69 | 70 | export { ButtonAddRemove } -------------------------------------------------------------------------------- /src/components/FoodCartInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image, Dimensions } from 'react-native' 3 | import { FoodModel } from '../redux' 4 | import { ButtonAddRemove } from './ButtonAddRemove' 5 | 6 | interface FoodCardInfoProps{ 7 | item: FoodModel; 8 | onUpdateCart: Function; 9 | } 10 | 11 | const FoodCardInfo: React.FC = ({ item, onUpdateCart }) => { 12 | 13 | const didUpdateCart = (unit: number) => { 14 | item.unit = unit; 15 | onUpdateCart(item); 16 | } 17 | 18 | return ( 19 | 21 | 22 | {item.name} 23 | {item.category} 24 | {item.description} 25 | 26 | 27 | ₹{item.price} 28 | { 30 | let unit = isNaN(item.unit) ? 0 : item.unit; 31 | didUpdateCart( unit + 1); 32 | }} 33 | onRemove={() => { 34 | let unit = isNaN(item.unit) ? 0 : item.unit; 35 | didUpdateCart( unit > 0 ? unit - 1 : unit); 36 | }} 37 | qty={item.unit} /> 38 | 39 | 40 | 41 | )} 42 | 43 | 44 | const styles = StyleSheet.create({ 45 | container: { 46 | display: 'flex', 47 | flex: 1, 48 | width: Dimensions.get('screen').width - 20, 49 | margin: 10, 50 | borderRadius: 20, 51 | backgroundColor: '#FFF', 52 | height: 100, 53 | justifyContent: 'flex-start', 54 | borderWidth: 1, 55 | borderColor: '#E5E5E5', 56 | flexDirection: 'row' 57 | 58 | 59 | 60 | }, 61 | navigation: { flex: 2, backgroundColor: 'red' }, 62 | body: { flex: 10, justifyContent: 'center', alignItems: 'center', backgroundColor: 'yellow' }, 63 | footer: { flex: 1, backgroundColor: 'cyan' } 64 | }) 65 | 66 | export { FoodCardInfo } -------------------------------------------------------------------------------- /src/components/FoodCard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image, Dimensions } from 'react-native' 3 | import { FoodModel } from '../redux' 4 | import { ButtonAddRemove } from './ButtonAddRemove' 5 | 6 | interface FoodCardProps{ 7 | item: FoodModel; 8 | onTap: Function; 9 | onUpdateCart: Function; 10 | } 11 | 12 | const FoodCard: React.FC = ({ item, onTap, onUpdateCart }) => { 13 | 14 | 15 | const didUpdateCart = (unit: number) => { 16 | 17 | item.unit = unit; 18 | onUpdateCart(item); 19 | } 20 | 21 | return ( 22 | 23 | 24 | onTap(item)} 25 | style={{ display: 'flex', flex: 1, flexDirection: 'row', justifyContent: 'space-around' }}> 26 | 27 | {item.name} 28 | {item.category} 29 | 30 | 31 | ₹{item.price} 32 | { 34 | let unit = isNaN(item.unit) ? 0 : item.unit; 35 | didUpdateCart( unit + 1); 36 | }} 37 | onRemove={() => { 38 | let unit = isNaN(item.unit) ? 0 : item.unit; 39 | didUpdateCart( unit > 0 ? unit - 1 : unit); 40 | }} 41 | qty={item.unit} /> 42 | 43 | 44 | 45 | )} 46 | 47 | 48 | const styles = StyleSheet.create({ 49 | container: { 50 | display: 'flex', 51 | flex: 1, 52 | width: Dimensions.get('screen').width - 20, 53 | margin: 10, 54 | borderRadius: 20, 55 | backgroundColor: '#FFF', 56 | height: 100, 57 | justifyContent: 'flex-start', 58 | borderWidth: 1, 59 | borderColor: '#E5E5E5', 60 | flexDirection: 'row' 61 | 62 | 63 | 64 | }, 65 | navigation: { flex: 2, backgroundColor: 'red' }, 66 | body: { flex: 10, justifyContent: 'center', alignItems: 'center', backgroundColor: 'yellow' }, 67 | footer: { flex: 1, backgroundColor: 'cyan' } 68 | }) 69 | 70 | export { FoodCard } -------------------------------------------------------------------------------- /src/redux/actions/shoppingAction.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Address } from 'expo-location' 3 | import { Dispatch } from 'react' 4 | import { BASE_URL } from '../../utils' 5 | import { FoodAvailability, FoodModel } from '../models' 6 | 7 | 8 | //availability Action 9 | 10 | export interface AvailabilityAction{ 11 | readonly type: 'ON_AVAILABILITY', 12 | payload: FoodAvailability 13 | } 14 | 15 | 16 | export interface FoodSearchAction{ 17 | readonly type: 'ON_FOODS_SEARCH', 18 | payload: [FoodModel] 19 | } 20 | 21 | 22 | export interface ShoppingErrorAction{ 23 | readonly type: 'ON_SHOPPING_ERROR', 24 | payload: any 25 | } 26 | 27 | 28 | 29 | export type ShoppingAction = AvailabilityAction | ShoppingErrorAction | FoodSearchAction 30 | 31 | 32 | //Trigger actions from components 33 | export const onAvailability = (postCode: string) => { 34 | 35 | 36 | 37 | return async ( dispatch: Dispatch) => { 38 | 39 | try { 40 | 41 | const response = await axios.get(`${BASE_URL}food/availability/${postCode}`) 42 | 43 | 44 | if(!response){ 45 | dispatch({ 46 | type: 'ON_SHOPPING_ERROR', 47 | payload: 'Availability error' 48 | }) 49 | }else{ 50 | // save our location in local storage 51 | dispatch({ 52 | type: 'ON_AVAILABILITY', 53 | payload: response.data 54 | }) 55 | } 56 | 57 | 58 | } catch (error) { 59 | dispatch({ 60 | type: 'ON_SHOPPING_ERROR', 61 | payload: error 62 | }) 63 | } 64 | 65 | } 66 | 67 | } 68 | 69 | 70 | 71 | //Trigger actions from components 72 | export const onSearchFoods = (postCode: string) => { 73 | 74 | 75 | return async ( dispatch: Dispatch) => { 76 | 77 | try { 78 | 79 | const response = await axios.get<[FoodModel]>(`${BASE_URL}food/search/${postCode}`) 80 | 81 | console.log(response) 82 | 83 | if(!response){ 84 | dispatch({ 85 | type: 'ON_SHOPPING_ERROR', 86 | payload: 'Availability error' 87 | }) 88 | }else{ 89 | // save our location in local storage 90 | dispatch({ 91 | type: 'ON_FOODS_SEARCH', 92 | payload: response.data 93 | }) 94 | } 95 | 96 | 97 | } catch (error) { 98 | dispatch({ 99 | type: 'ON_SHOPPING_ERROR', 100 | payload: error 101 | }) 102 | } 103 | 104 | } 105 | 106 | } 107 | 108 | 109 | -------------------------------------------------------------------------------- /src/screens/FoodDetailScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image, ImageBackground, Dimensions } from 'react-native' 3 | import { FlatList } from 'react-native-gesture-handler'; 4 | import { ButtonWithIcon, FoodCard } from '../components'; 5 | import { ApplicationState, FoodModel, onUpdateCart, UserState } from '../redux'; 6 | import { connect } from 'react-redux'; 7 | 8 | import { useNavigation, checkExistence } from '../utils' 9 | 10 | 11 | interface FoodDetailProps{ 12 | onUpdateCart: Function, 13 | navigation: { getParam: Function, goBack: Function} 14 | userReducer: UserState, 15 | } 16 | 17 | const _FoodDetailScreen: React.FC = (props) => { 18 | 19 | const { getParam, goBack } = props.navigation; 20 | 21 | const food = getParam('food') as FoodModel 22 | 23 | const { navigate } = useNavigation() 24 | 25 | const { Cart } = props.userReducer; 26 | 27 | 28 | return ( 29 | 30 | goBack()} width={42} height={42} /> 31 | {food.name} 32 | 33 | 34 | 37 | 38 | 39 | {food.name} 40 | {food.category} 41 | 42 | 43 | 44 | 45 | Food Will be ready within {food.readyTime} Minite(s) 46 | {food.description} 47 | 48 | 49 | {}} onUpdateCart={props.onUpdateCart} /> 50 | 51 | 52 | 53 | )} 54 | 55 | 56 | const styles = StyleSheet.create({ 57 | container: { flex: 1, backgroundColor: '#F2F2F2'}, 58 | navigation: { flex: 1, marginTop: 43, paddingLeft: 10, flexDirection: 'row', alignItems: 'center' }, 59 | body: { flex: 10, justifyContent: 'flex-start', alignItems: 'center', backgroundColor: '#FFF', paddingBottom: 160 }, 60 | footer: { flex: 1, backgroundColor: 'cyan' } 61 | }) 62 | 63 | 64 | const mapToStateProps = (state: ApplicationState) => ({ 65 | userReducer: state.userReducer, 66 | }) 67 | 68 | const FoodDetailScreen = connect(mapToStateProps, { onUpdateCart })(_FoodDetailScreen) 69 | 70 | 71 | export { FoodDetailScreen } -------------------------------------------------------------------------------- /src/screens/SearchScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image } from 'react-native' 3 | import { connect } from 'react-redux' 4 | import { ApplicationState, FoodModel, ShoppingState, onUpdateCart, UserState } from '../redux' 5 | 6 | import { ButtonWithIcon, FoodCard, SearchBar } from '../components' 7 | import { FlatList } from 'react-native-gesture-handler' 8 | 9 | 10 | import { checkExistence, useNavigation } from '../utils' 11 | 12 | interface SearchScreenProps{ 13 | userReducer: UserState, 14 | shoppingReducer: ShoppingState, 15 | onUpdateCart: Function, 16 | } 17 | 18 | 19 | const _SearchScreen: React.FC = (props) => { 20 | 21 | const { navigate } = useNavigation() 22 | 23 | const [isEditing, setIsEditing] = useState(false) 24 | const [keyword, setKeyword] = useState('') 25 | 26 | const { availableFoods } = props.shoppingReducer; 27 | 28 | const onTapFood = (item: FoodModel) => { 29 | navigate('FoodDetailPage', { food: item}) 30 | } 31 | 32 | const { Cart } = props.userReducer; 33 | 34 | return ( 35 | 36 | 37 | navigate("HomePage")} width={40} height={50} /> 38 | setIsEditing(false)} didTouch={() => setIsEditing(true)}/> 39 | 40 | 41 | 42 | 43 | { 49 | return item.name.includes(keyword) 50 | }) 51 | : availableFoods 52 | } 53 | renderItem={({ item}) => } 54 | keyExtractor={(item) => `${item._id}`} 55 | /> 56 | 57 | 58 | )} 59 | 60 | 61 | const styles = StyleSheet.create({ 62 | container: { flex: 1, backgroundColor: '#F2F2F2'}, 63 | navigation: { flex: 1, marginTop: 43, }, 64 | body: { flex: 10, justifyContent: 'center', alignItems: 'center' }, 65 | footer: { flex: 1, backgroundColor: 'cyan' } 66 | }) 67 | 68 | 69 | 70 | const mapStateToProps = (state: ApplicationState) => ({ 71 | shoppingReducer: state.shoppingReducer, 72 | userReducer: state.userReducer 73 | }) 74 | 75 | 76 | const SearchScreen = connect(mapStateToProps, {onUpdateCart})(_SearchScreen) 77 | 78 | export { SearchScreen } -------------------------------------------------------------------------------- /src/screens/RestaurantScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image, ImageBackground, Dimensions } from 'react-native' 3 | import { FlatList } from 'react-native-gesture-handler'; 4 | import { ButtonWithIcon, FoodCard } from '../components'; 5 | 6 | import { connect } from 'react-redux'; 7 | 8 | import { Restaurant, FoodModel, onUpdateCart, ApplicationState, UserState } from '../redux'; 9 | 10 | import { useNavigation, checkExistence } from '../utils' 11 | 12 | 13 | interface RestaurantProps{ 14 | userReducer: UserState, 15 | navigation: { getParam: Function, goBack: Function}, 16 | onUpdateCart: Function, 17 | } 18 | 19 | const _RestaurantScreen: React.FC = (props) => { 20 | 21 | const { getParam, goBack } = props.navigation; 22 | 23 | const restaurant = getParam('restaurant') as Restaurant 24 | 25 | const { navigate } = useNavigation() 26 | 27 | 28 | const { Cart } = props.userReducer; 29 | 30 | console.log(Cart); 31 | 32 | const onTapFood = (item: FoodModel) => { 33 | navigate('FoodDetailPage', { food: item}) 34 | } 35 | 36 | 37 | return ( 38 | 39 | goBack()} width={42} height={42} /> 40 | {restaurant.name} 41 | 42 | 43 | 46 | 47 | 48 | {restaurant.name} 49 | {restaurant.address} { restaurant.phone} 50 | 51 | 52 | 53 | } 57 | keyExtractor={(item) => `${item._id}`} 58 | /> 59 | 60 | 61 | )} 62 | 63 | 64 | const styles = StyleSheet.create({ 65 | container: { flex: 1, backgroundColor: '#F2F2F2'}, 66 | navigation: { flex: 1, marginTop: 43, paddingLeft: 10, flexDirection: 'row', alignItems: 'center' }, 67 | body: { flex: 11, justifyContent: 'flex-start', alignItems: 'center', backgroundColor: '#FFF', }, 68 | footer: { flex: 1, backgroundColor: 'cyan' } 69 | }) 70 | 71 | 72 | const mapToStateProps = (state: ApplicationState) => ({ 73 | userReducer: state.userReducer, 74 | }) 75 | 76 | const RestaurantScreen = connect(mapToStateProps, { onUpdateCart })(_RestaurantScreen) 77 | 78 | export { RestaurantScreen } -------------------------------------------------------------------------------- /src/screens/LoginScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { 3 | StyleSheet, 4 | View, 5 | Text, 6 | TouchableOpacity, 7 | TextInput, 8 | Image, 9 | } from "react-native"; 10 | import { TextField } from "../components"; 11 | import { ButtonWithTitle } from "../components/ButtonWithTitle"; 12 | import { connect } from "react-redux"; 13 | import { 14 | ApplicationState, 15 | OnUserLogin, 16 | OnUserSignup, 17 | UserState, 18 | } from "../redux"; 19 | 20 | interface LoginProps { 21 | OnUserLogin: Function; 22 | OnUserSignup: Function; 23 | userReducer: UserState; 24 | } 25 | 26 | const _LoginScreen: React.FC = ({ 27 | OnUserLogin, 28 | OnUserSignup, 29 | userReducer, 30 | }) => { 31 | const [email, setEmail] = useState(""); 32 | const [phone, setPhone] = useState(""); 33 | const [password, setPassword] = useState(""); 34 | const [title, setTitle] = useState("Login"); 35 | const [isSignup, setIsSignup] = useState(false); 36 | 37 | const onTapAuthenticate = () => { 38 | if (isSignup) { 39 | OnUserSignup(email, phone, password); 40 | } else { 41 | OnUserLogin(email, password); 42 | } 43 | }; 44 | 45 | const onTapOptions = () => { 46 | setIsSignup(!isSignup); 47 | setTitle(!isSignup ? "Signup" : "Login"); 48 | }; 49 | 50 | return ( 51 | 52 | 53 | {title} 54 | 55 | 56 | 61 | 62 | {isSignup && ( 63 | 68 | )} 69 | 74 | 75 | 81 | 82 | 93 | 94 | 95 | 96 | ); 97 | }; 98 | 99 | const styles = StyleSheet.create({ 100 | container: { flex: 1 }, 101 | navigation: { flex: 3, justifyContent: "center", paddingLeft: 30 }, 102 | body: { flex: 6, justifyContent: "center", alignItems: "center" }, 103 | footer: { flex: 3 }, 104 | }); 105 | 106 | const mapStateToProps = (state: ApplicationState) => ({ 107 | shoppingReducer: state.shoppingReducer, 108 | userReducer: state.userReducer, 109 | }); 110 | 111 | const LoginScreen = connect(mapStateToProps, { OnUserLogin, OnUserSignup })( 112 | _LoginScreen 113 | ); 114 | 115 | export { LoginScreen }; 116 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## Online Food Order App 2 | 3 |
4 |
5 | 6 |

7 | Indulge in the ultimate journey of mastering the art of Food Delivery Apps with our comprehensive course, packed with practical insights and hands-on learning. Dive deep into the intricacies of building your very own Food Delivery App, also known as the Online Food Order App, from inception to execution. 8 | 9 | ### Here's what awaits you in this enriching course: 10 | 11 | - Embark on your React Native journey for iOS and Android platforms through a hands-on demonstration. 12 | - Craft a seamless food order app from the ground up, covering every aspect from user signup to order placement and payment processing. 13 | - Gain profound insights into the inner workings of various cuisines and restaurant functionalities within the app ecosystem. 14 | - Explore the mechanics behind an efficient Cart system, ensuring a smooth user experience. 15 | - Master the art of collecting payments seamlessly using the Stripe payment gateway. 16 | - Delve into the architecture and communication dynamics of the backend system, providing a holistic understanding of the app's infrastructure. 17 | - Decode the backend code intricacies, unraveling its functionality and significance. 18 | - Elevate your skills with expert tips and tricks to propel your journey forward in the world of app development. 19 | 20 | Join us on this exhilarating voyage as we equip you with the tools and knowledge to excel in the realm of Food Delivery Apps. Let's embark on this journey together towards app development mastery! 21 | 22 | ## Backend Section: 23 | 24 | You can spin the backend server using Docker Kindly folllow the instructions provided in the backend documentation. 25 | 26 | [Spin Backend Local Server](https://github.com/codergogoi/Online_Food_Order_App/tree/master/backend) 27 | 28 | #### Backend Repositories: 29 | 30 | - [JS Version Backend to support Tutorial](https://github.com/codergogoi/Food_Order_Backend.git) 31 | 32 | - [TS Version With Some additional features](https://github.com/codergogoi/NodeJS_Online_Food_Order_Backend_TypeScript_Youtube.git) 33 | - [Stripe Server to collect Payment](https://github.com/codergogoi/Online_Food_Order_App/blob/master/stripe_server.zip) 34 | 35 | ### Postman Collection Link: 36 | 37 | https://documenter.getpostman.com/view/8734310/Szt5fBTP?version=latest 38 | 39 | ## How to get this course: 40 | 41 | ### Option 1: [CodEpisodes.com](https://www.codepisodes.com/) 42 | 43 | Our E-Learning Partner where you will get the complete course along with the Backend System Overview and system design walkthrough to how to build the complete food delivery platform. 44 | 45 | ### Option 2: [Youtube](https://www.youtube.com/@codewithjay) 46 | 47 | If you have enrolled for our [Premium Content Membership](https://www.youtube.com/@codewithjay/membership) $10 Monthly in Youtube Channel You will get the Mobile Application part without Backend. 48 | 49 | ### Legal: 50 | 51 | You are only allowed to Fork this repository and clone it for only learning purpose with credit to original repo. If we found redistributing any of the material of this tutorial series. My Lawyer will handle it with a penalty as per international copyright law. 52 | 53 | Note: Several persons have already been fined with penalties for violating the copyright rules. 54 | -------------------------------------------------------------------------------- /src/redux/actions/userActions.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { Dispatch } from "react"; 3 | import { BASE_URL } from "../../utils"; 4 | import AsyncStorage from "@react-native-async-storage/async-storage"; 5 | import { Address, FoodModel } from "../models"; 6 | 7 | export interface UpdateLocationAction { 8 | readonly type: "ON_UPDATE_LOCATION"; 9 | payload: Address; 10 | } 11 | 12 | export interface UserErrorAction { 13 | readonly type: "ON_USER_ERROR"; 14 | payload: any; 15 | } 16 | 17 | export interface UpdateCartAction { 18 | readonly type: "ON_UPDATE_CART"; 19 | payload: FoodModel; 20 | } 21 | 22 | export interface UserLoginAction { 23 | readonly type: "ON_USER_LOGIN"; 24 | payload: string; 25 | } 26 | 27 | export type UserAction = 28 | | UpdateLocationAction 29 | | UserErrorAction 30 | | UpdateCartAction 31 | | UserLoginAction; 32 | 33 | // User Actions trigger from Components 34 | 35 | export const onUpdateLocation = (location: Address) => { 36 | return async (dispatch: Dispatch) => { 37 | try { 38 | const locationString = JSON.stringify(location); 39 | await AsyncStorage.setItem("user_location", locationString); 40 | // save our location in local storage 41 | dispatch({ 42 | type: "ON_UPDATE_LOCATION", 43 | payload: location, 44 | }); 45 | } catch (error) { 46 | dispatch({ 47 | type: "ON_USER_ERROR", 48 | payload: error, 49 | }); 50 | } 51 | }; 52 | }; 53 | 54 | export const onUpdateCart = (item: FoodModel) => { 55 | return async (dispatch: Dispatch) => { 56 | dispatch({ 57 | type: "ON_UPDATE_CART", 58 | payload: item, 59 | }); 60 | }; 61 | }; 62 | 63 | export const OnUserLogin = (email: string, password: string) => { 64 | return async (dispatch: Dispatch) => { 65 | try { 66 | const response = await axios.post(`${BASE_URL}user/login`, { 67 | email, 68 | password, 69 | }); 70 | 71 | console.log(response); 72 | 73 | if (!response) { 74 | dispatch({ 75 | type: "ON_USER_ERROR", 76 | payload: "Login Error", 77 | }); 78 | } else { 79 | dispatch({ 80 | type: "ON_USER_LOGIN", 81 | payload: response.data, 82 | }); 83 | } 84 | } catch (error) { 85 | dispatch({ 86 | type: "ON_USER_ERROR", 87 | payload: "Login Error", 88 | }); 89 | } 90 | }; 91 | }; 92 | 93 | export const OnUserSignup = ( 94 | email: string, 95 | phone: string, 96 | password: string 97 | ) => { 98 | return async (dispatch: Dispatch) => { 99 | try { 100 | const response = await axios.post(`${BASE_URL}user/signup`, { 101 | email, 102 | phone, 103 | password, 104 | }); 105 | 106 | console.log(response); 107 | 108 | if (!response) { 109 | dispatch({ 110 | type: "ON_USER_ERROR", 111 | payload: "Login Error", 112 | }); 113 | } else { 114 | dispatch({ 115 | type: "ON_USER_LOGIN", 116 | payload: response.data, 117 | }); 118 | } 119 | } catch (error) { 120 | console.log(error); 121 | dispatch({ 122 | type: "ON_USER_ERROR", 123 | payload: "Login Error", 124 | }); 125 | } 126 | }; 127 | }; 128 | -------------------------------------------------------------------------------- /src/screens/LandingScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useReducer, useEffect } from "react"; 2 | import { View, Text, StyleSheet, Dimensions, Image } from "react-native"; 3 | 4 | import * as Location from "expo-location"; 5 | 6 | import { connect } from "react-redux"; 7 | import { 8 | onUpdateLocation, 9 | UserState, 10 | ApplicationState, 11 | Address, 12 | } from "../redux"; 13 | 14 | import { useNavigation } from "../utils"; 15 | 16 | const screenWidth = Dimensions.get("screen").width; 17 | 18 | interface LandingProps { 19 | userReducer: UserState; 20 | onUpdateLocation: Function; 21 | } 22 | 23 | const _LandingScreen: React.FC = (props) => { 24 | const { userReducer, onUpdateLocation } = props; 25 | 26 | const { navigate } = useNavigation(); 27 | 28 | const [errorMsg, setErrorMsg] = useState(""); 29 | const [address, setAddress] = useState
(); 30 | 31 | const [displayAddress, setDisplayAddress] = useState( 32 | "Waiting for Current Location" 33 | ); 34 | 35 | useEffect(() => { 36 | (async () => { 37 | let { status } = await Location.requestForegroundPermissionsAsync(); 38 | 39 | if (status !== "granted") { 40 | setErrorMsg("Permission to access location is not granted"); 41 | } 42 | 43 | let location: any = await Location.getCurrentPositionAsync({}); 44 | 45 | const { coords } = location; 46 | 47 | if (coords) { 48 | const { latitude, longitude } = coords; 49 | 50 | let addressResponse: any = await Location.reverseGeocodeAsync({ 51 | latitude, 52 | longitude, 53 | }); 54 | 55 | for (let item of addressResponse) { 56 | setAddress(item); 57 | onUpdateLocation(item); 58 | let currentAddress = `${item.name},${item.street}, ${item.postalCode}, ${item.country}`; 59 | setDisplayAddress(currentAddress); 60 | 61 | if (currentAddress.length > 0) { 62 | setTimeout(() => { 63 | navigate("homeStack"); 64 | }, 2000); 65 | } 66 | 67 | return; 68 | } 69 | } else { 70 | //notify user something went wrong with location 71 | } 72 | })(); 73 | }, []); 74 | 75 | return ( 76 | 77 | 78 | 79 | 80 | 84 | 85 | Your Delivery Address 86 | 87 | {displayAddress} 88 | 89 | 90 | 91 | ); 92 | }; 93 | 94 | const styles = StyleSheet.create({ 95 | container: { 96 | flex: 1, 97 | backgroundColor: "rgba(242,242,242,1)", 98 | }, 99 | navigation: { 100 | flex: 2, 101 | }, 102 | body: { 103 | flex: 9, 104 | justifyContent: "center", 105 | alignItems: "center", 106 | }, 107 | deliveryIcon: { 108 | width: 120, 109 | height: 120, 110 | }, 111 | addressContainer: { 112 | width: screenWidth - 100, 113 | borderBottomColor: "red", 114 | borderBottomWidth: 0.5, 115 | padding: 5, 116 | marginBottom: 10, 117 | alignItems: "center", 118 | }, 119 | addressTitle: { 120 | fontSize: 22, 121 | fontWeight: "700", 122 | color: "#7D7D7D", 123 | }, 124 | addressText: { 125 | fontSize: 20, 126 | fontWeight: "200", 127 | color: "#4F4F4F", 128 | }, 129 | 130 | footer: { 131 | flex: 1, 132 | }, 133 | }); 134 | 135 | const mapToStateProps = (state: ApplicationState) => ({ 136 | userReducer: state.userReducer, 137 | }); 138 | 139 | const LandingScreen = connect(mapToStateProps, { onUpdateLocation })( 140 | _LandingScreen 141 | ); 142 | 143 | export { LandingScreen }; 144 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import { StatusBar } from "expo-status-bar"; 2 | import React from "react"; 3 | import { Image, StyleSheet, Text, View } from "react-native"; 4 | import { HomeScreen } from "./src/screens/HomeScreen"; 5 | import { LandingScreen } from "./src/screens/LandingScreen"; 6 | 7 | import { Provider } from "react-redux"; 8 | import { store } from "./src/redux"; 9 | 10 | import { createAppContainer, createSwitchNavigator } from "react-navigation"; 11 | import { createStackNavigator } from "react-navigation-stack"; 12 | import { createBottomTabNavigator } from "react-navigation-tabs"; 13 | import { SearchScreen } from "./src/screens/SearchScreen"; 14 | import { RestaurantScreen } from "./src/screens/RestaurantScreen"; 15 | import { FoodDetailScreen } from "./src/screens/FoodDetailScreen"; 16 | import { CartScreen } from "./src/screens/CartScreen"; 17 | import { LoginScreen } from "./src/screens/LoginScreen"; 18 | 19 | const switchNavigator = createSwitchNavigator({ 20 | landingStack: { 21 | screen: createStackNavigator( 22 | { 23 | Landing: LandingScreen, 24 | // search address screen 25 | }, 26 | { 27 | defaultNavigationOptions: { 28 | headerShown: false, 29 | }, 30 | } 31 | ), 32 | }, 33 | 34 | homeStack: createBottomTabNavigator({ 35 | // Home tab Icon 36 | home: { 37 | screen: createStackNavigator( 38 | { 39 | HomePage: HomeScreen, 40 | SearchPage: SearchScreen, 41 | RestaurantPage: RestaurantScreen, 42 | FoodDetailPage: FoodDetailScreen, 43 | }, 44 | { 45 | defaultNavigationOptions: { 46 | headerShown: false, 47 | }, 48 | } 49 | ), 50 | navigationOptions: { 51 | tabBarIcon: ({ focused, tintColor }) => { 52 | let icon = 53 | focused == true 54 | ? require("./src/images/home_icon.png") 55 | : require("./src/images/home_n_icon.png"); 56 | return ; 57 | }, 58 | }, 59 | }, 60 | 61 | // Home tab Icon 62 | Offer: { 63 | screen: createStackNavigator({ 64 | OfferPage: HomeScreen, // 65 | }), 66 | navigationOptions: { 67 | tabBarIcon: ({ focused, tintColor }) => { 68 | let icon = 69 | focused == true 70 | ? require("./src/images/offer_icon.png") 71 | : require("./src/images/offer_n_icon.png"); 72 | return ; 73 | }, 74 | }, 75 | }, 76 | 77 | // Home tab Icon 78 | Cart: { 79 | screen: createStackNavigator( 80 | { 81 | CartPage: CartScreen, 82 | LoginPage: LoginScreen, 83 | }, 84 | { 85 | defaultNavigationOptions: { 86 | headerShown: false, 87 | }, 88 | } 89 | ), 90 | navigationOptions: { 91 | tabBarIcon: ({ focused, tintColor }) => { 92 | let icon = 93 | focused == true 94 | ? require("./src/images/cart_icon.png") 95 | : require("./src/images/cart_n_icon.png"); 96 | return ; 97 | }, 98 | }, 99 | }, 100 | // Home tab Icon 101 | Account: { 102 | screen: createStackNavigator({ 103 | AccountPage: HomeScreen, 104 | LoginPage: LoginScreen, 105 | }), 106 | navigationOptions: { 107 | tabBarIcon: ({ focused, tintColor }) => { 108 | let icon = 109 | focused == true 110 | ? require("./src/images/account_icon.png") 111 | : require("./src/images/account_n_icon.png"); 112 | return ; 113 | }, 114 | }, 115 | }, 116 | }), 117 | }); 118 | 119 | const AppNavigation = createAppContainer(switchNavigator); 120 | 121 | export default function App() { 122 | return ( 123 | 124 | 125 | 126 | ); 127 | } 128 | 129 | const styles = StyleSheet.create({ 130 | tabIcon: { 131 | width: 30, 132 | height: 30, 133 | }, 134 | }); 135 | -------------------------------------------------------------------------------- /src/screens/CartScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from 'react' 2 | import { StyleSheet, View, Text, TouchableOpacity, TextInput, Image } from 'react-native' 3 | import { connect } from 'react-redux' 4 | import { ApplicationState, FoodModel, ShoppingState, onUpdateCart, UserState } from '../redux' 5 | 6 | import { ButtonWithIcon, FoodCard, FoodCardInfo, SearchBar } from '../components' 7 | import { FlatList } from 'react-native-gesture-handler' 8 | 9 | 10 | import { checkExistence, useNavigation } from '../utils' 11 | import { ButtonWithTitle } from '../components/ButtonWithTitle' 12 | 13 | interface CartScreenProps{ 14 | userReducer: UserState, 15 | shoppingReducer: ShoppingState, 16 | onUpdateCart: Function, 17 | } 18 | 19 | 20 | const _CartScreen: React.FC = (props) => { 21 | 22 | const { navigate } = useNavigation() 23 | 24 | const [totalAmount, setTotalAmount] = useState(0); 25 | 26 | const { Cart, user } = props.userReducer; 27 | 28 | 29 | useEffect(() => { 30 | onCalculateAmount() 31 | },[Cart]); 32 | 33 | 34 | const onCalculateAmount = () => { 35 | 36 | let total = 0 37 | if(Array.isArray(Cart)){ 38 | Cart.map(food => { 39 | total += food.price * food.unit 40 | }) 41 | } 42 | 43 | setTotalAmount(total); 44 | } 45 | 46 | const onValidateOrder = () => { 47 | 48 | navigate('LoginPage'); 49 | 50 | } 51 | 52 | 53 | 54 | 55 | if(Cart.length > 0){ 56 | 57 | return ( 58 | 59 | 60 | My Cart 61 | { 64 | navigate("Order"); 65 | }} 66 | > 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | } 79 | keyExtractor={(item) => `${item._id}`} 80 | /> 81 | 82 | 83 | 84 | 85 | Total 86 | 87 | ₹ {totalAmount} 88 | 89 | 90 | 96 | 97 | ) 98 | 99 | }else{ 100 | 101 | return 102 | 103 | Your Cart is Empty!! 104 | 105 | 106 | 107 | } 108 | 109 | 110 | } 111 | 112 | 113 | const styles = StyleSheet.create({ 114 | container: { flex: 1, backgroundColor: '#F2F2F2'}, 115 | navigation: { flex: 1, marginTop: 43, }, 116 | body: { flex: 9, justifyContent: 'center', alignItems: 'center' }, 117 | footer: { flex: 2, justifyContent: 'center', paddingLeft: 10, paddingRight: 10 }, 118 | amountDetails: { 119 | flexDirection: "row", 120 | justifyContent: "space-between", 121 | padding: 10, 122 | margin: 5, 123 | }, 124 | }) 125 | 126 | 127 | 128 | const mapStateToProps = (state: ApplicationState) => ({ 129 | shoppingReducer: state.shoppingReducer, 130 | userReducer: state.userReducer 131 | }) 132 | 133 | 134 | const CartScreen = connect(mapStateToProps, {onUpdateCart})(_CartScreen) 135 | 136 | export { CartScreen } -------------------------------------------------------------------------------- /src/screens/HomeScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState} from 'react' 2 | import { View, Text, StyleSheet, Dimensions , Image } from 'react-native' 3 | import { FlatList, ScrollView } from 'react-native-gesture-handler' 4 | 5 | import { useNavigation } from '../utils' 6 | 7 | import { connect } from 'react-redux' 8 | import { ButtonWithIcon, CategoryCard, SearchBar, RestaurantCard } from '../components' 9 | import { onAvailability, onSearchFoods ,UserState, ApplicationState, ShoppingState, Restaurant, FoodModel } from '../redux' 10 | 11 | interface HomeProps{ 12 | userReducer: UserState, 13 | shoppingReducer: ShoppingState, 14 | onAvailability: Function, 15 | onSearchFoods: Function 16 | } 17 | 18 | export const _HomeScreen: React.FC = (props) => { 19 | 20 | 21 | const { navigate } = useNavigation() 22 | 23 | 24 | const { location } = props.userReducer; 25 | const { availability } = props.shoppingReducer; 26 | 27 | const { categories, foods, restaurants } = availability 28 | 29 | 30 | useEffect(() => { 31 | props.onAvailability(location.postalCode) 32 | setTimeout(() => { 33 | props.onSearchFoods(location.postalCode) 34 | }, 1000 ) 35 | 36 | }, []) 37 | 38 | const onTapRestaurant = (item: Restaurant) => { 39 | navigate('RestaurantPage', { restaurant: item}) 40 | } 41 | 42 | const onTapFood = (item: FoodModel) => { 43 | navigate('FoodDetailPage', { food: item}) 44 | } 45 | 46 | 47 | 48 | return ( 49 | 50 | 51 | 52 | {`${location.name},${location.street},${location.city}`} 53 | Edit 54 | 55 | 56 | { 57 | navigate('SearchPage') 58 | }} onTextChange={() => {}} /> 59 | {}} icon={require('../images/hambar.png')} width={50} height={40} /> 60 | 61 | 62 | 63 | 64 | 65 | { alert('Category tapped') }} /> } 70 | keyExtractor={(item) => `${item.id}`} 71 | /> 72 | 73 | Top Restaurants 74 | 75 | } 80 | keyExtractor={(item) => `${item._id}`} 81 | /> 82 | 83 | 84 | 30 Minutes Foods 85 | 86 | } 91 | keyExtractor={(item) => `${item._id}`} 92 | /> 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | ) 101 | 102 | } 103 | 104 | 105 | const styles = StyleSheet.create({ 106 | 107 | container: { 108 | flex: 1, 109 | backgroundColor: '#FFF' 110 | }, 111 | navigation: { 112 | flex: 2, 113 | }, 114 | body: { 115 | flex: 10, 116 | justifyContent: 'center', 117 | alignItems: 'center', 118 | }, 119 | 120 | 121 | }) 122 | 123 | 124 | const mapToStateProps = (state: ApplicationState) => ({ 125 | userReducer: state.userReducer, 126 | shoppingReducer: state.shoppingReducer 127 | }) 128 | 129 | const HomeScreen = connect(mapToStateProps, { onAvailability, onSearchFoods })(_HomeScreen) 130 | 131 | export { HomeScreen } -------------------------------------------------------------------------------- /backend/Online_Food_Order_App_Backend_API.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "bfa18a70-897c-4d53-bd2e-5f3f48524a88", 4 | "name": "FoodOrders", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "8734310" 7 | }, 8 | "item": [ 9 | { 10 | "name": "Food Availability", 11 | "request": { 12 | "method": "GET", 13 | "header": [], 14 | "url": { 15 | "raw": "{{OFA_BASE}}/food/availability/400012", 16 | "host": [ 17 | "{{OFA_BASE}}" 18 | ], 19 | "path": [ 20 | "food", 21 | "availability", 22 | "400012" 23 | ] 24 | } 25 | }, 26 | "response": [] 27 | }, 28 | { 29 | "name": "Foods - Search", 30 | "protocolProfileBehavior": { 31 | "disableBodyPruning": true 32 | }, 33 | "request": { 34 | "method": "GET", 35 | "header": [], 36 | "body": { 37 | "mode": "raw", 38 | "raw": "", 39 | "options": { 40 | "raw": { 41 | "language": "json" 42 | } 43 | } 44 | }, 45 | "url": { 46 | "raw": "{{OFA_BASE}}/food/search/5698", 47 | "host": [ 48 | "{{OFA_BASE}}" 49 | ], 50 | "path": [ 51 | "food", 52 | "search", 53 | "5698" 54 | ] 55 | } 56 | }, 57 | "response": [] 58 | }, 59 | { 60 | "name": "Login V1", 61 | "request": { 62 | "method": "POST", 63 | "header": [], 64 | "body": { 65 | "mode": "raw", 66 | "raw": "{\n\t\"email\": \"cg@gmail.com\",\n\t\"password\": \"1234567\"\n}", 67 | "options": { 68 | "raw": { 69 | "language": "json" 70 | } 71 | } 72 | }, 73 | "url": { 74 | "raw": "{{OFA_BASE}}/user/login/v1", 75 | "host": [ 76 | "{{OFA_BASE}}" 77 | ], 78 | "path": [ 79 | "user", 80 | "login", 81 | "v1" 82 | ] 83 | } 84 | }, 85 | "response": [] 86 | }, 87 | { 88 | "name": "Login", 89 | "request": { 90 | "auth": { 91 | "type": "noauth" 92 | }, 93 | "method": "POST", 94 | "header": [], 95 | "body": { 96 | "mode": "raw", 97 | "raw": "{\n\t\"email\": \"gogoi1@gogoi.com\",\n\t\"password\": \"123456\"\n}", 98 | "options": { 99 | "raw": { 100 | "language": "json" 101 | } 102 | } 103 | }, 104 | "url": { 105 | "raw": "{{OFA_BASE}}/user/login", 106 | "host": [ 107 | "{{OFA_BASE}}" 108 | ], 109 | "path": [ 110 | "user", 111 | "login" 112 | ] 113 | } 114 | }, 115 | "response": [] 116 | }, 117 | { 118 | "name": "Signup v1", 119 | "request": { 120 | "method": "POST", 121 | "header": [], 122 | "body": { 123 | "mode": "raw", 124 | "raw": "{\n\t\"email\": \"gogoi2@gogoi.com\",\n\t\"password\": \"123456\",\n \"phone\": \"8989898989\"\n\t\n}", 125 | "options": { 126 | "raw": { 127 | "language": "json" 128 | } 129 | } 130 | }, 131 | "url": { 132 | "raw": "{{OFA_BASE}}/user/signup", 133 | "host": [ 134 | "{{OFA_BASE}}" 135 | ], 136 | "path": [ 137 | "user", 138 | "signup" 139 | ] 140 | } 141 | }, 142 | "response": [] 143 | }, 144 | { 145 | "name": "Signup", 146 | "request": { 147 | "method": "POST", 148 | "header": [], 149 | "body": { 150 | "mode": "raw", 151 | "raw": "{\n\t\"email\": \"gogoi6@gogoi.com\",\n\t\"password\": \"123456\",\n \"phone\": \"8989898989\"\n\t\n}", 152 | "options": { 153 | "raw": { 154 | "language": "json" 155 | } 156 | } 157 | }, 158 | "url": { 159 | "raw": "{{OFA_BASE}}/user/create-account", 160 | "host": [ 161 | "{{OFA_BASE}}" 162 | ], 163 | "path": [ 164 | "user", 165 | "create-account" 166 | ] 167 | } 168 | }, 169 | "response": [] 170 | }, 171 | { 172 | "name": "User Verify", 173 | "request": { 174 | "auth": { 175 | "type": "bearer", 176 | "bearer": [ 177 | { 178 | "key": "token", 179 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZmI5MjI1NDFkZDk1MjAwMjQ2Zjc1YjQiLCJlbWFpbCI6ImdnNEBjb2RlcmdvZ29pLmNvbSIsInZlcmlmaWVkIjpmYWxzZSwiaWF0IjoxNjA1OTY4NDk3LCJleHAiOjE2MTM3NDQ0OTd9.0WIJWrA0kJ9C5tGkMxKqn3l_57dpWRzD0bPEEWij-tQ", 180 | "type": "string" 181 | } 182 | ] 183 | }, 184 | "method": "PATCH", 185 | "header": [], 186 | "body": { 187 | "mode": "raw", 188 | "raw": "{\n\t\"otp\": \"595959\"\n\t\n}", 189 | "options": { 190 | "raw": { 191 | "language": "json" 192 | } 193 | } 194 | }, 195 | "url": { 196 | "raw": "{{OFA_BASE}}/user/verify", 197 | "host": [ 198 | "{{OFA_BASE}}" 199 | ], 200 | "path": [ 201 | "user", 202 | "verify" 203 | ] 204 | } 205 | }, 206 | "response": [] 207 | }, 208 | { 209 | "name": "Get OTP", 210 | "protocolProfileBehavior": { 211 | "disableBodyPruning": true 212 | }, 213 | "request": { 214 | "method": "GET", 215 | "header": [], 216 | "body": { 217 | "mode": "raw", 218 | "raw": "{\n\t\"email\": \"gogoi1@gogoi.com\",\n\t\"password\": \"123456\"\n}", 219 | "options": { 220 | "raw": { 221 | "language": "json" 222 | } 223 | } 224 | }, 225 | "url": { 226 | "raw": "{{OFA_BASE}}/user/otp", 227 | "host": [ 228 | "{{OFA_BASE}}" 229 | ], 230 | "path": [ 231 | "user", 232 | "otp" 233 | ] 234 | } 235 | }, 236 | "response": [] 237 | }, 238 | { 239 | "name": "Create Order", 240 | "request": { 241 | "auth": { 242 | "type": "bearer", 243 | "bearer": [ 244 | { 245 | "key": "token", 246 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZjllM2RkY2ZkM2E0MDAwMjRkZTVmMjkiLCJlbWFpbCI6ImdvZ29pQGNvZGVyZ29nb2kuY29tIiwiaWF0IjoxNjA1MjY3MDIyLCJleHAiOjE2MTMwNDMwMjJ9.zPxKu-dIB1kb5-p5SrgJoJtmgIW5rCsKXiVcWafcVMs", 247 | "type": "string" 248 | } 249 | ] 250 | }, 251 | "method": "POST", 252 | "header": [], 253 | "body": { 254 | "mode": "raw", 255 | "raw": "{\n \"cart\": [\n {\n \"_id\": \"5ec01c2c6b0ad806dd79c3e1\",\n \"unit\": 2\n },\n {\n \"_id\": \"5ec0c77a43434c07314cdf63\",\n \"unit\": 3\n },\n {\n \"_id\": \"5ec0c7b943434c07314cdf64\",\n \"unit\": 1\n }\n ]\n}", 256 | "options": { 257 | "raw": { 258 | "language": "json" 259 | } 260 | } 261 | }, 262 | "url": { 263 | "raw": "{{OFA_BASE}}/user/create-order", 264 | "host": [ 265 | "{{OFA_BASE}}" 266 | ], 267 | "path": [ 268 | "user", 269 | "create-order" 270 | ] 271 | } 272 | }, 273 | "response": [] 274 | }, 275 | { 276 | "name": "View Orders", 277 | "protocolProfileBehavior": { 278 | "disableBodyPruning": true 279 | }, 280 | "request": { 281 | "method": "GET", 282 | "header": [], 283 | "body": { 284 | "mode": "raw", 285 | "raw": "", 286 | "options": { 287 | "raw": { 288 | "language": "json" 289 | } 290 | } 291 | }, 292 | "url": { 293 | "raw": "{{OFA_BASE}}/user/order/", 294 | "host": [ 295 | "{{OFA_BASE}}" 296 | ], 297 | "path": [ 298 | "user", 299 | "order", 300 | "" 301 | ] 302 | } 303 | }, 304 | "response": [] 305 | }, 306 | { 307 | "name": "Delete Order", 308 | "request": { 309 | "method": "DELETE", 310 | "header": [], 311 | "body": { 312 | "mode": "raw", 313 | "raw": "", 314 | "options": { 315 | "raw": { 316 | "language": "json" 317 | } 318 | } 319 | }, 320 | "url": { 321 | "raw": "{{OFA_BASE}}/user/order/63b72b458203385b11df3c2f", 322 | "host": [ 323 | "{{OFA_BASE}}" 324 | ], 325 | "path": [ 326 | "user", 327 | "order", 328 | "63b72b458203385b11df3c2f" 329 | ] 330 | } 331 | }, 332 | "response": [] 333 | }, 334 | { 335 | "name": "Get Offers", 336 | "protocolProfileBehavior": { 337 | "disableBodyPruning": true 338 | }, 339 | "request": { 340 | "auth": { 341 | "type": "bearer", 342 | "bearer": [ 343 | { 344 | "key": "token", 345 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZjllM2RkY2ZkM2E0MDAwMjRkZTVmMjkiLCJlbWFpbCI6ImdvZ29pQGNvZGVyZ29nb2kuY29tIiwiaWF0IjoxNjA1MjY3MDIyLCJleHAiOjE2MTMwNDMwMjJ9.zPxKu-dIB1kb5-p5SrgJoJtmgIW5rCsKXiVcWafcVMs", 346 | "type": "string" 347 | } 348 | ] 349 | }, 350 | "method": "GET", 351 | "header": [], 352 | "body": { 353 | "mode": "raw", 354 | "raw": "{\n \"cart\": [\n {\n \"_id\": \"5ec01c2c6b0ad806dd79c3e1\",\n \"unit\": 2\n },\n {\n \"_id\": \"5ec0c77a43434c07314cdf63\",\n \"unit\": 3\n },\n {\n \"_id\": \"5ec0c7b943434c07314cdf64\",\n \"unit\": 1\n }\n ]\n}", 355 | "options": { 356 | "raw": { 357 | "language": "json" 358 | } 359 | } 360 | }, 361 | "url": { 362 | "raw": "{{OFA_BASE}}/food/offers/1234534", 363 | "host": [ 364 | "{{OFA_BASE}}" 365 | ], 366 | "path": [ 367 | "food", 368 | "offers", 369 | "1234534" 370 | ] 371 | } 372 | }, 373 | "response": [] 374 | }, 375 | { 376 | "name": "User - Add To Cart", 377 | "request": { 378 | "method": "POST", 379 | "header": [], 380 | "body": { 381 | "mode": "raw", 382 | "raw": "", 383 | "options": { 384 | "raw": { 385 | "language": "json" 386 | } 387 | } 388 | }, 389 | "url": { 390 | "raw": "{{OFA_BASE}}/user/cart/5f8350abeb26d40024f4ecf0", 391 | "host": [ 392 | "{{OFA_BASE}}" 393 | ], 394 | "path": [ 395 | "user", 396 | "cart", 397 | "5f8350abeb26d40024f4ecf0" 398 | ] 399 | } 400 | }, 401 | "response": [] 402 | }, 403 | { 404 | "name": "User - Add New Order", 405 | "request": { 406 | "auth": { 407 | "type": "bearer", 408 | "bearer": [ 409 | { 410 | "key": "token", 411 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZWMwZDJiZjRmNTAxMTA5NjZlMzg1NWIiLCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpYXQiOjE1ODk2OTY1MDQsImV4cCI6MTU5NzQ3MjUwNH0.qoIKI6UO8PuFIOqe8A6r4Lm32YgPUFfwTz1DhPK2srI", 412 | "type": "string" 413 | } 414 | ] 415 | }, 416 | "method": "POST", 417 | "header": [], 418 | "body": { 419 | "mode": "raw", 420 | "raw": "", 421 | "options": { 422 | "raw": { 423 | "language": "json" 424 | } 425 | } 426 | }, 427 | "url": { 428 | "raw": "{{OFA_BASE}}/user/add-order", 429 | "host": [ 430 | "{{OFA_BASE}}" 431 | ], 432 | "path": [ 433 | "user", 434 | "add-order" 435 | ] 436 | } 437 | }, 438 | "response": [] 439 | }, 440 | { 441 | "name": "User - Edit Profile", 442 | "request": { 443 | "auth": { 444 | "type": "bearer", 445 | "bearer": [ 446 | { 447 | "key": "token", 448 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZWMzNzRkNTVkM2Q3ODA2YzkzZjk0NWEiLCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpYXQiOjE1ODk4Njc3MzQsImV4cCI6MTU5NzY0MzczNH0.JKQQB4WrngDdYTQIYY-sEYB4T7eHsuOmyT1iFGZtlU4", 449 | "type": "string" 450 | } 451 | ] 452 | }, 453 | "method": "POST", 454 | "header": [], 455 | "body": { 456 | "mode": "raw", 457 | "raw": "{\n\t\"address\": \"Edapally Cochin, 682024\",\n\t\"phone\": \"985679457\",\n\t\"lat\": \"16.89\",\n\t\"lng\": \"78.60\"\n}", 458 | "options": { 459 | "raw": { 460 | "language": "json" 461 | } 462 | } 463 | }, 464 | "url": { 465 | "raw": "{{OFA_BASE}}/user/address/", 466 | "host": [ 467 | "{{OFA_BASE}}" 468 | ], 469 | "path": [ 470 | "user", 471 | "address", 472 | "" 473 | ] 474 | } 475 | }, 476 | "response": [] 477 | }, 478 | { 479 | "name": "User - Edit Cart", 480 | "request": { 481 | "auth": { 482 | "type": "bearer", 483 | "bearer": [ 484 | { 485 | "key": "token", 486 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZWMzNzRkNTVkM2Q3ODA2YzkzZjk0NWEiLCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpYXQiOjE1ODk4Njc3MzQsImV4cCI6MTU5NzY0MzczNH0.JKQQB4WrngDdYTQIYY-sEYB4T7eHsuOmyT1iFGZtlU4", 487 | "type": "string" 488 | } 489 | ] 490 | }, 491 | "method": "PUT", 492 | "header": [], 493 | "body": { 494 | "mode": "raw", 495 | "raw": "", 496 | "options": { 497 | "raw": { 498 | "language": "json" 499 | } 500 | } 501 | }, 502 | "url": { 503 | "raw": "{{OFA_BASE}}/user/cart/5ec0c80043434c07314cdf66/0", 504 | "host": [ 505 | "{{OFA_BASE}}" 506 | ], 507 | "path": [ 508 | "user", 509 | "cart", 510 | "5ec0c80043434c07314cdf66", 511 | "0" 512 | ] 513 | } 514 | }, 515 | "response": [] 516 | }, 517 | { 518 | "name": "User - View Order Details", 519 | "protocolProfileBehavior": { 520 | "disableBodyPruning": true 521 | }, 522 | "request": { 523 | "method": "GET", 524 | "header": [], 525 | "body": { 526 | "mode": "raw", 527 | "raw": "", 528 | "options": { 529 | "raw": { 530 | "language": "json" 531 | } 532 | } 533 | }, 534 | "url": { 535 | "raw": "{{OFA_BASE}}/user/order/6065a2f62aad9fb177392993", 536 | "host": [ 537 | "{{OFA_BASE}}" 538 | ], 539 | "path": [ 540 | "user", 541 | "order", 542 | "6065a2f62aad9fb177392993" 543 | ] 544 | } 545 | }, 546 | "response": [] 547 | }, 548 | { 549 | "name": "User - Remove From Cart", 550 | "request": { 551 | "auth": { 552 | "type": "bearer", 553 | "bearer": [ 554 | { 555 | "key": "token", 556 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZWMzNzRkNTVkM2Q3ODA2YzkzZjk0NWEiLCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpYXQiOjE1ODk4Njc3MzQsImV4cCI6MTU5NzY0MzczNH0.JKQQB4WrngDdYTQIYY-sEYB4T7eHsuOmyT1iFGZtlU4", 557 | "type": "string" 558 | } 559 | ] 560 | }, 561 | "method": "POST", 562 | "header": [], 563 | "body": { 564 | "mode": "raw", 565 | "raw": "", 566 | "options": { 567 | "raw": { 568 | "language": "json" 569 | } 570 | } 571 | }, 572 | "url": { 573 | "raw": "{{OFA_BASE}}/user/cart/5ec0c80043434c07314cdf66", 574 | "host": [ 575 | "{{OFA_BASE}}" 576 | ], 577 | "path": [ 578 | "user", 579 | "cart", 580 | "5ec0c80043434c07314cdf66" 581 | ] 582 | } 583 | }, 584 | "response": [] 585 | }, 586 | { 587 | "name": "User - Profile", 588 | "protocolProfileBehavior": { 589 | "disableBodyPruning": true 590 | }, 591 | "request": { 592 | "auth": { 593 | "type": "bearer", 594 | "bearer": [ 595 | { 596 | "key": "token", 597 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZmIyODFlMmYyNTkwZjRiNGVkZThmMDEiLCJlbWFpbCI6ImdvMkBnbWFpbC5jb20iLCJ2ZXJpZmllZCI6ZmFsc2UsImlhdCI6MTYwNTkyODExNiwiZXhwIjoxNjEzNzA0MTE2fQ.t6reEnbyvf2jF38F5KuAhsgUxAB4329E8pP15dmr_6w", 598 | "type": "string" 599 | } 600 | ] 601 | }, 602 | "method": "GET", 603 | "header": [], 604 | "body": { 605 | "mode": "raw", 606 | "raw": "", 607 | "options": { 608 | "raw": { 609 | "language": "json" 610 | } 611 | } 612 | }, 613 | "url": { 614 | "raw": "{{OFA_BASE}}/user/profile/", 615 | "host": [ 616 | "{{OFA_BASE}}" 617 | ], 618 | "path": [ 619 | "user", 620 | "profile", 621 | "" 622 | ] 623 | } 624 | }, 625 | "response": [] 626 | }, 627 | { 628 | "name": "User - View Cart", 629 | "protocolProfileBehavior": { 630 | "disableBodyPruning": true 631 | }, 632 | "request": { 633 | "auth": { 634 | "type": "bearer", 635 | "bearer": [ 636 | { 637 | "key": "token", 638 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZWMzNzRkNTVkM2Q3ODA2YzkzZjk0NWEiLCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpYXQiOjE1ODk4Njc3MzQsImV4cCI6MTU5NzY0MzczNH0.JKQQB4WrngDdYTQIYY-sEYB4T7eHsuOmyT1iFGZtlU4", 639 | "type": "string" 640 | } 641 | ] 642 | }, 643 | "method": "GET", 644 | "header": [], 645 | "body": { 646 | "mode": "raw", 647 | "raw": "", 648 | "options": { 649 | "raw": { 650 | "language": "json" 651 | } 652 | } 653 | }, 654 | "url": { 655 | "raw": "{{OFA_BASE}}/user/cart", 656 | "host": [ 657 | "{{OFA_BASE}}" 658 | ], 659 | "path": [ 660 | "user", 661 | "cart" 662 | ] 663 | } 664 | }, 665 | "response": [] 666 | }, 667 | { 668 | "name": "Foods - View", 669 | "protocolProfileBehavior": { 670 | "disableBodyPruning": true 671 | }, 672 | "request": { 673 | "method": "GET", 674 | "header": [], 675 | "body": { 676 | "mode": "raw", 677 | "raw": "", 678 | "options": { 679 | "raw": { 680 | "language": "json" 681 | } 682 | } 683 | }, 684 | "url": { 685 | "raw": "{{OFA_BASE}}/food/offers", 686 | "host": [ 687 | "{{OFA_BASE}}" 688 | ], 689 | "path": [ 690 | "food", 691 | "offers" 692 | ] 693 | } 694 | }, 695 | "response": [] 696 | }, 697 | { 698 | "name": "Foods - in 30 Min", 699 | "protocolProfileBehavior": { 700 | "disableBodyPruning": true 701 | }, 702 | "request": { 703 | "method": "GET", 704 | "header": [], 705 | "body": { 706 | "mode": "raw", 707 | "raw": "", 708 | "options": { 709 | "raw": { 710 | "language": "json" 711 | } 712 | } 713 | }, 714 | "url": { 715 | "raw": "{{OFA_BASE}}/food/in-30-min", 716 | "host": [ 717 | "{{OFA_BASE}}" 718 | ], 719 | "path": [ 720 | "food", 721 | "in-30-min" 722 | ] 723 | } 724 | }, 725 | "response": [] 726 | }, 727 | { 728 | "name": "Foods - Top Restaurants", 729 | "protocolProfileBehavior": { 730 | "disableBodyPruning": true 731 | }, 732 | "request": { 733 | "method": "GET", 734 | "header": [], 735 | "body": { 736 | "mode": "raw", 737 | "raw": "", 738 | "options": { 739 | "raw": { 740 | "language": "json" 741 | } 742 | } 743 | }, 744 | "url": { 745 | "raw": "{{OFA_BASE}}/food/top/restaurants", 746 | "host": [ 747 | "{{OFA_BASE}}" 748 | ], 749 | "path": [ 750 | "food", 751 | "top", 752 | "restaurants" 753 | ] 754 | } 755 | }, 756 | "response": [] 757 | }, 758 | { 759 | "name": "Foods - form Specific restaurant", 760 | "protocolProfileBehavior": { 761 | "disableBodyPruning": true 762 | }, 763 | "request": { 764 | "method": "GET", 765 | "header": [], 766 | "body": { 767 | "mode": "raw", 768 | "raw": "", 769 | "options": { 770 | "raw": { 771 | "language": "json" 772 | } 773 | } 774 | }, 775 | "url": { 776 | "raw": "{{OFA_BASE}}/food/restaurant/5ec01a8cc37246066220a635", 777 | "host": [ 778 | "{{OFA_BASE}}" 779 | ], 780 | "path": [ 781 | "food", 782 | "restaurant", 783 | "5ec01a8cc37246066220a635" 784 | ] 785 | } 786 | }, 787 | "response": [] 788 | }, 789 | { 790 | "name": "Foods - Details by ID", 791 | "protocolProfileBehavior": { 792 | "disableBodyPruning": true 793 | }, 794 | "request": { 795 | "method": "GET", 796 | "header": [], 797 | "body": { 798 | "mode": "raw", 799 | "raw": "", 800 | "options": { 801 | "raw": { 802 | "language": "json" 803 | } 804 | } 805 | }, 806 | "url": { 807 | "raw": "{{OFA_BASE}}/food/5f8350abeb26d40024f4ecf0", 808 | "host": [ 809 | "{{OFA_BASE}}" 810 | ], 811 | "path": [ 812 | "food", 813 | "5f8350abeb26d40024f4ecf0" 814 | ] 815 | } 816 | }, 817 | "response": [] 818 | } 819 | ], 820 | "auth": { 821 | "type": "bearer", 822 | "bearer": [ 823 | { 824 | "key": "token", 825 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2M2I0Mjg1YzAxZmM3YzQ3MDA0NDcyNTgiLCJlbWFpbCI6ImdvZ29pMUBnb2dvaS5jb20iLCJ2ZXJpZmllZCI6dHJ1ZSwiaWF0IjoxNjczMDAwNTAzLCJleHAiOjE2ODA3NzY1MDN9.CIz12Uhl_dZ807MVnqKTEuIqux0gi4s1K6lbRpg94aY", 826 | "type": "string" 827 | } 828 | ] 829 | }, 830 | "event": [ 831 | { 832 | "listen": "prerequest", 833 | "script": { 834 | "type": "text/javascript", 835 | "exec": [ 836 | "" 837 | ] 838 | } 839 | }, 840 | { 841 | "listen": "test", 842 | "script": { 843 | "type": "text/javascript", 844 | "exec": [ 845 | "" 846 | ] 847 | } 848 | } 849 | ] 850 | } --------------------------------------------------------------------------------