├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── logo.png ├── menu.json └── reviews.json ├── src ├── App.css ├── App.jsx ├── Layout │ ├── Dashboard.jsx │ └── Main.jsx ├── Routes │ ├── AdminRoute.jsx │ ├── PrivateRoute.jsx │ └── Routes.jsx ├── assets │ ├── 404.gif │ ├── contact │ │ ├── banner.jpg │ │ └── emo.png │ ├── dashboard │ │ └── image-5.jpg │ ├── home │ │ ├── 01.jpg │ │ ├── 02.jpg │ │ ├── 03.png │ │ ├── 04.jpg │ │ ├── 05.png │ │ ├── 06.png │ │ ├── banner.jpg │ │ ├── chef-special.jpg │ │ ├── featured.jpg │ │ ├── slide1.jpg │ │ ├── slide2.jpg │ │ ├── slide3.jpg │ │ ├── slide4.jpg │ │ └── slide5.jpg │ ├── icon │ │ ├── 151-1511569_cart-notifications-free-shopping-cart-favicon-hd-png-removebg-preview.png │ │ └── correct.png │ ├── logo.png │ ├── menu │ │ ├── banner.png │ │ ├── dessert-bg.jpeg │ │ ├── menu-bg.jpg │ │ ├── pizza-bg.jpg │ │ ├── salad-bg.jpg │ │ └── soup-bg.jpg │ ├── others │ │ ├── Illustration.svg │ │ ├── authentication.gif │ │ ├── authentication.png │ │ ├── authentication1.png │ │ ├── authentication2.png │ │ ├── cupcake-dribbble.gif │ │ ├── cupcake.gif │ │ ├── download (1).jpg │ │ ├── loader2.gif │ │ ├── loader3.gif │ │ └── profile.png │ ├── reservation │ │ ├── category-pizza.jpg │ │ └── wood-grain-pattern-gray1x.png │ └── shop │ │ └── order.jpg ├── components │ ├── FoodCard │ │ └── FoodCard.jsx │ ├── SectionTitle │ │ └── SectionTitle.jsx │ └── SocialLogin │ │ └── SocialLogin.jsx ├── firebase │ └── firebase.config.js ├── hooks │ ├── useAdmin.jsx │ ├── useAuth.jsx │ ├── useAxiosPublic.jsx │ ├── useAxiosSecure.jsx │ ├── useCart.jsx │ └── useMenu.jsx ├── index.css ├── main.jsx ├── pages │ ├── Dashboard │ │ ├── AddItems │ │ │ └── AddItems.jsx │ │ ├── AllUsers │ │ │ └── AllUsers.jsx │ │ ├── Cart │ │ │ └── Cart.jsx │ │ ├── ManageItems │ │ │ └── ManageItems.jsx │ │ ├── UpdateItem │ │ │ └── UpdateItem.jsx │ │ └── __Page__level_security__.js │ ├── Home │ │ ├── Banner │ │ │ └── Banner.jsx │ │ ├── Category │ │ │ └── Category.jsx │ │ ├── Featured │ │ │ ├── Featured.css │ │ │ └── Featured.jsx │ │ ├── Home │ │ │ └── Home.jsx │ │ ├── PopularMenu │ │ │ └── PopularMenu.jsx │ │ └── Testimonials │ │ │ └── Testimonials.jsx │ ├── Login │ │ └── Login.jsx │ ├── Menu │ │ ├── Menu │ │ │ └── Menu.jsx │ │ └── MenuCategory │ │ │ └── MenuCategory.jsx │ ├── Order │ │ ├── Order │ │ │ └── Order.jsx │ │ └── OrderTab │ │ │ └── OrderTab.jsx │ ├── Shared │ │ ├── Cover │ │ │ └── Cover.jsx │ │ ├── Footer │ │ │ └── Footer.jsx │ │ ├── MenuItem │ │ │ └── MenuItem.jsx │ │ ├── NavBar │ │ │ └── NavBar.jsx │ │ └── Secret │ │ │ └── Secret.jsx │ └── SignUp │ │ └── SignUp.jsx └── providers │ └── AuthProvider.jsx ├── tailwind.config.js └── vite.config.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2020: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:react/recommended', 10 | 'plugin:react/jsx-runtime', 11 | 'plugin:react-hooks/recommended', 12 | ], 13 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 14 | settings: { react: { version: '18.2' } }, 15 | plugins: ['react-refresh'], 16 | rules: { 17 | 'react-refresh/only-export-components': 'warn', 18 | "react/prop-types": "off" 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bistro Boss Client with Image Upload 2 | 3 | Client Repo Link - [Bistro Boss Server with Image Upload](https://github.com/ProgrammingHero1/bistro-boss-server-with-image-upload-part_6) 4 | 5 | ## Overview 6 | BistroBoss is a comprehensive restaurant management system designed to streamline operations, enhance customer experience, and improve overall efficiency. The system leverages modern web technologies to provide a robust, scalable, and high-performance solution for managing complex data structures and interactions within a restaurant environment . 7 | 8 | ## What We Learn Today 9 | 10 | ### Why We Use React-Query, Axios, and React Hook Form 11 | - **React-Query**: Simplifies data fetching and state management, providing caching, synchronization, and background updates to enhance user experience and performance. 12 | - **Axios**: A promise-based HTTP client that makes it easier to make requests to the server, handle responses, and manage errors compared to the native `fetch` API. 13 | - **React Hook Form**: Provides a flexible and efficient way to handle form inputs, validations, and submission, reducing boilerplate code and improving performance with minimal re-renders . 14 | 15 | ### Create Add Item Form Using React Hook Form 16 | We learn how to create a user-friendly and dynamic form for adding new menu items using React Hook Form, ensuring smooth data handling and validation with minimal code. 17 | 18 | ### Design Form Using Daisy UI and React Hook Form 19 | Incorporate Daisy UI to style our forms seamlessly, combining it with React Hook Form to ensure that our forms are both visually appealing and functionally robust. 20 | 21 | ### Upload Image to Image Hosting Server Imgbb and Get Image URL 22 | Learn the process of uploading images to the Imgbb image hosting service, retrieving the image URL, and handling this URL within our application to store image references efficiently. 23 | 24 | ### Save Menu Item to the Server and Make API Secure 25 | Understand how to securely save new menu items to the server, ensuring data integrity and security through proper API design and implementation. 26 | 27 | ### Create Manage Items Admin Route and Load All Menu Items 28 | Develop an administrative route for managing menu items, including loading all items from the server and displaying them in a structured and manageable way. 29 | 30 | ### Delete Menu Item with VerifyAdmin Middleware 31 | Implement functionality to delete menu items securely, using the VerifyAdmin middleware to ensure that only authorized users can perform deletion operations. 32 | 33 | ### Create Update Menu Item Form 34 | Learn to create a form for updating existing menu items, allowing for efficient editing and ensuring that changes are accurately reflected in the system. 35 | 36 | 37 | --- 38 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bistro Boss Restaurant 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bistro-boss-client", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@smastrom/react-rating": "^1.3.1", 14 | "@tanstack/react-query": "^5.8.2", 15 | "axios": "^1.6.1", 16 | "daisyui": "^2.51.6", 17 | "firebase": "^9.22.0", 18 | "localforage": "^1.10.0", 19 | "match-sorter": "^6.3.1", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "react-helmet-async": "^1.3.0", 23 | "react-hook-form": "^7.43.9", 24 | "react-icons": "^4.11.0", 25 | "react-parallax": "^3.5.1", 26 | "react-responsive-carousel": "^3.2.23", 27 | "react-router-dom": "^6.11.2", 28 | "react-simple-captcha": "^9.0.2", 29 | "react-tabs": "^6.0.1", 30 | "sort-by": "^1.2.0", 31 | "sweetalert2": "^11.7.5", 32 | "swiper": "^9.3.2" 33 | }, 34 | "devDependencies": { 35 | "@types/react": "^18.0.28", 36 | "@types/react-dom": "^18.0.11", 37 | "@vitejs/plugin-react": "^4.0.0", 38 | "autoprefixer": "^10.4.14", 39 | "eslint": "^8.38.0", 40 | "eslint-plugin-react": "^7.32.2", 41 | "eslint-plugin-react-hooks": "^4.6.0", 42 | "eslint-plugin-react-refresh": "^0.3.4", 43 | "postcss": "^8.4.23", 44 | "tailwindcss": "^3.3.2", 45 | "vite": "^4.3.2" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/public/logo.png -------------------------------------------------------------------------------- /public/menu.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "642c155b2c4774f05c36eeaa", 4 | "name": "Haddock", 5 | "recipe": "Chargrilled fresh tuna steak (served medium rare) on classic Niçoise salad with French beans.", 6 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 7 | "category": "salad", 8 | "price": 14.7 9 | }, 10 | { 11 | "_id": "642c155b2c4774f05c36eeb9", 12 | "name": "Haddock", 13 | "recipe": "Chargrilled fresh tuna steak (served medium rare) on classic Niçoise salad with French beans.", 14 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 15 | "category": "drinks", 16 | "price": 14.7 17 | }, 18 | { 19 | "_id": "642c155b2c4774f05c36ee7c", 20 | "name": "Escalope de Veau", 21 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 22 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-5-370x247.jpg", 23 | "category": "popular", 24 | "price": 14.5 25 | }, 26 | { 27 | "_id": "642c155b2c4774f05c36ee88", 28 | "name": "Escalope de Veau", 29 | "recipe": "Pan roasted haddock fillet wrapped in smoked French bacon with pea purée and tomato and chive vinaigrette", 30 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 31 | "category": "dessert", 32 | "price": 12.5 33 | }, 34 | { 35 | "_id": "642c155b2c4774f05c36ee7a", 36 | "name": "Roast Duck Breast", 37 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 38 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-5-370x247.jpg", 39 | "category": "popular", 40 | "price": 14.5 41 | }, 42 | { 43 | "_id": "642c155b2c4774f05c36ee8c", 44 | "name": "Escalope de Veau", 45 | "recipe": "Pan roasted haddock fillet wrapped in smoked French bacon with pea purée and tomato and chive vinaigrette", 46 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 47 | "category": "dessert", 48 | "price": 12.5 49 | }, 50 | { 51 | "_id": "642c155b2c4774f05c36ee94", 52 | "name": "Escalope de Veau", 53 | "recipe": "Pan roasted haddock fillet wrapped in smoked French bacon with pea purée and tomato and chive vinaigrette", 54 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 55 | "category": "pizza", 56 | "price": 12.5 57 | }, 58 | { 59 | "_id": "642c155b2c4774f05c36ee9e", 60 | "name": "Breton Fish Stew", 61 | "recipe": "Chargrilled chicken with avocado, baby gem lettuce, baby spinach, shallots, French beans, walnuts, croutons and a mustard dressing", 62 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 63 | "category": "pizza", 64 | "price": 12.9 65 | }, 66 | { 67 | "_id": "642c155b2c4774f05c36eea6", 68 | "name": "Chicken and Walnut Salad", 69 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 70 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 71 | "category": "salad", 72 | "price": 13.5 73 | }, 74 | { 75 | "_id": "642c155b2c4774f05c36eeaf", 76 | "name": "Escalope de Veau", 77 | "recipe": "Pan roasted haddock fillet wrapped in smoked French bacon with pea purée and tomato and chive vinaigrette", 78 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 79 | "category": "soup", 80 | "price": 12.5 81 | }, 82 | { 83 | "_id": "642c155b2c4774f05c36ee89", 84 | "name": "Chicken and Walnut Salad", 85 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 86 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 87 | "category": "dessert", 88 | "price": 13.5 89 | }, 90 | { 91 | "_id": "642c155b2c4774f05c36ee8a", 92 | "name": "Escalope de Veau", 93 | "recipe": "Pan roasted haddock fillet wrapped in smoked French bacon with pea purée and tomato and chive vinaigrette", 94 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 95 | "category": "dessert", 96 | "price": 12.5 97 | }, 98 | { 99 | "_id": "642c155b2c4774f05c36ee96", 100 | "name": "Fish Parmentier", 101 | "recipe": "Sautéed breaded veal escalope with watercress, lemon and veal jus.", 102 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 103 | "category": "pizza", 104 | "price": 9.5 105 | }, 106 | { 107 | "_id": "642c155b2c4774f05c36ee81", 108 | "name": "Tuna Niçoise", 109 | "recipe": "Warm goats cheese and roasted vegetable salad with black olive tapenade crostini", 110 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 111 | "category": "offered", 112 | "price": 10.5 113 | }, 114 | { 115 | "_id": "642c155b2c4774f05c36ee84", 116 | "name": "Roast Duck Breast", 117 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 118 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-4-370x247.jpg", 119 | "category": "dessert", 120 | "price": 14.5 121 | }, 122 | { 123 | "_id": "642c155b2c4774f05c36eea4", 124 | "name": "Chicken and Walnut Salad", 125 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 126 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 127 | "category": "salad", 128 | "price": 13.5 129 | }, 130 | { 131 | "_id": "642c155b2c4774f05c36eea5", 132 | "name": "Fish Parmentier", 133 | "recipe": "Sautéed breaded veal escalope with watercress, lemon and veal jus.", 134 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 135 | "category": "salad", 136 | "price": 9.5 137 | }, 138 | { 139 | "_id": "642c155b2c4774f05c36ee87", 140 | "name": "Chicken and Walnut Salad", 141 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 142 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 143 | "category": "dessert", 144 | "price": 13.5 145 | }, 146 | { 147 | "_id": "642c155b2c4774f05c36ee83", 148 | "name": "Chicken and Walnut Salad", 149 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 150 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 151 | "category": "offered", 152 | "price": 13.5 153 | }, 154 | { 155 | "_id": "642c155b2c4774f05c36ee7f", 156 | "name": "Roasted Pork Belly", 157 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 158 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 159 | "category": "popular", 160 | "price": 14.5 161 | }, 162 | { 163 | "_id": "642c155b2c4774f05c36eea8", 164 | "name": "Chicken and Walnut Salad", 165 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 166 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 167 | "category": "salad", 168 | "price": 13.5 169 | }, 170 | { 171 | "_id": "642c155b2c4774f05c36eeba", 172 | "name": "Haddock", 173 | "recipe": "Chargrilled fresh tuna steak (served medium rare) on classic Niçoise salad with French beans.", 174 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 175 | "category": "drinks", 176 | "price": 14.7 177 | }, 178 | { 179 | "_id": "642c155b2c4774f05c36ee80", 180 | "name": "Roast Duck Breast", 181 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 182 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-4-370x247.jpg", 183 | "category": "offered", 184 | "price": 14.5 185 | }, 186 | { 187 | "_id": "642c155b2c4774f05c36ee90", 188 | "name": "Fish Parmentier", 189 | "recipe": "Sautéed breaded veal escalope with watercress, lemon and veal jus.", 190 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 191 | "category": "dessert", 192 | "price": 9.5 193 | }, 194 | { 195 | "_id": "642c155b2c4774f05c36eeb0", 196 | "name": "Chicken and Walnut Salad", 197 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 198 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 199 | "category": "soup", 200 | "price": 13.5 201 | }, 202 | { 203 | "_id": "642c155b2c4774f05c36ee8d", 204 | "name": "Chicken and Walnut Salad", 205 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 206 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 207 | "category": "dessert", 208 | "price": 13.5 209 | }, 210 | { 211 | "_id": "642c155b2c4774f05c36ee8e", 212 | "name": "Escalope de Veau", 213 | "recipe": "Pan roasted haddock fillet wrapped in smoked French bacon with pea purée and tomato and chive vinaigrette", 214 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 215 | "category": "dessert", 216 | "price": 12.5 217 | }, 218 | { 219 | "_id": "642c155b2c4774f05c36eea7", 220 | "name": "Fish Parmentier", 221 | "recipe": "Sautéed breaded veal escalope with watercress, lemon and veal jus.", 222 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 223 | "category": "salad", 224 | "price": 9.5 225 | }, 226 | { 227 | "_id": "642c155b2c4774f05c36ee99", 228 | "name": "Goats Cheese Pizza", 229 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 230 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2017/01/bbq-370x247.jpg", 231 | "category": "pizza", 232 | "price": 14.5 233 | }, 234 | { 235 | "_id": "642c155b2c4774f05c36eeb3", 236 | "name": "Fish Parmentier", 237 | "recipe": "Sautéed breaded veal escalope with watercress, lemon and veal jus.", 238 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 239 | "category": "soup", 240 | "price": 9.5 241 | }, 242 | { 243 | "_id": "642c155b2c4774f05c36eeb5", 244 | "name": "Fish Parmentier", 245 | "recipe": "Sautéed breaded veal escalope with watercress, lemon and veal jus.", 246 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 247 | "category": "soup", 248 | "price": 9.5 249 | }, 250 | { 251 | "_id": "642c155b2c4774f05c36eeb6", 252 | "name": "Haddock", 253 | "recipe": "Chargrilled fresh tuna steak (served medium rare) on classic Niçoise salad with French beans.", 254 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 255 | "category": "soup", 256 | "price": 14.7 257 | }, 258 | { 259 | "_id": "642c155b2c4774f05c36eeb8", 260 | "name": "Haddock", 261 | "recipe": "Chargrilled fresh tuna steak (served medium rare) on classic Niçoise salad with French beans.", 262 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 263 | "category": "soup", 264 | "price": 14.7 265 | }, 266 | { 267 | "_id": "642c155b2c4774f05c36ee7e", 268 | "name": "Fish Parmentier", 269 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 270 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-5-370x247.jpg", 271 | "category": "popular", 272 | "price": 14.5 273 | }, 274 | { 275 | "_id": "642c155b2c4774f05c36ee8b", 276 | "name": "Chicken and Walnut Salad", 277 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 278 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 279 | "category": "dessert", 280 | "price": 13.5 281 | }, 282 | { 283 | "_id": "642c155b2c4774f05c36ee8f", 284 | "name": "Chicken and Walnut Salad", 285 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 286 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 287 | "category": "dessert", 288 | "price": 13.5 289 | }, 290 | { 291 | "_id": "642c155b2c4774f05c36ee91", 292 | "name": "Haddock", 293 | "recipe": "Chargrilled fresh tuna steak (served medium rare) on classic Niçoise salad with French beans", 294 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 295 | "category": "dessert", 296 | "price": 14.7 297 | }, 298 | { 299 | "_id": "642c155b2c4774f05c36ee97", 300 | "name": "Haddock", 301 | "recipe": "Chargrilled fresh tuna steak (served medium rare) on classic Niçoise salad with French beans.", 302 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 303 | "category": "pizza", 304 | "price": 14.7 305 | }, 306 | { 307 | "_id": "642c155b2c4774f05c36eea0", 308 | "name": "Roasted Pork Belly", 309 | "recipe": "Chargrilled chicken with avocado, baby gem lettuce, baby spinach, shallots, French beans, walnuts, croutons and a mustard dressing", 310 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 311 | "category": "pizza", 312 | "price": 14.5 313 | }, 314 | { 315 | "_id": "642c155b2c4774f05c36eea9", 316 | "name": "Fish Parmentier", 317 | "recipe": "Sautéed breaded veal escalope with watercress, lemon and veal jus.", 318 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 319 | "category": "salad", 320 | "price": 9.5 321 | }, 322 | { 323 | "_id": "642c155b2c4774f05c36ee85", 324 | "name": "Tuna Niçoise", 325 | "recipe": "Warm goats cheese and roasted vegetable salad with black olive tapenade crostini", 326 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 327 | "category": "dessert", 328 | "price": 10.5 329 | }, 330 | { 331 | "_id": "642c155b2c4774f05c36ee93", 332 | "name": "Tuna Niçoise", 333 | "recipe": "Warm goats cheese and roasted vegetable salad with black olive tapenade crostini", 334 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 335 | "category": "pizza", 336 | "price": 10.5 337 | }, 338 | { 339 | "_id": "642c155b2c4774f05c36ee95", 340 | "name": "Chicken and Walnut Salad", 341 | "recipe": "Pan roasted pork belly with gratin potato, braised Savoy cabbage, apples, thyme and calvados jus", 342 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-3-370x247.jpg", 343 | "category": "pizza", 344 | "price": 13.5 345 | }, 346 | { 347 | "_id": "642c155b2c4774f05c36ee9b", 348 | "name": "Goats Cheese Pizza", 349 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 350 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2017/01/bbq-370x247.jpg", 351 | "category": "pizza", 352 | "price": 14.5 353 | }, 354 | { 355 | "_id": "642c155b2c4774f05c36eeb7", 356 | "name": "Fish Parmentier", 357 | "recipe": "Sautéed breaded veal escalope with watercress, lemon and veal jus.", 358 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 359 | "category": "soup", 360 | "price": 9.5 361 | }, 362 | { 363 | "_id": "642c155b2c4774f05c36ee9c", 364 | "name": "Breton Fish Stew", 365 | "recipe": "Chargrilled chicken with avocado, baby gem lettuce, baby spinach, shallots, French beans, walnuts, croutons and a mustard dressing", 366 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 367 | "category": "pizza", 368 | "price": 12.9 369 | }, 370 | { 371 | "_id": "642c155b2c4774f05c36ee9d", 372 | "name": "Goats Cheese Pizza", 373 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 374 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2017/01/bbq-370x247.jpg", 375 | "category": "pizza", 376 | "price": 14.5 377 | }, 378 | { 379 | "_id": "642c155b2c4774f05c36eeac", 380 | "name": "Roasted Pork Belly", 381 | "recipe": "Chargrilled chicken with avocado, baby gem lettuce, baby spinach, shallots, French beans, walnuts, croutons and a mustard dressing", 382 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 383 | "category": "salad", 384 | "price": 14.5 385 | }, 386 | { 387 | "_id": "642c155b2c4774f05c36eeb4", 388 | "name": "Haddock", 389 | "recipe": "Chargrilled fresh tuna steak (served medium rare) on classic Niçoise salad with French beans.", 390 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-1-370x247.jpg", 391 | "category": "soup", 392 | "price": 14.7 393 | }, 394 | { 395 | "_id": "642c155b2c4774f05c36ee98", 396 | "name": "Breton Fish Stew", 397 | "recipe": "Chargrilled chicken with avocado, baby gem lettuce, baby spinach, shallots, French beans, walnuts, croutons and a mustard dressing", 398 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 399 | "category": "pizza", 400 | "price": 12.9 401 | }, 402 | { 403 | "_id": "642c155b2c4774f05c36ee82", 404 | "name": "Escalope de Veau", 405 | "recipe": "Pan roasted haddock fillet wrapped in smoked French bacon with pea purée and tomato and chive vinaigrette", 406 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 407 | "category": "offered", 408 | "price": 12.5 409 | }, 410 | { 411 | "_id": "642c155b2c4774f05c36ee86", 412 | "name": "Escalope de Veau", 413 | "recipe": "Pan roasted haddock fillet wrapped in smoked French bacon with pea purée and tomato and chive vinaigrette", 414 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 415 | "category": "dessert", 416 | "price": 12.5 417 | }, 418 | { 419 | "_id": "642c155b2c4774f05c36ee9a", 420 | "name": "Breton Fish Stew", 421 | "recipe": "Chargrilled chicken with avocado, baby gem lettuce, baby spinach, shallots, French beans, walnuts, croutons and a mustard dressing", 422 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 423 | "category": "pizza", 424 | "price": 12.9 425 | }, 426 | { 427 | "_id": "642c155b2c4774f05c36eeab", 428 | "name": "Goats Cheese Pizza", 429 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 430 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2017/01/bbq-370x247.jpg", 431 | "category": "salad", 432 | "price": 14.5 433 | }, 434 | { 435 | "_id": "642c155b2c4774f05c36eeb1", 436 | "name": "Fish Parmentier", 437 | "recipe": "Sautéed breaded veal escalope with watercress, lemon and veal jus.", 438 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-8-370x247.jpg", 439 | "category": "soup", 440 | "price": 9.5 441 | }, 442 | { 443 | "_id": "642c155b2c4774f05c36eead", 444 | "name": "Roast Duck Breast", 445 | "recipe": "Roasted duck breast (served pink) with gratin potato and a griottine cherry sauce", 446 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-4-370x247.jpg", 447 | "category": "soup", 448 | "price": 14.5 449 | }, 450 | { 451 | "_id": "642c155b2c4774f05c36eeae", 452 | "name": "Tuna Niçoise", 453 | "recipe": "Warm goats cheese and roasted vegetable salad with black olive tapenade crostini", 454 | "image": "https://cristianonew.ukrdevs.com/wp-content/uploads/2016/08/product-2-370x247.jpg", 455 | "category": "soup", 456 | "price": 10.5 457 | } 458 | ] -------------------------------------------------------------------------------- /public/reviews.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "643010e0f5a7e52ce1e8fa65", 4 | "name": "Jane Doe", 5 | "details": "Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.", 6 | "rating": 3 7 | }, 8 | { 9 | "_id": "643010f9f5a7e52ce1e8fa66", 10 | "name": "John Doe", 11 | "details": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like)", 12 | "rating": 2 13 | }, 14 | { 15 | "_id": "6430110af5a7e52ce1e8fa67", 16 | "name": "MJ Doe", 17 | "details": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like)", 18 | "rating": 5 19 | }, 20 | { 21 | "_id": "64301123f5a7e52ce1e8fa68", 22 | "name": "Sarah Smith", 23 | "details": "I found the product to be incredibly useful and easy to use. The interface is intuitive, and it has all the features I need. Highly recommend it!", 24 | "rating": 4 25 | }, 26 | { 27 | "_id": "6430113af5a7e52ce1e8fa69", 28 | "name": "Robert Johnson", 29 | "details": "This is by far the best service I have ever used. The customer support is outstanding, and the product itself is top-notch. I couldn't be happier!", 30 | "rating": 5 31 | } 32 | ] -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/App.css -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | 3 | function App() { 4 | 5 | return ( 6 | <> 7 | 8 | 9 | ) 10 | } 11 | 12 | export default App 13 | -------------------------------------------------------------------------------- /src/Layout/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import { FaAd, FaBook, FaCalendar, FaEnvelope, FaHome, FaList, FaSearch, FaShoppingCart, FaUsers, FaUtensils } from "react-icons/fa"; 2 | import { NavLink, Outlet } from "react-router-dom"; 3 | import useCart from "../hooks/useCart"; 4 | import useAdmin from "../hooks/useAdmin"; 5 | 6 | 7 | const Dashboard = () => { 8 | const [cart] = useCart(); 9 | 10 | // TODO: get isAdmin value from the database 11 | const [isAdmin] = useAdmin(); 12 | 13 | return ( 14 |
15 | {/* dashboard side bar */} 16 |
17 | 93 |
94 | {/* dashboard content */} 95 |
96 | 97 |
98 |
99 | ); 100 | }; 101 | 102 | export default Dashboard; -------------------------------------------------------------------------------- /src/Layout/Main.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet, useLocation } from "react-router-dom"; 2 | import Footer from "../pages/Shared/Footer/Footer"; 3 | import NavBar from "../pages/Shared/NavBar/NavBar"; 4 | 5 | 6 | const Main = () => { 7 | const location = useLocation(); 8 | 9 | const noHeaderFooter = location.pathname.includes('login') || location.pathname.includes('signup'); 10 | 11 | return ( 12 |
13 | { noHeaderFooter || } 14 | 15 | { noHeaderFooter || } 16 |
17 | ); 18 | }; 19 | 20 | export default Main; -------------------------------------------------------------------------------- /src/Routes/AdminRoute.jsx: -------------------------------------------------------------------------------- 1 | import { Navigate, useLocation } from "react-router-dom"; 2 | import useAuth from "../hooks/useAuth"; 3 | import useAdmin from "../hooks/useAdmin"; 4 | 5 | 6 | const AdminRoute = ({ children }) => { 7 | const { user, loading } = useAuth(); 8 | const [isAdmin, isAdminLoading] = useAdmin(); 9 | const location = useLocation(); 10 | 11 | if (loading || isAdminLoading) { 12 | return 13 | } 14 | 15 | if (user && isAdmin) { 16 | return children; 17 | } 18 | 19 | return 20 | 21 | }; 22 | 23 | export default AdminRoute; -------------------------------------------------------------------------------- /src/Routes/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import { Navigate, useLocation } from "react-router"; 2 | import useAuth from "../hooks/useAuth"; 3 | 4 | 5 | const PrivateRoute = ({ children }) => { 6 | const { user, loading } = useAuth(); 7 | const location = useLocation(); 8 | 9 | if(loading){ 10 | return 11 | } 12 | 13 | if (user) { 14 | return children; 15 | } 16 | return 17 | }; 18 | 19 | export default PrivateRoute; -------------------------------------------------------------------------------- /src/Routes/Routes.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | createBrowserRouter, 3 | } from "react-router-dom"; 4 | import Main from "../Layout/Main"; 5 | import Home from "../pages/Home/Home/Home"; 6 | import Menu from "../pages/Menu/Menu/Menu"; 7 | import Order from "../pages/Order/Order/Order"; 8 | import Login from "../pages/Login/Login"; 9 | import SignUp from "../pages/SignUp/SignUp"; 10 | import PrivateRoute from "./PrivateRoute"; 11 | import Secret from "../pages/Shared/Secret/Secret"; 12 | import Dashboard from "../Layout/Dashboard"; 13 | import Cart from "../pages/Dashboard/Cart/Cart"; 14 | import AllUsers from "../pages/Dashboard/AllUsers/AllUsers"; 15 | import AddItems from "../pages/Dashboard/AddItems/AddItems"; 16 | import AdminRoute from "./AdminRoute"; 17 | import ManageItems from "../pages/Dashboard/ManageItems/ManageItems"; 18 | import UpdateItem from "../pages/Dashboard/UpdateItem/UpdateItem"; 19 | 20 | 21 | export const router = createBrowserRouter([ 22 | { 23 | path: "/", 24 | element:
, 25 | children: [ 26 | { 27 | path: '/', 28 | element: 29 | }, 30 | { 31 | path: 'menu', 32 | element: 33 | }, 34 | { 35 | path: 'order/:category', 36 | element: 37 | }, 38 | { 39 | path: 'login', 40 | element: 41 | }, 42 | { 43 | path: 'signup', 44 | element: 45 | }, 46 | { 47 | path: 'secret', 48 | element: 49 | } 50 | ] 51 | }, 52 | { 53 | path: 'dashboard', 54 | element: , 55 | children: [ 56 | // normal user routes 57 | { 58 | path: 'cart', 59 | element: 60 | }, 61 | 62 | // admin only routes 63 | { 64 | path: 'addItems', 65 | element: 66 | }, 67 | { 68 | path: 'manageItems', 69 | element: 70 | }, 71 | { 72 | path: 'updateItem/:id', 73 | element: , 74 | loader: ({params}) => fetch(`http://localhost:5000/menu/${params.id}`) 75 | }, 76 | { 77 | path: 'users', 78 | element: 79 | } 80 | 81 | ] 82 | } 83 | ]); -------------------------------------------------------------------------------- /src/assets/404.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/404.gif -------------------------------------------------------------------------------- /src/assets/contact/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/contact/banner.jpg -------------------------------------------------------------------------------- /src/assets/contact/emo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/contact/emo.png -------------------------------------------------------------------------------- /src/assets/dashboard/image-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/dashboard/image-5.jpg -------------------------------------------------------------------------------- /src/assets/home/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/01.jpg -------------------------------------------------------------------------------- /src/assets/home/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/02.jpg -------------------------------------------------------------------------------- /src/assets/home/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/03.png -------------------------------------------------------------------------------- /src/assets/home/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/04.jpg -------------------------------------------------------------------------------- /src/assets/home/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/05.png -------------------------------------------------------------------------------- /src/assets/home/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/06.png -------------------------------------------------------------------------------- /src/assets/home/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/banner.jpg -------------------------------------------------------------------------------- /src/assets/home/chef-special.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/chef-special.jpg -------------------------------------------------------------------------------- /src/assets/home/featured.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/featured.jpg -------------------------------------------------------------------------------- /src/assets/home/slide1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/slide1.jpg -------------------------------------------------------------------------------- /src/assets/home/slide2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/slide2.jpg -------------------------------------------------------------------------------- /src/assets/home/slide3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/slide3.jpg -------------------------------------------------------------------------------- /src/assets/home/slide4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/slide4.jpg -------------------------------------------------------------------------------- /src/assets/home/slide5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/home/slide5.jpg -------------------------------------------------------------------------------- /src/assets/icon/151-1511569_cart-notifications-free-shopping-cart-favicon-hd-png-removebg-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/icon/151-1511569_cart-notifications-free-shopping-cart-favicon-hd-png-removebg-preview.png -------------------------------------------------------------------------------- /src/assets/icon/correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/icon/correct.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/menu/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/menu/banner.png -------------------------------------------------------------------------------- /src/assets/menu/dessert-bg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/menu/dessert-bg.jpeg -------------------------------------------------------------------------------- /src/assets/menu/menu-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/menu/menu-bg.jpg -------------------------------------------------------------------------------- /src/assets/menu/pizza-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/menu/pizza-bg.jpg -------------------------------------------------------------------------------- /src/assets/menu/salad-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/menu/salad-bg.jpg -------------------------------------------------------------------------------- /src/assets/menu/soup-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/menu/soup-bg.jpg -------------------------------------------------------------------------------- /src/assets/others/Illustration.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 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/assets/others/authentication.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/authentication.gif -------------------------------------------------------------------------------- /src/assets/others/authentication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/authentication.png -------------------------------------------------------------------------------- /src/assets/others/authentication1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/authentication1.png -------------------------------------------------------------------------------- /src/assets/others/authentication2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/authentication2.png -------------------------------------------------------------------------------- /src/assets/others/cupcake-dribbble.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/cupcake-dribbble.gif -------------------------------------------------------------------------------- /src/assets/others/cupcake.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/cupcake.gif -------------------------------------------------------------------------------- /src/assets/others/download (1).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/download (1).jpg -------------------------------------------------------------------------------- /src/assets/others/loader2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/loader2.gif -------------------------------------------------------------------------------- /src/assets/others/loader3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/loader3.gif -------------------------------------------------------------------------------- /src/assets/others/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/others/profile.png -------------------------------------------------------------------------------- /src/assets/reservation/category-pizza.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/reservation/category-pizza.jpg -------------------------------------------------------------------------------- /src/assets/reservation/wood-grain-pattern-gray1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/reservation/wood-grain-pattern-gray1x.png -------------------------------------------------------------------------------- /src/assets/shop/order.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgrammingHero1/bistro-boss-client-with-image-upload-part_6/9285d836741cce716342e38ba24ea213a385ceef/src/assets/shop/order.jpg -------------------------------------------------------------------------------- /src/components/FoodCard/FoodCard.jsx: -------------------------------------------------------------------------------- 1 | import Swal from "sweetalert2"; 2 | import useAuth from "../../hooks/useAuth"; 3 | import { useLocation, useNavigate } from "react-router-dom"; 4 | import useAxiosSecure from "../../hooks/useAxiosSecure"; 5 | import useCart from "../../hooks/useCart"; 6 | 7 | 8 | const FoodCard = ({ item }) => { 9 | const { name, image, price, recipe, _id } = item; 10 | const { user } = useAuth(); 11 | const navigate = useNavigate(); 12 | const location = useLocation(); 13 | const axiosSecure = useAxiosSecure(); 14 | const [, refetch] = useCart(); 15 | 16 | const handleAddToCart = () => { 17 | if (user && user.email) { 18 | //send cart item to the database 19 | const cartItem = { 20 | menuId: _id, 21 | email: user.email, 22 | name, 23 | image, 24 | price 25 | } 26 | axiosSecure.post('/carts', cartItem) 27 | .then(res => { 28 | console.log(res.data) 29 | if (res.data.insertedId) { 30 | Swal.fire({ 31 | position: "top-end", 32 | icon: "success", 33 | title: `${name} added to your cart`, 34 | showConfirmButton: false, 35 | timer: 1500 36 | }); 37 | // refetch cart to update the cart items count 38 | refetch(); 39 | } 40 | 41 | }) 42 | } 43 | else { 44 | Swal.fire({ 45 | title: "You are not Logged In", 46 | text: "Please login to add to the cart?", 47 | icon: "warning", 48 | showCancelButton: true, 49 | confirmButtonColor: "#3085d6", 50 | cancelButtonColor: "#d33", 51 | confirmButtonText: "Yes, login!" 52 | }).then((result) => { 53 | if (result.isConfirmed) { 54 | // send the user to the login page 55 | navigate('/login', { state: { from: location } }) 56 | } 57 | }); 58 | } 59 | } 60 | return ( 61 |
62 |
Shoes
63 |

${price}

64 |
65 |

{name}

66 |

{recipe}

67 |
68 | 72 |
73 |
74 |
75 | ); 76 | }; 77 | 78 | export default FoodCard; -------------------------------------------------------------------------------- /src/components/SectionTitle/SectionTitle.jsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | const SectionTitle = ({heading, subHeading}) => { 4 | return ( 5 |
6 |

--- {subHeading} ---

7 |

{heading}

8 |
9 | ); 10 | }; 11 | 12 | export default SectionTitle; -------------------------------------------------------------------------------- /src/components/SocialLogin/SocialLogin.jsx: -------------------------------------------------------------------------------- 1 | import { FaGoogle } from "react-icons/fa"; 2 | import useAuth from "../../hooks/useAuth"; 3 | import useAxiosPublic from "../../hooks/useAxiosPublic"; 4 | import { useNavigate } from "react-router-dom"; 5 | 6 | 7 | const SocialLogin = () => { 8 | const { googleSignIn } = useAuth(); 9 | const axiosPublic = useAxiosPublic(); 10 | const navigate = useNavigate(); 11 | 12 | const handleGoogleSignIn = () =>{ 13 | googleSignIn() 14 | .then(result =>{ 15 | console.log(result.user); 16 | const userInfo = { 17 | email: result.user?.email, 18 | name: result.user?.displayName 19 | } 20 | axiosPublic.post('/users', userInfo) 21 | .then(res =>{ 22 | console.log(res.data); 23 | navigate('/'); 24 | }) 25 | }) 26 | } 27 | 28 | return ( 29 |
30 |
31 |
32 | 36 |
37 |
38 | ); 39 | }; 40 | 41 | export default SocialLogin; -------------------------------------------------------------------------------- /src/firebase/firebase.config.js: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp } from "firebase/app"; 3 | // TODO: Add SDKs for Firebase products that you want to use 4 | // https://firebase.google.com/docs/web/setup#available-libraries 5 | 6 | // Your web app's Firebase configuration 7 | const firebaseConfig = { 8 | apiKey: import.meta.env.VITE_apiKey, 9 | authDomain: import.meta.env.VITE_authDomain, 10 | projectId: import.meta.env.VITE_projectId, 11 | storageBucket: import.meta.env.VITE_storageBucket, 12 | messagingSenderId: import.meta.env.VITE_messagingSenderId, 13 | appId: import.meta.env.VITE_appId 14 | }; 15 | 16 | // Initialize Firebase 17 | export const app = initializeApp(firebaseConfig); -------------------------------------------------------------------------------- /src/hooks/useAdmin.jsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import useAuth from "./useAuth"; 3 | import useAxiosSecure from "./useAxiosSecure"; 4 | 5 | 6 | const useAdmin = () => { 7 | const { user } = useAuth(); 8 | const axiosSecure = useAxiosSecure(); 9 | const { data: isAdmin, isPending: isAdminLoading } = useQuery({ 10 | queryKey: [user?.email, 'isAdmin'], 11 | queryFn: async () => { 12 | const res = await axiosSecure.get(`/users/admin/${user.email}`); 13 | // console.log(res.data); 14 | return res.data?.admin; 15 | } 16 | }) 17 | return [isAdmin, isAdminLoading] 18 | }; 19 | 20 | export default useAdmin; -------------------------------------------------------------------------------- /src/hooks/useAuth.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { AuthContext } from "../providers/AuthProvider"; 3 | 4 | 5 | const useAuth = () => { 6 | const auth = useContext(AuthContext); 7 | return auth; 8 | }; 9 | 10 | export default useAuth; -------------------------------------------------------------------------------- /src/hooks/useAxiosPublic.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const axiosPublic = axios.create({ 4 | baseURL: 'http://localhost:5000' 5 | }) 6 | 7 | const useAxiosPublic = () => { 8 | return axiosPublic; 9 | }; 10 | 11 | export default useAxiosPublic; -------------------------------------------------------------------------------- /src/hooks/useAxiosSecure.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useNavigate } from "react-router-dom"; 3 | import useAuth from "./useAuth"; 4 | 5 | const axiosSecure = axios.create({ 6 | baseURL: 'http://localhost:5000' 7 | }) 8 | const useAxiosSecure = () => { 9 | const navigate = useNavigate(); 10 | const { logOut } = useAuth(); 11 | 12 | // request interceptor to add authorization header for every secure call to teh api 13 | axiosSecure.interceptors.request.use(function (config) { 14 | const token = localStorage.getItem('access-token') 15 | // console.log('request stopped by interceptors', token) 16 | config.headers.authorization = `Bearer ${token}`; 17 | return config; 18 | }, function (error) { 19 | // Do something with request error 20 | return Promise.reject(error); 21 | }); 22 | 23 | 24 | // intercepts 401 and 403 status 25 | axiosSecure.interceptors.response.use(function (response) { 26 | return response; 27 | }, async (error) => { 28 | const status = error.response.status; 29 | // console.log('status error in the interceptor', status); 30 | // for 401 or 403 logout the user and move the user to the login 31 | if (status === 401 || status === 403) { 32 | await logOut(); 33 | navigate('/login'); 34 | } 35 | return Promise.reject(error); 36 | }) 37 | 38 | 39 | return axiosSecure; 40 | }; 41 | 42 | export default useAxiosSecure; -------------------------------------------------------------------------------- /src/hooks/useCart.jsx: -------------------------------------------------------------------------------- 1 | // api, axios (axios secure), tan stack 2 | 3 | import { useQuery } from "@tanstack/react-query"; 4 | import useAxiosSecure from "./useAxiosSecure"; 5 | import useAuth from "./useAuth"; 6 | 7 | const useCart = () => { 8 | const axiosSecure = useAxiosSecure(); 9 | const { user} = useAuth(); 10 | const { refetch, data: cart = [] } = useQuery({ 11 | queryKey: ['cart', user?.email], 12 | queryFn: async() => { 13 | const res = await axiosSecure.get(`/carts?email=${user.email}`); 14 | return res.data; 15 | } 16 | }) 17 | 18 | return [cart, refetch] 19 | }; 20 | 21 | export default useCart; -------------------------------------------------------------------------------- /src/hooks/useMenu.jsx: -------------------------------------------------------------------------------- 1 | // import { useEffect, useState } from "react"; 2 | 3 | import { useQuery } from "@tanstack/react-query"; 4 | import useAxiosPublic from "./useAxiosPublic"; 5 | 6 | const useMenu = () => { 7 | const axiosPublic = useAxiosPublic(); 8 | // const [menu, setMenu] = useState([]); 9 | // const [loading, setLoading] = useState(true); 10 | // useEffect(() => { 11 | // fetch('http://localhost:5000/menu') 12 | // .then(res => res.json()) 13 | // .then(data => { 14 | // setMenu(data); 15 | // setLoading(false); 16 | // }); 17 | // }, []) 18 | 19 | const {data: menu = [], isPending: loading, refetch} = useQuery({ 20 | queryKey: ['menu'], 21 | queryFn: async() =>{ 22 | const res = await axiosPublic.get('/menu'); 23 | return res.data; 24 | } 25 | }) 26 | 27 | 28 | return [menu, loading, refetch] 29 | } 30 | 31 | export default useMenu; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import './index.css' 4 | import { 5 | RouterProvider, 6 | } from "react-router-dom"; 7 | import { router } from './Routes/Routes'; 8 | import { HelmetProvider } from 'react-helmet-async'; 9 | import AuthProvider from './providers/AuthProvider'; 10 | import { 11 | QueryClient, 12 | QueryClientProvider, 13 | } from '@tanstack/react-query' 14 | 15 | const queryClient = new QueryClient(); 16 | 17 | ReactDOM.createRoot(document.getElementById('root')).render( 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 |
26 |
27 |
28 |
, 29 | ) 30 | -------------------------------------------------------------------------------- /src/pages/Dashboard/AddItems/AddItems.jsx: -------------------------------------------------------------------------------- 1 | import { useForm } from "react-hook-form"; 2 | import SectionTitle from "../../../components/SectionTitle/SectionTitle"; 3 | import { FaUtensils } from "react-icons/fa"; 4 | import useAxiosPublic from "../../../hooks/useAxiosPublic"; 5 | import useAxiosSecure from "../../../hooks/useAxiosSecure"; 6 | import Swal from "sweetalert2"; 7 | 8 | const image_hosting_key = import.meta.env.VITE_IMAGE_HOSTING_KEY; 9 | const image_hosting_api = `https://api.imgbb.com/1/upload?key=${image_hosting_key}`; 10 | 11 | const AddItems = () => { 12 | const { register, handleSubmit, reset } = useForm(); 13 | const axiosPublic = useAxiosPublic(); 14 | const axiosSecure = useAxiosSecure(); 15 | const onSubmit = async (data) => { 16 | console.log(data) 17 | // image upload to imgbb and then get an url 18 | const imageFile = { image: data.image[0] } 19 | const res = await axiosPublic.post(image_hosting_api, imageFile, { 20 | headers: { 21 | 'content-type': 'multipart/form-data' 22 | } 23 | }); 24 | if (res.data.success) { 25 | // now send the menu item data to the server with the image url 26 | const menuItem = { 27 | name: data.name, 28 | category: data.category, 29 | price: parseFloat(data.price), 30 | recipe: data.recipe, 31 | image: res.data.data.display_url 32 | } 33 | // 34 | const menuRes = await axiosSecure.post('/menu', menuItem); 35 | console.log(menuRes.data) 36 | if(menuRes.data.insertedId){ 37 | // show success popup 38 | reset(); 39 | Swal.fire({ 40 | position: "top-end", 41 | icon: "success", 42 | title: `${data.name} is added to the menu.`, 43 | showConfirmButton: false, 44 | timer: 1500 45 | }); 46 | } 47 | } 48 | console.log( 'with image url', res.data); 49 | }; 50 | 51 | return ( 52 |
53 | 54 |
55 |
56 |
57 | 60 | 66 |
67 |
68 | {/* category */} 69 |
70 | 73 | 82 |
83 | 84 | {/* price */} 85 |
86 | 89 | 94 |
95 | 96 |
97 | {/* recipe details */} 98 |
99 | 102 | 103 |
104 | 105 |
106 | 107 |
108 | 109 | 112 |
113 |
114 |
115 | ); 116 | }; 117 | 118 | export default AddItems; -------------------------------------------------------------------------------- /src/pages/Dashboard/AllUsers/AllUsers.jsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import useAxiosSecure from "../../../hooks/useAxiosSecure"; 3 | import { FaTrashAlt, FaUsers } from "react-icons/fa"; 4 | import Swal from "sweetalert2"; 5 | 6 | 7 | const AllUsers = () => { 8 | const axiosSecure = useAxiosSecure(); 9 | const { data: users = [], refetch } = useQuery({ 10 | queryKey: ['users'], 11 | queryFn: async () => { 12 | const res = await axiosSecure.get('/users'); 13 | return res.data; 14 | } 15 | }) 16 | 17 | const handleMakeAdmin = user =>{ 18 | axiosSecure.patch(`/users/admin/${user._id}`) 19 | .then(res =>{ 20 | console.log(res.data) 21 | if(res.data.modifiedCount > 0){ 22 | refetch(); 23 | Swal.fire({ 24 | position: "top-end", 25 | icon: "success", 26 | title: `${user.name} is an Admin Now!`, 27 | showConfirmButton: false, 28 | timer: 1500 29 | }); 30 | } 31 | }) 32 | } 33 | 34 | const handleDeleteUser = user => { 35 | Swal.fire({ 36 | title: "Are you sure?", 37 | text: "You won't be able to revert this!", 38 | icon: "warning", 39 | showCancelButton: true, 40 | confirmButtonColor: "#3085d6", 41 | cancelButtonColor: "#d33", 42 | confirmButtonText: "Yes, delete it!" 43 | }).then((result) => { 44 | if (result.isConfirmed) { 45 | 46 | axiosSecure.delete(`/users/${user._id}`) 47 | .then(res => { 48 | if (res.data.deletedCount > 0) { 49 | refetch(); 50 | Swal.fire({ 51 | title: "Deleted!", 52 | text: "Your file has been deleted.", 53 | icon: "success" 54 | }); 55 | } 56 | }) 57 | } 58 | }); 59 | } 60 | 61 | return ( 62 |
63 |
64 |

All Users

65 |

Total Users: {users.length}

66 |
67 |
68 | 69 | {/* head */} 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | { 81 | users.map((user, index) => 82 | 83 | 84 | 85 | 93 | 100 | ) 101 | } 102 | 103 | 104 |
NameEmailRoleAction
{index + 1}{user.name}{user.email} 86 | { user.role === 'admin' ? 'Admin' : } 92 | 94 | 99 |
105 |
106 |
107 | ); 108 | }; 109 | 110 | export default AllUsers; -------------------------------------------------------------------------------- /src/pages/Dashboard/Cart/Cart.jsx: -------------------------------------------------------------------------------- 1 | import { FaTrashAlt } from "react-icons/fa"; 2 | import useCart from "../../../hooks/useCart"; 3 | import Swal from "sweetalert2"; 4 | import useAxiosSecure from "../../../hooks/useAxiosSecure"; 5 | 6 | 7 | const Cart = () => { 8 | const [cart, refetch] = useCart(); 9 | const totalPrice = cart.reduce((total, item) => total + item.price, 0); 10 | const axiosSecure = useAxiosSecure(); 11 | 12 | const handleDelete = id => { 13 | Swal.fire({ 14 | title: "Are you sure?", 15 | text: "You won't be able to revert this!", 16 | icon: "warning", 17 | showCancelButton: true, 18 | confirmButtonColor: "#3085d6", 19 | cancelButtonColor: "#d33", 20 | confirmButtonText: "Yes, delete it!" 21 | }).then((result) => { 22 | if (result.isConfirmed) { 23 | 24 | axiosSecure.delete(`/carts/${id}`) 25 | .then(res => { 26 | if (res.data.deletedCount > 0) { 27 | refetch(); 28 | Swal.fire({ 29 | title: "Deleted!", 30 | text: "Your file has been deleted.", 31 | icon: "success" 32 | }); 33 | } 34 | }) 35 | } 36 | }); 37 | } 38 | 39 | return ( 40 |
41 |
42 |

Items: {cart.length}

43 |

Total Price: {totalPrice}

44 | 45 | 46 |
47 |
48 | 49 | {/* head */} 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | { 63 | cart.map((item, index) => 64 | 67 | 76 | 79 | 80 | 87 | ) 88 | } 89 | 90 | 91 | 92 |
53 | # 54 | ImageNamePriceAction
65 | {index + 1} 66 | 68 |
69 |
70 |
71 | Avatar Tailwind CSS Component 72 |
73 |
74 |
75 |
77 | {item.name} 78 | ${item.price} 81 | 86 |
93 |
94 |
95 | ); 96 | }; 97 | 98 | export default Cart; -------------------------------------------------------------------------------- /src/pages/Dashboard/ManageItems/ManageItems.jsx: -------------------------------------------------------------------------------- 1 | import { FaEdit, FaTrashAlt } from "react-icons/fa"; 2 | import SectionTitle from "../../../components/SectionTitle/SectionTitle"; 3 | import useMenu from "../../../hooks/useMenu"; 4 | import Swal from "sweetalert2"; 5 | import useAxiosSecure from "../../../hooks/useAxiosSecure"; 6 | import { Link } from "react-router-dom"; 7 | 8 | 9 | const ManageItems = () => { 10 | const [menu, , refetch] = useMenu(); 11 | const axiosSecure = useAxiosSecure(); 12 | 13 | const handleDeleteItem = (item) => { 14 | Swal.fire({ 15 | title: "Are you sure?", 16 | text: "You won't be able to revert this!", 17 | icon: "warning", 18 | showCancelButton: true, 19 | confirmButtonColor: "#3085d6", 20 | cancelButtonColor: "#d33", 21 | confirmButtonText: "Yes, delete it!" 22 | }).then(async (result) => { 23 | if (result.isConfirmed) { 24 | const res = await axiosSecure.delete(`/menu/${item._id}`); 25 | // console.log(res.data); 26 | if (res.data.deletedCount > 0) { 27 | // refetch to update the ui 28 | refetch(); 29 | Swal.fire({ 30 | position: "top-end", 31 | icon: "success", 32 | title: `${item.name} has been deleted`, 33 | showConfirmButton: false, 34 | timer: 1500 35 | }); 36 | } 37 | 38 | 39 | } 40 | }); 41 | } 42 | 43 | return ( 44 |
45 | 46 |
47 |
48 | 49 | {/* head */} 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | { 64 | menu.map((item, index) => 65 | 68 | 77 | 80 | 81 | 90 | 97 | ) 98 | } 99 | 100 | 101 | 102 |
53 | # 54 | ImageItem NamePriceUpdateDelete
66 | {index + 1} 67 | 69 |
70 |
71 |
72 | Avatar Tailwind CSS Component 73 |
74 |
75 |
76 |
78 | {item.name} 79 | ${item.price} 82 | 83 | 88 | 89 | 91 | 96 |
103 |
104 |
105 |
106 | ); 107 | }; 108 | 109 | export default ManageItems; -------------------------------------------------------------------------------- /src/pages/Dashboard/UpdateItem/UpdateItem.jsx: -------------------------------------------------------------------------------- 1 | import { useLoaderData } from "react-router-dom"; 2 | import SectionTitle from "../../../components/SectionTitle/SectionTitle"; 3 | import { useForm } from "react-hook-form"; 4 | import Swal from "sweetalert2"; 5 | import useAxiosPublic from "../../../hooks/useAxiosPublic"; 6 | import useAxiosSecure from "../../../hooks/useAxiosSecure"; 7 | 8 | const image_hosting_key = import.meta.env.VITE_IMAGE_HOSTING_KEY; 9 | const image_hosting_api = `https://api.imgbb.com/1/upload?key=${image_hosting_key}`; 10 | 11 | const UpdateItem = () => { 12 | const {name, category, recipe, price, _id} = useLoaderData(); 13 | 14 | const { register, handleSubmit } = useForm(); 15 | const axiosPublic = useAxiosPublic(); 16 | const axiosSecure = useAxiosSecure(); 17 | const onSubmit = async (data) => { 18 | console.log(data) 19 | // image upload to imgbb and then get an url 20 | const imageFile = { image: data.image[0] } 21 | const res = await axiosPublic.post(image_hosting_api, imageFile, { 22 | headers: { 23 | 'content-type': 'multipart/form-data' 24 | } 25 | }); 26 | if (res.data.success) { 27 | // now send the menu item data to the server with the image url 28 | const menuItem = { 29 | name: data.name, 30 | category: data.category, 31 | price: parseFloat(data.price), 32 | recipe: data.recipe, 33 | image: res.data.data.display_url 34 | } 35 | // 36 | const menuRes = await axiosSecure.patch(`/menu/${_id}`, menuItem); 37 | console.log(menuRes.data) 38 | if(menuRes.data.modifiedCount > 0){ 39 | // show success popup 40 | // reset(); 41 | Swal.fire({ 42 | position: "top-end", 43 | icon: "success", 44 | title: `${data.name} is updated to the menu.`, 45 | showConfirmButton: false, 46 | timer: 1500 47 | }); 48 | } 49 | } 50 | console.log( 'with image url', res.data); 51 | }; 52 | 53 | 54 | return ( 55 |
56 | 57 |
58 |
59 |
60 | 63 | 70 |
71 |
72 | {/* category */} 73 |
74 | 77 | 86 |
87 | 88 | {/* price */} 89 |
90 | 93 | 99 |
100 | 101 |
102 | {/* recipe details */} 103 |
104 | 107 | 108 |
109 | 110 |
111 | 112 |
113 | 114 | 117 |
118 |
119 |
120 | ); 121 | }; 122 | 123 | export default UpdateItem; -------------------------------------------------------------------------------- /src/pages/Dashboard/__Page__level_security__.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 1. do not show the link to those user who should not see the link 3 | * 2. even if they gets the link, do not allow them to visit the link 4 | * 3. do not allow user to access the api. check admin in the server as well 5 | * (verifyAdmin) 6 | */ -------------------------------------------------------------------------------- /src/pages/Home/Banner/Banner.jsx: -------------------------------------------------------------------------------- 1 | import { Carousel } from 'react-responsive-carousel'; 2 | import "react-responsive-carousel/lib/styles/carousel.min.css"; 3 | 4 | import img1 from '../../../assets/home/01.jpg'; 5 | import img2 from '../../../assets/home/02.jpg'; 6 | import img3 from '../../../assets/home/03.png'; 7 | import img4 from '../../../assets/home/04.jpg'; 8 | import img5 from '../../../assets/home/05.png'; 9 | import img6 from '../../../assets/home/06.png'; 10 | 11 | const Banner = () => { 12 | return ( 13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 | ); 34 | }; 35 | 36 | export default Banner; -------------------------------------------------------------------------------- /src/pages/Home/Category/Category.jsx: -------------------------------------------------------------------------------- 1 | import { Swiper, SwiperSlide } from "swiper/react"; 2 | import { Pagination } from "swiper"; 3 | 4 | import "swiper/css"; 5 | import "swiper/css/pagination"; 6 | 7 | import slide1 from '../../../assets/home/slide1.jpg'; 8 | import slide2 from '../../../assets/home/slide2.jpg'; 9 | import slide3 from '../../../assets/home/slide3.jpg'; 10 | import slide4 from '../../../assets/home/slide4.jpg'; 11 | import slide5 from '../../../assets/home/slide5.jpg'; 12 | import SectionTitle from "../../../components/SectionTitle/SectionTitle"; 13 | 14 | const Category = () => { 15 | return ( 16 |
17 | 21 | 31 | 32 | 33 |

Salads

34 |
35 | 36 | 37 |

Pizzas

38 |
39 | 40 | 41 |

Soups

42 |
43 | 44 | 45 |

Desserts

46 |
47 | 48 | 49 |

Salads

50 |
51 |
52 |
53 | ); 54 | }; 55 | 56 | export default Category; -------------------------------------------------------------------------------- /src/pages/Home/Featured/Featured.css: -------------------------------------------------------------------------------- 1 | .featured-item{ 2 | background-image: url('../../../assets/home/featured.jpg'); 3 | } -------------------------------------------------------------------------------- /src/pages/Home/Featured/Featured.jsx: -------------------------------------------------------------------------------- 1 | import SectionTitle from "../../../components/SectionTitle/SectionTitle"; 2 | import featuredImg from '../../../assets/home/featured.jpg'; 3 | import './Featured.css'; 4 | 5 | 6 | const Featured = () => { 7 | return ( 8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 |

Aug 20, 2029

16 |

Where can i get some?

17 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptate expedita hic dolorem, iusto vel suscipit nam excepturi debitis magnam nostrum! Ut eum dignissimos culpa doloremque eligendi consectetur blanditiis laboriosam fugiat ea quia similique quam nisi reprehenderit numquam magnam nemo vitae cupiditate, atque maiores dicta minus pariatur. Perspiciatis nobis vero quas?

18 | 19 |
20 |
21 |
22 | ); 23 | }; 24 | 25 | export default Featured; -------------------------------------------------------------------------------- /src/pages/Home/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from "react-helmet-async"; 2 | import Banner from "../Banner/Banner"; 3 | import Category from "../Category/Category"; 4 | import Featured from "../Featured/Featured"; 5 | import PopularMenu from "../PopularMenu/PopularMenu"; 6 | import Testimonials from "../Testimonials/Testimonials"; 7 | 8 | const Home = () => { 9 | return ( 10 |
11 | 12 | Bistro Boss | Home 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ); 21 | }; 22 | 23 | export default Home; -------------------------------------------------------------------------------- /src/pages/Home/PopularMenu/PopularMenu.jsx: -------------------------------------------------------------------------------- 1 | import SectionTitle from "../../../components/SectionTitle/SectionTitle"; 2 | import MenuItem from "../../Shared/MenuItem/MenuItem"; 3 | import useMenu from "../../../hooks/useMenu"; 4 | 5 | 6 | const PopularMenu = () => { 7 | const [menu] = useMenu(); 8 | const popular = menu.filter(item => item.category === 'popular'); 9 | 10 | return ( 11 |
12 | 16 |
17 | { 18 | popular.map(item => ) 22 | } 23 |
24 | 25 |
26 | ); 27 | }; 28 | 29 | export default PopularMenu; -------------------------------------------------------------------------------- /src/pages/Home/Testimonials/Testimonials.jsx: -------------------------------------------------------------------------------- 1 | import SectionTitle from "../../../components/SectionTitle/SectionTitle"; 2 | import { Swiper, SwiperSlide } from "swiper/react"; 3 | import { Navigation } from "swiper"; 4 | 5 | // Import Swiper styles 6 | import "swiper/css"; 7 | import "swiper/css/navigation"; 8 | import { useEffect, useState } from "react"; 9 | import { Rating } from "@smastrom/react-rating"; 10 | import '@smastrom/react-rating/style.css' 11 | 12 | 13 | const Testimonials = () => { 14 | const [reviews, setReviews] = useState([]); 15 | 16 | useEffect(() => { 17 | fetch('http://localhost:5000/reviews') 18 | .then(res => res.json()) 19 | .then(data => setReviews(data)) 20 | }, []) 21 | 22 | return ( 23 |
24 | 28 | 29 | 30 | 31 | { 32 | reviews.map(review => 35 |
36 | 41 |

{review.details}

42 |

{review.name}

43 |
44 |
) 45 | } 46 |
47 |
48 | ); 49 | }; 50 | 51 | export default Testimonials; -------------------------------------------------------------------------------- /src/pages/Login/Login.jsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react'; 2 | import { loadCaptchaEnginge, LoadCanvasTemplate, validateCaptcha } from 'react-simple-captcha'; 3 | import { AuthContext } from '../../providers/AuthProvider'; 4 | import { Link, useLocation, useNavigate } from 'react-router-dom'; 5 | import { Helmet } from 'react-helmet-async'; 6 | import Swal from 'sweetalert2' 7 | import SocialLogin from '../../components/SocialLogin/SocialLogin'; 8 | 9 | const Login = () => { 10 | const [disabled, setDisabled] = useState(true); 11 | const { signIn } = useContext(AuthContext); 12 | const navigate = useNavigate(); 13 | const location = useLocation(); 14 | 15 | const from = location.state?.from?.pathname || "/"; 16 | console.log('state in the location login page', location.state) 17 | 18 | useEffect(() => { 19 | loadCaptchaEnginge(6); 20 | }, []) 21 | 22 | const handleLogin = event => { 23 | event.preventDefault(); 24 | const form = event.target; 25 | const email = form.email.value; 26 | const password = form.password.value; 27 | console.log(email, password); 28 | signIn(email, password) 29 | .then(result => { 30 | const user = result.user; 31 | console.log(user); 32 | Swal.fire({ 33 | title: 'User Login Successful.', 34 | showClass: { 35 | popup: 'animate__animated animate__fadeInDown' 36 | }, 37 | hideClass: { 38 | popup: 'animate__animated animate__fadeOutUp' 39 | } 40 | }); 41 | navigate(from, { replace: true }); 42 | }) 43 | } 44 | 45 | const handleValidateCaptcha = (e) => { 46 | const user_captcha_value = e.target.value; 47 | if (validateCaptcha(user_captcha_value)) { 48 | setDisabled(false); 49 | } 50 | else { 51 | setDisabled(true) 52 | } 53 | } 54 | 55 | return ( 56 | <> 57 | 58 | Bistro Boss | Login 59 | 60 |
61 |
62 |
63 |

Login now!

64 |

Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem quasi. In deleniti eaque aut repudiandae et a id nisi.

65 |
66 |
67 |
68 |
69 | 72 | 73 |
74 |
75 | 78 | 79 | 82 |
83 |
84 | 87 | 88 | 89 |
90 |
91 | {/* TODO: apply disabled for re captcha */} 92 | 93 |
94 |
95 |

New Here? Create an account

96 | 97 |
98 |
99 |
100 | 101 | ); 102 | }; 103 | 104 | export default Login; -------------------------------------------------------------------------------- /src/pages/Menu/Menu/Menu.jsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from 'react-helmet-async'; 2 | import Cover from '../../Shared/Cover/Cover'; 3 | import menuImg from '../../../assets/menu/menu-bg.jpg' 4 | import soupImg from '../../../assets/menu/soup-bg.jpg' 5 | import saladImg from '../../../assets/menu/salad-bg.jpg' 6 | import pizzaImg from '../../../assets/menu/pizza-bg.jpg' 7 | import dessertImg from '../../../assets/menu/dessert-bg.jpeg' 8 | import useMenu from '../../../hooks/useMenu'; 9 | import SectionTitle from '../../../components/SectionTitle/SectionTitle'; 10 | import MenuCategory from '../MenuCategory/MenuCategory'; 11 | 12 | 13 | const Menu = () => { 14 | const [menu] = useMenu(); 15 | const desserts = menu.filter(item => item.category === 'dessert'); 16 | const soup = menu.filter(item => item.category === 'soup'); 17 | const salad = menu.filter(item => item.category === 'salad'); 18 | const pizza = menu.filter(item => item.category === 'pizza'); 19 | const offered = menu.filter(item => item.category === 'offered'); 20 | return ( 21 |
22 | 23 | Bistro Boss | Menu 24 | 25 | 26 | {/* main cover */} 27 | 28 | {/* offered menu items */} 29 | 30 | {/* dessert menu items */} 31 | 32 | 33 | 34 | 35 |
36 | ); 37 | }; 38 | 39 | export default Menu; -------------------------------------------------------------------------------- /src/pages/Menu/MenuCategory/MenuCategory.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { Link } from 'react-router-dom'; 3 | import Cover from '../../Shared/Cover/Cover'; 4 | import MenuItem from '../../Shared/MenuItem/MenuItem'; 5 | 6 | const MenuCategory = ({items, title, img}) => { 7 | return ( 8 |
9 | { title && } 10 |
11 | { 12 | items.map(item => ) 16 | } 17 |
18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default MenuCategory; -------------------------------------------------------------------------------- /src/pages/Order/Order/Order.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import orderCoverImg from '../../../assets/shop/order.jpg' 3 | import Cover from '../../Shared/Cover/Cover'; 4 | import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; 5 | import 'react-tabs/style/react-tabs.css'; 6 | import useMenu from '../../../hooks/useMenu'; 7 | import OrderTab from '../OrderTab/OrderTab'; 8 | import { useParams } from 'react-router'; 9 | import { Helmet } from 'react-helmet-async'; 10 | 11 | const Order = () => { 12 | const categories = ['salad', 'pizza', 'soup', 'dessert', 'drinks']; 13 | const { category } = useParams(); 14 | const initialIndex = categories.indexOf(category); 15 | const [tabIndex, setTabIndex] = useState(initialIndex); 16 | const [menu] = useMenu(); 17 | 18 | const desserts = menu.filter(item => item.category === 'dessert'); 19 | const soup = menu.filter(item => item.category === 'soup'); 20 | const salad = menu.filter(item => item.category === 'salad'); 21 | const pizza = menu.filter(item => item.category === 'pizza'); 22 | const drinks = menu.filter(item => item.category === 'drinks'); 23 | 24 | return ( 25 |
26 | 27 | Bistro Boss | Order Food 28 | 29 | 30 | setTabIndex(index)}> 31 | 32 | Salad 33 | Pizza 34 | Soup 35 | Dessert 36 | Drinks 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | ); 56 | }; 57 | 58 | export default Order; -------------------------------------------------------------------------------- /src/pages/Order/OrderTab/OrderTab.jsx: -------------------------------------------------------------------------------- 1 | import FoodCard from '../../../components/FoodCard/FoodCard'; 2 | import { Swiper, SwiperSlide } from "swiper/react"; 3 | import { Pagination } from "swiper"; 4 | import "swiper/css"; 5 | import "swiper/css/pagination"; 6 | 7 | const OrderTab = ({ items }) => { 8 | const pagination = { 9 | clickable: true, 10 | renderBullet: function (index, className) { 11 | return '' + (index + 1) + ""; 12 | }, 13 | }; 14 | return ( 15 |
16 | 17 | 22 | 23 |
24 | { 25 | items.map(item => ) 29 | } 30 |
31 | 32 |
33 | 34 |
35 |
36 | ); 37 | }; 38 | 39 | export default OrderTab; -------------------------------------------------------------------------------- /src/pages/Shared/Cover/Cover.jsx: -------------------------------------------------------------------------------- 1 | import { Parallax } from 'react-parallax'; 2 | 3 | const Cover = ({ img, title }) => { 4 | return ( 5 | 11 |
12 |
13 |
14 |
15 |

{title}

16 |

Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem quasi. In deleniti eaque aut repudiandae et a id nisi.

17 | 18 |
19 |
20 |
21 |
22 | 23 | ); 24 | }; 25 | 26 | export default Cover; -------------------------------------------------------------------------------- /src/pages/Shared/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | 2 | const Footer = () => { 3 | return ( 4 | 25 | ); 26 | }; 27 | 28 | export default Footer; -------------------------------------------------------------------------------- /src/pages/Shared/MenuItem/MenuItem.jsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | const MenuItem = ({item}) => { 4 | const {name, image, price, recipe} = item; 5 | return ( 6 |
7 | 8 |
9 |

{name}----------

10 |

{recipe}

11 |
12 |

${price}

13 |
14 | ); 15 | }; 16 | 17 | export default MenuItem; -------------------------------------------------------------------------------- /src/pages/Shared/NavBar/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { AuthContext } from "../../../providers/AuthProvider"; 4 | import { FaShoppingCart } from 'react-icons/fa'; 5 | import useCart from "../../../hooks/useCart"; 6 | 7 | const NavBar = () => { 8 | const { user, logOut } = useContext(AuthContext); 9 | const [cart] = useCart(); 10 | 11 | const handleLogOut = () => { 12 | logOut() 13 | .then(() => { }) 14 | .catch(error => console.log(error)); 15 | } 16 | 17 | const navOptions = <> 18 |
  • Home
  • 19 |
  • Our Menu
  • 20 |
  • Order Food
  • 21 |
  • Secret
  • 22 |
  • 23 | 24 | 28 | 29 |
  • 30 | { 31 | user ? <> 32 | {/* {user?.displayName} */} 33 | 34 | : <> 35 |
  • Login
  • 36 | 37 | } 38 | 39 | 40 | return ( 41 | <> 42 |
    43 |
    44 |
    45 | 48 |
      49 | {navOptions} 50 |
    51 |
    52 | Bistro Boss 53 |
    54 |
    55 | 58 |
    59 |
    60 | Get started 61 |
    62 |
    63 | 64 | ); 65 | }; 66 | 67 | export default NavBar; -------------------------------------------------------------------------------- /src/pages/Shared/Secret/Secret.jsx: -------------------------------------------------------------------------------- 1 | 2 | const Secret = () => { 3 | return ( 4 |
    5 |

    Secret things

    6 |
    7 | ); 8 | }; 9 | 10 | export default Secret; -------------------------------------------------------------------------------- /src/pages/SignUp/SignUp.jsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { Helmet } from "react-helmet-async"; 3 | import { useForm } from "react-hook-form"; 4 | import { AuthContext } from "../../providers/AuthProvider"; 5 | import { Link, useNavigate } from "react-router-dom"; 6 | import Swal from 'sweetalert2' 7 | import useAxiosPublic from "../../hooks/useAxiosPublic"; 8 | import SocialLogin from "../../components/SocialLogin/SocialLogin"; 9 | 10 | const SignUp = () => { 11 | const axiosPublic = useAxiosPublic(); 12 | const { register, handleSubmit, reset, formState: { errors } } = useForm(); 13 | const { createUser, updateUserProfile } = useContext(AuthContext); 14 | const navigate = useNavigate(); 15 | 16 | const onSubmit = data => { 17 | 18 | createUser(data.email, data.password) 19 | .then(result => { 20 | const loggedUser = result.user; 21 | console.log(loggedUser); 22 | updateUserProfile(data.name, data.photoURL) 23 | .then(() => { 24 | // create user entry in the database 25 | const userInfo = { 26 | name: data.name, 27 | email: data.email 28 | } 29 | axiosPublic.post('/users', userInfo) 30 | .then(res => { 31 | if (res.data.insertedId) { 32 | console.log('user added to the database') 33 | reset(); 34 | Swal.fire({ 35 | position: 'top-end', 36 | icon: 'success', 37 | title: 'User created successfully.', 38 | showConfirmButton: false, 39 | timer: 1500 40 | }); 41 | navigate('/'); 42 | } 43 | }) 44 | 45 | 46 | }) 47 | .catch(error => console.log(error)) 48 | }) 49 | }; 50 | 51 | return ( 52 | <> 53 | 54 | Bistro Boss | Sign Up 55 | 56 |
    57 |
    58 |
    59 |

    Sign up now!

    60 |

    Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem quasi. In deleniti eaque aut repudiandae et a id nisi.

    61 |
    62 |
    63 |
    64 |
    65 | 68 | 69 | {errors.name && Name is required} 70 |
    71 |
    72 | 75 | 76 | {errors.photoURL && Photo URL is required} 77 |
    78 |
    79 | 82 | 83 | {errors.email && Email is required} 84 |
    85 |
    86 | 89 | 95 | {errors.password?.type === 'required' &&

    Password is required

    } 96 | {errors.password?.type === 'minLength' &&

    Password must be 6 characters

    } 97 | {errors.password?.type === 'maxLength' &&

    Password must be less than 20 characters

    } 98 | {errors.password?.type === 'pattern' &&

    Password must have one Uppercase one lower case, one number and one special character.

    } 99 | 102 |
    103 |
    104 | 105 |
    106 |
    107 |

    Already have an account Login

    108 | 109 |
    110 |
    111 |
    112 | 113 | ); 114 | }; 115 | 116 | export default SignUp; -------------------------------------------------------------------------------- /src/providers/AuthProvider.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useEffect, useState } from "react"; 2 | import { GoogleAuthProvider, createUserWithEmailAndPassword, getAuth, onAuthStateChanged, signInWithEmailAndPassword, signInWithPopup, signOut, updateProfile } from "firebase/auth"; 3 | import { app } from "../firebase/firebase.config"; 4 | import useAxiosPublic from "../hooks/useAxiosPublic"; 5 | 6 | export const AuthContext = createContext(null); 7 | 8 | const auth = getAuth(app); 9 | 10 | const AuthProvider = ({ children }) => { 11 | const [user, setUser] = useState(null); 12 | const [loading, setLoading] = useState(true); 13 | const googleProvider = new GoogleAuthProvider(); 14 | const axiosPublic = useAxiosPublic(); 15 | 16 | const createUser = (email, password) => { 17 | setLoading(true); 18 | return createUserWithEmailAndPassword(auth, email, password) 19 | } 20 | 21 | const signIn = (email, password) => { 22 | setLoading(true); 23 | return signInWithEmailAndPassword(auth, email, password); 24 | } 25 | 26 | const googleSignIn = () => { 27 | setLoading(true); 28 | return signInWithPopup(auth, googleProvider); 29 | } 30 | 31 | const logOut = () => { 32 | setLoading(true); 33 | return signOut(auth); 34 | } 35 | 36 | const updateUserProfile = (name, photo) => { 37 | return updateProfile(auth.currentUser, { 38 | displayName: name, photoURL: photo 39 | }); 40 | } 41 | 42 | useEffect(() => { 43 | const unsubscribe = onAuthStateChanged(auth, currentUser => { 44 | setUser(currentUser); 45 | if (currentUser) { 46 | // get token and store client 47 | const userInfo = { email: currentUser.email }; 48 | axiosPublic.post('/jwt', userInfo) 49 | .then(res => { 50 | if (res.data.token) { 51 | localStorage.setItem('access-token', res.data.token); 52 | } 53 | }) 54 | } 55 | else { 56 | // TODO: remove token (if token stored in the client side: Local storage, caching, in memory) 57 | localStorage.removeItem('access-token'); 58 | } 59 | setLoading(false); 60 | }); 61 | return () => { 62 | return unsubscribe(); 63 | } 64 | }, [axiosPublic]) 65 | 66 | const authInfo = { 67 | user, 68 | loading, 69 | createUser, 70 | signIn, 71 | googleSignIn, 72 | logOut, 73 | updateUserProfile 74 | } 75 | 76 | return ( 77 | 78 | {children} 79 | 80 | ); 81 | }; 82 | 83 | export default AuthProvider; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [require("daisyui")], 11 | } 12 | 13 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------