├── .env.local.example ├── .gitignore ├── LICENSE ├── README.md ├── dishes.json ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── img │ ├── Zinger.svg │ ├── authentication.svg │ ├── circle.svg │ ├── dishes │ │ ├── anaar_juice.jpg │ │ ├── blueberry_cake.jpg │ │ ├── butterscotch_cake.jpg │ │ ├── chicken_clear_soup.jpg │ │ ├── chicken_crispy.jpg │ │ ├── chicken_dominator.jpg │ │ ├── chicken_dragon.jpg │ │ ├── chicken_golden_delight.jpg │ │ ├── chicken_hot_and_sour_soup.jpg │ │ ├── chicken_lollipop.jpg │ │ ├── chicken_manchow_soup.jpg │ │ ├── chicken_mongolian.jpg │ │ ├── chicken_sangrila.jpg │ │ ├── choco_truffle_cake.jpg │ │ ├── coconut_juice.jpg │ │ ├── deluxe_veggie.jpg │ │ ├── indi_tandoori_paneer.jpg │ │ ├── kiwi_juice.jpg │ │ ├── mix_fruit_juice.jpg │ │ ├── mosambi_juice.jpg │ │ ├── movie_marathons_specials.jpg │ │ ├── non_veg_supremer.jpg │ │ ├── pineapple_cake.jpg │ │ ├── tomato_soup.jpg │ │ ├── veg_clear_soup.jpg │ │ ├── veg_hot_and_sour_soup.jpg │ │ └── veg_manchow_soup.jpg │ ├── eating_together.svg │ ├── empty.svg │ ├── empty_cart.svg │ ├── favicons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ ├── food gallery │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ ├── 7.jpg │ │ └── 8.jpg │ ├── profile_pic.svg │ ├── programming.svg │ ├── social │ │ ├── email.svg │ │ ├── github.svg │ │ └── linkedin.svg │ └── testimonials │ │ ├── customer-1.jpg │ │ ├── customer-2.jpg │ │ └── customer-3.jpg ├── sw.js ├── sw.js.map ├── workbox-6b19f60b.js └── workbox-6b19f60b.js.map ├── src ├── app │ └── store.js ├── components │ ├── About │ │ └── About.js │ ├── Banner │ │ └── Banner.js │ ├── CartDish │ │ └── CartDish.js │ ├── Dish │ │ ├── Dish.js │ │ └── DishInfo.js │ ├── Dropdown │ │ └── Dropdown.js │ ├── FoodGallery │ │ └── FoodGallery.js │ ├── Footer │ │ └── Footer.js │ ├── Header │ │ ├── Header.js │ │ ├── HeaderDashboard.js │ │ └── HeaderMobile.js │ ├── HowItWork │ │ └── HowItWork.js │ ├── Info │ │ └── Info.js │ ├── Layout │ │ └── Layout.js │ ├── Menu │ │ └── Menu.js │ ├── Order │ │ ├── Order.js │ │ ├── OrderDetails.js │ │ └── OrderItem.js │ ├── Search │ │ └── Search.js │ ├── SideBarMenu │ │ └── SideBarMenu.js │ └── Testimonials │ │ └── Testimonials.js ├── pages │ ├── 404.js │ ├── 500.js │ ├── _app.js │ ├── about.js │ ├── admin │ │ ├── add-category.js │ │ ├── add-dish.js │ │ ├── dashboard.js │ │ ├── dishes.js │ │ ├── index.js │ │ ├── order-details │ │ │ └── [id].js │ │ ├── update-dish │ │ │ └── [id].js │ │ └── users.js │ ├── api │ │ ├── admin │ │ │ ├── active-orders.js │ │ │ ├── add-category.js │ │ │ ├── add-dish.js │ │ │ ├── delete-dish.js │ │ │ ├── update-dish.js │ │ │ ├── update-order-status.js │ │ │ └── users.js │ │ ├── auth │ │ │ └── [...nextauth].js │ │ ├── cancel-order.js │ │ ├── categories.js │ │ ├── create-checkout-session.js │ │ ├── dishes.js │ │ ├── order-details │ │ │ └── [id].js │ │ ├── orders.js │ │ └── webhook.js │ ├── cart.js │ ├── index.js │ ├── order-details │ │ └── [id].js │ ├── orders.js │ ├── profile.js │ └── success.js ├── slices │ └── cartSlice.js ├── styles │ └── globals.css └── util │ ├── StorageService.js │ ├── Toast │ ├── NormalToast.js │ └── addedToCartToast.js │ ├── fetch.js │ ├── getCategories.js │ ├── getDishes.js │ └── mongodb.js ├── tailwind.config.js └── yarn.lock /.env.local.example: -------------------------------------------------------------------------------- 1 | 2 | # Authentication 3 | GOOGLE_ID= 4 | GOOGLE_SECRET= 5 | 6 | # Need to add this to... google cloud 7 | # http://localhost:3000/api/auth/callback/google 8 | 9 | 10 | NEXTAUTH_URL=http://localhost:3000 11 | 12 | 13 | HOST=http://localhost:3000 14 | 15 | 16 | # Stripe 17 | STRIPE_PUBLIC_KEY= 18 | STRIPE_SECRET_KEY= 19 | 20 | 21 | # Stripe Terminal/CLI 22 | STRIPE_SIGNING_SECRET= 23 | 24 | # Testing Webhook 25 | # stripe listen --forward-to localhost:3000/api/webhook 26 | 27 | 28 | # Mongodb Database 29 | # Use mongodb connection url with driver node.js and version 2.2.12 or later 30 | MONGODB_URI= 31 | # Your database name 32 | MONGODB_DB= 33 | # Add monogdb connection url 34 | MONGO_URI= 35 | 36 | -------------------------------------------------------------------------------- /.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Piyush Sati 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 7 | 8 | 9 | 13 | 14 |
15 |
16 | 17 | 18 | 19 | # Zinger 20 | 21 | Food ordering website for Zinger restaurant built using Next.js, TailwindCSS, Redux, Mongodb 22 | 23 | ![Logo](https://zinger.vercel.app/_next/image?url=%2Fimg%2FZinger.svg&w=128&q=75) 24 | 25 | 26 | 27 | 28 | ## Demo 29 | 30 | https://zinger.vercel.app 31 | 32 | 33 | ## Screenshots 34 | 35 | ![App Screenshot](https://i.ibb.co/8x3ZGnG/zinger.gif) 36 | 37 | 38 | ## Features 39 | 40 | - Responsive 41 | - Real Time and Dynamic 42 | - Progressive Web App (PWA) 43 | - Payment Gateway integration 44 | - Admin Dashboard with functionalities like adding products, deleting a product, updating products, adding a category, viewing users registered, updating order status, and canceling orders. 45 | - State management using Redux 46 | - Google authentication 47 | - Track order status real time 48 | - Cancel orders 49 | 50 | 51 | ## Run Locally 52 | 53 | Clone the project 54 | 55 | ```bash 56 | git clone https://github.com/Pinqua/Zinger.git 57 | ``` 58 | 59 | Go to the project directory 60 | 61 | ```bash 62 | cd Zinger 63 | ``` 64 | 65 | Install dependencies 66 | 67 | ```bash 68 | npm install 69 | ``` 70 | 71 | Create a **.env.local** file inside project directory with fields given below. 72 | 73 | ```bash 74 | # Authentication 75 | GOOGLE_ID= 76 | GOOGLE_SECRET= 77 | 78 | # Need to add this to... google cloud 79 | # http://localhost:3000/api/auth/callback/google 80 | 81 | 82 | NEXTAUTH_URL=http://localhost:3000 83 | 84 | 85 | HOST=http://localhost:3000 86 | 87 | 88 | # Stripe 89 | STRIPE_PUBLIC_KEY= 90 | STRIPE_SECRET_KEY= 91 | 92 | 93 | # Stripe Terminal/CLI 94 | STRIPE_SIGNING_SECRET= 95 | 96 | # Testing Webhook 97 | # stripe listen --forward-to localhost:3000/api/webhook 98 | 99 | 100 | # Mongodb Database 101 | 102 | # Your database name 103 | MONGODB_DB= 104 | # Add monogdb connection url 105 | MONGO_URI= 106 | # Add mongodb connection url but with driver node.js and version 2.2.12 or later 107 | MONGODB_URI= 108 | 109 | ``` 110 | 111 | Start the server 112 | 113 | ```bash 114 | npm run dev 115 | ``` 116 | 117 | Admin Access 118 | 119 | ``` 120 | To gain admin access, you need to add your email ID to the admin collection in MongoDB. 121 | After adding it, try logging in with the same email ID, and you should see the dashboard option. 122 | ``` 123 | ![182356880-d13b94f1-2a21-4e4e-8d1a-bb4faca0e61d (1)](https://user-images.githubusercontent.com/69719134/235835845-66a9ba70-e8d3-47f3-a213-298fcf3d0b89.png) 124 | 125 | 126 | 127 | 128 | ## Stripe Payment Gateway 129 | 130 | Test Stripe payment gateway with these card details. 131 | 132 | ``` 133 | BRAND - VISA 134 | CARD NUMBER - 4242424242424242 135 | CVC - Any 3 digits 136 | DATE - Any future date 137 | ``` 138 | 139 | See details: https://stripe.com/docs/testing 140 | 141 | 142 | ## Contributing 143 | 144 | Contributions are always welcome! 145 | 146 | 147 | ## License 148 | 149 | [MIT](https://choosealicense.com/licenses/mit/) 150 | 151 |
152 |
153 | 154 |

If you liked the repository, show your ❤️ by starring and forking it.

155 | 156 | -------------------------------------------------------------------------------- /dishes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title":"Chicken Crispy", 4 | "price":150, 5 | "description":"Chicken chunks, deep-fried and tossed in a fiery red marinade of garlic, red chilli, soy. A must-try starter!", 6 | "category":"chicken", 7 | "image":"/img/dishes/chicken_crispy.jpg" 8 | } 9 | , 10 | { 11 | "title":"Chicken Dragon", 12 | "price":185, 13 | "description":"A hearty starter with deep-fried chicken chunks coated in a flavour-packed sauce with a strong flavor of garlic.", 14 | "category":"chicken", 15 | "image":"/img/dishes/chicken_dragon.jpg" 16 | }, 17 | { 18 | "title":"Chicken Sangrila", 19 | "price":185, 20 | "description":"A flavor-packed dish with succulent chicken chunks coated in seasoned batter, deep-fried, and tossed in a spicy and thick Manchurian sauce.", 21 | "category":"chicken", 22 | "image":"/img/dishes/chicken_sangrila.jpg" 23 | }, 24 | { 25 | "title":"Chicken Lollipop", 26 | "price":150, 27 | "description":"Juicy chicken wings covered in a fiery red marinade of garlic, red chilli, soy and deep-fried.", 28 | "category":"chicken", 29 | "image":"/img/dishes/chicken_lollipop.jpg" 30 | }, 31 | { 32 | "title":"Chicken Mongolian", 33 | "price":180, 34 | "description":"A deliciously spicy dish with juicy chicken chunks coated in seasoned batter, deep fried, and tossed in a hot and spicy sauce.", 35 | "category":"chicken", 36 | "image":"/img/dishes/chicken_mongolian.jpg" 37 | }, 38 | { 39 | "title":"Tomato Soup", 40 | "price":70, 41 | "description":"A deliciously creamy and tangy soup made from fresh, ripe tomatoes.", 42 | "category":"soup", 43 | "image":"/img/dishes/tomato_soup.jpg" 44 | } 45 | , 46 | { 47 | "title":"Veg Manchow Soup", 48 | "price":70, 49 | "description":"A slightly spicy and thick soup simmered in hot and spicy and topped with fried noodles.", 50 | "category":"soup", 51 | "image":"/img/dishes/veg_manchow_soup.jpg" 52 | } 53 | , 54 | { 55 | "title":"Veg Hot and Sour Soup", 56 | "price":80, 57 | "description":"A delectable hot and sour soup packed with the goodness of vegetables.", 58 | "category":"soup", 59 | "image":"/img/dishes/veg_hot_and_sour_soup.jpg" 60 | } 61 | , 62 | { 63 | "title":"Veg Clear Soup", 64 | "price":50, 65 | "description":"A flavour packed soup prepared from vegetables simmered in a seasoned vegetable stock.", 66 | "category":"soup", 67 | "image":"/img/dishes/veg_clear_soup.jpg" 68 | } 69 | , 70 | { 71 | "title":"Chicken Clear Soup", 72 | "price":70, 73 | "description":"A flavour packed soup prepared by simmering chicken slivers in a seasoned stock.", 74 | "category":"soup", 75 | "image":"/img/dishes/chicken_clear_soup.jpg" 76 | } 77 | , 78 | { 79 | "title":"Chicken Manchow Soup", 80 | "price":80, 81 | "description":"A slightly spicy and thick soup simmered in hot and spicy flavors with chicken chunks, topped with fried noodles.", 82 | "category":"soup", 83 | "image":"/img/dishes/chicken_manchow_soup.jpg" 84 | } 85 | , 86 | { 87 | "title":"Chicken Hot and Sour Soup", 88 | "price":80, 89 | "description":"A delectable yet tangy flavoured soup packed with chicken chunks - served with fried noodles.", 90 | "category":"soup", 91 | "image":"/img/dishes/chicken_hot_and_sour_soup.jpg" 92 | }, 93 | { 94 | "title":"Butterscotch Cake [500 grams]", 95 | "price":300, 96 | "description":"Vanilla base layered with caramel blended vanilla cream and loaded with crunchy butter scotch nuts. Eggless cake", 97 | "category":"cake", 98 | "image":"/img/dishes/butterscotch_cake.jpg" 99 | }, 100 | { 101 | "title":"Choco Truffle Cake [500 grams]", 102 | "price":320, 103 | "description":"Chocolate sponge layered with a delightful combination of chocolate truffle and chocolate cream. Eggless cake", 104 | "category":"cake", 105 | "image":"/img/dishes/choco_truffle_cake.jpg" 106 | } 107 | , 108 | { 109 | "title":"Blueberry Cake [500 grams]", 110 | "price":290, 111 | "description":"Delicious cake with sweet blueberry compote, served cold.", 112 | "category":"cake", 113 | "image":"/img/dishes/blueberry_cake.jpg" 114 | } 115 | , 116 | { 117 | "title":"Pineapple Cake [500 grams]", 118 | "price":290, 119 | "description":"Vanilla sponge, vanilla cream, loaded with juicy pineapple cubes and added pineapple crush to enhance flavour. Eggless cake", 120 | "category":"cake", 121 | "image":"/img/dishes/pineapple_cake.jpg" 122 | } 123 | , 124 | { 125 | "title":"Chicken Dominator", 126 | "price":319, 127 | "description":"Loaded with double pepper barbecue chicken, peri-peri chicken, chicken tikka & grilled chicken rashers", 128 | "category":"pizza", 129 | "image":"/img/dishes/chicken_dominator.jpg" 130 | } 131 | , 132 | { 133 | "title":"Non Veg Supreme", 134 | "price":319, 135 | "description":"Supreme combination of black olives, onion, capsicum, grilled mushroom, pepper barbecue chicken, peri-peri chicken & grilled chicken rashers", 136 | "category":"pizza", 137 | "image":"/img/dishes/non_veg_supremer.jpg" 138 | } 139 | , 140 | { 141 | "title":"Indi Tandoori Paneer", 142 | "price":249, 143 | "description":"It is hot. It is spicy. It is oh-so-Indian. Tandoori paneer with capsicum, red paprika & mint mayo", 144 | "category":"pizza", 145 | "image":"/img/dishes/indi_tandoori_paneer.jpg" 146 | } 147 | , 148 | { 149 | "title":"Deluxe Veggie", 150 | "price":249, 151 | "description":"Veg delight - onion, capsicum, grilled mushroom, corn & paneer", 152 | "category":"pizza", 153 | "image":"/img/dishes/deluxe_veggie.jpg" 154 | } 155 | , 156 | { 157 | "title":"Chicken Golden Delight", 158 | "price":249, 159 | "description":"Double pepper barbecue chicken, golden corn and extra cheese, true delight", 160 | "category":"pizza", 161 | "image":"/img/dishes/chicken_golden_delight.jpg" 162 | } 163 | , 164 | { 165 | "title":"Movie Marathons Specials (Non Veg)", 166 | "price":335, 167 | "description":"Reg Pepper BBQ Chicken Pizza + Garlic Bread + Pepsi", 168 | "category":"pizza", 169 | "image":"/img/dishes/movie_marathons_specials.jpg" 170 | } 171 | , 172 | { 173 | "title":"Kiwi Juice", 174 | "price":60, 175 | "description":"Made from the exotic kiwi fruit.", 176 | "category":"juice", 177 | "image":"/img/dishes/kiwi_juice.jpg" 178 | }, 179 | { 180 | "title":"Mix Fruit Juice", 181 | "price":50, 182 | "description":"A classy mixture of all the fresh fruits together.", 183 | "category":"juice", 184 | "image":"/img/dishes/mix_fruit_juice.jpg" 185 | } 186 | , 187 | { 188 | "title":"Anaar Juice", 189 | "price":50, 190 | "description":"Best in class Anaar juice made with real fruits and nothing else.", 191 | "category":"juice", 192 | "image":"/img/dishes/anaar_juice.jpg" 193 | }, 194 | 195 | { 196 | "title":"Mosambi Juice", 197 | "price":35, 198 | "description":"Refreshing Mosambui Juice", 199 | "category":"juice", 200 | "image":"/img/dishes/mosambi_juice.jpg" 201 | }, 202 | 203 | { 204 | "title":"Coconut juice", 205 | "price":35, 206 | "description":"From the trees of coconut, this natural juice will make your day!", 207 | "category":"juice", 208 | "image":"/img/dishes/coconut_juice.jpg" 209 | } 210 | ] -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withPWA = require("next-pwa"); 2 | const runtimeCaching = require('next-pwa/cache') 3 | 4 | module.exports = withPWA({ 5 | pwa: { 6 | disable: process.env.NODE_ENV === "development", 7 | dest: "public", 8 | runtimeCaching, 9 | }, 10 | env: { 11 | stripe_public_key: process.env.STRIPE_PUBLIC_KEY, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zinger", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@heroicons/react": "^1.0.1", 12 | "@reduxjs/toolkit": "^1.6.0", 13 | "@stripe/stripe-js": "^1.15.1", 14 | "@tailwindcss/line-clamp": "^0.2.1", 15 | "axios": "^0.21.1", 16 | "fuse.js": "^6.4.6", 17 | "micro": "^9.3.4", 18 | "moment": "^2.29.1", 19 | "mongodb": "^3.6.9", 20 | "next": "11.0.1", 21 | "next-auth": "^3.27.2", 22 | "next-pwa": "^5.2.23", 23 | "nprogress": "^0.2.0", 24 | "react": "17.0.2", 25 | "react-currency-formatter": "^1.1.0", 26 | "react-dom": "17.0.2", 27 | "react-loader-spinner": "^4.0.0", 28 | "react-loading-skeleton": "^2.2.0", 29 | "react-onclickoutside": "^6.11.2", 30 | "react-redux": "^7.2.4", 31 | "react-reveal": "^1.2.2", 32 | "react-toastify": "^7.0.4", 33 | "stripe": "^8.160.0", 34 | "swr": "^0.5.6" 35 | }, 36 | "devDependencies": { 37 | "autoprefixer": "^10.2.6", 38 | "postcss": "^8.3.5", 39 | "tailwindcss": "^2.2.4" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/img/Zinger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/img/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/img/dishes/anaar_juice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/anaar_juice.jpg -------------------------------------------------------------------------------- /public/img/dishes/blueberry_cake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/blueberry_cake.jpg -------------------------------------------------------------------------------- /public/img/dishes/butterscotch_cake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/butterscotch_cake.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_clear_soup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_clear_soup.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_crispy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_crispy.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_dominator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_dominator.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_dragon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_dragon.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_golden_delight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_golden_delight.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_hot_and_sour_soup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_hot_and_sour_soup.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_lollipop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_lollipop.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_manchow_soup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_manchow_soup.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_mongolian.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_mongolian.jpg -------------------------------------------------------------------------------- /public/img/dishes/chicken_sangrila.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/chicken_sangrila.jpg -------------------------------------------------------------------------------- /public/img/dishes/choco_truffle_cake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/choco_truffle_cake.jpg -------------------------------------------------------------------------------- /public/img/dishes/coconut_juice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/coconut_juice.jpg -------------------------------------------------------------------------------- /public/img/dishes/deluxe_veggie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/deluxe_veggie.jpg -------------------------------------------------------------------------------- /public/img/dishes/indi_tandoori_paneer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/indi_tandoori_paneer.jpg -------------------------------------------------------------------------------- /public/img/dishes/kiwi_juice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/kiwi_juice.jpg -------------------------------------------------------------------------------- /public/img/dishes/mix_fruit_juice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/mix_fruit_juice.jpg -------------------------------------------------------------------------------- /public/img/dishes/mosambi_juice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/mosambi_juice.jpg -------------------------------------------------------------------------------- /public/img/dishes/movie_marathons_specials.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/movie_marathons_specials.jpg -------------------------------------------------------------------------------- /public/img/dishes/non_veg_supremer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/non_veg_supremer.jpg -------------------------------------------------------------------------------- /public/img/dishes/pineapple_cake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/pineapple_cake.jpg -------------------------------------------------------------------------------- /public/img/dishes/tomato_soup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/tomato_soup.jpg -------------------------------------------------------------------------------- /public/img/dishes/veg_clear_soup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/veg_clear_soup.jpg -------------------------------------------------------------------------------- /public/img/dishes/veg_hot_and_sour_soup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/veg_hot_and_sour_soup.jpg -------------------------------------------------------------------------------- /public/img/dishes/veg_manchow_soup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/dishes/veg_manchow_soup.jpg -------------------------------------------------------------------------------- /public/img/empty_cart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /public/img/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/img/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/favicon.ico -------------------------------------------------------------------------------- /public/img/favicons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/mstile-144x144.png -------------------------------------------------------------------------------- /public/img/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/favicons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/mstile-310x150.png -------------------------------------------------------------------------------- /public/img/favicons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/mstile-310x310.png -------------------------------------------------------------------------------- /public/img/favicons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/favicons/mstile-70x70.png -------------------------------------------------------------------------------- /public/img/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/img/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Zinger", 3 | "short_name": "Zinger", 4 | "description": "Food ordering website for Zinger restaurant build with 💗 🔥 by Piyush Sati", 5 | "icons": [ 6 | { 7 | "src": "/img/favicons/android-chrome-192x192.png", 8 | "sizes": "192x192", 9 | "type": "image/png" 10 | }, 11 | { 12 | "src": "/img/favicons/android-chrome-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png" 15 | } 16 | ], 17 | "start_url": "/", 18 | "theme_color": "#ffffff", 19 | "background_color": "#ffffff", 20 | "display": "standalone" 21 | } -------------------------------------------------------------------------------- /public/img/food gallery/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/food gallery/1.jpg -------------------------------------------------------------------------------- /public/img/food gallery/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/food gallery/2.jpg -------------------------------------------------------------------------------- /public/img/food gallery/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/food gallery/3.jpg -------------------------------------------------------------------------------- /public/img/food gallery/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/food gallery/4.jpg -------------------------------------------------------------------------------- /public/img/food gallery/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/food gallery/5.jpg -------------------------------------------------------------------------------- /public/img/food gallery/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/food gallery/6.jpg -------------------------------------------------------------------------------- /public/img/food gallery/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/food gallery/7.jpg -------------------------------------------------------------------------------- /public/img/food gallery/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/food gallery/8.jpg -------------------------------------------------------------------------------- /public/img/profile_pic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/img/social/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/img/social/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/img/social/linkedin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/img/testimonials/customer-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/testimonials/customer-1.jpg -------------------------------------------------------------------------------- /public/img/testimonials/customer-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/testimonials/customer-2.jpg -------------------------------------------------------------------------------- /public/img/testimonials/customer-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pinqua/Zinger/5593b46371c5e41565a7351886cb0e280b891669/public/img/testimonials/customer-3.jpg -------------------------------------------------------------------------------- /public/sw.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * Unless required by applicable law or agreed to in writing, software 8 | * distributed under the License is distributed on an "AS IS" BASIS, 9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | * See the License for the specific language governing permissions and 11 | * limitations under the License. 12 | */ 13 | 14 | // If the loader is already loaded, just stop. 15 | if (!self.define) { 16 | const singleRequire = name => { 17 | if (name !== 'require') { 18 | name = name + '.js'; 19 | } 20 | let promise = Promise.resolve(); 21 | if (!registry[name]) { 22 | 23 | promise = new Promise(async resolve => { 24 | if ("document" in self) { 25 | const script = document.createElement("script"); 26 | script.src = name; 27 | document.head.appendChild(script); 28 | script.onload = resolve; 29 | } else { 30 | importScripts(name); 31 | resolve(); 32 | } 33 | }); 34 | 35 | } 36 | return promise.then(() => { 37 | if (!registry[name]) { 38 | throw new Error(`Module ${name} didn’t register its module`); 39 | } 40 | return registry[name]; 41 | }); 42 | }; 43 | 44 | const require = (names, resolve) => { 45 | Promise.all(names.map(singleRequire)) 46 | .then(modules => resolve(modules.length === 1 ? modules[0] : modules)); 47 | }; 48 | 49 | const registry = { 50 | require: Promise.resolve(require) 51 | }; 52 | 53 | self.define = (moduleName, depsNames, factory) => { 54 | if (registry[moduleName]) { 55 | // Module is already loading or loaded. 56 | return; 57 | } 58 | registry[moduleName] = Promise.resolve().then(() => { 59 | let exports = {}; 60 | const module = { 61 | uri: location.origin + moduleName.slice(1) 62 | }; 63 | return Promise.all( 64 | depsNames.map(depName => { 65 | switch(depName) { 66 | case "exports": 67 | return exports; 68 | case "module": 69 | return module; 70 | default: 71 | return singleRequire(depName); 72 | } 73 | }) 74 | ).then(deps => { 75 | const facValue = factory(...deps); 76 | if(!exports.default) { 77 | exports.default = facValue; 78 | } 79 | return exports; 80 | }); 81 | }); 82 | }; 83 | } 84 | define("./sw.js",['./workbox-6b19f60b'], function (workbox) { 'use strict'; 85 | 86 | /** 87 | * Welcome to your Workbox-powered service worker! 88 | * 89 | * You'll need to register this file in your web app. 90 | * See https://goo.gl/nhQhGp 91 | * 92 | * The rest of the code is auto-generated. Please don't update this file 93 | * directly; instead, make changes to your Workbox build configuration 94 | * and re-run your build process. 95 | * See https://goo.gl/2aRDsh 96 | */ 97 | 98 | importScripts(); 99 | self.skipWaiting(); 100 | workbox.clientsClaim(); 101 | workbox.registerRoute("/", new workbox.NetworkFirst({ 102 | "cacheName": "start-url", 103 | plugins: [{ 104 | cacheWillUpdate: async ({ 105 | request, 106 | response, 107 | event, 108 | state 109 | }) => { 110 | if (response && response.type === 'opaqueredirect') { 111 | return new Response(response.body, { 112 | status: 200, 113 | statusText: 'OK', 114 | headers: response.headers 115 | }); 116 | } 117 | 118 | return response; 119 | } 120 | }] 121 | }), 'GET'); 122 | workbox.registerRoute(/.*/i, new workbox.NetworkOnly({ 123 | "cacheName": "dev", 124 | plugins: [] 125 | }), 'GET'); 126 | 127 | }); 128 | //# sourceMappingURL=sw.js.map 129 | -------------------------------------------------------------------------------- /public/sw.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"sw.js","sources":["C:/Users/PIYUSH~1/AppData/Local/Temp/80c96ff4bba0c703891a62888f6c1a97/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from 'D:/__S-M__/react_projects/Projects/zinger/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from 'D:/__S-M__/react_projects/Projects/zinger/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from 'D:/__S-M__/react_projects/Projects/zinger/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from 'D:/__S-M__/react_projects/Projects/zinger/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({request, response, event, state}) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, {status: 200, statusText: 'OK', headers: response.headers}); } return response; } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG+I;EAC/I;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAGAA,aAAa;EAUbC,IAAI,CAACC,WAAL;AAEAC,sBAAyB;AAIzBC,uBAA6B,CAAC,GAAD,EAAM,IAAIC,oBAAJ,CAAoC;EAAE,eAAY,WAAd;EAA2BC,EAAAA,OAAO,EAAE,CAAC;EAAEC,IAAAA,eAAe,EAAE,OAAO;EAACC,MAAAA,OAAD;EAAUC,MAAAA,QAAV;EAAoBC,MAAAA,KAApB;EAA2BC,MAAAA;EAA3B,KAAP,KAA6C;EAAE,UAAIF,QAAQ,IAAIA,QAAQ,CAACG,IAAT,KAAkB,gBAAlC,EAAoD;EAAE,eAAO,IAAIC,QAAJ,CAAaJ,QAAQ,CAACK,IAAtB,EAA4B;EAACC,UAAAA,MAAM,EAAE,GAAT;EAAcC,UAAAA,UAAU,EAAE,IAA1B;EAAgCC,UAAAA,OAAO,EAAER,QAAQ,CAACQ;EAAlD,SAA5B,CAAP;EAAiG;;EAAC,aAAOR,QAAP;EAAkB;EAA5O,GAAD;EAApC,CAApC,CAAN,EAAmU,KAAnU,CAA7B;AACAL,uBAA6B,CAAC,KAAD,EAAQ,IAAIc,mBAAJ,CAAmC;EAAE,eAAY,KAAd;EAAqBZ,EAAAA,OAAO,EAAE;EAA9B,CAAnC,CAAR,EAAgF,KAAhF,CAA7B;;"} -------------------------------------------------------------------------------- /src/app/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import cartReducer from "../slices/cartSlice"; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | cart: cartReducer, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/About/About.js: -------------------------------------------------------------------------------- 1 | function About() { 2 | return ( 3 |
4 |
5 |

Get Food Fast - Not Fast Food

6 |

7 | Hello, we're Zinger, your new food ordering option and restaurant. We 8 | know you're always busy. No time for cooking. So let us take care of 9 | that, we're really good at it, we promise! 10 |

11 |
12 |
13 | ); 14 | } 15 | 16 | export default About; 17 | -------------------------------------------------------------------------------- /src/components/Banner/Banner.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Fade from "react-reveal/Fade"; 3 | 4 | function Banner() { 5 | const orderNow = () => { 6 | window.scrollTo({top:document.getElementById("menu").offsetTop - 90, behavior: 'smooth'}); 7 | //window.location.href='#products-feed' 8 | }; 9 | 10 | const viewMore =()=>{ 11 | window.scrollTo({top:document.getElementById("about").offsetTop - 90, behavior: 'smooth'}); 12 | //window.location.href='#products-feed' 13 | } 14 | return ( 15 |
16 |
17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 | 26 |
27 |
28 |
29 | 30 |
31 |

Are you hungry?

32 |

33 | Don't Wait! 34 |

35 |
36 | 39 | 42 |
43 |
44 |
45 | 46 |
47 | 54 |
55 |
56 |
57 |
58 |
59 | ); 60 | } 61 | 62 | export default Banner; 63 | -------------------------------------------------------------------------------- /src/components/CartDish/CartDish.js: -------------------------------------------------------------------------------- 1 | import { MinusSmIcon, PlusIcon } from "@heroicons/react/solid"; 2 | import Image from "next/image"; 3 | import Currency from "react-currency-formatter"; 4 | import { useDispatch } from "react-redux"; 5 | import { updateQty, removeFromCart } from "../../slices/cartSlice"; 6 | import Fade from "react-reveal/Fade"; 7 | 8 | function CartDish({ 9 | _id, 10 | title, 11 | price, 12 | description, 13 | category, 14 | image, 15 | qty, 16 | border, 17 | disabled, 18 | }) { 19 | const dispatch = useDispatch(); 20 | const total = price * qty; 21 | 22 | const removeItemFromCart = () => dispatch(removeFromCart({ _id })); 23 | const incQty = () => 24 | dispatch( 25 | updateQty({ 26 | _id, 27 | title, 28 | price, 29 | description, 30 | category, 31 | image, 32 | qty: qty + 1, 33 | }) 34 | ); 35 | const decQty = () => 36 | dispatch( 37 | updateQty({ 38 | _id, 39 | title, 40 | price, 41 | description, 42 | category, 43 | image, 44 | qty: qty - 1, 45 | }) 46 | ); 47 | 48 | return ( 49 | 50 |
54 |
55 | 63 |
64 | 65 | {/* Middle */} 66 |
67 |

68 | {title} 69 |

70 |

71 | {description} 72 |

73 | 74 | {qty} × = 75 | 76 | 77 | 78 | 79 |
80 | 81 | {/* Buttons on the right of the dishes */} 82 |
83 |
84 | 91 |
92 | 93 | {qty} 94 | 95 |
96 | 103 |
104 | 112 |
113 |
114 |
115 | ); 116 | } 117 | 118 | export default CartDish; 119 | -------------------------------------------------------------------------------- /src/components/Dish/Dish.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Currency from "react-currency-formatter"; 3 | import { useDispatch } from "react-redux"; 4 | import { addToCart } from "../../slices/cartSlice"; 5 | import Fade from "react-reveal/Fade"; 6 | import { ShoppingCartIcon } from "@heroicons/react/solid"; 7 | 8 | function Dish({ _id, title, price, description, category, image }) { 9 | const dispatch = useDispatch(); 10 | const addItemToCart = () => { 11 | //Sending the Dish as an action to the REDUX store... the cart slice 12 | dispatch( 13 | addToCart({ 14 | _id, 15 | title, 16 | price, 17 | description, 18 | category, 19 | image, 20 | qty: 1, 21 | toast: true, 22 | }) 23 | ); 24 | }; 25 | 26 | return ( 27 | 28 |
29 |

30 | {category} 31 |

32 | 40 |

41 | {title} 42 |

43 |

44 | {description} 45 |

46 |
47 | 48 |
49 | 56 |
57 |
58 | ); 59 | } 60 | 61 | export default Dish; 62 | -------------------------------------------------------------------------------- /src/components/Dish/DishInfo.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Currency from "react-currency-formatter"; 3 | import Image from "next/image"; 4 | import { useRouter } from "next/router"; 5 | import axios from "axios"; 6 | import NormalToast from "../../util/Toast/NormalToast"; 7 | 8 | function DishInfo({ 9 | _id, 10 | title, 11 | price, 12 | description, 13 | category, 14 | image, 15 | border, 16 | removeFromSearchResults, 17 | }) { 18 | const router = useRouter(); 19 | const [disabled, setDisabled] = useState(false); 20 | 21 | const deleteDish = (_id) => { 22 | setDisabled(true); 23 | axios 24 | .post("/api/admin/delete-dish", { _id }) 25 | .then(() => { 26 | NormalToast("Dish deleted"); 27 | removeFromSearchResults(_id); 28 | setDisabled(false); 29 | }) 30 | .catch((err) => { 31 | NormalToast("Something went wrong", true); 32 | console.error(err); 33 | setDisabled(false); 34 | }); 35 | }; 36 | 37 | return ( 38 |
42 |
43 |
{title}
44 |
{category}
45 |

{description}

46 |
47 |

48 | Price - 49 | 50 |

51 |
52 |
53 | 61 | 69 |
70 |
71 |
72 | 79 |
80 |
81 | ); 82 | } 83 | 84 | export default DishInfo; 85 | -------------------------------------------------------------------------------- /src/components/Dropdown/Dropdown.js: -------------------------------------------------------------------------------- 1 | import { signOut, useSession } from "next-auth/client"; 2 | import { useRouter } from "next/router"; 3 | import onClickOutside from "react-onclickoutside"; 4 | 5 | function Dropdown({ hideDropDown }) { 6 | const [session] = useSession(); 7 | const router = useRouter(); 8 | Dropdown.handleClickOutside = hideDropDown; 9 | return ( 10 |
11 | {session && session?.admin && ( 12 |
router.push("/admin/dashboard")} 15 | > 16 | Dashboard 17 |
18 | )} 19 |
router.push("/profile")} 22 | > 23 | Profile 24 |
25 | 26 |
router.push("/orders")} 29 | > 30 | Orders 31 |
32 |
router.push("/about")} 35 | > 36 | Contact 37 |
38 |
{ 41 | signOut(); 42 | }} 43 | > 44 | Logout 45 |
46 |
47 | ); 48 | } 49 | 50 | const clickOutsideConfig = { 51 | handleClickOutside: () => Dropdown.handleClickOutside, 52 | }; 53 | 54 | export default onClickOutside(Dropdown, clickOutsideConfig); 55 | -------------------------------------------------------------------------------- /src/components/FoodGallery/FoodGallery.js: -------------------------------------------------------------------------------- 1 | function FoodGallery() { 2 | return ( 3 |
4 | {Array(8) 5 | .fill(0) 6 | .map((value, i) => ( 7 |
8 | 13 |
14 | ))} 15 |
16 | ); 17 | } 18 | 19 | export default FoodGallery; 20 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import Image from "next/image"; 3 | import { useRouter } from "next/router"; 4 | import { HeartIcon } from "@heroicons/react/solid"; 5 | 6 | function Footer({ admin }) { 7 | const router = useRouter(); 8 | const gmailHandler = () => { 9 | window.open( 10 | "mailto:" + 11 | "piyushsati311999@gmail.com" + 12 | "?subject=" + 13 | " " + 14 | "&body=" + 15 | " ", 16 | "_self" 17 | ); 18 | }; 19 | return ( 20 |
21 |
22 |
23 |
24 | 25 | Home 26 | 27 | {!admin ? ( 28 | 29 | Orders 30 | 31 | ) : ( 32 | <> 33 | )} 34 |
35 |
36 |
37 | email 46 |
47 |
48 | linkedin { 56 | router.push("https://www.linkedin.com/in/piyush-sati"); 57 | }} 58 | /> 59 |
60 |
61 | github router.push("https://github.com/Pinqua")} 69 | /> 70 |
71 |
72 |
73 |

74 | Made with by 75 | 76 | Piyush Sati 77 | 78 |

79 |
80 |
81 | ); 82 | } 83 | 84 | export default Footer; 85 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Image from "next/image"; 3 | import { ChevronDownIcon } from "@heroicons/react/outline"; 4 | import { ShoppingCartIcon } from "@heroicons/react/solid"; 5 | import { useRouter } from "next/router"; 6 | import { signIn, useSession } from "next-auth/client"; 7 | import { useSelector } from "react-redux"; 8 | import { selectItems } from "../../slices/cartSlice"; 9 | import Skeleton from "react-loading-skeleton"; 10 | import Dropdown from "../Dropdown/Dropdown"; 11 | 12 | function Header() { 13 | const router = useRouter(); 14 | const [session, loading] = useSession(); 15 | const items = useSelector(selectItems); 16 | const [dropDown, setDropDown] = useState(false); 17 | 18 | return ( 19 |
20 |
21 |
22 | Zinger router.push("/")} 30 | /> 31 |
32 |
33 | {!loading ? ( 34 | !session ? ( 35 | 36 | Login 37 | 38 | ) : ( 39 | setDropDown((value) => !value)} 42 | > 43 | 44 | 52 | 53 | 54 | {dropDown && ( 55 |
56 | setDropDown(false)} /> 57 |
58 | )} 59 |
60 | ) 61 | ) : ( 62 | 63 | )} 64 | router.push("/orders")}> 65 | Orders 66 | 67 | router.push("/about")}> 68 | About 69 | 70 |
71 |
router.push("/cart")} 74 | > 75 | 76 |
77 | {items?.length} 78 |
79 |
80 |
81 |
82 | ); 83 | } 84 | 85 | export default Header; 86 | -------------------------------------------------------------------------------- /src/components/Header/HeaderDashboard.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { signOut } from "next-auth/client"; 3 | import { useRouter } from "next/router"; 4 | 5 | function HeaderDashboard() { 6 | const router = useRouter(); 7 | return ( 8 |
9 |
10 |
11 | Zinger router.push("/")} 19 | /> 20 |
21 |
22 | router.push("/admin/dashboard")} 25 | > 26 | Dashboard 27 | 28 | 29 | Logout 30 | 31 |
32 |
33 |
34 | ); 35 | } 36 | 37 | export default HeaderDashboard; 38 | -------------------------------------------------------------------------------- /src/components/Header/HeaderMobile.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Image from "next/image"; 3 | import { MenuIcon } from "@heroicons/react/outline"; 4 | import { ShoppingCartIcon } from "@heroicons/react/solid"; 5 | import { useRouter } from "next/router"; 6 | import { useSelector } from "react-redux"; 7 | import { selectItems } from "../../slices/cartSlice"; 8 | import SideBarMenu from "../SideBarMenu/SideBarMenu"; 9 | 10 | function HeaderMobile() { 11 | const router = useRouter(); 12 | const items = useSelector(selectItems); 13 | const [showSideBar, setShowBar] = useState(false); 14 | 15 | return ( 16 | <> 17 |
18 |
19 |
20 |
21 | setShowBar(true)} /> 22 |
23 |
24 | Zinger router.push("/")} 32 | /> 33 |
34 |
35 |
router.push("/cart")} 38 | > 39 | 40 |
41 | {items?.length} 42 |
43 |
44 |
45 |
46 |
51 | setShowBar(false)} /> 52 |
53 | 54 | ); 55 | } 56 | 57 | export default HeaderMobile; 58 | -------------------------------------------------------------------------------- /src/components/HowItWork/HowItWork.js: -------------------------------------------------------------------------------- 1 | import { 2 | CreditCardIcon, 3 | CursorClickIcon, 4 | TruckIcon, 5 | } from "@heroicons/react/solid"; 6 | 7 | function HowItWork() { 8 | return ( 9 |
10 |
11 |

How It Works

12 |
13 |
14 |
15 | 16 |

Pick Meal

17 |

18 | Choose a meal from our diverse weekly menu. 19 |

20 |
21 |
22 | 23 |

Checkout

24 |

25 | Fill address, all the necessary details and make payment. 26 |

27 |
28 |
29 | 30 |

Fast Delivery

31 |

32 | Freshly prepared meal arrive on your doorstep in a refigerated 33 | box. 34 |

35 |
36 |
37 |
38 |
39 |
40 | ); 41 | } 42 | 43 | export default HowItWork; 44 | -------------------------------------------------------------------------------- /src/components/Info/Info.js: -------------------------------------------------------------------------------- 1 | import { 2 | ClockIcon, 3 | LocationMarkerIcon, 4 | PhoneIcon, 5 | } from "@heroicons/react/solid"; 6 | 7 | function Info() { 8 | return ( 9 |
10 |
11 |
12 |
13 | 14 |
15 |

Today 10am - 7pm

16 |

Working Hours

17 |
18 |
19 |
20 | 21 |
22 |

Roorkee,Uttrakhand

23 |

Get Directions

24 |
25 |
26 |
27 | 28 |
29 |

+91 7345679834

30 |

Call Now

31 |
32 |
33 |
34 | ); 35 | } 36 | 37 | export default Info; 38 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import StorageService from "../../util/StorageService"; 3 | import { store } from "../../app/store"; 4 | import { hydrate } from "../../slices/cartSlice"; 5 | import Footer from "../Footer/Footer"; 6 | import Head from "next/head"; 7 | import Header from "../Header/Header"; 8 | import { signIn, useSession } from "next-auth/client"; 9 | import Loader from "react-loader-spinner"; 10 | import HeaderMobile from "../Header/HeaderMobile"; 11 | import HeaderDashboard from "../Header/HeaderDashboard"; 12 | 13 | function Layout({ children, admin, auth }) { 14 | const [session, loading] = useSession(); 15 | 16 | useEffect(() => { 17 | store.subscribe(() => { 18 | StorageService.set("cart", JSON.stringify(store.getState().cart)); 19 | }); 20 | let cart = StorageService.get("cart"); 21 | cart = cart ? JSON.parse(cart) : { items: [] }; 22 | store.dispatch(hydrate(cart)); 23 | }, []); 24 | 25 | return ( 26 | <> 27 | 28 | 29 | 30 | 34 | Zinger 35 | 39 | 44 | 50 | 56 | 57 | 62 | 63 | 64 | 68 | 69 | 70 |
71 | {loading ? ( 72 |
73 | 74 |
75 | ) : admin ? ( 76 | session && session?.admin ? ( 77 | <> 78 | 79 | {children} 80 |
105 | 106 | ); 107 | } 108 | 109 | export default Layout; 110 | -------------------------------------------------------------------------------- /src/components/Menu/Menu.js: -------------------------------------------------------------------------------- 1 | import Dish from "../Dish/Dish"; 2 | import { useState } from "react"; 3 | import { AdjustmentsIcon } from "@heroicons/react/outline"; 4 | 5 | function Menu({ dishes, categories }) { 6 | const [categoryActive, setCategoryActive] = useState("all"); 7 | const [filteredDishes, setFilteredDishes] = useState(dishes); 8 | 9 | const activeCategoryHandler = (category) => { 10 | if (category === "all" || categoryActive === category) { 11 | setCategoryActive("all"); 12 | return; 13 | } 14 | setCategoryActive(category); 15 | filterDishes(category); 16 | }; 17 | 18 | const filterDishes = (category) => { 19 | setFilteredDishes( 20 | dishes.filter((dish) => dish?.category === category) 21 | ); 22 | }; 23 | 24 | return ( 25 | 67 | ); 68 | } 69 | 70 | export default Menu; 71 | -------------------------------------------------------------------------------- /src/components/Order/Order.js: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | import { useSession } from "next-auth/client"; 3 | import Link from "next/link"; 4 | import Currency from "react-currency-formatter"; 5 | import axios from "axios"; 6 | import { useState } from "react"; 7 | import NormalToast from "../../util/Toast/NormalToast"; 8 | 9 | function Order({ _id, id, amount_total, timestamp, items, status, admin }) { 10 | const [session, loading] = useSession(); 11 | const [disabled, setDisabled] = useState(false); 12 | 13 | const updateStatus = (e) => { 14 | setDisabled(true); 15 | axios 16 | .post("/api/admin/update-order-status", { 17 | status: e.target.value, 18 | _id: _id, 19 | }) 20 | .then(() => { 21 | setDisabled(false); 22 | }) 23 | .catch((err) => { 24 | console.error(err); 25 | setDisabled(false); 26 | }); 27 | }; 28 | const cancelOrder = () => { 29 | setDisabled(true); 30 | axios 31 | .post("/api/cancel-order", { status: "cancelled", _id: _id }) 32 | .then(() => { 33 | NormalToast("Order cancelled"); 34 | setDisabled(false); 35 | }) 36 | .catch((err) => { 37 | console.error(err); 38 | NormalToast("Something went wrong", true); 39 | setDisabled(false); 40 | }); 41 | }; 42 | 43 | return ( 44 |
45 |
46 | {admin ? ( 47 | status && !loading && session && session?.admin ? ( 48 | 59 | ) : ( 60 | <> 61 | ) 62 | ) : status ? ( 63 |
71 | {status} 72 |
73 | ) : ( 74 | <> 75 | )} 76 | {status && status !== "cancelled" && status !== "delivered" ? ( 77 | 85 | ) : ( 86 | <> 87 | )} 88 |
89 | 92 |
97 |
98 | {status && status === "cancelled" ? ( 99 |

100 | * Money will be refunded within 24 hour 101 |

102 | ) : ( 103 | <> 104 | )} 105 |

106 | ORDER # {id} 107 |

108 |
109 |
110 |
111 |

ORDER PLACED

112 |

{moment(timestamp).format("DD MMM YYYY")}

113 |
114 |
115 |

TOTAL

116 |

117 | 118 |

119 |
120 |
121 |

122 | {items?.length} items 123 |

124 |
125 |
126 |
127 |
128 | {items?.map((item) => ( 129 | 136 | ))} 137 |
138 |
139 |
140 | 141 |
142 | ); 143 | } 144 | 145 | export default Order; 146 | -------------------------------------------------------------------------------- /src/components/Order/OrderDetails.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import moment from "moment"; 3 | import Currency from "react-currency-formatter"; 4 | import useSWR from "swr"; 5 | import Skeleton from "react-loading-skeleton"; 6 | import OrderItem from "../../components/Order/OrderItem"; 7 | import { useSession } from "next-auth/client"; 8 | import axios from "axios"; 9 | import NormalToast from "../../util/Toast/NormalToast"; 10 | 11 | function OrderDetails({ id, admin }) { 12 | const [session, loading] = useSession(); 13 | const [disabled, setDisabled] = useState(false); 14 | const { data: order, error } = useSWR( 15 | !loading && session ? `/api/order-details/${id}` : null 16 | ); 17 | 18 | if (error) { 19 | console.error(error); 20 | } 21 | 22 | const updateStatus = (e) => { 23 | setDisabled(true); 24 | axios 25 | .post("/api/admin/update-order-status", { 26 | status: e.target.value, 27 | _id: id, 28 | }) 29 | .then(() => { 30 | setDisabled(false); 31 | }) 32 | .catch((err) => { 33 | setDisabled(false); 34 | console.error(err); 35 | }); 36 | }; 37 | 38 | const cancelOrder = () => { 39 | setDisabled(true); 40 | axios 41 | .post("/api/cancel-order", { status: "cancelled", _id: id }) 42 | .then(() => { 43 | NormalToast("Order cancelled"); 44 | setDisabled(false); 45 | }) 46 | .catch((err) => { 47 | console.error(err); 48 | NormalToast("Something went wrong", true); 49 | setDisabled(false); 50 | }); 51 | }; 52 | 53 | return ( 54 |
55 |
56 |
57 |
58 |

59 | Order Details 60 |

61 |

62 | {order ? moment(order?.timestamp).format("llll") : } 63 |

64 |
65 | {order && ( 66 |

67 | {order?.items?.length} items 68 |

69 | )} 70 |
71 |
72 | {order ? ( 73 | <> 74 | {admin && 75 | session?.admin && 76 | order?.order_status?.current?.status !== "cancelled" && 77 | order?.order_status?.current?.status !== "delivered" ? ( 78 | 88 | ) : ( 89 | <> 90 | )} 91 |
92 |
100 |

Order Status

101 |
    102 | {order?.order_status?.info?.map( 103 | ({ status, timestamp }, i) => ( 104 |
  • 108 | 109 | {status} 110 | 111 | 112 | {moment(timestamp).format("llll")} 113 | 114 |
  • 115 | ) 116 | )} 117 |
118 |
119 | {order?.order_status?.current?.status === "cancelled" ? ( 120 |

121 | * Money will be refunded within 24 hour 122 |

123 | ) : ( 124 | <> 125 | )} 126 |

127 | ORDER ID - 128 | 129 | {order?.id} 130 | 131 |

132 |

133 | EMAIL - 134 | 135 | {order?.customer_details?.email} 136 | 137 |

138 |
139 |

Address

140 |
141 |

142 | Name - 143 | {order?.shipping?.name} 144 |

145 |

146 | City - 147 | {order?.shipping?.address?.city} 148 |

149 |

150 | Country - 151 | {order?.shipping?.address?.country} 152 |

153 |

154 | Line 1 - 155 | {order?.shipping?.address?.line1}, 156 |

157 |

158 | Line 2 - 159 | {order?.shipping?.address?.line2} 160 |

161 |

162 | Postal Code - 163 | {order?.shipping?.address?.postal_code} 164 |

165 |

166 | State - 167 | {order?.shipping?.address?.state} 168 |

169 |
170 |
171 |
172 |

Amount

173 |
174 |

175 | Subtotal - 176 | 180 |

181 |

182 | Shipping - 183 | 187 |

188 |

189 | Total - 190 | 194 |

195 |
196 |
197 |
198 |

Items

199 | {order?.items?.map((item) => ( 200 | 201 | ))} 202 |
203 | {order?.order_status?.current?.status && 204 | order?.order_status?.current?.status !== "cancelled" && 205 | order?.order_status?.current?.status !== "delivered" ? ( 206 |
207 | 215 |
216 | ) : ( 217 | <> 218 | )} 219 |
220 | 221 | ) : ( 222 | 223 | )} 224 |
225 |
226 |
227 | ); 228 | } 229 | 230 | export default OrderDetails; 231 | -------------------------------------------------------------------------------- /src/components/Order/OrderItem.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Currency from "react-currency-formatter"; 3 | 4 | function OrderItem({ item }) { 5 | return ( 6 |
7 |
8 | {item?.title} 9 |
10 |

11 | Quantity - 12 | {item?.qty} 13 |

14 |

15 | Price - 16 | 17 |

18 |
19 |
20 |
21 | 28 |
29 |
30 | ); 31 | } 32 | 33 | export default OrderItem; 34 | -------------------------------------------------------------------------------- /src/components/Search/Search.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import { SearchIcon } from "@heroicons/react/outline"; 3 | import Fade from "react-reveal/Fade"; 4 | import Image from "next/image"; 5 | import getDishes from "../../util/getDishes"; 6 | import { useRouter } from "next/router"; 7 | 8 | function Search() { 9 | const [searchTerm, setSearchTerm] = useState(""); 10 | const [searchResults, setSearchResults] = useState([]); 11 | const { dishes, isLoading, error } = getDishes(); 12 | const [loading, setLoading] = useState(true); 13 | const searchRef = useRef(null); 14 | const router = useRouter(); 15 | const options = { 16 | keys: ["title", "description", "category"], 17 | }; 18 | 19 | const closeSearch = () => { 20 | setSearchTerm(""); 21 | setSearchResults([]); 22 | }; 23 | 24 | useEffect(() => { 25 | function handleClickOutside(e) { 26 | let targetEl = e.target; 27 | do { 28 | if (targetEl === searchRef.current) { 29 | return; 30 | } 31 | targetEl = targetEl.parentNode; 32 | } while (targetEl); 33 | closeSearch(); 34 | } 35 | window.addEventListener("click", handleClickOutside); 36 | return () => { 37 | window.removeEventListener("click", handleClickOutside); 38 | }; 39 | }, []); 40 | 41 | const searchDish = async (e) => { 42 | setLoading(true); 43 | let term = e.target.value; 44 | setSearchTerm(term); 45 | term = term.toLowerCase(); 46 | // Dynamically load fuse.js 47 | const Fuse = (await import("fuse.js")).default; 48 | const fuse = new Fuse(dishes ? dishes : [], options); 49 | setSearchResults(fuse.search(term)); 50 | setLoading(false); 51 | }; 52 | 53 | if (error) { 54 | console.error(error); 55 | } 56 | 57 | return ( 58 |
59 |
60 | 61 |
62 | 69 | 70 | {searchTerm ? ( 71 |
72 | {!isLoading || !loading ? ( 73 | searchResults?.length ? ( 74 | searchResults.map(({ item: { _id, title, image } }, i) => ( 75 | 76 |
82 |
83 | {title} 84 |
85 |
86 | 93 |
94 |
95 |
96 | )) 97 | ) : ( 98 |

99 | No dish found 100 |

101 | ) 102 | ) : ( 103 |

Loading...

104 | )} 105 |
106 | ) : ( 107 | <> 108 | )} 109 |
110 | ); 111 | } 112 | 113 | export default Search; 114 | -------------------------------------------------------------------------------- /src/components/SideBarMenu/SideBarMenu.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { 3 | HomeIcon, 4 | InformationCircleIcon, 5 | LogoutIcon, 6 | MailIcon, 7 | ShoppingBagIcon, 8 | ShoppingCartIcon, 9 | TableIcon, 10 | UserCircleIcon, 11 | XIcon, 12 | } from "@heroicons/react/outline"; 13 | import { signIn, signOut, useSession } from "next-auth/client"; 14 | import { useRouter } from "next/router"; 15 | import onClickOutside from "react-onclickoutside"; 16 | import Skeleton from "react-loading-skeleton"; 17 | 18 | function SideBarMenu({ closeSideBar }) { 19 | const [session, loading] = useSession(); 20 | const router = useRouter(); 21 | SideBarMenu.handleClickOutside = closeSideBar; 22 | const sideBarClickHandler = (href) => { 23 | closeSideBar(); 24 | router.push(href); 25 | }; 26 | 27 | return ( 28 |
29 |
30 | Zinger 38 |
39 |
40 |
41 | {!loading ? ( 42 | session ? ( 43 | sideBarClickHandler("/profile")} 51 | /> 52 | ) : ( 53 | 54 | Login/Signup 55 | 56 | ) 57 | ) : ( 58 | 59 | )} 60 |
61 |
62 |
63 | sideBarClickHandler("/")} 65 | className="link inline-flex" 66 | > 67 | Home 68 | 69 |
70 | {session && session?.admin && ( 71 |
72 | sideBarClickHandler("/admin/dashboard")} 74 | className="link inline-flex" 75 | > 76 | Dashboard 77 | 78 |
79 | )} 80 | {session && ( 81 |
82 | sideBarClickHandler("/profile")} 84 | className="link inline-flex" 85 | > 86 | Profile 87 | 88 |
89 | )} 90 |
91 | sideBarClickHandler("/cart")} 93 | className="link inline-flex" 94 | > 95 | Cart 96 | 97 |
98 |
99 | sideBarClickHandler("/orders")} 101 | className="link inline-flex" 102 | > 103 | Orders 104 | 105 |
106 |
107 | sideBarClickHandler("/about")} 109 | className="link inline-flex" 110 | > 111 | Contact 112 | 113 |
114 |
115 | sideBarClickHandler("/about")} 117 | className="link inline-flex" 118 | > 119 | About 120 | 121 |
122 | {session && ( 123 |
124 | { 126 | signOut(); 127 | }} 128 | className="link inline-flex" 129 | > 130 | Logout 131 | 132 |
133 | )} 134 |
135 |
136 | 137 |
138 |
139 | ); 140 | } 141 | 142 | const clickOutsideConfig = { 143 | handleClickOutside: () => SideBarMenu.handleClickOutside, 144 | }; 145 | 146 | export default onClickOutside(SideBarMenu, clickOutsideConfig); 147 | -------------------------------------------------------------------------------- /src/components/Testimonials/Testimonials.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | function Testimonials() { 4 | return ( 5 |
6 |
7 |

Our Customers can’t live Without us

8 |
9 |
10 |
"
11 |

12 | "Zinger is just awesome! I just launched a startup which leaves me 13 | with no time for cooking, so Zinger is a life-saver. Now that I 14 | got used to it, I couldn't live without my daily meals! 15 |

16 |
17 |
18 | 26 |
27 | 28 | Alberto Duncan 29 |
30 |
31 |
32 |
"
33 |

34 | "Inexpensive, healthy and great-tasting meals, delivered right to 35 | my home. We have lots of food delivery here in Lisbon, but no one 36 | comes even close to Zinger. Me and my family are so in love! 37 |

38 |
39 |
40 | 48 |
49 | 50 | Joana Silva 51 |
52 |
53 |
54 |
"
55 |

56 | I was looking for a quick and easy food delivery service in San 57 | Franciso. I tried a lot of them and ended up with Omnifood. Best 58 | food delivery service in the Bay Area. Keep up the great work! 59 |

60 |
61 |
62 | 70 |
71 | 72 | Milton Chapman 73 |
74 |
75 |
76 |
77 |
78 | ); 79 | } 80 | 81 | export default Testimonials; 82 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | // pages/404.js 2 | import Head from "next/head"; 3 | import Fade from "react-reveal/Fade"; 4 | 5 | export default function Custom404() { 6 | return ( 7 | <> 8 | 9 | Zinger | Page Not Found 10 | 11 | 12 |
13 |
14 |
15 | 16 |

17 | 404 18 |

19 |

20 | Page Not Found 21 |

22 |
23 |
24 | 27 |
28 |
29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/500.js: -------------------------------------------------------------------------------- 1 | // pages/500.js 2 | import Head from "next/head"; 3 | import Fade from "react-reveal/Fade"; 4 | 5 | export default function Custom500() { 6 | return ( 7 | <> 8 | 9 | Zinger | Internal Server Error 10 | 11 |
12 |
13 |
14 | 15 |

16 | 500 17 |

18 |

19 | Internal Server Error 20 |

21 |
22 |
23 | 26 |
27 |
28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/pages/_app.js: -------------------------------------------------------------------------------- 1 | import Router from "next/router"; 2 | import NProgress from "nprogress"; //nprogress module 3 | import { Provider } from "react-redux"; 4 | import { store } from "../app/store"; 5 | import { Provider as NextAuthProvider } from "next-auth/client"; 6 | import { ToastContainer } from "react-toastify"; //styles of nprogress 7 | import Layout from "../components/Layout/Layout"; 8 | import "../styles/globals.css"; 9 | import "react-toastify/dist/ReactToastify.css"; 10 | import "nprogress/nprogress.css"; 11 | import { SWRConfig } from "swr"; 12 | import fetcher from "../util/fetch"; 13 | 14 | //Binding events. 15 | Router.events.on("routeChangeStart", () => NProgress.start()); 16 | Router.events.on("routeChangeComplete", () => NProgress.done()); 17 | Router.events.on("routeChangeError", () => NProgress.done()); 18 | 19 | function MyApp({ Component, pageProps }) { 20 | return ( 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | 39 | export default MyApp; 40 | -------------------------------------------------------------------------------- /src/pages/about.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import Image from "next/image"; 3 | import Head from "next/head"; 4 | import Fade from "react-reveal/Fade"; 5 | 6 | function About() { 7 | return ( 8 | <> 9 | 10 | Zinger | About 11 | 12 |
13 |
14 |
15 |

16 | About 17 |

18 |
19 |
20 | 27 |
28 |
29 | 30 |

31 | This is a food ordering website for Zinger restaurant built 32 | using 33 | 34 | Next.js, 35 | 36 | 37 | Redux, 38 | 39 | 40 | Tailwindcss, 41 | 42 | 43 | MongoDB 44 | 45 | by 46 | 47 | 48 | Piyush Sati 49 | 50 | 51 | to enhance and showcase his development skills. 52 |

53 |

54 | If you wanna get in touch email 55 | 56 | piyushsati311999@gmail.com 57 | 58 |

59 |
60 |
61 |
62 |
63 |
64 |
65 | 66 | ); 67 | } 68 | 69 | export default About; 70 | -------------------------------------------------------------------------------- /src/pages/admin/add-category.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import axios from "axios"; 3 | import NormalToast from "../../util/Toast/NormalToast"; 4 | import Head from "next/head"; 5 | 6 | function AddCategory() { 7 | const [categoryName, setCategoryName] = useState(""); 8 | const [disabled, setDisabled] = useState(false); 9 | 10 | const formHandler = (e) => { 11 | setDisabled(true); 12 | e.preventDefault(); 13 | axios 14 | .post("/api/admin/add-category", { name: categoryName }) 15 | .then(() => { 16 | NormalToast("Category added successfully"); 17 | setCategoryName(""); 18 | setDisabled(false); 19 | }) 20 | .catch((err) => { 21 | console.error(err); 22 | NormalToast("Something went wrong", true); 23 | setDisabled(false); 24 | }); 25 | }; 26 | 27 | return ( 28 | <> 29 | 30 | Zinger | Add Category 31 | 32 |
33 |
34 |

35 | Add Category 36 |

37 |
38 | setCategoryName(e.target.value)} 44 | disabled={disabled} 45 | /> 46 | 54 |
55 |
56 |
57 | 58 | ); 59 | } 60 | 61 | AddCategory.admin = true; 62 | export default AddCategory; 63 | -------------------------------------------------------------------------------- /src/pages/admin/add-dish.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import axios from "axios"; 3 | import NormalToast from "../../util/Toast/NormalToast"; 4 | import { connectToDatabase } from "../../util/mongodb"; 5 | import getCategories from "../../util/getCategories"; 6 | import Head from "next/head"; 7 | 8 | function AddDish(props) { 9 | const [title, setTitle] = useState(""); 10 | const [description, setDescription] = useState(""); 11 | const [price, setPrice] = useState(""); 12 | const [image, setImage] = useState(""); 13 | const [category, setCategory] = useState(props?.categories[0]?.name); 14 | const { categories, error } = getCategories(props?.categories); 15 | const [disabled, setDisabled] = useState(false); 16 | 17 | if (error) { 18 | console.error(error); 19 | } 20 | 21 | const formHandler = (e) => { 22 | e.preventDefault(); 23 | setDisabled(true); 24 | axios 25 | .post("/api/admin/add-dish", { 26 | title, 27 | category, 28 | description, 29 | price, 30 | image, 31 | }) 32 | .then((res) => { 33 | NormalToast("Dish added successfully"); 34 | setTitle(""); 35 | setDescription(""); 36 | setPrice(""); 37 | setImage(""); 38 | setCategory(""); 39 | setDisabled(false); 40 | }) 41 | .catch((err) => { 42 | NormalToast("Something went wrong", true); 43 | console.error(err); 44 | setDisabled(false); 45 | }); 46 | }; 47 | 48 | return ( 49 | <> 50 | 51 | Zinger | Add Dish 52 | 53 |
54 |
55 |

56 | Add Dish 57 |

58 |
59 | setTitle(e.target.value)} 66 | disabled={disabled} 67 | /> 68 | 80 | 90 | setPrice(e.target.value)} 97 | disabled={disabled} 98 | /> 99 | setImage(e.target.value)} 106 | disabled={disabled} 107 | /> 108 | 116 |
117 |
118 |
119 | 120 | ); 121 | } 122 | 123 | AddDish.admin = true; 124 | export default AddDish; 125 | 126 | export const getStaticProps = async () => { 127 | const { db } = await connectToDatabase(); 128 | let categories = await db.collection("categories").find({}).toArray(); 129 | categories = JSON.parse(JSON.stringify(categories)); 130 | return { 131 | props: { 132 | categories, 133 | }, 134 | revalidate: 1, 135 | }; 136 | }; 137 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import useSWR from "swr"; 3 | import Skeleton from "react-loading-skeleton"; 4 | import Image from "next/image"; 5 | import Order from "../../components/Order/Order"; 6 | import { useSession } from "next-auth/client"; 7 | import Head from "next/head"; 8 | import { ArchiveIcon, PlusIcon, UsersIcon } from "@heroicons/react/outline"; 9 | 10 | function Dashboard() { 11 | const [session, loading] = useSession(); 12 | const { data: orders, error } = useSWR( 13 | !loading && session && session.admin ? "/api/admin/active-orders" : null 14 | ); 15 | 16 | if (error) { 17 | console.error(error); 18 | } 19 | 20 | return ( 21 | <> 22 | 23 | Zinger | Dashboard 24 | 25 |
26 |
27 |
28 |

29 | Dashboard 30 |

31 |
32 | 33 |
34 | 35 | Dishes 36 |
37 | 38 | 39 |
40 | 41 | Users 42 |
43 | 44 | 45 |
46 | 47 | Dish 48 |
49 | 50 | 51 |
52 | 53 | Category 54 |
55 | 56 |
57 |
58 |

59 | Active Orders 60 |

61 |
62 |
63 |

64 | {orders ? ( 65 | <> 66 | 67 | {orders?.length} 68 | 69 | Orders 70 | 71 | ) : ( 72 | 73 | )} 74 |

75 | {orders ? ( 76 | orders?.length ? ( 77 |
78 | {orders.map( 79 | ({ 80 | _id, 81 | id, 82 | amount_total, 83 | items, 84 | timestamp, 85 | order_status, 86 | }) => ( 87 | 97 | ) 98 | )} 99 |
100 | ) : ( 101 |
102 | 109 |
110 | ) 111 | ) : ( 112 | 113 | )} 114 |
115 |
116 |
117 |
118 | 119 | ); 120 | } 121 | 122 | Dashboard.admin = true; 123 | 124 | export default Dashboard; 125 | -------------------------------------------------------------------------------- /src/pages/admin/dishes.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { connectToDatabase } from "../../util/mongodb"; 3 | import getDishes from "../../util/getDishes"; 4 | import Head from "next/head"; 5 | import DishInfo from "../../components/Dish/DishInfo"; 6 | 7 | function Dishes(props) { 8 | const [searchTerm, setSearchTerm] = useState(""); 9 | const { dishes, error } = getDishes(props?.dishes); 10 | const [searchResult, setSearchResult] = useState(dishes); 11 | const options = { 12 | keys: ["title", "description", "category"], 13 | }; 14 | 15 | if (error) { 16 | console.error(error); 17 | } 18 | 19 | const searchDish = async (e) => { 20 | let term = e.target.value; 21 | setSearchTerm(term); 22 | term = term.toLowerCase(); 23 | // Dynamically load fuse.js 24 | const Fuse = (await import("fuse.js")).default; 25 | const fuse = new Fuse(dishes ? dishes : [], options); 26 | const result = fuse 27 | .search(term) 28 | .map(({ item: { _id, title, price, description, category, image } }) => ({ 29 | _id, 30 | title, 31 | price, 32 | description, 33 | category, 34 | image, 35 | })); 36 | setSearchResult(result); 37 | }; 38 | 39 | const removeFromSearchResults = (_id) => { 40 | setSearchResult((dishes) => 41 | dishes.filter((dish) => dish._id !== _id) 42 | ); 43 | }; 44 | 45 | return ( 46 | <> 47 | 48 | Zinger | Dishes 49 | 50 |
51 |
52 |

53 | Dishes 54 |

55 |
56 | 63 |
64 |
65 | {(searchTerm ? searchResult : dishes)?.map( 66 | ({ _id, title, price, description, category, image }, i) => ( 67 | 78 | ) 79 | )} 80 |
81 |
82 |
83 | 84 | ); 85 | } 86 | 87 | Dishes.admin = true; 88 | export default Dishes; 89 | 90 | export const getStaticProps = async () => { 91 | const { db } = await connectToDatabase(); 92 | let dishes = await db.collection("dishes").find({}).toArray(); 93 | dishes = JSON.parse(JSON.stringify(dishes)); 94 | return { 95 | props: { 96 | dishes, 97 | }, 98 | revalidate: 1, 99 | }; 100 | }; 101 | -------------------------------------------------------------------------------- /src/pages/admin/index.js: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import { useEffect } from "react"; 3 | import Head from "next/head"; 4 | 5 | function Admin() { 6 | const router = useRouter(); 7 | 8 | useEffect(() => { 9 | router.replace("/admin/dashboard"); 10 | }, [router]); 11 | 12 | return ( 13 | <> 14 | 15 | Zinger | Admin Panel 16 | 17 |
18 |
19 | Welcome to Admin Panel 20 |
21 | Wait while redirecting to Dashboard 22 |
23 |
24 | 25 | ); 26 | } 27 | 28 | Admin.admin = true; 29 | export default Admin; 30 | -------------------------------------------------------------------------------- /src/pages/admin/order-details/[id].js: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import OrderDetails from "../../../components/Order/OrderDetails"; 3 | import Head from "next/head"; 4 | 5 | function orderDetails() { 6 | const router = useRouter(); 7 | return ( 8 | <> 9 | 10 | Zinger | OrderDetails 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | orderDetails.admin = true; 18 | export default orderDetails; 19 | -------------------------------------------------------------------------------- /src/pages/admin/update-dish/[id].js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import axios from "axios"; 3 | import { useRouter } from "next/router"; 4 | import { connectToDatabase } from "../../../util/mongodb"; 5 | import getCategories from "../../../util/getCategories"; 6 | import { ObjectId } from "bson"; 7 | import NormalToast from "../../../util/Toast/NormalToast"; 8 | import Head from "next/head"; 9 | 10 | function UpdateDish(props) { 11 | const [title, setTitle] = useState(props?.dish?.title); 12 | const [description, setDescription] = useState(props?.dish?.description); 13 | const [price, setPrice] = useState(props?.dish?.price); 14 | const [image, setImage] = useState(props?.dish?.image); 15 | const [category, setCategory] = useState(props?.dish?.category); 16 | const router = useRouter(); 17 | const { categories, error } = getCategories(props?.categories); 18 | const [disabled, setDisabled] = useState(false); 19 | 20 | if (error) { 21 | console.error(error); 22 | } 23 | 24 | const formHandler = (e) => { 25 | e.preventDefault(); 26 | setDisabled(true); 27 | axios 28 | .post("/api/admin/update-dish", { 29 | _id: router.query.id, 30 | title, 31 | category, 32 | description, 33 | price, 34 | image, 35 | }) 36 | .then((res) => { 37 | NormalToast("Updated successfully"); 38 | setDisabled(false); 39 | }) 40 | .catch((err) => { 41 | NormalToast("Something went wrong", err); 42 | console.error(err); 43 | setDisabled(false); 44 | }); 45 | }; 46 | 47 | return ( 48 | <> 49 | 50 | Zinger | Update Dish 51 | 52 |
53 |
54 |

55 | Update Dish 56 |

57 |
58 | setTitle(e.target.value)} 65 | disabled={disabled} 66 | /> 67 | 79 | 89 | setPrice(e.target.value)} 96 | disabled={disabled} 97 | /> 98 | setImage(e.target.value)} 105 | disabled={disabled} 106 | /> 107 | 115 |
116 |
117 |
118 | 119 | ); 120 | } 121 | 122 | UpdateDish.admin = true; 123 | export default UpdateDish; 124 | 125 | export const getStaticPaths = async () => { 126 | const { db } = await connectToDatabase(); 127 | const dishes = await db.collection("dishes").find({}).toArray(); 128 | const paths = dishes.map((dish) => ({ 129 | params: { id: dish._id.toString() }, 130 | })); 131 | return { 132 | paths, 133 | fallback: true, 134 | }; 135 | }; 136 | 137 | export const getStaticProps = async (context) => { 138 | let dish; 139 | let categories; 140 | try { 141 | const { db } = await connectToDatabase(); 142 | dish = await db 143 | .collection("dishes") 144 | .findOne({ _id: ObjectId(context.params.id) }); 145 | categories = await db.collection("categories").find({}).toArray(); 146 | } catch (err) { 147 | console.error(err); 148 | return { 149 | notFound: true, 150 | }; 151 | } 152 | if (!dish) { 153 | return { 154 | notFound: true, 155 | }; 156 | } 157 | dish = JSON.parse(JSON.stringify(dish)); 158 | categories = JSON.parse(JSON.stringify(categories)); 159 | return { 160 | props: { 161 | dish, 162 | categories, 163 | }, 164 | revalidate: 1, 165 | }; 166 | }; 167 | -------------------------------------------------------------------------------- /src/pages/admin/users.js: -------------------------------------------------------------------------------- 1 | import { useSession } from "next-auth/client"; 2 | import Skeleton from "react-loading-skeleton"; 3 | import useSWR from "swr"; 4 | import Head from "next/head"; 5 | 6 | function Users() { 7 | const [session, loading] = useSession(); 8 | const { data: users, error } = useSWR( 9 | !loading && session && session.admin ? "/api/admin/users" : null 10 | ); 11 | 12 | return ( 13 | <> 14 | 15 | Zinger | Users 16 | 17 |
18 |
19 |
20 |

21 | Users 22 |

23 |
24 | {!error && !users ? ( 25 | 26 | ) : ( 27 | 28 | 29 | 30 | 33 | 36 | 39 | 40 | 41 | 42 | {users?.map((user) => ( 43 | 44 | 51 | 52 | 53 | 54 | ))} 55 | 56 |
31 | Pic 32 | 34 | Name 35 | 37 | Email 38 |
45 | 50 | {user?.name}{user?.email}
57 | )} 58 |
59 |
60 |
61 |
62 | 63 | ); 64 | } 65 | 66 | Users.admin = true; 67 | export default Users; 68 | -------------------------------------------------------------------------------- /src/pages/api/admin/active-orders.js: -------------------------------------------------------------------------------- 1 | import { getSession } from "next-auth/client"; 2 | import { connectToDatabase } from "../../../util/mongodb"; 3 | 4 | export default async (req, res) => { 5 | try { 6 | const session = await getSession({ req }); 7 | if (session && session.admin) { 8 | const { db } = await connectToDatabase(); 9 | let orders = await db 10 | .collection("orders") 11 | .find({ 12 | payment_status: "paid", 13 | "order_status.current.status": { 14 | $in: ["shipping soon", "shipped", "out for delivery"], 15 | }, 16 | }) 17 | .sort({ timestamp: -1 }) 18 | .toArray(); 19 | orders = JSON.parse(JSON.stringify(orders)); 20 | return res.status(200).json(orders); 21 | } else { 22 | return res.status(401).json({ message: "Unauthorized" }); 23 | } 24 | } catch (err) { 25 | console.error(err); 26 | return res.status(500).json({ message: "Internal Server Error" }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/pages/api/admin/add-category.js: -------------------------------------------------------------------------------- 1 | import { getSession } from "next-auth/client"; 2 | import { connectToDatabase } from "../../../util/mongodb"; 3 | 4 | export default async (req, res) => { 5 | try { 6 | if (req.method === "POST") { 7 | const session = await getSession({ req }); 8 | if (session) { 9 | const { db } = await connectToDatabase(); 10 | const admin = await db 11 | .collection("admins") 12 | .findOne({ user: session.user.email }); 13 | if (!admin) { 14 | return res.status(401).json({ message: "Unauthorized" }); 15 | } else { 16 | await db.collection("categories").insertOne(req.body); 17 | return res 18 | .status(200) 19 | .json({ message: "Category added successfully" }); 20 | } 21 | } else { 22 | return res.status(401).json({ message: "Unauthorized" }); 23 | } 24 | } else { 25 | return res.status(400).json({ message: "Bad Request" }); 26 | } 27 | } catch (err) { 28 | console.error(err); 29 | return res.status(500).json({ message: "Internal Server Error" }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/pages/api/admin/add-dish.js: -------------------------------------------------------------------------------- 1 | import { getSession } from "next-auth/client"; 2 | import { connectToDatabase } from "../../../util/mongodb"; 3 | 4 | export default async (req, res) => { 5 | try { 6 | if (req.method === "POST") { 7 | const session = await getSession({ req }); 8 | if (session) { 9 | const { db } = await connectToDatabase(); 10 | const admin = await db 11 | .collection("admins") 12 | .findOne({ user: session.user.email }); 13 | if (!admin) { 14 | return res.status(401).json({ message: "Unauthorized" }); 15 | } else { 16 | const dish = { ...req.body, price: parseInt(req.body.price) }; 17 | await db.collection("dishes").insertOne(dish); 18 | return res 19 | .status(200) 20 | .json({ message: "Dish added successfully" }); 21 | } 22 | } else { 23 | return res.status(401).json({ message: "Unauthorized" }); 24 | } 25 | } else { 26 | return res.status(400).json({ message: "Bad Request" }); 27 | } 28 | } catch (err) { 29 | console.error(err); 30 | return res.status(500).json({ message: "Internal Server Error" }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/pages/api/admin/delete-dish.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "bson"; 2 | import { getSession } from "next-auth/client"; 3 | import { connectToDatabase } from "../../../util/mongodb"; 4 | 5 | export default async (req, res) => { 6 | try { 7 | if (req.method === "POST") { 8 | const session = await getSession({ req }); 9 | if (session) { 10 | const { db } = await connectToDatabase(); 11 | const admin = await db 12 | .collection("admins") 13 | .findOne({ user: session.user.email }); 14 | if (!admin) { 15 | return res.status(401).json({ message: "Unauthorized" }); 16 | } else { 17 | await db 18 | .collection("dishes") 19 | .deleteOne({ _id: ObjectId(req.body._id) }); 20 | return res 21 | .status(200) 22 | .json({ message: "Dish deleted successfully" }); 23 | } 24 | } else { 25 | return res.status(401).json({ message: "Unauthorized" }); 26 | } 27 | } else { 28 | return res.status(400).json({ message: "Bad Request" }); 29 | } 30 | } catch (err) { 31 | console.error(err); 32 | return res.status(500).json({ message: "Internal Server Error" }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/pages/api/admin/update-dish.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "bson"; 2 | import { getSession } from "next-auth/client"; 3 | import { connectToDatabase } from "../../../util/mongodb"; 4 | 5 | export default async (req, res) => { 6 | try { 7 | if (req.method === "POST") { 8 | const session = await getSession({ req }); 9 | if (session) { 10 | const { db } = await connectToDatabase(); 11 | const admin = await db 12 | .collection("admins") 13 | .findOne({ user: session.user.email }); 14 | if (!admin) { 15 | return res.status(401).json({ message: "Unauthorized" }); 16 | } else { 17 | const { _id, title, category, description, price, image } = req.body; 18 | await db 19 | .collection("dishes") 20 | .replaceOne( 21 | { _id: ObjectId(_id) }, 22 | { title, category, description, price: parseInt(price), image } 23 | ); 24 | return res 25 | .status(200) 26 | .json({ message: "Dish updated successfully" }); 27 | } 28 | } else { 29 | return res.status(401).json({ message: "Unauthorized" }); 30 | } 31 | } else { 32 | return res.status(400).json({ message: "Bad Request" }); 33 | } 34 | } catch (err) { 35 | console.error(err); 36 | return res.status(500).json({ message: "Internal Server Error" }); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/pages/api/admin/update-order-status.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "bson"; 2 | import { getSession } from "next-auth/client"; 3 | import { connectToDatabase } from "../../../util/mongodb"; 4 | 5 | export default async (req, res) => { 6 | try { 7 | const session = await getSession({ req }); 8 | if (session && session.admin) { 9 | if (req.method === "POST") { 10 | const { status, _id } = req.body; 11 | const { db } = await connectToDatabase(); 12 | const result = await db 13 | .collection("orders") 14 | .findOne({ _id: ObjectId(_id) }); 15 | if (result) { 16 | const ord_status = { status, timestamp: new Date() }; 17 | const order_status = { 18 | current: ord_status, 19 | info: [...result.order_status.info, ord_status], 20 | }; 21 | await db 22 | .collection("orders") 23 | .updateOne({ _id: ObjectId(_id) }, { $set: { order_status } }); 24 | return res 25 | .status(200) 26 | .json({ message: "Order status updated successfully" }); 27 | } else { 28 | return res.status(400).json({ message: "Bad Request" }); 29 | } 30 | } else { 31 | return res.status(400).json({ message: "Bad Request" }); 32 | } 33 | } else { 34 | return res.status(401).json({ message: "Unauthorized" }); 35 | } 36 | } catch (err) { 37 | console.error(err); 38 | return res.status(500).json({ message: "Internal Server Error" }); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/pages/api/admin/users.js: -------------------------------------------------------------------------------- 1 | import { getSession } from "next-auth/client"; 2 | import { connectToDatabase } from "../../../util/mongodb"; 3 | 4 | export default async (req, res) => { 5 | try { 6 | const session = await getSession({ req }); 7 | if (session && session.admin) { 8 | const { db } = await connectToDatabase(); 9 | let users = await db.collection("users").find({}).toArray(); 10 | users = JSON.parse(JSON.stringify(users)); 11 | return res.status(200).json(users); 12 | } else { 13 | return res.status(401).json({ message: "Unauthorized" }); 14 | } 15 | } catch (err) { 16 | console.error(err); 17 | return res.status(500).json({ message: "Internal Server Error" }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/pages/api/auth/[...nextauth].js: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | import Providers from "next-auth/providers"; 3 | import { connectToDatabase } from "../../../util/mongodb"; 4 | 5 | export default NextAuth({ 6 | providers: [ 7 | Providers.Google({ 8 | clientId: process.env.GOOGLE_ID, 9 | clientSecret: process.env.GOOGLE_SECRET, 10 | }), 11 | ], 12 | 13 | callbacks: { 14 | async session(session, token) { 15 | session.admin = false; 16 | const { db } = await connectToDatabase(); 17 | const result = await db 18 | .collection("admins") 19 | .findOne({ user: session.user.email }); 20 | if (result) { 21 | session.admin = true; 22 | } 23 | return session; 24 | }, 25 | }, 26 | // A database is optional, but required to persist accounts in a database 27 | database: `${process.env.MONGO_URI}`, 28 | theme: "dark", 29 | }); 30 | -------------------------------------------------------------------------------- /src/pages/api/cancel-order.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "bson"; 2 | import { getSession } from "next-auth/client"; 3 | import { connectToDatabase } from "../../util/mongodb"; 4 | 5 | export default async (req, res) => { 6 | try { 7 | const session = await getSession({ req }); 8 | if (session) { 9 | if (req.method === "POST") { 10 | const { status, _id } = req.body; 11 | const { db } = await connectToDatabase(); 12 | const result = await db 13 | .collection("orders") 14 | .findOne({ _id: ObjectId(_id) }); 15 | if (result) { 16 | if (result.user === session.user.email || session.admin) { 17 | const ord_status = { status, timestamp: new Date() }; 18 | const order_status = { 19 | current: ord_status, 20 | info: [...result.order_status.info, ord_status], 21 | }; 22 | await db 23 | .collection("orders") 24 | .updateOne({ _id: ObjectId(_id) }, { $set: { order_status } }); 25 | return res.status(200).json({ message: "Order Cancelled" }); 26 | } else { 27 | return res.status(401).json({ message: "Unauthorized" }); 28 | } 29 | } else { 30 | return res.status(400).json({ message: "Bad Request" }); 31 | } 32 | } else { 33 | return res.status(400).json({ message: "Bad Request" }); 34 | } 35 | } else { 36 | return res.status(401).json({ message: "Unauthorized" }); 37 | } 38 | } catch (err) { 39 | console.error(err); 40 | return res.status(500).json({ message: "Internal Server Error" }); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/pages/api/categories.js: -------------------------------------------------------------------------------- 1 | import { connectToDatabase } from "../../util/mongodb"; 2 | 3 | export default async (req, res) => { 4 | try { 5 | const { db } = await connectToDatabase(); 6 | let categories = await db.collection("categories").find({}).toArray(); 7 | categories = JSON.parse(JSON.stringify(categories)); 8 | return res.status(200).json(categories); 9 | } catch (err) { 10 | console.error(err); 11 | return res.status(500).json({ message: "Internal Server Error" }); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/pages/api/create-checkout-session.js: -------------------------------------------------------------------------------- 1 | import { connectToDatabase } from "../../util/mongodb"; 2 | 3 | const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); 4 | 5 | export default async (req, res) => { 6 | const { items, email } = req.body; 7 | 8 | try { 9 | const { db } = await connectToDatabase(); 10 | //await db.collection("temp").deleteMany({ user: email }); 11 | //to delete temp doc in 56 days automatically. run only one time 12 | //await db.collection("temp").createIndex({ "createdAt": 1 }, { expireAfterSeconds:4838400 }) 13 | const result = await db.collection("temp").insertOne({ 14 | user: email, 15 | items, 16 | createdAt: new Date(), 17 | }); 18 | const transformedItems = items.map((item) => ({ 19 | description: item.description, 20 | quantity: item.qty, 21 | price_data: { 22 | currency: "INR", 23 | //unit_amount_decimal insted to unit_amount for decimal 24 | unit_amount: item.price * 100, 25 | product_data: { 26 | name: item.title, 27 | // images: [item.image], 28 | }, 29 | }, 30 | })); 31 | try { 32 | const session = await stripe.checkout.sessions.create({ 33 | payment_method_types: ["card"], 34 | shipping_rates: ["shr_1J1z9cSILNiInkjsOUxwYnGT"], 35 | shipping_address_collection: { 36 | allowed_countries: ["GB", "US", "CA", "IN"], 37 | }, 38 | line_items: transformedItems, 39 | mode: "payment", 40 | success_url: `${process.env.HOST}/success`, 41 | cancel_url: `${process.env.HOST}/cart`, 42 | metadata: { 43 | id: JSON.stringify(result.insertedId), 44 | }, 45 | }); 46 | return res.status(200).json({ id: session.id }); 47 | } catch (err) { 48 | console.error(err); 49 | return res.status(400).json({ message: "Bad Request" }); 50 | } 51 | } catch (err) { 52 | console.error(err); 53 | return res.status(400).json({ message: "Bad Request" }); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/pages/api/dishes.js: -------------------------------------------------------------------------------- 1 | import { connectToDatabase } from "../../util/mongodb"; 2 | //import dis from "../../../dishes.json" 3 | 4 | export default async (req, res) => { 5 | try { 6 | const { db } = await connectToDatabase(); 7 | let dishes = await db.collection("dishes").find({}).toArray(); 8 | dishes = JSON.parse(JSON.stringify(dishes)); 9 | return res.status(200).json(dishes); 10 | } catch (err) { 11 | console.error(err); 12 | return res.status(500).json({ message: "Internal Server Error" }); 13 | } 14 | }; 15 | 16 | 17 | /*dis.forEach(async(itm)=>{ 18 | await db.collection("dishes").insertOne(itm) 19 | })*/ 20 | 21 | //await db.collection("dishes").deleteMany({}) 22 | -------------------------------------------------------------------------------- /src/pages/api/order-details/[id].js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "bson"; 2 | import { getSession } from "next-auth/client"; 3 | import { connectToDatabase } from "../../../util/mongodb"; 4 | 5 | export default async (req, res) => { 6 | try { 7 | const session = await getSession({ req }); 8 | if (session) { 9 | const { db } = await connectToDatabase(); 10 | let order; 11 | try { 12 | if (session.admin) { 13 | order = await db 14 | .collection("orders") 15 | .findOne({ _id: ObjectId(req.query.id) }); 16 | } else { 17 | order = await db 18 | .collection("orders") 19 | .findOne({ user: session.user.email, _id: ObjectId(req.query.id) }); 20 | } 21 | } catch (err) { 22 | console.error(err); 23 | return res.status(400).json({ message: "Bad Request" }); 24 | } 25 | if (!order) { 26 | return res.status(404).json({ message: "Not Found" }); 27 | } 28 | order = JSON.parse(JSON.stringify(order)); 29 | return res.status(200).json(order); 30 | } else { 31 | return res.status(401).json({ message: "Unauthorized" }); 32 | } 33 | } catch (err) { 34 | console.error(err); 35 | return res.status(500).json({ message: "Internal Server Error" }); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/pages/api/orders.js: -------------------------------------------------------------------------------- 1 | import { getSession } from "next-auth/client"; 2 | import { connectToDatabase } from "../../util/mongodb"; 3 | 4 | export default async (req, res) => { 5 | try { 6 | const session = await getSession({ req }); 7 | if (session) { 8 | const { db } = await connectToDatabase(); 9 | let orders = await db 10 | .collection("orders") 11 | .find({ user: session.user.email, payment_status: "paid" }) 12 | .sort({ timestamp: -1 }) 13 | .toArray(); 14 | orders = JSON.parse(JSON.stringify(orders)); 15 | return res.status(200).json(orders); 16 | } else { 17 | return res.status(401).json({ message: "Unauthorized" }); 18 | } 19 | } catch (err) { 20 | console.error(err); 21 | return res.status(500).json({ message: "Internal Server Error" }); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/pages/api/webhook.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "bson"; 2 | import { buffer } from "micro"; 3 | import { connectToDatabase } from "../../util/mongodb"; 4 | 5 | //Establish connection to Stripe 6 | const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); 7 | const endpointSecret = process.env.STRIPE_SIGNING_SECRET; 8 | 9 | const fulfillOrder = async (session) => { 10 | try { 11 | const { db } = await connectToDatabase(); 12 | let result = await db 13 | .collection("temp") 14 | .findOne({ _id: ObjectId(JSON.parse(session.metadata.id)) }); 15 | delete result._id; 16 | const ord_status = { status: "shipping soon", timestamp: new Date() }; 17 | await db.collection("orders").insertOne({ 18 | order_status: { 19 | current: ord_status, 20 | info: [ord_status], 21 | }, 22 | ...result, 23 | ...session, 24 | timestamp: new Date(), 25 | }); 26 | console.log(`SUCCESS: Order ${session.id} has been added to the DB`); 27 | } catch (err) { 28 | console.error(err); 29 | } 30 | }; 31 | 32 | export default async (req, res) => { 33 | if (req.method === "POST") { 34 | const requestBuffer = await buffer(req); 35 | const payload = requestBuffer.toString(); 36 | const sig = req.headers["stripe-signature"]; 37 | 38 | let event; 39 | 40 | //Verify that the EVENT posted came from stripe 41 | try { 42 | event = stripe.webhooks.constructEvent(payload, sig, endpointSecret); 43 | } catch (err) { 44 | console.error(err.message); 45 | return res.status(400).json({ message: err.message }); 46 | } 47 | 48 | //Handle the checkout.session.completed event 49 | if (event.type === "checkout.session.completed") { 50 | const session = event.data.object; 51 | 52 | //Fulfill the order 53 | return fulfillOrder(session) 54 | .then(() => res.status(200).json({ message: "success" })) 55 | .catch((err) => { 56 | console.error(err); 57 | return res.status(400).json({ message: err.message }); 58 | }); 59 | } 60 | } 61 | }; 62 | 63 | export const config = { 64 | api: { 65 | bodyParser: false, 66 | externalResolver: true, 67 | }, 68 | }; 69 | -------------------------------------------------------------------------------- /src/pages/cart.js: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from "react-redux"; 2 | import Currency from "react-currency-formatter"; 3 | import { signIn, useSession } from "next-auth/client"; 4 | import { loadStripe } from "@stripe/stripe-js"; 5 | import axios from "axios"; 6 | import { emptyCart, selectItems, selectTotal } from "../slices/cartSlice"; 7 | import CartDish from "../components/CartDish/CartDish"; 8 | import { CreditCardIcon } from "@heroicons/react/solid"; 9 | import { useState } from "react"; 10 | import Head from "next/head"; 11 | import Image from "next/image"; 12 | 13 | const stripePromise = loadStripe(process.env.stripe_public_key); 14 | 15 | function Cart() { 16 | const items = useSelector(selectItems); 17 | const total = useSelector(selectTotal); 18 | const [session] = useSession(); 19 | const [disabled, setDisabled] = useState(false); 20 | const dispatch = useDispatch(); 21 | 22 | const createCheckoutSession = async () => { 23 | setDisabled(true); 24 | try { 25 | const stripe = await stripePromise; 26 | //call the backend to create a checkout session 27 | const checkoutSession = await axios.post("/api/create-checkout-session", { 28 | items: items, 29 | email: session.user.email, 30 | }); 31 | //Redirect user/customer to Stripe Checkout 32 | const result = await stripe.redirectToCheckout({ 33 | sessionId: checkoutSession.data.id, 34 | }); 35 | if (result.error) { 36 | alert(result.error.message); 37 | console.error(result.error.message); 38 | } 39 | } catch (err) { 40 | console.error(err); 41 | alert(err); 42 | } 43 | setDisabled(false); 44 | }; 45 | 46 | return ( 47 | <> 48 | 49 | Zinger | Cart 50 | 51 |
52 |
53 | {items?.length ? ( 54 |
55 |
56 |

57 | Shopping Cart 58 |

59 |
60 | 61 | Items 62 | 63 | {items?.length} 64 | 65 | 66 | 74 |
75 | {items.map((item, i) => ( 76 | 88 | ))} 89 |
90 |
91 | ) : ( 92 |
93 |
94 | 101 |

102 | Your Cart is Empty 103 |

104 |
105 |
106 | )} 107 | {items?.length ? ( 108 |
109 |

110 | Subtotal ({items.length} items) : 111 | 112 | 113 | 114 |

115 | {session ? ( 116 | 126 | ) : ( 127 | 134 | )} 135 |
136 | ) : ( 137 | <> 138 | )} 139 |
140 |
141 | 142 | ); 143 | } 144 | 145 | export default Cart; 146 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import About from "../components/About/About"; 2 | import Banner from "../components/Banner/Banner"; 3 | import FoodGallery from "../components/FoodGallery/FoodGallery"; 4 | import HowItWork from "../components/HowItWork/HowItWork"; 5 | import Info from "../components/Info/Info"; 6 | import Menu from "../components/Menu/Menu"; 7 | import Testimonials from "../components/Testimonials/Testimonials"; 8 | import getCategories from "../util/getCategories"; 9 | import getDishes from "../util/getDishes"; 10 | import { connectToDatabase } from "../util/mongodb"; 11 | 12 | export default function Home(props) { 13 | const { dishes, error } = getDishes(props?.dishes); 14 | const { categories, error: err } = getCategories(props?.categories); 15 | 16 | if (err) { 17 | console.error(err); 18 | } 19 | 20 | if (error) { 21 | console.error(error); 22 | } 23 | return ( 24 | <> 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | 36 | export const getStaticProps = async () => { 37 | const { db } = await connectToDatabase(); 38 | let dishes = await db.collection("dishes").find({}).toArray(); 39 | dishes = JSON.parse(JSON.stringify(dishes)); 40 | let categories = await db.collection("categories").find({}).toArray(); 41 | categories = JSON.parse(JSON.stringify(categories)); 42 | 43 | return { 44 | props: { 45 | dishes, 46 | categories, 47 | }, 48 | revalidate: 1, 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /src/pages/order-details/[id].js: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import OrderDetails from "../../components/Order/OrderDetails"; 3 | import Head from "next/head"; 4 | function orderDetails() { 5 | const router = useRouter(); 6 | return ( 7 | <> 8 | 9 | Zinger | OrderDetails 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | orderDetails.auth = true; 17 | export default orderDetails; 18 | -------------------------------------------------------------------------------- /src/pages/orders.js: -------------------------------------------------------------------------------- 1 | import { useSession, signIn } from "next-auth/client"; 2 | import Order from "../components/Order/Order"; 3 | import useSWR from "swr"; 4 | import Skeleton from "react-loading-skeleton"; 5 | import Image from "next/image"; 6 | import Head from "next/head"; 7 | 8 | function Orders() { 9 | const [session, loading] = useSession(); 10 | const { data: orders, error } = useSWR( 11 | !loading && session ? "/api/orders" : null 12 | ); 13 | 14 | if (error) { 15 | console.error(error); 16 | } 17 | 18 | return ( 19 | <> 20 | 21 | Zinger | Orders 22 | 23 |
24 |
25 |

26 | Your Orders 27 |

28 | {session ? ( 29 | <> 30 |

31 | {orders ? ( 32 | <> 33 | 34 | {orders?.length} 35 | 36 | Orders 37 | 38 | ) : ( 39 | 40 | )} 41 |

42 | {orders ? ( 43 | orders.length ? ( 44 |
45 | {orders.map( 46 | ({ 47 | _id, 48 | id, 49 | amount_total, 50 | items, 51 | timestamp, 52 | order_status, 53 | }) => ( 54 | 63 | ) 64 | )} 65 |
66 | ) : ( 67 |
68 | 75 |
76 | ) 77 | ) : ( 78 | 79 | )} 80 | 81 | ) : ( 82 | <> 83 |
84 |

85 | Please 86 | 90 | login 91 | 92 | in to view your orders. 93 |

94 |
95 | 101 |
102 |
103 | 104 | )} 105 |
106 |
107 | 108 | ); 109 | } 110 | 111 | export default Orders; 112 | -------------------------------------------------------------------------------- /src/pages/profile.js: -------------------------------------------------------------------------------- 1 | import { useSession } from "next-auth/client"; 2 | import Head from "next/head"; 3 | import Fade from "react-reveal/Fade"; 4 | 5 | function Profile() { 6 | const [session] = useSession(); 7 | 8 | return ( 9 | <> 10 | 11 | Zinger | Profile 12 | 13 |
14 |
15 |

16 | Profile 17 |

18 |
19 | 20 | 28 |

29 | 30 | Name - 31 | 32 | {session?.user?.name} 33 |

34 |

35 | 36 | Email - 37 | 38 | {session?.user?.email} 39 |

40 |

41 | "Whoever said money can't buy happiness didn't know where to 42 | shop". 43 |

44 |
45 |
46 |
47 |
48 | 49 | ); 50 | } 51 | 52 | Profile.auth = true; 53 | 54 | export default Profile; 55 | -------------------------------------------------------------------------------- /src/pages/success.js: -------------------------------------------------------------------------------- 1 | import { CheckCircleIcon } from "@heroicons/react/solid"; 2 | import { useSession } from "next-auth/client"; 3 | import { useRouter } from "next/router"; 4 | import Custom404 from "./404"; 5 | import Head from "next/head"; 6 | 7 | function Success() { 8 | const router = useRouter(); 9 | const [session, loading] = useSession(); 10 | 11 | if (!loading && !session) { 12 | return ; 13 | } 14 | 15 | return ( 16 | <> 17 | 18 | Zinger | Order Placed Successfully 19 | 20 |
21 |
22 |
23 |
24 | 25 |

26 | Order Placed Successfully 27 |

28 |
29 |

30 | Thank you for shopping with us. Your order will be delivered soon. 31 |

32 | 38 |
39 |
40 |
41 | 42 | ); 43 | } 44 | 45 | export default Success; 46 | -------------------------------------------------------------------------------- /src/slices/cartSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import addedToCartToast from "../util/Toast/addedToCartToast"; 3 | 4 | const initialState = { 5 | items: [], 6 | }; 7 | 8 | export const cartSlice = createSlice({ 9 | name: "cart", 10 | initialState, 11 | reducers: { 12 | //Actions 13 | hydrate: (state, action) => { 14 | return action.payload; 15 | }, 16 | //Add item to cart 17 | addToCart: (state, action) => { 18 | const index = state.items.findIndex( 19 | (cartItem) => cartItem._id === action.payload._id 20 | ); 21 | if (index >= 0) { 22 | let newCart = [...state.items]; 23 | newCart[index] = { 24 | ...newCart[index], 25 | qty: newCart[index].qty + 1, 26 | }; 27 | state.items = newCart; 28 | } else { 29 | let item = { ...action.payload }; 30 | delete item.toast; 31 | state.items = [...state.items, item]; 32 | } 33 | //Toast to indicate item added to cart 34 | if (action.payload.toast) { 35 | addedToCartToast(action.payload.image, action.payload.title); 36 | } 37 | }, 38 | //Update the quantity of item in cart 39 | updateQty: (state, action) => { 40 | let newCart = [...state.items]; 41 | const index = state.items.findIndex( 42 | (cartItem) => cartItem._id === action.payload._id 43 | ); 44 | if (index >= 0) { 45 | if (action.payload.qty >= 1) { 46 | newCart[index] = action.payload; 47 | state.items = newCart; 48 | } else { 49 | newCart.splice(index, 1); 50 | state.items = newCart; 51 | } 52 | } else { 53 | console.warn("Dish not present in the cart!"); 54 | } 55 | }, 56 | //Remove a item from cart 57 | removeFromCart: (state, action) => { 58 | const index = state.items.findIndex( 59 | (cartItem) => cartItem._id === action.payload._id 60 | ); 61 | let newBastek = [...state.items]; 62 | if (index >= 0) { 63 | newBastek.splice(index, 1); 64 | } else { 65 | console.warn( 66 | `Can't remove dish (_id:${action.payload._id}) as its not in the cart` 67 | ); 68 | } 69 | state.items = newBastek; 70 | }, 71 | //Empty the cart 72 | emptyCart: (state, action) => { 73 | state.items = []; 74 | }, 75 | }, 76 | }); 77 | 78 | export const { addToCart, removeFromCart, updateQty, hydrate, emptyCart } = 79 | cartSlice.actions; 80 | 81 | // Selectors - This is how we pull information from the Global store slice 82 | export const selectItems = (state) => state.cart.items; 83 | export const selectTotal = (state) => 84 | state.cart.items.reduce((total, item) => total + item.price * item.qty, 0); 85 | 86 | export default cartSlice.reducer; 87 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | /* ./styles/globals.css */ 2 | 3 | /* Tailwindcss */ 4 | @tailwind base; 5 | @tailwind components; 6 | @tailwind utilities; 7 | 8 | /* Fonts */ 9 | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;0,600;0,700;0,800;1,400&display=swap"); 10 | 11 | /* Custom tailwind class */ 12 | @layer components { 13 | .link { 14 | @apply cursor-pointer hover:text-primary-light active:text-primary-light; 15 | } 16 | .button { 17 | @apply cursor-pointer rounded text-center p-2 text-sm text-white bg-gradient-to-b from-primary-light to-primary-dark border border-primary-light focus:outline-none focus:ring-2 focus:ring-primary-dark active:from-primary-dark; 18 | } 19 | .button-ghost{ 20 | @apply cursor-pointer rounded text-center p-2 text-sm text-primary-light focus:outline-none border-2 border-primary-light hover:shadow-md focus:shadow-md active:shadow-md; 21 | } 22 | .button-red { 23 | @apply cursor-pointer rounded text-center p-2 text-sm text-white bg-gradient-to-b from-red-500 to-red-800 border-red-500 focus:outline-none focus:ring-2 focus:ring-red-500 active:from-red-800; 24 | } 25 | .dropDownOption { 26 | @apply w-full cursor-pointer hover:bg-gray-100 py-2 px-3; 27 | } 28 | .dashboard-link { 29 | @apply link rounded border px-4 py-1 hover:bg-primary-light hover:text-white transition-all duration-200 text-sm; 30 | } 31 | .heading{ 32 | @apply lg:text-4xl text-center font-medium sm:text-3xl text-2xl ; 33 | } 34 | } 35 | 36 | * { 37 | -webkit-tap-highlight-color: transparent; 38 | scroll-behavior: smooth; 39 | } 40 | 41 | .heading::after { 42 | display: block; 43 | height: 2px; 44 | background-color: #ab3c2a; 45 | content: " "; 46 | width: 100px; 47 | margin: 0 auto; 48 | margin-top: 30px; 49 | } 50 | html, 51 | body { 52 | font-family: "Poppins", sans-serif; 53 | -webkit-font-smoothing: antialiased; 54 | -moz-osx-font-smoothing: grayscale; 55 | color: #1f2937; 56 | scroll-behavior: smooth; 57 | } 58 | .main_heading{ 59 | min-width: 400px; 60 | } 61 | 62 | .heightFix, 63 | .heightFixAdmin { 64 | min-height: calc(100vh - 150px); 65 | height: 100%; 66 | } 67 | 68 | .loader svg { 69 | width: 150px; 70 | height: 150px; 71 | } 72 | 73 | /* Hide scrollbar for Chrome, Safari and Opera */ 74 | .hideScrollBar::-webkit-scrollbar { 75 | display: none; 76 | } 77 | .table_col_img { 78 | min-width: 40px; 79 | } 80 | 81 | .table_col { 82 | min-width: 120px; 83 | } 84 | 85 | /* Hide scrollbar for IE, Edge and Firefox */ 86 | .hideScrollBar { 87 | -ms-overflow-style: none; /* IE and Edge */ 88 | scrollbar-width: none; /* Firefox */ 89 | } 90 | 91 | .glassmorphism { 92 | background-color: white; 93 | background: rgba(255, 255, 255, 0.9); 94 | backdrop-filter: blur(6px); 95 | -webkit-backdrop-filter: blur(6px); 96 | } 97 | 98 | .layout { 99 | min-width: 320px; 100 | } 101 | .sideBarMenu { 102 | min-width: 250px; 103 | } 104 | 105 | .layout .Toastify__progress-bar--default { 106 | background: #ab3c2a; 107 | background: linear-gradient(#ab3c2a, #792a1e) !important; 108 | } 109 | 110 | @media only screen and (max-width: 780px) { 111 | .loader svg { 112 | width: 120px; 113 | height: 120px; 114 | } 115 | } 116 | 117 | @media only screen and (max-width: 780px) { 118 | .heightFix { 119 | min-height: calc(100vh - 200px); 120 | height: 100%; 121 | } 122 | } 123 | 124 | @media only screen and (max-width: 640px) { 125 | .main_heading{ 126 | min-width:auto; 127 | } 128 | } 129 | 130 | -------------------------------------------------------------------------------- /src/util/StorageService.js: -------------------------------------------------------------------------------- 1 | const LOCALSTORAGE_KEY_PREFIX = "Zinger"; 2 | 3 | export default { 4 | get(item) { 5 | try { 6 | return window.localStorage.getItem(`${LOCALSTORAGE_KEY_PREFIX}:${item}`); 7 | } catch (e) { 8 | console.error(e); 9 | return null; 10 | } 11 | }, 12 | set(item, value) { 13 | try { 14 | window.localStorage.setItem(`${LOCALSTORAGE_KEY_PREFIX}:${item}`, value); 15 | } catch (e) { 16 | console.error(e); 17 | } 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/util/Toast/NormalToast.js: -------------------------------------------------------------------------------- 1 | import { toast } from "react-toastify"; 2 | 3 | const NormalToast = (msg, error) => { 4 | toast( 5 |
6 | {msg} 7 |
, 8 | { 9 | position: "top-right", 10 | autoClose: 4000, 11 | style: { 12 | background: "white", 13 | color: "#1f2937", 14 | fontFamily: "Poppins, sans-serif", 15 | height: "auto", 16 | }, 17 | hideProgressBar: true, 18 | pauseOnHover: false, 19 | draggable: true, 20 | draggablePercent: 25, 21 | } 22 | ); 23 | }; 24 | 25 | export default NormalToast; 26 | -------------------------------------------------------------------------------- /src/util/Toast/addedToCartToast.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { toast } from "react-toastify"; 3 | import Image from "next/image"; 4 | import { CreditCardIcon } from "@heroicons/react/outline"; 5 | 6 | const addedToCartToast = (image, title) => { 7 | toast( 8 |
9 |
10 | 18 |
19 |
20 |

Added to cart

21 |

22 | {title.slice(0, 22)} 23 | {title.length > 22 ? "…" : ""} 24 |

25 | 26 | 29 | 30 |
31 |
, 32 | 33 | { 34 | position: "top-right", 35 | autoClose: 6000, 36 | style: { 37 | backgroundColor: "white", 38 | color: "#1f2937", 39 | fontFamily: "Poppins, sans-serif", 40 | height: "auto", 41 | }, 42 | hideProgressBar: false, 43 | pauseOnHover: false, 44 | draggable: true, 45 | draggablePercent: 25, 46 | } 47 | ); 48 | }; 49 | 50 | export default addedToCartToast; 51 | -------------------------------------------------------------------------------- /src/util/fetch.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const fetcher = (...args) => 4 | args.length ? axios.get(...args).then((res) => res.data) : null; 5 | 6 | export default fetcher; 7 | -------------------------------------------------------------------------------- /src/util/getCategories.js: -------------------------------------------------------------------------------- 1 | import useSWR from "swr"; 2 | 3 | const getCategories = (initialData) => { 4 | let res; 5 | if (initialData) { 6 | res = useSWR("/api/categories", { initialData }); 7 | } else { 8 | res = useSWR("/api/categories"); 9 | } 10 | return { 11 | categories: res.data, 12 | isLoading: !res.error && !res.data, 13 | error: res.error, 14 | }; 15 | }; 16 | 17 | export default getCategories; 18 | -------------------------------------------------------------------------------- /src/util/getDishes.js: -------------------------------------------------------------------------------- 1 | import useSWR from "swr"; 2 | 3 | const getDishes = (initialData) => { 4 | let res; 5 | if (initialData) { 6 | res = useSWR("/api/dishes", { initialData }); 7 | } else { 8 | res = useSWR("/api/dishes"); 9 | } 10 | return { 11 | dishes: res.data, 12 | isLoading: !res.error && !res.data, 13 | error: res.error, 14 | }; 15 | }; 16 | 17 | export default getDishes; 18 | -------------------------------------------------------------------------------- /src/util/mongodb.js: -------------------------------------------------------------------------------- 1 | import { MongoClient } from "mongodb"; 2 | 3 | const MONGODB_URI = process.env.MONGODB_URI; 4 | const MONGODB_DB = process.env.MONGODB_DB; 5 | 6 | if (!MONGODB_URI) { 7 | throw new Error( 8 | "Please define the MONGODB_URI environment variable inside .env.local" 9 | ); 10 | } 11 | 12 | if (!MONGODB_DB) { 13 | throw new Error( 14 | "Please define the MONGODB_DB environment variable inside .env.local" 15 | ); 16 | } 17 | 18 | /** 19 | * Global is used here to maintain a cached connection across hot reloads 20 | * in development. This prevents connections growing exponentially 21 | * during API Route usage. 22 | */ 23 | let cached = global.mongo; 24 | 25 | if (!cached) { 26 | cached = global.mongo = { conn: null, promise: null }; 27 | } 28 | 29 | export async function connectToDatabase() { 30 | if (cached.conn) { 31 | return cached.conn; 32 | } 33 | 34 | if (!cached.promise) { 35 | const opts = { 36 | useNewUrlParser: true, 37 | useUnifiedTopology: true, 38 | }; 39 | 40 | cached.promise = MongoClient.connect(MONGODB_URI, opts).then((client) => { 41 | return { 42 | client, 43 | db: client.db(MONGODB_DB), 44 | }; 45 | }); 46 | } 47 | cached.conn = await cached.promise; 48 | return cached.conn; 49 | } 50 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: "jit", 3 | purge: [ 4 | "./src/pages/**/*.{js,ts,jsx,tsx}", 5 | "./src/components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | darkMode: false, // or 'media' or 'class' 8 | theme: { 9 | fontFamily: { 10 | Poppins: ["Poppins", "sans-serif"], 11 | }, 12 | extend: { 13 | colors: { 14 | primary:{ 15 | light:"#ab3c2a", 16 | dark:"#792a1e", 17 | } 18 | }, 19 | }, 20 | screens: { 21 | xxs: "375px", 22 | xs: "425px", 23 | sm: "640px", 24 | md: "768px", 25 | lg: "1024px", 26 | xl: "1280px", 27 | "2xl": "1536px", 28 | }, 29 | }, 30 | variants: { 31 | extend: {}, 32 | }, 33 | plugins: [require("@tailwindcss/line-clamp")], 34 | } 35 | --------------------------------------------------------------------------------