├── .gitignore
├── README.md
├── assets
├── 2022-08-03_094154.png
├── 2022-08-03_094255.png
├── 2022-08-03_094343.png
├── 2022-08-03_094443.png
├── 2022-08-03_094522.png
├── 2022-08-03_094556.png
└── 2022-08-03_094720.png
├── client
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── actions
│ ├── cartActions.js
│ ├── orderActions.js
│ ├── productAction.js
│ └── userActions.js
│ ├── assets
│ ├── home.png
│ └── logo2.png
│ ├── components
│ ├── Links
│ │ ├── Links.js
│ │ └── Links.module.scss
│ ├── MetaData.js
│ ├── admin
│ │ ├── navbar
│ │ │ ├── Navbar.js
│ │ │ └── Navbar.module.scss
│ │ └── sidebar
│ │ │ ├── Sidebar.js
│ │ │ └── Sidebar.module.scss
│ ├── announcement
│ │ ├── Announcement.js
│ │ └── Announcement.module.scss
│ ├── footer
│ │ ├── Footer.js
│ │ └── Footer.module.scss
│ ├── header
│ │ ├── Navbar.js
│ │ ├── Navbar.scss
│ │ └── Search.js
│ ├── loader
│ │ ├── ButtonLoader.js
│ │ ├── ButtonLoader.scss
│ │ └── Loader.js
│ ├── profileLinks
│ │ ├── ProfileLink.js
│ │ └── ProfileLink.module.scss
│ ├── route
│ │ └── ProtectedRoute.js
│ └── widget
│ │ ├── Widget.js
│ │ └── Widget.scss
│ ├── config.js
│ ├── constants
│ ├── cartConstants.js
│ ├── orderConstants.js
│ ├── productsConstants.js
│ └── userConstants.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── pages
│ ├── about
│ │ ├── About.js
│ │ └── About.module.scss
│ ├── admin
│ │ ├── dashboard
│ │ │ ├── Dashboard.js
│ │ │ └── Dashboard.module.scss
│ │ ├── newProduct
│ │ │ ├── NewProduct.js
│ │ │ └── NewProduct.module.scss
│ │ ├── orders
│ │ │ ├── Orders.js
│ │ │ ├── Orders.module.scss
│ │ │ └── processOrder
│ │ │ │ ├── ProcessOrder.js
│ │ │ │ └── Processorder.module.scss
│ │ ├── productDetails
│ │ │ ├── ProductDetails.js
│ │ │ └── ProductDetails.module.scss
│ │ ├── productReview
│ │ │ ├── ProductReview.js
│ │ │ └── ProductReview.module.scss
│ │ ├── products
│ │ │ ├── ProductsList.js
│ │ │ └── ProductsList.module.scss
│ │ ├── updateProduct
│ │ │ ├── UpdateProduct.js
│ │ │ └── UpdateProduct.module.scss
│ │ └── users
│ │ │ ├── Users.js
│ │ │ ├── Users.module.scss
│ │ │ └── userDetails
│ │ │ ├── UserDetails.js
│ │ │ └── UserDetails.module.scss
│ ├── auth
│ │ ├── forgotPassword
│ │ │ ├── ForgotPassword.js
│ │ │ └── ForgotPassword.module.scss
│ │ ├── login
│ │ │ ├── Login.js
│ │ │ └── Login.module.scss
│ │ ├── register
│ │ │ ├── Register.js
│ │ │ └── Register.module.scss
│ │ └── resetPassword
│ │ │ ├── ResetPassword.js
│ │ │ └── ResetPassword.module.scss
│ ├── cart
│ │ ├── Cart.js
│ │ ├── Cart.module.scss
│ │ ├── checkoutSteps
│ │ │ ├── CheckoutSteps.js
│ │ │ └── CheckoutSteps.module.scss
│ │ ├── confirmOrder
│ │ │ ├── ConfirmOrder.js
│ │ │ └── ConfirmOrder.module.scss
│ │ ├── payment
│ │ │ ├── Payment.js
│ │ │ └── Payment.module.scss
│ │ ├── shipping
│ │ │ ├── Shipping.js
│ │ │ └── Shipping.module.scss
│ │ └── success
│ │ │ ├── Success.js
│ │ │ └── Success.module.scss
│ ├── contact
│ │ ├── Contact.js
│ │ └── Contact.module.scss
│ ├── home
│ │ ├── Home.js
│ │ ├── banner
│ │ │ ├── Banner.js
│ │ │ └── Banner.module.scss
│ │ ├── category
│ │ │ ├── Category.js
│ │ │ └── Category.module.scss
│ │ └── fashion
│ │ │ ├── Fashion.js
│ │ │ └── Fashion.module.scss
│ ├── products
│ │ ├── Product.js
│ │ ├── Products.js
│ │ └── Products.module.scss
│ ├── reviews
│ │ └── ListReview.js
│ ├── singleProduct
│ │ ├── SingleProduct.js
│ │ └── SingleProduct.module.scss
│ └── user
│ │ ├── Profile.js
│ │ ├── Profile.module.scss
│ │ ├── changePassword
│ │ ├── ChangePassword.js
│ │ └── ChangePassword.module.scss
│ │ ├── myOrders
│ │ ├── MyOrders.js
│ │ └── MyOrders.module.scss
│ │ ├── orderDetails
│ │ ├── OrderDetails.js
│ │ └── OrderDetails.module.scss
│ │ └── updateProfile
│ │ ├── UpdateProfile.js
│ │ └── UpdateProfile.module.scss
│ ├── reducers
│ ├── cartReducers.js
│ ├── orderReducers.js
│ ├── productReducers.js
│ └── userReducers.js
│ ├── reportWebVitals.js
│ ├── setupTests.js
│ └── store.js
├── config
└── database.js
├── controller
├── authController.js
├── orderController.js
├── paymentController.js
└── productController.js
├── index.js
├── middleware
├── auth.js
├── catchAsyncErrors.js
└── error.js
├── models
├── order.js
├── product.js
└── user.js
├── package-lock.json
├── package.json
├── routes
├── auth.js
├── order.js
├── payment.js
└── product.js
└── utils
├── apiFeatures.js
├── errorHandler.js
├── jwtToken.js
└── sendEmail.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MERN Stack Full Ecommerce Site
2 |
3 | Powerful MERN Stack Ecommerce Project using React, Redux, Node.js, Express, MongoDB, Stripe.We are going to using Redux for state management, stipe to handle our payments, Cloudinary to handle our images, and follow the best practices to implement Authentication & Authorization.
4 |
5 | ## Features
6 |
7 | - Build powerful and fully functional E-commerce website using MERN
8 | - Redux (Best state management tool)
9 | - Authentication using cookies
10 | - Complete Admin Dashboard to manage products, orders, reviews, users
11 | - Add third party site like cloudinary to upload images
12 | - Payment Integration using Stripe
13 | - Add filters, search and pagination
14 | - Complete ratings & reviews system
15 | - Complete Cart & Checkout process
16 |
17 | [Live Site](https://shopx-mern-app.herokuapp.com/)
18 |
19 | ## Homepage
20 |
21 |
22 |
23 | ## Product Page
24 |
25 |
26 |
27 | ## Product Details Page
28 |
29 |
30 |
31 | ## Cart Page
32 |
33 |
34 |
35 | ## Order Details
36 |
37 |
38 |
--------------------------------------------------------------------------------
/assets/2022-08-03_094154.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/assets/2022-08-03_094154.png
--------------------------------------------------------------------------------
/assets/2022-08-03_094255.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/assets/2022-08-03_094255.png
--------------------------------------------------------------------------------
/assets/2022-08-03_094343.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/assets/2022-08-03_094343.png
--------------------------------------------------------------------------------
/assets/2022-08-03_094443.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/assets/2022-08-03_094443.png
--------------------------------------------------------------------------------
/assets/2022-08-03_094522.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/assets/2022-08-03_094522.png
--------------------------------------------------------------------------------
/assets/2022-08-03_094556.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/assets/2022-08-03_094556.png
--------------------------------------------------------------------------------
/assets/2022-08-03_094720.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/assets/2022-08-03_094720.png
--------------------------------------------------------------------------------
/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": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.8.2",
7 | "@emotion/styled": "^11.8.1",
8 | "@mui/material": "^5.5.2",
9 | "@mui/x-data-grid": "^5.7.0",
10 | "@stripe/react-stripe-js": "^1.7.0",
11 | "@stripe/stripe-js": "^1.25.0",
12 | "@testing-library/jest-dom": "^5.16.2",
13 | "@testing-library/react": "^12.1.4",
14 | "@testing-library/user-event": "^13.5.0",
15 | "axios": "^0.26.1",
16 | "bootstrap": "^5.1.3",
17 | "countries-list": "^2.6.1",
18 | "framer-motion": "^6.2.8",
19 | "rc-slider": "^9.6.5",
20 | "react": "^17.0.2",
21 | "react-alert": "^7.0.3",
22 | "react-alert-template-basic": "^1.0.2",
23 | "react-bootstrap": "^2.2.1",
24 | "react-dom": "^17.0.2",
25 | "react-helmet": "^6.1.0",
26 | "react-icons": "^4.3.1",
27 | "react-js-pagination": "^3.0.3",
28 | "react-paginate": "^8.1.2",
29 | "react-redux": "^7.2.6",
30 | "react-router-dom": "^5.3.0",
31 | "react-scripts": "5.0.0",
32 | "redux": "^4.1.2",
33 | "redux-devtools-extension": "^2.13.9",
34 | "redux-thunk": "^2.4.1",
35 | "sass": "^1.49.9",
36 | "swiper": "^8.0.7",
37 | "web-vitals": "^2.1.4"
38 | },
39 | "scripts": {
40 | "start": "react-scripts start",
41 | "build": "react-scripts build",
42 | "test": "react-scripts test",
43 | "eject": "react-scripts eject"
44 | },
45 | "eslintConfig": {
46 | "extends": [
47 | "react-app",
48 | "react-app/jest"
49 | ]
50 | },
51 | "browserslist": {
52 | "production": [
53 | ">0.2%",
54 | "not dead",
55 | "not op_mini all"
56 | ],
57 | "development": [
58 | "last 1 chrome version",
59 | "last 1 firefox version",
60 | "last 1 safari version"
61 | ]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 | React App
22 |
23 |
24 | You need to enable JavaScript to run this app.
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/client/public/logo512.png
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/src/App.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/client/src/actions/cartActions.js:
--------------------------------------------------------------------------------
1 | import { axiosInstance } from "../config";
2 | import {
3 | ADD_TO_CART,
4 | REMOVE_ITEM_CART,
5 | SAVE_SHIPPING_INFO,
6 | } from "../constants/cartConstants";
7 |
8 | export const addItemToCart = (id, quantity) => async (dispatch, getState) => {
9 | const { data } = await axiosInstance.get(`/api/v1/product/${id}`);
10 |
11 | dispatch({
12 | type: ADD_TO_CART,
13 | payload: {
14 | product: data.product._id,
15 | name: data.product.name,
16 | price: data.product.price,
17 | image: data.product.images[0].url,
18 | stock: data.product.stock,
19 | quantity,
20 | },
21 | });
22 |
23 | localStorage.setItem(
24 | "cartItems",
25 | JSON.stringify(getState().cart.cartItems)
26 | );
27 | };
28 |
29 | export const removeItemFromCart = (id) => async (dispatch, getState) => {
30 | dispatch({
31 | type: REMOVE_ITEM_CART,
32 | payload: id,
33 | });
34 |
35 | localStorage.setItem(
36 | "cartItems",
37 | JSON.stringify(getState().cart.cartItems)
38 | );
39 | };
40 |
41 | export const saveShippingInfo = (data) => async (dispatch) => {
42 | dispatch({
43 | type: SAVE_SHIPPING_INFO,
44 | payload: data,
45 | });
46 |
47 | localStorage.setItem("shippingInfo", JSON.stringify(data));
48 | };
49 |
--------------------------------------------------------------------------------
/client/src/actions/orderActions.js:
--------------------------------------------------------------------------------
1 | import { axiosInstance } from "../config";
2 | import {
3 | CREATE_ORDER_REQUEST,
4 | CREATE_ORDER_SUCCESS,
5 | CREATE_ORDER_FAIL,
6 | CLEAR_ERRORS,
7 | ALL_ORDERS_REQUEST,
8 | ALL_ORDERS_SUCCESS,
9 | ALL_ORDERS_FAIL,
10 | UPDATE_ORDER_REQUEST,
11 | UPDATE_ORDER_SUCCESS,
12 | UPDATE_ORDER_FAIL,
13 | DELETE_ORDER_REQUEST,
14 | DELETE_ORDER_SUCCESS,
15 | DELETE_ORDER_FAIL,
16 | ORDER_DETAILS_REQUEST,
17 | ORDER_DETAILS_SUCCESS,
18 | ORDER_DETAILS_FAIL,
19 | MY_ORDERS_REQUEST,
20 | MY_ORDERS_SUCCESS,
21 | MY_ORDERS_FAIL,
22 | } from "../constants/orderConstants";
23 |
24 | export const createOrder = (order) => async (dispatch, getState) => {
25 | try {
26 | dispatch({ type: CREATE_ORDER_REQUEST });
27 |
28 | const config = {
29 | headers: {
30 | "Content-Type": "application/json",
31 | },
32 | };
33 |
34 | const { data } = await axiosInstance.post(
35 | "/api/v1/order/new",
36 | order,
37 | config
38 | );
39 |
40 | dispatch({
41 | type: CREATE_ORDER_SUCCESS,
42 | payload: data,
43 | });
44 | } catch (error) {
45 | dispatch({
46 | type: CREATE_ORDER_FAIL,
47 | payload: error.response.data.message,
48 | });
49 | }
50 | };
51 |
52 | // Get curretly logged in user orders
53 | export const myOrders = () => async (dispatch) => {
54 | try {
55 | dispatch({ type: MY_ORDERS_REQUEST });
56 |
57 | const { data } = await axiosInstance.get("/api/v1/orders/me");
58 |
59 | dispatch({
60 | type: MY_ORDERS_SUCCESS,
61 | payload: data.orders,
62 | });
63 | } catch (error) {
64 | dispatch({
65 | type: MY_ORDERS_FAIL,
66 | payload: error.response.data.message,
67 | });
68 | }
69 | };
70 |
71 | // Get all orders - ADMIN
72 | export const allOrders = () => async (dispatch) => {
73 | try {
74 | dispatch({ type: ALL_ORDERS_REQUEST });
75 |
76 | const { data } = await axiosInstance.get(`/api/v1/admin/orders`);
77 |
78 | dispatch({
79 | type: ALL_ORDERS_SUCCESS,
80 | payload: data,
81 | });
82 | } catch (error) {
83 | dispatch({
84 | type: ALL_ORDERS_FAIL,
85 | payload: error.response.data.message,
86 | });
87 | }
88 | };
89 |
90 | // update order
91 | export const updateOrder = (id, orderData) => async (dispatch) => {
92 | try {
93 | dispatch({ type: UPDATE_ORDER_REQUEST });
94 |
95 | const config = {
96 | headers: {
97 | "Content-Type": "application/json",
98 | },
99 | };
100 |
101 | const { data } = await axiosInstance.put(
102 | `/api/v1/admin/order/${id}`,
103 | orderData,
104 | config
105 | );
106 |
107 | dispatch({
108 | type: UPDATE_ORDER_SUCCESS,
109 | payload: data.success,
110 | });
111 | } catch (error) {
112 | dispatch({
113 | type: UPDATE_ORDER_FAIL,
114 | payload: error.response.data.message,
115 | });
116 | }
117 | };
118 |
119 | // Delete order
120 | export const deleteOrder = (id) => async (dispatch) => {
121 | try {
122 | dispatch({ type: DELETE_ORDER_REQUEST });
123 |
124 | const { data } = await axiosInstance.delete(
125 | `/api/v1/admin/order/${id}`
126 | );
127 |
128 | dispatch({
129 | type: DELETE_ORDER_SUCCESS,
130 | payload: data.success,
131 | });
132 | } catch (error) {
133 | dispatch({
134 | type: DELETE_ORDER_FAIL,
135 | payload: error.response.data.message,
136 | });
137 | }
138 | };
139 |
140 | // Get order details
141 | export const getOrderDetails = (id) => async (dispatch) => {
142 | try {
143 | dispatch({ type: ORDER_DETAILS_REQUEST });
144 |
145 | const { data } = await axiosInstance.get(`/api/v1/order/${id}`);
146 |
147 | dispatch({
148 | type: ORDER_DETAILS_SUCCESS,
149 | payload: data.order,
150 | });
151 | } catch (error) {
152 | dispatch({
153 | type: ORDER_DETAILS_FAIL,
154 | payload: error.response.data.message,
155 | });
156 | }
157 | };
158 |
159 | // Clear Errors
160 | export const clearErrors = () => async (dispatch) => {
161 | dispatch({
162 | type: CLEAR_ERRORS,
163 | });
164 | };
165 |
--------------------------------------------------------------------------------
/client/src/assets/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/client/src/assets/home.png
--------------------------------------------------------------------------------
/client/src/assets/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/client/src/assets/logo2.png
--------------------------------------------------------------------------------
/client/src/components/Links/Links.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | import styles from "./Links.module.scss";
4 |
5 | const Links = () => {
6 | return (
7 |
8 | Home
9 | Products
10 | Contact
11 | About
12 |
13 | );
14 | };
15 |
16 | export default Links;
17 |
--------------------------------------------------------------------------------
/client/src/components/Links/Links.module.scss:
--------------------------------------------------------------------------------
1 | .links {
2 | height: 50px;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | border-bottom: 1px solid black;
7 |
8 | a {
9 | margin: 0 1rem;
10 | text-decoration: none;
11 | text-transform: uppercase;
12 | font-weight: 500;
13 | letter-spacing: 0.5px;
14 | color: black;
15 | font-family: var(--font-poppins);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/client/src/components/MetaData.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Helmet } from "react-helmet";
3 |
4 | const MetaData = ({ title }) => {
5 | return (
6 |
7 | {`${title} - ShopX`}
8 |
9 | );
10 | };
11 |
12 | export default MetaData;
13 |
--------------------------------------------------------------------------------
/client/src/components/admin/navbar/Navbar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styles from "./Navbar.module.scss";
3 |
4 | const Navbar = () => {
5 | return (
6 |
7 |
Admin
8 |
9 | );
10 | };
11 |
12 | export default Navbar;
13 |
--------------------------------------------------------------------------------
/client/src/components/admin/navbar/Navbar.module.scss:
--------------------------------------------------------------------------------
1 | .navbar {
2 | height: 60px;
3 | background: rgba(230, 228, 228, 0.25);
4 | // box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
5 | // backdrop-filter: blur(4px);
6 | // -webkit-backdrop-filter: blur(4px);
7 | border: 1px solid rgba(255, 255, 255, 0.18);
8 | padding: 0 1rem;
9 |
10 | display: flex;
11 | align-items: center;
12 | font-family: var(--font-poppins);
13 | width: 100%;
14 | }
15 |
--------------------------------------------------------------------------------
/client/src/components/admin/sidebar/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | import { MdOutlineDashboard, MdOutlineFavoriteBorder } from "react-icons/md";
4 | import { BiUserCircle } from "react-icons/bi";
5 | import { FiPlusSquare } from "react-icons/fi";
6 |
7 | import styles from "./Sidebar.module.scss";
8 | import { HiTemplate } from "react-icons/hi";
9 | import { AiFillStar, AiOutlineHome } from "react-icons/ai";
10 |
11 | const Sidebar = () => {
12 | return (
13 |
14 |
19 |
20 |
Main
21 |
22 |
23 |
24 |
25 | Home
26 |
27 |
28 |
29 |
30 |
31 | Dashboard
32 |
33 |
34 |
35 |
36 |
37 |
List
38 |
39 |
40 |
41 | Products
42 |
43 |
44 |
45 |
46 |
50 | Orders
51 |
52 |
53 |
54 |
55 |
56 | Users
57 |
58 |
59 |
60 |
61 |
62 | Reviews
63 |
64 |
65 |
66 |
67 |
68 |
Service
69 |
70 |
71 |
72 |
73 | Add Product
74 |
75 |
76 |
77 |
78 |
79 | );
80 | };
81 |
82 | export default Sidebar;
83 |
--------------------------------------------------------------------------------
/client/src/components/admin/sidebar/Sidebar.module.scss:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | min-height: 100vh;
3 | background: rgba(230, 228, 228, 0.25);
4 | // box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
5 | border: 1px solid rgba(255, 255, 255, 0.18);
6 |
7 | font-family: var(--font-roboto);
8 | padding: 1rem;
9 | width: 100%;
10 | div {
11 | margin-top: 30px;
12 | span {
13 | font-weight: 500;
14 | letter-spacing: 1px;
15 | font-size: 16px;
16 | }
17 | div {
18 | display: flex;
19 | flex-direction: column;
20 | gap: 5px;
21 | li {
22 | list-style: none;
23 | padding: 10px 5px;
24 |
25 | &:hover {
26 | border-right: 4px solid #00ccd6;
27 | background-color: #e6ffff;
28 |
29 | border-radius: 5px;
30 | a {
31 | color: #00ccd6;
32 | }
33 | }
34 | a {
35 | text-decoration: none;
36 | font-weight: 500;
37 | letter-spacing: 1px;
38 | padding: 10px 0px;
39 | color: gray;
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/client/src/components/announcement/Announcement.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styles from "./Announcement.module.scss";
3 |
4 | const Announcement = () => {
5 | return (
6 |
7 | FREE SHIPPING ALL ACROSS THE COUNTRY !
8 |
9 | );
10 | };
11 |
12 | export default Announcement;
13 |
--------------------------------------------------------------------------------
/client/src/components/announcement/Announcement.module.scss:
--------------------------------------------------------------------------------
1 | .announcement {
2 | background-image: linear-gradient(120deg, #f6d365 0%, #fda085 100%);
3 | width: 100%;
4 | height: 40px;
5 | display: flex;
6 | margin: auto;
7 | justify-content: center;
8 | align-items: center;
9 | text-transform: uppercase;
10 | font-weight: 500;
11 | letter-spacing: 0.5px;
12 | color: black;
13 | font-family: var(--font-poppins);
14 | }
15 |
--------------------------------------------------------------------------------
/client/src/components/footer/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { AiOutlineMail } from "react-icons/ai";
3 | import styles from "./Footer.module.scss";
4 |
5 | const Footer = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | {/* about us */}
12 |
13 |
14 |
About Us
15 |
16 |
17 | Lorem ipsum dolor sit, amet consectetur
18 | adipisicing elit. Est, et atque ducimus
19 | deserunt asperiores aliquid?
20 |
21 |
22 |
23 |
24 | {/* information */}
25 |
26 |
27 |
Information
28 |
29 |
About Us
30 | Contact Us
31 | FAQs
32 | Privacy Policy
33 | Refund policy
34 | Cookie Policy
35 |
36 |
37 |
38 | {/* customer service */}
39 |
40 |
41 |
CUSTTOMER SERVICE
42 |
43 |
My Account
44 | Support Center
45 | Terms & Conditions
46 | Returns & Exchanges
47 | Shipping & Delivery
48 |
49 |
50 |
51 | {/* the optimal newsletter */}
52 |
53 |
54 |
THE OPTIMAL NEWSLETTER
55 |
56 |
57 | Lorem ipsum dolor sit amet consectetur
58 | adipisicing elit. Omnis, saepe.
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | © 2022 Optimal. All Rights Reserved.
72 |
73 |
74 | );
75 | };
76 |
77 | export default Footer;
78 |
--------------------------------------------------------------------------------
/client/src/components/footer/Footer.module.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | background-color: #fafafa;
3 | font-family: var(--font-poppins);
4 | margin-top: 25px;
5 |
6 | .footer_info {
7 | padding: 2rem;
8 |
9 | .about_us {
10 | h5 {
11 | font-weight: 500;
12 | text-transform: uppercase;
13 | }
14 | div {
15 | margin-top: 25px;
16 | p {
17 | color: var(--color-gray);
18 | }
19 | }
20 | }
21 | .information {
22 | h5 {
23 | font-weight: 500;
24 | text-transform: uppercase;
25 | }
26 | div {
27 | margin-top: 25px;
28 | li {
29 | color: var(--color-gray);
30 | list-style: none;
31 | margin: 5px 0;
32 | font-size: 16px;
33 | }
34 | }
35 | }
36 | .newsletter {
37 | h5 {
38 | font-weight: 500;
39 | text-transform: uppercase;
40 | }
41 | div {
42 | margin-top: 25px;
43 | p {
44 | color: var(--color-gray);
45 | font-size: 16px;
46 | }
47 | input {
48 | height: 35px;
49 | border: 1px solid black;
50 | outline: none;
51 | background-color: #fff;
52 | padding: 0.5rem 1rem;
53 | }
54 | button {
55 | border: none;
56 | height: 35px;
57 | padding: 0 0.5rem;
58 | background-color: black;
59 | color: white;
60 | }
61 | }
62 | }
63 | }
64 | .copyright {
65 | height: 50px;
66 | background-color: black;
67 | display: flex;
68 | align-items: center;
69 | justify-content: center;
70 |
71 | span {
72 | color: #fff;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/client/src/components/header/Search.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | const Search = ({ history }) => {
4 | const [keyword, setKeyword] = useState("");
5 |
6 | const searchHandler = (e) => {
7 | e.preventDefault();
8 |
9 | if (keyword.trim()) {
10 | history.push(`/products/search/${keyword}`);
11 | } else {
12 | history.push("/products");
13 | }
14 | };
15 | return (
16 |
29 | );
30 | };
31 |
32 | export default Search;
33 |
--------------------------------------------------------------------------------
/client/src/components/loader/ButtonLoader.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./ButtonLoader.scss";
3 |
4 | const ButtonLoader = () => {
5 | return
;
6 | };
7 |
8 | export default ButtonLoader;
9 |
--------------------------------------------------------------------------------
/client/src/components/loader/ButtonLoader.scss:
--------------------------------------------------------------------------------
1 | /* Button Loader */
2 | .lds-dual-ring {
3 | display: inline-block;
4 | width: 16px;
5 | height: 16px;
6 | }
7 | .lds-dual-ring:after {
8 | content: " ";
9 | display: block;
10 | width: 24px;
11 | height: 24px;
12 | border-radius: 50%;
13 | border: 3px solid #fff;
14 | border-color: #fff transparent #fff transparent;
15 | animation: lds-dual-ring 1.2s linear infinite;
16 | }
17 | @keyframes lds-dual-ring {
18 | 0% {
19 | transform: rotate(0deg);
20 | }
21 | 100% {
22 | transform: rotate(360deg);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/components/loader/Loader.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Spinner } from "react-bootstrap";
3 |
4 | const Loader = () => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 |
12 | export default Loader;
13 |
--------------------------------------------------------------------------------
/client/src/components/profileLinks/ProfileLink.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { Link } from "react-router-dom";
4 | import Loader from "../loader/Loader";
5 | import styles from "./ProfileLink.module.scss";
6 | import { AiOutlineEdit, AiOutlineLogout, AiOutlineUser } from "react-icons/ai";
7 | import { RiLockPasswordLine } from "react-icons/ri";
8 | import { MdFavoriteBorder } from "react-icons/md";
9 | import { logout } from "../../actions/userActions";
10 | import { useAlert } from "react-alert";
11 |
12 | const ProfileLink = () => {
13 | const { user, loading } = useSelector((state) => state.auth);
14 |
15 | const alert = useAlert();
16 | const dispatch = useDispatch();
17 |
18 | const logoutHandler = () => {
19 | dispatch(logout());
20 | alert.success("Logged out successfully.");
21 | };
22 | return (
23 |
24 | {loading ? (
25 |
26 | ) : (
27 |
28 |
29 |
30 | {user && (
31 |
32 | )}
33 |
34 |
{user?.name}
35 |
{user?.email}
36 |
37 |
38 |
39 |
40 |
41 |
{" "}
42 | Profile
43 |
44 |
45 |
{" "}
46 | Edit Profile
47 |
48 |
49 |
53 | Password
54 |
55 |
56 |
57 | My Order
58 |
59 |
60 |
61 | Logout
62 |
63 |
64 |
65 |
66 | )}
67 |
68 | );
69 | };
70 |
71 | export default ProfileLink;
72 |
--------------------------------------------------------------------------------
/client/src/components/profileLinks/ProfileLink.module.scss:
--------------------------------------------------------------------------------
1 | .profile_links {
2 | background-color: #ffffff;
3 | box-shadow: 0 4px 8px 5px rgba(232, 226, 226, 0.2),
4 | 0 6px 20px 0 rgba(0, 0, 0, 0.19);
5 | padding: 1rem;
6 | border-radius: 20px;
7 | font-family: var(--font-poppins);
8 |
9 | div {
10 | img {
11 | height: 150px;
12 | width: 150px;
13 | border-radius: 50%;
14 | border: 3px solid #e8daff;
15 | }
16 |
17 | h4 {
18 | letter-spacing: 1px;
19 | font-weight: 500;
20 | }
21 |
22 | p {
23 | color: var(--color-gray);
24 | }
25 | }
26 |
27 | .links {
28 | display: flex;
29 | flex-direction: column;
30 |
31 | a {
32 | text-decoration: none;
33 | padding: 10px 30px;
34 | color: var(--color-gray);
35 | margin: 5px 0;
36 | border-radius: 5px;
37 | font-weight: 500;
38 | letter-spacing: 0.3px;
39 | font-size: 16px;
40 | transition: all 0.3s ease;
41 |
42 | &:hover {
43 | background-color: #e8daff;
44 | color: black;
45 | }
46 | }
47 |
48 | button {
49 | padding: 10px 30px;
50 | color: var(--color-gray);
51 | margin: 5px 0;
52 | border-radius: 5px;
53 | font-weight: 500;
54 | letter-spacing: 0.3px;
55 | font-size: 16px;
56 | border: none;
57 | background-color: #ffffff;
58 | text-align: start;
59 | transition: all 0.3s ease;
60 |
61 | &:hover {
62 | background-color: #ffd6e8;
63 | color: black;
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/client/src/components/route/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import { Route, Redirect } from "react-router-dom";
3 | import { useSelector } from "react-redux";
4 |
5 | const ProtectedRoute = ({ isAdmin, component: Component, ...rest }) => {
6 | const { isAuthenticated, loading, user } = useSelector(
7 | (state) => state.auth
8 | );
9 | return (
10 |
11 | {loading === false && (
12 | {
15 | if (isAuthenticated === false) {
16 | return ;
17 | }
18 |
19 | if (isAdmin === true && user.role !== "admin") {
20 | return ;
21 | }
22 |
23 | return ;
24 | }}
25 | />
26 | )}
27 |
28 | );
29 | };
30 |
31 | export default ProtectedRoute;
32 |
--------------------------------------------------------------------------------
/client/src/components/widget/Widget.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Widget.scss";
3 | import { AiOutlineArrowUp } from "react-icons/ai";
4 |
5 | const Widget = ({ title, icon, link, total }) => {
6 | const diff = 20;
7 | return (
8 |
9 |
10 | {title}
11 | {total}
12 | {link}
13 |
14 |
21 |
22 | );
23 | };
24 |
25 | export default Widget;
26 |
--------------------------------------------------------------------------------
/client/src/components/widget/Widget.scss:
--------------------------------------------------------------------------------
1 | .widget {
2 | display: flex;
3 | justify-content: space-between;
4 | flex: 1;
5 | padding: 10px;
6 | -webkit-box-shadow: 2px 4px 10px 1px rgba(0, 0, 0, 0.47);
7 | box-shadow: 2px 4px 10px 1px rgba(201, 201, 201, 0.47);
8 | border-radius: 10px;
9 | min-height: 100px;
10 |
11 | .left,
12 | .right {
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: space-between;
16 |
17 | .title {
18 | font-weight: bold;
19 | font-size: 18px;
20 | color: rgb(160, 160, 160);
21 | }
22 |
23 | .counter {
24 | font-size: 32px;
25 | font-weight: 300;
26 | }
27 |
28 | .link {
29 | width: max-content;
30 | font-size: 16px;
31 | border-bottom: 1px solid gray;
32 | }
33 |
34 | .percentage {
35 | display: flex;
36 | align-items: center;
37 | font-size: 14px;
38 |
39 | &.positive {
40 | color: green;
41 | }
42 | &.negative {
43 | color: red;
44 | }
45 | }
46 |
47 | .icon {
48 | font-size: 24px;
49 | padding: 5px;
50 | border-radius: 5px;
51 | align-self: flex-end;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/client/src/config.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export const axiosInstance = axios.create({
4 | baseURL:
5 | "https://e-shop-app.onrender.com",
6 | });
7 |
--------------------------------------------------------------------------------
/client/src/constants/cartConstants.js:
--------------------------------------------------------------------------------
1 | export const ADD_TO_CART = "ADD_TO_CART";
2 | export const REMOVE_ITEM_CART = "REMOVE_ITEM_CART";
3 | export const SAVE_SHIPPING_INFO = "SAVE_SHIPPING_INFO";
4 |
--------------------------------------------------------------------------------
/client/src/constants/orderConstants.js:
--------------------------------------------------------------------------------
1 | export const CREATE_ORDER_REQUEST = "CREATE_ORDER_REQUEST";
2 | export const CREATE_ORDER_SUCCESS = "CREATE_ORDER_SUCCESS";
3 | export const CREATE_ORDER_FAIL = "CREATE_ORDER_FAIL";
4 |
5 | export const MY_ORDERS_REQUEST = "MY_ORDERS_REQUEST";
6 | export const MY_ORDERS_SUCCESS = "MY_ORDERS_SUCCESS";
7 | export const MY_ORDERS_FAIL = "MY_ORDERS_FAIL";
8 |
9 | export const ALL_ORDERS_REQUEST = "ALL_ORDERS_REQUEST";
10 | export const ALL_ORDERS_SUCCESS = "ALL_ORDERS_SUCCESS";
11 | export const ALL_ORDERS_FAIL = "ALL_ORDERS_FAIL";
12 |
13 | export const UPDATE_ORDER_REQUEST = "UPDATE_ORDER_REQUEST";
14 | export const UPDATE_ORDER_SUCCESS = "UPDATE_ORDER_SUCCESS";
15 | export const UPDATE_ORDER_RESET = "UPDATE_ORDER_RESET";
16 | export const UPDATE_ORDER_FAIL = "UPDATE_ORDER_FAIL";
17 |
18 | export const DELETE_ORDER_REQUEST = "DELETE_ORDER_REQUEST";
19 | export const DELETE_ORDER_SUCCESS = "DELETE_ORDER_SUCCESS";
20 | export const DELETE_ORDER_RESET = "DELETE_ORDER_RESET";
21 | export const DELETE_ORDER_FAIL = "DELETE_ORDER_FAIL";
22 |
23 | export const ORDER_DETAILS_REQUEST = "ORDER_DETAILS_REQUEST";
24 | export const ORDER_DETAILS_SUCCESS = "ORDER_DETAILS_SUCCESS";
25 | export const ORDER_DETAILS_FAIL = "ORDER_DETAILS_FAIL";
26 |
27 | export const CLEAR_ERRORS = "CLEAR_ERRORS";
28 |
--------------------------------------------------------------------------------
/client/src/constants/productsConstants.js:
--------------------------------------------------------------------------------
1 | export const ADMIN_PRODUCTS_REQUEST = "ADMIN_PRODUCTS_REQUEST";
2 | export const ADMIN_PRODUCTS_SUCCESS = "ADMIN_PRODUCTS_SUCCESS";
3 | export const ADMIN_PRODUCTS_FAIL = "ADMIN_PRODUCTS_FAIL";
4 |
5 | export const ALL_PRODUCTS_REQUEST = "ALL_PRODUCTS_REQUEST";
6 | export const ALL_PRODUCTS_SUCCESS = "ALL_PRODUCTS_SUCCESS";
7 | export const ALL_PRODUCTS_FAIL = "ALL_PRODUCTS_FAIL";
8 |
9 | export const NEW_PRODUCT_REQUEST = "NEW_PRODUCT_REQUEST";
10 | export const NEW_PRODUCT_SUCCESS = "NEW_PRODUCT_SUCCESS";
11 | export const NEW_PRODUCT_RESET = "NEW_PRODUCT_RESET";
12 | export const NEW_PRODUCT_FAIL = "NEW_PRODUCT_FAIL";
13 |
14 | export const DELETE_PRODUCT_REQUEST = "DELETE_PRODUCT_REQUEST";
15 | export const DELETE_PRODUCT_SUCCESS = "DELETE_PRODUCT_SUCCESS";
16 | export const DELETE_PRODUCT_RESET = "DELETE_PRODUCT_RESET";
17 | export const DELETE_PRODUCT_FAIL = "DELETE_PRODUCT_FAIL";
18 |
19 | export const UPDATE_PRODUCT_REQUEST = "UPDATE_PRODUCT_REQUEST";
20 | export const UPDATE_PRODUCT_SUCCESS = "UPDATE_PRODUCT_SUCCESS";
21 | export const UPDATE_PRODUCT_RESET = "UPDATE_PRODUCT_RESET";
22 | export const UPDATE_PRODUCT_FAIL = "UPDATE_PRODUCT_FAIL";
23 |
24 | export const PRODUCT_DETAILS_REQUEST = "PRODUCT_DETAILS_REQUEST";
25 | export const PRODUCT_DETAILS_SUCCESS = "PRODUCT_DETAILS_SUCCESS";
26 | export const PRODUCT_DETAILS_FAIL = "PRODUCT_DETAILS_FAIL";
27 |
28 | export const NEW_REVIEW_REQUEST = "NEW_REVIEW_REQUEST";
29 | export const NEW_REVIEW_SUCCESS = "NEW_REVIEW_SUCCESS";
30 | export const NEW_REVIEW_RESET = "NEW_REVIEW_RESET";
31 | export const NEW_REVIEW_FAIL = "NEW_REVIEW_FAIL";
32 |
33 | export const GET_REVIEWS_REQUEST = "GET_REVIEWS_REQUEST";
34 | export const GET_REVIEWS_SUCCESS = "GET_REVIEWS_SUCCESS";
35 | export const GET_REVIEWS_FAIL = "GET_REVIEWS_FAIL";
36 |
37 | export const DELETE_REVIEW_REQUEST = "DELETE_REVIEW_REQUEST";
38 | export const DELETE_REVIEW_SUCCESS = "DELETE_REVIEW_SUCCESS";
39 | export const DELETE_REVIEW_RESET = "DELETE_REVIEW_RESET";
40 | export const DELETE_REVIEW_FAIL = "DELETE_REVIEW_FAIL";
41 |
42 | export const CLEAR_ERRORS = "CLEAR_ERRORS";
43 |
--------------------------------------------------------------------------------
/client/src/constants/userConstants.js:
--------------------------------------------------------------------------------
1 | export const LOGIN_REQUEST = "LOGIN_REQUEST";
2 | export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
3 | export const LOGIN_FAIL = "LOGIN_FAIL";
4 |
5 | export const REGISTER_USER_REQUEST = "REGISTER_USER_REQUEST";
6 | export const REGISTER_USER_SUCCESS = "REGISTER_USER_SUCCESS";
7 | export const REGISTER_USER_FAIL = "REGISTER_USER_FAIL";
8 |
9 | export const LOAD_USER_REQUEST = "LOAD_USER_REQUEST";
10 | export const LOAD_USER_SUCCESS = "LOAD_USER_SUCCESS";
11 | export const LOAD_USER_FAIL = "LOAD_USER_FAIL";
12 |
13 | export const UPDATE_PROFILE_REQUEST = "UPDATE_PROFILE_REQUEST";
14 | export const UPDATE_PROFILE_SUCCESS = "UPDATE_PROFILE_SUCCESS";
15 | export const UPDATE_PROFILE_RESET = "UPDATE_PROFILE_RESET";
16 | export const UPDATE_PROFILE_FAIL = "UPDATE_PROFILE_FAIL";
17 |
18 | export const UPDATE_PASSWORD_REQUEST = "UPDATE_PASSWORD_REQUEST";
19 | export const UPDATE_PASSWORD_SUCCESS = "UPDATE_PASSWORD_SUCCESS";
20 | export const UPDATE_PASSWORD_RESET = "UPDATE_PASSWORD_RESET";
21 | export const UPDATE_PASSWORD_FAIL = "UPDATE_PASSWORD_FAIL";
22 |
23 | export const FORGOT_PASSWORD_REQUEST = "FORGOT_PASSWORD_REQUEST";
24 | export const FORGOT_PASSWORD_SUCCESS = "FORGOT_PASSWORD_SUCCESS";
25 | export const FORGOT_PASSWORD_FAIL = "FORGOT_PASSWORD_FAIL";
26 |
27 | export const NEW_PASSWORD_REQUEST = "NEW_PASSWORD_REQUEST";
28 | export const NEW_PASSWORD_SUCCESS = "NEW_PASSWORD_SUCCESS";
29 | export const NEW_PASSWORD_FAIL = "NEW_PASSWORD_FAIL";
30 |
31 | export const ALL_USERS_REQUEST = "ALL_USERS_REQUEST";
32 | export const ALL_USERS_SUCCESS = "ALL_USERS_SUCCESS";
33 | export const ALL_USERS_FAIL = "ALL_USERS_FAIL";
34 |
35 | export const USER_DETAILS_REQUEST = "USER_DETAILS_REQUEST";
36 | export const USER_DETAILS_SUCCESS = "USER_DETAILS_SUCCESS";
37 | export const USER_DETAILS_FAIL = "USER_DETAILS_FAIL";
38 |
39 | export const UPDATE_USER_REQUEST = "UPDATE_USER_REQUEST";
40 | export const UPDATE_USER_SUCCESS = "UPDATE_USER_SUCCESS";
41 | export const UPDATE_USER_RESET = "UPDATE_USER_RESET";
42 | export const UPDATE_USER_FAIL = "UPDATE_USER_FAIL";
43 |
44 | export const DELETE_USER_REQUEST = "DELETE_USER_REQUEST";
45 | export const DELETE_USER_SUCCESS = "DELETE_USER_SUCCESS";
46 | export const DELETE_USER_RESET = "DELETE_USER_RESET";
47 | export const DELETE_USER_FAIL = "DELETE_USER_FAIL";
48 |
49 | export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";
50 | export const LOGOUT_FAIL = "LOGOUT_FAIL";
51 |
52 | export const CLEAR_ERRORS = "CLEAR_ERRORS";
53 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&family=Lobster&family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600&family=Roboto:ital,wght@0,400;0,500;0,700;0,900;1,400;1,500;1,700;1,900&display=swap");
2 |
3 | :root {
4 | --font-lato: "Lato", sans-serif;
5 | --font-lobster: "Lobster", cursive;
6 | --font-poppins: "Poppins", sans-serif;
7 | --font-roboto: "Roboto", sans-serif;
8 |
9 | --color-golden: #dcca87;
10 | --color-black: #0c0c0c;
11 | --color-gray: #545454;
12 | --color-crimson: #f5efdb;
13 | --color-grey: #aaaaaa;
14 | --color-white: #ffffff;
15 | }
16 |
17 | * {
18 | box-sizing: border-box;
19 | padding: 0;
20 | margin: 0;
21 | scroll-behavior: smooth;
22 | }
23 |
24 | body {
25 | width: 100%;
26 | height: 100%;
27 | margin: 0px;
28 | padding: 0px;
29 | overflow-x: hidden;
30 | position: relative;
31 | /* background-color: rgb(0, 30, 60) !important; */
32 | }
33 |
34 | /* Scroll Bar */
35 |
36 | ::-webkit-scrollbar-track {
37 | background-color: #f5f5f5;
38 | }
39 |
40 | ::-webkit-scrollbar {
41 | width: 8px;
42 | background-color: #f5f5f5;
43 | }
44 |
45 | ::-webkit-scrollbar-thumb {
46 | background-color: rgba(66, 66, 66, 0.2);
47 | border: 0px;
48 | background-clip: padding-box;
49 | border-radius: 5px;
50 | }
51 |
52 | .ratings {
53 | font-size: 1.2rem;
54 | color: #fdcc0d;
55 | }
56 |
57 | #no_of_reviews {
58 | font-size: 0.85rem;
59 | color: grey;
60 | margin-left: 0.5rem;
61 | }
62 |
63 | /* Ratings */
64 |
65 | .rating-outer {
66 | display: inline-block;
67 | position: relative;
68 | font-family: FontAwesome;
69 | color: #fdcc0d;
70 | }
71 |
72 | .rating-outer::before {
73 | content: "\f006 \f006 \f006 \f006 \f006";
74 | }
75 |
76 | .rating-inner {
77 | position: absolute;
78 | top: 0;
79 | left: 0;
80 | white-space: nowrap;
81 | overflow: hidden;
82 | width: 0;
83 | }
84 |
85 | .rating-inner::before {
86 | content: "\f005 \f005 \f005 \f005 \f005";
87 | color: #f8ce0b;
88 | }
89 |
90 | .greenColor {
91 | color: green;
92 | }
93 |
94 | .redColor {
95 | color: red;
96 | }
97 |
98 | /* Checkout Steps */
99 |
100 | .checkout-progress div {
101 | box-sizing: border-box;
102 | }
103 |
104 | .checkout-progress {
105 | display: block;
106 | clear: both;
107 | margin: 20px auto;
108 | width: auto;
109 | font-family: sans-serif;
110 | overflow: auto;
111 | }
112 |
113 | .step {
114 | margin: 0;
115 | border: 0;
116 | letter-spacing: 1px;
117 | line-height: 30px;
118 | padding: 5px 10px 5px 15px;
119 | color: grey;
120 | text-decoration: none;
121 | cursor: default;
122 | font-weight: bold;
123 | float: left;
124 | height: auto;
125 | }
126 |
127 | .incomplete {
128 | background: #eeeeee;
129 | }
130 |
131 | .active-step {
132 | background: #fa9c23;
133 | color: #fff;
134 | }
135 |
136 | .triangle-active {
137 | float: left;
138 | width: 0;
139 | border-top: 20px solid transparent;
140 | border-left: 15px solid #fa9c23;
141 | border-bottom: 20px solid transparent;
142 | margin-left: -1px;
143 | }
144 |
145 | .triangle2-active {
146 | width: 0;
147 | float: left;
148 | border-top: 20px solid #fa9c23;
149 | border-left: 15px solid #fff;
150 | border-bottom: 20px solid #fa9c23;
151 | margin-right: -1px;
152 | }
153 |
154 | .triangle-incomplete {
155 | float: left;
156 | width: 0;
157 | border-top: 20px solid transparent;
158 | border-left: 15px solid #eeeeee;
159 | border-bottom: 20px solid transparent;
160 | margin-left: -1px;
161 | }
162 |
163 | .triangle2-incomplete {
164 | width: 0;
165 | float: left;
166 | border-top: 20px solid #eeeeee;
167 | border-left: 15px solid #fff;
168 | border-bottom: 20px solid #eeeeee;
169 | margin-right: -1px;
170 | }
171 |
172 | .image_file {
173 | height: 40px;
174 | width: 300px;
175 | border-radius: 20px;
176 | position: relative;
177 |
178 | display: flex;
179 | justify-content: center;
180 | align-items: center;
181 |
182 | /* border: 4px solid #ffffff; */
183 | overflow: hidden;
184 | background-image: linear-gradient(to bottom, #2590eb 50%, #ffffff 50%);
185 | background-size: 100% 200%;
186 | transition: all 1s;
187 | color: #ffffff;
188 | font-size: 100px;
189 | }
190 | .image_file input {
191 | height: 300px;
192 | width: 300px;
193 | position: absolute;
194 | top: 0;
195 | left: 0;
196 | opacity: 0;
197 | cursor: pointer;
198 | }
199 |
200 | /* Reviews */
201 |
202 | .rating {
203 | margin-top: 10rem;
204 | }
205 |
206 | .stars {
207 | height: 100px;
208 | display: flex;
209 | align-items: center;
210 | padding-left: 0;
211 | }
212 |
213 | .star {
214 | display: inline;
215 | list-style: none;
216 | font-size: 3rem;
217 | padding-left: 0.9rem;
218 | color: #e3e3e3;
219 | }
220 |
221 | .star:first-child {
222 | padding-left: 0;
223 | }
224 |
225 | .orange {
226 | color: #fa9c23;
227 | }
228 |
229 | .yellow {
230 | color: #fdcc0d;
231 | }
232 |
233 | .review_user {
234 | font-size: 0.8rem;
235 | color: grey;
236 | }
237 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import reportWebVitals from "./reportWebVitals";
6 | import { Provider } from "react-redux";
7 | import store from "./store";
8 | import "bootstrap/dist/css/bootstrap.min.css";
9 |
10 | import { positions, transitions, Provider as AlertProvider } from "react-alert";
11 | import AlertTemplate from "react-alert-template-basic";
12 |
13 | const options = {
14 | timeout: 5000,
15 | position: positions.BOTTOM_CENTER,
16 | transition: transitions.SCALE,
17 | };
18 |
19 | ReactDOM.render(
20 |
21 |
22 |
23 |
24 | ,
25 | document.getElementById("root")
26 | );
27 |
28 | reportWebVitals();
29 |
--------------------------------------------------------------------------------
/client/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/pages/about/About.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import {
3 | FcAutomotive,
4 | FcCurrencyExchange,
5 | FcCustomerSupport,
6 | FcRating,
7 | } from "react-icons/fc";
8 | import Footer from "../../components/footer/Footer";
9 | import Navbar from "../../components/header/Navbar";
10 | import MetaData from "../../components/MetaData";
11 | import styles from "./About.module.scss";
12 |
13 | const About = () => {
14 | const abouts = [
15 | {
16 | icon: ,
17 | title: "Free Delevery",
18 | description:
19 | "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Beatae, nihil!",
20 | },
21 | {
22 | icon: ,
23 | title: "100% Cash Back",
24 | description:
25 | "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Beatae, nihil!",
26 | },
27 | {
28 | icon: ,
29 | title: "Quality Product",
30 | description:
31 | "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Beatae, nihil!",
32 | },
33 | {
34 | icon: ,
35 | title: "24/7 Support",
36 | description:
37 | "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Beatae, nihil!",
38 | },
39 | ];
40 | return (
41 |
42 |
43 |
44 |
45 |
46 |
47 |
About Us
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
59 |
60 |
61 |
62 |
63 |
64 | Know About Our Ecomerce Business,
65 | History
66 |
67 |
68 | Lorem ipsum dolor sit amet consectetur
69 | adipisicing elit. Eaque voluptatibus
70 | est, assumenda cupiditate id nam illo
71 | odio ipsum itaque maxime.
72 |
73 |
Contact Us
74 |
75 |
76 |
77 |
78 |
79 |
Our Features
80 |
81 |
82 |
83 |
84 | {abouts.map((about, index) => (
85 |
89 |
90 |
{about.icon}
91 |
{about.title}
92 |
{about.description}
93 |
94 |
95 | ))}
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | );
106 | };
107 |
108 | export default About;
109 |
--------------------------------------------------------------------------------
/client/src/pages/about/About.module.scss:
--------------------------------------------------------------------------------
1 | .about {
2 | font-family: var(--font-poppins);
3 | .about_title {
4 | background-color: aliceblue;
5 | min-height: 120px;
6 | display: flex;
7 | align-items: center;
8 | }
9 | .about_info {
10 | margin-top: 50px;
11 |
12 | .image {
13 | text-align: center;
14 | @media screen and (max-width: 900px) {
15 | text-align: left;
16 | }
17 | img {
18 | height: 300px;
19 | width: 400px;
20 |
21 | @media screen and (max-width: 900px) {
22 | height: 200px;
23 | width: 250px;
24 | text-align: left;
25 | }
26 | }
27 | }
28 |
29 | .info {
30 | width: 70%;
31 |
32 | h4 {
33 | font-weight: 500;
34 | letter-spacing: 0.5px;
35 | }
36 | p {
37 | font-weight: 500;
38 | letter-spacing: 0.5px;
39 | color: var(--color-grey);
40 | }
41 | button {
42 | border: none;
43 | background-color: goldenrod;
44 | color: #fff;
45 | padding: 0.5rem 1rem;
46 | font-weight: 500;
47 | border-radius: 20px;
48 |
49 | &:hover {
50 | background-color: rgb(231, 174, 31);
51 | }
52 | }
53 | }
54 |
55 | .features {
56 | margin-top: 50px;
57 |
58 | .feature {
59 | padding: 1rem;
60 | text-align: center;
61 | border-bottom: 2px solid #fff;
62 | transition: all 0.5s ease;
63 | border-radius: 10px;
64 |
65 | &:hover {
66 | border-bottom: 2px solid #e7ae1f;
67 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
68 | }
69 |
70 | h5 {
71 | margin-top: 15px;
72 | font-weight: 500;
73 | }
74 |
75 | P {
76 | margin-top: 5px;
77 | font-weight: 500;
78 | color: var(--color-gray);
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/client/src/pages/admin/dashboard/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import Navbar from "../../../components/admin/navbar/Navbar";
3 | import Sidebar from "../../../components/admin/sidebar/Sidebar";
4 | import Widget from "../../../components/widget/Widget";
5 | import { AiOutlineShoppingCart, AiOutlineUser } from "react-icons/ai";
6 | import { Link } from "react-router-dom";
7 | import { useDispatch, useSelector } from "react-redux";
8 | import styles from "./Dashboard.module.scss";
9 | import Loader from "../../../components/loader/Loader";
10 | import { getAdminProducts } from "../../../actions/productAction";
11 | import { allUsers } from "../../../actions/userActions";
12 | import { allOrders } from "../../../actions/orderActions";
13 | import MetaData from "../../../components/MetaData";
14 |
15 | const Dashboard = () => {
16 | const dispatch = useDispatch();
17 |
18 | const { orders } = useSelector((state) => state.allOrders);
19 | const { users } = useSelector((state) => state.allUsers);
20 | const { loading, products } = useSelector((state) => state.products);
21 |
22 | useEffect(() => {
23 | dispatch(getAdminProducts());
24 | }, [dispatch]);
25 | useEffect(() => {
26 | dispatch(allOrders());
27 | }, [dispatch]);
28 | useEffect(() => {
29 | dispatch(allUsers());
30 | }, [dispatch]);
31 |
32 | let stockout = products?.filter((item) => item.stock === 0);
33 |
34 | const user = {
35 | title: "USERS",
36 | total: users?.length,
37 | link: (
38 |
39 | See all user
40 |
41 | ),
42 | icon: ,
43 | };
44 | const order = {
45 | title: "ORDERS",
46 | total: orders?.length,
47 | link: (
48 |
49 | See all orders
50 |
51 | ),
52 | icon: ,
53 | };
54 | const product = {
55 | title: "PRODUCTS",
56 | total: products?.length,
57 | link: (
58 |
59 | See all products
60 |
61 | ),
62 | icon: ,
63 | };
64 | const stock = {
65 | title: "STOCK OUT",
66 | total: stockout.length,
67 | link: (
68 |
69 | See all user
70 |
71 | ),
72 | icon: ,
73 | };
74 |
75 | return (
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | {loading ? (
85 | <>
86 |
87 | >
88 | ) : (
89 | <>
90 |
91 |
97 |
103 |
109 |
115 |
116 | >
117 | )}
118 |
119 |
120 |
121 | );
122 | };
123 |
124 | export default Dashboard;
125 |
--------------------------------------------------------------------------------
/client/src/pages/admin/dashboard/Dashboard.module.scss:
--------------------------------------------------------------------------------
1 | .dashboard {
2 | width: 100%;
3 | min-height: 88.5vh;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 |
8 | .widgets {
9 | display: flex;
10 | padding: 20px;
11 | gap: 20px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/pages/admin/newProduct/NewProduct.module.scss:
--------------------------------------------------------------------------------
1 | .new_product {
2 | width: 100%;
3 | min-height: 88.5vh;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 | background-color: #fff;
8 |
9 | .product_input {
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | margin-top: 5px;
14 |
15 | .form {
16 | background-color: white;
17 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
18 | // backdrop-filter: blur(4px);
19 | // -webkit-backdrop-filter: blur(4px);
20 | // border: 1px solid rgba(255, 255, 255, 0.18);
21 | padding: 2rem;
22 | border-radius: 20px;
23 | min-width: 50%;
24 | margin-top: 10px;
25 |
26 | .from_group {
27 | display: flex;
28 | flex-direction: column;
29 | gap: 5px;
30 | margin-top: 15px;
31 |
32 | label {
33 | color: black;
34 | font-weight: 500;
35 | letter-spacing: 1px;
36 | }
37 |
38 | input {
39 | height: 40px;
40 | border-radius: 20px;
41 | border: 2px solid #fff;
42 | outline: none;
43 | padding: 0.5rem 1rem;
44 | background-color: rgb(216, 214, 214);
45 | font-size: 16px;
46 | }
47 |
48 | textarea {
49 | height: 100px;
50 | border-radius: 20px;
51 | border: 2px solid #fff;
52 | outline: none;
53 | padding: 0.5rem 1rem;
54 | background-color: rgb(216, 214, 214);
55 | font-size: 16px;
56 | }
57 |
58 | select {
59 | height: 40px;
60 | border-radius: 20px;
61 | border: 2px solid #fff;
62 | outline: none;
63 | padding: 0.5rem 1rem;
64 | background-color: rgb(216, 214, 214);
65 | font-size: 16px;
66 | }
67 |
68 | button {
69 | height: 35px;
70 | border-radius: 20px;
71 | border: none;
72 | width: 80%;
73 | margin: 1rem auto;
74 | text-transform: uppercase;
75 | letter-spacing: 1px;
76 | font-weight: 500;
77 | transition: all 0.5s ease-in-out;
78 |
79 | &:hover {
80 | width: 90%;
81 | }
82 | }
83 | // p {
84 | // a {
85 | // text-decoration: none;
86 | // color: rgb(33, 216, 33);
87 | // cursor: pointer;
88 | // }
89 | // }
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/client/src/pages/admin/orders/Orders.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import {
3 | allOrders,
4 | clearErrors,
5 | deleteOrder,
6 | } from "../../../actions/orderActions";
7 | import Sidebar from "../../../components/admin/sidebar/Sidebar";
8 | import { useAlert } from "react-alert";
9 | import { useDispatch, useSelector } from "react-redux";
10 | import styles from "./Orders.module.scss";
11 | import Loader from "../../../components/loader/Loader";
12 | import { Table } from "react-bootstrap";
13 | import { AiOutlineDelete, AiOutlineEye } from "react-icons/ai";
14 | import { Link } from "react-router-dom";
15 | import { DELETE_ORDER_RESET } from "../../../constants/orderConstants";
16 | import Navbar from "../../../components/admin/navbar/Navbar";
17 | import MetaData from "../../../components/MetaData";
18 |
19 | const Orders = ({ history }) => {
20 | const alert = useAlert();
21 | const dispatch = useDispatch();
22 |
23 | const { loading, error, orders } = useSelector((state) => state.allOrders);
24 | const { isDeleted } = useSelector((state) => state.order);
25 |
26 | useEffect(() => {
27 | dispatch(allOrders());
28 |
29 | if (error) {
30 | alert.error(error);
31 | dispatch(clearErrors());
32 | }
33 |
34 | if (isDeleted) {
35 | alert.success("Order deleted successfully");
36 | history.push("/admin/orders");
37 | dispatch({ type: DELETE_ORDER_RESET });
38 | }
39 | }, [dispatch, alert, error, isDeleted, history]);
40 |
41 | const deleteOrderHandler = (id) => {
42 | dispatch(deleteOrder(id));
43 | };
44 |
45 | return (
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | {loading ? (
56 | <>
57 |
58 | >
59 | ) : (
60 | <>
61 |
62 |
63 |
64 | Order ID
65 | No of Items
66 | Amount
67 | Address
68 | Status
69 | Actions
70 |
71 |
72 |
73 |
74 | {orders?.map((order) => (
75 |
76 | {order?._id}
77 |
78 | {order.orderItems.length}
79 |
80 | {order?.totalPrice}
81 |
82 | {order?.shippingInfo.city}
83 |
84 | {order?.orderStatus}
85 |
86 |
89 |
92 |
93 |
95 | deleteOrderHandler(
96 | order._id
97 | )
98 | }
99 | >
100 |
103 |
104 |
105 |
106 | ))}
107 |
108 |
109 | >
110 | )}
111 |
112 |
113 |
114 |
115 | );
116 | };
117 |
118 | export default Orders;
119 |
--------------------------------------------------------------------------------
/client/src/pages/admin/orders/Orders.module.scss:
--------------------------------------------------------------------------------
1 | .orders {
2 | width: 100%;
3 | min-height: 88.5vh;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 |
8 | .table {
9 | background-color: white;
10 | border-radius: 10px;
11 | padding: 1rem;
12 |
13 | .actions {
14 | a {
15 | text-decoration: none;
16 | padding: 5px;
17 | border-radius: 5px;
18 | margin: 5px;
19 |
20 | &:nth-child(1) {
21 | background-color: #00adb5;
22 | color: #fff;
23 | &:hover {
24 | color: #fff;
25 | background-color: #0b949b;
26 | }
27 | }
28 | &:nth-child(2) {
29 | background-color: #dd6616;
30 | color: #fff;
31 | &:hover {
32 | color: #fff;
33 | background-color: #c95c13;
34 | }
35 | }
36 | }
37 |
38 | button {
39 | padding: 4px;
40 | border-radius: 5px;
41 | margin: 5px;
42 | background-color: #cf3737;
43 | color: #fff;
44 | border: none;
45 |
46 | &:hover {
47 | color: white;
48 | background-color: #950101;
49 | }
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/client/src/pages/admin/orders/processOrder/Processorder.module.scss:
--------------------------------------------------------------------------------
1 | .process_order {
2 | width: 100%;
3 | min-height: 88.5vh;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 | }
8 |
--------------------------------------------------------------------------------
/client/src/pages/admin/productDetails/ProductDetails.module.scss:
--------------------------------------------------------------------------------
1 | .product_details {
2 | width: 100%;
3 | min-height: 88.5vh;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 |
8 | .preview_image {
9 | height: 350px;
10 | width: 500px;
11 | border-radius: 20px;
12 | img {
13 | height: 300px;
14 | width: 450px;
15 | border-radius: 20px;
16 | }
17 | }
18 |
19 | .image_thumbline {
20 | display: flex;
21 | align-items: center;
22 | width: 450px;
23 |
24 | div {
25 | img {
26 | height: 100px;
27 | width: 135px;
28 | margin: 5px;
29 | }
30 | }
31 | }
32 |
33 | .Product_info {
34 | p {
35 | color: var(--color-gray);
36 | font-size: 16px;
37 | margin-top: 5px;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/client/src/pages/admin/productReview/ProductReview.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useAlert } from "react-alert";
3 | import { Table } from "react-bootstrap";
4 | import { AiOutlineDelete } from "react-icons/ai";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import {
7 | clearErrors,
8 | deleteReview,
9 | getProductReviews,
10 | } from "../../../actions/productAction";
11 | import Navbar from "../../../components/admin/navbar/Navbar";
12 | import Sidebar from "../../../components/admin/sidebar/Sidebar";
13 | import MetaData from "../../../components/MetaData";
14 | import { DELETE_REVIEW_RESET } from "../../../constants/productsConstants";
15 | import styles from "./ProductReview.module.scss";
16 |
17 | const ProductReview = () => {
18 | const [productId, setProductId] = useState("");
19 |
20 | const alert = useAlert();
21 | const dispatch = useDispatch();
22 |
23 | const { error, reviews } = useSelector((state) => state.productReviews);
24 | const { isDeleted, error: deleteError } = useSelector(
25 | (state) => state.review
26 | );
27 |
28 | useEffect(() => {
29 | if (error) {
30 | alert.error(error);
31 | dispatch(clearErrors());
32 | }
33 |
34 | if (deleteError) {
35 | alert.error(deleteError);
36 | dispatch(clearErrors());
37 | }
38 |
39 | if (productId !== "") {
40 | dispatch(getProductReviews(productId));
41 | }
42 |
43 | if (isDeleted) {
44 | alert.success("Review deleted successfully");
45 | dispatch({ type: DELETE_REVIEW_RESET });
46 | }
47 | }, [dispatch, alert, error, productId, isDeleted, deleteError]);
48 |
49 | const deleteReviewHandler = (id) => {
50 | dispatch(deleteReview(id, productId));
51 | };
52 |
53 | const submitHandler = (e) => {
54 | e.preventDefault();
55 | dispatch(getProductReviews(productId));
56 | };
57 | return (
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
90 |
91 | {reviews && reviews.length > 0 ? (
92 |
93 | {/* table */}
94 |
95 |
96 |
97 | Review ID
98 | Rating
99 | Comment
100 | Name
101 | Actions
102 |
103 |
104 |
105 |
106 | {reviews?.map((review) => (
107 |
108 | {review?._id}
109 |
110 | {review.rating}
111 | {review.comment}
112 | {review.name}
113 |
114 |
117 | deleteReviewHandler(
118 | review._id
119 | )
120 | }
121 | >
122 |
125 |
126 |
127 |
128 | ))}
129 |
130 |
131 |
132 | ) : (
133 |
No Reviews.
134 | )}
135 |
136 |
137 |
138 |
139 | );
140 | };
141 |
142 | export default ProductReview;
143 |
--------------------------------------------------------------------------------
/client/src/pages/admin/productReview/ProductReview.module.scss:
--------------------------------------------------------------------------------
1 | .review {
2 | font-family: var(--font-poppins);
3 | .form_group {
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 |
8 | input {
9 | height: 35px;
10 | margin-top: 10px;
11 | border-radius: 20px;
12 | outline: none;
13 | padding: 0.5rem 1rem;
14 | width: 100%;
15 | }
16 |
17 | button {
18 | border: none;
19 | margin-top: 10px;
20 | padding: 5px 15px;
21 | border-radius: 20px;
22 | background-color: burlywood;
23 | color: black;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/client/src/pages/admin/products/ProductsList.module.scss:
--------------------------------------------------------------------------------
1 | .products {
2 | width: 100%;
3 | min-height: 88.5vh;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 |
8 | .table {
9 | background-color: white;
10 | border-radius: 10px;
11 | padding: 1rem;
12 |
13 | .actions {
14 | a {
15 | text-decoration: none;
16 | padding: 5px;
17 | border-radius: 5px;
18 | margin: 5px;
19 |
20 | &:nth-child(1) {
21 | background-color: #00adb5;
22 | color: #fff;
23 | &:hover {
24 | color: #fff;
25 | background-color: #0b949b;
26 | }
27 | }
28 | &:nth-child(2) {
29 | background-color: #dd6616;
30 | color: #fff;
31 | &:hover {
32 | color: #fff;
33 | background-color: #c95c13;
34 | }
35 | }
36 | }
37 |
38 | button {
39 | padding: 4px;
40 | border-radius: 5px;
41 | margin: 5px;
42 | background-color: #cf3737;
43 | color: #fff;
44 | border: none;
45 |
46 | &:hover {
47 | color: white;
48 | background-color: #950101;
49 | }
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/client/src/pages/admin/updateProduct/UpdateProduct.module.scss:
--------------------------------------------------------------------------------
1 | .new_product {
2 | width: 100%;
3 | min-height: 88.5vh;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 |
8 | .product_input {
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 |
13 | .form {
14 | background-color: white;
15 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
16 | // backdrop-filter: blur(4px);
17 | // -webkit-backdrop-filter: blur(4px);
18 | // border: 1px solid rgba(255, 255, 255, 0.18);
19 | padding: 2rem;
20 | border-radius: 20px;
21 | min-width: 50%;
22 | margin-top: 10px;
23 |
24 | .from_group {
25 | display: flex;
26 | flex-direction: column;
27 | gap: 5px;
28 | margin-top: 15px;
29 |
30 | label {
31 | color: black;
32 | font-weight: 500;
33 | letter-spacing: 1px;
34 | }
35 |
36 | input {
37 | height: 40px;
38 | border-radius: 20px;
39 | border: 2px solid #fff;
40 | outline: none;
41 | padding: 0.5rem 1rem;
42 | background-color: rgb(216, 214, 214);
43 | font-size: 16px;
44 | }
45 |
46 | textarea {
47 | height: 100px;
48 | border-radius: 20px;
49 | border: 2px solid #fff;
50 | outline: none;
51 | padding: 0.5rem 1rem;
52 | background-color: rgb(216, 214, 214);
53 | font-size: 16px;
54 | }
55 |
56 | select {
57 | height: 40px;
58 | border-radius: 20px;
59 | border: 2px solid #fff;
60 | outline: none;
61 | padding: 0.5rem 1rem;
62 | background-color: rgb(216, 214, 214);
63 | font-size: 16px;
64 | }
65 |
66 | button {
67 | height: 35px;
68 | border-radius: 20px;
69 | border: none;
70 | width: 80%;
71 | margin: 1rem auto;
72 | text-transform: uppercase;
73 | letter-spacing: 1px;
74 | font-weight: 500;
75 | transition: all 0.5s ease-in-out;
76 |
77 | &:hover {
78 | width: 90%;
79 | }
80 | }
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/client/src/pages/admin/users/Users.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useAlert } from "react-alert";
3 | import { Table } from "react-bootstrap";
4 | import { AiOutlineDelete, AiOutlineEye } from "react-icons/ai";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { Link } from "react-router-dom";
7 | import {
8 | allUsers,
9 | clearErrors,
10 | deleteUser,
11 | } from "../../../actions/userActions";
12 | import Loader from "../../../components/loader/Loader";
13 | import Sidebar from "../../../components/admin/sidebar/Sidebar";
14 | import { DELETE_USER_RESET } from "../../../constants/userConstants";
15 | import styles from "./Users.module.scss";
16 | import Navbar from "../../../components/admin/navbar/Navbar";
17 | import MetaData from "../../../components/MetaData";
18 |
19 | const Users = ({ history }) => {
20 | const alert = useAlert();
21 | const dispatch = useDispatch();
22 |
23 | const { loading, error, users } = useSelector((state) => state.allUsers);
24 | const { isDeleted } = useSelector((state) => state.user);
25 |
26 | useEffect(() => {
27 | dispatch(allUsers());
28 |
29 | if (error) {
30 | alert.error(error);
31 | dispatch(clearErrors());
32 | }
33 |
34 | if (isDeleted) {
35 | alert.success("User deleted successfully");
36 | history.push("/admin/users");
37 | dispatch({ type: DELETE_USER_RESET });
38 | }
39 | }, [dispatch, alert, error, isDeleted, history]);
40 |
41 | const deleteUserHandler = (id) => {
42 | dispatch(deleteUser(id));
43 | };
44 | return (
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {loading ? (
55 | <>
56 |
57 | >
58 | ) : (
59 | <>
60 |
61 |
62 |
63 | ID
64 | Image
65 | Name
66 | Email
67 | Role
68 | Actions
69 |
70 |
71 |
72 |
73 | {users?.map((user) => (
74 |
75 | {user?._id}
76 |
77 |
85 |
86 | {user?.name}
87 | {user?.email}
88 | {user?.role}
89 |
90 |
93 |
96 |
97 |
99 | deleteUserHandler(
100 | user._id
101 | )
102 | }
103 | >
104 |
107 |
108 |
109 |
110 | ))}
111 |
112 |
113 | >
114 | )}
115 |
116 |
117 |
118 |
119 | );
120 | };
121 |
122 | export default Users;
123 |
--------------------------------------------------------------------------------
/client/src/pages/admin/users/Users.module.scss:
--------------------------------------------------------------------------------
1 | .users {
2 | width: 100%;
3 | min-height: 88.5vh;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 |
8 | .table {
9 | background-color: white;
10 | border-radius: 10px;
11 | padding: 1rem;
12 |
13 | .actions {
14 | a {
15 | text-decoration: none;
16 | padding: 5px;
17 | border-radius: 5px;
18 | margin: 5px;
19 |
20 | &:nth-child(1) {
21 | background-color: #00adb5;
22 | color: #fff;
23 | &:hover {
24 | color: #fff;
25 | background-color: #0b949b;
26 | }
27 | }
28 | &:nth-child(2) {
29 | background-color: #dd6616;
30 | color: #fff;
31 | &:hover {
32 | color: #fff;
33 | background-color: #c95c13;
34 | }
35 | }
36 | }
37 |
38 | button {
39 | padding: 4px;
40 | border-radius: 5px;
41 | margin: 5px;
42 | background-color: #cf3737;
43 | color: #fff;
44 | border: none;
45 |
46 | &:hover {
47 | color: white;
48 | background-color: #950101;
49 | }
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/client/src/pages/admin/users/userDetails/UserDetails.module.scss:
--------------------------------------------------------------------------------
1 | .user_details {
2 | width: 100%;
3 | min-height: 100vh;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 | background-color: #fff;
8 | font-family: var(--font-poppins);
9 | position: relative;
10 |
11 | .user_information {
12 | background-color: #fff;
13 | box-shadow: 0 4px 8px 5px rgba(232, 226, 226, 0.2),
14 | 0 6px 20px 0 rgba(0, 0, 0, 0.19);
15 | border-radius: 20px;
16 |
17 | div {
18 | button {
19 | background-color: #f2effe;
20 | padding: 5px 15px;
21 | border: none;
22 | border-radius: 20px;
23 | color: #8067f8;
24 | transition: all 0.5s ease-in-out;
25 |
26 | &:hover {
27 | background-color: #ddd9e9;
28 | }
29 | }
30 | }
31 | }
32 |
33 | .modal {
34 | position: absolute;
35 | top: 20%;
36 | right: 30%;
37 | background: #fff;
38 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
39 | backdrop-filter: blur(4px);
40 | -webkit-backdrop-filter: blur(4px);
41 | border: 1px solid rgba(255, 255, 255, 0.18);
42 | padding: 2rem;
43 | border-radius: 20px;
44 | width: 400px;
45 | z-index: 999;
46 |
47 | .from_group {
48 | display: flex;
49 | flex-direction: column;
50 | gap: 5px;
51 | margin-top: 15px;
52 |
53 | label {
54 | color: white;
55 | font-weight: 500;
56 | letter-spacing: 1px;
57 | }
58 |
59 | input {
60 | height: 40px;
61 | border-radius: 20px;
62 | border: 2px solid #fff;
63 | outline: none;
64 | padding: 0.5rem 1rem;
65 | background-color: rgb(216, 214, 214);
66 | font-size: 16px;
67 | }
68 | select {
69 | height: 40px;
70 | border-radius: 20px;
71 | border: 2px solid #fff;
72 | outline: none;
73 | padding: 0.5rem 1rem;
74 | background-color: rgb(216, 214, 214);
75 | font-size: 16px;
76 | }
77 |
78 | button {
79 | height: 35px;
80 | border-radius: 20px;
81 | border: none;
82 | width: 80%;
83 | margin: 1rem auto;
84 | text-transform: uppercase;
85 | letter-spacing: 1px;
86 | font-weight: 500;
87 | transition: all 0.5s ease-in-out;
88 |
89 | &:hover {
90 | width: 90%;
91 | }
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/client/src/pages/auth/forgotPassword/ForgotPassword.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from "react";
2 | import { useAlert } from "react-alert";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { clearErrors, forgotPassword } from "../../../actions/userActions";
5 | import Footer from "../../../components/footer/Footer";
6 | import Navbar from "../../../components/header/Navbar";
7 | import MetaData from "../../../components/MetaData";
8 | import styles from "./ForgotPassword.module.scss";
9 |
10 | const ForgotPassword = () => {
11 | const [email, setEmail] = useState("");
12 |
13 | const alert = useAlert();
14 | const dispatch = useDispatch();
15 |
16 | const { error, loading, message } = useSelector(
17 | (state) => state.forgotPassword
18 | );
19 |
20 | useEffect(() => {
21 | if (error) {
22 | alert.error(error);
23 | dispatch(clearErrors());
24 | }
25 |
26 | if (message) {
27 | alert.success(message);
28 | }
29 | }, [dispatch, alert, error, message]);
30 |
31 | const submitHandler = (e) => {
32 | e.preventDefault();
33 |
34 | const formData = new FormData();
35 | formData.set("email", email);
36 |
37 | dispatch(forgotPassword(formData));
38 | };
39 | return (
40 |
41 |
42 |
43 |
71 |
72 |
73 | );
74 | };
75 |
76 | export default ForgotPassword;
77 |
--------------------------------------------------------------------------------
/client/src/pages/auth/forgotPassword/ForgotPassword.module.scss:
--------------------------------------------------------------------------------
1 | .forgot_password {
2 | margin-top: 100px;
3 | }
4 |
--------------------------------------------------------------------------------
/client/src/pages/auth/login/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from "react";
2 | import { useAlert } from "react-alert";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { Link } from "react-router-dom";
5 | import { clearErrors, login } from "../../../actions/userActions";
6 | import Footer from "../../../components/footer/Footer";
7 | import Navbar from "../../../components/header/Navbar";
8 | import ButtonLoader from "../../../components/loader/ButtonLoader";
9 | import MetaData from "../../../components/MetaData";
10 | import styles from "./Login.module.scss";
11 |
12 | const Login = ({ history, location }) => {
13 | const [email, setEmail] = useState("");
14 | const [password, setPassword] = useState("");
15 |
16 | const alert = useAlert();
17 | const dispatch = useDispatch();
18 |
19 | const { isAuthenticated, error, loading } = useSelector(
20 | (state) => state.auth
21 | );
22 |
23 | const redirect = location.search ? location.search.split("=")[1] : "/";
24 |
25 | useEffect(() => {
26 | if (isAuthenticated) {
27 | history.push(redirect);
28 | }
29 |
30 | if (error) {
31 | alert.error(error);
32 | dispatch(clearErrors());
33 | }
34 | }, [dispatch, alert, isAuthenticated, error, history, redirect]);
35 |
36 | const submitHandler = (e) => {
37 | e.preventDefault();
38 | dispatch(login(email, password));
39 | };
40 | return (
41 |
42 |
43 |
44 |
45 |
46 |
Login
47 |
72 |
73 |
74 | Dont Have Account ?{" "}
75 | Signup
76 |
77 |
78 | Forgot Password?
79 |
80 |
81 |
82 |
83 |
84 |
85 | );
86 | };
87 |
88 | export default Login;
89 |
--------------------------------------------------------------------------------
/client/src/pages/auth/login/Login.module.scss:
--------------------------------------------------------------------------------
1 | .login {
2 | min-height: 89vh;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | font-family: var(--font-poppins);
7 |
8 | background-image: url("../../../assets/home.png");
9 | background-repeat: no-repeat;
10 | background-size: cover;
11 |
12 | .login_container {
13 | background: rgba(230, 228, 228, 0.25);
14 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
15 | // backdrop-filter: blur(4px);
16 | // -webkit-backdrop-filter: blur(4px);
17 | border: 1px solid rgba(255, 255, 255, 0.18);
18 | padding: 2rem;
19 | border-radius: 20px;
20 | width: 400px;
21 |
22 | .from_group {
23 | display: flex;
24 | flex-direction: column;
25 | gap: 5px;
26 | margin-top: 15px;
27 |
28 | label {
29 | color: white;
30 | font-weight: 500;
31 | letter-spacing: 1px;
32 | }
33 |
34 | input {
35 | height: 40px;
36 | border-radius: 20px;
37 | border: 2px solid green;
38 | outline: none;
39 | padding: 0.5rem 1rem;
40 | background-color: rgb(216, 214, 214);
41 | font-size: 16px;
42 | }
43 |
44 | button {
45 | height: 35px;
46 | border-radius: 20px;
47 | border: none;
48 | width: 80%;
49 | margin: 1rem auto;
50 | text-transform: uppercase;
51 | letter-spacing: 1px;
52 | font-weight: 500;
53 | transition: all 0.5s ease-in-out;
54 |
55 | &:hover {
56 | width: 90%;
57 | }
58 | }
59 | p {
60 | a {
61 | text-decoration: none;
62 | color: rgb(33, 216, 33);
63 | cursor: pointer;
64 | }
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/client/src/pages/auth/register/Register.module.scss:
--------------------------------------------------------------------------------
1 | .login {
2 | min-height: 89vh;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | font-family: var(--font-poppins);
7 |
8 | background-image: url("../../../assets/home.png");
9 | background-repeat: no-repeat;
10 | background-size: cover;
11 |
12 | .login_container {
13 | background: rgba(230, 228, 228, 0.25);
14 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
15 | // backdrop-filter: blur(4px);
16 | // -webkit-backdrop-filter: blur(4px);
17 | border: 1px solid rgba(255, 255, 255, 0.18);
18 | padding: 2rem;
19 | border-radius: 20px;
20 | width: 400px;
21 |
22 | .from_group {
23 | display: flex;
24 | flex-direction: column;
25 | gap: 5px;
26 | margin-top: 15px;
27 |
28 | label {
29 | color: white;
30 | font-weight: 500;
31 | letter-spacing: 1px;
32 | }
33 |
34 | input {
35 | height: 40px;
36 | border-radius: 20px;
37 | border: 2px solid green;
38 | outline: none;
39 | padding: 0.5rem 1rem;
40 | background-color: rgb(216, 214, 214);
41 | font-size: 16px;
42 | }
43 |
44 | button {
45 | height: 35px;
46 | border-radius: 20px;
47 | border: none;
48 | width: 80%;
49 | margin: 1rem auto;
50 | text-transform: uppercase;
51 | letter-spacing: 1px;
52 | font-weight: 500;
53 | transition: all 0.5s ease-in-out;
54 |
55 | &:hover {
56 | width: 90%;
57 | }
58 | }
59 | p {
60 | a {
61 | text-decoration: none;
62 | color: rgb(33, 216, 33);
63 | cursor: pointer;
64 | }
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/client/src/pages/auth/resetPassword/ResetPassword.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from "react";
2 | import { useAlert } from "react-alert";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { clearErrors, resetPassword } from "../../../actions/userActions";
5 | import Footer from "../../../components/footer/Footer";
6 | import Navbar from "../../../components/header/Navbar";
7 | import MetaData from "../../../components/MetaData";
8 |
9 | import styles from "./ResetPassword.module.scss";
10 |
11 | const ResetPassword = ({ history, match }) => {
12 | const [password, setPassword] = useState("");
13 | const [confirmPassword, setConfirmPassword] = useState("");
14 |
15 | const alert = useAlert();
16 | const dispatch = useDispatch();
17 |
18 | const { error, success } = useSelector((state) => state.forgotPassword);
19 |
20 | useEffect(() => {
21 | if (error) {
22 | alert.error(error);
23 | dispatch(clearErrors());
24 | }
25 |
26 | if (success) {
27 | alert.success("Password updated successfully");
28 | history.push("/login");
29 | }
30 | }, [dispatch, alert, error, success, history]);
31 |
32 | const submitHandler = (e) => {
33 | e.preventDefault();
34 |
35 | const formData = new FormData();
36 | formData.set("password", password);
37 | formData.set("confirmPassword", confirmPassword);
38 |
39 | dispatch(resetPassword(match.params.token, formData));
40 | };
41 | return (
42 |
43 |
44 |
45 |
90 |
91 |
92 | );
93 | };
94 |
95 | export default ResetPassword;
96 |
--------------------------------------------------------------------------------
/client/src/pages/auth/resetPassword/ResetPassword.module.scss:
--------------------------------------------------------------------------------
1 | .reset_password {
2 | margin-top: 100px;
3 | }
4 |
--------------------------------------------------------------------------------
/client/src/pages/cart/Cart.module.scss:
--------------------------------------------------------------------------------
1 | .cart {
2 | min-height: 100vh;
3 | display: flex;
4 | margin-top: 30px;
5 | font-family: var(--font-poppins);
6 |
7 | .cart_container {
8 | padding: 1rem;
9 | .cart_item {
10 | .item_info {
11 | display: flex;
12 | align-items: center;
13 |
14 | a {
15 | text-decoration: none;
16 | }
17 |
18 | .stockCounter {
19 | display: flex;
20 | flex-direction: row;
21 | align-items: center;
22 | gap: 10px;
23 |
24 | span {
25 | height: 30px;
26 | width: 30px;
27 | padding: 5px;
28 | display: flex;
29 | align-items: center;
30 | justify-content: center;
31 | border-radius: 50%;
32 | border: 2px solid var(--color-gray);
33 | &:nth-child(1) {
34 | &:hover {
35 | background-color: rgb(182, 9, 9);
36 | border: 2px solid maroon;
37 | color: white;
38 | }
39 | }
40 | &:nth-child(3) {
41 | &:hover {
42 | background-color: rgb(12, 146, 12);
43 | border: 2px solid green;
44 | color: white;
45 | }
46 | }
47 | }
48 |
49 | input {
50 | width: 30%;
51 | padding: 5px 10px;
52 | border-radius: 20px;
53 | outline: 2px solid green;
54 | border: none;
55 | text-align: center;
56 | color: black;
57 | }
58 | }
59 | }
60 |
61 | button {
62 | border: none;
63 | padding: 5px 15px;
64 | border-radius: 20px;
65 |
66 | &:hover {
67 | color: red;
68 | }
69 | }
70 | }
71 | }
72 |
73 | .order_summary {
74 | padding: 1rem;
75 | button {
76 | height: 35px;
77 | border-radius: 20px;
78 | border: none;
79 | width: 80%;
80 | margin: 1rem auto;
81 | text-transform: uppercase;
82 | letter-spacing: 1px;
83 | font-weight: 500;
84 | transition: all 0.5s ease-in-out;
85 |
86 | &:hover {
87 | width: 90%;
88 | }
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/client/src/pages/cart/checkoutSteps/CheckoutSteps.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | const CheckoutSteps = ({ shipping, confirmOrder, payment }) => {
5 | return (
6 |
7 | {shipping ? (
8 |
9 |
10 |
Shipping
11 |
12 |
13 | ) : (
14 |
15 |
16 |
Shipping
17 |
18 |
19 | )}
20 |
21 | {confirmOrder ? (
22 |
23 |
24 |
Confirm Order
25 |
26 |
27 | ) : (
28 |
29 |
30 |
Confirm Order
31 |
32 |
33 | )}
34 |
35 | {payment ? (
36 |
37 |
38 |
Payment
39 |
40 |
41 | ) : (
42 |
43 |
44 |
Payment
45 |
46 |
47 | )}
48 |
49 | );
50 | };
51 |
52 | export default CheckoutSteps;
53 |
--------------------------------------------------------------------------------
/client/src/pages/cart/checkoutSteps/CheckoutSteps.module.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehedi008h/MERN-Stack-Full-Ecommerce-Site/f3d192cd2a6af2e805207f710f1ef5d750fac7cf/client/src/pages/cart/checkoutSteps/CheckoutSteps.module.scss
--------------------------------------------------------------------------------
/client/src/pages/cart/confirmOrder/ConfirmOrder.module.scss:
--------------------------------------------------------------------------------
1 | .confirm {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | margin-top: 10px;
7 | font-family: var(--font-poppins);
8 |
9 | .order_summary {
10 | background-color: aliceblue;
11 | padding: 1.5rem;
12 | border-radius: 10px;
13 |
14 | button {
15 | background-color: rgb(250, 156, 35);
16 | padding: 0.5rem 1rem;
17 | border: none;
18 | border-radius: 20px;
19 | color: #fff;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/client/src/pages/cart/payment/Payment.module.scss:
--------------------------------------------------------------------------------
1 | .payment {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | margin-top: 10px;
6 | font-family: var(--font-poppins);
7 |
8 | .payment_container {
9 | background: rgba(230, 228, 228, 0.25);
10 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
11 | // backdrop-filter: blur(4px);
12 | // -webkit-backdrop-filter: blur(4px);
13 | border: 1px solid rgba(255, 255, 255, 0.18);
14 | padding: 2rem;
15 | border-radius: 20px;
16 | width: 400px;
17 |
18 | margin: auto;
19 |
20 | .from_group {
21 | display: flex;
22 | flex-direction: column;
23 | gap: 5px;
24 | margin-top: 15px;
25 |
26 | label {
27 | color: black;
28 | font-weight: 500;
29 | letter-spacing: 1px;
30 | }
31 |
32 | input {
33 | height: 40px;
34 | border-radius: 20px;
35 | border: 2px solid white;
36 | outline: none;
37 | padding: 0.5rem 1rem;
38 | background-color: rgb(216, 214, 214);
39 | font-size: 16px;
40 | }
41 |
42 | button {
43 | height: 35px;
44 | border-radius: 20px;
45 | border: none;
46 | width: 80%;
47 | margin: 1rem auto;
48 | text-transform: uppercase;
49 | letter-spacing: 1px;
50 | font-weight: 500;
51 | background-color: rgb(250, 156, 35);
52 | color: #fff;
53 | transition: all 0.5s ease-in-out;
54 |
55 | &:hover {
56 | width: 90%;
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/client/src/pages/cart/shipping/Shipping.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import CheckoutSteps from "../checkoutSteps/CheckoutSteps";
4 | import { countries } from "countries-list";
5 | import styles from "./Shipping.module.scss";
6 | import { saveShippingInfo } from "../../../actions/cartActions";
7 | import Navbar from "../../../components/header/Navbar";
8 | import Footer from "../../../components/footer/Footer";
9 | import MetaData from "../../../components/MetaData";
10 |
11 | const Shipping = ({ history }) => {
12 | const countriesList = Object.values(countries);
13 |
14 | const { shippingInfo } = useSelector((state) => state.cart);
15 |
16 | const [address, setAddress] = useState(shippingInfo.address);
17 | const [city, setCity] = useState(shippingInfo.city);
18 | const [postalCode, setPostalCode] = useState(shippingInfo.postalCode);
19 | const [phoneNo, setPhoneNo] = useState(shippingInfo.phoneNo);
20 | const [country, setCountry] = useState(shippingInfo.country);
21 |
22 | const dispatch = useDispatch();
23 |
24 | const submitHandler = (e) => {
25 | e.preventDefault();
26 |
27 | dispatch(
28 | saveShippingInfo({ address, city, phoneNo, postalCode, country })
29 | );
30 | history.push("/confirm");
31 | };
32 | return (
33 |
34 |
35 |
36 |
108 |
109 |
110 | );
111 | };
112 |
113 | export default Shipping;
114 |
--------------------------------------------------------------------------------
/client/src/pages/cart/shipping/Shipping.module.scss:
--------------------------------------------------------------------------------
1 | .shipping {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | font-family: var(--font-poppins);
7 | margin-bottom: 20px;
8 |
9 | .shipping_container {
10 | background: rgba(230, 228, 228, 0.25);
11 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
12 | // backdrop-filter: blur(4px);
13 | // -webkit-backdrop-filter: blur(4px);
14 | border: 1px solid rgba(255, 255, 255, 0.18);
15 | padding: 2rem;
16 | border-radius: 20px;
17 | width: 400px;
18 | margin-top: 10px;
19 |
20 | .from_group {
21 | display: flex;
22 | flex-direction: column;
23 | gap: 5px;
24 | margin-top: 15px;
25 |
26 | label {
27 | color: black;
28 | font-weight: 500;
29 | letter-spacing: 1px;
30 | }
31 |
32 | input {
33 | height: 40px;
34 | border-radius: 20px;
35 | border: 2px solid white;
36 | outline: none;
37 | padding: 0.5rem 1rem;
38 | background-color: rgb(216, 214, 214);
39 | font-size: 16px;
40 | }
41 |
42 | select {
43 | height: 40px;
44 | border-radius: 20px;
45 | border: 2px solid white;
46 | outline: none;
47 | padding: 0.5rem 1rem;
48 | background-color: rgb(216, 214, 214);
49 | font-size: 16px;
50 | }
51 |
52 | button {
53 | height: 35px;
54 | border-radius: 20px;
55 | border: none;
56 | width: 80%;
57 | margin: 1rem auto;
58 | text-transform: uppercase;
59 | letter-spacing: 1px;
60 | font-weight: 500;
61 | transition: all 0.5s ease-in-out;
62 | background-color: #fa9c23;
63 | color: #fff;
64 |
65 | &:hover {
66 | width: 90%;
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/client/src/pages/cart/success/Success.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import Footer from "../../../components/footer/Footer";
3 | import Navbar from "../../../components/header/Navbar";
4 | import MetaData from "../../../components/MetaData";
5 | import styles from "./Success.module.scss";
6 |
7 | const Success = () => {
8 | return (
9 |
10 |
11 |
12 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default Success;
48 |
--------------------------------------------------------------------------------
/client/src/pages/cart/success/Success.module.scss:
--------------------------------------------------------------------------------
1 | .success {
2 | min-height: calc(100vh - 75px);
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: center;
6 | align-items: center;
7 | margin-top: 50px;
8 | font-family: var(--font-poppins);
9 |
10 | @supports (
11 | animation: grow 0.5s cubic-bezier(0.25, 0.25, 0.25, 1) forwards
12 | ) {
13 | .tick {
14 | stroke-opacity: 0;
15 | stroke-dasharray: 29px;
16 | stroke-dashoffset: 29px;
17 | animation: draw 0.5s cubic-bezier(0.25, 0.25, 0.25, 1) forwards;
18 | animation-delay: 0.6s;
19 | }
20 |
21 | .circle {
22 | fill-opacity: 0;
23 | stroke: #219a00;
24 | stroke-width: 16px;
25 | transform-origin: center;
26 | transform: scale(0);
27 | animation: grow 1s cubic-bezier(0.25, 0.25, 0.25, 1.25) forwards;
28 | }
29 | }
30 |
31 | @keyframes grow {
32 | 60% {
33 | transform: scale(0.8);
34 | stroke-width: 4px;
35 | fill-opacity: 0;
36 | }
37 | 100% {
38 | transform: scale(0.9);
39 | stroke-width: 8px;
40 | fill-opacity: 1;
41 | fill: #219a00;
42 | }
43 | }
44 |
45 | @keyframes draw {
46 | 0%,
47 | 100% {
48 | stroke-opacity: 1;
49 | }
50 | 100% {
51 | stroke-dashoffset: 0;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/client/src/pages/contact/Contact.module.scss:
--------------------------------------------------------------------------------
1 | .contact {
2 | font-family: var(--font-poppins);
3 | .contact_title {
4 | background-color: aliceblue;
5 | min-height: 120px;
6 | display: flex;
7 | align-items: center;
8 | }
9 |
10 | .contact_info {
11 | .about {
12 | width: 80%;
13 | @media screen and (max-width: 900px) {
14 | width: 90%;
15 | }
16 |
17 | p {
18 | color: var(--color-gray);
19 | }
20 |
21 | div {
22 | display: flex;
23 | flex-direction: row;
24 | span {
25 | height: 40px;
26 | width: 40px;
27 | padding: 10px;
28 | background-color: aliceblue;
29 | margin: 5px;
30 | border-radius: 50%;
31 | display: flex;
32 | align-items: center;
33 | justify-content: center;
34 |
35 | &:nth-child(1) {
36 | color: #139bf7;
37 | }
38 | &:nth-child(2) {
39 | color: #c629a1;
40 | }
41 | &:nth-child(3) {
42 | color: #ff0000;
43 | }
44 | }
45 | }
46 | }
47 | .contact {
48 | p {
49 | color: var(--color-gray);
50 | }
51 | }
52 | }
53 |
54 | .contact_form {
55 | background-color: aliceblue;
56 | padding: 2rem 0;
57 | margin: 5rem 0 0 0;
58 |
59 | .form {
60 | width: 400px;
61 | background: rgba(230, 228, 228, 0.25);
62 | box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
63 | padding: 2rem;
64 | border-radius: 10px;
65 | margin-top: 25px;
66 | @media screen and (max-width: 900px) {
67 | width: 300px;
68 | margin: 20px auto auto auto;
69 | }
70 | .from_group {
71 | display: flex;
72 | flex-direction: column;
73 | gap: 5px;
74 | margin-top: 15px;
75 |
76 | label {
77 | color: black;
78 | font-weight: 500;
79 | letter-spacing: 1px;
80 | }
81 |
82 | input {
83 | height: 40px;
84 | border-radius: 20px;
85 | border: 2px solid #fff;
86 | outline: none;
87 | padding: 0.5rem 1rem;
88 | font-size: 16px;
89 | }
90 | textarea {
91 | border-radius: 20px;
92 | border: 2px solid #fff;
93 | outline: none;
94 | padding: 0.5rem 1rem;
95 | font-size: 16px;
96 | }
97 |
98 | button {
99 | height: 35px;
100 | border-radius: 20px;
101 | border: none;
102 | width: 80%;
103 | margin: 1rem auto;
104 | text-transform: uppercase;
105 | letter-spacing: 1px;
106 | font-weight: 500;
107 | transition: all 0.5s ease-in-out;
108 | background-color: #c629a1;
109 | color: #fff;
110 |
111 | &:hover {
112 | width: 90%;
113 | }
114 | }
115 | }
116 | }
117 |
118 | .contact_img {
119 | img {
120 | height: 500px;
121 | width: 500px;
122 |
123 | @media screen and (max-width: 900px) {
124 | width: 300px;
125 | height: 300px;
126 | }
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/client/src/pages/home/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect } from "react";
2 | import { useAlert } from "react-alert";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { getAdminProducts } from "../../actions/productAction";
5 | import Footer from "../../components/footer/Footer";
6 | import Navbar from "../../components/header/Navbar";
7 | import Loader from "../../components/loader/Loader";
8 | import MetaData from "../../components/MetaData";
9 | import Banner from "./banner/Banner";
10 | import Category from "./category/Category";
11 | import Fashion from "./fashion/Fashion";
12 |
13 | const Home = () => {
14 | const alert = useAlert();
15 | const dispatch = useDispatch();
16 | const { loading, error, products } = useSelector((state) => state.products);
17 |
18 | // filter products by types
19 |
20 | const mens = products.filter((item) => item.type === "Men");
21 | const womens = products.filter((item) => item.type === "Women");
22 | const kids = products.filter((item) => item.type === "Kids");
23 |
24 | useEffect(() => {
25 | dispatch(getAdminProducts());
26 |
27 | if (error) {
28 | return alert.error(error);
29 | }
30 | }, [dispatch, alert, error]);
31 | return (
32 |
33 |
34 |
35 |
36 |
37 | {loading ? (
38 | <>
39 |
40 | >
41 | ) : (
42 | <>
43 |
44 |
45 |
46 | >
47 | )}
48 |
49 |
50 |
51 | );
52 | };
53 |
54 | export default Home;
55 |
--------------------------------------------------------------------------------
/client/src/pages/home/banner/Banner.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | // Import Swiper React components
4 | import { Swiper, SwiperSlide } from "swiper/react";
5 |
6 | // Import Swiper styles
7 | import "swiper/css";
8 | import "swiper/css/pagination";
9 |
10 | // import required modules
11 | import { Pagination } from "swiper";
12 |
13 | import styles from "./Banner.module.scss";
14 |
15 | const Banner = () => {
16 | return (
17 |
18 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
SPRING / SUMMER COLLECTION 2022
32 |
Get up to 30% off New Arrivals
33 |
34 | Shop now
35 |
36 |
37 |
38 |
39 |
40 | {/*
*/}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
SPRING / SUMMER COLLECTION 2022
57 |
Get up to 30% off New Arrivals
58 |
59 | Shop now
60 |
61 |
62 |
63 |
64 |
65 | {/*
*/}
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | );
78 | };
79 |
80 | export default Banner;
81 |
--------------------------------------------------------------------------------
/client/src/pages/home/banner/Banner.module.scss:
--------------------------------------------------------------------------------
1 | .banner {
2 | .swiper1 {
3 | min-height: 80vh;
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | font-family: var(--font-poppins);
8 | background-image: url("https://res.cloudinary.com/mehedi08h/image/upload/v1648866793/shopx/banner/parallax_mrz2mq.jpg");
9 |
10 | .slider_text {
11 | margin-top: 30px;
12 | p {
13 | color: var(--color-gray);
14 | font-weight: 700;
15 | letter-spacing: 1px;
16 | }
17 | h1 {
18 | color: black;
19 | line-height: 70px;
20 | font-size: 50px;
21 | font-weight: 700;
22 | letter-spacing: 1px;
23 | }
24 | div {
25 | margin-top: 30px;
26 | a {
27 | border: none;
28 | text-decoration: none;
29 | background-color: brown;
30 | padding: 0.5rem 1rem;
31 | color: #fff;
32 | font-weight: 500;
33 | text-transform: uppercase;
34 | }
35 | }
36 | }
37 | }
38 |
39 | .swiper2 {
40 | min-height: 80vh;
41 | display: flex;
42 | align-items: center;
43 | justify-content: center;
44 | font-family: var(--font-poppins);
45 | background-image: url("https://res.cloudinary.com/mehedi08h/image/upload/v1648866794/shopx/banner/2_rvde7n.jpg");
46 | background-size: cover;
47 | background-repeat: no-repeat;
48 |
49 | .slider_text {
50 | margin-top: 30px;
51 | p {
52 | color: #fff;
53 | font-weight: 700;
54 | letter-spacing: 1px;
55 | }
56 | h1 {
57 | color: black;
58 | line-height: 70px;
59 | font-size: 50px;
60 | font-weight: 700;
61 | letter-spacing: 1px;
62 | }
63 | div {
64 | margin-top: 30px;
65 | a {
66 | border: none;
67 | text-decoration: none;
68 | background-color: brown;
69 | padding: 0.5rem 1rem;
70 | color: #fff;
71 | font-weight: 500;
72 | text-transform: uppercase;
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/client/src/pages/home/category/Category.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | import styles from "./Category.module.scss";
4 |
5 | const Category = () => {
6 | const categorys = [
7 | {
8 | image: "https://res.cloudinary.com/mehedi08h/image/upload/v1648871045/shopx/21_odgu2m.jpg",
9 | title: "Mens's Fashion",
10 | link: Mens,
11 | },
12 | {
13 | image: "https://res.cloudinary.com/mehedi08h/image/upload/v1648871046/shopx/20_vtlnkz.jpg",
14 | title: "Women's Fashion",
15 | link: Women,
16 | },
17 | {
18 | image: "https://res.cloudinary.com/mehedi08h/image/upload/v1648871045/shopx/18_y8o95o.jpg",
19 | title: "Kid's Fashion",
20 | link: Kids,
21 | },
22 | {
23 | image: "https://res.cloudinary.com/mehedi08h/image/upload/v1648871045/shopx/13_upzdtm.jpg",
24 | title: "ACCESSORIES'S",
25 | link: Accessories,
26 | },
27 | ];
28 | return (
29 |
30 |
31 |
32 | {categorys.map((item, index) => (
33 |
34 |
35 |
36 |
37 |
38 |
{item.title}
39 |
{item.link}
40 |
41 |
42 |
43 | ))}
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default Category;
51 |
--------------------------------------------------------------------------------
/client/src/pages/home/category/Category.module.scss:
--------------------------------------------------------------------------------
1 | .category {
2 | font-family: var(--font-poppins);
3 | margin-top: 30px;
4 |
5 | .item {
6 | position: relative;
7 | height: 373px;
8 | width: 100%;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | border-radius: 10px;
13 | transition: all 0.5s ease-in-out;
14 | &:hover a {
15 | display: block;
16 | }
17 |
18 | img {
19 | height: 373px;
20 | width: 100%;
21 | border-radius: 10px;
22 | }
23 |
24 | div {
25 | position: absolute;
26 | bottom: 0%;
27 | background: rgba(230, 228, 228, 0.25);
28 | backdrop-filter: blur(4px);
29 | -webkit-backdrop-filter: blur(4px);
30 | border: 1px solid rgba(255, 255, 255, 0.18);
31 | min-height: 60px;
32 | width: 100%;
33 | padding: 1rem;
34 |
35 | h4 {
36 | font-weight: 700;
37 | text-align: center;
38 | color: var(--color-gray);
39 | }
40 |
41 | p {
42 | text-align: center;
43 | a {
44 | text-decoration: none;
45 | font-weight: 500;
46 | font-size: 18px;
47 | color: palevioletred;
48 | display: none;
49 | &:hover {
50 | color: var(--color-gray);
51 | }
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/client/src/pages/home/fashion/Fashion.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BsArrowLeftShort, BsArrowRightShort } from "react-icons/bs";
3 | import { Link } from "react-router-dom";
4 | import styles from "./Fashion.module.scss";
5 |
6 | const Fashion = ({ products, type }) => {
7 | const scrollRef = React.useRef(null);
8 |
9 | let data;
10 |
11 | switch (type) {
12 | case "mens":
13 | data = {
14 | title: "Mens's Fashion",
15 | link: "See all",
16 | };
17 | break;
18 | case "womens":
19 | data = {
20 | title: "Womens's Fashion",
21 | link: "See all",
22 | };
23 | break;
24 | case "kids":
25 | data = {
26 | title: "Kids's Fashion",
27 | link: "See all",
28 | };
29 | break;
30 | default:
31 | break;
32 | }
33 | const scroll = (direction) => {
34 | const { current } = scrollRef;
35 |
36 | if (direction === "left") {
37 | current.scrollLeft -= 300;
38 | } else {
39 | current.scrollLeft += 300;
40 | }
41 | };
42 | return (
43 |
44 |
45 |
46 |
{data.title}
47 |
48 | {data.link}
49 |
50 |
51 |
52 | {/* product section */}
53 |
54 |
55 |
59 | {products?.map((product, index) => (
60 |
61 |
65 |
66 |
67 | {product?.name}
68 |
69 |
70 |
$ {product?.price}
71 |
72 | ))}
73 |
74 |
75 | scroll("left")}
78 | />
79 | scroll("right")}
82 | />
83 |
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default Fashion;
91 |
--------------------------------------------------------------------------------
/client/src/pages/home/fashion/Fashion.module.scss:
--------------------------------------------------------------------------------
1 | .fashion {
2 | font-family: var(--font-poppins);
3 |
4 | div {
5 | div {
6 | h4 {
7 | font-weight: 700;
8 | }
9 |
10 | span {
11 | background-color: palevioletred;
12 | padding: 0.5rem 1.5rem;
13 | border-radius: 20px;
14 |
15 | &:hover a {
16 | border-bottom: 2px solid palegreen;
17 | }
18 |
19 | a {
20 | text-decoration: none;
21 | color: #fff;
22 | font-weight: 500;
23 | border-bottom: 2px solid palevioletred;
24 | transition: all 0.3s ease-in-out;
25 | }
26 | }
27 | }
28 |
29 | .products_container {
30 | display: flex;
31 | flex-direction: row;
32 | max-width: 100%;
33 | position: relative;
34 |
35 | .products_container_branch {
36 | margin-top: 15px;
37 | display: flex;
38 | flex-direction: row;
39 | width: max-content;
40 | overflow-x: scroll;
41 | -ms-overflow-style: none;
42 | scrollbar-width: none;
43 |
44 | &::-webkit-scrollbar {
45 | display: none;
46 | }
47 |
48 | .item {
49 | display: flex;
50 | margin: 1rem;
51 | flex-direction: column;
52 | align-items: center;
53 | width: 300px;
54 | background-color: aliceblue;
55 | border-radius: 10px;
56 | img {
57 | height: 200px;
58 | width: 300px;
59 | border-radius: 10px 10px 0 0;
60 | }
61 | p {
62 | a {
63 | text-decoration: none;
64 | color: black;
65 |
66 | &:hover {
67 | color: palevioletred;
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
74 | .app__gallery_images_arrows {
75 | width: 100%;
76 | display: flex;
77 | justify-content: space-between;
78 | align-items: center;
79 |
80 | padding: 0 1rem;
81 | position: absolute;
82 | bottom: 5%;
83 | }
84 |
85 | .gallery__arrow_icon {
86 | color: var(--color-black);
87 | font-size: 2rem;
88 | cursor: pointer;
89 | background: var(--color-golden);
90 | border-radius: 5px;
91 |
92 | &:hover {
93 | color: var(--color-white);
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/client/src/pages/products/Product.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useAlert } from "react-alert";
3 | import { AiFillStar, AiOutlineEye } from "react-icons/ai";
4 | import { MdOutlineFavoriteBorder } from "react-icons/md";
5 | import { useDispatch } from "react-redux";
6 | import { Link } from "react-router-dom";
7 | import { addItemToCart } from "../../actions/cartActions";
8 |
9 | import styles from "./Products.module.scss";
10 |
11 | const Product = ({ product }) => {
12 | const dispatch = useDispatch();
13 | const alert = useAlert();
14 |
15 | const addToCart = () => {
16 | dispatch(addItemToCart(product._id, 1));
17 | alert.success("Item Added to Cart");
18 | };
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
{product?.name}
27 |
28 |
29 |
30 |
31 |
{product?.numOfReviews}
32 |
33 |
34 | $ {product?.price}
35 |
36 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default Product;
54 |
--------------------------------------------------------------------------------
/client/src/pages/products/Products.module.scss:
--------------------------------------------------------------------------------
1 | .spinner {
2 | height: 88vh;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | }
7 |
8 | .products {
9 | font-family: var(--font-poppins);
10 | .filter {
11 | margin-bottom: 100px;
12 | p {
13 | background-color: black;
14 | padding: 0.5rem 2rem;
15 | color: #fff;
16 | font-weight: 500;
17 | letter-spacing: 0.5px;
18 | font-size: 1.2rem;
19 | width: 100%;
20 | }
21 |
22 | .categories {
23 | li {
24 | margin: 2px 0;
25 | &:hover {
26 | color: var(--color-gray);
27 | }
28 | }
29 | }
30 | }
31 | }
32 | .product {
33 | background-color: #f4f4f4;
34 | padding: 1rem;
35 | border-radius: 20px;
36 | font-family: var(--font-poppins);
37 | position: relative;
38 | transition: all 0.5s ease-in-out;
39 |
40 | &:hover .product_image {
41 | img {
42 | transform: scale(0.8, 0.8);
43 | }
44 | }
45 | &:hover .link_container {
46 | display: grid;
47 | }
48 |
49 | .product_image {
50 | text-align: center;
51 | img {
52 | width: 200px;
53 | height: 200px;
54 | object-fit: contain;
55 | transition: all 0.5s ease-in-out;
56 | }
57 | }
58 |
59 | a {
60 | text-decoration: none;
61 | text-align: center;
62 | margin-top: 10px;
63 | color: var(--color-black);
64 |
65 | .product_name {
66 | height: 40px;
67 | }
68 | }
69 |
70 | .product_rating {
71 | width: max-content;
72 | }
73 |
74 | .link_container {
75 | position: absolute;
76 | display: flex;
77 | flex-direction: column;
78 | gap: 15px;
79 | top: 10%;
80 | right: 8%;
81 | z-index: 1;
82 | display: none;
83 |
84 | button {
85 | background-color: #fff;
86 | border: none;
87 | padding: 5px;
88 | border-radius: 50%;
89 | color: rgb(218, 131, 145);
90 | cursor: pointer;
91 | }
92 |
93 | a {
94 | background-color: #fff;
95 | padding: 5px;
96 | border-radius: 50%;
97 | color: rgb(47, 45, 218);
98 | cursor: pointer;
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/client/src/pages/reviews/ListReview.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ListReview = ({ reviews }) => {
4 | return (
5 |
6 |
Other's Reviews:
7 |
8 | {reviews &&
9 | reviews.map((review) => (
10 |
11 |
19 |
by {review.name}
20 |
21 |
22 |
23 |
24 | ))}
25 |
26 | );
27 | };
28 |
29 | export default ListReview;
30 |
--------------------------------------------------------------------------------
/client/src/pages/user/Profile.module.scss:
--------------------------------------------------------------------------------
1 | .profile {
2 | display: flex;
3 | background-color: #fff;
4 | font-family: var(--font-poppins);
5 |
6 | .profile_container {
7 | background-color: #fff;
8 | box-shadow: 0 4px 8px 5px rgba(232, 226, 226, 0.2),
9 | 0 6px 20px 0 rgba(0, 0, 0, 0.19);
10 | border-radius: 10px;
11 |
12 | div {
13 | a {
14 | text-decoration: none;
15 | background-color: #f2effe;
16 | padding: 5px 15px;
17 | border: none;
18 | border-radius: 20px;
19 | color: #8067f8;
20 | transition: all 0.5s ease-in-out;
21 |
22 | &:hover {
23 | background-color: #ddd9e9;
24 | }
25 | }
26 | }
27 |
28 | .image {
29 | text-align: center;
30 | img {
31 | height: 250px;
32 | width: 250px;
33 | border-radius: 50%;
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/client/src/pages/user/changePassword/ChangePassword.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from "react";
2 | import { useAlert } from "react-alert";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { clearErrors, updatePassword } from "../../../actions/userActions";
5 | import Footer from "../../../components/footer/Footer";
6 | import Navbar from "../../../components/header/Navbar";
7 | import ButtonLoader from "../../../components/loader/ButtonLoader";
8 | import MetaData from "../../../components/MetaData";
9 | import ProfileLink from "../../../components/profileLinks/ProfileLink";
10 | import { UPDATE_PASSWORD_RESET } from "../../../constants/userConstants";
11 | import styles from "./ChangePassword.module.scss";
12 |
13 | const ChangePassword = ({ history }) => {
14 | const [oldPassword, setOldPassword] = useState("");
15 | const [password, setPassword] = useState("");
16 |
17 | const alert = useAlert();
18 | const dispatch = useDispatch();
19 |
20 | const { error, isUpdated, loading } = useSelector((state) => state.user);
21 |
22 | useEffect(() => {
23 | if (error) {
24 | alert.error(error);
25 | dispatch(clearErrors());
26 | }
27 |
28 | if (isUpdated) {
29 | alert.success("Password updated successfully");
30 |
31 | history.push("/me");
32 |
33 | dispatch({
34 | type: UPDATE_PASSWORD_RESET,
35 | });
36 | }
37 | }, [dispatch, alert, error, history, isUpdated]);
38 |
39 | const submitHandler = (e) => {
40 | e.preventDefault();
41 |
42 | const formData = new FormData();
43 | formData.set("oldPassword", oldPassword);
44 | formData.set("password", password);
45 |
46 | dispatch(updatePassword(formData));
47 | };
48 | return (
49 |
50 |
51 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
61 | Update Password
62 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | );
115 | };
116 |
117 | export default ChangePassword;
118 |
--------------------------------------------------------------------------------
/client/src/pages/user/changePassword/ChangePassword.module.scss:
--------------------------------------------------------------------------------
1 | .update_password {
2 | display: flex;
3 | background-color: #fff;
4 | min-height: 100vh;
5 | font-family: var(--font-poppins);
6 |
7 | .form_container {
8 | background-color: #ffffff;
9 | box-shadow: 0 4px 8px 5px rgba(232, 226, 226, 0.2),
10 | 0 6px 20px 0 rgba(0, 0, 0, 0.19);
11 | border-radius: 10px;
12 | padding: 1rem;
13 |
14 | .form {
15 | width: 400px;
16 | margin: 25px auto;
17 |
18 | .from_group {
19 | display: flex;
20 | flex-direction: column;
21 | gap: 5px;
22 | margin-top: 15px;
23 |
24 | label {
25 | color: black;
26 | font-weight: 500;
27 | letter-spacing: 1px;
28 | text-align: left;
29 | font-size: 16px;
30 | }
31 |
32 | input {
33 | height: 40px;
34 | border-radius: 20px;
35 | border: 2px solid #fff;
36 | outline: none;
37 | padding: 0.5rem 1rem;
38 | background-color: rgb(216, 214, 214);
39 | font-size: 16px;
40 | }
41 | button {
42 | height: 35px;
43 | border-radius: 20px;
44 | border: none;
45 | width: 80%;
46 | margin: 1rem auto;
47 | text-transform: uppercase;
48 | letter-spacing: 1px;
49 | font-weight: 500;
50 | font-size: 16px;
51 | background-color: #d3eacd;
52 | transition: all 0.5s ease-in-out;
53 |
54 | &:hover {
55 | width: 90%;
56 | }
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/client/src/pages/user/myOrders/MyOrders.module.scss:
--------------------------------------------------------------------------------
1 | .orders {
2 | display: flex;
3 | background-color: #fff;
4 | min-height: 100vh;
5 | font-family: var(--font-poppins);
6 |
7 | .orders_container {
8 | background-color: #ffffff;
9 | box-shadow: 0 4px 8px 5px rgba(232, 226, 226, 0.2),
10 | 0 6px 20px 0 rgba(0, 0, 0, 0.19);
11 | padding: 1rem;
12 | border-radius: 10px;
13 |
14 | .view_button {
15 | text-decoration: none;
16 | background-color: #e5e5f3;
17 | color: #5b5bd6;
18 | padding: 5px 15px;
19 | border-radius: 20px;
20 | transition: all 0.5s ease;
21 | &:hover {
22 | background-color: #eeeef3;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/client/src/pages/user/orderDetails/OrderDetails.module.scss:
--------------------------------------------------------------------------------
1 | .order_details {
2 | display: flex;
3 | background-color: #fff;
4 | min-height: 100vh;
5 | font-family: var(--font-poppins);
6 |
7 | .details {
8 | background-color: #fff;
9 | box-shadow: 0 4px 8px 5px rgba(232, 226, 226, 0.2),
10 | 0 6px 20px 0 rgba(0, 0, 0, 0.19);
11 | border-radius: 10px;
12 |
13 | div {
14 | button {
15 | border: none;
16 | background-color: #f8ecc3;
17 | padding: 5px 15px;
18 | border-radius: 20px;
19 | transition: all 0.5s;
20 |
21 | &:hover {
22 | background-color: #e4dcc2;
23 | }
24 | }
25 | }
26 |
27 | .done {
28 | color: aqua;
29 | }
30 |
31 | .inProgress {
32 | display: flex;
33 | flex-direction: column;
34 | align-items: center;
35 | animation: inProgress 1s ease infinite alternate;
36 | }
37 |
38 | .undone {
39 | display: flex;
40 | flex-direction: column;
41 | align-items: center;
42 | opacity: 0.3;
43 | }
44 | }
45 | }
46 |
47 | @keyframes inProgress {
48 | from {
49 | opacity: 0;
50 | }
51 | to {
52 | opacity: 1;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/client/src/pages/user/updateProfile/UpdateProfile.module.scss:
--------------------------------------------------------------------------------
1 | .update_profile {
2 | display: flex;
3 | background-color: #fff;
4 | min-height: 100vh;
5 | font-family: var(--font-poppins);
6 |
7 | .form_container {
8 | background-color: #ffffff;
9 | box-shadow: 0 4px 8px 5px rgba(232, 226, 226, 0.2),
10 | 0 6px 20px 0 rgba(0, 0, 0, 0.19);
11 | border-radius: 10px;
12 | padding: 1rem;
13 |
14 | .form {
15 | width: 400px;
16 | margin: 25px auto;
17 |
18 | .from_group {
19 | display: flex;
20 | flex-direction: column;
21 | gap: 5px;
22 | margin-top: 15px;
23 |
24 | label {
25 | color: black;
26 | font-weight: 500;
27 | letter-spacing: 1px;
28 | text-align: left;
29 | font-size: 16px;
30 | }
31 |
32 | input {
33 | height: 40px;
34 | border-radius: 20px;
35 | border: 2px solid #fff;
36 | outline: none;
37 | padding: 0.5rem 1rem;
38 | background-color: rgb(216, 214, 214);
39 | font-size: 16px;
40 | }
41 | button {
42 | height: 35px;
43 | border-radius: 20px;
44 | border: none;
45 | width: 80%;
46 | margin: 1rem auto;
47 | text-transform: uppercase;
48 | letter-spacing: 1px;
49 | font-weight: 500;
50 | font-size: 16px;
51 | background-color: #d3eacd;
52 | transition: all 0.5s ease-in-out;
53 |
54 | &:hover {
55 | width: 90%;
56 | }
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/client/src/reducers/cartReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_TO_CART,
3 | REMOVE_ITEM_CART,
4 | SAVE_SHIPPING_INFO,
5 | } from "../constants/cartConstants";
6 |
7 | export const cartReducer = (
8 | state = { cartItems: [], shippingInfo: {} },
9 | action
10 | ) => {
11 | switch (action.type) {
12 | case ADD_TO_CART:
13 | const item = action.payload;
14 |
15 | const isItemExist = state.cartItems.find(
16 | (i) => i.product === item.product
17 | );
18 |
19 | if (isItemExist) {
20 | return {
21 | ...state,
22 | cartItems: state.cartItems.map((i) =>
23 | i.product === isItemExist.product ? item : i
24 | ),
25 | };
26 | } else {
27 | return {
28 | ...state,
29 | cartItems: [...state.cartItems, item],
30 | };
31 | }
32 |
33 | case REMOVE_ITEM_CART:
34 | return {
35 | ...state,
36 | cartItems: state.cartItems.filter(
37 | (i) => i.product !== action.payload
38 | ),
39 | };
40 |
41 | case SAVE_SHIPPING_INFO:
42 | return {
43 | ...state,
44 | shippingInfo: action.payload,
45 | };
46 |
47 | default:
48 | return state;
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/client/src/reducers/orderReducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | CREATE_ORDER_REQUEST,
3 | CREATE_ORDER_SUCCESS,
4 | CREATE_ORDER_FAIL,
5 | CLEAR_ERRORS,
6 | ALL_ORDERS_REQUEST,
7 | ALL_ORDERS_SUCCESS,
8 | ALL_ORDERS_FAIL,
9 | UPDATE_ORDER_REQUEST,
10 | DELETE_ORDER_REQUEST,
11 | UPDATE_ORDER_SUCCESS,
12 | DELETE_ORDER_SUCCESS,
13 | UPDATE_ORDER_FAIL,
14 | DELETE_ORDER_FAIL,
15 | UPDATE_ORDER_RESET,
16 | DELETE_ORDER_RESET,
17 | ORDER_DETAILS_REQUEST,
18 | ORDER_DETAILS_SUCCESS,
19 | ORDER_DETAILS_FAIL,
20 | MY_ORDERS_REQUEST,
21 | MY_ORDERS_SUCCESS,
22 | MY_ORDERS_FAIL,
23 | } from "../constants/orderConstants";
24 |
25 | export const newOrderReducer = (state = {}, action) => {
26 | switch (action.type) {
27 | case CREATE_ORDER_REQUEST:
28 | return {
29 | ...state,
30 | loading: true,
31 | };
32 |
33 | case CREATE_ORDER_SUCCESS:
34 | return {
35 | loading: false,
36 | order: action.payload,
37 | };
38 |
39 | case CREATE_ORDER_FAIL:
40 | return {
41 | loading: false,
42 | error: action.payload,
43 | };
44 |
45 | case CLEAR_ERRORS:
46 | return {
47 | ...state,
48 | error: null,
49 | };
50 |
51 | default:
52 | return state;
53 | }
54 | };
55 |
56 | export const myOrdersReducer = (state = { orders: [] }, action) => {
57 | switch (action.type) {
58 | case MY_ORDERS_REQUEST:
59 | return {
60 | loading: true,
61 | };
62 |
63 | case MY_ORDERS_SUCCESS:
64 | return {
65 | loading: false,
66 | orders: action.payload,
67 | };
68 |
69 | case MY_ORDERS_FAIL:
70 | return {
71 | loading: false,
72 | error: action.payload,
73 | };
74 | case CLEAR_ERRORS:
75 | return {
76 | ...state,
77 | error: null,
78 | };
79 |
80 | default:
81 | return state;
82 | }
83 | };
84 |
85 | export const orderDetailsReducer = (state = { order: {} }, action) => {
86 | switch (action.type) {
87 | case ORDER_DETAILS_REQUEST:
88 | return {
89 | loading: true,
90 | };
91 |
92 | case ORDER_DETAILS_SUCCESS:
93 | return {
94 | loading: false,
95 | order: action.payload,
96 | };
97 |
98 | case ORDER_DETAILS_FAIL:
99 | return {
100 | loading: false,
101 | error: action.payload,
102 | };
103 | case CLEAR_ERRORS:
104 | return {
105 | ...state,
106 | error: null,
107 | };
108 |
109 | default:
110 | return state;
111 | }
112 | };
113 |
114 | export const allOrdersReducer = (state = { orders: [] }, action) => {
115 | switch (action.type) {
116 | case ALL_ORDERS_REQUEST:
117 | return {
118 | loading: true,
119 | };
120 |
121 | case ALL_ORDERS_SUCCESS:
122 | return {
123 | loading: false,
124 | orders: action.payload.orders,
125 | totalAmount: action.payload.totalAmount,
126 | };
127 |
128 | case ALL_ORDERS_FAIL:
129 | return {
130 | loading: false,
131 | error: action.payload,
132 | };
133 | case CLEAR_ERRORS:
134 | return {
135 | ...state,
136 | error: null,
137 | };
138 |
139 | default:
140 | return state;
141 | }
142 | };
143 |
144 | export const orderReducer = (state = {}, action) => {
145 | switch (action.type) {
146 | case UPDATE_ORDER_REQUEST:
147 | case DELETE_ORDER_REQUEST:
148 | return {
149 | ...state,
150 | loading: true,
151 | };
152 |
153 | case UPDATE_ORDER_SUCCESS:
154 | return {
155 | ...state,
156 | loading: false,
157 | isUpdated: action.payload,
158 | };
159 |
160 | case DELETE_ORDER_SUCCESS:
161 | return {
162 | ...state,
163 | loading: false,
164 | isDeleted: action.payload,
165 | };
166 |
167 | case UPDATE_ORDER_FAIL:
168 | case DELETE_ORDER_FAIL:
169 | return {
170 | ...state,
171 | error: action.payload,
172 | };
173 |
174 | case UPDATE_ORDER_RESET:
175 | return {
176 | ...state,
177 | isUpdated: false,
178 | };
179 |
180 | case DELETE_ORDER_RESET:
181 | return {
182 | ...state,
183 | isDeleted: false,
184 | };
185 |
186 | case CLEAR_ERRORS:
187 | return {
188 | ...state,
189 | error: null,
190 | };
191 |
192 | default:
193 | return state;
194 | }
195 | };
196 |
--------------------------------------------------------------------------------
/client/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/client/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/client/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, combineReducers, applyMiddleware } from "redux";
2 | import thunk from "redux-thunk";
3 | import { composeWithDevTools } from "redux-devtools-extension";
4 | import {
5 | newProductReducer,
6 | newReviewReducer,
7 | productDetailsReducer,
8 | productReducer,
9 | productReviewsReducer,
10 | productsReducer,
11 | reviewReducer,
12 | } from "./reducers/productReducers";
13 | import {
14 | allUsersReducer,
15 | authReducer,
16 | forgotPasswordReducer,
17 | userDetailsReducer,
18 | userReducer,
19 | } from "./reducers/userReducers";
20 | import { cartReducer } from "./reducers/cartReducers";
21 | import {
22 | allOrdersReducer,
23 | myOrdersReducer,
24 | newOrderReducer,
25 | orderDetailsReducer,
26 | orderReducer,
27 | } from "./reducers/orderReducers";
28 |
29 | const reducer = combineReducers({
30 | auth: authReducer,
31 | forgotPassword: forgotPasswordReducer,
32 | products: productsReducer,
33 | newProduct: newProductReducer,
34 | productDetails: productDetailsReducer,
35 | product: productReducer,
36 | cart: cartReducer,
37 | newOrder: newOrderReducer,
38 | allUsers: allUsersReducer,
39 | user: userReducer,
40 | userDetails: userDetailsReducer,
41 | allOrders: allOrdersReducer,
42 | order: orderReducer,
43 | orderDetails: orderDetailsReducer,
44 | myOrders: myOrdersReducer,
45 | productReviews: productReviewsReducer,
46 | review: reviewReducer,
47 | newReview: newReviewReducer,
48 | });
49 |
50 | let initialState = {
51 | cart: {
52 | cartItems: localStorage.getItem("cartItems")
53 | ? JSON.parse(localStorage.getItem("cartItems"))
54 | : [],
55 | shippingInfo: localStorage.getItem("shippingInfo")
56 | ? JSON.parse(localStorage.getItem("shippingInfo"))
57 | : {},
58 | },
59 | };
60 |
61 | const middlware = [thunk];
62 | const store = createStore(
63 | reducer,
64 | initialState,
65 | composeWithDevTools(applyMiddleware(...middlware))
66 | );
67 |
68 | export default store;
69 |
--------------------------------------------------------------------------------
/config/database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const connectDatabase = () => {
4 | mongoose
5 | .connect(process.env.DB_URI, {
6 | useNewUrlParser: true,
7 | useUnifiedTopology: true,
8 | })
9 | .then((con) => {
10 | console.log(
11 | `MongoDB Database connected with HOST: ${con.connection.host}`
12 | );
13 | });
14 | };
15 |
16 | module.exports = connectDatabase;
17 |
--------------------------------------------------------------------------------
/controller/orderController.js:
--------------------------------------------------------------------------------
1 | const Order = require("../models/order");
2 | const Product = require("../models/product");
3 |
4 | const ErrorHandler = require("../utils/errorHandler");
5 | const catchAsyncErrors = require("../middleware/catchAsyncErrors");
6 |
7 | // Create a new order => /api/v1/order/new
8 | exports.newOrder = catchAsyncErrors(async (req, res, next) => {
9 | const {
10 | orderItems,
11 | shippingInfo,
12 | itemsPrice,
13 | taxPrice,
14 | shippingPrice,
15 | totalPrice,
16 | paymentInfo,
17 | } = req.body;
18 |
19 | const order = await Order.create({
20 | orderItems,
21 | shippingInfo,
22 | itemsPrice,
23 | taxPrice,
24 | shippingPrice,
25 | totalPrice,
26 | paymentInfo,
27 | paidAt: Date.now(),
28 | user: req.user._id,
29 | });
30 |
31 | res.status(200).json({
32 | success: true,
33 | order,
34 | });
35 | });
36 |
37 | // Get logged in user orders => /api/v1/orders/me
38 | exports.myOrders = catchAsyncErrors(async (req, res, next) => {
39 | const orders = await Order.find({ user: req.user.id });
40 |
41 | res.status(200).json({
42 | success: true,
43 | orders,
44 | });
45 | });
46 |
47 | // Get single order => /api/v1/order/:id
48 | exports.getSingleOrder = catchAsyncErrors(async (req, res, next) => {
49 | const order = await Order.findById(req.params.id).populate(
50 | "user",
51 | "name email"
52 | );
53 |
54 | if (!order) {
55 | return next(new ErrorHandler("No Order found with this ID", 404));
56 | }
57 |
58 | res.status(200).json({
59 | success: true,
60 | order,
61 | });
62 | });
63 |
64 | // admin section
65 |
66 | // Get all orders - ADMIN => /api/v1/admin/orders/
67 | exports.allOrders = catchAsyncErrors(async (req, res, next) => {
68 | const orders = await Order.find();
69 |
70 | let totalAmount = 0;
71 |
72 | orders.forEach((order) => {
73 | totalAmount += order.totalPrice;
74 | });
75 |
76 | res.status(200).json({
77 | success: true,
78 | totalAmount,
79 | orders,
80 | });
81 | });
82 |
83 | // Update / Process order - ADMIN => /api/v1/admin/order/:id
84 | exports.updateOrder = catchAsyncErrors(async (req, res, next) => {
85 | const order = await Order.findById(req.params.id);
86 |
87 | if (order.orderStatus === "Delivered") {
88 | return next(
89 | new ErrorHandler("You have already delivered this order", 400)
90 | );
91 | }
92 |
93 | order.orderItems.forEach(async (item) => {
94 | await updateStock(item.product, item.quantity);
95 | });
96 |
97 | (order.orderStatus = req.body.status), (order.deliveredAt = Date.now());
98 |
99 | await order.save();
100 |
101 | res.status(200).json({
102 | success: true,
103 | });
104 | });
105 |
106 | async function updateStock(id, quantity) {
107 | const product = await Product.findById(id);
108 |
109 | product.stock = product.stock - quantity;
110 |
111 | await product.save({ validateBeforeSave: false });
112 | }
113 |
114 | // Delete order => /api/v1/admin/order/:id
115 | exports.deleteOrder = catchAsyncErrors(async (req, res, next) => {
116 | const order = await Order.findById(req.params.id);
117 |
118 | if (!order) {
119 | return next(new ErrorHandler("No Order found with this ID", 404));
120 | }
121 |
122 | await order.remove();
123 |
124 | res.status(200).json({
125 | success: true,
126 | });
127 | });
128 |
--------------------------------------------------------------------------------
/controller/paymentController.js:
--------------------------------------------------------------------------------
1 | const catchAsyncErrors = require("../middleware/catchAsyncErrors");
2 |
3 | const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
4 |
5 | // Process stripe payments => /api/v1/payment/process
6 | exports.processPayment = catchAsyncErrors(async (req, res, next) => {
7 | const paymentIntent = await stripe.paymentIntents.create({
8 | amount: req.body.amount,
9 | currency: "usd",
10 |
11 | metadata: { integration_check: "accept_a_payment" },
12 | });
13 |
14 | res.status(200).json({
15 | success: true,
16 | client_secret: paymentIntent.client_secret,
17 | });
18 | });
19 |
20 | // Send stripe API Key => /api/v1/stripeapi
21 | exports.sendStripApi = catchAsyncErrors(async (req, res, next) => {
22 | res.status(200).json({
23 | stripeApiKey: process.env.STRIPE_API_KEY,
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const dotenv = require("dotenv");
3 | const bodyParser = require("body-parser");
4 | const cookieParser = require("cookie-parser");
5 | const fileUpload = require("express-fileupload");
6 | const connectDatabase = require("./config/database");
7 | const cloudinary = require("cloudinary");
8 | const path = require("path");
9 | const errorMiddleware = require("./middleware/error");
10 |
11 | const app = express();
12 | dotenv.config();
13 |
14 | app.use(express.json());
15 | app.use(bodyParser.urlencoded({ extended: true }));
16 | app.use(cookieParser());
17 | app.use(fileUpload());
18 |
19 | // import all routes
20 | const auth = require("./routes/auth");
21 | const products = require("./routes/product");
22 | const payment = require("./routes/payment");
23 | const order = require("./routes/order");
24 |
25 | app.use("/api/v1", auth);
26 | app.use("/api/v1", products);
27 | app.use("/api/v1", payment);
28 | app.use("/api/v1", order);
29 |
30 | app.use(express.static(path.join(__dirname, "/client/build")));
31 |
32 | app.get("*", (req, res) => {
33 | res.sendFile(path.join(__dirname, "/client/build", "index.html"));
34 | });
35 |
36 | // connecting to database
37 | connectDatabase();
38 |
39 | // Setting up cloudinary configuration
40 | cloudinary.config({
41 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
42 | api_key: process.env.CLOUDINARY_API_KEY,
43 | api_secret: process.env.CLOUDINARY_API_SECRET,
44 | });
45 |
46 | app.use("/", (req, res) => {
47 | res.send("App is running.");
48 | });
49 |
50 | // Middleware to handle error
51 | app.use(errorMiddleware);
52 |
53 | const PORT = process.env.PORT || 5000;
54 | app.listen(PORT, () => {
55 | console.log("Server is running on port", PORT);
56 | });
57 |
--------------------------------------------------------------------------------
/middleware/auth.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/user");
2 |
3 | const jwt = require("jsonwebtoken");
4 | const ErrorHandler = require("../utils/errorHandler");
5 | const catchAsyncErrors = require("./catchAsyncErrors");
6 |
7 | // Checks if user is authenticated or not
8 | exports.isAuthenticatedUser = catchAsyncErrors(async (req, res, next) => {
9 | const { token } = req.cookies;
10 |
11 | if (!token) {
12 | return next(new ErrorHandler("Login first to access this resource.", 401));
13 | }
14 |
15 | const decoded = jwt.verify(token, process.env.JWT_SECRET);
16 | req.user = await User.findById(decoded.id);
17 | next();
18 | });
19 |
20 | // Handling users roles
21 | exports.authorizeRoles = (...roles) => {
22 | return (req, res, next) => {
23 | if (!roles.includes(req.user.role)) {
24 | return next(
25 | new ErrorHandler(
26 | `Role (${req.user.role}) is not allowed to acccess this resource`,
27 | 403
28 | )
29 | );
30 | }
31 | next();
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/middleware/catchAsyncErrors.js:
--------------------------------------------------------------------------------
1 | module.exports = (func) => (req, res, next) =>
2 | Promise.resolve(func(req, res, next)).catch(next);
3 |
--------------------------------------------------------------------------------
/middleware/error.js:
--------------------------------------------------------------------------------
1 | const ErrorHandler = require("../utils/errorHandler");
2 |
3 | module.exports = (err, req, res, next) => {
4 | err.statusCode = err.statusCode || 500;
5 |
6 | let error = { ...err };
7 |
8 | error.message = err.message;
9 |
10 | // Wrong Mongoose Object ID Error
11 | if (err.name === "CastError") {
12 | console.log(err);
13 | const message = `Resource not found. Invalid: ${err.path}`;
14 | error = new ErrorHandler(message, 400);
15 | }
16 |
17 | // Handling Mongoose Validation Error
18 | if (err.name === "ValidationError") {
19 | const message = Object.values(err.errors).map((value) => value.message);
20 | error = new ErrorHandler(message, 400);
21 | }
22 |
23 | // Handling Mongoose duplicate key errors
24 | if (err.code === 11000) {
25 | const message = `Duplicate ${Object.keys(err.keyValue)} entered`;
26 | error = new ErrorHandler(message, 400);
27 | }
28 |
29 | // Handling wrong JWT error
30 | if (err.name === "JsonWebTokenError") {
31 | const message = "JSON Web Token is invalid. Try Again!!!";
32 | error = new ErrorHandler(message, 400);
33 | }
34 |
35 | // Handling Expired JWT error
36 | if (err.name === "TokenExpiredError") {
37 | const message = "JSON Web Token is expired. Try Again!!!";
38 | error = new ErrorHandler(message, 400);
39 | }
40 |
41 | res.status(error.statusCode).json({
42 | success: false,
43 | message: error.message || "Internal Server Error",
44 | });
45 | };
46 |
--------------------------------------------------------------------------------
/models/order.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const orderSchema = mongoose.Schema({
4 | shippingInfo: {
5 | address: {
6 | type: String,
7 | required: true,
8 | },
9 | city: {
10 | type: String,
11 | required: true,
12 | },
13 | phoneNo: {
14 | type: String,
15 | required: true,
16 | },
17 | postalCode: {
18 | type: String,
19 | required: true,
20 | },
21 | country: {
22 | type: String,
23 | required: true,
24 | },
25 | },
26 | user: {
27 | type: mongoose.Schema.Types.ObjectId,
28 | required: true,
29 | ref: "User",
30 | },
31 | orderItems: [
32 | {
33 | name: {
34 | type: String,
35 | required: true,
36 | },
37 | quantity: {
38 | type: Number,
39 | required: true,
40 | },
41 | image: {
42 | type: String,
43 | required: true,
44 | },
45 | price: {
46 | type: Number,
47 | required: true,
48 | },
49 | product: {
50 | type: mongoose.Schema.Types.ObjectId,
51 | required: true,
52 | ref: "Product",
53 | },
54 | },
55 | ],
56 | paymentInfo: {
57 | id: {
58 | type: String,
59 | },
60 | status: {
61 | type: String,
62 | },
63 | },
64 | paidAt: {
65 | type: Date,
66 | },
67 |
68 | itemsPrice: {
69 | type: Number,
70 | required: true,
71 | default: 0.0,
72 | },
73 | taxPrice: {
74 | type: Number,
75 | required: true,
76 | default: 0.0,
77 | },
78 | shippingPrice: {
79 | type: Number,
80 | required: true,
81 | default: 0.0,
82 | },
83 | totalPrice: {
84 | type: Number,
85 | required: true,
86 | default: 0.0,
87 | },
88 | orderStatus: {
89 | type: String,
90 | required: true,
91 | default: "Processing",
92 | },
93 | deliveredAt: {
94 | type: Date,
95 | },
96 | createdAt: {
97 | type: Date,
98 | default: Date.now,
99 | },
100 | });
101 |
102 | module.exports = mongoose.model("Order", orderSchema);
103 |
--------------------------------------------------------------------------------
/models/product.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const productSchema = new mongoose.Schema({
4 | name: {
5 | type: String,
6 | required: [true, "Please enter product name"],
7 | trim: true,
8 | maxLength: [100, "Product name cannot exceed 100 characters"],
9 | },
10 | price: {
11 | type: Number,
12 | required: [true, "Please enter product price"],
13 | maxLength: [5, "Product name cannot exceed 5 characters"],
14 | default: 0.0,
15 | },
16 | description: {
17 | type: String,
18 | required: [true, "Please enter product description"],
19 | },
20 | ratings: {
21 | type: Number,
22 | default: 0,
23 | },
24 | images: [
25 | {
26 | public_id: {
27 | type: String,
28 | required: true,
29 | },
30 | url: {
31 | type: String,
32 | required: true,
33 | },
34 | },
35 | ],
36 | category: {
37 | type: String,
38 | required: [true, "Please select category for this product"],
39 | enum: {
40 | values: [
41 | "Eid Collection",
42 | "New Collection",
43 | "Featured",
44 | "Footwear",
45 | "Accessories",
46 | "Clothing",
47 | "Beauty/Health",
48 | "Sports",
49 | "Outdoor",
50 | "Other",
51 | ],
52 | message: "Please select correct category for product",
53 | },
54 | },
55 | type: {
56 | type: String,
57 | required: [false, "Please select type for this product"],
58 | },
59 | seller: {
60 | type: String,
61 | required: [true, "Please enter product seller"],
62 | },
63 | stock: {
64 | type: Number,
65 | required: [true, "Please enter product stock"],
66 | maxLength: [5, "Product name cannot exceed 5 characters"],
67 | default: 0,
68 | },
69 | numOfReviews: {
70 | type: Number,
71 | default: 0,
72 | },
73 | reviews: [
74 | {
75 | user: {
76 | type: mongoose.Schema.ObjectId,
77 | ref: "User",
78 | required: true,
79 | },
80 | name: {
81 | type: String,
82 | required: true,
83 | },
84 | rating: {
85 | type: Number,
86 | required: true,
87 | },
88 | comment: {
89 | type: String,
90 | required: true,
91 | },
92 | },
93 | ],
94 | user: {
95 | type: mongoose.Schema.ObjectId,
96 | ref: "User",
97 | required: true,
98 | },
99 | createdAt: {
100 | type: Date,
101 | default: Date.now,
102 | },
103 | });
104 |
105 | module.exports = mongoose.model("Product", productSchema);
106 |
--------------------------------------------------------------------------------
/models/user.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const validator = require("validator");
3 | const bcrypt = require("bcryptjs");
4 | const jwt = require("jsonwebtoken");
5 | const crypto = require("crypto");
6 |
7 | const userSchema = new mongoose.Schema({
8 | name: {
9 | type: String,
10 | required: [true, "Please enter your name"],
11 | maxLength: [30, "Your name cannot exceed 30 characters"],
12 | },
13 | email: {
14 | type: String,
15 | required: [true, "Please enter your email"],
16 | unique: true,
17 | validate: [validator.isEmail, "Please enter valid email address"],
18 | },
19 | phone: {
20 | type: String,
21 | required: [false, "Please enter your phone number"],
22 | maxLength: [200, "Your phone number cannot exceed 200 characters"],
23 | },
24 | address: {
25 | type: String,
26 | required: [false, "Please enter your address"],
27 | maxLength: [200, "Your address cannot exceed 200 characters"],
28 | },
29 | password: {
30 | type: String,
31 | required: [true, "Please enter your password"],
32 | minlength: [6, "Your password must be longer than 6 characters"],
33 | select: false,
34 | },
35 | avatar: {
36 | public_id: {
37 | type: String,
38 | required: true,
39 | },
40 | url: {
41 | type: String,
42 | required: true,
43 | },
44 | },
45 | role: {
46 | type: String,
47 | default: "user",
48 | },
49 | createdAt: {
50 | type: Date,
51 | default: Date.now,
52 | },
53 | resetPasswordToken: String,
54 | resetPasswordExpire: Date,
55 | });
56 |
57 | // Encrypting password before saving user
58 | userSchema.pre("save", async function (next) {
59 | if (!this.isModified("password")) {
60 | next();
61 | }
62 |
63 | this.password = await bcrypt.hash(this.password, 10);
64 | });
65 |
66 | // Compare user password
67 | userSchema.methods.comparePassword = async function (enteredPassword) {
68 | return await bcrypt.compare(enteredPassword, this.password);
69 | };
70 |
71 | // Return JWT token
72 | userSchema.methods.getJwtToken = function () {
73 | return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
74 | expiresIn: process.env.JWT_EXPIRES_TIME,
75 | });
76 | };
77 |
78 | // Generate password reset token
79 | userSchema.methods.getResetPasswordToken = function () {
80 | // Generate token
81 | const resetToken = crypto.randomBytes(20).toString("hex");
82 |
83 | // Hash and set to resetPasswordToken
84 | this.resetPasswordToken = crypto
85 | .createHash("sha256")
86 | .update(resetToken)
87 | .digest("hex");
88 |
89 | // Set token expire time
90 | this.resetPasswordExpire = Date.now() + 30 * 60 * 1000;
91 |
92 | return resetToken;
93 | };
94 |
95 | module.exports = mongoose.model("User", userSchema);
96 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shopx",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js",
8 | "heroku-postbuild": "cd client && npm install && npm run build"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcryptjs": "^2.4.3",
15 | "body-parser": "^1.19.2",
16 | "cloudinary": "^1.28.1",
17 | "cookie-parser": "^1.4.6",
18 | "dotenv": "^16.0.0",
19 | "express": "^4.17.3",
20 | "express-fileupload": "^1.3.1",
21 | "jsonwebtoken": "^8.5.1",
22 | "mongoose": "^6.2.7",
23 | "nodemailer": "^6.7.2",
24 | "rc-slider": "^9.7.5",
25 | "stripe": "^8.209.0",
26 | "validator": "^13.7.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/routes/auth.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 |
4 | const {
5 | registerUser,
6 | loginUser,
7 | forgotPassword,
8 | resetPassword,
9 | getUserProfile,
10 | updatePassword,
11 | updateProfile,
12 | logout,
13 | allUsers,
14 | getUserDetails,
15 | updateUser,
16 | deleteUser,
17 | } = require("../controller/authController");
18 | const { isAuthenticatedUser, authorizeRoles } = require("../middleware/auth");
19 |
20 | router.route("/register").post(registerUser);
21 | router.route("/login").post(loginUser);
22 | router.route("/password/forgot").post(forgotPassword);
23 | router.route("/password/reset/:token").put(resetPassword);
24 | router.route("/logout").get(logout);
25 |
26 | router.route("/password/update").put(isAuthenticatedUser, updatePassword);
27 | router.route("/me").get(isAuthenticatedUser, getUserProfile);
28 | router.route("/me/update").put(isAuthenticatedUser, updateProfile);
29 |
30 | // admin
31 | router
32 | .route("/admin/users")
33 | .get(isAuthenticatedUser, authorizeRoles("admin"), allUsers);
34 | router
35 | .route("/admin/user/:id")
36 | .get(isAuthenticatedUser, authorizeRoles("admin"), getUserDetails)
37 | .put(isAuthenticatedUser, authorizeRoles("admin"), updateUser)
38 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteUser);
39 |
40 | module.exports = router;
41 |
--------------------------------------------------------------------------------
/routes/order.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 |
4 | const {
5 | newOrder,
6 | allOrders,
7 | updateOrder,
8 | deleteOrder,
9 | getSingleOrder,
10 | myOrders,
11 | } = require("../controller/orderController");
12 |
13 | const { isAuthenticatedUser, authorizeRoles } = require("../middleware/auth");
14 |
15 | router.route("/order/new").post(isAuthenticatedUser, newOrder);
16 | router.route("/order/:id").get(isAuthenticatedUser, getSingleOrder);
17 | router.route("/orders/me").get(isAuthenticatedUser, myOrders);
18 |
19 | // admin sections
20 | router
21 | .route("/admin/orders/")
22 | .get(isAuthenticatedUser, authorizeRoles("admin"), allOrders);
23 | router
24 | .route("/admin/order/:id")
25 | .put(isAuthenticatedUser, authorizeRoles("admin"), updateOrder)
26 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteOrder);
27 |
28 | module.exports = router;
29 |
--------------------------------------------------------------------------------
/routes/payment.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 |
4 | const {
5 | processPayment,
6 | sendStripApi,
7 | } = require("../controller/paymentController");
8 |
9 | const { isAuthenticatedUser } = require("../middleware/auth");
10 |
11 | router.route("/payment/process").post(isAuthenticatedUser, processPayment);
12 | router.route("/stripeapi").get(isAuthenticatedUser, sendStripApi);
13 |
14 | module.exports = router;
15 |
--------------------------------------------------------------------------------
/routes/product.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const {
3 | newProduct,
4 | getProducts,
5 | getSingleProducts,
6 | getAdminProducts,
7 | updateProduct,
8 | deleteProduct,
9 | createProductReview,
10 | getProductReviews,
11 | deleteReview,
12 | } = require("../controller/productController");
13 | const { isAuthenticatedUser, authorizeRoles } = require("../middleware/auth");
14 | const router = express.Router();
15 |
16 | router.route("/products").get(getProducts);
17 | router.route("/product/:id").get(getSingleProducts);
18 |
19 | router
20 | .route("/admin/products/new")
21 | .post(isAuthenticatedUser, authorizeRoles("admin"), newProduct);
22 | router.route("/admin/products").get(getAdminProducts);
23 | router
24 | .route("/admin/product/:id")
25 | .put(isAuthenticatedUser, authorizeRoles("admin"), updateProduct)
26 | .delete(isAuthenticatedUser, authorizeRoles("admin"), deleteProduct);
27 |
28 | // product reviews
29 |
30 | router.route("/review").put(isAuthenticatedUser, createProductReview);
31 | router.route("/reviews").get(isAuthenticatedUser, getProductReviews);
32 | router.route("/reviews").delete(isAuthenticatedUser, deleteReview);
33 |
34 | module.exports = router;
35 |
--------------------------------------------------------------------------------
/utils/apiFeatures.js:
--------------------------------------------------------------------------------
1 | class APIFeatures {
2 | constructor(query, queryStr) {
3 | this.query = query;
4 | this.queryStr = queryStr;
5 | }
6 |
7 | search() {
8 | const keyword = this.queryStr.keyword
9 | ? {
10 | name: {
11 | $regex: this.queryStr.keyword,
12 | $options: "i",
13 | },
14 | }
15 | : {};
16 |
17 | this.query = this.query.find({ ...keyword });
18 | return this;
19 | }
20 |
21 | filter() {
22 | const queryCopy = { ...this.queryStr };
23 |
24 | // Removing fields from the query
25 | const removeFields = ["keyword", "limit", "page"];
26 | removeFields.forEach((el) => delete queryCopy[el]);
27 |
28 | // Advance filter for price, ratings etc
29 | let queryStr = JSON.stringify(queryCopy);
30 | queryStr = queryStr.replace(
31 | /\b(gt|gte|lt|lte)\b/g,
32 | (match) => `$${match}`
33 | );
34 |
35 | this.query = this.query.find(JSON.parse(queryStr));
36 | return this;
37 | }
38 |
39 | pagination(resPerPage) {
40 | const currentPage = Number(this.queryStr.page) || 1;
41 | const skip = resPerPage * (currentPage - 1);
42 |
43 | this.query = this.query.limit(resPerPage).skip(skip);
44 | return this;
45 | }
46 | }
47 |
48 | module.exports = APIFeatures;
49 |
--------------------------------------------------------------------------------
/utils/errorHandler.js:
--------------------------------------------------------------------------------
1 | // Error Handler Class
2 | class ErrorHandler extends Error {
3 | constructor(message, statusCode) {
4 | super(message);
5 | this.statusCode = statusCode;
6 |
7 | Error.captureStackTrace(this, this.constructor);
8 | }
9 | }
10 |
11 | module.exports = ErrorHandler;
12 |
--------------------------------------------------------------------------------
/utils/jwtToken.js:
--------------------------------------------------------------------------------
1 | // Create and send token and save in the cookie.
2 | const sendToken = (user, statusCode, res) => {
3 | // Create Jwt token
4 | const token = user.getJwtToken();
5 |
6 | // Options for cookie
7 | const options = {
8 | expires: new Date(
9 | Date.now() + process.env.COOKIE_EXPIRES_TIME * 24 * 60 * 60 * 1000
10 | ),
11 | httpOnly: true,
12 | };
13 |
14 | res.status(statusCode).cookie("token", token, options).json({
15 | success: true,
16 | token,
17 | user,
18 | });
19 | };
20 |
21 | module.exports = sendToken;
22 |
--------------------------------------------------------------------------------
/utils/sendEmail.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require("nodemailer");
2 |
3 | const sendEmail = async (options) => {
4 | const transporter = nodemailer.createTransport({
5 | host: process.env.SMTP_HOST,
6 | port: process.env.SMTP_PORT,
7 | auth: {
8 | user: process.env.SMTP_EMAIL,
9 | pass: process.env.SMTP_PASSWORD,
10 | },
11 | });
12 |
13 | const message = {
14 | from: `${process.env.SMTP_FROM_NAME} <${process.env.SMTP_FROM_EMAIL}>`,
15 | to: options.email,
16 | subject: options.subject,
17 | text: options.message,
18 | };
19 |
20 | await transporter.sendMail(message);
21 | };
22 |
23 | module.exports = sendEmail;
24 |
--------------------------------------------------------------------------------