├── dashboard ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── images │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ ├── 9.png │ │ ├── logo.png │ │ ├── user.png │ │ ├── favicon.png │ │ ├── product.png │ │ ├── static.png │ │ └── not-found.png │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── src │ ├── index.js │ ├── components │ │ ├── LoadingError │ │ │ ├── Error.js │ │ │ ├── Loading.js │ │ │ └── Toast.js │ │ ├── Categories │ │ │ ├── MainCategories.js │ │ │ ├── CreateCategory.js │ │ │ └── CategoriesTable.js │ │ ├── Home │ │ │ ├── SalesStatistics.js │ │ │ ├── ProductsStatistics.js │ │ │ ├── Main.js │ │ │ ├── LatestOrder.js │ │ │ └── TopTotal.js │ │ ├── products │ │ │ ├── Product.js │ │ │ └── MainProducts.js │ │ ├── orders │ │ │ ├── OrderMain.js │ │ │ ├── OrderDetailInfo.js │ │ │ ├── Orders.js │ │ │ ├── OrderDetailProducts.js │ │ │ └── OrderDetailmain.js │ │ ├── Header.js │ │ ├── sidebar.js │ │ └── users │ │ │ └── UserComponent.js │ ├── screens │ │ ├── HomeScreen.js │ │ ├── OrderScreen.js │ │ ├── UsersScreen.js │ │ ├── AddProduct.js │ │ ├── productScreen.js │ │ ├── CategoriesScreen.js │ │ ├── OrderDetailScreen.js │ │ ├── ProductEditScreen.js │ │ ├── NotFound.js │ │ └── LoginScreen.js │ ├── Redux │ │ ├── Constants │ │ │ ├── UserContants.js │ │ │ ├── OrderConstants.js │ │ │ └── ProductConstants.js │ │ ├── Reducers │ │ │ ├── userReducers.js │ │ │ ├── OrderReducres.js │ │ │ └── ProductReducers.js │ │ ├── store.js │ │ └── Actions │ │ │ ├── userActions.js │ │ │ ├── OrderActions.js │ │ │ └── ProductActions.js │ ├── PrivateRouter.js │ ├── App.js │ ├── responsive.css │ └── data │ │ └── Products.js ├── .gitignore ├── package.json └── README.md ├── client frontend ├── public │ ├── robots.txt │ ├── favicon.png │ ├── images │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ ├── 9.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── logo.png │ │ ├── user.png │ │ └── not-found.png │ ├── logo22.png │ ├── manifest.json │ └── index.html ├── src │ ├── components │ │ ├── LoadingError │ │ │ ├── Error.js │ │ │ ├── Loading.js │ │ │ └── Toast.js │ │ ├── homeComponents │ │ │ ├── CalltoActionSection.js │ │ │ ├── pagination.js │ │ │ ├── ContactInfo.js │ │ │ ├── Rating.js │ │ │ └── ShopSection.js │ │ ├── Footer.js │ │ └── profileComponents │ │ │ ├── Orders.js │ │ │ └── ProfileTabs.js │ ├── Redux │ │ ├── Constants │ │ │ ├── CartConstants.js │ │ │ ├── ProductConstants.js │ │ │ ├── OrderConstants.js │ │ │ └── UserContants.js │ │ ├── Reducers │ │ │ ├── CartReducers.js │ │ │ ├── ProductReducers.js │ │ │ ├── userReducers.js │ │ │ └── OrderReducres.js │ │ ├── Actions │ │ │ ├── cartActions.js │ │ │ ├── ProductActions.js │ │ │ ├── OrderActions.js │ │ │ └── userActions.js │ │ └── store.js │ ├── index.js │ ├── PrivateRouter.js │ ├── screens │ │ ├── HomeScreen.js │ │ ├── NotFound.js │ │ ├── PaymentScreen.js │ │ ├── Login.js │ │ ├── ShippingScreen.js │ │ ├── Register.js │ │ ├── ProfileScreen.js │ │ └── CartScreen.js │ ├── App.js │ ├── data │ │ └── Products.js │ └── responsive.css ├── .gitignore ├── package.json └── README.md ├── server ├── utils │ └── generateToken.js ├── data │ ├── users.js │ └── Products.js ├── .gitignore ├── config │ └── MongoDb.js ├── Middleware │ ├── Errors.js │ └── AuthMiddleware.js ├── package.json ├── DataImport.js ├── server.js ├── Models │ ├── UserModel.js │ ├── ProductModel.js │ └── OrderModel.js └── Routes │ ├── orderRoutes.js │ ├── UserRoutes.js │ └── ProductRoutes.js └── SourceCode.md /dashboard/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /dashboard/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/favicon.ico -------------------------------------------------------------------------------- /dashboard/public/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/1.png -------------------------------------------------------------------------------- /dashboard/public/images/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/10.png -------------------------------------------------------------------------------- /dashboard/public/images/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/11.png -------------------------------------------------------------------------------- /dashboard/public/images/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/12.png -------------------------------------------------------------------------------- /dashboard/public/images/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/13.png -------------------------------------------------------------------------------- /dashboard/public/images/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/14.png -------------------------------------------------------------------------------- /dashboard/public/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/2.png -------------------------------------------------------------------------------- /dashboard/public/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/3.png -------------------------------------------------------------------------------- /dashboard/public/images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/4.png -------------------------------------------------------------------------------- /dashboard/public/images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/5.png -------------------------------------------------------------------------------- /dashboard/public/images/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/6.png -------------------------------------------------------------------------------- /dashboard/public/images/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/7.png -------------------------------------------------------------------------------- /dashboard/public/images/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/8.png -------------------------------------------------------------------------------- /dashboard/public/images/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/9.png -------------------------------------------------------------------------------- /dashboard/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/logo192.png -------------------------------------------------------------------------------- /dashboard/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/logo512.png -------------------------------------------------------------------------------- /dashboard/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/logo.png -------------------------------------------------------------------------------- /dashboard/public/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/user.png -------------------------------------------------------------------------------- /client frontend/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/favicon.png -------------------------------------------------------------------------------- /client frontend/public/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/1.png -------------------------------------------------------------------------------- /client frontend/public/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/2.png -------------------------------------------------------------------------------- /client frontend/public/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/3.png -------------------------------------------------------------------------------- /client frontend/public/images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/4.png -------------------------------------------------------------------------------- /client frontend/public/images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/5.png -------------------------------------------------------------------------------- /client frontend/public/images/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/6.png -------------------------------------------------------------------------------- /client frontend/public/images/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/7.png -------------------------------------------------------------------------------- /client frontend/public/images/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/8.png -------------------------------------------------------------------------------- /client frontend/public/images/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/9.png -------------------------------------------------------------------------------- /client frontend/public/logo22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/logo22.png -------------------------------------------------------------------------------- /dashboard/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/favicon.png -------------------------------------------------------------------------------- /dashboard/public/images/product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/product.png -------------------------------------------------------------------------------- /dashboard/public/images/static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/static.png -------------------------------------------------------------------------------- /client frontend/public/images/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/10.png -------------------------------------------------------------------------------- /client frontend/public/images/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/11.png -------------------------------------------------------------------------------- /client frontend/public/images/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/12.png -------------------------------------------------------------------------------- /client frontend/public/images/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/13.png -------------------------------------------------------------------------------- /client frontend/public/images/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/14.png -------------------------------------------------------------------------------- /dashboard/public/images/not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/dashboard/public/images/not-found.png -------------------------------------------------------------------------------- /client frontend/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/logo.png -------------------------------------------------------------------------------- /client frontend/public/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/user.png -------------------------------------------------------------------------------- /client frontend/public/images/not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MERN-Folk/Full-SorceCode-ShoeShop-Ecommerce-Web/HEAD/client frontend/public/images/not-found.png -------------------------------------------------------------------------------- /server/utils/generateToken.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | const generateToken = (id) => { 4 | return jwt.sign({ id }, process.env.JWT_SECRET, { 5 | expiresIn: "30d", 6 | }); 7 | }; 8 | 9 | export default generateToken; 10 | -------------------------------------------------------------------------------- /client frontend/src/components/LoadingError/Error.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Message = ({ variant, children }) => { 4 | return
{children}
; 5 | }; 6 | 7 | Message.defaultProps = { 8 | variant: "alert-info", 9 | }; 10 | 11 | export default Message; 12 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Constants/CartConstants.js: -------------------------------------------------------------------------------- 1 | export const CART_ADD_ITEM = "CART_ADD_ITEM"; 2 | export const CART_REMOVE_ITEM = "CART_REMOVE_ITEM"; 3 | export const CART_CLEAR_ITEMS = "CART_CLEAR_ITEMS"; 4 | export const CART_SAVE_SHIPPING_ADDRESS = "CART_SAVE_SHIPPING_ADDRESS"; 5 | export const CART_SAVE_PAYMENT_METHOD = "CART_SAVE_PAYMENT_METHOD"; 6 | -------------------------------------------------------------------------------- /client frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import store from "./Redux/store"; 5 | import { Provider } from "react-redux"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | 12 | document.getElementById("root") 13 | ); 14 | -------------------------------------------------------------------------------- /dashboard/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import { Provider } from "react-redux"; 5 | import store from "./Redux/store"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | , 13 | document.getElementById("root") 14 | ); 15 | -------------------------------------------------------------------------------- /server/data/users.js: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | 3 | const users = [ 4 | { 5 | name: "Admin", 6 | email: "admin@example.com", 7 | password: bcrypt.hashSync("123456", 10), 8 | isAdmin: true, 9 | }, 10 | { 11 | name: "User", 12 | email: "user@example.com", 13 | password: bcrypt.hashSync("123456", 10), 14 | }, 15 | ]; 16 | 17 | export default users; 18 | -------------------------------------------------------------------------------- /dashboard/src/components/LoadingError/Error.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Message = ({ variant, children }) => { 4 | return ( 5 |
6 |
{children}
7 |
8 | ); 9 | }; 10 | 11 | Message.defaultProps = { 12 | variant: "alert-info", 13 | }; 14 | 15 | export default Message; 16 | -------------------------------------------------------------------------------- /dashboard/.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 | -------------------------------------------------------------------------------- /server/.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 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /client frontend/.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 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /dashboard/src/screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Header from "../components/Header"; 3 | import Main from "../components/Home/Main"; 4 | import Sidebar from "./../components/sidebar"; 5 | 6 | const HomeScreen = () => { 7 | return ( 8 | <> 9 | 10 |
11 |
12 |
13 |
14 | 15 | ); 16 | }; 17 | 18 | export default HomeScreen; 19 | -------------------------------------------------------------------------------- /server/config/MongoDb.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const connectDatabase = async () => { 4 | try { 5 | const conn = await mongoose.connect(process.env.MONGO_URL, { 6 | useUnifiedTopology: true, 7 | useNewUrlParser: true, 8 | }); 9 | 10 | console.log(`MongoDB Connected`); 11 | } catch (error) { 12 | console.error(`Error: ${error.message}`); 13 | process.exit(1); 14 | } 15 | }; 16 | 17 | export default connectDatabase; 18 | -------------------------------------------------------------------------------- /dashboard/src/Redux/Constants/UserContants.js: -------------------------------------------------------------------------------- 1 | export const USER_LOGIN_REQUEST = "USER_LOGIN_REQUEST"; 2 | export const USER_LOGIN_SUCCESS = "USER_LOGIN_SUCCESS"; 3 | export const USER_LOGIN_FAIL = "USER_LOGIN_FAIL"; 4 | export const USER_LOGOUT = "USER_LOGOUT"; 5 | 6 | export const USER_LIST_REQUEST = "USER_LIST_REQUEST"; 7 | export const USER_LIST_SUCCESS = "USER_LIST_SUCCESS"; 8 | export const USER_LIST_FAIL = "USER_LIST_FAIL"; 9 | export const USER_LIST_RESET = "USER_LIST_RESET"; 10 | -------------------------------------------------------------------------------- /dashboard/src/components/LoadingError/Loading.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Loading = () => { 4 | return ( 5 |
6 |
11 | Loading... 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default Loading; 18 | -------------------------------------------------------------------------------- /client frontend/src/components/LoadingError/Loading.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Loading = () => { 4 | return ( 5 |
6 |
11 | Loading... 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default Loading; 18 | -------------------------------------------------------------------------------- /dashboard/src/screens/OrderScreen.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Sidebar from "./../components/sidebar"; 3 | import Header from "./../components/Header"; 4 | import OrderMain from "../components/orders/OrderMain"; 5 | 6 | const OrderScreen = () => { 7 | return ( 8 | <> 9 | 10 |
11 |
12 | 13 |
14 | 15 | ); 16 | }; 17 | 18 | export default OrderScreen; 19 | -------------------------------------------------------------------------------- /dashboard/src/screens/UsersScreen.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Sidebar from "./../components/sidebar"; 3 | import Header from "./../components/Header"; 4 | import UserComponent from "../components/users/UserComponent"; 5 | 6 | const UsersScreen = () => { 7 | return ( 8 | <> 9 | 10 |
11 |
12 | 13 |
14 | 15 | ); 16 | }; 17 | 18 | export default UsersScreen; 19 | -------------------------------------------------------------------------------- /dashboard/src/components/LoadingError/Toast.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ToastContainer } from "react-toastify"; 3 | 4 | const Toast = () => { 5 | return ( 6 |
7 | 14 | {/* Same as */} 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default Toast; 21 | -------------------------------------------------------------------------------- /dashboard/src/screens/AddProduct.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Sidebar from "./../components/sidebar"; 3 | import Header from "./../components/Header"; 4 | import AddProductMain from "./../components/products/AddProductMain"; 5 | 6 | const AddProduct = () => { 7 | return ( 8 | <> 9 | 10 |
11 |
12 | 13 |
14 | 15 | ); 16 | }; 17 | 18 | export default AddProduct; 19 | -------------------------------------------------------------------------------- /client frontend/src/components/LoadingError/Toast.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ToastContainer } from "react-toastify"; 3 | 4 | const Toast = () => { 5 | return ( 6 |
7 | 14 | {/* Same as */} 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default Toast; 21 | -------------------------------------------------------------------------------- /dashboard/src/screens/productScreen.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Sidebar from "./../components/sidebar"; 3 | import Header from "./../components/Header"; 4 | import MainProducts from "./../components/products/MainProducts"; 5 | 6 | const ProductScreen = () => { 7 | return ( 8 | <> 9 | 10 |
11 |
12 | 13 |
14 | 15 | ); 16 | }; 17 | 18 | export default ProductScreen; 19 | -------------------------------------------------------------------------------- /dashboard/src/screens/CategoriesScreen.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Sidebar from "./../components/sidebar"; 3 | import Header from "./../components/Header"; 4 | import MainCategories from "./../components/Categories/MainCategories"; 5 | 6 | const CategoriesScreen = () => { 7 | return ( 8 | <> 9 | 10 |
11 |
12 | 13 |
14 | 15 | ); 16 | }; 17 | 18 | export default CategoriesScreen; 19 | -------------------------------------------------------------------------------- /server/Middleware/Errors.js: -------------------------------------------------------------------------------- 1 | const notFound = (req, res, next) => { 2 | const error = new Error(`Not found - ${req.originalUrl}`); 3 | res.status(404); 4 | next(error); 5 | }; 6 | 7 | const errorHandler = (err, req, res, next) => { 8 | const statusCode = res.statusCode === 200 ? 500 : res.statusCode; 9 | res.status(statusCode); 10 | res.json({ 11 | message: err.message, 12 | stack: process.env.NODE_ENV === "production" ? null : err.stack, 13 | }); 14 | }; 15 | 16 | export { notFound, errorHandler }; 17 | -------------------------------------------------------------------------------- /client frontend/src/PrivateRouter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Redirect, Route } from "react-router-dom"; 3 | 4 | function PrivateRouter({ component: Component, ...rest }) { 5 | return ( 6 | { 9 | const token = window.localStorage.getItem("userInfo"); 10 | if (token) { 11 | return ; 12 | } else { 13 | return ; 14 | } 15 | }} 16 | /> 17 | ); 18 | } 19 | 20 | export default PrivateRouter; 21 | -------------------------------------------------------------------------------- /dashboard/src/screens/OrderDetailScreen.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Sidebar from "./../components/sidebar"; 3 | import Header from "./../components/Header"; 4 | import OrderDetailmain from "../components/orders/OrderDetailmain"; 5 | 6 | const OrderDetailScreen = ({ match }) => { 7 | const orderId = match.params.id; 8 | return ( 9 | <> 10 | 11 |
12 |
13 | 14 |
15 | 16 | ); 17 | }; 18 | 19 | export default OrderDetailScreen; 20 | -------------------------------------------------------------------------------- /dashboard/src/screens/ProductEditScreen.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Sidebar from "./../components/sidebar"; 3 | import Header from "./../components/Header"; 4 | import EditProductMain from "./../components/products/EditproductMain"; 5 | 6 | const ProductEditScreen = ({ match }) => { 7 | const productId = match.params.id; 8 | return ( 9 | <> 10 | 11 |
12 |
13 | 14 |
15 | 16 | ); 17 | }; 18 | export default ProductEditScreen; 19 | -------------------------------------------------------------------------------- /dashboard/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 frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "ShoeShop", 3 | "name": "ShoeShop E-commerce", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "favicon.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "favicon.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 | -------------------------------------------------------------------------------- /dashboard/src/Redux/Constants/OrderConstants.js: -------------------------------------------------------------------------------- 1 | export const ORDER_LIST_REQUEST = "ORDER_LIST_REQUEST"; 2 | export const ORDER_LIST_SUCCESS = "ORDER_LIST_SUCCESS"; 3 | export const ORDER_LIST_FAIL = "ORDER_LIST_FAIL"; 4 | 5 | export const ORDER_DETAILS_REQUEST = "ORDER_DETAILS_REQUEST"; 6 | export const ORDER_DETAILS_SUCCESS = "ORDER_DETAILS_SUCCESS"; 7 | export const ORDER_DETAILS_FAIL = "ORDER_DETAILS_FAIL"; 8 | 9 | export const ORDER_DELIVERED_REQUEST = "ORDER_DELIVERED_REQUEST"; 10 | export const ORDER_DELIVERED_SUCCESS = "ORDER_DELIVERED_SUCCESS"; 11 | export const ORDER_DELIVERED_FAIL = "ORDER_DELIVERED_FAIL"; 12 | export const ORDER_DELIVERED_RESET = "ORDER_DELIVERED_RESET"; 13 | -------------------------------------------------------------------------------- /dashboard/src/PrivateRouter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { Redirect, Route } from "react-router-dom"; 4 | 5 | function PrivateRouter({ component: Component, ...rest }) { 6 | const userLogin = useSelector((state) => state.userLogin); 7 | const { userInfo } = userLogin; 8 | return ( 9 | { 12 | if (userInfo && userInfo.isAdmin) { 13 | return ; 14 | } else { 15 | return ; 16 | } 17 | }} 18 | /> 19 | ); 20 | } 21 | 22 | export default PrivateRouter; 23 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "server.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "node ./server.js", 10 | "server":"nodemon ./server.js" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "bcryptjs": "^2.4.3", 16 | "dotenv": "^10.0.0", 17 | "express": "^4.17.2", 18 | "express-async-handler": "^1.2.0", 19 | "jsonwebtoken": "^8.5.1", 20 | "mongoose": "^6.1.4", 21 | "morgan": "^1.10.0" 22 | }, 23 | "devDependencies": { 24 | "concurrently": "^7.0.0", 25 | "nodemon": "^2.0.15" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Constants/ProductConstants.js: -------------------------------------------------------------------------------- 1 | export const PRODUCT_LIST_REQUEST = "PRODUCT_LIST_REQUEST"; 2 | export const PRODUCT_LIST_SUCCESS = "PRODUCT_LIST_SUCCESS"; 3 | export const PRODUCT_LIST_FAIL = "PRODUCT_LIST_FAIL"; 4 | 5 | export const PRODUCT_DETAILS_REQUEST = "PRODUCT_DETAILS_REQUEST"; 6 | export const PRODUCT_DETAILS_SUCCESS = "PRODUCT_DETAILS_SUCCESS"; 7 | export const PRODUCT_DETAILS_FAIL = "PRODUCT_DETAILS_FAIL"; 8 | 9 | export const PRODUCT_CREATE_REVIEW_REQUEST = "PRODUCT_CREATE_REVIEW_REQUEST"; 10 | export const PRODUCT_CREATE_REVIEW_SUCCESS = "PRODUCT_CREATE_REVIEW_SUCCESS"; 11 | export const PRODUCT_CREATE_REVIEW_FAIL = "PRODUCT_CREATE_REVIEW_FAIL"; 12 | export const PRODUCT_CREATE_REVIEW_RESET = "PRODUCT_CREATE_REVIEW_RESET"; 13 | -------------------------------------------------------------------------------- /dashboard/src/components/Categories/MainCategories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CreateCategory from "./CreateCategory"; 3 | import CategoriesTable from "./CategoriesTable"; 4 | 5 | const MainCategories = () => { 6 | return ( 7 |
8 |
9 |

Categories

10 |
11 | 12 |
13 |
14 |
15 | {/* Create category */} 16 | 17 | {/* Categories table */} 18 | 19 |
20 |
21 |
22 |
23 | ); 24 | }; 25 | 26 | export default MainCategories; 27 | -------------------------------------------------------------------------------- /client frontend/src/screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Header from "./../components/Header"; 3 | import ShopSection from "./../components/homeComponents/ShopSection"; 4 | import ContactInfo from "./../components/homeComponents/ContactInfo"; 5 | import CalltoActionSection from "./../components/homeComponents/CalltoActionSection"; 6 | import Footer from "./../components/Footer"; 7 | 8 | const HomeScreen = ({ match }) => { 9 | window.scrollTo(0, 0); 10 | const keyword = match.params.keyword; 11 | const pagenumber = match.params.pagenumber; 12 | return ( 13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 | ); 21 | }; 22 | 23 | export default HomeScreen; 24 | -------------------------------------------------------------------------------- /server/DataImport.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import User from "./Models/UserModel.js"; 3 | import users from "./data/users.js"; 4 | import Product from "./Models/ProductModel.js"; 5 | import products from "./data/Products.js"; 6 | import asyncHandler from "express-async-handler"; 7 | 8 | const ImportData = express.Router(); 9 | 10 | ImportData.post( 11 | "/user", 12 | asyncHandler(async (req, res) => { 13 | await User.remove({}); 14 | const importUser = await User.insertMany(users); 15 | res.send({ importUser }); 16 | }) 17 | ); 18 | 19 | ImportData.post( 20 | "/products", 21 | asyncHandler(async (req, res) => { 22 | await Product.remove({}); 23 | const importProducts = await Product.insertMany(products); 24 | res.send({ importProducts }); 25 | }) 26 | ); 27 | 28 | export default ImportData; 29 | -------------------------------------------------------------------------------- /dashboard/src/screens/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const NotFound = () => { 5 | return ( 6 | <> 7 |
8 |
9 |

Page Not Found

10 | Not-found 15 | 20 |
21 |
22 | 23 | ); 24 | }; 25 | 26 | export default NotFound; 27 | -------------------------------------------------------------------------------- /client frontend/src/components/homeComponents/CalltoActionSection.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const CalltoActionSection = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |

DO you need more tips?

11 |

Sign up free and get the latest tips.

12 |
13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 | ); 22 | }; 23 | 24 | export default CalltoActionSection; 25 | -------------------------------------------------------------------------------- /SourceCode.md: -------------------------------------------------------------------------------- 1 | # SOURCE CODE 2 | 3 | 1. 4 | 5 | # https://github.com/irenemmassy/frontend-shoeshop 6 | 7 | 8 | 9 | # https://frontend-shoeshop.herokuapp.com/ 10 | 11 | 2. 12 | 13 | # https://github.com/irenemmassy/admin-dashboard-shoeshop 14 | 15 | 16 | 17 | # https://admin-dashboard-shoeshop.herokuapp.com/ 18 | 19 | 3. 20 | 21 | # https://github.com/irenemmassy/Full-SorceCode-ShoeShop-Ecommerce-Web 22 | 23 | 4. # SERVER DEPENDENCIES 24 | 25 | npm install bcryptjs dotenv express express-async-handler jsonwebtoken mongoose morgan 26 | npm install -D concurrently nodemon 27 | 28 | 5. # SERVER .ENV 29 | 30 | PORT = 31 | NODE_ENV 32 | JWT_SECRET 33 | PAYPAL_CLIENT_ID 34 | MONGO_URL 35 | -------------------------------------------------------------------------------- /client frontend/src/screens/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import Header from "./../components/Header"; 4 | 5 | const NotFound = () => { 6 | return ( 7 | <> 8 |
9 |
10 |
11 |

Page Not Found

12 | Not-found 17 | 22 |
23 |
24 | 25 | ); 26 | }; 27 | 28 | export default NotFound; 29 | -------------------------------------------------------------------------------- /dashboard/src/components/Home/SalesStatistics.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const SaleStatistics = () => { 4 | return ( 5 |
6 |
7 |
8 |
Sale statistics
9 | 20 |
21 |
22 |
23 | ); 24 | }; 25 | 26 | export default SaleStatistics; 27 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Constants/OrderConstants.js: -------------------------------------------------------------------------------- 1 | export const ORDER_CREATE_REQUEST = "ORDER_CREATE_REQUEST"; 2 | export const ORDER_CREATE_SUCCESS = "ORDER_CREATE_SUCCESS"; 3 | export const ORDER_CREATE_FAIL = "ORDER_CREATE_FAIL"; 4 | export const ORDER_CREATE_RESET = "ORDER_CREATE_RESET"; 5 | 6 | export const ORDER_DETAILS_REQUEST = "ORDER_DETAILS_REQUEST"; 7 | export const ORDER_DETAILS_SUCCESS = "ORDER_DETAILS_SUCCESS"; 8 | export const ORDER_DETAILS_FAIL = "ORDER_DETAILS_FAIL"; 9 | 10 | export const ORDER_PAY_REQUEST = "ORDER_PAY_REQUEST"; 11 | export const ORDER_PAY_SUCCESS = "ORDER_PAY_SUCCESS"; 12 | export const ORDER_PAY_FAIL = "ORDER_PAY_FAIL"; 13 | export const ORDER_PAY_RESET = "ORDER_PAY_RESET"; 14 | 15 | export const ORDER_LIST_MY_REQUEST = "ORDER_LIST_MY_REQUEST"; 16 | export const ORDER_LIST_MY_SUCCESS = "ORDER_LIST_MY_SUCCESS"; 17 | export const ORDER_LIST_MY_FAIL = "ORDER_LIST_MY_FAIL"; 18 | export const ORDER_LIST_MY_RESET = "ORDER_LIST_MY_RESET"; 19 | -------------------------------------------------------------------------------- /dashboard/src/components/Home/ProductsStatistics.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ProductsStatistics = () => { 4 | return ( 5 |
6 |
7 |
8 |
Products statistics
9 | 20 |
21 |
22 |
23 | ); 24 | }; 25 | 26 | export default ProductsStatistics; 27 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Constants/UserContants.js: -------------------------------------------------------------------------------- 1 | export const USER_LOGIN_REQUEST = "USER_LOGIN_REQUEST"; 2 | export const USER_LOGIN_SUCCESS = "USER_LOGIN_SUCCESS"; 3 | export const USER_LOGIN_FAIL = "USER_LOGIN_FAIL"; 4 | export const USER_LOGOUT = "USER_LOGOUT"; 5 | 6 | export const USER_REGISTER_REQUEST = "USER_REGISTER_REQUEST"; 7 | export const USER_REGISTER_SUCCESS = "USER_REGISTER_SUCCESS"; 8 | export const USER_REGISTER_FAIL = "USER_REGISTER_FAIL"; 9 | 10 | export const USER_DETAILS_REQUEST = "USER_DETAILS_REQUEST"; 11 | export const USER_DETAILS_SUCCESS = "USER_DETAILS_SUCCESS"; 12 | export const USER_DETAILS_FAIL = "USER_DETAILS_FAIL"; 13 | export const USER_DETAILS_RESET = "USER_DETAILS_RESET"; 14 | 15 | export const USER_UPDATE_PROFILE_REQUEST = "USER_UPDATE_PROFILE_REQUEST"; 16 | export const USER_UPDATE_PROFILE_SUCCESS = "USER_UPDATE_PROFILE_SUCCESS"; 17 | export const USER_UPDATE_PROFILE_FAIL = "USER_UPDATE_PROFILE_FAIL"; 18 | export const USER_UPDATE_PROFILE_RESET = "USER_UPDATE_PROFILE_RESET"; 19 | -------------------------------------------------------------------------------- /client frontend/src/components/homeComponents/pagination.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const Pagination = (props) => { 5 | const { page, pages, keyword = "" } = props; 6 | return ( 7 | pages > 1 && ( 8 | 29 | ) 30 | ); 31 | }; 32 | 33 | export default Pagination; 34 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import dotenv from "dotenv"; 3 | import connectDatabase from "./config/MongoDb.js"; 4 | import ImportData from "./DataImport.js"; 5 | import productRoute from "./Routes/ProductRoutes.js"; 6 | import { errorHandler, notFound } from "./Middleware/Errors.js"; 7 | import userRouter from "./Routes/UserRoutes.js"; 8 | import orderRouter from "./Routes/orderRoutes.js"; 9 | 10 | dotenv.config(); 11 | connectDatabase(); 12 | const app = express(); 13 | app.use(express.json()); 14 | 15 | // API 16 | app.use("/api/import", ImportData); 17 | app.use("/api/products", productRoute); 18 | app.use("/api/users", userRouter); 19 | app.use("/api/orders", orderRouter); 20 | app.get("/api/config/paypal", (req, res) => { 21 | res.send(process.env.PAYPAL_CLIENT_ID); 22 | }); 23 | 24 | // ERROR HANDLER 25 | app.use(notFound); 26 | app.use(errorHandler); 27 | 28 | const PORT = process.env.PORT || 1000; 29 | 30 | app.listen(PORT, console.log(`server run in port ${PORT}`)); 31 | -------------------------------------------------------------------------------- /server/Models/UserModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import bcrypt from "bcryptjs"; 3 | 4 | const userSchema = mongoose.Schema( 5 | { 6 | name: { 7 | type: String, 8 | required: true, 9 | }, 10 | email: { 11 | type: String, 12 | required: true, 13 | unique: true, 14 | }, 15 | password: { 16 | type: String, 17 | required: true, 18 | }, 19 | isAdmin: { 20 | type: Boolean, 21 | required: true, 22 | default: false, 23 | }, 24 | }, 25 | { 26 | timestamps: true, 27 | } 28 | ); 29 | 30 | // Login 31 | userSchema.methods.matchPassword = async function (enterPassword) { 32 | return await bcrypt.compare(enterPassword, this.password); 33 | }; 34 | 35 | // Register 36 | userSchema.pre("save", async function (next) { 37 | if (!this.isModified("password")) { 38 | next(); 39 | } 40 | const salt = await bcrypt.genSalt(10); 41 | this.password = await bcrypt.hash(this.password, salt); 42 | }); 43 | 44 | const User = mongoose.model("User", userSchema); 45 | 46 | export default User; 47 | -------------------------------------------------------------------------------- /dashboard/src/Redux/Constants/ProductConstants.js: -------------------------------------------------------------------------------- 1 | export const PRODUCT_LIST_REQUEST = "PRODUCT_LIST_REQUEST"; 2 | export const PRODUCT_LIST_SUCCESS = "PRODUCT_LIST_SUCCESS"; 3 | export const PRODUCT_LIST_FAIL = "PRODUCT_LIST_FAIL"; 4 | 5 | export const PRODUCT_EDIT_REQUEST = "PRODUCT_EDIT_REQUEST"; 6 | export const PRODUCT_EDIT_SUCCESS = "PRODUCT_EDIT_SUCCESS"; 7 | export const PRODUCT_EDIT_FAIL = "PRODUCT_EDIT_FAIL"; 8 | 9 | export const PRODUCT_DELETE_REQUEST = "PRODUCT_DELETE_REQUEST"; 10 | export const PRODUCT_DELETE_SUCCESS = "PRODUCT_DELETE_SUCCESS"; 11 | export const PRODUCT_DELETE_FAIL = "PRODUCT_DELETE_FAIL"; 12 | 13 | export const PRODUCT_CREATE_REQUEST = "PRODUCT_CREATE_REQUEST"; 14 | export const PRODUCT_CREATE_SUCCESS = "PRODUCT_CREATE_SUCCESS"; 15 | export const PRODUCT_CREATE_FAIL = "PRODUCT_CREATE_FAIL"; 16 | export const PRODUCT_CREATE_RESET = "PRODUCT_CREATE_RESET"; 17 | 18 | export const PRODUCT_UPDATE_REQUEST = "PRODUCT_UPDATE_REQUEST"; 19 | export const PRODUCT_UPDATE_SUCCESS = "PRODUCT_UPDATE_SUCCESS"; 20 | export const PRODUCT_UPDATE_FAIL = "PRODUCT_UPDATE_FAIL"; 21 | export const PRODUCT_UPDATE_RESET = "PRODUCT_UPDATE_RESET"; 22 | -------------------------------------------------------------------------------- /server/Middleware/AuthMiddleware.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import asyncHandler from "express-async-handler"; 3 | import User from "../Models/UserModel.js"; 4 | 5 | const protect = asyncHandler(async (req, res, next) => { 6 | let token; 7 | 8 | if ( 9 | req.headers.authorization && 10 | req.headers.authorization.startsWith("Bearer") 11 | ) { 12 | try { 13 | token = req.headers.authorization.split(" ")[1]; 14 | 15 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 16 | 17 | req.user = await User.findById(decoded.id).select("-password"); 18 | next(); 19 | } catch (error) { 20 | console.error(error); 21 | res.status(401); 22 | throw new Error("Not authorized, token failed"); 23 | } 24 | } 25 | if (!token) { 26 | res.status(401); 27 | throw new Error("Not authorized, no token"); 28 | } 29 | }); 30 | 31 | const admin = (req, res, next) => { 32 | if (req.user && req.user.isAdmin) { 33 | next(); 34 | } else { 35 | res.status(401); 36 | throw new Error("Not authorized as an Admin"); 37 | } 38 | }; 39 | export { protect, admin }; 40 | -------------------------------------------------------------------------------- /dashboard/src/components/Categories/CreateCategory.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const CreateCategory = () => { 4 | return ( 5 |
6 |
7 |
8 | 11 | 17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 29 |
30 | 31 |
32 | 33 |
34 |
35 |
36 | ); 37 | }; 38 | 39 | export default CreateCategory; 40 | -------------------------------------------------------------------------------- /dashboard/src/Redux/Reducers/userReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | USER_LIST_FAIL, 3 | USER_LIST_REQUEST, 4 | USER_LIST_RESET, 5 | USER_LIST_SUCCESS, 6 | USER_LOGIN_FAIL, 7 | USER_LOGIN_REQUEST, 8 | USER_LOGIN_SUCCESS, 9 | USER_LOGOUT, 10 | } from "../Constants/UserContants"; 11 | 12 | // LOGIN 13 | export const userLoginReducer = (state = {}, action) => { 14 | switch (action.type) { 15 | case USER_LOGIN_REQUEST: 16 | return { loading: true }; 17 | case USER_LOGIN_SUCCESS: 18 | return { loading: false, userInfo: action.payload }; 19 | case USER_LOGIN_FAIL: 20 | return { loading: false, error: action.payload }; 21 | case USER_LOGOUT: 22 | return {}; 23 | default: 24 | return state; 25 | } 26 | }; 27 | 28 | // ALL USER 29 | export const userListReducer = (state = { users: [] }, action) => { 30 | switch (action.type) { 31 | case USER_LIST_REQUEST: 32 | return { loading: true }; 33 | case USER_LIST_SUCCESS: 34 | return { loading: false, users: action.payload }; 35 | case USER_LIST_FAIL: 36 | return { loading: false, error: action.payload }; 37 | case USER_LIST_RESET: 38 | return { users: [] }; 39 | default: 40 | return state; 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /dashboard/src/components/Home/Main.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TopTotal from "./TopTotal"; 3 | import LatestOrder from "./LatestOrder"; 4 | import SaleStatistics from "./SalesStatistics"; 5 | import ProductsStatistics from "./ProductsStatistics"; 6 | import { useSelector } from "react-redux"; 7 | 8 | const Main = () => { 9 | const orderList = useSelector((state) => state.orderList); 10 | const { loading, error, orders } = orderList; 11 | const productList = useSelector((state) => state.productList); 12 | const { products } = productList; 13 | return ( 14 | <> 15 |
16 |
17 |

Dashboard

18 |
19 | {/* Top Total */} 20 | 21 | 22 |
23 | {/* STATICS */} 24 | 25 | 26 |
27 | 28 | {/* LATEST ORDER */} 29 |
30 | 31 |
32 |
33 | 34 | ); 35 | }; 36 | 37 | export default Main; 38 | -------------------------------------------------------------------------------- /client frontend/src/components/homeComponents/ContactInfo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ContactInfo = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 |
Call Us 24x7
13 |

0736 230 063

14 |
15 |
16 |
17 |
18 |
19 | 20 |
21 |
Headquarter
22 |

Arusha Njiro Pepsi

23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 |
Fax
31 |

0736 230 063

32 |
33 |
34 |
35 |
36 | ); 37 | }; 38 | 39 | export default ContactInfo; 40 | -------------------------------------------------------------------------------- /dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin-dashboard", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy":"http://localhost:5000", 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^5.15.0", 8 | "@testing-library/react": "^11.2.7", 9 | "@testing-library/user-event": "^12.8.3", 10 | "axios": "^0.24.0", 11 | "jquery": "^3.6.0", 12 | "moment": "^2.29.1", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-redux": "^7.2.6", 16 | "react-router-dom": "^5.3.0", 17 | "react-scripts": "4.0.3", 18 | "react-toastify": "^8.1.0", 19 | "redux": "^4.1.2", 20 | "redux-devtools-extension": "^2.13.9", 21 | "redux-thunk": "^2.4.1", 22 | "web-vitals": "^1.1.2" 23 | }, 24 | "scripts": { 25 | "start": "set PORT=4000 && react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shoeshop", 3 | "version": "0.1.0", 4 | "proxy":"http://localhost:5000", 5 | "private": true, 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^5.15.0", 8 | "@testing-library/react": "^11.2.7", 9 | "@testing-library/user-event": "^12.8.3", 10 | "axios": "^0.24.0", 11 | "moment": "^2.29.1", 12 | "react": "^17.0.2", 13 | "react-dom": "^17.0.2", 14 | "react-paypal-button-v2": "^2.6.3", 15 | "react-redux": "^7.2.6", 16 | "react-router": "^6.0.2", 17 | "react-router-dom": "^5.3.0", 18 | "react-scripts": "4.0.3", 19 | "react-toastify": "^8.1.0", 20 | "redux": "^4.1.2", 21 | "redux-devtools-extension": "^2.13.9", 22 | "redux-thunk": "^2.4.0", 23 | "web-vitals": "^1.1.2" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /server/Models/ProductModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const reviewSchema = mongoose.Schema( 4 | { 5 | name: { type: String, required: true }, 6 | rating: { type: Number, required: true }, 7 | comment: { type: String, required: true }, 8 | user: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | required: true, 11 | ref: "User", 12 | }, 13 | }, 14 | { 15 | timestamps: true, 16 | } 17 | ); 18 | 19 | const productSchema = mongoose.Schema( 20 | { 21 | name: { 22 | type: String, 23 | required: true, 24 | }, 25 | image: { 26 | type: String, 27 | }, 28 | description: { 29 | type: String, 30 | required: true, 31 | }, 32 | reviews: [reviewSchema], 33 | rating: { 34 | type: Number, 35 | required: true, 36 | default: 0, 37 | }, 38 | numReviews: { 39 | type: Number, 40 | required: true, 41 | default: 0, 42 | }, 43 | price: { 44 | type: Number, 45 | required: true, 46 | default: 0, 47 | }, 48 | countInStock: { 49 | type: Number, 50 | required: true, 51 | default: 0, 52 | }, 53 | }, 54 | { 55 | timestamps: true, 56 | } 57 | ); 58 | 59 | const Product = mongoose.model("Product", productSchema); 60 | 61 | export default Product; 62 | -------------------------------------------------------------------------------- /client frontend/src/components/homeComponents/Rating.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Rating = ({ value, text }) => { 4 | return ( 5 |
6 | = 1 9 | ? "fas fa-star" 10 | : value >= 0.5 11 | ? "fas fa-star-half-alt" 12 | : "far fa-star" 13 | } 14 | > 15 | = 2 18 | ? "fas fa-star" 19 | : value >= 1.5 20 | ? "fas fa-star-half-alt" 21 | : "far fa-star" 22 | } 23 | > 24 | = 3 27 | ? "fas fa-star" 28 | : value >= 2.5 29 | ? "fas fa-star-half-alt" 30 | : "far fa-star" 31 | } 32 | > 33 | = 4 36 | ? "fas fa-star" 37 | : value >= 3.5 38 | ? "fas fa-star-half-alt" 39 | : "far fa-star" 40 | } 41 | > 42 | = 5 45 | ? "fas fa-star" 46 | : value >= 4.5 47 | ? "fas fa-star-half-alt" 48 | : "far fa-star" 49 | } 50 | > 51 | 52 | {text && text} 53 |
54 | ); 55 | }; 56 | 57 | export default Rating; 58 | -------------------------------------------------------------------------------- /client frontend/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Footer = () => { 4 | return ( 5 |
6 |
7 |
8 | mastercard 12 |
13 |
14 | visa 18 |
19 |
20 | paypal 24 |
25 |
26 | express 30 |
31 |
32 | discover 36 |
37 |
38 |
39 | ); 40 | }; 41 | 42 | export default Footer; 43 | -------------------------------------------------------------------------------- /dashboard/src/Redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, applyMiddleware } from "redux"; 2 | import thunk from "redux-thunk"; 3 | import { composeWithDevTools } from "redux-devtools-extension"; 4 | import { userListReducer, userLoginReducer } from "./Reducers/userReducers"; 5 | import { 6 | productCreateReducer, 7 | productDeleteReducer, 8 | productEditReducer, 9 | productListReducer, 10 | productUpdateReducer, 11 | } from "./Reducers/ProductReducers"; 12 | import { 13 | orderDeliveredReducer, 14 | orderDetailsReducer, 15 | orderListReducer, 16 | } from "./Reducers/OrderReducres"; 17 | 18 | const reducer = combineReducers({ 19 | userLogin: userLoginReducer, 20 | userList: userListReducer, 21 | productList: productListReducer, 22 | productDelete: productDeleteReducer, 23 | productCreate: productCreateReducer, 24 | productEdit: productEditReducer, 25 | productUpdate: productUpdateReducer, 26 | orderList: orderListReducer, 27 | orderDetails: orderDetailsReducer, 28 | orderDeliver: orderDeliveredReducer, 29 | }); 30 | 31 | // login 32 | const userInfoFromLocalStorage = localStorage.getItem("userInfo") 33 | ? JSON.parse(localStorage.getItem("userInfo")) 34 | : null; 35 | 36 | const initialState = { 37 | userLogin: { userInfo: userInfoFromLocalStorage }, 38 | }; 39 | 40 | const middleware = [thunk]; 41 | 42 | const store = createStore( 43 | reducer, 44 | initialState, 45 | composeWithDevTools(applyMiddleware(...middleware)) 46 | ); 47 | 48 | export default store; 49 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Reducers/CartReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | CART_ADD_ITEM, 3 | CART_CLEAR_ITEMS, 4 | CART_REMOVE_ITEM, 5 | CART_SAVE_PAYMENT_METHOD, 6 | CART_SAVE_SHIPPING_ADDRESS, 7 | } from "../Constants/CartConstants"; 8 | 9 | export const cartReducer = ( 10 | state = { cartItems: [], shippingAddress: {} }, 11 | action 12 | ) => { 13 | switch (action.type) { 14 | case CART_ADD_ITEM: 15 | const item = action.payload; 16 | const existItem = state.cartItems.find((x) => x.product === item.product); 17 | 18 | if (existItem) { 19 | return { 20 | ...state, 21 | cartItems: state.cartItems.map((x) => 22 | x.product === existItem.product ? item : x 23 | ), 24 | }; 25 | } else { 26 | return { 27 | ...state, 28 | cartItems: [...state.cartItems, item], 29 | }; 30 | } 31 | case CART_REMOVE_ITEM: 32 | return { 33 | ...state, 34 | cartItems: state.cartItems.filter((x) => x.product !== action.payload), 35 | }; 36 | case CART_SAVE_SHIPPING_ADDRESS: 37 | return { 38 | ...state, 39 | shippingAddress: action.payload, 40 | }; 41 | case CART_SAVE_PAYMENT_METHOD: 42 | return { 43 | ...state, 44 | paymentMethod: action.payload, 45 | }; 46 | case CART_CLEAR_ITEMS: 47 | return { 48 | ...state, 49 | cartItems: [], 50 | }; 51 | default: 52 | return state; 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Actions/cartActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { 3 | CART_ADD_ITEM, 4 | CART_REMOVE_ITEM, 5 | CART_SAVE_PAYMENT_METHOD, 6 | CART_SAVE_SHIPPING_ADDRESS, 7 | } from "../Constants/CartConstants"; 8 | 9 | // ADD TO CART 10 | export const addToCart = (id, qty) => async (dispatch, getState) => { 11 | const { data } = await axios.get(`/api/products/${id}`); 12 | 13 | dispatch({ 14 | type: CART_ADD_ITEM, 15 | payload: { 16 | product: data._id, 17 | name: data.name, 18 | image: data.image, 19 | price: data.price, 20 | countInStock: data.countInStock, 21 | qty, 22 | }, 23 | }); 24 | 25 | localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems)); 26 | }; 27 | 28 | // REMOVE PRODUCT FROM CART 29 | export const removefromcart = (id) => (dispatch, getState) => { 30 | dispatch({ 31 | type: CART_REMOVE_ITEM, 32 | payload: id, 33 | }); 34 | 35 | localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems)); 36 | }; 37 | 38 | // SAVE SHIPPING ADDRESS 39 | export const saveShippingAddress = (data) => (dispatch) => { 40 | dispatch({ 41 | type: CART_SAVE_SHIPPING_ADDRESS, 42 | payload: data, 43 | }); 44 | 45 | localStorage.setItem("shippingAddress", JSON.stringify(data)); 46 | }; 47 | 48 | // SAVE PAYMENT METHOD 49 | export const savePaymentMethod = (data) => (dispatch) => { 50 | dispatch({ 51 | type: CART_SAVE_PAYMENT_METHOD, 52 | payload: data, 53 | }); 54 | 55 | localStorage.setItem("paymentMethod", JSON.stringify(data)); 56 | }; 57 | -------------------------------------------------------------------------------- /dashboard/src/components/products/Product.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useDispatch } from "react-redux"; 4 | import { deleteProduct } from "../../Redux/Actions/ProductActions"; 5 | 6 | const Product = (props) => { 7 | const { product } = props; 8 | const dispatch = useDispatch(); 9 | 10 | const deletehandler = (id) => { 11 | if (window.confirm("Are you sure??")) { 12 | dispatch(deleteProduct(id)); 13 | } 14 | }; 15 | 16 | return ( 17 | <> 18 |
19 |
20 | 21 | Product 22 | 23 |
24 | 25 | {product.name} 26 | 27 |
${product.price}
28 |
29 | 33 | 34 | 35 | deletehandler(product._id)} 38 | className="btn btn-sm btn-outline-danger p-2 pb-3 col-md-6" 39 | > 40 | 41 | 42 |
43 |
44 |
45 |
46 | 47 | ); 48 | }; 49 | 50 | export default Product; 51 | -------------------------------------------------------------------------------- /client frontend/src/screens/PaymentScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { savePaymentMethod } from "../Redux/Actions/cartActions"; 4 | import Header from "./../components/Header"; 5 | 6 | const PaymentScreen = ({ history }) => { 7 | window.scrollTo(0, 0); 8 | 9 | const cart = useSelector((state) => state.cart); 10 | const { shippingAddress } = cart; 11 | 12 | if (!shippingAddress) { 13 | history.push("/shipping"); 14 | } 15 | 16 | const [paymentMethod, setPaymentMethod] = useState("PayPal"); 17 | 18 | const dispatch = useDispatch(); 19 | 20 | const submitHandler = (e) => { 21 | e.preventDefault(); 22 | dispatch(savePaymentMethod(paymentMethod)); 23 | history.push("/placeorder"); 24 | }; 25 | return ( 26 | <> 27 |
28 |
29 |
33 |
SELECT PAYMENT METHOD
34 |
35 |
36 | setPaymentMethod(e.target.value)} 41 | /> 42 | 43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 | ); 51 | }; 52 | 53 | export default PaymentScreen; 54 | -------------------------------------------------------------------------------- /dashboard/src/Redux/Reducers/OrderReducres.js: -------------------------------------------------------------------------------- 1 | import { 2 | ORDER_DELIVERED_FAIL, 3 | ORDER_DELIVERED_REQUEST, 4 | ORDER_DELIVERED_RESET, 5 | ORDER_DELIVERED_SUCCESS, 6 | ORDER_DETAILS_FAIL, 7 | ORDER_DETAILS_REQUEST, 8 | ORDER_DETAILS_SUCCESS, 9 | ORDER_LIST_FAIL, 10 | ORDER_LIST_REQUEST, 11 | ORDER_LIST_SUCCESS, 12 | } from "../Constants/OrderConstants"; 13 | 14 | export const orderListReducer = (state = { orders: [] }, action) => { 15 | switch (action.type) { 16 | case ORDER_LIST_REQUEST: 17 | return { loading: true }; 18 | case ORDER_LIST_SUCCESS: 19 | return { loading: false, orders: action.payload }; 20 | case ORDER_LIST_FAIL: 21 | return { loading: false, error: action.payload }; 22 | default: 23 | return state; 24 | } 25 | }; 26 | 27 | // ORDER DETAILS 28 | export const orderDetailsReducer = ( 29 | state = { loading: true, orderItems: [], shippingAddress: {} }, 30 | action 31 | ) => { 32 | switch (action.type) { 33 | case ORDER_DETAILS_REQUEST: 34 | return { ...state, loading: true }; 35 | case ORDER_DETAILS_SUCCESS: 36 | return { loading: false, order: action.payload }; 37 | case ORDER_DETAILS_FAIL: 38 | return { loading: false, error: action.payload }; 39 | default: 40 | return state; 41 | } 42 | }; 43 | 44 | // ORDER DELIVERED 45 | export const orderDeliveredReducer = (state = {}, action) => { 46 | switch (action.type) { 47 | case ORDER_DELIVERED_REQUEST: 48 | return { loading: true }; 49 | case ORDER_DELIVERED_SUCCESS: 50 | return { loading: false, success: true }; 51 | case ORDER_DELIVERED_FAIL: 52 | return { loading: false, error: action.payload }; 53 | case ORDER_DELIVERED_RESET: 54 | return {}; 55 | default: 56 | return state; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /client frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./App.css"; 3 | import "./responsive.css"; 4 | import "react-toastify/dist/ReactToastify.css"; 5 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; 6 | import HomeScreen from "./screens/HomeScreen"; 7 | import SingleProduct from "./screens/SingleProduct"; 8 | import Login from "./screens/Login"; 9 | import Register from "./screens/Register"; 10 | import CartScreen from "./screens/CartScreen"; 11 | import ShippingScreen from "./screens/ShippingScreen"; 12 | import ProfileScreen from "./screens/ProfileScreen"; 13 | import PaymentScreen from "./screens/PaymentScreen"; 14 | import PlaceOrderScreen from "./screens/PlaceOrderScreen"; 15 | import OrderScreen from "./screens/OrderScreen"; 16 | import NotFound from "./screens/NotFound"; 17 | import PrivateRouter from "./PrivateRouter"; 18 | 19 | const App = () => { 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /client frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 25 | 26 | 30 | 31 | 32 | ShoeShop E-commerce 33 | 34 | 35 | 36 |
37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /dashboard/src/components/Home/LatestOrder.js: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | import React from "react"; 3 | import { Link } from "react-router-dom"; 4 | import Message from "../LoadingError/Error"; 5 | import Loading from "../LoadingError/Loading"; 6 | 7 | const LatestOrder = (props) => { 8 | const { loading, error, orders } = props; 9 | return ( 10 |
11 |

New orders

12 | {loading ? ( 13 | 14 | ) : error ? ( 15 | {error} 16 | ) : ( 17 |
18 | 19 | 20 | {orders.slice(0, 5).map((order) => ( 21 | 22 | 25 | 26 | 27 | 38 | 39 | 44 | 45 | ))} 46 | 47 |
23 | {order.user.name} 24 | {order.user.email}${order.totalPrice} 28 | {order.isPaid ? ( 29 | 30 | Paid At {moment(order.paidAt).format("MMM Do YY")} 31 | 32 | ) : ( 33 | 34 | Not Paid 35 | 36 | )} 37 | {moment(order.createdAt).calendar()} 40 | 41 | 42 | 43 |
48 |
49 | )} 50 |
51 | ); 52 | }; 53 | 54 | export default LatestOrder; 55 | -------------------------------------------------------------------------------- /server/Models/OrderModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const orderSchema = mongoose.Schema( 4 | { 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | ref: "User", 9 | }, 10 | orderItems: [ 11 | { 12 | name: { type: String, required: true }, 13 | qty: { type: Number, required: true }, 14 | image: { type: String, required: true }, 15 | price: { type: Number, required: true }, 16 | product: { 17 | type: mongoose.Schema.Types.ObjectId, 18 | required: true, 19 | ref: "Product", 20 | }, 21 | }, 22 | ], 23 | shippingAddress: { 24 | address: { type: String, required: true }, 25 | city: { type: String, required: true }, 26 | postalCode: { type: String, required: true }, 27 | country: { type: String, required: true }, 28 | }, 29 | paymentMethod: { 30 | type: String, 31 | required: true, 32 | default: "Paypal", 33 | }, 34 | paymentResult: { 35 | id: { type: String }, 36 | status: { type: String }, 37 | update_time: { type: String }, 38 | email_address: { type: String }, 39 | }, 40 | taxPrice: { 41 | type: Number, 42 | required: true, 43 | default: 0.0, 44 | }, 45 | shippingPrice: { 46 | type: Number, 47 | required: true, 48 | default: 0.0, 49 | }, 50 | totalPrice: { 51 | type: Number, 52 | required: true, 53 | default: 0.0, 54 | }, 55 | isPaid: { 56 | type: Boolean, 57 | required: true, 58 | default: false, 59 | }, 60 | paidAt: { 61 | type: Date, 62 | }, 63 | isDelivered: { 64 | type: Boolean, 65 | required: true, 66 | default: false, 67 | }, 68 | deliveredAt: { 69 | type: Date, 70 | }, 71 | }, 72 | { 73 | timestamps: true, 74 | } 75 | ); 76 | 77 | const Order = mongoose.model("Order", orderSchema); 78 | 79 | export default Order; 80 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Reducers/ProductReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | PRODUCT_CREATE_REVIEW_FAIL, 3 | PRODUCT_CREATE_REVIEW_REQUEST, 4 | PRODUCT_CREATE_REVIEW_RESET, 5 | PRODUCT_CREATE_REVIEW_SUCCESS, 6 | PRODUCT_DETAILS_FAIL, 7 | PRODUCT_DETAILS_REQUEST, 8 | PRODUCT_DETAILS_SUCCESS, 9 | PRODUCT_LIST_FAIL, 10 | PRODUCT_LIST_REQUEST, 11 | PRODUCT_LIST_SUCCESS, 12 | } from "../Constants/ProductConstants"; 13 | 14 | // PRODUCT LIST 15 | export const productListReducer = (state = { products: [] }, action) => { 16 | switch (action.type) { 17 | case PRODUCT_LIST_REQUEST: 18 | return { loading: true, products: [] }; 19 | case PRODUCT_LIST_SUCCESS: 20 | return { 21 | loading: false, 22 | pages: action.payload.pages, 23 | page: action.payload.page, 24 | products: action.payload.products, 25 | }; 26 | case PRODUCT_LIST_FAIL: 27 | return { loading: false, error: action.payload }; 28 | default: 29 | return state; 30 | } 31 | }; 32 | 33 | // SINGLE PRODUCT 34 | export const productDetailsReducer = ( 35 | state = { product: { reviews: [] } }, 36 | action 37 | ) => { 38 | switch (action.type) { 39 | case PRODUCT_DETAILS_REQUEST: 40 | return { ...state, loading: true }; 41 | case PRODUCT_DETAILS_SUCCESS: 42 | return { loading: false, product: action.payload }; 43 | case PRODUCT_DETAILS_FAIL: 44 | return { loading: false, error: action.payload }; 45 | default: 46 | return state; 47 | } 48 | }; 49 | 50 | // PRODUCT REVIEW CREATE 51 | export const productCreateReviewReducer = (state = {}, action) => { 52 | switch (action.type) { 53 | case PRODUCT_CREATE_REVIEW_REQUEST: 54 | return { loading: true }; 55 | case PRODUCT_CREATE_REVIEW_SUCCESS: 56 | return { loading: false, success: true }; 57 | case PRODUCT_CREATE_REVIEW_FAIL: 58 | return { loading: false, error: action.payload }; 59 | case PRODUCT_CREATE_REVIEW_RESET: 60 | return {}; 61 | default: 62 | return state; 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /dashboard/src/components/Home/TopTotal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const TopTotal = (props) => { 4 | const { orders, products } = props; 5 | let totalSale = 0; 6 | if (orders) { 7 | orders.map((order) => 8 | order.isPaid === true ? (totalSale = totalSale + order.totalPrice) : null 9 | ); 10 | } 11 | return ( 12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 |
20 |
Total Sales
{" "} 21 | ${totalSale.toFixed(0)} 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 |
33 |
Total Orders
34 | {orders ? {orders.length} : 0} 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 |
46 |
Total Products
47 | {products ? {products.length} : 0} 48 |
49 |
50 |
51 |
52 |
53 | ); 54 | }; 55 | 56 | export default TopTotal; 57 | -------------------------------------------------------------------------------- /dashboard/src/components/orders/OrderMain.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Message from "../LoadingError/Error"; 3 | import Loading from "../LoadingError/Loading"; 4 | import Orders from "./Orders"; 5 | import { useSelector } from "react-redux"; 6 | 7 | const OrderMain = () => { 8 | const orderList = useSelector((state) => state.orderList); 9 | const { loading, error, orders } = orderList; 10 | 11 | return ( 12 |
13 |
14 |

Orders

15 |
16 | 17 |
18 |
19 |
20 |
21 | 26 |
27 |
28 | 34 |
35 |
36 | 41 |
42 |
43 |
44 |
45 |
46 | {loading ? ( 47 | 48 | ) : error ? ( 49 | {error} 50 | ) : ( 51 | 52 | )} 53 |
54 |
55 |
56 |
57 | ); 58 | }; 59 | 60 | export default OrderMain; 61 | -------------------------------------------------------------------------------- /dashboard/src/components/orders/OrderDetailInfo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const OrderDetailInfo = (props) => { 4 | const { order } = props; 5 | return ( 6 |
7 |
8 |
9 | 10 | 11 | 12 |
13 |
Customer
14 |

15 | {order.user.name}
16 | {order.user.email} 17 |

18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 |
27 |
Order info
28 |

29 | Shipping: {order.shippingAddress.country}
Pay method:{" "} 30 | {order.paymentMethod} 31 |

32 |
33 |
34 |
35 |
36 |
37 | 38 | 39 | 40 |
41 |
Deliver to
42 |

43 | Address: {order.shippingAddress.city} 44 |
45 | {order.shippingAddress.address} 46 |
{order.shippingAddress.postalCode} 47 |

48 |
49 |
50 |
51 |
52 | ); 53 | }; 54 | 55 | export default OrderDetailInfo; 56 | -------------------------------------------------------------------------------- /client frontend/src/Redux/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 | productCreateReviewReducer, 6 | productDetailsReducer, 7 | productListReducer, 8 | } from "./Reducers/ProductReducers"; 9 | import { cartReducer } from "./Reducers/CartReducers"; 10 | import { 11 | userDetailsReducer, 12 | userLoginReducer, 13 | userRegisterReducer, 14 | userUpdateProfileReducer, 15 | } from "./Reducers/userReducers"; 16 | import { 17 | orderCreateReducer, 18 | orderDetailsReducer, 19 | orderListMyReducer, 20 | orderPayReducer, 21 | } from "./Reducers/OrderReducres"; 22 | 23 | const reducer = combineReducers({ 24 | productList: productListReducer, 25 | productDetails: productDetailsReducer, 26 | productReviewCreate: productCreateReviewReducer, 27 | cart: cartReducer, 28 | userLogin: userLoginReducer, 29 | userRegister: userRegisterReducer, 30 | userDetails: userDetailsReducer, 31 | userUpdateProfile: userUpdateProfileReducer, 32 | orderCreate: orderCreateReducer, 33 | orderDetails: orderDetailsReducer, 34 | orderPay: orderPayReducer, 35 | orderListMy: orderListMyReducer, 36 | }); 37 | 38 | const cartItemsFromLocalStorage = localStorage.getItem("cartItems") 39 | ? JSON.parse(localStorage.getItem("cartItems")) 40 | : []; 41 | 42 | // login 43 | const userInfoFromLocalStorage = localStorage.getItem("userInfo") 44 | ? JSON.parse(localStorage.getItem("userInfo")) 45 | : null; 46 | 47 | // shippingAddress 48 | const shippingAddressFromLocalStorage = localStorage.getItem("shippingAddress") 49 | ? JSON.parse(localStorage.getItem("shippingAddress")) 50 | : {}; 51 | 52 | const initialState = { 53 | cart: { 54 | cartItems: cartItemsFromLocalStorage, 55 | shippingAddress: shippingAddressFromLocalStorage, 56 | }, 57 | userLogin: { userInfo: userInfoFromLocalStorage }, 58 | }; 59 | 60 | const middleware = [thunk]; 61 | 62 | const store = createStore( 63 | reducer, 64 | initialState, 65 | composeWithDevTools(applyMiddleware(...middleware)) 66 | ); 67 | 68 | export default store; 69 | -------------------------------------------------------------------------------- /dashboard/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import "./App.css"; 3 | import "./responsive.css"; 4 | import "react-toastify/dist/ReactToastify.css"; 5 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; 6 | import HomeScreen from "./screens/HomeScreen"; 7 | import ProductScreen from "./screens/productScreen"; 8 | import CategoriesScreen from "./screens/CategoriesScreen"; 9 | import OrderScreen from "./screens/OrderScreen"; 10 | import OrderDetailScreen from "./screens/OrderDetailScreen"; 11 | import AddProduct from "./screens/AddProduct"; 12 | import Login from "./screens/LoginScreen"; 13 | import UsersScreen from "./screens/UsersScreen"; 14 | import ProductEditScreen from "./screens/ProductEditScreen"; 15 | import NotFound from "./screens/NotFound"; 16 | import PrivateRouter from "./PrivateRouter"; 17 | import { useDispatch, useSelector } from "react-redux"; 18 | import { listProducts } from "./Redux/Actions/ProductActions"; 19 | import { listOrders } from "./Redux/Actions/OrderActions"; 20 | 21 | function App() { 22 | const dispatch = useDispatch(); 23 | 24 | const userLogin = useSelector((state) => state.userLogin); 25 | const { userInfo } = userLogin; 26 | 27 | useEffect(() => { 28 | if (userInfo && userInfo.isAdmin) { 29 | dispatch(listProducts()); 30 | dispatch(listOrders()); 31 | } 32 | }, [dispatch, userInfo]); 33 | 34 | return ( 35 | <> 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 | ); 55 | } 56 | 57 | export default App; 58 | -------------------------------------------------------------------------------- /client frontend/src/screens/Login.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { Link } from "react-router-dom"; 4 | import Message from "../components/LoadingError/Error"; 5 | import Loading from "../components/LoadingError/Loading"; 6 | import Header from "./../components/Header"; 7 | import { login } from "./../Redux/Actions/userActions"; 8 | 9 | const Login = ({ location, history }) => { 10 | window.scrollTo(0, 0); 11 | const [email, setEmail] = useState(""); 12 | const [password, setPassword] = useState(""); 13 | 14 | const dispatch = useDispatch(); 15 | const redirect = location.search ? location.search.split("=")[1] : "/"; 16 | 17 | const userLogin = useSelector((state) => state.userLogin); 18 | const { error, loading, userInfo } = userLogin; 19 | 20 | useEffect(() => { 21 | if (userInfo) { 22 | history.push(redirect); 23 | } 24 | }, [userInfo, history, redirect]); 25 | 26 | const submitHandler = (e) => { 27 | e.preventDefault(); 28 | dispatch(login(email, password)); 29 | }; 30 | 31 | return ( 32 | <> 33 |
34 |
35 | {error && {error}} 36 | {loading && } 37 |
41 | setEmail(e.target.value)} 46 | /> 47 | setPassword(e.target.value)} 52 | /> 53 | 54 |

55 | 58 | Create Account 59 | 60 |

61 |
62 |
63 | 64 | ); 65 | }; 66 | 67 | export default Login; 68 | -------------------------------------------------------------------------------- /client frontend/src/screens/ShippingScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import Header from "../components/Header"; 4 | import { saveShippingAddress } from "../Redux/Actions/cartActions"; 5 | 6 | const ShippingScreen = ({ history }) => { 7 | window.scrollTo(0, 0); 8 | 9 | const cart = useSelector((state) => state.cart); 10 | const { shippingAddress } = cart; 11 | 12 | const [address, setAddress] = useState(shippingAddress.address); 13 | const [city, setCity] = useState(shippingAddress.city); 14 | const [postalCode, setPostalCode] = useState(shippingAddress.postalCode); 15 | const [country, setCountry] = useState(shippingAddress.country); 16 | 17 | const dispatch = useDispatch(); 18 | 19 | const submitHandler = (e) => { 20 | e.preventDefault(); 21 | dispatch(saveShippingAddress({ address, city, postalCode, country })); 22 | history.push("/payment"); 23 | }; 24 | return ( 25 | <> 26 |
27 |
28 |
32 |
DELIVERY ADDRESS
33 | setAddress(e.target.value)} 39 | /> 40 | setCity(e.target.value)} 46 | /> 47 | setPostalCode(e.target.value)} 53 | /> 54 | setCountry(e.target.value)} 60 | /> 61 | 62 |
63 |
64 | 65 | ); 66 | }; 67 | 68 | export default ShippingScreen; 69 | -------------------------------------------------------------------------------- /dashboard/src/screens/LoginScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import Loading from "../components/LoadingError/Loading"; 4 | import Toast from "../components/LoadingError/Toast"; 5 | import { login } from "../Redux/Actions/userActions"; 6 | import Message from "./../components/LoadingError/Error"; 7 | 8 | const Login = ({ history }) => { 9 | window.scrollTo(0, 0); 10 | const [email, setEmail] = useState(""); 11 | const [password, setPassword] = useState(""); 12 | 13 | const dispatch = useDispatch(); 14 | 15 | const userLogin = useSelector((state) => state.userLogin); 16 | const { error, loading, userInfo } = userLogin; 17 | 18 | useEffect(() => { 19 | if (userInfo) { 20 | history.push("/"); 21 | } 22 | }, [userInfo, history]); 23 | 24 | const submitHandler = (e) => { 25 | e.preventDefault(); 26 | dispatch(login(email, password)); 27 | }; 28 | return ( 29 | <> 30 | 31 |
35 |
36 | {error && {error}} 37 | {loading && } 38 |

Sign in

39 |
40 |
41 | setEmail(e.target.value)} 47 | /> 48 |
49 |
50 | setPassword(e.target.value)} 56 | /> 57 |
58 | 59 |
60 | 63 |
64 |
65 |
66 |
67 | 68 | ); 69 | }; 70 | 71 | export default Login; 72 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Reducers/userReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | USER_DETAILS_FAIL, 3 | USER_DETAILS_REQUEST, 4 | USER_DETAILS_RESET, 5 | USER_DETAILS_SUCCESS, 6 | USER_LOGIN_FAIL, 7 | USER_LOGIN_REQUEST, 8 | USER_LOGIN_SUCCESS, 9 | USER_LOGOUT, 10 | USER_REGISTER_FAIL, 11 | USER_REGISTER_REQUEST, 12 | USER_REGISTER_SUCCESS, 13 | USER_UPDATE_PROFILE_FAIL, 14 | USER_UPDATE_PROFILE_REQUEST, 15 | USER_UPDATE_PROFILE_SUCCESS, 16 | } from "../Constants/UserContants"; 17 | 18 | // LOGIN 19 | export const userLoginReducer = (state = {}, action) => { 20 | switch (action.type) { 21 | case USER_LOGIN_REQUEST: 22 | return { loading: true }; 23 | case USER_LOGIN_SUCCESS: 24 | return { loading: false, userInfo: action.payload }; 25 | case USER_LOGIN_FAIL: 26 | return { loading: false, error: action.payload }; 27 | case USER_LOGOUT: 28 | return {}; 29 | default: 30 | return state; 31 | } 32 | }; 33 | 34 | // REGISTER 35 | export const userRegisterReducer = (state = {}, action) => { 36 | switch (action.type) { 37 | case USER_REGISTER_REQUEST: 38 | return { loading: true }; 39 | case USER_REGISTER_SUCCESS: 40 | return { loading: false, userInfo: action.payload }; 41 | case USER_REGISTER_FAIL: 42 | return { loading: false, error: action.payload }; 43 | default: 44 | return state; 45 | } 46 | }; 47 | 48 | // USER DETAILS 49 | export const userDetailsReducer = (state = { user: {} }, action) => { 50 | switch (action.type) { 51 | case USER_DETAILS_REQUEST: 52 | return { ...state, loading: true }; 53 | case USER_DETAILS_SUCCESS: 54 | return { loading: false, user: action.payload }; 55 | case USER_DETAILS_FAIL: 56 | return { loading: false, error: action.payload }; 57 | case USER_DETAILS_RESET: 58 | return { user: {} }; 59 | default: 60 | return state; 61 | } 62 | }; 63 | 64 | // UPDATE PROFILE 65 | export const userUpdateProfileReducer = (state = {}, action) => { 66 | switch (action.type) { 67 | case USER_UPDATE_PROFILE_REQUEST: 68 | return { loading: true }; 69 | case USER_UPDATE_PROFILE_SUCCESS: 70 | return { loading: false, success: true, userInfo: action.payload }; 71 | case USER_UPDATE_PROFILE_FAIL: 72 | return { loading: false, error: action.payload }; 73 | default: 74 | return state; 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /dashboard/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 18 | 19 | 23 | 24 | 25 | 29 | 33 | 34 | 43 | ShoeShop Admin Dashboard 44 | 45 | 46 | 47 |
48 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Reducers/OrderReducres.js: -------------------------------------------------------------------------------- 1 | import { 2 | ORDER_CREATE_FAIL, 3 | ORDER_CREATE_REQUEST, 4 | ORDER_CREATE_RESET, 5 | ORDER_CREATE_SUCCESS, 6 | ORDER_DETAILS_FAIL, 7 | ORDER_DETAILS_REQUEST, 8 | ORDER_DETAILS_SUCCESS, 9 | ORDER_LIST_MY_FAIL, 10 | ORDER_LIST_MY_REQUEST, 11 | ORDER_LIST_MY_RESET, 12 | ORDER_LIST_MY_SUCCESS, 13 | ORDER_PAY_FAIL, 14 | ORDER_PAY_REQUEST, 15 | ORDER_PAY_RESET, 16 | ORDER_PAY_SUCCESS, 17 | } from "../Constants/OrderConstants"; 18 | 19 | // CREATE ORDER 20 | export const orderCreateReducer = (state = {}, action) => { 21 | switch (action.type) { 22 | case ORDER_CREATE_REQUEST: 23 | return { loading: true }; 24 | case ORDER_CREATE_SUCCESS: 25 | return { loading: false, success: true, order: action.payload }; 26 | case ORDER_CREATE_FAIL: 27 | return { loading: false, error: action.payload }; 28 | case ORDER_CREATE_RESET: 29 | return {}; 30 | default: 31 | return state; 32 | } 33 | }; 34 | 35 | // ORDER DETAILS 36 | export const orderDetailsReducer = ( 37 | state = { loading: true, orderItems: [], shippingAddress: {} }, 38 | action 39 | ) => { 40 | switch (action.type) { 41 | case ORDER_DETAILS_REQUEST: 42 | return { ...state, loading: true }; 43 | case ORDER_DETAILS_SUCCESS: 44 | return { loading: false, order: action.payload }; 45 | case ORDER_DETAILS_FAIL: 46 | return { loading: false, error: action.payload }; 47 | default: 48 | return state; 49 | } 50 | }; 51 | 52 | // ORDER PAY 53 | export const orderPayReducer = (state = {}, action) => { 54 | switch (action.type) { 55 | case ORDER_PAY_REQUEST: 56 | return { loading: true }; 57 | case ORDER_PAY_SUCCESS: 58 | return { loading: false, success: true }; 59 | case ORDER_PAY_FAIL: 60 | return { loading: false, error: action.payload }; 61 | case ORDER_PAY_RESET: 62 | return {}; 63 | default: 64 | return state; 65 | } 66 | }; 67 | 68 | // USER ORDERS 69 | export const orderListMyReducer = (state = { orders: [] }, action) => { 70 | switch (action.type) { 71 | case ORDER_LIST_MY_REQUEST: 72 | return { loading: true }; 73 | case ORDER_LIST_MY_SUCCESS: 74 | return { loading: false, orders: action.payload }; 75 | case ORDER_LIST_MY_FAIL: 76 | return { loading: false, error: action.payload }; 77 | case ORDER_LIST_MY_RESET: 78 | return { orders: [] }; 79 | default: 80 | return state; 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /client frontend/src/screens/Register.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { Link } from "react-router-dom"; 4 | import Message from "../components/LoadingError/Error"; 5 | import Loading from "../components/LoadingError/Loading"; 6 | import { register } from "../Redux/Actions/userActions"; 7 | import Header from "./../components/Header"; 8 | 9 | const Register = ({ location, history }) => { 10 | window.scrollTo(0, 0); 11 | const [name, setName] = useState(""); 12 | const [email, setEmail] = useState(""); 13 | const [password, setPassword] = useState(""); 14 | 15 | const dispatch = useDispatch(); 16 | const redirect = location.search ? location.search.split("=")[1] : "/"; 17 | 18 | const userRegister = useSelector((state) => state.userRegister); 19 | const { error, loading, userInfo } = userRegister; 20 | 21 | useEffect(() => { 22 | if (userInfo) { 23 | history.push(redirect); 24 | } 25 | }, [userInfo, history, redirect]); 26 | 27 | const submitHandler = (e) => { 28 | e.preventDefault(); 29 | dispatch(register(name, email, password)); 30 | }; 31 | 32 | return ( 33 | <> 34 |
35 |
36 | {error && {error}} 37 | {loading && } 38 | 39 |
43 | setName(e.target.value)} 48 | /> 49 | setEmail(e.target.value)} 54 | /> 55 | setPassword(e.target.value)} 60 | /> 61 | 62 | 63 |

64 | 65 | I Have Account Login 66 | 67 |

68 |
69 |
70 | 71 | ); 72 | }; 73 | 74 | export default Register; 75 | -------------------------------------------------------------------------------- /client frontend/src/components/profileComponents/Orders.js: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | import React from "react"; 3 | import { Link } from "react-router-dom"; 4 | import Message from "../LoadingError/Error"; 5 | import Loading from "../LoadingError/Loading"; 6 | const Orders = (props) => { 7 | const { loading, error, orders } = props; 8 | return ( 9 |
10 | {loading ? ( 11 | 12 | ) : error ? ( 13 | {error} 14 | ) : ( 15 | <> 16 | {orders.length === 0 ? ( 17 |
18 | No Orders 19 | 26 | START SHOPPING 27 | 28 |
29 | ) : ( 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {orders.map((order) => ( 42 | 48 | 53 | 54 | 59 | 60 | 61 | ))} 62 | 63 |
IDSTATUSDATETOTAL
49 | 50 | {order._id} 51 | 52 | {order.isPaid ? <>Paid : <>Not Paid} 55 | {order.isPaid 56 | ? moment(order.paidAt).calendar() 57 | : moment(order.createdAt).calendar()} 58 | ${order.totalPrice}
64 |
65 | )} 66 | 67 | )} 68 |
69 | ); 70 | }; 71 | 72 | export default Orders; 73 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Actions/ProductActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { 3 | PRODUCT_CREATE_REVIEW_FAIL, 4 | PRODUCT_CREATE_REVIEW_REQUEST, 5 | PRODUCT_CREATE_REVIEW_SUCCESS, 6 | PRODUCT_DETAILS_FAIL, 7 | PRODUCT_DETAILS_REQUEST, 8 | PRODUCT_DETAILS_SUCCESS, 9 | PRODUCT_LIST_FAIL, 10 | PRODUCT_LIST_REQUEST, 11 | PRODUCT_LIST_SUCCESS, 12 | } from "../Constants/ProductConstants"; 13 | import { logout } from "./userActions"; 14 | 15 | // PRODUCT LIST 16 | export const listProduct = 17 | (keyword = " ", pageNumber = " ") => 18 | async (dispatch) => { 19 | try { 20 | dispatch({ type: PRODUCT_LIST_REQUEST }); 21 | const { data } = await axios.get( 22 | `/api/products?keyword=${keyword}&pageNumber=${pageNumber}` 23 | ); 24 | dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data }); 25 | } catch (error) { 26 | dispatch({ 27 | type: PRODUCT_LIST_FAIL, 28 | payload: 29 | error.response && error.response.data.message 30 | ? error.response.data.message 31 | : error.message, 32 | }); 33 | } 34 | }; 35 | 36 | // SINGLE PRODUCT 37 | export const listProductDetails = (id) => async (dispatch) => { 38 | try { 39 | dispatch({ type: PRODUCT_DETAILS_REQUEST }); 40 | const { data } = await axios.get(`/api/products/${id}`); 41 | dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data }); 42 | } catch (error) { 43 | dispatch({ 44 | type: PRODUCT_DETAILS_FAIL, 45 | payload: 46 | error.response && error.response.data.message 47 | ? error.response.data.message 48 | : error.message, 49 | }); 50 | } 51 | }; 52 | 53 | // PRODUCT REVIEW CREATE 54 | export const createProductReview = 55 | (productId, review) => async (dispatch, getState) => { 56 | try { 57 | dispatch({ type: PRODUCT_CREATE_REVIEW_REQUEST }); 58 | 59 | const { 60 | userLogin: { userInfo }, 61 | } = getState(); 62 | 63 | const config = { 64 | headers: { 65 | "Content-Type": "application/json", 66 | Authorization: `Bearer ${userInfo.token}`, 67 | }, 68 | }; 69 | 70 | await axios.post(`/api/products/${productId}/review`, review, config); 71 | dispatch({ type: PRODUCT_CREATE_REVIEW_SUCCESS }); 72 | } catch (error) { 73 | const message = 74 | error.response && error.response.data.message 75 | ? error.response.data.message 76 | : error.message; 77 | if (message === "Not authorized, token failed") { 78 | dispatch(logout()); 79 | } 80 | dispatch({ 81 | type: PRODUCT_CREATE_REVIEW_FAIL, 82 | payload: message, 83 | }); 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /dashboard/src/responsive.css: -------------------------------------------------------------------------------- 1 | .mobile-block { 2 | display: none; 3 | } 4 | 5 | .btn-mobile { 6 | display: none; 7 | } 8 | 9 | @media (max-width: 1200px) { 10 | /* tablet devices */ 11 | .main-wrap { 12 | margin-left: 220px; 13 | } 14 | 15 | .navbar-aside { 16 | max-width: 220px; 17 | } 18 | } 19 | @media (max-width: 992px) { 20 | /* small tablet devices */ 21 | .itemlist .col-price { 22 | text-align: right; 23 | } 24 | .itemlist .col-check { 25 | display: none; 26 | } 27 | 28 | .card-header .col-check { 29 | display: none; 30 | } 31 | } 32 | @media all and (max-width: 768px) { 33 | /* mobile devices */ 34 | .mobile-block { 35 | display: block !important; 36 | } 37 | 38 | .mobile-hide { 39 | display: none !important; 40 | } 41 | 42 | .btn-mobile { 43 | display: inline-block !important; 44 | } 45 | 46 | .navbar-aside { 47 | z-index: 900; 48 | transform: translateX(-100%); 49 | overflow-x: hidden; 50 | transition: visibility 0.2s ease-in-out, transform 0.2s ease-in-out; 51 | } 52 | .navbar-aside .aside-top .logo { 53 | height: 40px; 54 | } 55 | 56 | .navbar-aside.show { 57 | visibility: visible; 58 | transform: translateX(0); 59 | } 60 | 61 | .mobile-offcanvas { 62 | visibility: hidden; 63 | transform: translateX(-100%); 64 | border-radius: 0; 65 | display: block; 66 | position: fixed; 67 | top: 0; 68 | left: 0; 69 | height: 100%; 70 | z-index: 1200; 71 | width: 80%; 72 | overflow-y: scroll; 73 | overflow-x: hidden; 74 | transition: visibility 0.2s ease-in-out, transform 0.2s ease-in-out; 75 | } 76 | 77 | .mobile-offcanvas.show { 78 | visibility: visible; 79 | transform: translateX(0); 80 | } 81 | 82 | .main-header { 83 | padding-left: 0.5rem; 84 | padding-right: 0.5rem; 85 | } 86 | .main-header .col-nav { 87 | order: 1; 88 | width: 100%; 89 | margin-bottom: 0.8rem; 90 | margin-left: auto; 91 | } 92 | .main-header .col-search { 93 | flex-grow: 1; 94 | width: 100%; 95 | order: 2; 96 | } 97 | 98 | .main-wrap { 99 | margin-left: 0 !important; 100 | } 101 | 102 | .content-header { 103 | flex-direction: column; 104 | align-items: flex-start; 105 | margin-bottom: 1rem; 106 | } 107 | .content-header .content-title { 108 | margin-bottom: 1rem; 109 | } 110 | 111 | .card-header input.form-control { 112 | margin-bottom: 1rem; 113 | } 114 | 115 | .order-info-wrap .icontext { 116 | margin-bottom: 1rem; 117 | } 118 | 119 | .table-responsive table { 120 | min-width: 700px; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /dashboard/src/Redux/Actions/userActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | USER_LIST_FAIL, 3 | USER_LIST_REQUEST, 4 | USER_LIST_RESET, 5 | USER_LIST_SUCCESS, 6 | USER_LOGIN_FAIL, 7 | USER_LOGIN_REQUEST, 8 | USER_LOGIN_SUCCESS, 9 | USER_LOGOUT, 10 | } from "../Constants/UserContants"; 11 | import axios from "axios"; 12 | import { toast } from "react-toastify"; 13 | 14 | // LOGIN 15 | export const login = (email, password) => async (dispatch) => { 16 | const ToastObjects = { 17 | pauseOnFocusLoss: false, 18 | draggable: false, 19 | pauseOnHover: false, 20 | autoClose: 2000, 21 | }; 22 | try { 23 | dispatch({ type: USER_LOGIN_REQUEST }); 24 | 25 | const config = { 26 | headers: { 27 | "Content-Type": "application/json", 28 | }, 29 | }; 30 | 31 | const { data } = await axios.post( 32 | `/api/users/login`, 33 | { email, password }, 34 | config 35 | ); 36 | 37 | if (!data.isAdmin === true) { 38 | toast.error("You are not Admin", ToastObjects); 39 | dispatch({ 40 | type: USER_LOGIN_FAIL, 41 | }); 42 | } else { 43 | dispatch({ type: USER_LOGIN_SUCCESS, payload: data }); 44 | } 45 | 46 | localStorage.setItem("userInfo", JSON.stringify(data)); 47 | } catch (error) { 48 | const message = 49 | error.response && error.response.data.message 50 | ? error.response.data.message 51 | : error.message; 52 | if (message === "Not authorized, token failed") { 53 | dispatch(logout()); 54 | } 55 | dispatch({ 56 | type: USER_LOGIN_FAIL, 57 | payload: message, 58 | }); 59 | } 60 | }; 61 | 62 | // LOGOUT 63 | export const logout = () => (dispatch) => { 64 | localStorage.removeItem("userInfo"); 65 | dispatch({ type: USER_LOGOUT }); 66 | dispatch({ type: USER_LIST_RESET }); 67 | }; 68 | 69 | // ALL USER 70 | export const listUser = () => async (dispatch, getState) => { 71 | try { 72 | dispatch({ type: USER_LIST_REQUEST }); 73 | 74 | const { 75 | userLogin: { userInfo }, 76 | } = getState(); 77 | 78 | const config = { 79 | headers: { 80 | Authorization: `Bearer ${userInfo.token}`, 81 | }, 82 | }; 83 | 84 | const { data } = await axios.get(`/api/users`, config); 85 | 86 | dispatch({ type: USER_LIST_SUCCESS, payload: data }); 87 | } catch (error) { 88 | const message = 89 | error.response && error.response.data.message 90 | ? error.response.data.message 91 | : error.message; 92 | if (message === "Not authorized, token failed") { 93 | dispatch(logout()); 94 | } 95 | dispatch({ 96 | type: USER_LIST_FAIL, 97 | payload: message, 98 | }); 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /dashboard/src/components/orders/Orders.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import moment from "moment"; 4 | 5 | const Orders = (props) => { 6 | const { orders } = props; 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | {orders.map((order) => ( 24 | 25 | 28 | 29 | 30 | 41 | 42 | 49 | 54 | 55 | ))} 56 | 57 | {/* Not paid Not delivered */} 58 | {/* 59 | 62 | 63 | 64 | 67 | 68 | 71 | 76 | */} 77 | 78 |
NameEmailTotalPaidDateStatus 18 | Action 19 |
26 | {order.user.name} 27 | {order.user.email}${order.totalPrice} 31 | {order.isPaid ? ( 32 | 33 | Paid At {moment(order.paidAt).format("MMM Do YY")} 34 | 35 | ) : ( 36 | 37 | Not Paid 38 | 39 | )} 40 | {moment(order.createdAt).format("MMM Do YY")} 43 | {order.isDelivered ? ( 44 | Delivered 45 | ) : ( 46 | Not delivered 47 | )} 48 | 50 | 51 | 52 | 53 |
60 | Velcro Sneakers For Boys & Girls (Blue) 61 | user@example.com$45,789 65 | Not paid 66 | Dec 12 2021 69 | Not Delivered 70 | 72 | 73 | 74 | 75 |
79 | ); 80 | }; 81 | 82 | export default Orders; 83 | -------------------------------------------------------------------------------- /server/data/Products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | name: "Velcro Ballerinas For Girls (Pink)", 4 | image: "/images/6.png", 5 | description: 6 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 7 | price: 89, 8 | countInStock: 3, 9 | rating: 4, 10 | numReviews: 4, 11 | }, 12 | { 13 | name: "Velcro Sneakers For Boys & Girls (Blue)", 14 | image: "/images/5.png", 15 | description: 16 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 17 | price: 599, 18 | countInStock: 10, 19 | rating: 2, 20 | numReviews: 2, 21 | }, 22 | { 23 | name: "Sesame Street Unisex-Child ELMO Puppet Slipper", 24 | image: "/images/4.png", 25 | description: 26 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 27 | price: 929, 28 | countInStock: 0, 29 | rating: 3.5, 30 | numReviews: 3, 31 | }, 32 | { 33 | name: "Lace Casual Boots For Boys & Girls (Tan)", 34 | image: "/images/3.png", 35 | description: 36 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 37 | price: 399, 38 | countInStock: 10, 39 | rating: 5, 40 | numReviews: 9, 41 | }, 42 | { 43 | name: "Lace Walking Shoes For Boys & Girls (Pink)", 44 | image: "/images/2.png", 45 | description: 46 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 47 | price: 49, 48 | countInStock: 7, 49 | rating: 2, 50 | numReviews: 2, 51 | }, 52 | { 53 | name: "Women Red Heels Sandal", 54 | image: "/images/1.png", 55 | description: 56 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 57 | price: 29, 58 | countInStock: 0, 59 | rating: 0, 60 | numReviews: 0, 61 | }, 62 | ]; 63 | 64 | export default products; 65 | -------------------------------------------------------------------------------- /dashboard/src/data/Products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | _id: "1", 4 | name: "Velcro Ballerinas For Girls (Pink)", 5 | image: "/images/6.png", 6 | description: 7 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 8 | price: 89, 9 | countInStock: 3, 10 | rating: 4, 11 | numReviews: 4, 12 | }, 13 | { 14 | _id: "2", 15 | name: "Velcro Sneakers For Boys & Girls (Blue)", 16 | image: "/images/5.png", 17 | description: 18 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 19 | price: 599, 20 | countInStock: 10, 21 | rating: 2, 22 | numReviews: 2, 23 | }, 24 | { 25 | _id: "3", 26 | name: "Sesame Street Unisex-Child ELMO Puppet Slipper", 27 | image: "/images/4.png", 28 | description: 29 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 30 | price: 929, 31 | countInStock: 0, 32 | rating: 3.5, 33 | numReviews: 3, 34 | }, 35 | { 36 | _id: "4", 37 | name: "Lace Casual Boots For Boys & Girls (Tan)", 38 | image: "/images/3.png", 39 | description: 40 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 41 | price: 399, 42 | countInStock: 10, 43 | rating: 5, 44 | numReviews: 9, 45 | }, 46 | { 47 | _id: "5", 48 | name: "Lace Walking Shoes For Boys & Girls (Pink)", 49 | image: "/images/2.png", 50 | description: 51 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 52 | price: 49, 53 | countInStock: 7, 54 | rating: 2, 55 | numReviews: 2, 56 | }, 57 | { 58 | _id: "6", 59 | name: "Women Red Heels Sandal", 60 | image: "/images/1.png", 61 | description: 62 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 63 | price: 29, 64 | countInStock: 0, 65 | rating: 0, 66 | numReviews: 0, 67 | }, 68 | ]; 69 | 70 | export default products; 71 | -------------------------------------------------------------------------------- /client frontend/src/data/Products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | _id: "1", 4 | name: "Velcro Ballerinas For Girls (Pink)", 5 | image: "/images/6.png", 6 | description: 7 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 8 | price: 89, 9 | countInStock: 3, 10 | rating: 4, 11 | numReviews: 4, 12 | }, 13 | { 14 | _id: "2", 15 | name: "Velcro Sneakers For Boys & Girls (Blue)", 16 | image: "/images/5.png", 17 | description: 18 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 19 | price: 599, 20 | countInStock: 10, 21 | rating: 2, 22 | numReviews: 2, 23 | }, 24 | { 25 | _id: "3", 26 | name: "Sesame Street Unisex-Child ELMO Puppet Slipper", 27 | image: "/images/4.png", 28 | description: 29 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 30 | price: 929, 31 | countInStock: 0, 32 | rating: 3.5, 33 | numReviews: 3, 34 | }, 35 | { 36 | _id: "4", 37 | name: "Lace Casual Boots For Boys & Girls (Tan)", 38 | image: "/images/3.png", 39 | description: 40 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 41 | price: 399, 42 | countInStock: 10, 43 | rating: 5, 44 | numReviews: 9, 45 | }, 46 | { 47 | _id: "5", 48 | name: "Lace Walking Shoes For Boys & Girls (Pink)", 49 | image: "/images/2.png", 50 | description: 51 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 52 | price: 49, 53 | countInStock: 7, 54 | rating: 2, 55 | numReviews: 2, 56 | }, 57 | { 58 | _id: "6", 59 | name: "Women Red Heels Sandal", 60 | image: "/images/1.png", 61 | description: 62 | "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 63 | price: 29, 64 | countInStock: 0, 65 | rating: 0, 66 | numReviews: 0, 67 | }, 68 | ]; 69 | 70 | export default products; 71 | -------------------------------------------------------------------------------- /dashboard/src/Redux/Reducers/ProductReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | PRODUCT_CREATE_FAIL, 3 | PRODUCT_CREATE_REQUEST, 4 | PRODUCT_CREATE_RESET, 5 | PRODUCT_CREATE_SUCCESS, 6 | PRODUCT_DELETE_FAIL, 7 | PRODUCT_DELETE_REQUEST, 8 | PRODUCT_DELETE_SUCCESS, 9 | PRODUCT_EDIT_FAIL, 10 | PRODUCT_EDIT_REQUEST, 11 | PRODUCT_EDIT_SUCCESS, 12 | PRODUCT_LIST_FAIL, 13 | PRODUCT_LIST_REQUEST, 14 | PRODUCT_LIST_SUCCESS, 15 | PRODUCT_UPDATE_FAIL, 16 | PRODUCT_UPDATE_REQUEST, 17 | PRODUCT_UPDATE_RESET, 18 | PRODUCT_UPDATE_SUCCESS, 19 | } from "../Constants/ProductConstants"; 20 | 21 | // ALL PRODUCTS 22 | export const productListReducer = (state = { products: [] }, action) => { 23 | switch (action.type) { 24 | case PRODUCT_LIST_REQUEST: 25 | return { loading: true, products: [] }; 26 | case PRODUCT_LIST_SUCCESS: 27 | return { loading: false, products: action.payload }; 28 | case PRODUCT_LIST_FAIL: 29 | return { loading: false, error: action.payload }; 30 | default: 31 | return state; 32 | } 33 | }; 34 | 35 | // DELETE PRODUCT 36 | export const productDeleteReducer = (state = {}, action) => { 37 | switch (action.type) { 38 | case PRODUCT_DELETE_REQUEST: 39 | return { loading: true }; 40 | case PRODUCT_DELETE_SUCCESS: 41 | return { loading: false, success: true }; 42 | case PRODUCT_DELETE_FAIL: 43 | return { loading: false, error: action.payload }; 44 | default: 45 | return state; 46 | } 47 | }; 48 | 49 | // DELETE PRODUCT 50 | export const productCreateReducer = (state = {}, action) => { 51 | switch (action.type) { 52 | case PRODUCT_CREATE_REQUEST: 53 | return { loading: true }; 54 | case PRODUCT_CREATE_SUCCESS: 55 | return { loading: false, success: true, product: action.payload }; 56 | case PRODUCT_CREATE_FAIL: 57 | return { loading: false, error: action.payload }; 58 | case PRODUCT_CREATE_RESET: 59 | return {}; 60 | default: 61 | return state; 62 | } 63 | }; 64 | 65 | // EDIT PRODUCT 66 | export const productEditReducer = ( 67 | state = { product: { reviews: [] } }, 68 | action 69 | ) => { 70 | switch (action.type) { 71 | case PRODUCT_EDIT_REQUEST: 72 | return { ...state, loading: true }; 73 | case PRODUCT_EDIT_SUCCESS: 74 | return { loading: false, product: action.payload }; 75 | case PRODUCT_EDIT_FAIL: 76 | return { loading: false, error: action.payload }; 77 | default: 78 | return state; 79 | } 80 | }; 81 | 82 | // UPDATE PRODUCT 83 | export const productUpdateReducer = (state = { product: {} }, action) => { 84 | switch (action.type) { 85 | case PRODUCT_UPDATE_REQUEST: 86 | return { loading: true }; 87 | case PRODUCT_UPDATE_SUCCESS: 88 | return { loading: false, success: true, product: action.payload }; 89 | case PRODUCT_UPDATE_FAIL: 90 | return { loading: false, error: action.payload }; 91 | case PRODUCT_UPDATE_RESET: 92 | return { product: {} }; 93 | default: 94 | return state; 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /dashboard/src/components/orders/OrderDetailProducts.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const OrderDetailProducts = (props) => { 5 | const { order, loading } = props; 6 | 7 | if (!loading) { 8 | // Calculate Price 9 | const addDecimals = (num) => { 10 | return (Math.round(num * 100) / 100).toFixed(2); 11 | }; 12 | 13 | order.itemsPrice = addDecimals( 14 | order.orderItems.reduce((acc, item) => acc + item.price * item.qty, 0) 15 | ); 16 | } 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | {order.orderItems.map((item, index) => ( 32 | 33 | 46 | 47 | 48 | 49 | 50 | ))} 51 | 52 | 53 | 83 | 84 | 85 |
ProductUnit PriceQuantity 26 | Total 27 |
34 | 35 |
36 | {item.name} 42 |
43 |
{item.name}
44 | 45 |
${item.price} {item.qty} ${item.qty * item.price}
54 |
55 |
56 |
Subtotal:
${order.itemsPrice}
57 |
58 |
59 |
Shipping cost:
${order.shippingPrice}
60 |
61 |
62 |
Grand total:
63 |
64 | ${order.totalPrice} 65 |
66 |
67 |
68 |
Status:
69 |
70 | {order.isPaid ? ( 71 | 72 | Payment done 73 | 74 | ) : ( 75 | 76 | Not Paid 77 | 78 | )} 79 |
80 |
81 |
82 |
86 | ); 87 | }; 88 | 89 | export default OrderDetailProducts; 90 | -------------------------------------------------------------------------------- /client frontend/src/components/homeComponents/ShopSection.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import Rating from "./Rating"; 4 | import Pagination from "./pagination"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { listProduct } from "../../Redux/Actions/ProductActions"; 7 | import Loading from "../LoadingError/Loading"; 8 | import Message from "../LoadingError/Error"; 9 | 10 | const ShopSection = (props) => { 11 | const { keyword, pagenumber } = props; 12 | const dispatch = useDispatch(); 13 | 14 | const productList = useSelector((state) => state.productList); 15 | const { loading, error, products, page, pages } = productList; 16 | 17 | useEffect(() => { 18 | dispatch(listProduct(keyword, pagenumber)); 19 | }, [dispatch, keyword, pagenumber]); 20 | return ( 21 | <> 22 |
23 |
24 |
25 |
26 |
27 | {loading ? ( 28 |
29 | 30 |
31 | ) : error ? ( 32 | {error} 33 | ) : ( 34 | <> 35 | {products.map((product) => ( 36 |
40 |
41 | 42 |
43 | {product.name} 44 |
45 | 46 | 47 |
48 |

49 | 50 | {product.name} 51 | 52 |

53 | 54 | 58 |

${product.price}

59 |
60 |
61 |
62 | ))} 63 | 64 | )} 65 | 66 | {/* Pagination */} 67 | 72 |
73 |
74 |
75 |
76 |
77 | 78 | ); 79 | }; 80 | 81 | export default ShopSection; 82 | -------------------------------------------------------------------------------- /dashboard/src/Redux/Actions/OrderActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | ORDER_DELIVERED_FAIL, 3 | ORDER_DELIVERED_REQUEST, 4 | ORDER_DELIVERED_SUCCESS, 5 | ORDER_DETAILS_FAIL, 6 | ORDER_DETAILS_REQUEST, 7 | ORDER_DETAILS_SUCCESS, 8 | ORDER_LIST_FAIL, 9 | ORDER_LIST_REQUEST, 10 | ORDER_LIST_SUCCESS, 11 | } from "../Constants/OrderConstants"; 12 | import { logout } from "./userActions"; 13 | import axios from "axios"; 14 | 15 | export const listOrders = () => async (dispatch, getState) => { 16 | try { 17 | dispatch({ type: ORDER_LIST_REQUEST }); 18 | 19 | const { 20 | userLogin: { userInfo }, 21 | } = getState(); 22 | 23 | const config = { 24 | headers: { 25 | Authorization: `Bearer ${userInfo.token}`, 26 | }, 27 | }; 28 | 29 | const { data } = await axios.get(`/api/orders/all`, config); 30 | 31 | dispatch({ type: ORDER_LIST_SUCCESS, payload: data }); 32 | } catch (error) { 33 | const message = 34 | error.response && error.response.data.message 35 | ? error.response.data.message 36 | : error.message; 37 | if (message === "Not authorized, token failed") { 38 | dispatch(logout()); 39 | } 40 | dispatch({ 41 | type: ORDER_LIST_FAIL, 42 | payload: message, 43 | }); 44 | } 45 | }; 46 | 47 | // ORDER DETAILS 48 | export const getOrderDetails = (id) => async (dispatch, getState) => { 49 | try { 50 | dispatch({ type: ORDER_DETAILS_REQUEST }); 51 | 52 | const { 53 | userLogin: { userInfo }, 54 | } = getState(); 55 | 56 | const config = { 57 | headers: { 58 | Authorization: `Bearer ${userInfo.token}`, 59 | }, 60 | }; 61 | 62 | const { data } = await axios.get(`/api/orders/${id}`, config); 63 | dispatch({ type: ORDER_DETAILS_SUCCESS, payload: data }); 64 | } catch (error) { 65 | const message = 66 | error.response && error.response.data.message 67 | ? error.response.data.message 68 | : error.message; 69 | if (message === "Not authorized, token failed") { 70 | dispatch(logout()); 71 | } 72 | dispatch({ 73 | type: ORDER_DETAILS_FAIL, 74 | payload: message, 75 | }); 76 | } 77 | }; 78 | 79 | // ORDER DELIVER 80 | export const deliverOrder = (order) => async (dispatch, getState) => { 81 | try { 82 | dispatch({ type: ORDER_DELIVERED_REQUEST }); 83 | 84 | const { 85 | userLogin: { userInfo }, 86 | } = getState(); 87 | 88 | const config = { 89 | headers: { 90 | Authorization: `Bearer ${userInfo.token}`, 91 | }, 92 | }; 93 | 94 | const { data } = await axios.put( 95 | `/api/orders/${order._id}/delivered`, 96 | {}, 97 | config 98 | ); 99 | dispatch({ type: ORDER_DELIVERED_SUCCESS, payload: data }); 100 | } catch (error) { 101 | const message = 102 | error.response && error.response.data.message 103 | ? error.response.data.message 104 | : error.message; 105 | if (message === "Not authorized, token failed") { 106 | dispatch(logout()); 107 | } 108 | dispatch({ 109 | type: ORDER_DELIVERED_FAIL, 110 | payload: message, 111 | }); 112 | } 113 | }; 114 | -------------------------------------------------------------------------------- /server/Routes/orderRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import asyncHandler from "express-async-handler"; 3 | import { admin, protect } from "../Middleware/AuthMiddleware.js"; 4 | import Order from "./../Models/OrderModel.js"; 5 | 6 | const orderRouter = express.Router(); 7 | 8 | // CREATE ORDER 9 | orderRouter.post( 10 | "/", 11 | protect, 12 | asyncHandler(async (req, res) => { 13 | const { 14 | orderItems, 15 | shippingAddress, 16 | paymentMethod, 17 | itemsPrice, 18 | taxPrice, 19 | shippingPrice, 20 | totalPrice, 21 | } = req.body; 22 | 23 | if (orderItems && orderItems.length === 0) { 24 | res.status(400); 25 | throw new Error("No order items"); 26 | return; 27 | } else { 28 | const order = new Order({ 29 | orderItems, 30 | user: req.user._id, 31 | shippingAddress, 32 | paymentMethod, 33 | itemsPrice, 34 | taxPrice, 35 | shippingPrice, 36 | totalPrice, 37 | }); 38 | 39 | const createOrder = await order.save(); 40 | res.status(201).json(createOrder); 41 | } 42 | }) 43 | ); 44 | 45 | // ADMIN GET ALL ORDERS 46 | orderRouter.get( 47 | "/all", 48 | protect, 49 | admin, 50 | asyncHandler(async (req, res) => { 51 | const orders = await Order.find({}) 52 | .sort({ _id: -1 }) 53 | .populate("user", "id name email"); 54 | res.json(orders); 55 | }) 56 | ); 57 | // USER LOGIN ORDERS 58 | orderRouter.get( 59 | "/", 60 | protect, 61 | asyncHandler(async (req, res) => { 62 | const order = await Order.find({ user: req.user._id }).sort({ _id: -1 }); 63 | res.json(order); 64 | }) 65 | ); 66 | 67 | // GET ORDER BY ID 68 | orderRouter.get( 69 | "/:id", 70 | protect, 71 | asyncHandler(async (req, res) => { 72 | const order = await Order.findById(req.params.id).populate( 73 | "user", 74 | "name email" 75 | ); 76 | 77 | if (order) { 78 | res.json(order); 79 | } else { 80 | res.status(404); 81 | throw new Error("Order Not Found"); 82 | } 83 | }) 84 | ); 85 | 86 | // ORDER IS PAID 87 | orderRouter.put( 88 | "/:id/pay", 89 | protect, 90 | asyncHandler(async (req, res) => { 91 | const order = await Order.findById(req.params.id); 92 | 93 | if (order) { 94 | order.isPaid = true; 95 | order.paidAt = Date.now(); 96 | order.paymentResult = { 97 | id: req.body.id, 98 | status: req.body.status, 99 | update_time: req.body.update_time, 100 | email_address: req.body.email_address, 101 | }; 102 | 103 | const updatedOrder = await order.save(); 104 | res.json(updatedOrder); 105 | } else { 106 | res.status(404); 107 | throw new Error("Order Not Found"); 108 | } 109 | }) 110 | ); 111 | 112 | // ORDER IS PAID 113 | orderRouter.put( 114 | "/:id/delivered", 115 | protect, 116 | asyncHandler(async (req, res) => { 117 | const order = await Order.findById(req.params.id); 118 | 119 | if (order) { 120 | order.isDelivered = true; 121 | order.deliveredAt = Date.now(); 122 | 123 | const updatedOrder = await order.save(); 124 | res.json(updatedOrder); 125 | } else { 126 | res.status(404); 127 | throw new Error("Order Not Found"); 128 | } 129 | }) 130 | ); 131 | 132 | export default orderRouter; 133 | -------------------------------------------------------------------------------- /dashboard/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 the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will 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 frontend/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 the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will 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 | -------------------------------------------------------------------------------- /server/Routes/UserRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import asyncHandler from "express-async-handler"; 3 | import { protect, admin } from "../Middleware/AuthMiddleware.js"; 4 | import generateToken from "../utils/generateToken.js"; 5 | import User from "./../Models/UserModel.js"; 6 | 7 | const userRouter = express.Router(); 8 | 9 | // LOGIN 10 | userRouter.post( 11 | "/login", 12 | asyncHandler(async (req, res) => { 13 | const { email, password } = req.body; 14 | const user = await User.findOne({ email }); 15 | 16 | if (user && (await user.matchPassword(password))) { 17 | res.json({ 18 | _id: user._id, 19 | name: user.name, 20 | email: user.email, 21 | isAdmin: user.isAdmin, 22 | token: generateToken(user._id), 23 | createdAt: user.createdAt, 24 | }); 25 | } else { 26 | res.status(401); 27 | throw new Error("Invalid Email or Password"); 28 | } 29 | }) 30 | ); 31 | 32 | // REGISTER 33 | userRouter.post( 34 | "/", 35 | asyncHandler(async (req, res) => { 36 | const { name, email, password } = req.body; 37 | 38 | const userExists = await User.findOne({ email }); 39 | 40 | if (userExists) { 41 | res.status(400); 42 | throw new Error("User already exists"); 43 | } 44 | 45 | const user = await User.create({ 46 | name, 47 | email, 48 | password, 49 | }); 50 | 51 | if (user) { 52 | res.status(201).json({ 53 | _id: user._id, 54 | name: user.name, 55 | email: user.email, 56 | isAdmin: user.isAdmin, 57 | token: generateToken(user._id), 58 | }); 59 | } else { 60 | res.status(400); 61 | throw new Error("Invalid User Data"); 62 | } 63 | }) 64 | ); 65 | 66 | // PROFILE 67 | userRouter.get( 68 | "/profile", 69 | protect, 70 | asyncHandler(async (req, res) => { 71 | const user = await User.findById(req.user._id); 72 | 73 | if (user) { 74 | res.json({ 75 | _id: user._id, 76 | name: user.name, 77 | email: user.email, 78 | isAdmin: user.isAdmin, 79 | createdAt: user.createdAt, 80 | }); 81 | } else { 82 | res.status(404); 83 | throw new Error("User not found"); 84 | } 85 | }) 86 | ); 87 | 88 | // UPDATE PROFILE 89 | userRouter.put( 90 | "/profile", 91 | protect, 92 | asyncHandler(async (req, res) => { 93 | const user = await User.findById(req.user._id); 94 | 95 | if (user) { 96 | user.name = req.body.name || user.name; 97 | user.email = req.body.email || user.email; 98 | if (req.body.password) { 99 | user.password = req.body.password; 100 | } 101 | const updatedUser = await user.save(); 102 | res.json({ 103 | _id: updatedUser._id, 104 | name: updatedUser.name, 105 | email: updatedUser.email, 106 | isAdmin: updatedUser.isAdmin, 107 | createdAt: updatedUser.createdAt, 108 | token: generateToken(updatedUser._id), 109 | }); 110 | } else { 111 | res.status(404); 112 | throw new Error("User not found"); 113 | } 114 | }) 115 | ); 116 | 117 | // GET ALL USER ADMIN 118 | userRouter.get( 119 | "/", 120 | protect, 121 | admin, 122 | asyncHandler(async (req, res) => { 123 | const users = await User.find({}); 124 | res.json(users); 125 | }) 126 | ); 127 | 128 | export default userRouter; 129 | -------------------------------------------------------------------------------- /client frontend/src/responsive.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 991.98px) { 2 | .display-none { 3 | display: none !important; 4 | } 5 | .search-button { 6 | font-size: 10px; 7 | } 8 | .Login-Register a { 9 | font-size: 10px; 10 | } 11 | .btn-group button { 12 | font-size: 13px; 13 | } 14 | .badge { 15 | top: -10px; 16 | padding: 5px; 17 | font-size: 7px; 18 | } 19 | .input-group { 20 | height: 30px; 21 | } 22 | .single-image, 23 | .single-image img { 24 | height: 400px; 25 | } 26 | .author-card-avatar img { 27 | margin-top: -20%; 28 | } 29 | .order-box { 30 | width: 50px; 31 | height: 50px; 32 | font-size: 15px; 33 | } 34 | .order-detail p { 35 | font-size: 12px; 36 | } 37 | .order-detail { 38 | padding: 20px 0; 39 | } 40 | .order-detail h5 { 41 | font-size: 13px; 42 | } 43 | .subtotal-order button { 44 | margin-bottom: 30px; 45 | } 46 | .single-product { 47 | margin-top: 20px; 48 | } 49 | } 50 | @media (max-width: 768px) { 51 | .pc-header { 52 | display: none !important; 53 | } 54 | .mobile-header { 55 | display: block; 56 | } 57 | .cart-mobile-icon i { 58 | font-size: 13px; 59 | margin-left: 10px; 60 | } 61 | .btn-group button { 62 | font-size: 12px; 63 | } 64 | .mobile-header img { 65 | width: 60px; 66 | } 67 | .input-group { 68 | width: 100%; 69 | margin-top: 5px; 70 | } 71 | .search-button { 72 | width: 30%; 73 | } 74 | .subscribe-head .form-section input[type="email"] { 75 | height: 45px; 76 | width: 70%; 77 | } 78 | .subscribe-head .form-section input[type="submit"] { 79 | height: 47px; 80 | padding: 0 58px; 81 | } 82 | .subscribe-head h2 { 83 | font-size: 25px; 84 | } 85 | .contact-Box { 86 | margin: 30px 0; 87 | } 88 | .contactInfo { 89 | padding: 20px 0; 90 | } 91 | .cart-buttons { 92 | margin: 40px 0; 93 | } 94 | .cart-buttons a button, 95 | .cart-buttons .d-flex button { 96 | width: 100%; 97 | } 98 | .total .sub, 99 | .total .total-price { 100 | font-size: 16px; 101 | } 102 | 103 | .single-image { 104 | margin-bottom: 50px; 105 | margin-right: 0; 106 | } 107 | .product-name { 108 | font-size: 23px; 109 | } 110 | .author-card-avatar { 111 | display: none; 112 | } 113 | .order-detail { 114 | margin-top: 20px; 115 | } 116 | } 117 | @media (max-width: 575.98px) { 118 | .subscribe-head .form-section input[type="email"] { 119 | width: 100%; 120 | } 121 | .subscribe-head .form-section input[type="submit"] { 122 | width: 100%; 123 | padding: 0 0; 124 | margin-top: 20px; 125 | margin-left: 0px; 126 | } 127 | .subscribe-head h2 { 128 | font-size: 25px; 129 | } 130 | .subscribe-section { 131 | padding: 70px 0 70px; 132 | } 133 | .card-name { 134 | width: 8%; 135 | margin: 0 7px; 136 | } 137 | .login-center { 138 | padding: 20px 0; 139 | } 140 | .center { 141 | display: flex; 142 | justify-content: center; 143 | align-items: center; 144 | flex-direction: column; 145 | } 146 | .center h5 { 147 | font-size: 17px; 148 | margin-top: 10px; 149 | } 150 | .center p { 151 | font-size: 17px; 152 | } 153 | } 154 | @media (max-width: 400px) { 155 | .subscribe-head h2 { 156 | font-size: 20px; 157 | } 158 | 159 | .single-image, 160 | .single-image img { 161 | height: 200px; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /dashboard/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import $ from "jquery"; 4 | import { useDispatch } from "react-redux"; 5 | import { logout } from "../Redux/Actions/userActions"; 6 | 7 | const Header = () => { 8 | const dispatch = useDispatch(); 9 | useEffect(() => { 10 | $("[data-trigger]").on("click", function (e) { 11 | e.preventDefault(); 12 | e.stopPropagation(); 13 | var offcanvas_id = $(this).attr("data-trigger"); 14 | $(offcanvas_id).toggleClass("show"); 15 | }); 16 | 17 | $(".btn-aside-minimize").on("click", function () { 18 | if (window.innerWidth < 768) { 19 | $("body").removeClass("aside-mini"); 20 | $(".navbar-aside").removeClass("show"); 21 | } else { 22 | // minimize sidebar on desktop 23 | $("body").toggleClass("aside-mini"); 24 | } 25 | }); 26 | }, []); 27 | 28 | const logoutHandler = () => { 29 | dispatch(logout()); 30 | }; 31 | 32 | return ( 33 |
34 |
35 |
36 |
37 | 43 | 46 |
47 | 48 | 53 |
54 |
55 |
56 | 62 |
    63 |
  • 64 | 65 | 66 | 67 |
  • 68 |
  • 69 | 70 | 71 | 72 |
  • 73 |
  • 74 | 75 | English 76 | 77 |
  • 78 |
  • 79 | 80 | User 85 | 86 |
    87 | 88 | My profile 89 | 90 | 91 | Settings 92 | 93 | 98 | Exit 99 | 100 |
    101 |
  • 102 |
103 |
104 |
105 | ); 106 | }; 107 | 108 | export default Header; 109 | -------------------------------------------------------------------------------- /client frontend/src/components/profileComponents/ProfileTabs.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import Message from "../LoadingError/Error"; 4 | import Toast from "./../LoadingError/Toast"; 5 | import Loading from "./../LoadingError/Loading"; 6 | import { toast } from "react-toastify"; 7 | import { updateUserProfile } from "../../Redux/Actions/userActions"; 8 | 9 | const ProfileTabs = () => { 10 | const [name, setName] = useState(""); 11 | const [email, setEmail] = useState(""); 12 | const [password, setPassword] = useState(""); 13 | const [confirmPassword, setConfirmPassword] = useState(""); 14 | const toastId = React.useRef(null); 15 | 16 | const Toastobjects = { 17 | pauseOnFocusLoss: false, 18 | draggable: false, 19 | pauseOnHover: false, 20 | autoClose: 2000, 21 | }; 22 | 23 | const dispatch = useDispatch(); 24 | 25 | const userDetails = useSelector((state) => state.userDetails); 26 | const { loading, error, user } = userDetails; 27 | 28 | const userUpdateProfile = useSelector((state) => state.userUpdateProfile); 29 | const { loading: updateLoading } = userUpdateProfile; 30 | 31 | useEffect(() => { 32 | if (user) { 33 | setName(user.name); 34 | setEmail(user.email); 35 | } 36 | }, [dispatch, user]); 37 | 38 | const submitHandler = (e) => { 39 | e.preventDefault(); 40 | // Password match 41 | if (password !== confirmPassword) { 42 | if (!toast.isActive(toastId.current)) { 43 | toastId.current = toast.error("Password does not match", Toastobjects); 44 | } 45 | } else { 46 | dispatch(updateUserProfile({ id: user._id, name, email, password })); 47 | if (!toast.isActive(toastId.current)) { 48 | toastId.current = toast.success("Profile Updated", Toastobjects); 49 | } 50 | } 51 | }; 52 | return ( 53 | <> 54 | 55 | {error && {error}} 56 | {loading && } 57 | {updateLoading && } 58 |
59 |
60 |
61 | 62 | setName(e.target.value)} 68 | /> 69 |
70 |
71 | 72 |
73 |
74 | 75 | setEmail(e.target.value)} 81 | /> 82 |
83 |
84 |
85 |
86 | 87 | setPassword(e.target.value)} 92 | /> 93 |
94 |
95 |
96 |
97 | 98 | setConfirmPassword(e.target.value)} 103 | /> 104 |
105 |
106 | 107 |
108 | 109 | ); 110 | }; 111 | 112 | export default ProfileTabs; 113 | -------------------------------------------------------------------------------- /dashboard/src/components/products/MainProducts.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import Product from "./Product"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { listProducts } from "../../Redux/Actions/ProductActions"; 6 | import Loading from "../LoadingError/Loading"; 7 | import Message from "../LoadingError/Error"; 8 | 9 | const MainProducts = () => { 10 | const dispatch = useDispatch(); 11 | 12 | const productList = useSelector((state) => state.productList); 13 | const { loading, error, products } = productList; 14 | 15 | const productDelete = useSelector((state) => state.productDelete); 16 | const { error: errorDelete, success: successDelete } = productDelete; 17 | 18 | useEffect(() => { 19 | dispatch(listProducts()); 20 | }, [dispatch, successDelete]); 21 | 22 | return ( 23 |
24 |
25 |

Products

26 |
27 | 28 | Create new 29 | 30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 | 42 |
43 |
44 | 50 |
51 |
52 | 57 |
58 |
59 |
60 | 61 |
62 | {errorDelete && ( 63 | {errorDelete} 64 | )} 65 | {loading ? ( 66 | 67 | ) : error ? ( 68 | {error} 69 | ) : ( 70 |
71 | {/* Products */} 72 | {products.map((product) => ( 73 | 74 | ))} 75 |
76 | )} 77 | 78 | 107 |
108 |
109 |
110 | ); 111 | }; 112 | 113 | export default MainProducts; 114 | -------------------------------------------------------------------------------- /dashboard/src/components/sidebar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link, NavLink } from "react-router-dom"; 3 | 4 | const Sidebar = () => { 5 | return ( 6 |
7 | 113 |
114 | ); 115 | }; 116 | 117 | export default Sidebar; 118 | -------------------------------------------------------------------------------- /dashboard/src/components/Categories/CategoriesTable.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const CategoriesTable = () => { 5 | return ( 6 |
7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {/* Table Data */} 22 | 23 | 24 | 29 | 30 | 33 | 34 | 53 | 54 | 55 | 60 | 61 | 64 | 65 | 66 | 85 | 86 | 87 | 92 | 93 | 96 | 97 | 98 | 117 | 118 | 119 |
11 |
12 | 13 |
14 |
IDNameDescriptionAction
25 |
26 | 27 |
28 |
1 31 | Men clothes 32 | Men clothes 35 |
36 | 41 | 42 | 43 |
44 | 45 | Edit info 46 | 47 | 48 | Delete 49 | 50 |
51 |
52 |
56 |
57 | 58 |
59 |
2 62 | Women fashion 63 | Fashions for Women 67 |
68 | 73 | 74 | 75 |
76 | 77 | Edit info 78 | 79 | 80 | Delete 81 | 82 |
83 |
84 |
88 |
89 | 90 |
91 |
3 94 | Kids clothes 95 | Clothes for kids 99 |
100 | 105 | 106 | 107 |
108 | 109 | Edit info 110 | 111 | 112 | Delete 113 | 114 |
115 |
116 |
120 |
121 | ); 122 | }; 123 | 124 | export default CategoriesTable; 125 | -------------------------------------------------------------------------------- /client frontend/src/screens/ProfileScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import Header from "../components/Header"; 4 | import ProfileTabs from "../components/profileComponents/ProfileTabs"; 5 | import { getUserDetails } from "../Redux/Actions/userActions"; 6 | import Orders from "./../components/profileComponents/Orders"; 7 | import moment from "moment"; 8 | import { listMyOrders } from "../Redux/Actions/OrderActions"; 9 | 10 | const ProfileScreen = () => { 11 | window.scrollTo(0, 0); 12 | 13 | const dispatch = useDispatch(); 14 | 15 | const userLogin = useSelector((state) => state.userLogin); 16 | const { userInfo } = userLogin; 17 | const orderListMy = useSelector((state) => state.orderListMy); 18 | const { loading, error, orders } = orderListMy; 19 | 20 | useEffect(() => { 21 | dispatch(listMyOrders()); 22 | dispatch(getUserDetails("profile")); 23 | }, [dispatch]); 24 | 25 | return ( 26 | <> 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | userprofileimage 36 |
37 |
38 |
39 | {userInfo.name} 40 |
41 | 42 | <>Joined {moment(userInfo.createdAt).format("LL")} 43 | 44 |
45 |
46 |
47 |
48 |
49 | 81 |
82 |
83 |
84 | 85 | {/* panels */} 86 |
90 |
96 | 97 |
98 |
104 | 105 |
106 |
107 |
108 |
109 | 110 | ); 111 | }; 112 | 113 | export default ProfileScreen; 114 | -------------------------------------------------------------------------------- /dashboard/src/components/orders/OrderDetailmain.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import OrderDetailProducts from "./OrderDetailProducts"; 3 | import OrderDetailInfo from "./OrderDetailInfo"; 4 | import { Link } from "react-router-dom"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { 7 | deliverOrder, 8 | getOrderDetails, 9 | } from "../../Redux/Actions/OrderActions"; 10 | import Loading from "../LoadingError/Loading"; 11 | import Message from "../LoadingError/Error"; 12 | import moment from "moment"; 13 | 14 | const OrderDetailmain = (props) => { 15 | const { orderId } = props; 16 | const dispatch = useDispatch(); 17 | 18 | const orderDetails = useSelector((state) => state.orderDetails); 19 | const { loading, error, order } = orderDetails; 20 | 21 | const orderDeliver = useSelector((state) => state.orderDeliver); 22 | const { loading: loadingDelivered, success: successDelivered } = orderDeliver; 23 | 24 | useEffect(() => { 25 | dispatch(getOrderDetails(orderId)); 26 | }, [dispatch, orderId, successDelivered]); 27 | 28 | const deliverHandler = () => { 29 | dispatch(deliverOrder(order)); 30 | }; 31 | 32 | return ( 33 |
34 |
35 | 36 | Back To Orders 37 | 38 |
39 | 40 | {loading ? ( 41 | 42 | ) : error ? ( 43 | {error} 44 | ) : ( 45 |
46 |
47 |
48 |
49 | 50 | 51 | 52 | {moment(order.createdAt).format("llll")} 53 | 54 | 55 |
56 | 57 | Order ID: {order._id} 58 | 59 |
60 |
61 | 71 | 72 | 73 | 74 |
75 |
76 |
77 |
78 | {/* Order info */} 79 | 80 | 81 |
82 |
83 |
84 | 85 |
86 |
87 | {/* Payment Info */} 88 |
89 |
90 | {order.isDelivered ? ( 91 | 95 | ) : ( 96 | <> 97 | {loadingDelivered && } 98 | 104 | 105 | )} 106 |
107 |
108 |
109 |
110 |
111 | )} 112 |
113 | ); 114 | }; 115 | 116 | export default OrderDetailmain; 117 | -------------------------------------------------------------------------------- /dashboard/src/components/users/UserComponent.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { listUser } from "../../Redux/Actions/userActions"; 5 | import Loading from "../LoadingError/Loading"; 6 | import Message from "../LoadingError/Error"; 7 | 8 | const UserComponent = () => { 9 | const dispatch = useDispatch(); 10 | 11 | const userList = useSelector((state) => state.userList); 12 | const { loading, error, users } = userList; 13 | 14 | useEffect(() => { 15 | dispatch(listUser()); 16 | }, [dispatch]); 17 | return ( 18 |
19 |
20 |

Customers

21 |
22 | 23 | Create new 24 | 25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 | 37 |
38 |
39 | 45 |
46 |
47 | 52 |
53 |
54 |
55 | 56 | {/* Card */} 57 |
58 | {loading ? ( 59 | 60 | ) : error ? ( 61 | {error} 62 | ) : ( 63 |
64 | {users.map((user) => ( 65 |
66 |
67 |
68 | User pic 73 |
74 |
75 |
{user.name}
76 |
77 | {user.isAdmin === true ? ( 78 |

Admin

79 | ) : ( 80 |

Customer

81 | )} 82 | 83 |

84 | {user.email} 85 |

86 |
87 |
88 |
89 |
90 | ))} 91 |
92 | )} 93 | 94 | {/* nav */} 95 | 114 |
115 |
116 |
117 | ); 118 | }; 119 | 120 | export default UserComponent; 121 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Actions/OrderActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | ORDER_CREATE_FAIL, 3 | ORDER_CREATE_REQUEST, 4 | ORDER_CREATE_SUCCESS, 5 | ORDER_DETAILS_FAIL, 6 | ORDER_DETAILS_REQUEST, 7 | ORDER_DETAILS_SUCCESS, 8 | ORDER_LIST_MY_FAIL, 9 | ORDER_LIST_MY_REQUEST, 10 | ORDER_LIST_MY_SUCCESS, 11 | ORDER_PAY_FAIL, 12 | ORDER_PAY_REQUEST, 13 | ORDER_PAY_SUCCESS, 14 | } from "../Constants/OrderConstants"; 15 | import axios from "axios"; 16 | import { CART_CLEAR_ITEMS } from "../Constants/CartConstants"; 17 | import { logout } from "./userActions"; 18 | 19 | // CREATE ORDER 20 | export const createOrder = (order) => async (dispatch, getState) => { 21 | try { 22 | dispatch({ type: ORDER_CREATE_REQUEST }); 23 | 24 | const { 25 | userLogin: { userInfo }, 26 | } = getState(); 27 | 28 | const config = { 29 | headers: { 30 | "Content-Type": "application/json", 31 | Authorization: `Bearer ${userInfo.token}`, 32 | }, 33 | }; 34 | 35 | const { data } = await axios.post(`/api/orders`, order, config); 36 | dispatch({ type: ORDER_CREATE_SUCCESS, payload: data }); 37 | dispatch({ type: CART_CLEAR_ITEMS, payload: data }); 38 | 39 | localStorage.removeItem("cartItems"); 40 | } catch (error) { 41 | const message = 42 | error.response && error.response.data.message 43 | ? error.response.data.message 44 | : error.message; 45 | if (message === "Not authorized, token failed") { 46 | dispatch(logout()); 47 | } 48 | dispatch({ 49 | type: ORDER_CREATE_FAIL, 50 | payload: message, 51 | }); 52 | } 53 | }; 54 | 55 | // ORDER DETAILS 56 | export const getOrderDetails = (id) => async (dispatch, getState) => { 57 | try { 58 | dispatch({ type: ORDER_DETAILS_REQUEST }); 59 | 60 | const { 61 | userLogin: { userInfo }, 62 | } = getState(); 63 | 64 | const config = { 65 | headers: { 66 | Authorization: `Bearer ${userInfo.token}`, 67 | }, 68 | }; 69 | 70 | const { data } = await axios.get(`/api/orders/${id}`, config); 71 | dispatch({ type: ORDER_DETAILS_SUCCESS, payload: data }); 72 | } catch (error) { 73 | const message = 74 | error.response && error.response.data.message 75 | ? error.response.data.message 76 | : error.message; 77 | if (message === "Not authorized, token failed") { 78 | dispatch(logout()); 79 | } 80 | dispatch({ 81 | type: ORDER_DETAILS_FAIL, 82 | payload: message, 83 | }); 84 | } 85 | }; 86 | 87 | // ORDER PAY 88 | export const payOrder = 89 | (orderId, paymentResult) => async (dispatch, getState) => { 90 | try { 91 | dispatch({ type: ORDER_PAY_REQUEST }); 92 | 93 | const { 94 | userLogin: { userInfo }, 95 | } = getState(); 96 | 97 | const config = { 98 | headers: { 99 | "Content-Type": "application/json", 100 | Authorization: `Bearer ${userInfo.token}`, 101 | }, 102 | }; 103 | 104 | const { data } = await axios.put( 105 | `/api/orders/${orderId}/pay`, 106 | paymentResult, 107 | config 108 | ); 109 | dispatch({ type: ORDER_PAY_SUCCESS, payload: data }); 110 | } catch (error) { 111 | const message = 112 | error.response && error.response.data.message 113 | ? error.response.data.message 114 | : error.message; 115 | if (message === "Not authorized, token failed") { 116 | dispatch(logout()); 117 | } 118 | dispatch({ 119 | type: ORDER_PAY_FAIL, 120 | payload: message, 121 | }); 122 | } 123 | }; 124 | 125 | // USER ORDERS 126 | export const listMyOrders = () => async (dispatch, getState) => { 127 | try { 128 | dispatch({ type: ORDER_LIST_MY_REQUEST }); 129 | 130 | const { 131 | userLogin: { userInfo }, 132 | } = getState(); 133 | 134 | const config = { 135 | headers: { 136 | Authorization: `Bearer ${userInfo.token}`, 137 | }, 138 | }; 139 | 140 | const { data } = await axios.get(`/api/orders/`, config); 141 | dispatch({ type: ORDER_LIST_MY_SUCCESS, payload: data }); 142 | } catch (error) { 143 | const message = 144 | error.response && error.response.data.message 145 | ? error.response.data.message 146 | : error.message; 147 | if (message === "Not authorized, token failed") { 148 | dispatch(logout()); 149 | } 150 | dispatch({ 151 | type: ORDER_LIST_MY_FAIL, 152 | payload: message, 153 | }); 154 | } 155 | }; 156 | -------------------------------------------------------------------------------- /client frontend/src/Redux/Actions/userActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | USER_DETAILS_FAIL, 3 | USER_DETAILS_REQUEST, 4 | USER_DETAILS_RESET, 5 | USER_DETAILS_SUCCESS, 6 | USER_LOGIN_FAIL, 7 | USER_LOGIN_REQUEST, 8 | USER_LOGIN_SUCCESS, 9 | USER_LOGOUT, 10 | USER_REGISTER_FAIL, 11 | USER_REGISTER_REQUEST, 12 | USER_REGISTER_SUCCESS, 13 | USER_UPDATE_PROFILE_FAIL, 14 | USER_UPDATE_PROFILE_REQUEST, 15 | USER_UPDATE_PROFILE_SUCCESS, 16 | } from "../Constants/UserContants"; 17 | import axios from "axios"; 18 | import { ORDER_LIST_MY_RESET } from "../Constants/OrderConstants"; 19 | 20 | // LOGIN 21 | export const login = (email, password) => async (dispatch) => { 22 | try { 23 | dispatch({ type: USER_LOGIN_REQUEST }); 24 | 25 | const config = { 26 | headers: { 27 | "Content-Type": "application/json", 28 | }, 29 | }; 30 | 31 | const { data } = await axios.post( 32 | `/api/users/login`, 33 | { email, password }, 34 | config 35 | ); 36 | dispatch({ type: USER_LOGIN_SUCCESS, payload: data }); 37 | 38 | localStorage.setItem("userInfo", JSON.stringify(data)); 39 | } catch (error) { 40 | dispatch({ 41 | type: USER_LOGIN_FAIL, 42 | payload: 43 | error.response && error.response.data.message 44 | ? error.response.data.message 45 | : error.message, 46 | }); 47 | } 48 | }; 49 | 50 | // LOGOUT 51 | export const logout = () => (dispatch) => { 52 | localStorage.removeItem("userInfo"); 53 | dispatch({ type: USER_LOGOUT }); 54 | dispatch({ type: USER_DETAILS_RESET }); 55 | dispatch({ type: ORDER_LIST_MY_RESET }); 56 | }; 57 | 58 | // REGISTER 59 | export const register = (name, email, password) => async (dispatch) => { 60 | try { 61 | dispatch({ type: USER_REGISTER_REQUEST }); 62 | 63 | const config = { 64 | headers: { 65 | "Content-Type": "application/json", 66 | }, 67 | }; 68 | 69 | const { data } = await axios.post( 70 | `/api/users`, 71 | { name, email, password }, 72 | config 73 | ); 74 | dispatch({ type: USER_REGISTER_SUCCESS, payload: data }); 75 | dispatch({ type: USER_LOGIN_SUCCESS, payload: data }); 76 | 77 | localStorage.setItem("userInfo", JSON.stringify(data)); 78 | } catch (error) { 79 | dispatch({ 80 | type: USER_REGISTER_FAIL, 81 | payload: 82 | error.response && error.response.data.message 83 | ? error.response.data.message 84 | : error.message, 85 | }); 86 | } 87 | }; 88 | 89 | // USER DETAILS 90 | export const getUserDetails = (id) => async (dispatch, getState) => { 91 | try { 92 | dispatch({ type: USER_DETAILS_REQUEST }); 93 | const { 94 | userLogin: { userInfo }, 95 | } = getState(); 96 | 97 | const config = { 98 | headers: { 99 | Authorization: `Bearer ${userInfo.token}`, 100 | }, 101 | }; 102 | 103 | const { data } = await axios.get(`/api/users/${id}`, config); 104 | dispatch({ type: USER_DETAILS_SUCCESS, payload: data }); 105 | } catch (error) { 106 | const message = 107 | error.response && error.response.data.message 108 | ? error.response.data.message 109 | : error.message; 110 | if (message === "Not authorized, token failed") { 111 | dispatch(logout()); 112 | } 113 | dispatch({ 114 | type: USER_DETAILS_FAIL, 115 | payload: message, 116 | }); 117 | } 118 | }; 119 | 120 | // UPDATE PROFILE 121 | export const updateUserProfile = (user) => async (dispatch, getState) => { 122 | try { 123 | dispatch({ type: USER_UPDATE_PROFILE_REQUEST }); 124 | 125 | const { 126 | userLogin: { userInfo }, 127 | } = getState(); 128 | 129 | const config = { 130 | headers: { 131 | "Content-Type": "application/json", 132 | Authorization: `Bearer ${userInfo.token}`, 133 | }, 134 | }; 135 | 136 | const { data } = await axios.put(`/api/users/profile`, user, config); 137 | dispatch({ type: USER_UPDATE_PROFILE_SUCCESS, payload: data }); 138 | dispatch({ type: USER_LOGIN_SUCCESS, payload: data }); 139 | 140 | localStorage.setItem("userInfo", JSON.stringify(data)); 141 | } catch (error) { 142 | const message = 143 | error.response && error.response.data.message 144 | ? error.response.data.message 145 | : error.message; 146 | if (message === "Not authorized, token failed") { 147 | dispatch(logout()); 148 | } 149 | dispatch({ 150 | type: USER_UPDATE_PROFILE_FAIL, 151 | payload: message, 152 | }); 153 | } 154 | }; 155 | -------------------------------------------------------------------------------- /client frontend/src/screens/CartScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import Header from "./../components/Header"; 3 | import { Link } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { addToCart, removefromcart } from "./../Redux/Actions/cartActions"; 6 | 7 | const CartScreen = ({ match, location, history }) => { 8 | window.scrollTo(0, 0); 9 | const dispatch = useDispatch(); 10 | const productId = match.params.id; 11 | const qty = location.search ? Number(location.search.split("=")[1]) : 1; 12 | 13 | const cart = useSelector((state) => state.cart); 14 | const { cartItems } = cart; 15 | 16 | const total = cartItems.reduce((a, i) => a + i.qty * i.price, 0).toFixed(2); 17 | 18 | useEffect(() => { 19 | if (productId) { 20 | dispatch(addToCart(productId, qty)); 21 | } 22 | }, [dispatch, productId, qty]); 23 | 24 | const checkOutHandler = () => { 25 | history.push("/login?redirect=shipping"); 26 | }; 27 | 28 | const removeFromCartHandle = (id) => { 29 | dispatch(removefromcart(id)); 30 | }; 31 | return ( 32 | <> 33 |
34 | {/* Cart */} 35 |
36 | {cartItems.length === 0 ? ( 37 |
38 | Your cart is empty 39 | 46 | SHOPPING NOW 47 | 48 |
49 | ) : ( 50 | <> 51 |
52 | Total Cart Products 53 | 54 | ({cartItems.length}) 55 | 56 |
57 | {/* cartiterm */} 58 | {cartItems.map((item) => ( 59 |
60 |
removeFromCartHandle(item.product)} 62 | className="remove-button d-flex justify-content-center align-items-center" 63 | > 64 | 65 |
66 |
67 | {item.name} 68 |
69 |
70 | 71 |

{item.name}

72 | 73 |
74 |
75 |
QUANTITY
76 | 88 |
89 |
90 |
PRICE
91 |

${item.price}

92 |
93 |
94 | ))} 95 | 96 | {/* End of cart iterms */} 97 |
98 | total: 99 | ${total} 100 |
101 |
102 |
103 | 104 | 105 | 106 | {total > 0 && ( 107 |
108 | 109 |
110 | )} 111 |
112 | 113 | )} 114 |
115 | 116 | ); 117 | }; 118 | 119 | export default CartScreen; 120 | -------------------------------------------------------------------------------- /server/Routes/ProductRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import asyncHandler from "express-async-handler"; 3 | import Product from "./../Models/ProductModel.js"; 4 | import { admin, protect } from "./../Middleware/AuthMiddleware.js"; 5 | 6 | const productRoute = express.Router(); 7 | 8 | // GET ALL PRODUCT 9 | productRoute.get( 10 | "/", 11 | asyncHandler(async (req, res) => { 12 | const pageSize = 12; 13 | const page = Number(req.query.pageNumber) || 1; 14 | const keyword = req.query.keyword 15 | ? { 16 | name: { 17 | $regex: req.query.keyword, 18 | $options: "i", 19 | }, 20 | } 21 | : {}; 22 | const count = await Product.countDocuments({ ...keyword }); 23 | const products = await Product.find({ ...keyword }) 24 | .limit(pageSize) 25 | .skip(pageSize * (page - 1)) 26 | .sort({ _id: -1 }); 27 | res.json({ products, page, pages: Math.ceil(count / pageSize) }); 28 | }) 29 | ); 30 | 31 | // ADMIN GET ALL PRODUCT WITHOUT SEARCH AND PEGINATION 32 | productRoute.get( 33 | "/all", 34 | protect, 35 | admin, 36 | asyncHandler(async (req, res) => { 37 | const products = await Product.find({}).sort({ _id: -1 }); 38 | res.json(products); 39 | }) 40 | ); 41 | 42 | // GET SINGLE PRODUCT 43 | productRoute.get( 44 | "/:id", 45 | asyncHandler(async (req, res) => { 46 | const product = await Product.findById(req.params.id); 47 | if (product) { 48 | res.json(product); 49 | } else { 50 | res.status(404); 51 | throw new Error("Product not Found"); 52 | } 53 | }) 54 | ); 55 | 56 | // PRODUCT REVIEW 57 | productRoute.post( 58 | "/:id/review", 59 | protect, 60 | asyncHandler(async (req, res) => { 61 | const { rating, comment } = req.body; 62 | const product = await Product.findById(req.params.id); 63 | 64 | if (product) { 65 | const alreadyReviewed = product.reviews.find( 66 | (r) => r.user.toString() === req.user._id.toString() 67 | ); 68 | if (alreadyReviewed) { 69 | res.status(400); 70 | throw new Error("Product already Reviewed"); 71 | } 72 | const review = { 73 | name: req.user.name, 74 | rating: Number(rating), 75 | comment, 76 | user: req.user._id, 77 | }; 78 | 79 | product.reviews.push(review); 80 | product.numReviews = product.reviews.length; 81 | product.rating = 82 | product.reviews.reduce((acc, item) => item.rating + acc, 0) / 83 | product.reviews.length; 84 | 85 | await product.save(); 86 | res.status(201).json({ message: "Reviewed Added" }); 87 | } else { 88 | res.status(404); 89 | throw new Error("Product not Found"); 90 | } 91 | }) 92 | ); 93 | 94 | // DELETE PRODUCT 95 | productRoute.delete( 96 | "/:id", 97 | protect, 98 | admin, 99 | asyncHandler(async (req, res) => { 100 | const product = await Product.findById(req.params.id); 101 | if (product) { 102 | await product.remove(); 103 | res.json({ message: "Product deleted" }); 104 | } else { 105 | res.status(404); 106 | throw new Error("Product not Found"); 107 | } 108 | }) 109 | ); 110 | 111 | // CREATE PRODUCT 112 | productRoute.post( 113 | "/", 114 | protect, 115 | admin, 116 | asyncHandler(async (req, res) => { 117 | const { name, price, description, image, countInStock } = req.body; 118 | const productExist = await Product.findOne({ name }); 119 | if (productExist) { 120 | res.status(400); 121 | throw new Error("Product name already exist"); 122 | } else { 123 | const product = new Product({ 124 | name, 125 | price, 126 | description, 127 | image, 128 | countInStock, 129 | user: req.user._id, 130 | }); 131 | if (product) { 132 | const createdproduct = await product.save(); 133 | res.status(201).json(createdproduct); 134 | } else { 135 | res.status(400); 136 | throw new Error("Invalid product data"); 137 | } 138 | } 139 | }) 140 | ); 141 | 142 | // UPDATE PRODUCT 143 | productRoute.put( 144 | "/:id", 145 | protect, 146 | admin, 147 | asyncHandler(async (req, res) => { 148 | const { name, price, description, image, countInStock } = req.body; 149 | const product = await Product.findById(req.params.id); 150 | if (product) { 151 | product.name = name || product.name; 152 | product.price = price || product.price; 153 | product.description = description || product.description; 154 | product.image = image || product.image; 155 | product.countInStock = countInStock || product.countInStock; 156 | 157 | const updatedProduct = await product.save(); 158 | res.json(updatedProduct); 159 | } else { 160 | res.status(404); 161 | throw new Error("Product not found"); 162 | } 163 | }) 164 | ); 165 | export default productRoute; 166 | -------------------------------------------------------------------------------- /dashboard/src/Redux/Actions/ProductActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | PRODUCT_CREATE_FAIL, 3 | PRODUCT_CREATE_REQUEST, 4 | PRODUCT_CREATE_SUCCESS, 5 | PRODUCT_DELETE_FAIL, 6 | PRODUCT_DELETE_REQUEST, 7 | PRODUCT_DELETE_SUCCESS, 8 | PRODUCT_EDIT_FAIL, 9 | PRODUCT_EDIT_REQUEST, 10 | PRODUCT_EDIT_SUCCESS, 11 | PRODUCT_LIST_FAIL, 12 | PRODUCT_LIST_REQUEST, 13 | PRODUCT_LIST_SUCCESS, 14 | PRODUCT_UPDATE_FAIL, 15 | PRODUCT_UPDATE_REQUEST, 16 | PRODUCT_UPDATE_SUCCESS, 17 | } from "../Constants/ProductConstants"; 18 | import axios from "axios"; 19 | import { logout } from "./userActions"; 20 | 21 | export const listProducts = () => async (dispatch, getState) => { 22 | try { 23 | dispatch({ type: PRODUCT_LIST_REQUEST }); 24 | 25 | const { 26 | userLogin: { userInfo }, 27 | } = getState(); 28 | 29 | const config = { 30 | headers: { 31 | Authorization: `Bearer ${userInfo.token}`, 32 | }, 33 | }; 34 | 35 | const { data } = await axios.get(`/api/products/all`, config); 36 | 37 | dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data }); 38 | } catch (error) { 39 | const message = 40 | error.response && error.response.data.message 41 | ? error.response.data.message 42 | : error.message; 43 | if (message === "Not authorized, token failed") { 44 | dispatch(logout()); 45 | } 46 | dispatch({ 47 | type: PRODUCT_LIST_FAIL, 48 | payload: message, 49 | }); 50 | } 51 | }; 52 | 53 | // DELETE PRODUCT 54 | export const deleteProduct = (id) => async (dispatch, getState) => { 55 | try { 56 | dispatch({ type: PRODUCT_DELETE_REQUEST }); 57 | 58 | const { 59 | userLogin: { userInfo }, 60 | } = getState(); 61 | 62 | const config = { 63 | headers: { 64 | Authorization: `Bearer ${userInfo.token}`, 65 | }, 66 | }; 67 | 68 | await axios.delete(`/api/products/${id}`, config); 69 | 70 | dispatch({ type: PRODUCT_DELETE_SUCCESS }); 71 | } catch (error) { 72 | const message = 73 | error.response && error.response.data.message 74 | ? error.response.data.message 75 | : error.message; 76 | if (message === "Not authorized, token failed") { 77 | dispatch(logout()); 78 | } 79 | dispatch({ 80 | type: PRODUCT_DELETE_FAIL, 81 | payload: message, 82 | }); 83 | } 84 | }; 85 | 86 | // CREATE PRODUCT 87 | export const createProduct = 88 | (name, price, description, image, countInStock) => 89 | async (dispatch, getState) => { 90 | try { 91 | dispatch({ type: PRODUCT_CREATE_REQUEST }); 92 | 93 | const { 94 | userLogin: { userInfo }, 95 | } = getState(); 96 | 97 | const config = { 98 | headers: { 99 | Authorization: `Bearer ${userInfo.token}`, 100 | }, 101 | }; 102 | 103 | const { data } = await axios.post( 104 | `/api/products/`, 105 | { name, price, description, image, countInStock }, 106 | config 107 | ); 108 | 109 | dispatch({ type: PRODUCT_CREATE_SUCCESS, payload: data }); 110 | } catch (error) { 111 | const message = 112 | error.response && error.response.data.message 113 | ? error.response.data.message 114 | : error.message; 115 | if (message === "Not authorized, token failed") { 116 | dispatch(logout()); 117 | } 118 | dispatch({ 119 | type: PRODUCT_CREATE_FAIL, 120 | payload: message, 121 | }); 122 | } 123 | }; 124 | 125 | // EDIT PRODUCT 126 | export const editProduct = (id) => async (dispatch) => { 127 | try { 128 | dispatch({ type: PRODUCT_EDIT_REQUEST }); 129 | const { data } = await axios.get(`/api/products/${id}`); 130 | dispatch({ type: PRODUCT_EDIT_SUCCESS, payload: data }); 131 | } catch (error) { 132 | const message = 133 | error.response && error.response.data.message 134 | ? error.response.data.message 135 | : error.message; 136 | if (message === "Not authorized, token failed") { 137 | dispatch(logout()); 138 | } 139 | dispatch({ 140 | type: PRODUCT_EDIT_FAIL, 141 | payload: message, 142 | }); 143 | } 144 | }; 145 | 146 | // UPDATE PRODUCT 147 | export const updateProduct = (product) => async (dispatch, getState) => { 148 | try { 149 | dispatch({ type: PRODUCT_UPDATE_REQUEST }); 150 | 151 | const { 152 | userLogin: { userInfo }, 153 | } = getState(); 154 | 155 | const config = { 156 | headers: { 157 | "Content-Type": "application/json", 158 | Authorization: `Bearer ${userInfo.token}`, 159 | }, 160 | }; 161 | 162 | const { data } = await axios.put( 163 | `/api/products/${product._id}`, 164 | product, 165 | config 166 | ); 167 | 168 | dispatch({ type: PRODUCT_UPDATE_SUCCESS, payload: data }); 169 | dispatch({ type: PRODUCT_EDIT_SUCCESS, payload: data }); 170 | } catch (error) { 171 | const message = 172 | error.response && error.response.data.message 173 | ? error.response.data.message 174 | : error.message; 175 | if (message === "Not authorized, token failed") { 176 | dispatch(logout()); 177 | } 178 | dispatch({ 179 | type: PRODUCT_UPDATE_FAIL, 180 | payload: message, 181 | }); 182 | } 183 | }; 184 | --------------------------------------------------------------------------------