├── .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 |
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 |
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 |
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 | ID |
79 | User |
80 | DATE |
81 | TOTAL |
82 | PAID |
83 | DELIVERED |
84 | ACTIONS |
85 |
86 |
87 |
88 | {orders.items.map((p, idx) => (
89 |
90 | {p._id} |
91 | {p.userId.name} |
92 | {moment(p.createdAt).format('MM/DD/YYYY')} |
93 |
94 |
95 | $
96 | {p.totalPrice +
97 | 0.05 * p.totalPrice +
98 | Math.floor(0.14 * p.totalPrice)}
99 |
100 | |
101 |
102 | {p.isPaid ? (
103 | Yes
104 | ) : (
105 | No
106 | )}
107 | |
108 |
109 | {p.isDelivered ? (
110 | Yes
111 | ) : (
112 | No
113 | )}
114 | |
115 |
116 |
125 |
132 | |
133 |
134 | ))}
135 |
136 |
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 |
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 | ID |
93 | Name |
94 | PRICE |
95 | CATEGORY |
96 | BRAND |
97 | ACTIONS |
98 |
99 |
100 |
101 | {products.items.map((p, idx) => (
102 |
103 | {p._id} |
104 | {p.name} |
105 |
106 | ${p.price}
107 | |
108 | {p.category[0]} |
109 | {p.brand} |
110 |
111 |
120 |
127 | |
128 |
129 | ))}
130 |
131 |
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 |
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 | ID |
76 | Name |
77 | Email |
78 | IS ADMIN |
79 | ACTIONS |
80 |
81 |
82 |
83 | {users.items.map((p, idx) => (
84 |
85 | {p._id} |
86 | {p.name} |
87 | {p.email} |
88 |
89 | {p.isAdmin ? (
90 | Yes
91 | ) : (
92 | No
93 | )}
94 | |
95 |
96 |
105 |
112 | |
113 |
114 | ))}
115 |
116 |
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 |
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 | ID |
60 | DATE |
61 | TOTAL |
62 | PAID |
63 | DELIVERED |
64 | ACTIONS |
65 |
66 |
67 |
68 | {orders.items.map((p, idx) => (
69 |
70 | {p._id} |
71 | {moment(p.createdAt).format('MM/DD/YYYY')} |
72 |
73 | ${p.totalPrice}
74 | |
75 |
76 | {p.isPaid ? (
77 | Yes
78 | ) : (
79 | No
80 | )}
81 | |
82 |
83 | {p.isDelivered ? (
84 | Yes
85 | ) : (
86 | No
87 | )}
88 | |
89 |
90 |
93 | {/* Details */}
94 | |
95 |
96 | ))}
97 |
98 |
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 |
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 |
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 |
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 |
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 | 
193 |
194 |
195 |
196 | - Log in
197 | 
198 |
199 |
200 |
201 | - Home page
202 | 
203 |
204 |
205 |
206 | - Product Page
207 | 
208 |
209 |
210 |
211 | - Reviews
212 | 
213 |
214 |
215 |
216 | - Cart
217 | 
218 |
219 |
220 |
221 | - Check out Steps
222 | 
223 | 
224 | 
225 |
226 |
227 |
228 | - order page
229 | 
230 |
231 |
232 |
233 | - paypal Payment
234 | 
235 | 
236 | 
237 |
238 |
239 |
240 | - Stripe Payment
241 | 
242 |
243 |
244 |
245 | - Search Page
246 | 
247 |
248 |
249 |
250 | - Admin Dashboard
251 | 
252 | 
253 |
254 |
255 |
256 | - Admin All Orders Details
257 | 
258 |
259 |
260 |
261 | - Admin All Product Details
262 | 
263 |
264 |
265 |
266 | - Add Product
267 | 
268 |
269 |
270 |
271 | - Edit Product
272 | 
273 |
274 |
275 |
276 | - Admin All Users details
277 | 
278 |
279 |
280 |
281 | - Edit User
282 | 
283 |
284 |
285 |
286 | - Mobile Responsive Design
287 |
288 | - Home Page
289 |
290 | 
291 |
292 |
293 |
294 | - Product Page
295 |
296 | 
297 |
298 |
299 |
300 | - Reviews
301 |
302 | 
303 |
304 |
305 |
306 | - Cart
307 |
308 | 
309 |
310 |
311 |
312 | - Check out
313 |
314 | 
315 |
316 |
317 |
318 | 
319 |
320 |
321 |
322 | 
323 |
324 |
325 |
326 | - DashBoard
327 |
328 | 
329 |
330 |
331 |
332 | 
333 |
334 |
335 |
336 | - Add Product
337 |
338 | 
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
--------------------------------------------------------------------------------