├── .env.template ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── favicon.png ├── index.html ├── logo.png ├── logo192.png ├── manifest.json └── robots.txt ├── src ├── App.test.tsx ├── App.tsx ├── Firebase │ └── index.js ├── Pages │ ├── About │ │ └── index.tsx │ ├── Admin │ │ ├── Dashboard.tsx │ │ └── index.tsx │ ├── Auth │ │ ├── Login.tsx │ │ ├── Register.tsx │ │ └── index.tsx │ ├── Home │ │ └── index.tsx │ ├── Menu │ │ └── index.tsx │ ├── Profile │ │ └── index.tsx │ ├── Services │ │ └── index.tsx │ └── index.tsx ├── components │ ├── Admin │ │ ├── AddFood │ │ │ ├── CategoriesSelector.tsx │ │ │ └── index.tsx │ │ ├── Content.tsx │ │ ├── Dashboard │ │ │ ├── CategoryCards.tsx │ │ │ └── index.tsx │ │ ├── Menu │ │ │ └── index.tsx │ │ ├── Sidenav.tsx │ │ ├── SidenavMenu.tsx │ │ └── Users │ │ │ ├── index.tsx │ │ │ └── user.tsx │ ├── Assets │ │ └── index.tsx │ ├── Cart │ │ ├── Body.tsx │ │ ├── CartTotal.tsx │ │ ├── Header.tsx │ │ ├── Item.tsx │ │ └── index.tsx │ ├── Checkout │ │ ├── Header.tsx │ │ ├── Selector.tsx │ │ ├── body.tsx │ │ ├── footer.tsx │ │ ├── forms │ │ │ ├── Card.tsx │ │ │ └── Momo.tsx │ │ └── index.tsx │ ├── Contact │ │ ├── form.tsx │ │ ├── header.tsx │ │ └── index.tsx │ ├── Container │ │ └── index.tsx │ ├── EmptyCart │ │ └── index.tsx │ ├── Filters │ │ ├── Button.tsx │ │ └── index.tsx │ ├── FoodItem │ │ ├── action.tsx │ │ └── index.tsx │ ├── Footer │ │ └── index.tsx │ ├── Header │ │ ├── DropDown.tsx │ │ ├── LoginAction.tsx │ │ ├── Navigations.tsx │ │ ├── index.tsx │ │ └── mobile-nav.tsx │ ├── Loader │ │ └── index.tsx │ ├── NotFound │ │ └── index.tsx │ ├── Sections │ │ ├── Fruits │ │ │ └── index.tsx │ │ ├── Menu │ │ │ └── index.tsx │ │ └── index.tsx │ ├── Showcase │ │ ├── Statics.tsx │ │ ├── index.tsx │ │ ├── left.tsx │ │ └── right.tsx │ ├── Upload │ │ └── index.tsx │ └── index.tsx ├── context │ ├── StateProvider.js │ ├── initialState.js │ └── reducer.js ├── firebase.config.js ├── img │ ├── NotFound.svg │ ├── avatar.png │ ├── c1.png │ ├── c2.png │ ├── c3.png │ ├── c4.png │ ├── c6.png │ ├── c7.png │ ├── chef1.png │ ├── cheff.png │ ├── cu1.png │ ├── cu2.png │ ├── cu3.png │ ├── cu4.png │ ├── cu5.png │ ├── cu6.png │ ├── d1.png │ ├── d2.png │ ├── d3.png │ ├── d4.png │ ├── d5.png │ ├── d6.png │ ├── d7.png │ ├── d8.png │ ├── delivery.png │ ├── emptyCart.svg │ ├── f1.png │ ├── f10.png │ ├── f2.png │ ├── f3.png │ ├── f4.png │ ├── f5.png │ ├── f6.png │ ├── f7.png │ ├── f8.png │ ├── f9.png │ ├── fi1.png │ ├── fi2.png │ ├── fi3.png │ ├── fi4.png │ ├── fi5.png │ ├── hero-bg.png │ ├── i1.png │ ├── i2.png │ ├── i3.png │ ├── i4.png │ ├── i5.png │ ├── i6.png │ ├── i7.png │ ├── logo.png │ ├── momo.png │ ├── r1.png │ ├── r2.png │ ├── r3.png │ ├── r4.png │ ├── r5.png │ └── visa.png ├── index.css ├── index.tsx ├── logo.svg ├── react-app-env.d.ts ├── reportWebVitals.ts ├── setupTests.ts └── utils │ ├── categories.tsx │ ├── fetchSessionData.js │ ├── filters.tsx │ ├── functions.tsx │ └── showcaseStatic.tsx ├── tailwind.config.js ├── tsconfig.json └── types.tsx /.env.template: -------------------------------------------------------------------------------- 1 | REACT_APP_FIREBASE_API_KEY=[YOUR_KEY] 2 | REACT_APP_FIREBASE_AUTH_DOMAIN=[YOUR_AUTH_DOMIAN] 3 | REACT_APP_FIREBASE_DB_URL=[YOUR_DB_URL] 4 | REACT_APP_FIREBASE_PROJECT_ID=[YOUR_PROJECT_ID] 5 | REACT_APP_FIREBASE_STORAGE_BUCKET=[YOUR_STORAGE_BUCKET] 6 | REACT_APP_FIREBASE_MESSAGING_ID=[YOUR_MESSAGING_ID] 7 | REACT_APP_FIREBASE_APP_ID=[YOUR_APP_ID] 8 | REACT_APP_FIREBASE_MEASUREMENT_ID=[YOUR_MEASUREMENT_ID] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .env 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart Online Restaurant App 2 | 3 | ## STACK USED 4 | - ReactJS (Typescript) 5 | - Redux 6 | - TailwindCSS 7 | - Firebase 8 | ## You Can See 9 | DEMO HERE! 10 | ## Deployment Status 11 | [![Netlify Status](https://api.netlify.com/api/v1/badges/44598840-1a12-4b3d-a79f-a89d91d34db8/deploy-status)](https://app.netlify.com/sites/bzone-restaurant/deploys) 12 | 13 | ## UI Desktop View 14 | ![image](https://user-images.githubusercontent.com/55560024/173251550-db16bf1d-8fa1-4af0-be5d-c447b1cca69b.png) 15 | ![image](https://user-images.githubusercontent.com/55560024/173251554-27cfaea6-697a-44b2-94a7-2cb27d4d4bcf.png) 16 | ![image](https://user-images.githubusercontent.com/55560024/173251557-0310516d-ee3a-4a42-aa81-57e4b92b5460.png) 17 | ![image](https://user-images.githubusercontent.com/55560024/173251559-332ef304-31f5-4f6a-bf01-7c13c2c6c9a0.png) 18 | ![image](https://user-images.githubusercontent.com/55560024/173251571-5e9fbb30-3965-42b0-a4f7-fa831ea8dffc.png) 19 | ![image](https://user-images.githubusercontent.com/55560024/173251578-3b271ede-7633-4be4-b0eb-73b3958bf225.png) 20 | ![image](https://user-images.githubusercontent.com/55560024/173251584-f433e696-7f52-4cd1-b98a-95584469e225.png) 21 | ![image](https://user-images.githubusercontent.com/55560024/173251587-0d0a59ce-129c-4a77-8002-7e30c32d84b4.png) 22 | ![image](https://user-images.githubusercontent.com/55560024/173251594-8c21e432-b05f-453e-b309-a50c13584cb5.png) 23 | 24 | 25 | ## UI Mobile View 26 | ![image](https://user-images.githubusercontent.com/55560024/173251731-e780f959-bbe0-4512-b24a-20da2d55b013.png) 27 | ![image](https://user-images.githubusercontent.com/55560024/173251732-e6cffedd-eaa9-4e0d-8362-1380ab956ef4.png) 28 | ![image](https://user-images.githubusercontent.com/55560024/173251734-7e129933-41d0-48eb-baa8-f4565ab4fb80.png) 29 | ![image](https://user-images.githubusercontent.com/55560024/173251737-ea9ba79f-c908-4785-bf55-53ebdcbac103.png) 30 | ![image](https://user-images.githubusercontent.com/55560024/173251742-093dff58-558d-4b81-9339-0f147951e827.png) 31 | ![image](https://user-images.githubusercontent.com/55560024/173251746-cd564b82-ec57-43fe-800f-e36e7ad8e2a0.png) 32 | 33 | ## Thank You! 34 | 35 | Thanks to Kevin Inoue for sharing this freebie! 36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bentilzone-restaurant", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.5.2", 10 | "@types/node": "^16.11.38", 11 | "@types/react": "^18.0.12", 12 | "@types/react-dom": "^18.0.5", 13 | "firebase": "^9.8.2", 14 | "framer-motion": "^6.3.10", 15 | "react": "^18.1.0", 16 | "react-dom": "^18.1.0", 17 | "react-icons": "^4.4.0", 18 | "react-router-dom": "^6.3.0", 19 | "react-scripts": "5.0.1", 20 | "react-toastify": "^9.0.3", 21 | "typescript": "^4.7.3", 22 | "web-vitals": "^2.1.4" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "devDependencies": { 49 | "autoprefixer": "^10.4.7", 50 | "postcss": "^8.4.14", 51 | "tailwindcss": "^3.1.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartCoolDev/Smart-Restaurant-react/d465a3f3ddd180d5c3b32df0d46dfb15c2ea477f/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartCoolDev/Smart-Restaurant-react/d465a3f3ddd180d5c3b32df0d46dfb15c2ea477f/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 18 | Bentilzone Restaurant 19 | 20 | 39 | 40 | 41 | 42 |
43 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartCoolDev/Smart-Restaurant-react/d465a3f3ddd180d5c3b32df0d46dfb15c2ea477f/public/logo.png -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartCoolDev/Smart-Restaurant-react/d465a3f3ddd180d5c3b32df0d46dfb15c2ea477f/public/logo192.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import "react-toastify/dist/ReactToastify.css"; 2 | 3 | import { 4 | About, 5 | Admin, 6 | Home, 7 | Login, 8 | Menu, 9 | Profile, 10 | Services, 11 | Signup, 12 | } from "./Pages"; 13 | import { Cart, Footer, Header } from "./components"; 14 | import { Route, Routes } from "react-router-dom"; 15 | import { 16 | calculateCartTotal, 17 | dispatchUsers, 18 | fetchFoodData, 19 | fetchUserCartData, 20 | isAdmin, 21 | } from "./utils/functions"; 22 | 23 | import { AnimatePresence } from "framer-motion"; 24 | import Contact from "./components/Contact"; 25 | import { ToastContainer } from "react-toastify"; 26 | import { useEffect } from "react"; 27 | import { useStateValue } from "./context/StateProvider"; 28 | 29 | function App() { 30 | const [{ showCart,showContactForm, user, foodItems, cartItems, adminMode }, dispatch] = 31 | useStateValue(); 32 | 33 | useEffect(() => { 34 | fetchFoodData(dispatch); 35 | dispatchUsers(dispatch); 36 | user && fetchUserCartData(user, dispatch); 37 | }, []); 38 | 39 | useEffect(() => { 40 | foodItems && 41 | cartItems.length > 0 && 42 | calculateCartTotal(cartItems, foodItems, dispatch); 43 | }, [cartItems, foodItems, dispatch]); 44 | return ( 45 | 46 | 47 |
48 | {showCart && } 49 | {showContactForm && } 50 | {!(adminMode && isAdmin(user)) &&
} 51 |
{}} 57 | > 58 | {/* Routes */} 59 | 60 | } /> 61 | } /> 62 | } /> 63 | } /> 64 | } /> 65 | } /> 66 | } /> 67 | } /> 68 | 69 | 70 | {!(adminMode && isAdmin(user)) &&
} 71 |
72 |
73 |
74 | ); 75 | } 76 | 77 | export default App; 78 | -------------------------------------------------------------------------------- /src/Firebase/index.js: -------------------------------------------------------------------------------- 1 | import { app, firestore, storage } from "../firebase.config"; 2 | import { collection, deleteDoc, doc, getDocs, orderBy, query, setDoc } from "firebase/firestore"; 3 | import { createUserWithEmailAndPassword, getAuth, signInWithEmailAndPassword, signInWithPopup } from "firebase/auth"; 4 | import { 5 | deleteObject, 6 | getDownloadURL, 7 | ref, 8 | uploadBytesResumable, 9 | } from "firebase/storage"; 10 | 11 | import { MdOutlineCloudUpload } from "react-icons/md"; 12 | import { toast } from "react-toastify"; 13 | import { shuffleItems } from "../utils/functions"; 14 | 15 | export const firebaseUploadImage = ( 16 | imageFile, 17 | promise, 18 | progressHandler, 19 | action, 20 | to 21 | ) => { 22 | promise(true); 23 | // progressHandler(0) 24 | toast.info(`Upload started.....`, { 25 | icon: , 26 | }); 27 | const storageRef = ref( 28 | storage, 29 | `Images/${to}/${Date.now()}-${imageFile.name}` 30 | ); 31 | const uploadPhoto = uploadBytesResumable(storageRef, imageFile); 32 | uploadPhoto.on( 33 | "state_changed", 34 | (snapshot) => { 35 | progressHandler( 36 | `Upload status: ${Math.round( 37 | (snapshot.bytesTransferred / snapshot.totalBytes) * 100 38 | )}%` 39 | ); 40 | }, 41 | (error) => { 42 | console.log(error); 43 | toast.error("Error while uploading, Try again🤗"); 44 | action(null); 45 | setTimeout(() => { 46 | promise(false); 47 | }, 3000); 48 | }, 49 | () => { 50 | getDownloadURL(uploadPhoto.snapshot.ref).then((downloadUrl) => { 51 | action(downloadUrl); 52 | promise(false); 53 | toast.success("Photo Uploaded Successfully😊"); 54 | }); 55 | } 56 | ); 57 | }; 58 | 59 | export const firebaseRemoveUploadedImage = ( 60 | ImageFile, 61 | imageHandler, 62 | promise 63 | ) => { 64 | const dummy = "https://firebasestorage.googleapis.com/v0/b/bentilzone-restaurant.appspot.com/o/Images" 65 | promise(true); 66 | toast.info(`Removing Image.....`, { 67 | icon: , 68 | autoClose: 1500, 69 | toastId: "remove-image", 70 | }); 71 | if(ImageFile.includes(dummy)) 72 | { 73 | const deleteRef = ref(storage, ImageFile); 74 | deleteObject(deleteRef).then(() => { 75 | imageHandler(null); 76 | promise(false); 77 | toast.success("Photo removed Successfully😊", { autoClose: 2000, toastId: "remove-image" }); 78 | }); 79 | }else{ 80 | imageHandler(null); 81 | promise(false); 82 | toast.success("Photo removed Successfully😊", { autoClose: 2000, toastId: "remove-image" }); 83 | } 84 | }; 85 | export const silentRemoveUploadedImage = (ImageFile) => { 86 | const deleteRef = ref(storage, ImageFile); 87 | deleteObject(deleteRef).then(() => {}); 88 | }; 89 | 90 | export const firebaseSaveProduct = async (data) => { 91 | await setDoc(doc(firestore, "Food", `${Date.now()}`), data, { 92 | merge: true, 93 | }); 94 | }; 95 | 96 | 97 | // Authenticate user using PROVIDER 98 | export const AUTHPROVIDER = async (provider) => { 99 | const firebaseAuth = getAuth(app); 100 | const { 101 | user: { refreshToken, providerData }, 102 | } = await signInWithPopup(firebaseAuth, provider); 103 | // add provider data to user 104 | await firebaseAddUser(providerData[0]); 105 | let userData = await firebaseGetUser(providerData[0].uid); 106 | return { refreshToken, userData }; 107 | }; 108 | 109 | // Signup with email and password 110 | export const EMAILSIGNUP = async (email, password) => { 111 | const firebaseAuth = getAuth(app); 112 | return createUserWithEmailAndPassword(firebaseAuth, email, password) 113 | }; 114 | 115 | // Signin with email and password 116 | export const EMAILSIGNIN = async (email, password) => { 117 | const firebaseAuth = getAuth(app); 118 | const result = await signInWithEmailAndPassword(firebaseAuth, email, password) 119 | let user = result.user.providerData[0]; 120 | 121 | 122 | return await firebaseGetUser(user.uid) 123 | }; 124 | 125 | 126 | // Fetch All Food Products from Firestore 127 | export const firebaseFetchFoodItems = async () => { 128 | const items = await getDocs( 129 | query(collection(firestore, "Food"), orderBy("id", "desc")) 130 | ); 131 | 132 | return shuffleItems(items.docs.map((doc) => doc.data())); 133 | } 134 | 135 | 136 | // cart operation 137 | export const firebaseAddToCart = async (data) => { 138 | await setDoc(doc(firestore, "CartItems", `${data.id}`), data, { 139 | merge: true, 140 | }); 141 | }; 142 | 143 | 144 | 145 | // Fetch All Cart Items from Firestore 146 | export const firebaseFetchAllCartItems = async () => { 147 | const items = await getDocs( 148 | query(collection(firestore, "CartItems"), orderBy("id", "desc")) 149 | ); 150 | 151 | return shuffleItems(items.docs.map((doc) => doc.data())); 152 | } 153 | 154 | // Update Cart Items 155 | export const firebaseUpdateCartItem = async (data) => { 156 | await setDoc(doc(firestore, "CartItems", `${data.id}`), data, { 157 | merge: true, 158 | }); 159 | } 160 | 161 | // Delete Cart from Firestore 162 | export const firebaseDeleteCartItem = async (item) => { 163 | await deleteDoc(doc(firestore, "CartItems", `${item.id}`)); 164 | } 165 | 166 | // Delete Cart from Firestore 167 | export const firebaseEmptyCart = async () => { 168 | await deleteDoc(doc(firestore, "CartItems")); 169 | } 170 | 171 | // Empty user cart from firestore 172 | export const firebaseEmptyUserCart = async (cartItems) => { 173 | cartItems.forEach((item) => { 174 | firebaseDeleteCartItem(item); 175 | }) 176 | } 177 | 178 | // Logout user 179 | export const firebaseLogout = async () => { 180 | await getAuth(app).signOut(); 181 | } 182 | 183 | // ADMIN USER MANAGEMENT 184 | 185 | // firestore add to users collection 186 | export const firebaseAddUser = async (data) => { 187 | // check if user already exists 188 | const user = await firebaseGetUser(data.uid); 189 | if (user.length === 0) { 190 | await setDoc(doc(firestore, "Users", `${data.uid}`), data, { 191 | merge: true, 192 | }); 193 | } 194 | } 195 | 196 | // get user 197 | export const firebaseGetUser = async (uid) => { 198 | const user = await getDocs( 199 | query(collection(firestore, "Users")) 200 | ); 201 | let users = user.docs.map((doc) => doc.data()); 202 | return users.filter((user) => user.uid === uid) 203 | } 204 | 205 | // update user 206 | export const firebaseUpdateUser = async (data) => { 207 | await setDoc(doc(firestore, "Users", `${data.uid}`), data, { 208 | merge: true, 209 | }); 210 | } 211 | 212 | // firebase get all users 213 | export const firebaseGetAllUsers = async () => { 214 | const users = await getDocs( 215 | query(collection(firestore, "Users")) 216 | ); 217 | let usersData = users.docs.map((doc) => doc.data()); 218 | return usersData 219 | } 220 | 221 | // delete food 222 | export const firebaseDeleteFood = async (id) => { 223 | await deleteDoc(doc(firestore, "Food", `${id}`)); 224 | } -------------------------------------------------------------------------------- /src/Pages/About/index.tsx: -------------------------------------------------------------------------------- 1 | const About = () => { 2 | return ( 3 |
4 |

About Page Cominig Up soon!!

5 |
6 | ); 7 | } 8 | 9 | export default About; -------------------------------------------------------------------------------- /src/Pages/Admin/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Content, Sidenav, Stats } from '../../components'; 3 | 4 | 5 | const Dashboard = () => { 6 | const [activePage, setActivePage] = useState("Dashboard"); 7 | const [element, setElement] = useState(); 8 | return ( 9 |
10 | 11 | 12 |
13 | ); 14 | } 15 | 16 | 17 | export default Dashboard; -------------------------------------------------------------------------------- /src/Pages/Admin/index.tsx: -------------------------------------------------------------------------------- 1 | import AddFood from "../../components/Admin/AddFood"; 2 | import Dashboard from "./Dashboard"; 3 | import Home from "../Home"; 4 | import { useStateValue } from "../../context/StateProvider"; 5 | import { isAdmin } from "../../utils/functions"; 6 | 7 | const Admin = () => { 8 | const [{user}] = useStateValue() 9 | return ( 10 | <> 11 | {isAdmin(user) ? : } 12 | 13 | ); 14 | }; 15 | 16 | export default Admin; 17 | -------------------------------------------------------------------------------- /src/Pages/Auth/Login.tsx: -------------------------------------------------------------------------------- 1 | import { Link, useNavigate } from "react-router-dom"; 2 | import ProviderAuth, { ImageBox } from "."; 3 | import { toast } from "react-toastify"; 4 | 5 | import { motion } from "framer-motion"; 6 | import { useState } from "react"; 7 | import { useStateValue } from "../../context/StateProvider"; 8 | import { EMAILSIGNIN } from "../../Firebase"; 9 | 10 | const Login = () => { 11 | const navigate = useNavigate(); 12 | const [{ user }, dispatch] = useStateValue(); 13 | const [email, setEmail] = useState(""); 14 | const [password, setPassword] = useState(""); 15 | 16 | const EmailAuth = () => { 17 | if (!user) { 18 | if (email.length > 0 && password.length > 0) { 19 | toast.promise( 20 | EMAILSIGNIN(email, password), 21 | { 22 | pending: "Signing in...", 23 | success: "Signin successful: WELCOME!", 24 | error: "Error signing account, Please try again🤗", 25 | } 26 | ).then((userData) => { 27 | // Signed in 28 | const user = userData[0]; 29 | dispatch({ 30 | type: "SET_USER", 31 | user: user, 32 | }); 33 | localStorage.setItem("user", JSON.stringify(user)); 34 | navigate("/"); 35 | } 36 | ).catch((error) => { 37 | // const errorCode = error.code; 38 | const errorMessage = error.message; 39 | toast.error(errorMessage, { autoClose: 15000 }); 40 | } 41 | ); 42 | 43 | } else { 44 | toast.warn("Please fill all the fields", { autoClose: 15000 }); 45 | } 46 | } 47 | }; 48 | 49 | return ( 50 |
51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |

59 | OR 60 |

61 |
62 |
63 | setEmail(e.target.value)} 68 | /> 69 |
70 | 71 |
72 | setPassword(e.target.value)} 77 | /> 78 |
79 | 80 |
81 | 85 | Forgot password? 86 | 87 |
88 | 89 | 94 | Sign in 95 | 96 | 97 |
98 |

99 | Don't have an account? 100 |

101 |
102 | 103 | 107 | Sign Up 108 | 109 | 110 | 111 |
112 |
113 |
114 |
115 | ); 116 | }; 117 | 118 | export default Login; 119 | -------------------------------------------------------------------------------- /src/Pages/Auth/Register.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { Link, useNavigate } from "react-router-dom"; 3 | import ProviderAuth, { ImageBox } from "."; 4 | import { toast } from "react-toastify"; 5 | 6 | // import { motion } from "framer-motion"; 7 | import { useState } from "react"; 8 | import { useStateValue } from "../../context/StateProvider"; 9 | import { EMAILSIGNUP, firebaseAddUser } from "../../Firebase"; 10 | 11 | // toast.configure() 12 | 13 | const Login = () => { 14 | const navigate = useNavigate(); 15 | const [{ user }, dispatch] = useStateValue(); 16 | const [email, setEmail] = useState(''); 17 | const [password, setPassword] = useState(''); 18 | 19 | const EmailAuth = () => { 20 | if (!user) { 21 | if (email.length > 0 && password.length > 0) { 22 | toast.promise( 23 | EMAILSIGNUP(email, password), 24 | { 25 | pending: "Creating Account...", 26 | success: "Signup successful: WELCOME!", 27 | error: "Error Creating account, Please try again🤗", 28 | } 29 | ).then((userCredential) => { 30 | // Signed in 31 | const user = userCredential.user.providerData[0]; 32 | firebaseAddUser(user); 33 | dispatch({ 34 | type: "SET_USER", 35 | user: user, 36 | }); 37 | localStorage.setItem("user", JSON.stringify(user)); 38 | navigate("/"); 39 | } 40 | ).catch((error) => { 41 | // const errorCode = error.code; 42 | const errorMessage = error.message; 43 | toast.error(errorMessage, { autoClose: 15000 }); 44 | } 45 | ); 46 | 47 | } else { 48 | toast.warn("Please fill all the fields", { autoClose: 15000 }); 49 | } 50 | } 51 | }; 52 | 53 | return ( 54 |
55 |
56 |
57 | 58 |
59 |
60 | 61 |
62 |

63 | OR 64 |

65 |
66 |
67 | setEmail(e.target.value)} 72 | /> 73 |
74 | 75 |
76 | setPassword(e.target.value)} 81 | /> 82 |
83 | 84 |
85 | 86 |

90 | Sign Up 91 |

92 | 93 |
94 |

95 | Already have an account? 96 |

97 |
98 | 102 | Login 103 | 104 | 105 |
106 |
107 |
108 |
109 | ); 110 | }; 111 | 112 | export default Login; 113 | -------------------------------------------------------------------------------- /src/Pages/Auth/index.tsx: -------------------------------------------------------------------------------- 1 | import "react-toastify/dist/ReactToastify.css"; 2 | 3 | import { Cheff1 } from "../../components/Assets"; 4 | import { 5 | // GithubAuthProvider, 6 | GoogleAuthProvider, 7 | } from "firebase/auth"; 8 | import { useNavigate } from "react-router-dom"; 9 | import { toast } from "react-toastify"; 10 | import { FcGoogle } from "react-icons/fc"; 11 | import { BsGithub } from "react-icons/bs"; 12 | 13 | import { motion } from "framer-motion"; 14 | import { useStateValue } from "../../context/StateProvider"; 15 | import { AUTHPROVIDER } from "../../Firebase"; 16 | import { MdOutlineNotificationsActive } from "react-icons/md"; 17 | import { fetchUserCartData } from "../../utils/functions"; 18 | 19 | const ProviderAuth = () => { 20 | const GOOGLE_PROVIDER = new GoogleAuthProvider(); 21 | // const GITHUB_PROVIDER = new GithubAuthProvider(); 22 | const [{ user }, dispatch] = useStateValue(); 23 | const navigate = useNavigate(); 24 | 25 | const AUTH = async ({ provider }: { provider: any }) => { 26 | if (!user) { 27 | toast 28 | .promise(AUTHPROVIDER(provider), { 29 | pending: "Signing in...", 30 | success: "Signin successful", 31 | error: "Error Signing in, Please try again🤗", 32 | }) 33 | .then(({ refreshToken, userData }) => { 34 | // Signed in 35 | const user = userData[0]; 36 | // const userData = getUserData(user); 37 | dispatch({ 38 | type: "SET_USER", 39 | user: user, 40 | }); 41 | fetchUserCartData(user, dispatch); 42 | localStorage.setItem("user", JSON.stringify(user)); 43 | navigate("/"); 44 | }) 45 | .catch((error) => { 46 | // const errorCode = error.code; 47 | const errorMessage = error.message; 48 | toast.error(errorMessage, { autoClose: 15000 }); 49 | }); 50 | } 51 | }; 52 | return ( 53 |
54 | 58 | toast.warn("GitHub Signin is not available yet", { 59 | autoClose: 2000, 60 | icon: ( 61 | 62 | ), 63 | toastId: "github", 64 | }) 65 | } 66 | > 67 | 68 | Github 69 | 70 | AUTH({ provider: GOOGLE_PROVIDER })} 74 | > 75 | 76 | Google 77 | 78 |
79 | ); 80 | }; 81 | 82 | export const ImageBox = () => { 83 | return ( 84 |
85 | 96 |
97 | ); 98 | }; 99 | 100 | export default ProviderAuth; 101 | -------------------------------------------------------------------------------- /src/Pages/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import { FruitsSection, MenuSection, ShowcaseBanner, } from "../../components" 2 | 3 | const Home = () => { 4 | return ( 5 |
6 | 7 | 8 | 9 | 10 |
11 | 12 | ) 13 | } 14 | 15 | export default Home -------------------------------------------------------------------------------- /src/Pages/Menu/index.tsx: -------------------------------------------------------------------------------- 1 | import { MenuSection } from "../../components"; 2 | 3 | const Menu = () => { 4 | return ( 5 |
6 | 7 | 8 |
9 | ); 10 | } 11 | 12 | export default Menu; -------------------------------------------------------------------------------- /src/Pages/Profile/index.tsx: -------------------------------------------------------------------------------- 1 | import { BiUser } from "react-icons/bi"; 2 | import { BsPhone } from "react-icons/bs"; 3 | import { 4 | MdOutlineDataSaverOn, 5 | MdDeleteOutline, 6 | } from "react-icons/md"; 7 | 8 | import { motion } from "framer-motion"; 9 | import { toast } from "react-toastify"; 10 | import { useState } from "react"; 11 | import { useStateValue } from "../../context/StateProvider"; 12 | import { AssetUploader, Loader } from "../../components"; 13 | import { updateUserData } from "../../utils/functions"; 14 | import { firebaseRemoveUploadedImage } from "../../Firebase"; 15 | 16 | 17 | const UpdateProfile = () => { 18 | const [{ user }, dispatch] = useStateValue(); 19 | const [displayName, setDisplayName] = useState(user.displayName) 20 | // const [email, setEmail] = useState(user.email) 21 | const [photoURL, setPhotoURL] = useState(user.photoURL) 22 | const [loading, setLoading] = useState(false) 23 | const [phoneNumber, setPhoneNumber] = useState(user.phoneNumber) 24 | const [btnText, setBtnText] = useState("Save") 25 | const [loaderMessage, setLoadermessage] = useState(""); 26 | 27 | const deleteImage = async () => { 28 | setLoadermessage("Removing Photo......"); 29 | firebaseRemoveUploadedImage(photoURL, setPhotoURL, setLoading); 30 | const data = { ...user, photoURL: null }; 31 | await updateUserData(data, dispatch, false); 32 | }; 33 | const saveChanges = async () => { 34 | setBtnText("Saving...."); 35 | if(displayName.lenth < 0 || phoneNumber.length !== 10) 36 | { 37 | toast.error("Fill out fields correctly") 38 | setBtnText("Save") 39 | }else{ 40 | const data = { 41 | ...user, 42 | displayName, 43 | phoneNumber, 44 | photoURL, 45 | } 46 | await updateUserData(data, dispatch, true); 47 | setBtnText("Save"); 48 | } 49 | 50 | }; 51 | 52 | const updatePhotoUrl = async (newUrl: string) => { 53 | setPhotoURL(newUrl); 54 | const data = { ...user, photoURL: newUrl }; 55 | await updateUserData(data, dispatch, false) 56 | } 57 | 58 | const validateNumber = (value: any) => { 59 | if (isNaN(value)) { 60 | toast.error("Please enter a valid phone number", { toastId: 123 }); 61 | return ""; 62 | } 63 | return value; 64 | }; 65 | 66 | 67 | 68 | return ( 69 |
70 |
71 |
72 | 73 | setDisplayName(e.target.value)} 81 | /> 82 |
83 | 84 |
85 |
86 | 87 | setPhoneNumber(validateNumber(e.target.value))} 94 | /> 95 |
96 |
97 |
98 | { 99 | loading ? ( 100 | 101 | ):( 102 | <> 103 | {photoURL ? ( 104 | <> 105 |
106 | uploaded food 111 | deleteImage()} 117 | > 118 | 119 | 120 |
121 | 122 | ) : ( 123 | 128 | )} 129 | 130 | ) 131 | } 132 |
133 | 134 |
135 | saveChanges()} 139 | > 140 | {btnText} 141 | 142 |
143 |
144 |
145 | ); 146 | }; 147 | 148 | export default UpdateProfile; 149 | -------------------------------------------------------------------------------- /src/Pages/Services/index.tsx: -------------------------------------------------------------------------------- 1 | const Services = () => { 2 | return ( 3 |
4 |

Services Page Cominig Up soon!!

5 |
6 | ); 7 | } 8 | 9 | export default Services; -------------------------------------------------------------------------------- /src/Pages/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | export {default as Home } from './Home'; 3 | export {default as Admin} from './Admin'; 4 | export {default as Login} from './Auth/Login'; 5 | export {default as Signup} from './Auth/Register'; 6 | export {default as Profile} from './Profile' 7 | export {default as About} from './About'; 8 | export {default as Services} from './Services'; 9 | export {default as Menu} from './Menu'; -------------------------------------------------------------------------------- /src/components/Admin/AddFood/CategoriesSelector.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export type FoodCategory = { 3 | id: number; 4 | name: string; 5 | urlParam: string; 6 | }; 7 | 8 | export type FoodCategories = FoodCategory[]; 9 | 10 | interface Props { 11 | categories: FoodCategories; 12 | action: any; 13 | selected: string; 14 | } 15 | const CategoriesSelector: React.FC = ({ 16 | categories, 17 | action, 18 | selected, 19 | }) => { 20 | return ( 21 | 33 | ); 34 | }; 35 | 36 | export default CategoriesSelector; 37 | -------------------------------------------------------------------------------- /src/components/Admin/AddFood/index.tsx: -------------------------------------------------------------------------------- 1 | import { AssetUploader, Loader } from "../.."; 2 | import { BiCategory, BiFoodMenu } from "react-icons/bi"; 3 | import { 4 | MdDeleteOutline, 5 | MdOutlineDataSaverOn, 6 | MdOutlineFastfood, 7 | MdOutlineFoodBank, 8 | MdOutlineProductionQuantityLimits, 9 | } from "react-icons/md"; 10 | import { 11 | firebaseFetchFoodItems, 12 | firebaseRemoveUploadedImage, 13 | firebaseSaveProduct, 14 | } from "../../../Firebase"; 15 | 16 | import { Categories } from "../../../utils/categories"; 17 | import CategoriesSelector from "./CategoriesSelector"; 18 | import { GiTakeMyMoney } from "react-icons/gi"; 19 | import { motion } from "framer-motion"; 20 | import { toast } from "react-toastify"; 21 | import { useState } from "react"; 22 | import { useStateValue } from "../../../context/StateProvider"; 23 | import { fetchFoodData } from "../../../utils/functions"; 24 | 25 | const AddFood = () => { 26 | const [title, setTitle] = useState(""); 27 | const [calories, setCalories] = useState(""); 28 | const [price, setPrice] = useState(""); 29 | const [image, setImage] = useState(null); 30 | const [category, setCategory] = useState(""); 31 | const [loading, setLoading] = useState(false); 32 | const [quantity, setQuantity] = useState(""); 33 | const [description, setDescription] = useState(""); 34 | const [loaderMessage, setLoadermessage] = useState(""); 35 | const [{ foodItems }, dispatch] = useStateValue(); 36 | 37 | const deleteImage = () => { 38 | setLoadermessage("Removing Photo......"); 39 | firebaseRemoveUploadedImage(image, setImage, setLoading); 40 | }; 41 | const saveItem = () => { 42 | setLoadermessage(`Saving Product ${title}.`); 43 | setLoading(true); 44 | try { 45 | if (!title || !calories || !price || !image || !category) { 46 | toast.error("Please fill all fields before saving product 🤗"); 47 | setLoading(false); 48 | return; 49 | } else { 50 | const data = { 51 | id: Date.now(), 52 | title: title, 53 | calories: calories, 54 | category: category, 55 | description: description, 56 | price: price, 57 | imageURL: image, 58 | qty: quantity, 59 | }; 60 | toast 61 | .promise(firebaseSaveProduct(data), { 62 | pending: "Saving Product...", 63 | success: "Product saved successfully", 64 | error: "Error saving product, Please try again🤗", 65 | }) 66 | .then(() => { 67 | clearForm(); 68 | setLoading(false); 69 | fetchFoodData(dispatch); 70 | }) 71 | .catch((error) => { 72 | console.log(error); 73 | }); 74 | setLoadermessage(""); 75 | setLoading(false); 76 | } 77 | } catch (error) { 78 | console.log(error); 79 | toast.error("Error whilesaving product"); 80 | } 81 | }; 82 | const clearForm = () => { 83 | setTitle(""); 84 | setCalories(""); 85 | setPrice(""); 86 | setImage(null); 87 | // setCategory(""); 88 | setQuantity(""); 89 | setDescription(""); 90 | }; 91 | 92 | const validateNumber = (value: any) => { 93 | if (isNaN(value)) { 94 | toast.error("Please enter a valid number", { toastId: 123 }); 95 | return ""; 96 | } 97 | return value; 98 | }; 99 | 100 | 101 | 102 | return ( 103 |
104 |
105 |
106 | 107 | setTitle(e.target.value)} 115 | /> 116 |
117 | 118 |
119 |
120 | 121 | 126 |
127 |
128 | 129 | setQuantity(validateNumber(e.target.value))} 137 | /> 138 |
139 |
140 |
141 | {loading ? ( 142 | 143 | ) : ( 144 | <> 145 | {image ? ( 146 | <> 147 |
148 | uploaded food 153 | deleteImage()} 159 | > 160 | 161 | 162 |
163 | 164 | ) : ( 165 | 170 | )} 171 | 172 | )} 173 |
174 |
175 |
176 | 177 | setCalories(e.target.value)} 185 | /> 186 |
187 |
188 | 189 | setPrice(validateNumber(e.target.value))} 197 | /> 198 |
199 |
200 |
201 | 202 | setDescription(e.target.value)} 210 | /> 211 |
212 | 213 |
214 | saveItem()} 218 | > 219 | Save 220 | 221 |
222 |
223 |
224 | ); 225 | }; 226 | 227 | export default AddFood; 228 | -------------------------------------------------------------------------------- /src/components/Admin/Content.tsx: -------------------------------------------------------------------------------- 1 | import { FaShopify } from "react-icons/fa"; 2 | import { Link } from "react-router-dom"; 3 | import { useStateValue } from "../../context/StateProvider"; 4 | import { ToggleAdminMode } from "../../utils/functions"; 5 | 6 | const Content = ({ pageTitle, Element }: { pageTitle: string, Element:JSX.Element }) => { 7 | const [{}, dispatch] = useStateValue(); 8 | return ( 9 |
10 |
11 | {pageTitle} 12 | 13 | {/* home button */} 14 | ToggleAdminMode(dispatch, false)}> 15 | 19 | 20 |
21 |
{Element}
22 | 23 |
24 | ); 25 | }; 26 | export default Content; -------------------------------------------------------------------------------- /src/components/Admin/Dashboard/CategoryCards.tsx: -------------------------------------------------------------------------------- 1 | import { Categories } from "../../../utils/categories" 2 | const CategoryCards = () => { 3 | let category = Categories[Math.floor(Math.random() * Categories.length)]; 4 | return ( 5 |
6 | {/* Catgory card */} 7 | 8 |
9 | ) 10 | } 11 | 12 | export default CategoryCards -------------------------------------------------------------------------------- /src/components/Admin/Dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import CategoryCards from "./CategoryCards"; 2 | 3 | const Dashboard = () => { 4 | return ( 5 |
6 |
7 | 8 | 9 | 10 | 11 |
12 |
13 | ); 14 | }; 15 | 16 | export default Dashboard; 17 | -------------------------------------------------------------------------------- /src/components/Admin/Menu/index.tsx: -------------------------------------------------------------------------------- 1 | import { FaSearch } from "react-icons/fa"; 2 | import { FoodItem } from "../../../../types"; 3 | import { SingleFoodItem } from "../../FoodItem"; 4 | import React, { useState } from "react"; 5 | import { useStateValue } from "../../../context/StateProvider"; 6 | 7 | const Menu = () => { 8 | const [{ foodItems }, dispatch] = useStateValue(); 9 | const [query, setQuery] = useState(""); 10 | const [filteredFoodItems, setFilteredFoodItems] = useState(foodItems); 11 | 12 | const filterFood = () => { 13 | if(query.length === 0) { 14 | setFilteredFoodItems(foodItems); 15 | }else{ 16 | const filteredFoodItems = foodItems.filter((foodItem:FoodItem) => foodItem.title.toLowerCase().includes(query.toLowerCase())); 17 | setFilteredFoodItems(filteredFoodItems); 18 | } 19 | } 20 | const searchFood = (e: React.ChangeEvent) => { 21 | setQuery(e.target.value); 22 | filterFood(); 23 | } 24 | return ( 25 |
26 | {/* search bar */} 27 |
28 | searchFood(e)} 34 | /> 35 | {/* search button */} 36 | 39 |
40 |
41 | { 42 | filteredFoodItems.map((item: FoodItem) => ( 43 | 44 | )) 45 | } 46 |
47 |
48 | ); 49 | }; 50 | 51 | export default Menu; 52 | -------------------------------------------------------------------------------- /src/components/Admin/Sidenav.tsx: -------------------------------------------------------------------------------- 1 | import { Logo } from "../Assets"; 2 | import SidenavMenu from "./SidenavMenu"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | import { AiFillLock } from "react-icons/ai"; 5 | import { useStateValue } from "../../context/StateProvider"; 6 | import { logout, ToggleAdminMode } from "../../utils/functions"; 7 | import { motion } from "framer-motion"; 8 | const Sidenav = ({ 9 | activePage, 10 | setActivePage, 11 | setPageContent, 12 | }: { 13 | activePage: string; 14 | setActivePage: any; 15 | setPageContent: any; 16 | }) => { 17 | return ( 18 |
19 | 20 | 25 | 26 |
27 | ); 28 | }; 29 | 30 | const SidenavHeader = () => { 31 | const [{ adminMode }, dispatch] = useStateValue(); 32 | return ( 33 | 40 | ToggleAdminMode(dispatch, false)} 42 | to={"/"} 43 | className="flex items-center ml-1 pb-8 w-full justify-center" 44 | > 45 | Logo 46 |

47 | Bentilzone 48 |

49 | 50 |
51 | ); 52 | }; 53 | 54 | const SidenavFooter = () => { 55 | const [{ user }, dispatch] = useStateValue(); 56 | const navigate = useNavigate(); 57 | return ( 58 | logout(user, dispatch, navigate)} 64 | className="flex items-center justify-center mt-auto px-3 gap-3 text-orange-50 cursor-pointer opacity-70 hover:opacity-100" 65 | > 66 | 67 |
Logout
68 |
69 | ); 70 | }; 71 | 72 | export default Sidenav; 73 | -------------------------------------------------------------------------------- /src/components/Admin/SidenavMenu.tsx: -------------------------------------------------------------------------------- 1 | import { AiFillDashboard } from "react-icons/ai"; 2 | import { FiUsers } from "react-icons/fi"; 3 | import { 4 | MdAddModerator, 5 | MdOutlineFavoriteBorder, 6 | MdRestaurantMenu, 7 | } from "react-icons/md"; 8 | import { motion } from "framer-motion"; 9 | import { FaCogs } from "react-icons/fa"; 10 | import AddFood from "./AddFood"; 11 | import Dashboard from "./Dashboard"; 12 | import Users from "./Users"; 13 | import Menu from "./Menu"; 14 | import { useStateValue } from "../../context/StateProvider"; 15 | 16 | const SidenavMenu = ({ 17 | activePage, 18 | setActivePage, 19 | setPageContent, 20 | }: { 21 | activePage: string; 22 | setActivePage: any; 23 | setPageContent: any; 24 | }) => ( 25 | 31 | } 34 | title="Dashboard" 35 | setActivePage={setActivePage} 36 | setPageContent={setPageContent} 37 | pageContent={} 38 | /> 39 | } 42 | title="Add Food" 43 | setActivePage={setActivePage} 44 | setPageContent={setPageContent} 45 | pageContent={} 46 | /> 47 | } 50 | title="Menu" 51 | setActivePage={setActivePage} 52 | setPageContent={setPageContent} 53 | pageContent={} 54 | /> 55 | } 58 | title="Orders" 59 | setActivePage={setActivePage} 60 | setPageContent={setPageContent} 61 | pageContent={ 62 |
Orders
63 | } 64 | /> 65 | } 68 | title="Users" 69 | setActivePage={setActivePage} 70 | setPageContent={setPageContent} 71 | pageContent={} 72 | /> 73 | } 76 | title="Settings" 77 | setActivePage={setActivePage} 78 | setPageContent={setPageContent} 79 | pageContent={ 80 |
Settings
81 | } 82 | /> 83 | 84 | ); 85 | 86 | const NavItem = ({ 87 | activePage, 88 | svgIcon, 89 | title, 90 | setActivePage, 91 | setPageContent, 92 | pageContent, 93 | }: { 94 | activePage: string; 95 | setActivePage: any; 96 | svgIcon: any; 97 | title: string; 98 | setPageContent: any; 99 | pageContent: JSX.Element; 100 | }) => { 101 | const handleClick = () => { 102 | setActivePage(title); 103 | setPageContent(pageContent); 104 | }; 105 | const [{users, foodItems}, dispatch] = useStateValue() 106 | return ( 107 | 114 |

{svgIcon}

115 |
{title} 116 | { 117 | (title === "Menu" || title === "Users") && ( 118 |
119 |

120 | { 121 | title === "Menu"? foodItems.length:users.length 122 | } 123 |

124 |
125 | ) 126 | } 127 |
128 |
129 | ); 130 | }; 131 | export default SidenavMenu; 132 | -------------------------------------------------------------------------------- /src/components/Admin/Users/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { FaSearch } from "react-icons/fa"; 3 | import { useStateValue } from "../../../context/StateProvider"; 4 | import User from "./user"; 5 | 6 | 7 | const Users = () => { 8 | const [{ users }, dispatch] = useStateValue(); 9 | const [query, setQuery] = useState(""); 10 | const [filteredUsers, setFilteredUsers] = useState(users); 11 | 12 | const filterUsers = () => { 13 | if(query.length === 0) { 14 | setFilteredUsers(users); 15 | }else{ 16 | const filter = users.filter((item:any) => item.displayName.toLowerCase().includes(query.toLowerCase())); 17 | setFilteredUsers(filter); 18 | } 19 | } 20 | const searchUsers = (e: React.ChangeEvent) => { 21 | setQuery(e.target.value); 22 | filterUsers(); 23 | } 24 | return ( 25 |
26 | {/* search bar */} 27 |
28 | searchUsers(e)} 34 | /> 35 | {/* search button */} 36 | 39 |
40 | 41 | {/* dasboard statistics and counts */} 42 |
43 | { 44 | filteredUsers.map((user:any) => ( 45 | 46 | )) 47 | } 48 |
49 |
50 | ); 51 | }; 52 | 53 | export default Users; 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/Admin/Users/user.tsx: -------------------------------------------------------------------------------- 1 | import { MdDeleteForever, MdEmail } from "react-icons/md"; 2 | import { GiShieldDisabled } from "react-icons/gi"; 3 | import { Avatar } from "../../Assets"; 4 | import { FcGoogle } from "react-icons/fc"; 5 | import {motion} from "framer-motion"; 6 | const User = ({item}: {item:any}) => { 7 | return ( 8 |
9 |
10 | 16 |
17 |
18 | {item?.displayName || 'User'} 19 |
20 | {item?.email} 21 | {item?.phoneNumber} 22 |
23 | 24 | { 25 | item?.providerId !== 'google.com' ? : 26 | } {item.providerId} 27 | 28 |
29 |

33 | 34 |

35 |

39 | 40 |

41 |
42 |
43 |
44 | ) 45 | } 46 | 47 | export default User -------------------------------------------------------------------------------- /src/components/Assets/index.tsx: -------------------------------------------------------------------------------- 1 | import AvatarI from "../../img/avatar.png"; 2 | import Cheff1I from "../../img/chef1.png"; 3 | import CheffI from "../../img/cheff.png"; 4 | import LogoI from "../../img/chef1.png"; 5 | import BikeDeliveryI from "../../img/delivery.png"; 6 | import HeroBgI from "../../img/hero-bg.png"; 7 | import EmptyCartI from "../../img/emptyCart.svg"; 8 | import NotFoundI from "../../img/NotFound.svg"; 9 | import Visa from "../../img/visa.png"; 10 | import Momo from "../../img/momo.png"; 11 | // Showcase Banner Static Assets 12 | import IcreamI from "../../img/i1.png"; 13 | import StrawberryI from "../../img/f5.png"; 14 | import ChickenI from "../../img/c3.png"; 15 | import FishI from "../../img/fi3.png"; 16 | 17 | 18 | 19 | // Exports 20 | export const Logo = LogoI 21 | export const Avatar = AvatarI 22 | export const Cheff = CheffI 23 | export const Cheff1 = Cheff1I 24 | export const BikeDelivery = BikeDeliveryI 25 | export const HeroBg = HeroBgI 26 | export const Icecream = IcreamI 27 | export const Strawberry = StrawberryI 28 | export const Chicken = ChickenI 29 | export const Fish = FishI 30 | export const EmptyCartImg = EmptyCartI 31 | export const NotFoundImg = NotFoundI 32 | export const CreditCard = Visa 33 | export const MOMO = Momo -------------------------------------------------------------------------------- /src/components/Cart/Body.tsx: -------------------------------------------------------------------------------- 1 | import CartItem from './Item' 2 | import CartTotal from './CartTotal' 3 | import { useStateValue } from '../../context/StateProvider'; 4 | 5 | const CartBody = ({action}:{action:any}) => { 6 | const [{cartItems}] = useStateValue(); 7 | return ( 8 |
9 |
10 | { 11 | cartItems && cartItems.length > 0 && cartItems.map((item:any, index:number) => { 12 | return 13 | } ) 14 | } 15 |
16 | 17 |
18 | ) 19 | } 20 | 21 | export default CartBody -------------------------------------------------------------------------------- /src/components/Cart/CartTotal.tsx: -------------------------------------------------------------------------------- 1 | import {motion} from 'framer-motion' 2 | import { useStateValue } from '../../context/StateProvider'; 3 | 4 | const CartTotal = ({checkoutState}: {checkoutState:any}) => { 5 | const [{cartTotal}] = useStateValue(); 6 | return ( 7 |
8 |
9 |

Sub Total

10 |

-

11 |

{cartTotal}

12 |
13 |
14 |

Delivery

15 |

-

16 |

{0.00}

17 |
18 |
19 |
20 |

Total

21 |

-

22 |

{cartTotal}

23 |
24 | checkoutState(true)} whileTap={{scale:0.8}} className='w-full p-2 rounded-full bg-gradient-to-tr from-orange-400 to-orange-600 text-gray-50 text-lg my-2 hover:shadow-lg'> 25 | Checkout ₵{cartTotal} 26 | 27 | 28 |
29 | ) 30 | } 31 | 32 | export default CartTotal -------------------------------------------------------------------------------- /src/components/Cart/Header.tsx: -------------------------------------------------------------------------------- 1 | import { BiRefresh } from "react-icons/bi"; 2 | import { MdLogin, MdOutlineKeyboardBackspace } from "react-icons/md"; 3 | import { motion } from "framer-motion"; 4 | import { MdShoppingBasket } from "react-icons/md"; 5 | import { useStateValue } from "../../context/StateProvider"; 6 | import { emptyCart, hideCart } from "../../utils/functions"; 7 | import { Link } from "react-router-dom"; 8 | const CartHeader = () => { 9 | const [{ user, cartItems, foodItems }, dispatch] = useStateValue(); 10 | 11 | return ( 12 |
13 | hideCart(dispatch)}> 14 | 15 | 16 | 17 |
18 | Cart 19 | 20 |
21 | 22 | {user ? ( 23 | emptyCart(cartItems, foodItems, dispatch)} 27 | className="flex items-center justify-center gap-2 p-1 px-2 my-2 bg-cardOverlay rounded-md hover:shadow-sm text-textColor text-base" 28 | > 29 | clear 30 | 31 | ) : ( 32 | hideCart(dispatch)}> 33 | 38 | Login to cart 39 | 40 | 41 | )} 42 |
43 | ); 44 | }; 45 | 46 | export default CartHeader; 47 | -------------------------------------------------------------------------------- /src/components/Cart/Item.tsx: -------------------------------------------------------------------------------- 1 | import { BiMinus, BiPlus } from "react-icons/bi"; 2 | 3 | import { MdDelete } from "react-icons/md"; 4 | import { motion } from "framer-motion"; 5 | import { cartItem } from "../../../types"; 6 | import { deleteCartItem, getFoodyById, updateCartItemQty } from "../../utils/functions"; 7 | import { useStateValue } from "../../context/StateProvider"; 8 | 9 | const CartItem = ({ item }: { item: cartItem }) => { 10 | const [{ foodItems, cartItems }, dispatch] = useStateValue(); 11 | const { id, fid, qty } = item; 12 | const foodItem = getFoodyById(foodItems, fid); 13 | 14 | return ( 15 |
16 |
17 | 22 | 23 |
24 |

{foodItem?.title}

25 |

26 | {foodItem?.price} 27 |

28 |
29 |
30 | 31 |
32 | 1 ? () => updateCartItemQty(cartItems, foodItems, item, dispatch, -1) : () => {}} 36 | > 37 | 38 | 39 |

40 | {qty} 41 |

42 | updateCartItemQty(cartItems, foodItems, item, dispatch, 1)} 46 | > 47 | 48 | 49 |
50 | 51 | deleteCartItem(cartItems, foodItems, item, dispatch)} 55 | > 56 | 57 | 58 |
59 | ); 60 | }; 61 | 62 | export default CartItem; 63 | -------------------------------------------------------------------------------- /src/components/Cart/index.tsx: -------------------------------------------------------------------------------- 1 | import { useStateValue } from "../../context/StateProvider"; 2 | import CartBody from "./Body"; 3 | import CarttHeader from "./Header"; 4 | import { motion } from "framer-motion"; 5 | import EmptyCart from "../EmptyCart"; 6 | import NotFound from "../NotFound"; 7 | import Checkout from "../Checkout"; 8 | import { useState } from "react"; 9 | const Cart = () => { 10 | const [{ cartItems }] = useStateValue(); 11 | const [checkoutOpen, setCheckoutOpen] = useState(false); 12 | return ( 13 | <> 14 | {checkoutOpen ? ( 15 | 16 | ) : ( 17 | <> 18 | 24 | 25 | {cartItems && cartItems.length > 0 ? ( 26 | 27 | ) : ( 28 |
29 | 30 |
31 | )} 32 |
33 | {!cartItems && } 34 | 35 | )} 36 | 37 | ); 38 | }; 39 | 40 | export default Cart; 41 | -------------------------------------------------------------------------------- /src/components/Checkout/Header.tsx: -------------------------------------------------------------------------------- 1 | import { MdOutlineKeyboardBackspace } from "react-icons/md"; 2 | import { motion } from "framer-motion"; 3 | import { RiSecurePaymentLine } from "react-icons/ri"; 4 | import { BsShieldLock } from "react-icons/bs"; 5 | 6 | const Header = ({ action }: { action: any }) => { 7 | return ( 8 |
9 | action(false)}> 10 | 11 | 12 | 17 |

Secured Payment

18 |
19 | 24 | 25 | 26 | 27 |
28 | ); 29 | }; 30 | 31 | export default Header; 32 | -------------------------------------------------------------------------------- /src/components/Checkout/Selector.tsx: -------------------------------------------------------------------------------- 1 | import { useStateValue } from "../../context/StateProvider"; 2 | import { CreditCard, MOMO } from "../Assets"; 3 | const Selector = () => { 4 | const [{paymentMethod}, dispatch] = useStateValue(); 5 | const setPaymentMethod = (method:string) => { 6 | dispatch({ 7 | type: "SET_PAYMENT_METHOD", 8 | paymentMethod: method, 9 | }); 10 | } 11 | return ( 12 |
13 |
18 | 32 |
33 |
38 | 52 |
53 |
54 | ); 55 | }; 56 | 57 | export default Selector; 58 | -------------------------------------------------------------------------------- /src/components/Checkout/body.tsx: -------------------------------------------------------------------------------- 1 | import { BiLock } from "react-icons/bi"; 2 | import CardForm from "./forms/Card"; 3 | import CheckoutFooter from "./footer"; 4 | import MomoForm from "./forms/Momo"; 5 | import Selector from "./Selector"; 6 | import { motion } from "framer-motion"; 7 | import { useStateValue } from "../../context/StateProvider"; 8 | import { emptyCart } from "../../utils/functions"; 9 | import { useState } from "react"; 10 | import { ImSpinner3 } from "react-icons/im"; 11 | import { toast } from "react-toastify"; 12 | 13 | const Body = ({ action }: { action: any }) => { 14 | const [{ checkoutData, cartTotal, paymentMethod, cartItems, foodItems }, dispatch] = 15 | useStateValue(); 16 | const [loading, setLoading] = useState(false); 17 | 18 | const completePayment = () => { 19 | if(!checkoutData) return toast.error("Complete payment info") 20 | setLoading(true); 21 | setTimeout(async () => { 22 | setLoading(false); 23 | await emptyCart(cartItems, foodItems, dispatch); 24 | action(false); 25 | toast.success("Order completed successfuly with payment. Thank you for your patronage.", { 26 | position: "top-center", 27 | autoClose: 6000 28 | }); 29 | }, 3000); 30 | }; 31 | return ( 32 |
33 | {/* Payment Selectors */} 34 | 35 | {/* payment form */} 36 |
37 | {paymentMethod === "mobile_money" ? : } 38 |
39 |

40 | Amount Due:{" "} 41 | {`GH₵${cartTotal}`}{" "} 42 |

43 |
44 | {/* pay now button */} 45 | 46 |
47 | 52 | {!loading && } 53 | {!loading ? ( 54 | "PAY NOW" 55 | ) : ( 56 | 57 | )} 58 | 59 |
60 |
61 | 62 |
63 | ); 64 | }; 65 | 66 | export default Body; 67 | -------------------------------------------------------------------------------- /src/components/Checkout/footer.tsx: -------------------------------------------------------------------------------- 1 | import { BsShieldLock } from "react-icons/bs"; 2 | 3 | const CheckoutFooter = () => { 4 | return ( 5 |
6 |
7 | 8 |

9 | Secured by BENSTACK 10 |

11 |
12 |
13 | ); 14 | }; 15 | 16 | export default CheckoutFooter; 17 | -------------------------------------------------------------------------------- /src/components/Checkout/forms/Card.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const CardForm = () => { 4 | return ( 5 |
6 |
7 | 13 | 20 |
21 |
22 | 28 | 35 |
36 |
37 |
38 | 44 | 51 |
52 |
53 | 59 | 66 |
67 |
68 |
69 | ) 70 | } 71 | 72 | export default CardForm -------------------------------------------------------------------------------- /src/components/Checkout/forms/Momo.tsx: -------------------------------------------------------------------------------- 1 | import { useStateValue } from "../../../context/StateProvider"; 2 | 3 | const MomoForm = () => { 4 | const [{checkoutData}, dispatch] = useStateValue(); 5 | const updateCheckoutData = (key:string, val:string) => { 6 | dispatch({ 7 | type: "UPDATE_CHECKOUT_DATA", 8 | checkoutData: { 9 | ...checkoutData, 10 | [key]:val 11 | } 12 | }); 13 | } 14 | return ( 15 |
16 |
17 | 23 | 34 |
35 |
36 | 42 | 49 |
50 |
51 | 57 | updateCheckoutData("phone", e.target.value)} 64 | /> 65 |
66 |
67 | ) 68 | } 69 | 70 | export default MomoForm -------------------------------------------------------------------------------- /src/components/Checkout/index.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import Body from "./body"; 3 | import Header from "./Header"; 4 | const Checkout = ({handler}: {handler: any}) => { 5 | return ( 6 | 12 |
13 | 14 | 15 | ); 16 | }; 17 | 18 | export default Checkout; 19 | -------------------------------------------------------------------------------- /src/components/Contact/form.tsx: -------------------------------------------------------------------------------- 1 | import {EmptyCartImg} from '../Assets' 2 | import { toast } from 'react-toastify'; 3 | import { useState } from 'react'; 4 | 5 | const Form = () => { 6 | const [name, setName] = useState('') 7 | const [email, setEmail] = useState('') 8 | const [message, setMessage] = useState('') 9 | const [subject, setSubject] = useState('') 10 | 11 | const submitForm = (e:any) => { 12 | e.preventDefault() 13 | return toast.info(`${name} Form handling is not implemented yet`, { 14 | position: 'top-left', 15 | autoClose: 3000, 16 | toastId: 'form' 17 | }) 18 | } 19 | return ( 20 |
21 | not found 22 |
23 |
24 | setName(e.target.value)} 30 | /> 31 |
32 |
33 | setEmail(e.target.value)} 39 | /> 40 |
41 |
42 | setSubject(e.target.value)} 48 | /> 49 |
50 |
51 |