├── .gitignore ├── Client ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── 1.ico │ ├── favicon.ico │ ├── images │ │ ├── p1.jpg │ │ ├── p2.jpg │ │ ├── p3.jpg │ │ └── p4.jpg │ ├── index.html │ └── robots.txt └── src │ ├── App.jsx │ ├── api │ └── api.js │ ├── assets │ └── cover.jpg │ ├── components │ ├── AddProductModal.jsx │ ├── CheckoutSteps.jsx │ ├── MyNavbar.jsx │ ├── PaypalCheckoutButton.jsx │ ├── Product.jsx │ ├── Review.jsx │ ├── Reviews.jsx │ ├── StripeCheckoutButton.jsx │ └── index.js │ ├── data │ └── data.js │ ├── index.js │ ├── index.scss │ ├── pages │ ├── AdminOrders.jsx │ ├── AdminProductEdit.jsx │ ├── AdminProducts.jsx │ ├── AdminUserEdit.jsx │ ├── AdminUsers.jsx │ ├── Cart.jsx │ ├── Dashboard.jsx │ ├── Home.jsx │ ├── Login.jsx │ ├── Map.jsx │ ├── Order.jsx │ ├── Orders.jsx │ ├── Payment.jsx │ ├── PlaceOrder.jsx │ ├── Product.jsx │ ├── Search.jsx │ ├── Shipping.jsx │ ├── Signup.jsx │ ├── UserProfile.jsx │ └── index.js │ ├── redux │ ├── cartSlice.js │ ├── store.js │ └── userSlice.js │ └── utils │ └── searchUtils.js ├── README.md ├── REQUIRED .md ├── Server ├── api │ ├── controllers │ │ ├── authController.js │ │ ├── cartController.js │ │ ├── orderController.js │ │ ├── productController.js │ │ ├── reviewController.js │ │ └── userControlller.js │ ├── index.js │ └── routes │ │ ├── authRoute.js │ │ ├── cartRoute.js │ │ ├── orderRoute.js │ │ ├── productRoute.js │ │ ├── reviewRoute.js │ │ ├── seedRoute.js │ │ └── userRoute.js ├── config │ └── index.js ├── data │ └── index.js ├── middlewares │ ├── authMiddleware.js │ └── errorHandler.js ├── models │ ├── cartModel.js │ ├── orderModel.js │ ├── productModel.js │ ├── reviewModel.js │ └── userModel.js ├── package-lock.json ├── package.json ├── server.js ├── services │ └── index.js ├── upload │ └── images │ │ ├── p1.jpg │ │ ├── p2.jpg │ │ ├── p3.jpg │ │ └── p4.jpg └── utils │ ├── createError.js │ └── upload.js └── screenshots ├── 1.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png ├── 16.png ├── 17.png ├── 18.png ├── 19.png ├── 2.png ├── 20.png ├── 21.png ├── 22.png ├── 23.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png ├── 9.png ├── cover.jpg └── responsive ├── 1.png ├── 10.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png └── 9.png /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | .env 5 | node_modules 6 | /node_modules/* 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /Client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /Client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /Client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-end", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.10.4", 7 | "@emotion/styled": "^11.10.4", 8 | "@mui/icons-material": "^5.10.3", 9 | "@mui/material": "^5.10.5", 10 | "@paypal/react-paypal-js": "^7.8.1", 11 | "@react-google-maps/api": "^2.13.1", 12 | "@reduxjs/toolkit": "^1.8.5", 13 | "@stripe/react-stripe-js": "^1.10.0", 14 | "@stripe/stripe-js": "^1.37.0", 15 | "@testing-library/jest-dom": "^5.16.5", 16 | "@testing-library/react": "^13.4.0", 17 | "@testing-library/user-event": "^13.5.0", 18 | "axios": "^0.27.2", 19 | "bootstrap": "^5.2.1", 20 | "google-maps-react": "^2.0.6", 21 | "moment": "^2.29.4", 22 | "react": "^18.2.0", 23 | "react-bootstrap": "^2.5.0", 24 | "react-dom": "^18.2.0", 25 | "react-google-charts": "^4.0.0", 26 | "react-google-maps": "^9.4.5", 27 | "react-redux": "^8.0.2", 28 | "react-router-dom": "^6.4.0", 29 | "react-scripts": "5.0.1", 30 | "react-stripe-checkout": "^2.6.3", 31 | "react-toastify": "^9.0.8", 32 | "web-vitals": "^2.1.4" 33 | }, 34 | "scripts": { 35 | "start": "react-scripts start", 36 | "build": "react-scripts build", 37 | "test": "react-scripts test", 38 | "eject": "react-scripts eject" 39 | }, 40 | "eslintConfig": { 41 | "extends": [ 42 | "react-app", 43 | "react-app/jest" 44 | ] 45 | }, 46 | "browserslist": { 47 | "production": [ 48 | ">0.2%", 49 | "not dead", 50 | "not op_mini all" 51 | ], 52 | "development": [ 53 | "last 1 chrome version", 54 | "last 1 firefox version", 55 | "last 1 safari version" 56 | ] 57 | }, 58 | "devDependencies": { 59 | "node-sass": "^7.0.3", 60 | "sass": "^1.54.9", 61 | "sass-loader": "^13.0.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Client/public/1.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Client/public/1.ico -------------------------------------------------------------------------------- /Client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Client/public/favicon.ico -------------------------------------------------------------------------------- /Client/public/images/p1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Client/public/images/p1.jpg -------------------------------------------------------------------------------- /Client/public/images/p2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Client/public/images/p2.jpg -------------------------------------------------------------------------------- /Client/public/images/p3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Client/public/images/p3.jpg -------------------------------------------------------------------------------- /Client/public/images/p4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Client/public/images/p4.jpg -------------------------------------------------------------------------------- /Client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | Amazon Store 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /Client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /Client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 2 | import { PayPalScriptProvider } from '@paypal/react-paypal-js'; 3 | import Alert from 'react-bootstrap/Alert'; 4 | import { ToastContainer } from 'react-toastify'; 5 | import { Navbar } from './components'; 6 | import { 7 | Cart, 8 | Home, 9 | Orders, 10 | Login, 11 | Signup, 12 | Product, 13 | Shipping, 14 | Payment, 15 | PlaceOrder, 16 | Order, 17 | UserProfile, 18 | Search, 19 | AdminOrders, 20 | AdminUsers, 21 | AdminUserEdit, 22 | AdminProducts, 23 | AdminProductEdit, 24 | Dashboard, 25 | // Map, 26 | } from './pages'; 27 | 28 | function App() { 29 | return ( 30 | 34 | 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 | 69 | {' '} 70 | Error this route is wrong{' '} 71 | 72 | } 73 | /> 74 | 75 | 76 |
77 | 83 |
84 |
85 | ); 86 | } 87 | 88 | export default App; 89 | -------------------------------------------------------------------------------- /Client/src/api/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const baseURL = process.env.REACT_APP_API_URL; 4 | 5 | const API = axios.create({ 6 | baseURL, 7 | headers: { 8 | // 'Content-Type': 'multipart/form-data', 9 | 'Content-Type': 'application/json', 10 | authorization: `${localStorage.getItem('access-token')}`, 11 | }, 12 | transformRequest: [ 13 | function (data, headers) { 14 | if (data instanceof FormData) { 15 | headers['Content-Type'] = 'multipart/form-data'; 16 | } else { 17 | headers['Content-Type'] = 'application/json'; 18 | } 19 | if (localStorage.getItem('access-token')) 20 | headers['authorization'] = `${localStorage.getItem('access-token')}`; 21 | // Do not change data 22 | if (data instanceof FormData) { 23 | return data; 24 | } 25 | return JSON.stringify(data); 26 | }, 27 | ], 28 | }); 29 | 30 | // api.get(url, 31 | // ,{ 32 | // headers: { 33 | // authorization: localStorage.getItem("access-token"), 34 | // }, 35 | // }) 36 | 37 | export default API; 38 | -------------------------------------------------------------------------------- /Client/src/assets/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Client/src/assets/cover.jpg -------------------------------------------------------------------------------- /Client/src/components/AddProductModal.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | import Button from 'react-bootstrap/Button'; 3 | import Form from 'react-bootstrap/Form'; 4 | import Alert from 'react-bootstrap/Alert'; 5 | import FloatingLabel from 'react-bootstrap/FloatingLabel'; 6 | import Modal from 'react-bootstrap/Modal'; 7 | import API from '../api/api.js'; 8 | import { toast } from 'react-toastify'; 9 | 10 | function AddProductModal({ handleClose, show, addProduct }) { 11 | const formRef = useRef(null); 12 | const [errorMessage, setErrorMessage] = useState(''); 13 | 14 | const handleAddProduct = async (e) => { 15 | e.preventDefault(); 16 | 17 | let name = formRef.current.name.value.trim(), 18 | description = formRef.current.description.value.trim(), 19 | price = formRef.current.price.value.trim(), 20 | countInStock = formRef.current.countInStock.value.trim(), 21 | category = formRef.current.category.value.trim(), 22 | image = formRef.current.image.files[0], 23 | brand = formRef.current.brand.value.trim(); 24 | 25 | setErrorMessage(''); 26 | console.log(image); 27 | if ( 28 | !name || 29 | !description || 30 | !price || 31 | !countInStock || 32 | !category || 33 | !brand || 34 | !image 35 | ) 36 | return setErrorMessage('Please Enter All Fields'); 37 | 38 | try { 39 | let data = new FormData(); 40 | data.append('image', image); //<-- CHANGED .value to .files[0] 41 | data.append('name', name); 42 | data.append('description', description); 43 | data.append('price', price); 44 | data.append('countInStock', countInStock); 45 | data.append('category', category); 46 | data.append('brand', brand); 47 | 48 | console.log('asd'); 49 | const { data: res } = await API.post('/products', data, { 50 | 'Content-Type': 'multipart/form-data', 51 | }); 52 | console.log(res); 53 | addProduct(res.product); 54 | formRef.current.reset(); 55 | handleClose(); 56 | } catch (error) { 57 | setErrorMessage('Error while Adding Product'); 58 | console.log('erro fetching cart'); 59 | } 60 | }; 61 | return ( 62 | <> 63 | 64 | 65 | Add Product 66 | 67 | 68 |
69 | 70 | Name 71 | 77 | 78 | 79 | 80 | Image 81 | 87 | 88 | 89 | 90 | Description 91 | 97 | 98 | 99 | 100 | Price 101 | 107 | 108 | 109 | 110 | count in stock 111 | 117 | 118 | 119 | 120 | category 121 | 127 | 128 | 129 | 130 | Brand 131 | 137 | 138 | 139 | {errorMessage && {errorMessage}} 140 |
141 |
142 | 143 | 146 | 149 | 150 |
151 | 152 | ); 153 | } 154 | 155 | export default AddProductModal; 156 | -------------------------------------------------------------------------------- /Client/src/components/CheckoutSteps.jsx: -------------------------------------------------------------------------------- 1 | import Row from 'react-bootstrap/Row'; 2 | import Col from 'react-bootstrap/Col'; 3 | 4 | const CheckoutSteps = ({ step1, step2, step3, step4 }) => { 5 | return ( 6 | 7 | 12 | Sign in 13 | 14 | 19 | Shipping 20 | 21 | 26 | Payment 27 | 28 | 33 | Place Order 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default CheckoutSteps; 40 | -------------------------------------------------------------------------------- /Client/src/components/MyNavbar.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Nav from 'react-bootstrap/Nav'; 3 | import Navbar from 'react-bootstrap/Navbar'; 4 | import Badge from 'react-bootstrap/Badge'; 5 | import NavDropdown from 'react-bootstrap/NavDropdown'; 6 | import Form from 'react-bootstrap/Form'; 7 | import Button from 'react-bootstrap/Button'; 8 | import InputGroup from 'react-bootstrap/InputGroup'; 9 | import SearchIcon from '@mui/icons-material/Search'; 10 | import { useNavigate, Link } from 'react-router-dom'; 11 | import { useDispatch, useSelector } from 'react-redux'; 12 | import { logout } from '../redux/userSlice.js'; 13 | import { clearCart, setCartItems } from '../redux/cartSlice.js'; 14 | import { useEffect } from 'react'; 15 | import API from '../api/api.js'; 16 | import { toast } from 'react-toastify'; 17 | 18 | function MyNavbar() { 19 | const dispatch = useDispatch(); 20 | const navigate = useNavigate(); 21 | const { currentUser } = useSelector((state) => state.user); 22 | const { cart, cartCount } = useSelector((state) => state.cart); 23 | 24 | useEffect(() => { 25 | const fecthData = async () => { 26 | try { 27 | const { data: res } = await API.get('/cart'); 28 | res.cart && dispatch(setCartItems(res.cart)); 29 | } catch (error) { 30 | toast.dismiss(); 31 | console.log('erro fetching cart'); 32 | } 33 | }; 34 | if (currentUser) { 35 | fecthData(); 36 | } 37 | }, [dispatch, currentUser]); 38 | 39 | const handleSearch = (e) => { 40 | e.preventDefault(); 41 | const category = e.target.search.value.trim() || 'all'; 42 | navigate(`/search?category=${category}`); 43 | }; 44 | 45 | const handleLogOut = (e) => { 46 | dispatch(logout()); 47 | dispatch(clearCart()); 48 | navigate('/login'); 49 | }; 50 | return ( 51 | 59 | 60 | 61 | Amazon Store 62 | 63 | 64 | 65 | 66 | 67 |
68 | 69 | 70 | 73 | 74 |
75 | 76 | 124 |
125 |
126 |
127 | ); 128 | } 129 | 130 | export default MyNavbar; 131 | -------------------------------------------------------------------------------- /Client/src/components/PaypalCheckoutButton.jsx: -------------------------------------------------------------------------------- 1 | import { CircularProgress } from '@mui/material'; 2 | import { PayPalButtons, usePayPalScriptReducer } from '@paypal/react-paypal-js'; 3 | import { useEffect } from 'react'; 4 | import API from '../api/api.js'; 5 | import { toast } from 'react-toastify'; 6 | 7 | const PaypalCheckoutButton = ({ totalPrice, orderId, setOrderDetails }) => { 8 | const [{ isPending }, paypalDispatch] = usePayPalScriptReducer(); 9 | 10 | const createOrder = async (data, actions) => { 11 | return actions.order 12 | .create({ 13 | purchase_units: [ 14 | { 15 | description: 'order Desc', 16 | amount: { 17 | value: totalPrice, 18 | }, 19 | }, 20 | ], 21 | }) 22 | .then((orderID) => { 23 | toast.dismiss(); 24 | toast.success('Order Paid Successfully'); 25 | return orderID; 26 | }); 27 | }; 28 | 29 | const onApprove = async (data, actions) => { 30 | try { 31 | const { data: res } = await API.put(`/orders/${orderId}/pay`); 32 | setOrderDetails((prev) => ({ ...prev, isPaid: true })); 33 | console.log(res); 34 | } catch (err) { 35 | console.log(err); 36 | // toast.error(getError(err)); 37 | } 38 | }; 39 | function onError(err) { 40 | console.log(err); 41 | toast.dismiss(); 42 | toast.error('error while paying Order'); 43 | // toast.error(getError(err)); 44 | } 45 | 46 | useEffect(() => { 47 | const loadPaypalScript = async () => { 48 | const clientId = process.env.REACT_APP_PAYPAL_CLIENT_ID; 49 | paypalDispatch({ 50 | type: 'resetOptions', 51 | value: { 52 | 'client-id': clientId, 53 | currency: 'USD', 54 | }, 55 | }); 56 | paypalDispatch({ type: 'setLoadingStatus', value: 'pending' }); 57 | }; 58 | loadPaypalScript(); 59 | }, [paypalDispatch]); 60 | return ( 61 | <> 62 | {isPending ? ( 63 | 64 | ) : ( 65 | 70 | )} 71 | 72 | ); 73 | }; 74 | 75 | export default PaypalCheckoutButton; 76 | -------------------------------------------------------------------------------- /Client/src/components/Product.jsx: -------------------------------------------------------------------------------- 1 | import Button from 'react-bootstrap/Button'; 2 | import Card from 'react-bootstrap/Card'; 3 | import Alert from 'react-bootstrap/Alert'; 4 | import Review from './Review.jsx'; 5 | import { Link, useNavigate } from 'react-router-dom'; 6 | import API from '../api/api.js'; 7 | import { useDispatch, useSelector } from 'react-redux'; 8 | import { setCartItems } from '../redux/cartSlice.js'; 9 | import { useState } from 'react'; 10 | import { toast } from 'react-toastify'; 11 | 12 | const Product = ({ p }) => { 13 | const dispatch = useDispatch(); 14 | const navigate = useNavigate(); 15 | const { currentUser } = useSelector((state) => state.user); 16 | 17 | const [buttonState, setButtonState] = useState({ 18 | loading: false, 19 | error: '', 20 | }); 21 | 22 | const handleAddToCart = (e) => { 23 | !currentUser && navigate('/login'); 24 | const addToCartRequest = async () => { 25 | setButtonState((prev) => ({ ...prev, loading: true })); 26 | try { 27 | const item = { quantity: 1, product: p._id }; 28 | const { data: res } = await API.post('/cart', { 29 | items: [item], 30 | }); 31 | dispatch(setCartItems(res.cart)); 32 | setButtonState((prev) => ({ ...prev, loading: false })); 33 | console.log(res.cart); 34 | toast.dismiss(); 35 | toast.success('item added to cart succefully'); 36 | } catch (error) { 37 | setButtonState({ error: error.message, loading: false }); 38 | toast.dismiss(); 39 | toast.error('Error While adding to cart'); 40 | console.log(error); 41 | } 42 | }; 43 | currentUser && addToCartRequest(); 44 | }; 45 | 46 | return ( 47 | 48 | 49 | {p && p.image && ( 50 | 54 | )} 55 | 56 | 57 | 58 | {p.name} 59 | {p.description} 60 | 61 | 62 | ${p.price} 63 | 64 | 65 | {p.countInStock > 0 ? ( 66 | 69 | ) : ( 70 | 73 | )} 74 | 75 | 76 | ); 77 | }; 78 | 79 | export default Product; 80 | -------------------------------------------------------------------------------- /Client/src/components/Review.jsx: -------------------------------------------------------------------------------- 1 | import StarIcon from '@mui/icons-material/Star'; 2 | import StarHalfIcon from '@mui/icons-material/StarHalf'; 3 | import StarBorderIcon from '@mui/icons-material/StarBorder'; 4 | import { useEffect, useState } from 'react'; 5 | 6 | const Review = ({ rating, numReviews }) => { 7 | return ( 8 | <> 9 | {[1, 2, 3, 4, 5].map((idx) => ( 10 | 11 | {rating === idx - 0.5 ? ( 12 | 13 | ) : rating >= idx ? ( 14 | 15 | ) : ( 16 | 17 | )} 18 | 19 | ))} 20 | {numReviews !== -1 && ( 21 |

{numReviews ?? 0} Reviews

22 | )} 23 | 24 | ); 25 | }; 26 | 27 | export default Review; 28 | -------------------------------------------------------------------------------- /Client/src/components/Reviews.jsx: -------------------------------------------------------------------------------- 1 | import Alert from 'react-bootstrap/Alert'; 2 | import ListGroup from 'react-bootstrap/ListGroup'; 3 | import Form from 'react-bootstrap/Form'; 4 | import Button from 'react-bootstrap/Button'; 5 | import Review from '../components/Review.jsx'; 6 | import { Link } from 'react-router-dom'; 7 | import { useEffect, useRef, useState } from 'react'; 8 | import { useSelector } from 'react-redux'; 9 | import API from '../api/api.js'; 10 | import { toast } from 'react-toastify'; 11 | 12 | const Reviews = ({ productId }) => { 13 | const { currentUser } = useSelector((state) => state.user); 14 | const [reviews, setReviews] = useState([]); 15 | const [errorMessage, setErrorMessage] = useState([]); 16 | const formRef = useRef(null); 17 | 18 | const handleSubmitReview = async (e) => { 19 | e.preventDefault(); 20 | let ratingOption = formRef.current.ratingOption.value, 21 | comment = formRef.current.comment.value.trim(); 22 | 23 | let err = []; 24 | setErrorMessage([]); 25 | 26 | if (!comment) err.push('please enter valid comment'); 27 | if (ratingOption === '0') err.push('please choose valid rating'); 28 | if (err.length !== 0) { 29 | setErrorMessage(err); 30 | console.log(err); 31 | return; 32 | } 33 | 34 | try { 35 | const { data: res } = await API.post(`/reviews/${productId}`, { 36 | comment, 37 | rating: ratingOption, 38 | name: currentUser.name, 39 | }); 40 | setReviews((prev) => [...prev, res.review]); 41 | formRef.current.reset(); 42 | console.log(res.review); 43 | } catch (error) { 44 | toast.dismiss(); 45 | toast.error('error while fetching Reviews'); 46 | console.log(error.message); 47 | } 48 | }; 49 | 50 | useEffect(() => { 51 | const fecthProduct = async () => { 52 | try { 53 | const { data: res } = await API.get(`/reviews/${productId}`); 54 | setReviews(res.reviews); 55 | } catch (error) { 56 | toast.dismiss(); 57 | toast.error('Error While Fetching products'); 58 | console.log(error.message); 59 | } 60 | }; 61 | fecthProduct(); 62 | }, [productId]); 63 | 64 | return ( 65 | <> 66 |

Reviews

67 | 68 | {reviews && 69 | reviews.map((r, idx) => ( 70 | 71 | {r.name} 72 | 73 |

2022-06-22

74 |

{r.comment}

75 |
76 | ))} 77 |
78 | 79 | {/* Write Review */} 80 | {currentUser ? ( 81 | <> 82 |

Write Customer Review

83 |
84 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | {errorMessage.includes('please choose valid rating') && ( 96 | 97 | please enter valid comment 98 | 99 | )} 100 | 101 | 105 | Review 106 | 112 | 113 | 114 | {errorMessage.includes('please enter valid comment') && ( 115 | 116 | please enter valid comment 117 | 118 | )} 119 | 120 | 123 |
124 | 125 | ) : ( 126 | 127 | Please Sign In to write a review 128 | 129 | )} 130 | 131 | ); 132 | }; 133 | 134 | export default Reviews; 135 | -------------------------------------------------------------------------------- /Client/src/components/StripeCheckoutButton.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import StripeCheckout from 'react-stripe-checkout'; 3 | import Button from 'react-bootstrap/Button'; 4 | import API from '../api/api.js'; 5 | import { toast } from 'react-toastify'; 6 | const KEY = process.env.REACT_APP_STRIPE_CLIENT_ID; 7 | 8 | const StripeCheckoutButton = ({ totalPrice, orderId, setOrderDetails }) => { 9 | const [stripeToken, setStripeToken] = useState(null); 10 | const onToken = (token) => { 11 | setStripeToken(token); 12 | }; 13 | useEffect(() => { 14 | const makeRequest = async () => { 15 | try { 16 | const { data: res } = await API.post(`/orders/${orderId}/stripe`, { 17 | tokenId: stripeToken.id, 18 | amount: totalPrice * 100, 19 | }); 20 | console.log(res); 21 | toast.success('Paid Successfully'); 22 | setOrderDetails((prev) => ({ 23 | ...prev, 24 | isPaid: true, 25 | })); 26 | } catch (err) { 27 | console.log(err); 28 | toast.error(err); 29 | } 30 | }; 31 | stripeToken && makeRequest(); 32 | }, [stripeToken, orderId, totalPrice]); 33 | 34 | return ( 35 | 43 | 46 | 47 | ); 48 | }; 49 | 50 | export default StripeCheckoutButton; 51 | -------------------------------------------------------------------------------- /Client/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './MyNavbar.jsx'; 2 | export { default as Product } from './Product.jsx'; 3 | export { default as Review } from './Review.jsx'; 4 | export { default as CheckoutSteps } from './CheckoutSteps.jsx'; 5 | export { default as Reviews } from './Reviews.jsx'; 6 | export { default as PaypalCheckoutButton } from './PaypalCheckoutButton.jsx'; 7 | export { default as AddProductModal } from './AddProductModal.jsx'; 8 | export { default as StripeCheckoutButton } from './StripeCheckoutButton.jsx'; 9 | -------------------------------------------------------------------------------- /Client/src/data/data.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | users: [ 3 | { 4 | name: 'admin', 5 | email: 'admin@gmail.com', 6 | password: '123456', 7 | isAdmin: true, 8 | }, 9 | { 10 | name: 'test', 11 | email: 'test@gmail.com', 12 | password: '123456', 13 | isAdmin: false, 14 | }, 15 | { 16 | name: 'omar', 17 | email: 'omar@gmail.com', 18 | password: '123456', 19 | isAdmin: false, 20 | }, 21 | { 22 | name: 'test2', 23 | email: 'test2@gmail.com', 24 | password: '123456', 25 | isAdmin: false, 26 | }, 27 | ], 28 | products: [ 29 | { 30 | name: 'Nike Slim shirt', 31 | description: 'high quality shirt', 32 | category: ['Shirts'], 33 | price: 120, 34 | countInStock: 10, 35 | brand: 'Nike', 36 | rating: 3, 37 | numReviews: 10, 38 | image: '/images/p1.jpg', // 679px × 829px 39 | }, 40 | { 41 | name: 'Adidas Fit Shirt', 42 | category: ['Shirts'], 43 | image: '/images/p2.jpg', 44 | price: 250, 45 | countInStock: 0, 46 | brand: 'Adidas', 47 | rating: 4.0, 48 | numReviews: 10, 49 | description: 'high quality product', 50 | }, 51 | { 52 | name: 'Nike Slim Pant', 53 | category: ['Pants'], 54 | image: '/images/p3.jpg', 55 | price: 25, 56 | countInStock: 15, 57 | brand: 'Nike', 58 | rating: 4.5, 59 | numReviews: 14, 60 | description: 'high quality product', 61 | }, 62 | { 63 | name: 'Adidas Fit Pant', 64 | category: ['Pants'], 65 | image: '/images/p4.jpg', 66 | price: 65, 67 | countInStock: 5, 68 | brand: 'Puma', 69 | rating: 4.5, 70 | numReviews: 10, 71 | description: 'high quality product', 72 | }, 73 | ], 74 | }; 75 | export default data; 76 | -------------------------------------------------------------------------------- /Client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.scss'; 4 | import 'react-toastify/dist/ReactToastify.css'; 5 | import App from './App'; 6 | 7 | // redux 8 | import store from './redux/store.js'; 9 | import { Provider } from 'react-redux'; 10 | 11 | const root = ReactDOM.createRoot(document.getElementById('root')); 12 | root.render( 13 | 14 | 15 | 16 | 17 | , 18 | ); 19 | -------------------------------------------------------------------------------- /Client/src/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | .body { 11 | min-height: 100vh; 12 | } 13 | main { 14 | flex: 1 0 auto; 15 | } 16 | 17 | /* Map */ 18 | 19 | .full-box { 20 | height: 100vh; 21 | max-width: initial; 22 | } 23 | .full-box header, 24 | .full-box footer { 25 | display: none; 26 | } 27 | .full-box .container { 28 | height: 100vh; 29 | max-width: initial; 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | .full-box .mt-3 { 35 | margin-top: 0 !important; 36 | } 37 | 38 | .map-input-box { 39 | box-sizing: border-box; 40 | position: absolute; 41 | left: 0; 42 | right: 0; 43 | margin: 0.5rem auto; 44 | width: 25rem; 45 | height: 3rem; 46 | display: flex; 47 | } 48 | .map-input-box input { 49 | border-radius: 1rem 0 0 1rem; 50 | border-right: 0; 51 | } 52 | .map-input-box button { 53 | border-radius: 0 1rem 1rem 0; 54 | border-left: 0; 55 | } 56 | 57 | $primary: #f0c040; 58 | $secondary: #f08000; 59 | /* import bootstrap to set changes */ 60 | @import '~bootstrap/scss/bootstrap'; 61 | 62 | .myNav { 63 | position: sticky; 64 | top: 0; 65 | z-index: 1; 66 | } 67 | -------------------------------------------------------------------------------- /Client/src/pages/AdminOrders.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Table from 'react-bootstrap/Table'; 3 | import Row from 'react-bootstrap/Row'; 4 | import Col from 'react-bootstrap/Col'; 5 | import Button from 'react-bootstrap/Button'; 6 | import Alert from 'react-bootstrap/Alert'; 7 | import Badge from 'react-bootstrap/Badge'; 8 | import { useNavigate, Link } from 'react-router-dom'; 9 | import { useEffect, useState } from 'react'; 10 | import API from '../api/api.js'; 11 | import { CircularProgress } from '@mui/material'; 12 | import moment from 'moment'; 13 | import { useSelector } from 'react-redux'; 14 | import { toast } from 'react-toastify'; 15 | 16 | const AdminOrders = () => { 17 | const { currentUser } = useSelector((state) => state.user); 18 | const navigate = useNavigate(); 19 | 20 | const [orders, setOrders] = useState({ 21 | items: [], 22 | loading: false, 23 | error: false, 24 | }); 25 | useEffect(() => { 26 | if (!currentUser || !currentUser.isAdmin) navigate('/'); 27 | const fecthData = async () => { 28 | setOrders((prev) => ({ ...prev, loading: true })); 29 | try { 30 | const { data: res } = await API.get('/orders'); 31 | setOrders({ loading: false, error: false, items: res.orders }); 32 | } catch (error) { 33 | setOrders((prev) => ({ ...prev, loading: false, error: true })); 34 | console.log(error.message); 35 | toast.dismiss(); 36 | toast.error('Error While Fetching Orders'); 37 | } 38 | }; 39 | currentUser && fecthData(); 40 | }, [currentUser, navigate]); 41 | 42 | const handleDeletOrder = async (id) => { 43 | try { 44 | await API.delete(`/orders/${id}`); 45 | const filterd = orders.items.filter((e) => e._id !== id); 46 | setOrders({ 47 | loading: false, 48 | error: false, 49 | items: filterd, 50 | }); 51 | toast.dismiss(); 52 | toast.success('Order Deleted Successfully'); 53 | } catch (error) { 54 | console.log(error.message); 55 | toast.dismiss(); 56 | toast.error('Error While Deleting Order'); 57 | } 58 | }; 59 | return ( 60 | 61 | {!currentUser ? ( 62 | 63 | You Are not logged in Sign In To see your 64 | orders 65 | 66 | ) : ( 67 | <> 68 |

All Orders

69 | 70 | {orders.loading ? ( 71 | 72 | ) : orders.error ? ( 73 | Error While fetching orders 74 | ) : ( 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {orders.items.map((p, idx) => ( 89 | 90 | 91 | 92 | 93 | 101 | 108 | 115 | 133 | 134 | ))} 135 | 136 |
IDUser DATETOTALPAIDDELIVEREDACTIONS
{p._id}{p.userId.name}{moment(p.createdAt).format('MM/DD/YYYY')} 94 | 95 | $ 96 | {p.totalPrice + 97 | 0.05 * p.totalPrice + 98 | Math.floor(0.14 * p.totalPrice)} 99 | 100 | 102 | {p.isPaid ? ( 103 | Yes 104 | ) : ( 105 | No 106 | )} 107 | 109 | {p.isDelivered ? ( 110 | Yes 111 | ) : ( 112 | No 113 | )} 114 | 116 | 125 | 132 |
137 | )} 138 | 139 | )} 140 |
141 | ); 142 | }; 143 | 144 | export default AdminOrders; 145 | -------------------------------------------------------------------------------- /Client/src/pages/AdminProductEdit.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | import Form from 'react-bootstrap/Form'; 3 | import FloatingLabel from 'react-bootstrap/FloatingLabel'; 4 | import Alert from 'react-bootstrap/Alert'; 5 | import Row from 'react-bootstrap/Row'; 6 | import Col from 'react-bootstrap/Col'; 7 | import Button from 'react-bootstrap/Button'; 8 | import Container from 'react-bootstrap/Container'; 9 | import { useParams } from 'react-router-dom'; 10 | import API from '../api/api.js'; 11 | import { toast } from 'react-toastify'; 12 | 13 | const AdminUserEdit = () => { 14 | const params = useParams(); 15 | const { id: productId } = params; 16 | const formRef = useRef(null); 17 | const [product, setProduct] = useState({}); 18 | const [success, setSuccess] = useState(false); 19 | const [errorMessage, setErrorMessage] = useState(''); 20 | 21 | useEffect(() => { 22 | const fecthData = async () => { 23 | try { 24 | const { data: res } = await API.get(`/products/${productId}`); 25 | setProduct(res.product); 26 | } catch (error) { 27 | console.log(error.message); 28 | toast.dismiss(); 29 | toast.error('Error Fetching Products'); 30 | } 31 | }; 32 | fecthData(); 33 | }, [productId]); 34 | 35 | const submitHandler = async (e) => { 36 | e.preventDefault(); 37 | 38 | let name = formRef.current.name.value.trim(), 39 | description = formRef.current.description.value.trim(), 40 | price = formRef.current.price.value.trim(), 41 | countInStock = formRef.current.countInStock.value.trim(), 42 | category = formRef.current.category.value.trim(), 43 | // image = formRef.current.image.value.trim(), 44 | brand = formRef.current.brand.value.trim(); 45 | 46 | setErrorMessage(''); 47 | if (!name || !description || !price || !countInStock || !category || !brand) 48 | return setErrorMessage('Please Enter All Fields'); 49 | setSuccess(false); 50 | try { 51 | const { data: res } = await API.put(`/products/${product._id}`, { 52 | name, 53 | description, 54 | price, 55 | countInStock, 56 | category, 57 | // image, 58 | brand, 59 | }); 60 | toast.dismiss(); 61 | toast.success('Product Edited Succefully'); 62 | setSuccess(true); 63 | } catch (error) { 64 | toast.dismiss(); 65 | toast.error('Error while editing product'); 66 | console.log(error); 67 | } 68 | }; 69 | return ( 70 | 71 | 72 | 73 |

Edit Product

74 |

{product._id}

75 | 76 |
77 | 78 | Name 79 | 85 | 86 | 87 | 88 | Description 89 | 95 | 96 | 97 | 98 | Price 99 | 105 | 106 | 107 | 108 | count in stock 109 | 115 | 116 | 117 | 118 | Category 119 | 125 | 126 | 127 | 128 | Brand 129 | 135 | 136 | {errorMessage && {errorMessage}} 137 | {success && ( 138 | Product updated successfully 139 | )} 140 | 141 |
142 | 143 |
144 |
145 | 146 |
147 |
148 | ); 149 | }; 150 | 151 | export default AdminUserEdit; 152 | -------------------------------------------------------------------------------- /Client/src/pages/AdminProducts.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Table from 'react-bootstrap/Table'; 3 | import Button from 'react-bootstrap/Button'; 4 | import Stack from 'react-bootstrap/Stack'; 5 | import Alert from 'react-bootstrap/Alert'; 6 | import { useNavigate, Link } from 'react-router-dom'; 7 | import { useEffect, useState } from 'react'; 8 | import API from '../api/api.js'; 9 | import { CircularProgress } from '@mui/material'; 10 | import { useSelector } from 'react-redux'; 11 | import AddProductModal from '../components/AddProductModal.jsx'; 12 | 13 | const AdminProducts = () => { 14 | const { currentUser } = useSelector((state) => state.user); 15 | const [show, setShow] = useState(false); 16 | const navigate = useNavigate(); 17 | 18 | const [products, setProducts] = useState({ 19 | items: [], 20 | loading: false, 21 | error: false, 22 | }); 23 | useEffect(() => { 24 | if (!currentUser.isAdmin) navigate('/'); 25 | 26 | const fecthData = async () => { 27 | setProducts((prev) => ({ ...prev, loading: true })); 28 | try { 29 | const { data: res } = await API.get('/products'); 30 | setProducts({ loading: false, error: false, items: res.products }); 31 | } catch (error) { 32 | setProducts((prev) => ({ ...prev, loading: false, error: true })); 33 | 34 | console.log(error.message); 35 | } 36 | }; 37 | currentUser && fecthData(); 38 | }, [currentUser, navigate]); 39 | 40 | const addProduct = (product) => { 41 | let temp = products.items; 42 | temp.push(product); 43 | setProducts({ 44 | loading: false, 45 | error: false, 46 | items: temp, 47 | }); 48 | }; 49 | const handleDeletProduct = async (id) => { 50 | try { 51 | await API.delete(`/products/${id}`); 52 | const filterd = products.items.filter((e) => e._id !== id); 53 | setProducts({ 54 | loading: false, 55 | error: false, 56 | items: filterd, 57 | }); 58 | } catch (error) { 59 | console.log(error.message); 60 | } 61 | }; 62 | 63 | return ( 64 | 65 | {!currentUser ? ( 66 | 67 | You Are not logged in Sign In 68 | 69 | ) : ( 70 | <> 71 | {show && ( 72 | setShow(false)} 75 | addProduct={addProduct} 76 | /> 77 | )} 78 | 79 |

Products

80 | 83 |
84 | {products.loading ? ( 85 | 86 | ) : products.error ? ( 87 | Error While fetching products 88 | ) : ( 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | {products.items.map((p, idx) => ( 102 | 103 | 104 | 105 | 108 | 109 | 110 | 128 | 129 | ))} 130 | 131 |
IDName PRICECATEGORYBRANDACTIONS
{p._id}{p.name} 106 | ${p.price} 107 | {p.category[0]}{p.brand} 111 | 120 | 127 |
132 | )} 133 | 134 | )} 135 |
136 | ); 137 | }; 138 | 139 | export default AdminProducts; 140 | -------------------------------------------------------------------------------- /Client/src/pages/AdminUserEdit.jsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useReducer, useState } from 'react'; 2 | import Form from 'react-bootstrap/Form'; 3 | import Alert from 'react-bootstrap/Alert'; 4 | import Row from 'react-bootstrap/Row'; 5 | import Col from 'react-bootstrap/Col'; 6 | import Button from 'react-bootstrap/Button'; 7 | import Container from 'react-bootstrap/Container'; 8 | import { useNavigate, useParams } from 'react-router-dom'; 9 | import API from '../api/api.js'; 10 | import { toast } from 'react-toastify'; 11 | 12 | const AdminUserEdit = () => { 13 | const params = useParams(); 14 | const { id: userId } = params; 15 | const [user, setUser] = useState({}); 16 | const [success, setSuccess] = useState(false); 17 | 18 | useEffect(() => { 19 | const fecthData = async () => { 20 | try { 21 | const { data: res } = await API.get(`/users/${userId}`); 22 | setUser(res.user); 23 | } catch (error) { 24 | toast.dismiss(); 25 | toast.error('Error While Fetchig users'); 26 | console.log(error.message); 27 | } 28 | }; 29 | fecthData(); 30 | }, [userId]); 31 | const submitHandler = async (e) => { 32 | e.preventDefault(); 33 | 34 | let name = e.target.name.value.trim(), 35 | email = e.target.email.value.trim(), 36 | isAdmin = e.target.isAdmin.checked; 37 | 38 | setSuccess(false); 39 | try { 40 | const { data: res } = await API.put(`/users/${user._id}`, { 41 | name, 42 | email, 43 | isAdmin, 44 | }); 45 | setSuccess(true); 46 | } catch (error) { 47 | toast.dismiss(); 48 | toast.error('Error While editin user'); 49 | console.log(error); 50 | } 51 | }; 52 | return ( 53 | 54 | 55 | 56 |

Edit User

57 |

{user._id}

58 | 59 |
60 | 61 | Name 62 | 68 | 69 | 70 | Email 71 | 77 | 78 | 79 | 86 | {success && ( 87 | user updated successfully 88 | )} 89 |
90 | 91 |
92 | 93 | 94 |
95 |
96 | ); 97 | }; 98 | 99 | export default AdminUserEdit; 100 | -------------------------------------------------------------------------------- /Client/src/pages/AdminUsers.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Table from 'react-bootstrap/Table'; 3 | import Button from 'react-bootstrap/Button'; 4 | import Alert from 'react-bootstrap/Alert'; 5 | import Badge from 'react-bootstrap/Badge'; 6 | import { useNavigate, Link } from 'react-router-dom'; 7 | import { useEffect, useState } from 'react'; 8 | import API from '../api/api.js'; 9 | import { CircularProgress } from '@mui/material'; 10 | import { useSelector } from 'react-redux'; 11 | import { toast } from 'react-toastify'; 12 | 13 | const AdminOrders = () => { 14 | const { currentUser } = useSelector((state) => state.user); 15 | const navigate = useNavigate(); 16 | 17 | const [users, setUsers] = useState({ 18 | items: [], 19 | loading: false, 20 | error: false, 21 | }); 22 | useEffect(() => { 23 | if (!currentUser.isAdmin) navigate('/'); 24 | 25 | const fecthData = async () => { 26 | setUsers((prev) => ({ ...prev, loading: true })); 27 | try { 28 | const { data: res } = await API.get('/users'); 29 | setUsers({ loading: false, error: false, items: res.users }); 30 | } catch (error) { 31 | setUsers((prev) => ({ ...prev, loading: false, error: true })); 32 | console.log(error.message); 33 | toast.dismiss(); 34 | toast.error('Error While Fetchig users'); 35 | } 36 | }; 37 | currentUser && fecthData(); 38 | }, [currentUser, navigate]); 39 | 40 | const handleDeletUser = async (id) => { 41 | try { 42 | await API.delete(`/users/${id}`); 43 | const filterd = users.items.filter((e) => e._id !== id); 44 | setUsers({ 45 | loading: false, 46 | error: false, 47 | items: filterd, 48 | }); 49 | toast.dismiss(); 50 | toast.success('user deleted Successfully'); 51 | } catch (error) { 52 | toast.dismiss(); 53 | toast.error('Error While deleting user'); 54 | console.log(error.message); 55 | } 56 | }; 57 | return ( 58 | 59 | {!currentUser ? ( 60 | 61 | You Are not logged in Sign In 62 | 63 | ) : ( 64 | <> 65 |

Users

66 | 67 | {users.loading ? ( 68 | 69 | ) : users.error ? ( 70 | Error While fetching users 71 | ) : ( 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | {users.items.map((p, idx) => ( 84 | 85 | 86 | 87 | 88 | 95 | 113 | 114 | ))} 115 | 116 |
IDName EmailIS ADMINACTIONS
{p._id}{p.name}{p.email} 89 | {p.isAdmin ? ( 90 | Yes 91 | ) : ( 92 | No 93 | )} 94 | 96 | 105 | 112 |
117 | )} 118 | 119 | )} 120 |
121 | ); 122 | }; 123 | 124 | export default AdminOrders; 125 | -------------------------------------------------------------------------------- /Client/src/pages/Cart.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Row from 'react-bootstrap/Row'; 3 | import Col from 'react-bootstrap/Col'; 4 | import Alert from 'react-bootstrap/Alert'; 5 | import ListGroup from 'react-bootstrap/ListGroup'; 6 | import Button from 'react-bootstrap/Button'; 7 | import Image from 'react-bootstrap/Image'; 8 | import { Link, useNavigate } from 'react-router-dom'; 9 | import { useEffect, useState } from 'react'; 10 | import AddBoxIcon from '@mui/icons-material/AddBox'; 11 | import RemoveIcon from '@mui/icons-material/Remove'; 12 | import DeleteIcon from '@mui/icons-material/Delete'; 13 | import API from '../api/api.js'; 14 | import { useDispatch, useSelector } from 'react-redux'; 15 | import { setCartItems } from '../redux/cartSlice.js'; 16 | import { toast } from 'react-toastify'; 17 | 18 | const deepClone = (obj) => JSON.parse(JSON.stringify(obj)); 19 | const Cart = () => { 20 | // const [cartItems, setCartItems] = useState([]); 21 | const dispatch = useDispatch(); 22 | const navigate = useNavigate(); 23 | const { currentUser } = useSelector((state) => state.user); 24 | const { 25 | cartCount, 26 | cart: { items: cartItems }, 27 | } = useSelector((state) => state.cart); 28 | 29 | useEffect(() => { 30 | const fecthCart = async () => { 31 | try { 32 | const { data: res } = await API.get(`/cart`); 33 | if (!res.cart) return; 34 | dispatch(setCartItems(res.cart)); 35 | console.log(res.cart); 36 | } catch (error) { 37 | console.log(error.message); 38 | toast.dismiss(); 39 | toast.error('Error While Fetchig cart'); 40 | } 41 | }; 42 | currentUser && fecthCart(); 43 | }, [dispatch, currentUser]); 44 | 45 | const handleChangeQuantity = async (idx, num, productId) => { 46 | let temp = deepClone(cartItems); 47 | 48 | temp[idx].quantity += num; 49 | if (temp[idx].quantity === 0) { 50 | temp.splice(idx, 1); 51 | } 52 | try { 53 | const item = { quantity: num, product: productId }; 54 | const { data: res } = await API.post('/cart', { 55 | items: [item], 56 | }); 57 | dispatch(setCartItems(res.cart)); 58 | // dispatch(setCartItems({ items: deepClone(temp) })); 59 | console.log(res.cart); 60 | } catch (error) { 61 | console.log(error); 62 | } 63 | 64 | dispatch(setCartItems({ items: deepClone(temp) })); 65 | }; 66 | const handleDeleteItem = async (idx, productId) => { 67 | let temp = deepClone(cartItems); 68 | temp.splice(idx, 1); 69 | try { 70 | const { data: res } = await API.patch('/cart', { 71 | productId, 72 | }); 73 | dispatch(setCartItems({ items: deepClone(temp) })); 74 | console.log(res.cart); 75 | } catch (error) { 76 | console.log(error); 77 | } 78 | }; 79 | 80 | return ( 81 | 82 | {!currentUser ? ( 83 | 84 | No Products in your Cart Sign In and add 85 | products to your Cart 86 | 87 | ) : ( 88 | <> 89 |

Shopping Cart

90 | 91 | 92 | {cartCount > 0 ? ( 93 | 94 | {cartItems.map((p, idx) => ( 95 | 96 | 97 | 98 | {p && p.product && ( 99 | 108 | )} 109 | 110 | {p.product.name} 111 | 112 | 113 | 114 | 115 | 124 | {p.quantity} 125 | 135 | 136 | 137 | 138 | ${p.product.price} 139 | 140 | handleDeleteItem(idx, p.product._id)} 144 | > 145 | 146 | 147 | 148 | 149 | ))} 150 | 151 | ) : ( 152 | 153 | Cart is empty. Go Shopping 154 | 155 | )} 156 | 157 | 158 | 159 | 160 |

161 | Subtotal ({cartCount} items) :{' '} 162 | {cartItems && 163 | cartItems.reduce( 164 | (acc, item) => acc + item.product.price * item.quantity, 165 | 0, 166 | )} 167 |

168 |
169 | 170 | 178 | 179 |
180 | 181 |
182 | 183 | )} 184 |
185 | ); 186 | }; 187 | 188 | export default Cart; 189 | -------------------------------------------------------------------------------- /Client/src/pages/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import Chart from 'react-google-charts'; 3 | import Row from 'react-bootstrap/Row'; 4 | import Alert from 'react-bootstrap/Alert'; 5 | import Container from 'react-bootstrap/Container'; 6 | import Col from 'react-bootstrap/Col'; 7 | import Card from 'react-bootstrap/Card'; 8 | import API from '../api/api.js'; 9 | import { toast } from 'react-toastify'; 10 | import { useSelector } from 'react-redux'; 11 | 12 | import { CircularProgress } from '@mui/material'; 13 | 14 | const Dashboard = () => { 15 | const [dashboardData, setDashboardData] = useState({ 16 | loading: false, 17 | error: false, 18 | summary: {}, 19 | }); 20 | 21 | useEffect(() => { 22 | const fetchData = async () => { 23 | try { 24 | const { data: res } = await API.get('/orders/dashboard'); 25 | setDashboardData({ 26 | loading: false, 27 | summary: res, 28 | }); 29 | } catch (err) { 30 | toast.dismiss(); 31 | toast.error('Error While Fetchig dashboard'); 32 | setDashboardData({ 33 | loading: false, 34 | error: true, 35 | }); 36 | } 37 | }; 38 | fetchData(); 39 | }, []); 40 | 41 | return ( 42 | 43 |

Dashboard

44 | {dashboardData.loading ? ( 45 | 46 | ) : dashboardData.error ? ( 47 | Error while fetching Data 48 | ) : ( 49 | <> 50 | 51 | 52 | 53 | 54 | 55 | {dashboardData.summary && 56 | dashboardData.summary.users && 57 | dashboardData.summary.users[0] 58 | ? dashboardData.summary.users[0].numUsers 59 | : 0} 60 | 61 | Users 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | {dashboardData.summary.orders && 70 | dashboardData.summary.orders[0] 71 | ? dashboardData.summary.orders[0].numOrders 72 | : 0} 73 | 74 | Orders 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | $ 83 | {dashboardData.summary.orders && 84 | dashboardData.summary.orders[0] 85 | ? dashboardData.summary.orders[0].totalSales.toFixed(2) 86 | : 0} 87 | 88 | Orders 89 | 90 | 91 | 92 | 93 |
94 |

Sales

95 | {dashboardData.summary && 96 | dashboardData.summary.dailyOrders && 97 | dashboardData.summary.dailyOrders.length === 0 ? ( 98 | No Sale 99 | ) : ( 100 | dashboardData.summary && 101 | dashboardData.summary.dailyOrders && ( 102 | // <>asd 103 | Loading Chart...
} 108 | data={[ 109 | ['Date', 'Sales'], 110 | ...dashboardData.summary.dailyOrders.map((x) => [ 111 | x._id, 112 | x.sales, 113 | ]), 114 | ]} 115 | /> 116 | ) 117 | )} 118 | 119 |
120 |

Categories

121 | {dashboardData.summary && 122 | dashboardData.summary.productCategories && 123 | dashboardData.summary.productCategories.length === 0 ? ( 124 | No Category 125 | ) : ( 126 | dashboardData.summary && 127 | dashboardData.summary.productCategories && ( 128 | Loading Chart...
} 133 | data={[ 134 | ['Category', 'Products'], 135 | ...dashboardData.summary.productCategories.map((x) => [ 136 | String(x._id), 137 | x.count, 138 | ]), 139 | ]} 140 | /> 141 | ) 142 | )} 143 | 144 | 145 | )} 146 |
147 | ); 148 | }; 149 | 150 | export default Dashboard; 151 | -------------------------------------------------------------------------------- /Client/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Row from 'react-bootstrap/Row'; 3 | import Col from 'react-bootstrap/Col'; 4 | import Alert from 'react-bootstrap/Alert'; 5 | import data from '../data/data.js'; 6 | import { Product } from '../components'; 7 | import { useEffect, useState } from 'react'; 8 | import API from '../api/api.js'; 9 | import CircularProgress from '@mui/material/CircularProgress'; 10 | import { toast } from 'react-toastify'; 11 | 12 | const Home = () => { 13 | const [products, setProducts] = useState({ 14 | items: [], 15 | loading: false, 16 | error: false, 17 | }); 18 | 19 | useEffect(() => { 20 | const fecthData = async () => { 21 | setProducts((prev) => ({ ...prev, loading: true })); 22 | try { 23 | const { data: res } = await API.get('/products'); 24 | setProducts({ loading: false, error: false, items: res.products }); 25 | } catch (error) { 26 | setProducts((prev) => ({ ...prev, loading: false, error: true })); 27 | toast.dismiss(); 28 | toast.error('Error While Fetchig produtct'); 29 | console.log(error.message); 30 | } 31 | }; 32 | fecthData(); 33 | }, []); 34 | return ( 35 | 36 |

Featured Products

37 | 38 | {products.loading ? ( 39 | 40 | ) : products.error ? ( 41 | Error While fetching products 42 | ) : ( 43 | products.items.map((p, idx) => ( 44 | 45 | 46 | 47 | )) 48 | )} 49 | 50 |
51 | ); 52 | }; 53 | 54 | export default Home; 55 | -------------------------------------------------------------------------------- /Client/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import Button from 'react-bootstrap/Button'; 2 | import Form from 'react-bootstrap/Form'; 3 | import Container from 'react-bootstrap/Container'; 4 | import Row from 'react-bootstrap/Row'; 5 | import Alert from 'react-bootstrap/Alert'; 6 | import Col from 'react-bootstrap/Col'; 7 | import { Link, useNavigate } from 'react-router-dom'; 8 | import { useEffect, useRef, useState } from 'react'; 9 | import API from '../api/api.js'; 10 | import { useDispatch, useSelector } from 'react-redux'; 11 | import { loginStart, loginSuccess, loginFailure } from '../redux/userSlice.js'; 12 | 13 | function Login() { 14 | const formRef = useRef(null); 15 | const navigate = useNavigate(); 16 | const dispatch = useDispatch(); 17 | const [errorMessage, setErrorMessage] = useState(''); 18 | const { currentUser } = useSelector((state) => state.user); 19 | 20 | useEffect(() => { 21 | currentUser && navigate('/'); 22 | }, [currentUser, navigate]); 23 | const handleSubmit = async (e) => { 24 | e.preventDefault(); 25 | setErrorMessage(''); 26 | const email = formRef.current.email.value.trim(), 27 | password = formRef.current.password.value.trim(); 28 | if (!email || !password) { 29 | setErrorMessage('Please fill all fields'); 30 | return; 31 | } 32 | 33 | dispatch(loginStart()); 34 | try { 35 | const { data: res } = await API.post('/auth/login', { 36 | email, 37 | password, 38 | }); 39 | formRef.current.reset(); 40 | localStorage.setItem('access-token', res.token); 41 | 42 | dispatch(loginSuccess(res.user)); 43 | navigate('/'); 44 | } catch (error) { 45 | dispatch(loginFailure('Invalid email or password')); 46 | const status = error.response.status; 47 | 48 | if (status === 401) setErrorMessage('Invalid email or password'); 49 | else if (status === 404) setErrorMessage('User not found'); 50 | } 51 | }; 52 | return ( 53 | 54 | 55 | 56 |

Log in

57 |
58 | 59 | Email address 60 | 65 | 66 | 67 | 68 | Password 69 | 74 | 75 | {errorMessage && {errorMessage}} 76 | 77 | 80 | 81 |

82 | New customer? 83 | 84 | Create your account 85 | 86 |

87 |
88 | 89 |
90 |
91 | ); 92 | } 93 | 94 | export default Login; 95 | -------------------------------------------------------------------------------- /Client/src/pages/Map.jsx: -------------------------------------------------------------------------------- 1 | // import { useContext, useEffect, useRef, useState } from 'react'; 2 | // import { 3 | // LoadScript, 4 | // GoogleMap, 5 | // StandaloneSearchBox, 6 | // Marker, 7 | // } from '@react-google-maps/api'; 8 | // import { useNavigate } from 'react-router-dom'; 9 | // import Button from 'react-bootstrap/Button'; 10 | // import { toast } from 'react-toastify'; 11 | // import { useSelector } from 'react-redux'; 12 | 13 | // const defaultLocation = { lat: 45.516, lng: -73.56 }; 14 | // const libs = ['places']; 15 | // const googleApiKey = process.env.REACT_APP_GOOGLE_API_KEY; 16 | 17 | // export default function MapScreen() { 18 | // const { currentUser } = useSelector((state) => state.user); 19 | // const navigate = useNavigate(); 20 | // const [center, setCenter] = useState(defaultLocation); 21 | // const [location, setLocation] = useState(center); 22 | 23 | // const mapRef = useRef(null); 24 | // const placeRef = useRef(null); 25 | // const markerRef = useRef(null); 26 | 27 | // const getUserCurrentLocation = () => { 28 | // if (!navigator.geolocation) { 29 | // alert('Geolocation os not supported by this browser'); 30 | // } else { 31 | // navigator.geolocation.getCurrentPosition((position) => { 32 | // setCenter({ 33 | // lat: position.coords.latitude, 34 | // lng: position.coords.longitude, 35 | // }); 36 | // setLocation({ 37 | // lat: position.coords.latitude, 38 | // lng: position.coords.longitude, 39 | // }); 40 | // }); 41 | // } 42 | // }; 43 | // useEffect(() => { 44 | // getUserCurrentLocation(); 45 | // }, []); 46 | 47 | // const onLoad = (map) => { 48 | // mapRef.current = map; 49 | // }; 50 | // const onIdle = () => { 51 | // setLocation({ 52 | // lat: mapRef.current.center.lat(), 53 | // lng: mapRef.current.center.lng(), 54 | // }); 55 | // }; 56 | 57 | // const onLoadPlaces = (place) => { 58 | // placeRef.current = place; 59 | // }; 60 | // const onPlacesChanged = () => { 61 | // const place = placeRef.current.getPlaces()[0].geometry.location; 62 | // setCenter({ lat: place.lat(), lng: place.lng() }); 63 | // setLocation({ lat: place.lat(), lng: place.lng() }); 64 | // }; 65 | 66 | // const onMarkerLoad = (marker) => { 67 | // markerRef.current = marker; 68 | // }; 69 | 70 | // const onConfirm = () => { 71 | // const places = placeRef.current.getPlaces() || [{}]; 72 | // // ctxDispatch({ 73 | // // type: 'SAVE_SHIPPING_ADDRESS_MAP_LOCATION', 74 | // // payload: { 75 | // // lat: location.lat, 76 | // // lng: location.lng, 77 | // // address: places[0].formatted_address, 78 | // // name: places[0].name, 79 | // // vicinity: places[0].vicinity, 80 | // // googleAddressId: places[0].id, 81 | // // }, 82 | // // }); 83 | // toast.success('location selected successfully.'); 84 | // navigate('/shipping'); 85 | // }; 86 | // return ( 87 | //
88 | // 89 | // 97 | // 101 | //
102 | // 103 | // 106 | //
107 | //
108 | // 109 | //
110 | //
111 | //
112 | // ); 113 | // } 114 | -------------------------------------------------------------------------------- /Client/src/pages/Order.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Button from 'react-bootstrap/Button'; 3 | import Row from 'react-bootstrap/Row'; 4 | import Col from 'react-bootstrap/Col'; 5 | import Alert from 'react-bootstrap/Alert'; 6 | import Card from 'react-bootstrap/Card'; 7 | import ListGroup from 'react-bootstrap/ListGroup'; 8 | import Image from 'react-bootstrap/Image'; 9 | import { Link, Navigate, useNavigate, useParams } from 'react-router-dom'; 10 | import { useEffect, useState } from 'react'; 11 | import { useSelector } from 'react-redux'; 12 | import API from '../api/api.js'; 13 | import moment from 'moment'; 14 | import PaypalCheckoutButton from '../components/PaypalCheckoutButton.jsx'; 15 | import { toast } from 'react-toastify'; 16 | import StripeCheckoutButton from '../components/StripeCheckoutButton.jsx'; 17 | 18 | const Order = () => { 19 | const params = useParams(); 20 | const navigate = useNavigate(); 21 | const { id: orderId } = params; 22 | const { currentUser } = useSelector((state) => state.user); 23 | const [orderDetails, setOrderDetails] = useState({}); 24 | 25 | useEffect(() => { 26 | const fecthData = async () => { 27 | try { 28 | const { data: res } = await API.get(`/orders/${orderId}`); 29 | setOrderDetails(res.order); 30 | 31 | const itemsPrice = res.order.items.reduce( 32 | (acc, item) => acc + item.product.price * item.quantity, 33 | 0, 34 | ); 35 | const shipping = 0.05 * itemsPrice; 36 | const tax = Math.floor(0.14 * itemsPrice); 37 | const totalPrice = shipping + tax + itemsPrice; 38 | 39 | setOrderDetails((prev) => ({ 40 | ...prev, 41 | itemsPrice, 42 | shipping, 43 | tax, 44 | totalPrice, 45 | })); 46 | // console.log(res.order); 47 | } catch (error) { 48 | toast.dismiss(); 49 | toast.error('Error While Fetchig order'); 50 | console.log(error.message); 51 | } 52 | }; 53 | currentUser && fecthData(); 54 | }, [orderId, currentUser]); 55 | 56 | const handleDeliver = async (e) => { 57 | try { 58 | const { data: res } = await API.put(`/orders/${orderId}`, { 59 | isDelivered: true, 60 | DeliveredAt: Date.now(), 61 | }); 62 | 63 | setOrderDetails((prev) => ({ 64 | ...prev, 65 | isDelivered: true, 66 | DeliveredAt: res.order.DeliveredAt, 67 | })); 68 | } catch (error) { 69 | toast.dismiss(); 70 | toast.error('Error Deleivering Order'); 71 | console.log(error.message); 72 | } 73 | }; 74 | return ( 75 | 76 | {!currentUser ? ( 77 | 78 | You Are not logged in Sign In To see your 79 | orders 80 | 81 | ) : ( 82 | <> 83 |

Order {orderDetails._id}

84 | 85 | 86 | 87 | Shipping 88 | 89 |

90 | Name : {currentUser.name} 91 |

92 |

93 | Address : {orderDetails.address} 94 |

95 | {orderDetails.isDelivered ? ( 96 | 97 | Deliverd at{' '} 98 | {moment(orderDetails.DeliveredAt).format('MM/DD/YYYY')} 99 | 100 | ) : ( 101 | Not Delivered Yet 102 | )} 103 |
104 |
105 | 106 | 107 | Payment 108 | 109 |

110 | Method : {orderDetails.paymentMethod} 111 |

112 | {orderDetails.isPaid ? ( 113 | 114 | Paid at {moment(orderDetails.paidAt).format('MM/DD/YYYY')} 115 | 116 | ) : ( 117 | Not Paid Yet 118 | )} 119 |
120 |
121 | 122 | 123 | Items 124 | 125 | 126 | {orderDetails && 127 | orderDetails.items && 128 | orderDetails.items.map((p, idx) => ( 129 | 130 | 131 | 132 | {p && p.product && ( 133 | 142 | )} 143 | 147 | {p.product.name} 148 | 149 | 150 | 151 | 152 | {p.quantity} 153 | 154 | 155 | 156 | $ {p.product.price} 157 | 158 | 159 | 160 | ))} 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | Order Summary 169 | 170 | 171 | 172 | 173 | items 174 | 175 | 176 | ${orderDetails && orderDetails.itemsPrice} 177 | 178 | 179 | 180 | 181 | 182 | 183 | shipping 184 | 185 | 186 | ${orderDetails && orderDetails.shipping} 187 | 188 | 189 | 190 | 191 | 192 | 193 | Tax 194 | 195 | ${orderDetails && orderDetails.tax} 196 | 197 | 198 | 199 | 200 | 201 | Order Total 202 | 203 | 204 | ${orderDetails && orderDetails.totalPrice} 205 | 206 | 207 | 208 | 209 | {orderDetails.isPaid || 210 | (currentUser.isAdmin && 211 | currentUser._id !== orderDetails.userId) ? ( 212 | <> 213 | ) : orderDetails.paymentMethod === 'paypal' ? ( 214 | 215 | 221 | 222 | ) : ( 223 | 224 | 229 | 230 | )} 231 | {orderDetails.isPaid && 232 | !orderDetails.isDelivered && 233 | currentUser.isAdmin && ( 234 | 235 | 243 | 244 | )} 245 | 246 | 247 | 248 | 249 |
250 | 251 | )} 252 |
253 | ); 254 | }; 255 | 256 | export default Order; 257 | -------------------------------------------------------------------------------- /Client/src/pages/Orders.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Table from 'react-bootstrap/Table'; 3 | import Button from 'react-bootstrap/Button'; 4 | import Alert from 'react-bootstrap/Alert'; 5 | import Badge from 'react-bootstrap/Badge'; 6 | import { Link } from 'react-router-dom'; 7 | import { useEffect, useState } from 'react'; 8 | import API from '../api/api.js'; 9 | import { CircularProgress } from '@mui/material'; 10 | import moment from 'moment'; 11 | import { useSelector } from 'react-redux'; 12 | import { toast } from 'react-toastify'; 13 | 14 | const Order = () => { 15 | const { currentUser } = useSelector((state) => state.user); 16 | const [orders, setOrders] = useState({ 17 | items: [], 18 | loading: false, 19 | error: false, 20 | }); 21 | useEffect(() => { 22 | const fecthData = async () => { 23 | setOrders((prev) => ({ ...prev, loading: true })); 24 | try { 25 | const { data: res } = await API.get('/orders/mine'); 26 | setOrders({ loading: false, error: false, items: res.orders }); 27 | } catch (error) { 28 | setOrders((prev) => ({ ...prev, loading: false, error: true })); 29 | toast.dismiss(); 30 | toast.error('Error While Fetchig orders'); 31 | console.log(error.message); 32 | } 33 | }; 34 | currentUser && fecthData(); 35 | }, []); 36 | return ( 37 | 38 | {!currentUser ? ( 39 | 40 | You Are not logged in Sign In To see your 41 | orders 42 | 43 | ) : ( 44 | <> 45 |

Order History

46 | 47 | {orders.loading ? ( 48 | 49 | ) : orders.error ? ( 50 | Error While fetching orders 51 | ) : ( 52 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {orders.items.map((p, idx) => ( 69 | 70 | 71 | 72 | 75 | 82 | 89 | 95 | 96 | ))} 97 | 98 |
IDDATETOTALPAIDDELIVEREDACTIONS
{p._id}{moment(p.createdAt).format('MM/DD/YYYY')} 73 | ${p.totalPrice} 74 | 76 | {p.isPaid ? ( 77 | Yes 78 | ) : ( 79 | No 80 | )} 81 | 83 | {p.isDelivered ? ( 84 | Yes 85 | ) : ( 86 | No 87 | )} 88 | 90 | 93 | {/* Details */} 94 |
99 | )} 100 | 101 | )} 102 |
103 | ); 104 | }; 105 | 106 | export default Order; 107 | -------------------------------------------------------------------------------- /Client/src/pages/Payment.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Alert from 'react-bootstrap/Alert'; 3 | import Row from 'react-bootstrap/Row'; 4 | import Col from 'react-bootstrap/Col'; 5 | import Button from 'react-bootstrap/Button'; 6 | import FloatingLabel from 'react-bootstrap/FloatingLabel'; 7 | import Form from 'react-bootstrap/Form'; 8 | import FormCheck from 'react-bootstrap/FormCheck'; 9 | import { CheckoutSteps } from '../components'; 10 | import { useDispatch, useSelector } from 'react-redux'; 11 | import { Link, useNavigate } from 'react-router-dom'; 12 | import { setPaymentMethod } from '../redux/cartSlice.js'; 13 | 14 | const Payment = () => { 15 | const { paymentMethod } = useSelector((state) => state.cart); 16 | const { currentUser } = useSelector((state) => state.user); 17 | const navigate = useNavigate(); 18 | const dispatch = useDispatch(); 19 | 20 | const handleSubmitPayment = (e) => { 21 | e.preventDefault(); 22 | const method = e.target.group1.value; 23 | 24 | dispatch(setPaymentMethod(method)); 25 | navigate('/placeorder'); 26 | }; 27 | return ( 28 | 29 | {!currentUser ? ( 30 | 31 | You Are not logged in Sign In 32 | 33 | ) : ( 34 | <> 35 | 36 | 37 | 38 |

Shipping Address

39 |
40 |
41 | 48 |
49 |
50 | 57 |
58 | 59 | 60 |
61 | 62 |
63 | 64 | )} 65 |
66 | ); 67 | }; 68 | 69 | export default Payment; 70 | -------------------------------------------------------------------------------- /Client/src/pages/PlaceOrder.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Alert from 'react-bootstrap/Alert'; 3 | import Row from 'react-bootstrap/Row'; 4 | import Col from 'react-bootstrap/Col'; 5 | import Card from 'react-bootstrap/Card'; 6 | import ListGroup from 'react-bootstrap/ListGroup'; 7 | import Button from 'react-bootstrap/Button'; 8 | import Image from 'react-bootstrap/Image'; 9 | import { Link, useNavigate } from 'react-router-dom'; 10 | import { useEffect, useState } from 'react'; 11 | import CheckoutSteps from '../components/CheckoutSteps.jsx'; 12 | import { useDispatch, useSelector } from 'react-redux'; 13 | import API from '../api/api.js'; 14 | import { clearCart } from '../redux/cartSlice.js'; 15 | import { toast } from 'react-toastify'; 16 | 17 | const PlaceOrder = () => { 18 | const [orderDetails, setOrderDetails] = useState({}); 19 | const navigate = useNavigate(); 20 | const dispatch = useDispatch(); 21 | useEffect(() => { 22 | const itemsPrice = cartItems.reduce( 23 | (acc, item) => acc + item.product.price * item.quantity, 24 | 0, 25 | ); 26 | const shipping = 0.05 * itemsPrice; 27 | const tax = Math.floor(0.14 * itemsPrice); 28 | const totalPrice = shipping + tax + itemsPrice; 29 | 30 | setOrderDetails({ 31 | itemsPrice, 32 | shipping, 33 | tax, 34 | totalPrice, 35 | }); 36 | }, []); 37 | 38 | const { currentUser } = useSelector((state) => state.user); 39 | const { 40 | address, 41 | paymentMethod, 42 | cart: { items: cartItems }, 43 | } = useSelector((state) => state.cart); 44 | 45 | const handleCheckout = async (e) => { 46 | try { 47 | const sentAddress = 48 | address.country + ' , ' + address.city + ', ' + address.address; 49 | const { data: res } = await API.post('/cart/checkout', { 50 | address: sentAddress, 51 | paymentMethod, 52 | }); 53 | console.log(res); 54 | dispatch(clearCart()); 55 | navigate(`/orders/${res.order._id}`); 56 | } catch (error) { 57 | toast.dismiss(); 58 | toast.error('Error While Fetchig users'); 59 | console.log(error); 60 | } 61 | }; 62 | return ( 63 | 64 | {!currentUser ? ( 65 | 66 | You Are not logged in Sign In 67 | 68 | ) : ( 69 | <> 70 | 71 |

Preview Order

72 | 73 | 74 | 75 | Shipping 76 | 77 |

78 | Name : {currentUser.name} 79 |

80 |

81 | Address : 82 | {address.country + 83 | ' , ' + 84 | address.city + 85 | ', ' + 86 | address.address} 87 |

88 | 93 | Edit 94 | 95 |
96 |
97 | 98 | 99 | Payment 100 | 101 |

102 | Method : {paymentMethod} 103 |

104 | 109 | Edit 110 | 111 |
112 |
113 | 114 | 115 | Items 116 | 117 | 118 | {cartItems && 119 | cartItems.map((p, idx) => ( 120 | 121 | 122 | 123 | {p && p.product && ( 124 | 133 | )} 134 | 138 | {p.product.name} 139 | 140 | 141 | 142 | 143 | {p.quantity} 144 | 145 | 146 | 147 | $ {p.product.price} 148 | 149 | 150 | 151 | ))} 152 | 153 | 154 | 159 | Edit 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | Order Summary 168 | 169 | 170 | 171 | 172 | items 173 | 174 | ${orderDetails.itemsPrice} 175 | 176 | 177 | 178 | 179 | 180 | shipping 181 | 182 | ${orderDetails.shipping} 183 | 184 | 185 | 186 | 187 | 188 | Tax 189 | 190 | ${orderDetails.tax} 191 | 192 | 193 | 194 | 195 | 196 | Order Total 197 | 198 | {orderDetails.totalPrice} 199 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 209 | 210 | 211 |
212 | 213 | )} 214 |
215 | ); 216 | }; 217 | 218 | export default PlaceOrder; 219 | -------------------------------------------------------------------------------- /Client/src/pages/Product.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Badge from 'react-bootstrap/Badge'; 3 | import Row from 'react-bootstrap/Row'; 4 | import Col from 'react-bootstrap/Col'; 5 | import ListGroup from 'react-bootstrap/ListGroup'; 6 | import Button from 'react-bootstrap/Button'; 7 | import Image from 'react-bootstrap/Image'; 8 | import Review from '../components/Review.jsx'; 9 | import { useParams, useNavigate } from 'react-router-dom'; 10 | import { useEffect, useState } from 'react'; 11 | import { useDispatch, useSelector } from 'react-redux'; 12 | import API from '../api/api.js'; 13 | import { setCartItems } from '../redux/cartSlice.js'; 14 | import Reviews from '../components/Reviews.jsx'; 15 | import { toast } from 'react-toastify'; 16 | 17 | const Product = () => { 18 | const params = useParams(); 19 | const { id: productId } = params; 20 | const dispatch = useDispatch(); 21 | const navigate = useNavigate(); 22 | 23 | const { currentUser } = useSelector((state) => state.user); 24 | const [proudct, setProudct] = useState({}); 25 | 26 | useEffect(() => { 27 | const fecthProduct = async () => { 28 | try { 29 | const { data: res } = await API.get(`/products/${productId}`); 30 | setProudct(res.product); 31 | } catch (error) { 32 | console.log(error.message); 33 | toast.dismiss(); 34 | toast.error('Error While Fetchig product'); 35 | } 36 | }; 37 | fecthProduct(); 38 | }, [productId]); 39 | 40 | const handleAddToCart = (e) => { 41 | !currentUser && navigate('/login'); 42 | const addToCartRequest = async () => { 43 | try { 44 | const item = { quantity: 1, product: productId }; 45 | const { data: res } = await API.post('/cart', { 46 | items: [item], 47 | }); 48 | dispatch(setCartItems(res.cart)); 49 | toast.dismiss(); 50 | toast.success('item added to cart succefully'); 51 | } catch (error) { 52 | console.log(error); 53 | toast.dismiss(); 54 | toast.error('Error While adding to cart'); 55 | } 56 | }; 57 | currentUser && addToCartRequest(); 58 | }; 59 | return ( 60 | 61 | {/* Product Details */} 62 | 63 | 64 | {proudct && proudct.image && ( 65 | 69 | )} 70 | 71 | 72 |

{proudct.name}

73 |
74 | 75 |
76 |

77 | ${proudct.price} 78 |

79 |
80 | {proudct && proudct.image && ( 81 | 86 | )} 87 |
88 | Description :

{proudct.description}

89 | 90 | 91 | 92 | 93 | 94 | Price : 95 | 96 | ${proudct.price} 97 | 98 | 99 | 100 | 101 | 102 | Status : 103 | 104 | {proudct.countInStock > 0 ? ( 105 | in Stock 106 | ) : ( 107 | out of stock 108 | )} 109 | 110 | 111 | 112 | 113 | {proudct.countInStock > 0 ? ( 114 | 117 | ) : ( 118 | 121 | )} 122 | 123 | 124 | 125 |
126 | 127 | {/* Reviews */} 128 | 129 |
130 | ); 131 | }; 132 | 133 | export default Product; 134 | -------------------------------------------------------------------------------- /Client/src/pages/Search.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Link, useNavigate, useLocation } from 'react-router-dom'; 3 | import Row from 'react-bootstrap/Row'; 4 | import Col from 'react-bootstrap/Col'; 5 | import Container from 'react-bootstrap/Container'; 6 | import Alert from 'react-bootstrap/Alert'; 7 | import { Review } from '../components'; 8 | import Button from 'react-bootstrap/Button'; 9 | import { Product } from '../components'; 10 | import API from '../api/api.js'; 11 | import { CircularProgress } from '@mui/material'; 12 | import CancelSharpIcon from '@mui/icons-material/CancelSharp'; 13 | import { prices, ratings, getSearchParams } from '../utils/searchUtils.js'; 14 | import { toast } from 'react-toastify'; 15 | 16 | const Search = () => { 17 | const navigate = useNavigate(); 18 | const { search } = useLocation(); 19 | const [categories, setCategories] = useState([]); 20 | const [numPages, setNumPages] = useState(0); 21 | const [products, setProducts] = useState({ 22 | items: [], 23 | loading: false, 24 | error: false, 25 | }); 26 | const { category, query, price, rating, order, page } = 27 | getSearchParams(search); 28 | 29 | useEffect(() => { 30 | const fecthProducts = async () => { 31 | setProducts((prev) => ({ ...prev, loading: true })); 32 | try { 33 | const { data: res } = await API.get( 34 | `/products/search?page=${page}&query=${query}&category=${category}&price=${price}&rating=${rating}&order=${order}`, 35 | ); 36 | setProducts({ loading: false, error: false, items: res.products }); 37 | setNumPages(res.pages); 38 | } catch (error) { 39 | toast.dismiss(); 40 | toast.error('Error While searching'); 41 | setProducts((prev) => ({ ...prev, loading: false, error: true })); 42 | console.log(error.message); 43 | } 44 | }; 45 | 46 | fecthProducts(); 47 | }, [page, query, category, price, rating, order]); 48 | 49 | useEffect(() => { 50 | const fetchCategories = async () => { 51 | try { 52 | const { data: res } = await API.get(`/products/categories`); 53 | setCategories(res.categories); 54 | } catch (err) { 55 | console.log(err); 56 | } 57 | }; 58 | fetchCategories(); 59 | }, []); 60 | 61 | const formatFilterUrl = (filter) => { 62 | const filterPage = filter.page || page; 63 | const filterCategory = filter.category || category; 64 | const filterQuery = filter.query || query; 65 | const filterRating = filter.rating || rating; 66 | const filterPrice = filter.price || price; 67 | const sortOrder = filter.order || order; 68 | return `/search?category=${filterCategory}&query=${filterQuery}&price=${filterPrice}&rating=${filterRating}&order=${sortOrder}&page=${filterPage}`; 69 | }; 70 | return ( 71 | 72 | 73 | 74 |

Categories

75 |
76 |
    77 |
  • 78 | 82 | Any 83 | 84 |
  • 85 | {categories.map((c, idx) => ( 86 |
  • 87 | 91 | {c} 92 | 93 |
  • 94 | ))} 95 |
96 |
97 |
98 |

Price

99 |
    100 |
  • 101 | 105 | Any 106 | 107 |
  • 108 | {prices.map((p, idx) => ( 109 |
  • 110 | 114 | {p.name} 115 | 116 |
  • 117 | ))} 118 |
119 |
120 |
121 |

Avg. Customer Review

122 |
    123 | {ratings.map((r, idx) => ( 124 |
  • 125 | 129 | 130 | & UP 131 | 132 |
  • 133 | ))} 134 |
135 |
136 | 137 | 138 | {products.loading ? ( 139 | 140 | ) : products.error ? ( 141 | Error While fetching products 142 | ) : ( 143 | <> 144 | 145 | 146 |
147 | 148 | {products.items.length === 0 149 | ? 'No' 150 | : products.items.length}{' '} 151 | Results 152 | 153 | {query !== 'all' && ' : ' + query} 154 | {category !== 'all' && ' : ' + category} 155 | {price !== 'all' && ' : Price ' + price} 156 | {rating !== 'all' && ' : Rating ' + rating + ' & up'} 157 | {query !== 'all' || 158 | category !== 'all' || 159 | rating !== 'all' || 160 | price !== 'all' ? ( 161 | 168 | ) : null} 169 |
170 | 171 | 172 | Sort by{' '} 173 | 184 | 185 |
186 | {products.items.length === 0 ? ( 187 | No Product Found 188 | ) : ( 189 | 190 | {products.items.map((p, idx) => ( 191 | 192 | 193 | 194 | ))} 195 | 196 | )} 197 | 198 |
199 | {numPages > 1 && 200 | [...Array(numPages).keys()].map((x) => ( 201 | 210 | ))} 211 |
212 | 213 | )} 214 | 215 |
216 |
217 | ); 218 | }; 219 | export default Search; 220 | -------------------------------------------------------------------------------- /Client/src/pages/Shipping.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Row from 'react-bootstrap/Row'; 3 | import Col from 'react-bootstrap/Col'; 4 | import Alert from 'react-bootstrap/Alert'; 5 | import Button from 'react-bootstrap/Button'; 6 | import FloatingLabel from 'react-bootstrap/FloatingLabel'; 7 | import Form from 'react-bootstrap/Form'; 8 | import { CheckoutSteps } from '../components'; 9 | import { useRef, useState } from 'react'; 10 | import { Link, useNavigate } from 'react-router-dom'; 11 | import { useDispatch, useSelector } from 'react-redux'; 12 | import { setAddress } from '../redux/cartSlice.js'; 13 | 14 | const Shipping = () => { 15 | const navigate = useNavigate(); 16 | const dispatch = useDispatch(); 17 | const [errorMessage, setErrorMessage] = useState(''); 18 | const formRef = useRef(null); 19 | const { address } = useSelector((state) => state.cart); 20 | const { currentUser } = useSelector((state) => state.user); 21 | 22 | const handleSubmitAddress = (e) => { 23 | e.preventDefault(); 24 | 25 | let address = formRef.current.address.value.trim(), 26 | city = formRef.current.city.value.trim(), 27 | postalCode = formRef.current.postalCode.value.trim(), 28 | country = formRef.current.country.value.trim(); 29 | 30 | setErrorMessage(''); 31 | if (!address || !city || !postalCode || !country) { 32 | setErrorMessage('missing field , Please Enter All Fields'); 33 | return; 34 | } 35 | 36 | dispatch( 37 | setAddress({ 38 | address, 39 | postalCode, 40 | city, 41 | country, 42 | }), 43 | ); 44 | navigate('/payment'); 45 | }; 46 | return ( 47 | 48 | {!currentUser ? ( 49 | 50 | You Are not logged in Sign In To see your 51 | orders 52 | 53 | ) : ( 54 | <> 55 | 56 | 57 | 58 |

Shipping Address

59 |
60 | 61 | 67 | 68 | 69 | 70 | 76 | 77 | 78 | 79 | 85 | 86 | 87 | 88 | 94 | 95 | 96 | {errorMessage && {errorMessage}} 97 | 98 | 99 |
100 | 101 |
102 | 103 | )} 104 |
105 | ); 106 | }; 107 | 108 | export default Shipping; 109 | -------------------------------------------------------------------------------- /Client/src/pages/Signup.jsx: -------------------------------------------------------------------------------- 1 | import Button from 'react-bootstrap/Button'; 2 | import Form from 'react-bootstrap/Form'; 3 | import Container from 'react-bootstrap/Container'; 4 | import Row from 'react-bootstrap/Row'; 5 | import Alert from 'react-bootstrap/Alert'; 6 | import Col from 'react-bootstrap/Col'; 7 | import { Link, useNavigate } from 'react-router-dom'; 8 | import { useEffect, useRef, useState } from 'react'; 9 | import API from '../api/api.js'; 10 | import { useDispatch, useSelector } from 'react-redux'; 11 | import { loginStart, loginSuccess, loginFailure } from '../redux/userSlice.js'; 12 | 13 | function Signup() { 14 | const formRef = useRef(null); 15 | const navigate = useNavigate(); 16 | const dispatch = useDispatch(); 17 | const [errorMessage, setErrorMessage] = useState([]); 18 | const { currentUser } = useSelector((state) => state.user); 19 | 20 | useEffect(() => { 21 | currentUser && navigate('/'); 22 | }, [currentUser, navigate]); 23 | const handleSubmit = async (e) => { 24 | e.preventDefault(); 25 | let name = formRef.current.name.value.trim(), 26 | email = formRef.current.email.value.trim(), 27 | password = formRef.current.password.value.trim(), 28 | confirmPassword = formRef.current.confirmPassword.value.trim(); 29 | 30 | setErrorMessage([]); 31 | let err = []; 32 | if (!name || !email || !password || !confirmPassword) { 33 | err.push('missing field'); 34 | } 35 | if (password !== confirmPassword) { 36 | err.push('password not equal confirm password'); 37 | } 38 | if (err.length !== 0) { 39 | setErrorMessage(err); 40 | return; 41 | } 42 | 43 | dispatch(loginStart()); 44 | try { 45 | const { data: res } = await API.post('/auth/signup', { 46 | name, 47 | email, 48 | password, 49 | }); 50 | formRef.current.reset(); 51 | localStorage.setItem('access-token', res.token); 52 | 53 | dispatch(loginSuccess(res.user)); 54 | navigate('/'); 55 | } catch (error) { 56 | dispatch(loginFailure('Invalid email or password')); 57 | if (error.response.status === 409) { 58 | setErrorMessage(['Email already exists']); 59 | } 60 | } 61 | }; 62 | return ( 63 | 64 | 65 | 66 |

Sign up

67 |
68 | 69 | Name 70 | 75 | 76 | 77 | 78 | Email address 79 | 84 | 85 | 86 | 87 | Password 88 | 93 | 94 | 95 | 96 | confirm Password 97 | 102 | 103 | 104 | {errorMessage && 105 | errorMessage.map((e, idx) => ( 106 | 107 | {e} 108 | 109 | ))} 110 | 111 | 114 | 115 |

116 | Already have an account? 117 | 118 | Log-In{' '} 119 | 120 |

121 |
122 | 123 |
124 |
125 | ); 126 | } 127 | 128 | export default Signup; 129 | -------------------------------------------------------------------------------- /Client/src/pages/UserProfile.jsx: -------------------------------------------------------------------------------- 1 | import Container from 'react-bootstrap/Container'; 2 | import Row from 'react-bootstrap/Row'; 3 | import Col from 'react-bootstrap/Col'; 4 | import Alert from 'react-bootstrap/Alert'; 5 | import Button from 'react-bootstrap/Button'; 6 | import FloatingLabel from 'react-bootstrap/FloatingLabel'; 7 | import Form from 'react-bootstrap/Form'; 8 | import { CheckoutSteps } from '../components'; 9 | import { useRef, useState } from 'react'; 10 | import { Link, useNavigate } from 'react-router-dom'; 11 | import { useDispatch, useSelector } from 'react-redux'; 12 | import { setAddress } from '../redux/cartSlice.js'; 13 | import API from '../api/api.js'; 14 | import { loginSuccess } from '../redux/userSlice.js'; 15 | import { toast } from 'react-toastify'; 16 | 17 | const Shipping = () => { 18 | const dispatch = useDispatch(); 19 | const [errorMessage, setErrorMessage] = useState([]); 20 | const [isSuccess, setIsSucess] = useState(false); 21 | const formRef = useRef(null); 22 | const { currentUser } = useSelector((state) => state.user); 23 | 24 | const handleSubmitAddress = async (e) => { 25 | e.preventDefault(); 26 | 27 | let name = formRef.current.name.value.trim(), 28 | email = formRef.current.email.value.trim(), 29 | oldPassword = formRef.current.oldPassword.value.trim(), 30 | newPassword = formRef.current.newPassword.value.trim(), 31 | confirmPassword = formRef.current.confirmPassword.value.trim(); 32 | 33 | setErrorMessage([]); 34 | setIsSucess(false); 35 | 36 | let err = []; 37 | if (!name || !email || !oldPassword || !newPassword || !confirmPassword) { 38 | err.push('missing field , Please Enter All Fields'); 39 | } 40 | if (newPassword !== confirmPassword) { 41 | err.push('password not equal confirm password'); 42 | } 43 | if (err.length > 0) { 44 | setErrorMessage(err); 45 | return; 46 | } 47 | 48 | try { 49 | const { data: res } = await API.put(`/users/${currentUser._id}`, { 50 | name, 51 | email, 52 | password: newPassword, 53 | }); 54 | formRef.current.reset(); 55 | setIsSucess(true); 56 | 57 | dispatch(loginSuccess(res.user)); 58 | } catch (error) { 59 | toast.dismiss(); 60 | toast.error('Error While editing user'); 61 | } 62 | }; 63 | return ( 64 | 65 | {!currentUser ? ( 66 | 67 | You Are not logged in Sign In 68 | 69 | ) : ( 70 | <> 71 | 72 | 73 |

User Profile

74 |
75 | 76 | 82 | 83 | 84 | 90 | 91 | 92 | 93 | 98 | 99 | 100 | 101 | 106 | 107 | 108 | 109 | 114 | 115 | {errorMessage && 116 | errorMessage.map((e, idx) => ( 117 | 118 | {e} 119 | 120 | ))} 121 | {isSuccess && ( 122 | user updated successfully 123 | )} 124 | 125 | 126 |
127 | 128 |
129 | 130 | )} 131 |
132 | ); 133 | }; 134 | 135 | export default Shipping; 136 | -------------------------------------------------------------------------------- /Client/src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home.jsx'; 2 | export { default as Cart } from './Cart.jsx'; 3 | export { default as Product } from './Product.jsx'; 4 | export { default as Login } from './Login.jsx'; 5 | export { default as Signup } from './Signup.jsx'; 6 | export { default as Shipping } from './Shipping.jsx'; 7 | export { default as Payment } from './Payment.jsx'; 8 | export { default as PlaceOrder } from './PlaceOrder.jsx'; 9 | export { default as Order } from './Order.jsx'; 10 | export { default as Orders } from './Orders.jsx'; 11 | export { default as UserProfile } from './UserProfile.jsx'; 12 | export { default as Search } from './Search.jsx'; 13 | export { default as AdminOrders } from './AdminOrders.jsx'; 14 | export { default as AdminUsers } from './AdminUsers.jsx'; 15 | export { default as AdminUserEdit } from './AdminUserEdit.jsx'; 16 | export { default as AdminProducts } from './AdminProducts.jsx'; 17 | export { default as AdminProductEdit } from './AdminProductEdit.jsx'; 18 | export { default as Dashboard } from './Dashboard.jsx'; 19 | // export { default as Map } from './Map.jsx'; 20 | -------------------------------------------------------------------------------- /Client/src/redux/cartSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const getAddress = () => { 4 | let temp; 5 | if (localStorage.getItem('address')) { 6 | temp = JSON.parse(localStorage.getItem('address')); 7 | } else { 8 | temp = { 9 | address: '', 10 | city: '', 11 | country: '', 12 | postalCode: '', 13 | }; 14 | } 15 | return temp; 16 | }; 17 | 18 | const initialState = { 19 | cart: localStorage.getItem('cart') 20 | ? JSON.parse(localStorage.getItem('cart')) 21 | : {}, 22 | cartCount: localStorage.getItem('cart') 23 | ? JSON.parse(localStorage.getItem('cart')).items.length 24 | : 0, 25 | address: getAddress(), 26 | paymentMethod: localStorage.getItem('paymentMethod') 27 | ? JSON.parse(localStorage.getItem('paymentMethod')) 28 | : 'paypal', 29 | }; 30 | 31 | export const cartSlice = createSlice({ 32 | name: 'cart', 33 | initialState, 34 | reducers: { 35 | setCartItems: (state, action) => { 36 | state.cart = action.payload; 37 | state.cartCount = action.payload.items.length; 38 | localStorage.setItem('cart', JSON.stringify(action.payload)); 39 | }, 40 | setAddress: (state, action) => { 41 | state.address = action.payload; 42 | localStorage.setItem('address', JSON.stringify(action.payload)); 43 | }, 44 | setPaymentMethod: (state, action) => { 45 | state.paymentMethod = action.payload; 46 | localStorage.setItem('paymentMethod', JSON.stringify(action.payload)); 47 | }, 48 | clearCart: (state) => { 49 | localStorage.removeItem('cart'); 50 | localStorage.removeItem('paymentMethod'); 51 | localStorage.removeItem('address'); 52 | state.cart = {}; 53 | state.cartCount = 0; 54 | state.address = getAddress(); 55 | state.paymentMethod = 'paypal'; 56 | }, 57 | }, 58 | }); 59 | 60 | export const { setCartItems, clearCart, setAddress, setPaymentMethod } = 61 | cartSlice.actions; 62 | 63 | export default cartSlice.reducer; 64 | -------------------------------------------------------------------------------- /Client/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import userReducer from './userSlice.js'; 3 | import cartReducer from './cartSlice.js'; 4 | 5 | const store = configureStore({ 6 | reducer: { 7 | user: userReducer, 8 | cart: cartReducer, 9 | }, 10 | }); 11 | 12 | export default store; 13 | -------------------------------------------------------------------------------- /Client/src/redux/userSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | currentUser: localStorage.getItem('currentUser') 5 | ? JSON.parse(localStorage.getItem('currentUser')) 6 | : null, 7 | loading: false, 8 | error: false, 9 | }; 10 | 11 | export const userSlice = createSlice({ 12 | name: 'user', 13 | initialState, 14 | reducers: { 15 | loginStart: (state) => { 16 | state.loading = true; 17 | }, 18 | loginSuccess: (state, action) => { 19 | state.loading = false; 20 | state.currentUser = action.payload; 21 | localStorage.setItem('currentUser', JSON.stringify(action.payload)); 22 | }, 23 | loginFailure: (state) => { 24 | state.loading = false; 25 | state.error = true; 26 | }, 27 | logout: (state) => { 28 | state.currentUser = null; 29 | localStorage.clear(); 30 | localStorage.clear(); 31 | }, 32 | }, 33 | }); 34 | 35 | export const { loginStart, loginSuccess, loginFailure, logout } = 36 | userSlice.actions; 37 | 38 | export default userSlice.reducer; 39 | -------------------------------------------------------------------------------- /Client/src/utils/searchUtils.js: -------------------------------------------------------------------------------- 1 | export const prices = [ 2 | { 3 | name: '$1 to $50', 4 | value: '1-50', 5 | }, 6 | { 7 | name: '$51 to $200', 8 | value: '51-200', 9 | }, 10 | { 11 | name: '$201 to $1000', 12 | value: '201-1000', 13 | }, 14 | { 15 | name: 'more than $1000', 16 | value: '1000-10000', 17 | }, 18 | ]; 19 | 20 | export const ratings = [ 21 | { 22 | name: '1stars & up', 23 | rating: 1, 24 | }, 25 | { 26 | name: '2stars & up', 27 | rating: 2, 28 | }, 29 | { 30 | name: '3stars & up', 31 | rating: 3, 32 | }, 33 | { 34 | name: '4stars & up', 35 | rating: 4, 36 | }, 37 | { 38 | name: '4stars & up', 39 | rating: 5, 40 | }, 41 | ]; 42 | 43 | export const getSearchParams = (search) => { 44 | const sp = new URLSearchParams(search); // /search?category=Shirts 45 | const category = sp.get('category') || 'all'; 46 | const query = sp.get('query') || 'all'; 47 | const price = sp.get('price') || 'all'; 48 | const rating = sp.get('rating') || 'all'; 49 | const order = sp.get('order') || 'newest'; 50 | const page = sp.get('page') || 1; 51 | 52 | return { 53 | sp, 54 | category, 55 | query, 56 | price, 57 | rating, 58 | order, 59 | page, 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 | # AmazonX 7 | 8 | ## 📝 Table of Contents 9 | 10 | - [About](#about) 11 | - [features](#features) 12 | 13 | - [Technologies used](#build) 14 | - [getting started](#start) 15 | - [Demo](#demo) 16 | - [screenshots](#screenshots) 17 | 18 | ### 🚩About 19 | 20 | AmazonX is a fully-functional Website that clones amazon website 21 | It is a simple, yet powerful, application that allows you buy anything from our online store 22 | 23 | it`s a full mimic of Amazon with most of its features. 24 | 25 | ### ✨ Features 26 | 27 | **User Features** 28 | 29 | - Authenticate users using json web tokens 30 | - show products && filter products based on (rating , price , newest , categories ) 31 | - Search for Products 32 | - Add Review & rating for products 33 | - add products to cart 34 | - edit your cart && checkout 35 | - add your address for order delivery 36 | - payment method using paypal & stripe 37 | - show order history for each user 38 | - edit user profile 39 | 40 | **Admin Features** 41 | 42 | - Admin Dashboard that shows statistics for store 43 | - show all orders 44 | - edit && delete && deleviry order 45 | - show all products 46 | - add && edit && delete products 47 | - show all users 48 | - edit & delete & users 49 | - make other users as admin 50 | 51 | ## 💻Technologies Used 52 | 53 | #### Frontend 54 | 55 | - React 56 | - React-Router dom 57 | - Redux Toolkit 58 | - Axios 59 | - react-google-charts 60 | - react-paypal 61 | - react-stripe 62 | - react-bootstrap 63 | - react-toastify 64 | 65 | #### backend 66 | 67 | - Node.js 68 | - Express 69 | 70 | - MongoDB 71 | - dotenv 72 | - jsonwebtoken `for authentication` 73 | - bcrypt `for hashing passwords` 74 | - multer `for uploading files` 75 | 76 | ## 🏁Getting Started 77 | 78 | 1. **_Clone the repository_** 79 | 80 | ```bash 81 | git clone https://github.com/omar214/AmazonX.git 82 | 83 | ``` 84 | 85 | 2. **_Go to the directory of the repository_** 86 | 87 | ```bash 88 | cd Amazon-store 89 | 90 | ``` 91 | 92 | 3. **_to run frontend_** 93 | 94 | ```bash 95 | cd Client 96 | 97 | npm install 98 | npm start 99 | ``` 100 | 101 | **_Client runs at port 3000_** 102 | 103 | 4.**_to run backend_** 104 | 105 | ```bash 106 | cd Server 107 | 108 | npm install 109 | npm start 110 | 111 | ``` 112 | 113 | **_server runs at port 8080_** 114 | 115 | 116 | ## 🎥 Demo 117 | 118 |
119 | 120 | 121 | 122 | **Home page & Sign in & Edit User** 123 | 124 | 125 | 126 | https://user-images.githubusercontent.com/60351557/192575420-c6dcc5ce-7ffa-474e-a46f-e1417463b2ab.mp4 127 | 128 | 129 | **Product Page & Cart & Checkout Steps** 130 | 131 | 132 | 133 | https://user-images.githubusercontent.com/60351557/192613910-a57c8874-e49b-4f64-a6e5-8ced825b8774.mp4 134 | 135 | 136 | 137 | **Search Page** 138 | 139 | 140 | 141 | https://user-images.githubusercontent.com/60351557/192614091-dd2119f8-4fc0-4f27-8854-87db6e7a993d.mp4 142 | 143 | 144 | 145 | **Paypal payment & Deliver Logic** 146 | 147 | 148 | 149 | 150 | https://user-images.githubusercontent.com/60351557/192614169-653e617a-1ab0-417a-abcf-5601f8a233b6.mp4 151 | 152 | 153 | 154 | **Stripe Payment** 155 | 156 | 157 | 158 | 159 | https://user-images.githubusercontent.com/60351557/192614226-9c1fa3c1-2bb1-48c4-9ada-4560ab7226b6.mp4 160 | 161 | 162 | 163 | **Admin Dashboard & Admin orders details** 164 | 165 | 166 | 167 | 168 | https://user-images.githubusercontent.com/60351557/192614274-e83e718a-2817-44d6-b437-2bc80cae8bf0.mp4 169 | 170 | 171 | **Admin ( Edit & Add & Delete) Products** 172 | 173 | 174 | 175 | https://user-images.githubusercontent.com/60351557/192614322-223dc69c-9a24-492f-b8ae-bb8c39ce6575.mp4 176 | 177 | 178 | 179 | **Admin ( Edit & Delete) Users** 180 | 181 | 182 | 183 | https://user-images.githubusercontent.com/60351557/192614346-598be757-5e44-491f-810b-b7828bba66f9.mp4 184 | 185 | 186 | 187 |
188 | 189 | ## 🎥 screenshots 190 | 191 | - Sign up 192 | ![Sign up](./screenshots/2.png) 193 | 194 |
195 | 196 | - Log in 197 | ![Log in](./screenshots/1.png) 198 | 199 |
200 | 201 | - Home page 202 | ![Home page](./screenshots/3.png) 203 | 204 |
205 | 206 | - Product Page 207 | ![Product Page](./screenshots/4.png) 208 | 209 |
210 | 211 | - Reviews 212 | ![Reviews](./screenshots/5.png) 213 | 214 |
215 | 216 | - Cart 217 | ![Cart page](./screenshots/6.png) 218 | 219 |
220 | 221 | - Check out Steps 222 | ![Checkout steps](./screenshots/7.png) 223 | ![Checkout steps](./screenshots/8.png) 224 | ![Checkout steps](./screenshots/9.png) 225 | 226 |
227 | 228 | - order page 229 | ![ order page](./screenshots/10.png) 230 | 231 |
232 | 233 | - paypal Payment 234 | ![paypal Payment](./screenshots/11.png) 235 | ![paypal Payment](./screenshots/12.png) 236 | ![paypal Payment](./screenshots/13.png) 237 | 238 |
239 | 240 | - Stripe Payment 241 | ![Stirpe Payment](./screenshots/15.png) 242 | 243 |
244 | 245 | - Search Page 246 | ![Search Page](./screenshots/14.png) 247 | 248 |
249 | 250 | - Admin Dashboard 251 | ![Admin Dashboard](./screenshots/16.png) 252 | ![Admin Dashboard](./screenshots/17.png) 253 | 254 |
255 | 256 | - Admin All Orders Details 257 | ![All Orders Details](./screenshots/18.png) 258 | 259 |
260 | 261 | - Admin All Product Details 262 | ![Products Details](./screenshots/19.png) 263 | 264 |
265 | 266 | - Add Product 267 | ![Add Product](./screenshots/20.png) 268 | 269 |
270 | 271 | - Edit Product 272 | ![Edit Product](./screenshots/21.png) 273 | 274 |
275 | 276 | - Admin All Users details 277 | ![All Users details](./screenshots/22.png) 278 | 279 |
280 | 281 | - Edit User 282 | ![Edit User](./screenshots/23.png) 283 | 284 |
285 | 286 | - Mobile Responsive Design 287 | 288 | - Home Page 289 | 290 | ![Home Page](./screenshots/responsive/1.png) 291 | 292 |
293 | 294 | - Product Page 295 | 296 | ![Product Page](./screenshots/responsive/2.png) 297 | 298 |
299 | 300 | - Reviews 301 | 302 | ![Reviews](./screenshots/responsive/3.png) 303 | 304 |
305 | 306 | - Cart 307 | 308 | ![Cart](./screenshots/responsive/4.png) 309 | 310 |
311 | 312 | - Check out 313 | 314 | ![Check out](./screenshots/responsive/5.png) 315 | 316 |
317 | 318 | ![Check out](./screenshots/responsive/6.png) 319 | 320 |
321 | 322 | ![Check out](./screenshots/responsive/7.png) 323 | 324 |
325 | 326 | - DashBoard 327 | 328 | ![DashBoard](./screenshots/responsive/8.png) 329 | 330 |
331 | 332 | ![DashBoard](./screenshots/responsive/9.png) 333 | 334 |
335 | 336 | - Add Product 337 | 338 | ![Add Product](./screenshots/responsive/10.png) 339 | 340 | -------------------------------------------------------------------------------- /REQUIRED .md: -------------------------------------------------------------------------------- 1 | **Logic**; 2 | 3 | - [ ] Readme 4 | - [ ] edit seed (users , products , orders , reviews) 5 | 6 | - [ ] map (put location ) 7 | 8 | --- 9 | 10 | ## Delayed 11 | 12 | - [ ] change page title using helmet 13 | - [ ] handle categories as array 14 | - [ ] add more validations 15 | 16 | ## Done 17 | 18 | **pages UI** 19 | 20 | - [x] sign up 21 | - [x] sign in 22 | - [x] home page 23 | - [x] product page 24 | - [x] cart page 25 | - [x] proceed to buy ( sign-in && shipp && payment && preview order ) 26 | - [x] order history 27 | - [x] order details 28 | - [x] cart && placeorder responsive check 29 | - [x] toastify 30 | 31 | components 32 | 33 | - [x] navbar 34 | - [x] product card 35 | - [x] footer 36 | 37 | --- 38 | 39 | **models** 40 | 41 | - [x] user model 42 | - [x] product model 43 | - [x] order model 44 | - [x] cart 45 | - [x] review 46 | 47 | **routes** 48 | 49 | - [x] auth 50 | - [x] user 51 | - [x] product 52 | - [x] orders 53 | - [x] cart 54 | - [x] review 55 | - [x] make cart Checkout 56 | 57 | --- 58 | 59 | **Pages Logic** 60 | 61 | - [x] sign up 62 | - [x] log in 63 | 64 | - [x] track form 65 | - [x] validate form 66 | - [x] send request 67 | - [x] store (token , user Data) in [localstorage , redux or reducer ] 68 | - [x] show user name in navbar (with frop down options && logout button logic) 69 | - [x] prevent going to sign up when logged in 70 | 71 | - [x] home page 72 | 73 | - [x]fetch products 74 | - [x] add To cart button Logic 75 | - [x] send request 76 | - [x] update cart (local storage , redux or reducer ) 77 | - [x] show badge in cart 78 | - [x] fetch badge when logging in 79 | - [x] hide badge when not logged 80 | 81 | - [x] product page 82 | 83 | - [x] fetch product 84 | - [x] add to cart logic (same as prev) 85 | - [x] fetch reviews 86 | - [x] add review logic 87 | - [x] track form 88 | - [x] send request 89 | - [x] push review to state 90 | 91 | - [x] cart page 92 | 93 | - [x] fetch cart 94 | - [x] add , remove , delete logic 95 | - [x] monitor state 96 | - [x] send req 97 | 98 | - [x] proceed To Chcek out 99 | 100 | - [x] store address (redux & localstorage) 101 | - [x] store payment method (redux & localstorage) 102 | - [x] place order details 103 | - [x] checkout route 104 | - [x] checkout button Logic 105 | - [x] then redirect to order details page 106 | 107 | - [x] order details 108 | 109 | - [x] fetch only 110 | 111 | - [x] order history 112 | 113 | - [x] fetch orders only 114 | 115 | - [x] protect routes 116 | 117 | - [x] edit user profile 118 | 119 | - [x] filter & search 120 | 121 | - [x] search route & controller 122 | - [x] search page 123 | - [x] track nav form & navigate 124 | 125 | - [x] admin 126 | 127 | - [x] orders 128 | 129 | - [x] order details 130 | - [x] delete order 131 | - [ ] could add pagination for them (not done) 132 | 133 | - [x] users 134 | 135 | - [x] fetch all users 136 | - [x] delete user 137 | - [x] edit user page 138 | - [x] fetch details 139 | - [x] edit request 140 | 141 | - [x] produts 142 | 143 | - [x] fetch products 144 | - [x] delete product 145 | - [x] add product 146 | - [x] edit product 147 | 148 | - [x] dashboard 149 | - [x] Payment Method 150 | - [x]backend 151 | - [x] front 152 | - [x] paypal 153 | - [x] stripe 154 | 155 | - [x] add To cart 156 | 157 | - [x] navbar cartCount 158 | - [ ] check count in stock (not done) 159 | - [ ] default behaviour (could make redirect after adding) (not done) 160 | 161 | - [x] Deliver Button (same place as paypal Button) 162 | - [x] add product image 163 | 164 | - [x] API 165 | - [x] handle product images (their url) 166 | - [x] handle add product (as form data ) 167 | -------------------------------------------------------------------------------- /Server/api/controllers/authController.js: -------------------------------------------------------------------------------- 1 | import User from '../../models/userModel.js'; 2 | import bcrypt from 'bcryptjs'; 3 | import jwt from 'jsonwebtoken'; 4 | import createError from '../../utils/createError.js'; 5 | import config from '../../config/index.js'; 6 | 7 | const genToken = (id, isAdmin = false) => { 8 | const token = jwt.sign({ id, isAdmin }, config.JWT_PASSWORD, { 9 | expiresIn: '24h', 10 | }); 11 | return token; 12 | }; 13 | 14 | const singup = async (req, res, next) => { 15 | try { 16 | let { email, name, password, isAdmin } = req.body; 17 | if (!email || !password || !name) 18 | return next(createError(400, 'Email, name , and password are required')); 19 | 20 | let user = await User.findOne({ email }); 21 | if (user) return next(createError(409, 'User already exists')); 22 | 23 | const hash = bcrypt.hashSync(password); 24 | user = new User({ ...req.body, password: hash }); 25 | const savedUser = await user.save(); 26 | 27 | let { password: _pass, ...other } = savedUser._doc; 28 | const token = genToken(other._id, other.isAdmin); 29 | res.status(200).json({ 30 | user: other, 31 | token, 32 | }); 33 | } catch (error) { 34 | next(error); 35 | } 36 | }; 37 | 38 | const login = async (req, res, next) => { 39 | try { 40 | let { email, password } = req.body; 41 | if (!email || !password) 42 | return next(createError(400, 'Email , and password are required')); 43 | 44 | const user = await User.findOne({ email }); 45 | if (!user) return next(createError(404, 'User not found')); 46 | 47 | // console.log(user); 48 | 49 | const isMatch = bcrypt.compareSync(password, user.password); 50 | if (!isMatch) return next(createError(401, 'Invalid password')); 51 | 52 | const token = genToken(user._id, user.isAdmin); 53 | 54 | const { password: _pass, ...other } = user._doc; 55 | res.status(200).json({ 56 | message: 'Login successful', 57 | user: other, 58 | token, 59 | }); 60 | } catch (error) { 61 | console.log(error); 62 | next(error); 63 | } 64 | }; 65 | 66 | const googleAuth = async (req, res, next) => { 67 | res.send('not done yet'); 68 | }; 69 | 70 | export { singup, login, googleAuth }; 71 | -------------------------------------------------------------------------------- /Server/api/controllers/cartController.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import Cart from '../../models/cartModel.js'; 3 | import Order from '../../models/orderModel.js'; 4 | import createError from '../../utils/createError.js'; 5 | 6 | const addToCart = async (req, res, next) => { 7 | try { 8 | const userId = req.userData.id; 9 | const { items } = req.body; 10 | let cart = await Cart.findOne({ userId }); 11 | 12 | const isFirst = !cart; 13 | if (cart) { 14 | // console.log(cart.items); 15 | for (let item of items) { 16 | const idx = cart.items.findIndex((el, idx) => 17 | el.product.equals(item.product), 18 | ); 19 | if (idx !== -1) { 20 | // inc qty 21 | cart.items[idx].quantity += item.quantity; 22 | } else { 23 | // push item 24 | cart.items.push(item); 25 | } 26 | } 27 | } else { 28 | cart = new Cart({ ...req.body, userId }); 29 | } 30 | 31 | const savedCart = await cart.save(); 32 | 33 | res.status(200).json({ 34 | message: isFirst 35 | ? 'cart created successfully' 36 | : 'cart edited successfully', 37 | cart: savedCart, 38 | }); 39 | } catch (error) { 40 | next(error); 41 | // console.log(error); 42 | } 43 | }; 44 | 45 | const getUserCart = async (req, res, next) => { 46 | try { 47 | const userId = req.userData.id; 48 | 49 | const cart = await Cart.findOne( 50 | { userId }, 51 | { 52 | items: 1, 53 | _id: 0, 54 | }, 55 | ).populate({ 56 | path: 'items.product', 57 | model: 'Product', 58 | select: 'name price image countInStock', 59 | }); 60 | res.status(200).json({ 61 | cart, 62 | }); 63 | } catch (error) { 64 | next(error); 65 | } 66 | }; 67 | 68 | const deleteCart = async (req, res, next) => { 69 | try { 70 | const userId = req.userData.id; 71 | 72 | let cart = await Cart.findOne({ userId }); 73 | if (!cart) return next(createError(404, 'Cart not found')); 74 | 75 | await Cart.deleteOne({ userId }); 76 | res.status(200).json('Cart deleted'); 77 | } catch (error) { 78 | next(error); 79 | } 80 | }; 81 | 82 | const deleteItem = async (req, res, next) => { 83 | try { 84 | const userId = req.userData.id; 85 | const { productId } = req.body; 86 | 87 | let cart = await Cart.findOne({ userId }); 88 | if (!cart) return next(createError(404, 'Cart not found')); 89 | 90 | const productIdx = cart.items.findIndex((el) => 91 | el.product.equals(productId), 92 | ); 93 | if (productIdx === -1) 94 | return next(createError(404, 'product is not in cart')); 95 | 96 | cart.items.splice(productIdx, 1); 97 | await cart.save(); 98 | 99 | res.status(200).json('Item Removed'); 100 | } catch (error) { 101 | next(error); 102 | } 103 | }; 104 | 105 | const checkout = async (req, res, next) => { 106 | try { 107 | const userId = req.userData.id; 108 | const { address, paymentMethod } = req.body; 109 | 110 | const cart = await Cart.findOne( 111 | { userId }, 112 | { 113 | items: 1, 114 | _id: 0, 115 | }, 116 | ).populate({ 117 | path: 'items.product', 118 | model: 'Product', 119 | select: 'price', 120 | }); 121 | if (!cart) return next(createError(404, 'Cart not found')); 122 | 123 | const totalPrice = cart.items.reduce( 124 | (acc, item) => acc + item.product.price * item.quantity, 125 | 0, 126 | ); 127 | let order = new Order({ 128 | totalPrice, 129 | address, 130 | paymentMethod, 131 | items: cart.items, 132 | userId, 133 | isPaid: false, 134 | isDelivered: false, 135 | }); 136 | const savedOrder = await order.save(); 137 | res.status(200).json({ message: 'checkout done', order: savedOrder }); 138 | 139 | await Cart.deleteOne({ userId }); 140 | } catch (error) { 141 | next(error); 142 | } 143 | }; 144 | 145 | export default { 146 | addToCart, 147 | deleteCart, 148 | getUserCart, 149 | deleteItem, 150 | checkout, 151 | }; 152 | -------------------------------------------------------------------------------- /Server/api/controllers/orderController.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import Order from '../../models/orderModel.js'; 3 | import User from '../../models/userModel.js'; 4 | import Product from '../../models/productModel.js'; 5 | import createError from '../../utils/createError.js'; 6 | import Stripe from 'stripe'; 7 | import config from '../../config/index.js'; 8 | 9 | const stripe = Stripe(config.STRIPE_SECRET_KEY); 10 | 11 | const addOrder = async (req, res, next) => { 12 | try { 13 | const userId = req.userData.id; 14 | let order; 15 | order = new Order({ ...req.body, userId }); 16 | const savedOrder = await order.save(); 17 | 18 | res.status(200).json({ 19 | message: 'order added', 20 | order: savedOrder, 21 | }); 22 | } catch (error) { 23 | next(error); 24 | // console.log(error); 25 | } 26 | }; 27 | 28 | const getAllOrders = async (req, res, next) => { 29 | try { 30 | const orders = await Order.find().populate({ 31 | path: 'userId', 32 | model: 'User', 33 | select: 'name', 34 | }); 35 | res.status(200).json({ 36 | count: orders.length, 37 | orders, 38 | }); 39 | } catch (error) { 40 | next(error); 41 | } 42 | }; 43 | const getOrderById = async (req, res, next) => { 44 | try { 45 | const id = req.params.id; 46 | if (!id || !mongoose.isValidObjectId(id)) 47 | return next(createError(401, 'valid id is required')); 48 | 49 | const order = await Order.findById(id).populate({ 50 | path: 'items.product', 51 | model: 'Product', 52 | select: 'name price image', 53 | }); 54 | if (!order) return next(createError(404, 'order is not found ')); 55 | 56 | res.status(200).json({ order }); 57 | } catch (error) { 58 | next(error); 59 | } 60 | }; 61 | const editOrder = async (req, res, next) => { 62 | try { 63 | const id = req.params.id; 64 | if (!id || !mongoose.isValidObjectId(id)) 65 | return next(createError(401, 'valid id is required')); 66 | 67 | let order = await Order.findOne({ _id: id }); 68 | if (!order) return next(createError(404, 'order is not found ')); 69 | 70 | console.log(order.userId); 71 | console.log(req.userData.id); 72 | 73 | // not admin & not order owner 74 | if (!order.userId.equals(req.userData.id) && !req.userData.isAdmin) 75 | return next(createError(403, 'you can only update your order')); 76 | 77 | for (let key in req.body) { 78 | order[key] = req.body[key]; 79 | } 80 | order = await order.save(); 81 | res.status(200).json({ order }); 82 | } catch (error) { 83 | next(error); 84 | } 85 | }; 86 | const payOrder = async (req, res, next) => { 87 | try { 88 | const id = req.params.id; 89 | if (!id || !mongoose.isValidObjectId(id)) 90 | return next(createError(401, 'valid id is required')); 91 | 92 | let order = await Order.findOne({ _id: id }); 93 | if (!order) return next(createError(404, 'order is not found ')); 94 | 95 | // not admin & not order owner 96 | if (!order.userId.equals(req.userData.id) && !req.userData.isAdmin) 97 | return next(createError(403, 'you can only update your order')); 98 | 99 | order.isPaid = true; 100 | order.paidAt = Date.now(); 101 | order = await order.save(); 102 | res.status(200).json({ message: 'order Paid Successfully', order }); 103 | } catch (error) { 104 | next(error); 105 | } 106 | }; 107 | const getUserOrders = async (req, res, next) => { 108 | try { 109 | const userId = req.userData.id; 110 | 111 | const orders = await Order.find({ userId }); 112 | res.status(200).json({ 113 | count: orders.length, 114 | orders, 115 | }); 116 | } catch (error) { 117 | next(error); 118 | } 119 | }; 120 | const deleteOrder = async (req, res, next) => { 121 | try { 122 | const id = req.params.id; 123 | if (!id || !mongoose.isValidObjectId(id)) 124 | return next(createError(401, 'valid id is required')); 125 | 126 | let order = await Order.findById(id); 127 | if (!order) return next(createError(404, 'order not found')); 128 | 129 | await Order.deleteOne({ _id: id }); 130 | res.status(200).json('order deleted'); 131 | } catch (error) { 132 | next(error); 133 | } 134 | }; 135 | 136 | const dashboard = async (req, res, next) => { 137 | try { 138 | const orders = await Order.aggregate([ 139 | { 140 | $group: { 141 | _id: null, 142 | numOrders: { $sum: 1 }, 143 | totalSales: { $sum: '$totalPrice' }, 144 | }, 145 | }, 146 | ]); 147 | const users = await User.aggregate([ 148 | { 149 | $group: { 150 | _id: null, 151 | numUsers: { $sum: 1 }, 152 | }, 153 | }, 154 | ]); 155 | const dailyOrders = await Order.aggregate([ 156 | { 157 | $group: { 158 | _id: { $dateToString: { format: '%Y-%m-%d', date: '$createdAt' } }, 159 | orders: { $sum: 1 }, 160 | sales: { $sum: '$totalPrice' }, 161 | }, 162 | }, 163 | { $sort: { _id: 1 } }, 164 | ]); 165 | const productCategories = await Product.aggregate([ 166 | { 167 | $group: { 168 | _id: '$category', 169 | count: { $sum: 1 }, 170 | }, 171 | }, 172 | ]); 173 | res.json({ users, orders, dailyOrders, productCategories }); 174 | } catch (error) { 175 | next(error); 176 | } 177 | }; 178 | const stripePayment = async (req, res, next) => { 179 | try { 180 | const orderdId = req.params.id; 181 | let order = await Order.findOne({ _id: orderdId }); 182 | if (!order) return next(createError(404, 'order is not found ')); 183 | 184 | const stripeRes = await stripe.charges.create({ 185 | source: req.body.tokenId, 186 | amount: req.body.amount, 187 | currency: 'usd', 188 | }); 189 | 190 | order.isPaid = true; 191 | order.paidAt = Date.now(); 192 | order = await order.save(); 193 | res.status(200).json({ 194 | message: 'Order Paid Successfully', 195 | order, 196 | // stripeRes, 197 | }); 198 | } catch (error) { 199 | console.log(error); 200 | next(error); 201 | } 202 | }; 203 | export default { 204 | addOrder, 205 | getAllOrders, 206 | getOrderById, 207 | editOrder, 208 | deleteOrder, 209 | getUserOrders, 210 | payOrder, 211 | dashboard, 212 | stripePayment, 213 | }; 214 | -------------------------------------------------------------------------------- /Server/api/controllers/productController.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import Product from '../../models/productModel.js'; 3 | import createError from '../../utils/createError.js'; 4 | import config from '../../config/index.js'; 5 | 6 | const addProduct = async (req, res, next) => { 7 | try { 8 | let image = req.file.filename; 9 | let { name } = req.body; 10 | 11 | let product = await Product.findOne({ name }); 12 | if (product) return next(createError(409, 'product already exists')); 13 | 14 | image = image || '/images/p1.jpg'; 15 | product = new Product({ ...req.body, image }); 16 | const savedProduct = await product.save(); 17 | 18 | res.status(200).json({ 19 | message: 'product added', 20 | product: savedProduct, 21 | }); 22 | } catch (error) { 23 | next(error); 24 | } 25 | }; 26 | 27 | const getAllProducts = async (req, res, next) => { 28 | try { 29 | const products = await Product.find(); 30 | res.status(200).json({ 31 | count: products.length, 32 | products, 33 | }); 34 | } catch (error) { 35 | next(error); 36 | } 37 | }; 38 | const getProductById = async (req, res, next) => { 39 | try { 40 | const id = req.params.id; 41 | if (!id || !mongoose.isValidObjectId(id)) 42 | return next(createError(401, 'valid id is required')); 43 | 44 | const product = await Product.findById(id); 45 | if (!product) return next(createError(404, 'product is not found ')); 46 | 47 | res.status(200).json({ product }); 48 | } catch (error) { 49 | next(error); 50 | } 51 | }; 52 | const editProduct = async (req, res, next) => { 53 | try { 54 | const id = req.params.id; 55 | if (!id || !mongoose.isValidObjectId(id)) 56 | return next(createError(401, 'valid id is required')); 57 | 58 | let product = await Product.findOne({ _id: id }); 59 | if (!product) return next(createError(404, 'product is not found ')); 60 | for (let key in req.body) { 61 | product[key] = req.body[key]; 62 | } 63 | product = await product.save(); 64 | res.status(200).json({ product }); 65 | } catch (error) { 66 | next(error); 67 | } 68 | }; 69 | const deleteProduct = async (req, res, next) => { 70 | try { 71 | const id = req.params.id; 72 | if (!id || !mongoose.isValidObjectId(id)) 73 | return next(createError(401, 'valid id is required')); 74 | 75 | let product = await Product.findById(id); 76 | if (!product) return next(createError(404, 'product not found')); 77 | 78 | await Product.deleteOne({ _id: id }); 79 | res.status(200).json('product deleted'); 80 | } catch (error) { 81 | next(error); 82 | } 83 | }; 84 | const getCategories = async (req, res, next) => { 85 | try { 86 | const categories = await Product.find().distinct('category'); 87 | res.status(200).json({ 88 | count: categories.length, 89 | categories, 90 | }); 91 | } catch (error) { 92 | next(error); 93 | } 94 | }; 95 | 96 | const search = async (req, res, next) => { 97 | try { 98 | const PAGE_SIZE = 3; 99 | 100 | const { query } = req; 101 | const pageSize = query.pageSize || PAGE_SIZE; 102 | const page = query.page || 1; 103 | const category = query.category || ''; 104 | const price = query.price || ''; 105 | const rating = query.rating || ''; 106 | const order = query.order || ''; 107 | const searchQuery = query.query || ''; 108 | 109 | const queryFilter = 110 | searchQuery && searchQuery !== 'all' 111 | ? { 112 | name: { 113 | $regex: searchQuery, 114 | $options: 'i', 115 | }, 116 | } 117 | : {}; 118 | const categoryFilter = 119 | category && category !== 'all' 120 | ? { 121 | category: { $in: category }, 122 | } 123 | : {}; 124 | const ratingFilter = 125 | rating && rating !== 'all' 126 | ? { 127 | rating: { 128 | $gte: Number(rating), 129 | }, 130 | } 131 | : {}; 132 | const priceFilter = 133 | price && price !== 'all' 134 | ? { 135 | // 1-50 136 | price: { 137 | $gte: Number(price.split('-')[0]), 138 | $lte: Number(price.split('-')[1]), 139 | }, 140 | } 141 | : {}; 142 | const sortOrder = 143 | order === 'featured' 144 | ? { featured: -1 } 145 | : order === 'lowest' 146 | ? { price: 1 } 147 | : order === 'highest' 148 | ? { price: -1 } 149 | : order === 'toprated' 150 | ? { rating: -1 } 151 | : order === 'newest' 152 | ? { createdAt: -1 } 153 | : { _id: -1 }; 154 | 155 | const products = await Product.find({ 156 | ...queryFilter, 157 | ...categoryFilter, 158 | ...priceFilter, 159 | ...ratingFilter, 160 | }) 161 | .sort(sortOrder) 162 | .skip(pageSize * (page - 1)) 163 | .limit(pageSize); 164 | 165 | const countProducts = await Product.countDocuments({ 166 | ...queryFilter, 167 | ...categoryFilter, 168 | ...priceFilter, 169 | ...ratingFilter, 170 | }); 171 | res.json({ 172 | countProducts: products.length, 173 | page, 174 | pages: Math.ceil(countProducts / pageSize), 175 | products, 176 | }); 177 | } catch (error) { 178 | next(error); 179 | } 180 | }; 181 | 182 | export default { 183 | addProduct, 184 | getAllProducts, 185 | getProductById, 186 | editProduct, 187 | deleteProduct, 188 | getCategories, 189 | search, 190 | }; 191 | -------------------------------------------------------------------------------- /Server/api/controllers/reviewController.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import Product from '../../models/productModel.js'; 3 | import Review from '../../models/reviewModel.js'; 4 | import createError from '../../utils/createError.js'; 5 | 6 | const addReview = async (req, res, next) => { 7 | try { 8 | const productId = req.params.id; 9 | if (!productId || !mongoose.isValidObjectId(productId)) 10 | return next(createError(401, 'valid id is required')); 11 | 12 | const { name, comment, rating } = req.body; 13 | if ((!name, !comment, !rating)) 14 | return next(400, `name & comment & rating is required`); 15 | 16 | const product = await Product.findById(productId); 17 | if (!product) return next(createError(404, 'product is not found ')); 18 | 19 | const review = new Review({ 20 | productId, 21 | ...req.body, 22 | }); 23 | 24 | const savedReview = await review.save(); 25 | 26 | res.status(200).json({ 27 | message: 'review added', 28 | review: savedReview, 29 | }); 30 | } catch (error) { 31 | next(error); 32 | } 33 | }; 34 | const getProductReviews = async (req, res, next) => { 35 | try { 36 | const productId = req.params.id; 37 | if (!productId || !mongoose.isValidObjectId(productId)) 38 | return next(createError(401, 'valid id is required')); 39 | 40 | const product = await Product.findById(productId); 41 | if (!product) return next(createError(404, 'product is not found ')); 42 | 43 | const reviews = await Review.find({ productId }); 44 | res.status(200).json({ 45 | count: reviews.length, 46 | reviews, 47 | }); 48 | } catch (error) { 49 | next(error); 50 | } 51 | }; 52 | 53 | const deleteReview = async (req, res, next) => { 54 | try { 55 | const reviewId = req.params.id; 56 | if (!reviewId || !mongoose.isValidObjectId(reviewId)) 57 | return next(createError(401, 'valid id is required')); 58 | 59 | await Review.deleteOne({ _id: reviewId }); 60 | res.status(200).json({ message: 'review deleted' }); 61 | } catch (error) { 62 | next(error); 63 | } 64 | }; 65 | 66 | export default { addReview, deleteReview, getProductReviews }; 67 | -------------------------------------------------------------------------------- /Server/api/controllers/userControlller.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import User from '../../models/userModel.js'; 3 | import createError from '../../utils/createError.js'; 4 | import config from '../../config/index.js'; 5 | import bcrypt from 'bcryptjs'; 6 | // import jwt from 'jsonwebtoken'; 7 | 8 | const getAllUsers = async (req, res, next) => { 9 | try { 10 | const users = await User.find(); 11 | res.status(200).json({ 12 | count: users.length, 13 | users, 14 | }); 15 | } catch (error) { 16 | next(error); 17 | } 18 | }; 19 | const getUserById = async (req, res, next) => { 20 | try { 21 | const id = req.params.id; 22 | if (!id || !mongoose.isValidObjectId(id)) 23 | return next(createError(401, 'valid id is required')); 24 | 25 | const user = await User.findOne({ _id: id }); 26 | if (!user) return next(createError(404, 'user is not found ')); 27 | 28 | res.status(200).json({ user }); 29 | } catch (error) { 30 | next(error); 31 | } 32 | }; 33 | 34 | const updateUser = async (req, res, next) => { 35 | try { 36 | const id = req.params.id; 37 | if (!id || !mongoose.isValidObjectId(id)) 38 | return next(createError(401, 'valid id is required')); 39 | 40 | const { email, password } = req.body; 41 | let user = await User.findOne({ email }); 42 | let myUser = await User.findOne({ _id: id }); 43 | 44 | if (user && !user._id.equals(myUser._id) && user.email === email) 45 | return next(createError(400, 'Email Already in use')); 46 | if (!myUser) return next(createError(404, 'user is not found ')); 47 | 48 | // not admin & not order owner 49 | if (req.userData.id !== id && !req.userData.isAdmin) 50 | return next(createError(403, 'you can only update your order')); 51 | 52 | for (let key in req.body) { 53 | myUser[key] = req.body[key]; 54 | } 55 | if (password) myUser.password = bcrypt.hashSync(password); 56 | 57 | myUser = await myUser.save(); 58 | res.status(200).json({ 59 | message: 'user updates successfully', 60 | user: myUser, 61 | }); 62 | } catch (error) { 63 | next(error); 64 | } 65 | }; 66 | const deleteUser = async (req, res, next) => { 67 | try { 68 | const id = req.params.id; 69 | if (!id || !mongoose.isValidObjectId(id)) 70 | return next(createError(401, 'valid id is required')); 71 | 72 | let user = await User.findById(id); 73 | if (!user) return next(createError(404, 'user not found')); 74 | 75 | await User.deleteOne({ _id: id }); 76 | res.status(200).json('user deleted'); 77 | } catch (error) { 78 | next(error); 79 | } 80 | }; 81 | 82 | export default { getAllUsers, getUserById, updateUser, deleteUser }; 83 | -------------------------------------------------------------------------------- /Server/api/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | 4 | import authRoute from './routes/authRoute.js'; 5 | import userRoute from './routes/userRoute.js'; 6 | import productRoute from './routes/productRoute.js'; 7 | import orderRoute from './routes/orderRoute.js'; 8 | import cartRoute from './routes/cartRoute.js'; 9 | import reviewRoute from './routes/reviewRoute.js'; 10 | import seedRoute from './routes/seedRoute.js'; 11 | 12 | router.use('/auth', authRoute); 13 | router.use('/users', userRoute); 14 | router.use('/products', productRoute); 15 | router.use('/orders', orderRoute); 16 | router.use('/cart', cartRoute); 17 | router.use('/reviews', reviewRoute); 18 | router.use('/seed', seedRoute); 19 | router.use('/', (req, res) => { 20 | res.json('this is api'); 21 | }); 22 | 23 | export default router; 24 | -------------------------------------------------------------------------------- /Server/api/routes/authRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import { googleAuth, login, singup } from '../controllers/authController.js'; 4 | import { verifyAuth, verifyAdmin } from '../../middlewares/authMiddleware.js'; 5 | 6 | router.post('/signup', singup); 7 | 8 | router.post('/login', login); 9 | 10 | router.post('/googleAuth', googleAuth); 11 | 12 | export default router; 13 | -------------------------------------------------------------------------------- /Server/api/routes/cartRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import cartController from '../controllers/cartController.js'; 4 | import { verifyAuth } from '../../middlewares/authMiddleware.js'; 5 | 6 | router.use(verifyAuth); 7 | 8 | router.get('/', cartController.getUserCart); 9 | 10 | router.post('/', cartController.addToCart); 11 | 12 | router.post('/checkout', cartController.checkout); 13 | 14 | router.delete('/', cartController.deleteCart); 15 | router.patch('/', cartController.deleteItem); 16 | 17 | export default router; 18 | -------------------------------------------------------------------------------- /Server/api/routes/orderRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import orderController from '../controllers/orderController.js'; 4 | import { verifyAuth, verifyAdmin } from '../../middlewares/authMiddleware.js'; 5 | 6 | router.get('/', verifyAuth, verifyAdmin, orderController.getAllOrders); 7 | router.get('/dashboard', verifyAuth, verifyAdmin, orderController.dashboard); 8 | router.get('/mine', verifyAuth, orderController.getUserOrders); 9 | router.get('/:id', orderController.getOrderById); 10 | 11 | router.post('/', verifyAuth, orderController.addOrder); 12 | router.delete('/:id', verifyAuth, verifyAdmin, orderController.deleteOrder); 13 | 14 | // not admin as i could pay & change it 15 | router.put('/:id', verifyAuth, orderController.editOrder); 16 | router.post('/:id/stripe', verifyAuth, orderController.stripePayment); 17 | router.put('/:id/pay', verifyAuth, orderController.payOrder); 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /Server/api/routes/productRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import productController from '../controllers/productController.js'; 4 | import { verifyAuth, verifyAdmin } from '../../middlewares/authMiddleware.js'; 5 | import upload from '../../utils/upload.js'; 6 | 7 | router.get('/', productController.getAllProducts); 8 | router.get('/search', productController.search); 9 | router.get('/categories', productController.getCategories); 10 | router.get('/:id', productController.getProductById); 11 | 12 | // router.post('/upload', upload.single('image'), productController.addProduct); 13 | router.post( 14 | '/', 15 | verifyAuth, 16 | verifyAdmin, 17 | upload.single('image'), 18 | productController.addProduct, 19 | ); 20 | router.post('/', verifyAuth, verifyAdmin, productController.addProduct); 21 | router.delete('/:id', verifyAuth, verifyAdmin, productController.deleteProduct); 22 | router.put('/:id', verifyAuth, verifyAdmin, productController.editProduct); 23 | 24 | export default router; 25 | -------------------------------------------------------------------------------- /Server/api/routes/reviewRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import reviewController from '../controllers/reviewController.js'; 4 | import { verifyAuth, verifyAdmin } from '../../middlewares/authMiddleware.js'; 5 | 6 | router.post('/:id', verifyAuth, reviewController.addReview); 7 | router.get('/:id', reviewController.getProductReviews); 8 | router.delete('/:id', verifyAuth, verifyAdmin, reviewController.deleteReview); 9 | 10 | export default router; 11 | -------------------------------------------------------------------------------- /Server/api/routes/seedRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import Product from '../../models/productModel.js'; 3 | import User from '../../models/userModel.js'; 4 | import data from '../../data/index.js'; 5 | 6 | const seedRouter = express.Router(); 7 | 8 | seedRouter.get('/', async (req, res) => { 9 | await Product.deleteMany({}); 10 | const createdProducts = await Product.insertMany(data.products); 11 | await User.deleteMany({}); 12 | const createdUsers = await User.insertMany(data.users); 13 | res.json({ 14 | message: 'seed added to DB', 15 | createdProducts, 16 | createdUsers, 17 | }); 18 | }); 19 | 20 | export default seedRouter; 21 | -------------------------------------------------------------------------------- /Server/api/routes/userRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import userController from '../controllers/userControlller.js'; 4 | 5 | import { verifyAuth, verifyAdmin } from '../../middlewares/authMiddleware.js'; 6 | 7 | router.put('/:id', verifyAuth, userController.updateUser); 8 | router.use(verifyAdmin); 9 | router.get('/', userController.getAllUsers); 10 | router.get('/:id', userController.getUserById); 11 | 12 | router.delete('/:id', userController.deleteUser); 13 | 14 | export default router; 15 | -------------------------------------------------------------------------------- /Server/config/index.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | dotenv.config(); 3 | 4 | const { PORT, ENV, dbURI, SALT, PEPPER, JWT_PASSWORD, STRIPE_SECRET_KEY } = 5 | process.env; 6 | 7 | export default { 8 | PORT: PORT || 5000, 9 | ENV, 10 | dbURI, 11 | SALT, 12 | PEPPER, 13 | JWT_PASSWORD, 14 | STRIPE_SECRET_KEY, 15 | }; 16 | -------------------------------------------------------------------------------- /Server/data/index.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs'; 2 | 3 | const data = { 4 | users: [ 5 | { 6 | name: 'admin', 7 | email: 'admin@gmail.com', 8 | password: bcrypt.hashSync('123456'), 9 | isAdmin: true, 10 | }, 11 | { 12 | name: 'test', 13 | email: 'test@gmail.com', 14 | password: bcrypt.hashSync('123456'), 15 | isAdmin: false, 16 | }, 17 | { 18 | name: 'omar', 19 | email: 'omar@gmail.com', 20 | password: bcrypt.hashSync('123456'), 21 | isAdmin: false, 22 | }, 23 | { 24 | name: 'test2', 25 | email: 'test2@gmail.com', 26 | password: bcrypt.hashSync('123456'), 27 | isAdmin: false, 28 | }, 29 | ], 30 | products: [ 31 | { 32 | name: 'Nike Slim shirt', 33 | description: 'high quality shirt', 34 | category: ['Shirts'], 35 | price: 120, 36 | countInStock: 10, 37 | brand: 'Nike', 38 | rating: 3, 39 | numReviews: 10, 40 | image: '/images/p1.jpg', // 679px × 829px 41 | }, 42 | { 43 | name: 'Adidas Fit Shirt', 44 | category: ['Shirts'], 45 | image: '/images/p2.jpg', 46 | price: 250, 47 | countInStock: 0, 48 | brand: 'Adidas', 49 | rating: 4.0, 50 | numReviews: 10, 51 | description: 'high quality product', 52 | }, 53 | { 54 | name: 'Nike Slim Pant', 55 | category: ['Pants'], 56 | image: '/images/p3.jpg', 57 | price: 25, 58 | countInStock: 15, 59 | brand: 'Nike', 60 | rating: 4.5, 61 | numReviews: 14, 62 | description: 'high quality product', 63 | }, 64 | { 65 | name: 'Adidas Fit Pant', 66 | category: ['Pants'], 67 | image: '/images/p4.jpg', 68 | price: 65, 69 | countInStock: 5, 70 | brand: 'Puma', 71 | rating: 4.5, 72 | numReviews: 10, 73 | description: 'high quality product', 74 | }, 75 | ], 76 | }; 77 | export default data; 78 | -------------------------------------------------------------------------------- /Server/middlewares/authMiddleware.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import config from '../config/index.js'; 3 | import createError from '../utils/createError.js'; 4 | 5 | // Verify 6 | const verifyAuth = (req, res, next) => { 7 | const token = req.headers.authorization; 8 | const JWT_KEY = config.JWT_PASSWORD; 9 | 10 | if (!token) next(createError(401, 'No token provided')); 11 | try { 12 | const decoded = jwt.verify(token, JWT_KEY); 13 | req.userData = decoded; 14 | next(); 15 | } catch (err) { 16 | next(err); 17 | } 18 | }; 19 | const verifyAdmin = (req, res, next) => { 20 | const token = req.headers.authorization; 21 | const JWT_KEY = config.JWT_PASSWORD; 22 | 23 | if (!token) next(createError(401, 'No token provided')); 24 | try { 25 | const decoded = jwt.verify(token, JWT_KEY); 26 | if (decoded.isAdmin) { 27 | next(); 28 | } else { 29 | next(createError(404, 'you are not admin')); 30 | } 31 | } catch (err) { 32 | next(err); 33 | } 34 | }; 35 | 36 | export { verifyAdmin, verifyAuth }; 37 | -------------------------------------------------------------------------------- /Server/middlewares/errorHandler.js: -------------------------------------------------------------------------------- 1 | const handleRouteError = (err, req, res, next) => { 2 | const status = err.status ?? 500; 3 | const message = err.message ?? 'Something went wrong!'; 4 | return res.status(status).json({ 5 | success: false, 6 | status, 7 | message, 8 | }); 9 | }; 10 | 11 | // handle not found routes 12 | const handleNotFound = (req, res) => { 13 | res.status(404); 14 | res.json({ 15 | error: { 16 | message: 'this route is not found', 17 | }, 18 | }); 19 | }; 20 | 21 | export { handleNotFound, handleRouteError }; 22 | -------------------------------------------------------------------------------- /Server/models/cartModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | var subProduct = mongoose.Schema( 4 | { 5 | quantity: { 6 | type: Number, 7 | default: 1, 8 | }, 9 | product: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: 'Product', 12 | }, 13 | }, 14 | { _id: false }, 15 | ); 16 | 17 | const cartSchema = new mongoose.Schema( 18 | { 19 | userId: { 20 | type: mongoose.Schema.Types.ObjectId, 21 | ref: 'User', 22 | required: [true, 'userId is required'], 23 | unique: [true, 'cart already exist'], 24 | }, 25 | items: { 26 | type: [subProduct], 27 | default: [], 28 | }, 29 | }, 30 | { timestamps: true }, 31 | ); 32 | 33 | const Cart = mongoose.model('Cart', cartSchema); 34 | 35 | export default Cart; 36 | -------------------------------------------------------------------------------- /Server/models/orderModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | var subProduct = mongoose.Schema( 4 | { 5 | quantity: { 6 | type: Number, 7 | default: 1, 8 | }, 9 | product: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: 'Product', 12 | }, 13 | }, 14 | { _id: false }, 15 | ); 16 | 17 | const orderSchema = new mongoose.Schema( 18 | { 19 | userId: { 20 | type: mongoose.Schema.Types.ObjectId, 21 | ref: 'User', 22 | required: [true, 'userId is required'], 23 | }, 24 | items: { 25 | type: [subProduct], 26 | }, 27 | totalPrice: { 28 | type: Number, 29 | default: 0, 30 | }, 31 | isPaid: { 32 | type: Boolean, 33 | default: false, 34 | }, 35 | paymentMethod: { 36 | type: String, 37 | default: 'paypal', 38 | }, 39 | isDelivered: { 40 | type: Boolean, 41 | default: false, 42 | }, 43 | address: { 44 | type: String, 45 | default: 'user address', 46 | }, 47 | paidAt: { 48 | type: Date, 49 | }, 50 | DeliveredAt: { 51 | type: Date, 52 | }, 53 | }, 54 | { timestamps: true }, 55 | ); 56 | 57 | const Order = mongoose.model('Order', orderSchema); 58 | 59 | export default Order; 60 | -------------------------------------------------------------------------------- /Server/models/productModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const productSchema = new mongoose.Schema( 4 | { 5 | name: { 6 | type: String, 7 | unique: [true, 'product name must be unique'], 8 | required: [true, 'Name is required'], 9 | }, 10 | description: { 11 | type: String, 12 | required: [true, 'description is required'], 13 | }, 14 | category: { 15 | type: [String], 16 | default: ['clothes'], 17 | }, 18 | price: { 19 | type: Number, 20 | validate: { 21 | validator: (value) => value > 0, 22 | message: 'price must be > 0', 23 | }, 24 | }, 25 | countInStock: { 26 | type: Number, 27 | default: 5, 28 | validate: { 29 | validator: (value) => value >= 0, 30 | message: 'countInStock must be > 0', 31 | }, 32 | }, 33 | image: { 34 | type: String, 35 | default: 'http://via.placeholder.com/150', 36 | }, 37 | brand: { 38 | type: String, 39 | default: 'clothes', 40 | }, 41 | rating: { 42 | type: Number, 43 | default: 3, 44 | validate: { 45 | validator: (value) => value >= 0 && value <= 5, 46 | message: 'rating must be between [0 : 5]', 47 | }, 48 | }, 49 | numReviews: { 50 | type: Number, 51 | default: 5, 52 | validate: { 53 | validator: (value) => value >= 0, 54 | message: 'num Reviews must be >= 0', 55 | }, 56 | }, 57 | }, 58 | { timestamps: true }, 59 | ); 60 | 61 | const Product = mongoose.model('Product', productSchema); 62 | 63 | export default Product; 64 | -------------------------------------------------------------------------------- /Server/models/reviewModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const reviewSchema = new mongoose.Schema( 4 | { 5 | productId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: 'Product', 8 | required: [true, 'userId is required'], 9 | }, 10 | name: { type: String, required: true }, 11 | comment: { type: String, required: true }, 12 | rating: { type: Number, required: true }, 13 | }, 14 | { 15 | timestamps: true, 16 | }, 17 | ); 18 | 19 | const Review = mongoose.model('Review', reviewSchema); 20 | 21 | export default Review; 22 | -------------------------------------------------------------------------------- /Server/models/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const userSchema = new mongoose.Schema( 4 | { 5 | name: { 6 | type: String, 7 | required: [true, 'Name is required'], 8 | }, 9 | email: { 10 | type: String, 11 | required: [true, 'Email is required'], 12 | unique: [true, 'Email is already in use'], 13 | }, 14 | password: { 15 | type: String, 16 | minlength: [6, 'Password must be at least 6 characters'], 17 | required: [true, 'Password is required'], 18 | }, 19 | image: { 20 | type: String, 21 | default: 'https://robohash.org/YOUR-TEXT.png', 22 | }, 23 | isAdmin: { 24 | type: Boolean, 25 | default: false, 26 | }, 27 | }, 28 | { timestamps: true }, 29 | ); 30 | 31 | const User = mongoose.model('User', userSchema); 32 | 33 | export default User; 34 | -------------------------------------------------------------------------------- /Server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "node server.js", 9 | "dev": "nodemon server.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "bcryptjs": "^2.4.3", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.0.2", 19 | "express": "^4.18.1", 20 | "jsonwebtoken": "^8.5.1", 21 | "mongoose": "^6.6.1", 22 | "morgan": "^1.10.0", 23 | "multer": "^1.4.5-lts.1", 24 | "stripe": "^10.11.0" 25 | }, 26 | "devDependencies": { 27 | "nodemon": "^2.0.20" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Server/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import morgan from 'morgan'; 3 | import cors from 'cors'; 4 | import mongoose from 'mongoose'; 5 | import allRoutes from './api/index.js'; 6 | import config from './config/index.js'; 7 | import { 8 | handleNotFound, 9 | handleRouteError, 10 | } from './middlewares/errorHandler.js'; 11 | 12 | const app = express(); 13 | // Middlewares 14 | app.use(cors()); 15 | app.use(express.urlencoded({ extended: true })); // send nested objects 16 | app.use(express.json()); // serve static files 17 | if (config.ENV === 'dev') { 18 | app.use(morgan('dev')); 19 | } 20 | 21 | mongoose 22 | .connect(config.dbURI, { 23 | useNewUrlParser: true, 24 | useUnifiedTopology: true, 25 | }) 26 | .then((result) => { 27 | console.log('connected to db '); 28 | console.log(`serve at http://localhost:${config.PORT}`); 29 | app.listen(config.PORT); 30 | }) 31 | .catch((err) => { 32 | console.log(`couldn't connect to db `, err); 33 | process.exit(0); 34 | }); 35 | 36 | // Routes which should handle requests 37 | app.use('/images', express.static('upload/images')); 38 | app.use('/api', allRoutes); 39 | app.use(handleRouteError); // handle errors 40 | app.use('*', handleNotFound); // handle not found routes 41 | 42 | export default app; 43 | -------------------------------------------------------------------------------- /Server/services/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Server/services/index.js -------------------------------------------------------------------------------- /Server/upload/images/p1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Server/upload/images/p1.jpg -------------------------------------------------------------------------------- /Server/upload/images/p2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Server/upload/images/p2.jpg -------------------------------------------------------------------------------- /Server/upload/images/p3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Server/upload/images/p3.jpg -------------------------------------------------------------------------------- /Server/upload/images/p4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/Server/upload/images/p4.jpg -------------------------------------------------------------------------------- /Server/utils/createError.js: -------------------------------------------------------------------------------- 1 | const createError = (status, message) => { 2 | const err = new Error(); 3 | err.status = status; 4 | err.message = message; 5 | return err; 6 | }; 7 | 8 | export default createError; 9 | -------------------------------------------------------------------------------- /Server/utils/upload.js: -------------------------------------------------------------------------------- 1 | import multer from 'multer'; 2 | import path from 'path'; 3 | 4 | const storage = multer.diskStorage({ 5 | destination: './upload/images', 6 | filename: (req, file, cb) => { 7 | return cb( 8 | null, 9 | `${file.fieldname}_${Date.now()}${path.extname(file.originalname)}`, 10 | ); 11 | }, 12 | }); 13 | 14 | const upload = multer({ 15 | storage: storage, 16 | }); 17 | 18 | export default upload; 19 | -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/10.png -------------------------------------------------------------------------------- /screenshots/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/11.png -------------------------------------------------------------------------------- /screenshots/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/12.png -------------------------------------------------------------------------------- /screenshots/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/13.png -------------------------------------------------------------------------------- /screenshots/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/14.png -------------------------------------------------------------------------------- /screenshots/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/15.png -------------------------------------------------------------------------------- /screenshots/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/16.png -------------------------------------------------------------------------------- /screenshots/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/17.png -------------------------------------------------------------------------------- /screenshots/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/18.png -------------------------------------------------------------------------------- /screenshots/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/19.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/20.png -------------------------------------------------------------------------------- /screenshots/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/21.png -------------------------------------------------------------------------------- /screenshots/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/22.png -------------------------------------------------------------------------------- /screenshots/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/23.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/3.png -------------------------------------------------------------------------------- /screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/4.png -------------------------------------------------------------------------------- /screenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/5.png -------------------------------------------------------------------------------- /screenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/6.png -------------------------------------------------------------------------------- /screenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/7.png -------------------------------------------------------------------------------- /screenshots/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/8.png -------------------------------------------------------------------------------- /screenshots/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/9.png -------------------------------------------------------------------------------- /screenshots/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/cover.jpg -------------------------------------------------------------------------------- /screenshots/responsive/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/1.png -------------------------------------------------------------------------------- /screenshots/responsive/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/10.png -------------------------------------------------------------------------------- /screenshots/responsive/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/2.png -------------------------------------------------------------------------------- /screenshots/responsive/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/3.png -------------------------------------------------------------------------------- /screenshots/responsive/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/4.png -------------------------------------------------------------------------------- /screenshots/responsive/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/5.png -------------------------------------------------------------------------------- /screenshots/responsive/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/6.png -------------------------------------------------------------------------------- /screenshots/responsive/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/7.png -------------------------------------------------------------------------------- /screenshots/responsive/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/8.png -------------------------------------------------------------------------------- /screenshots/responsive/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omar214/AmazonX/ae22212e5e40bab0d97be451e5f894dc6a749359/screenshots/responsive/9.png --------------------------------------------------------------------------------