├── .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 |
15 |
16 |
17 |
18 |
19 | # Zinger
20 |
21 | Food ordering website for Zinger restaurant built using Next.js, TailwindCSS, Redux, Mongodb
22 |
23 | 
24 |
25 |
26 |
27 |
28 | ## Demo
29 |
30 | https://zinger.vercel.app
31 |
32 |
33 | ## Screenshots
34 |
35 | 
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 | 
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 |
37 | Order Now
38 |
39 |
40 | View More
41 |
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 |
89 |
90 |
91 |
92 |
93 | {qty}
94 |
95 |
96 |
101 |
102 |
103 |
104 |
110 | Remove
111 |
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 |
53 |
54 | Add to Cart
55 |
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 | router.push(`/admin/update-dish/${_id}`)}
57 | disabled={disabled}
58 | >
59 | Update
60 |
61 | deleteDish(_id)}
65 | disabled={disabled}
66 | >
67 | Delete
68 |
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 |
46 |
47 |
48 | {
56 | router.push("https://www.linkedin.com/in/piyush-sati");
57 | }}
58 | />
59 |
60 |
61 | 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 |
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 |
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 | 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 |
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 |
81 | >
82 | ) : (
83 | signIn()
84 | )
85 | ) : auth ? (
86 | session ? (
87 | <>
88 |
89 |
90 | {children}
91 |
92 | >
93 | ) : (
94 | signIn()
95 | )
96 | ) : (
97 | <>
98 |
99 |
100 | {children}
101 |
102 | >
103 | )}
104 |
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 |
54 | Shipping soon
55 | Shipped
56 | Out for delivery
57 | Delivered
58 |
59 | ) : (
60 | <>>
61 | )
62 | ) : status ? (
63 |
71 | {status}
72 |
73 | ) : (
74 | <>>
75 | )}
76 | {status && status !== "cancelled" && status !== "delivered" ? (
77 |
83 | Cancel order
84 |
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 |
83 | Shipping soon
84 | Shipped
85 | Out for delivery
86 | Delivered
87 |
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 |
213 | Cancel Order
214 |
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 |
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 |
25 | HomePage
26 |
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 |
24 | HomePage
25 |
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 |
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 |
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 |
37 |
38 |
39 |
40 |
41 | Users
42 |
43 |
44 |
45 |
49 |
50 |
51 |
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 |
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 |
31 | Pic
32 |
33 |
34 | Name
35 |
36 |
37 | Email
38 |
39 |
40 |
41 |
42 | {users?.map((user) => (
43 |
44 |
45 |
50 |
51 | {user?.name}
52 | {user?.email}
53 |
54 | ))}
55 |
56 |
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 | dispatch(emptyCart())}
70 | disabled={disabled}
71 | >
72 | Empty Cart
73 |
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 | { }}
121 | disabled={disabled}
122 | >
123 |
124 | Proceed to checkout
125 |
126 | ) : (
127 |
132 | Sign in to checkout
133 |
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 |
router.replace("/orders")}
35 | >
36 | Go to my orders
37 |
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 |
27 | Checkout
28 |
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 |
--------------------------------------------------------------------------------