├── frontend ├── src │ ├── App.css │ ├── index.css │ ├── components │ │ ├── shared │ │ │ ├── Message.jsx │ │ │ ├── FromContainer.jsx │ │ │ ├── Loader.jsx │ │ │ └── CheckoutStep.jsx │ │ ├── footer.jsx │ │ ├── Rating.jsx │ │ └── Header.jsx │ ├── setupTests.js │ ├── constants │ │ ├── cartConstant.js │ │ ├── productConstant.js │ │ ├── orderConstant.js │ │ └── userContants.js │ ├── App.test.js │ ├── reportWebVitals.js │ ├── index.js │ ├── screens │ │ ├── ProductScreen.jsx │ │ ├── HomeScreen.jsx │ │ ├── PaymentScreen.jsx │ │ ├── LoginScreen.jsx │ │ ├── ShippingScreen.jsx │ │ ├── ProductDetails.jsx │ │ ├── RegisterScreen.jsx │ │ ├── CartScreen.jsx │ │ ├── PlaceOrderScreen.jsx │ │ ├── ProfileScreen.jsx │ │ └── OrderScreen.jsx │ ├── reducers │ │ ├── productReducer.js │ │ ├── cartReducer.js │ │ ├── userReducers.js │ │ └── orderReducer.js │ ├── actions │ │ ├── productActions.js │ │ ├── cartAction.js │ │ ├── orderAction.js │ │ └── userAction.js │ ├── App.js │ ├── store.js │ ├── logo.svg │ └── products.js ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── images │ │ ├── alexa.jpg │ │ ├── camera.jpg │ │ ├── mouse.jpg │ │ ├── boatHeadfone.jpg │ │ ├── micromaxInB.jpg │ │ └── playstation.jpg │ ├── manifest.json │ └── index.html ├── package.json ├── README.md └── .eslintcache ├── backend ├── utils │ └── generateToken.js ├── middlewares │ ├── errorMiddleware.js │ └── authMiddleware.js ├── routes │ ├── productsRoute.js │ ├── orderRoute.js │ └── UsersRoute.js ├── data │ ├── users.js │ ├── products.js │ └── books.js ├── config │ └── config.js ├── controllers │ ├── productsController.js │ ├── orderController.js │ └── usersController.js ├── models │ ├── UserModel.js │ ├── ProductModel.js │ └── OrderModel.js ├── server.js └── seeder.js ├── README.md ├── .gitignore └── package.json /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | main { 2 | min-height: 80vh; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/images/alexa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/alexa.jpg -------------------------------------------------------------------------------- /frontend/public/images/camera.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/camera.jpg -------------------------------------------------------------------------------- /frontend/public/images/mouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/mouse.jpg -------------------------------------------------------------------------------- /frontend/public/images/boatHeadfone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/boatHeadfone.jpg -------------------------------------------------------------------------------- /frontend/public/images/micromaxInB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/micromaxInB.jpg -------------------------------------------------------------------------------- /frontend/public/images/playstation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techinfo-youtube/ecommerce-mern-project-code/HEAD/frontend/public/images/playstation.jpg -------------------------------------------------------------------------------- /backend/utils/generateToken.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | const generateToken = (id) => { 4 | return jwt.sign({ id }, process.env.JWT_KEY, { 5 | expiresIn: "15d", 6 | }); 7 | }; 8 | 9 | module.exports = generateToken; 10 | -------------------------------------------------------------------------------- /frontend/src/components/shared/Message.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Alert } from "react-bootstrap"; 3 | 4 | const Message = ({ variant, children }) => { 5 | return {children}; 6 | }; 7 | 8 | export default Message; 9 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/src/constants/cartConstant.js: -------------------------------------------------------------------------------- 1 | export const CART_ADD_ITEM = "CART_ADD_ITEM"; 2 | export const CART_REMOVE_ITEM = "CART_REMOVE_ITEM"; 3 | export const CART_SAVE_SHIPPING_ADDRESS = "CART_SAVE_SHIPPING_ADDRESS"; 4 | export const CART_SAVE_PAYMENT_METHOD = "CART_SAVE_PAYMENT_METHOD"; 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ecommerce-mern-project-code 2 | complete ecommerce app project 3 | 4 | @ 5 | # watch videos on youtube channel 6 | https://youtube.com/playlist?list=PLuHGmgpyHfRw6vj1vd_Pf0Zjd2muTvjI5 7 | 8 | # you can fork or zip 9 | # please give credit add our channel name or channel link 10 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /backend/middlewares/errorMiddleware.js: -------------------------------------------------------------------------------- 1 | const errorHandler = (err, req, res, next) => { 2 | const statusCode = res.statusCode === 200 ? 500 : res.statusCode; 3 | res.status(statusCode); 4 | res.json({ 5 | message: err.message, 6 | stack: process.env.NODE_ENV === "production" ? null : err.stack, 7 | }); 8 | }; 9 | module.exports = { errorHandler }; 10 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /backend/routes/productsRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | getProduct, 4 | getProducts, 5 | } = require("../controllers/productsController"); 6 | const router = express.Router(); 7 | 8 | //GET ROUTE FOR ALL PRODUCTS 9 | router.route("/products").get(getProducts); 10 | 11 | //GET ROUTE FOR SINGLE PRODUCT 12 | router.route("/products/:id").get(getProduct); 13 | 14 | module.exports = router; 15 | -------------------------------------------------------------------------------- /frontend/src/constants/productConstant.js: -------------------------------------------------------------------------------- 1 | export const PRODUCT_LIST_REQUEST = "PRODUCT_LIST_REQUEST"; 2 | export const PRODUCT_LIST_SUCCESS = "PRODUCT_LIST_SUCCESS"; 3 | export const PRODUCT_LIST_FAILS = "PRODUCT_LIST_FAILS"; 4 | 5 | export const PRODUCT_DETAILS_REQUEST = "PRODUCT_DETAILS_REQUEST"; 6 | export const PRODUCT_DETAILS_SUCCESS = "PRODUCT_DETAILS_SUCCESS"; 7 | export const PRODUCT_DETAILS_FAILS = "PRODUCT_DETAILS_FAILS"; 8 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /frontend/src/components/shared/FromContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Container, Row, Col } from "react-bootstrap"; 4 | 5 | const FromContainer = ({ children }) => { 6 | return ( 7 | 8 | 9 | 10 | {children} 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default FromContainer; 18 | -------------------------------------------------------------------------------- /frontend/src/components/footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Container, Row, Col } from "react-bootstrap"; 3 | const footer = () => { 4 | return ( 5 | <> 6 | 15 | 16 | ); 17 | }; 18 | 19 | export default footer; 20 | -------------------------------------------------------------------------------- /backend/data/users.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcryptjs"); 2 | const users = [ 3 | { 4 | name: "admin", 5 | email: "admin@admin.com", 6 | password: bcrypt.hashSync("123456", 10), 7 | isAdmin: true, 8 | }, 9 | { 10 | name: "techinfoyt", 11 | email: "techinfoyt@xyz.com", 12 | password: bcrypt.hashSync("123456", 10), 13 | }, 14 | { 15 | name: "user", 16 | email: "user@user.com", 17 | password: bcrypt.hashSync("123456", 10), 18 | }, 19 | ]; 20 | module.exports = users; 21 | -------------------------------------------------------------------------------- /frontend/src/components/shared/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Spinner } from "react-bootstrap"; 3 | 4 | const Loader = () => { 5 | return ( 6 | 16 | Loading... 17 | 18 | ); 19 | }; 20 | 21 | export default Loader; 22 | -------------------------------------------------------------------------------- /backend/config/config.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | require("colors"); 3 | const connectDb = async () => { 4 | try { 5 | const conn = await mongoose.connect(process.env.MONGO_URI, { 6 | useUnifiedTopology: true, 7 | useNewUrlParser: true, 8 | useCreateIndex: true, 9 | }); 10 | console.log(`Mongodb Connected ${conn.connection.host}`.yellow); 11 | } catch (error) { 12 | console.error(`Error : ${error.message}`.red); 13 | process.exit(1); 14 | } 15 | }; 16 | 17 | module.exports = connectDb; 18 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /backend/routes/orderRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | addOrderItem, 4 | getOrderById, 5 | updateOrderToPaid, 6 | getMyOrders, 7 | } = require("../controllers/orderController"); 8 | const { protect } = require("../middlewares/authMiddleware"); 9 | const router = express.Router(); 10 | 11 | //getUserOrder 12 | router.route("/myorders").get(protect, getMyOrders); 13 | //get order by id 14 | router.route("/:id").get(protect, getOrderById); 15 | //craete new order 16 | router.route("/").post(protect, addOrderItem); 17 | //update order 18 | router.route("/:id/pay").put(protect, updateOrderToPaid); 19 | module.exports = router; 20 | -------------------------------------------------------------------------------- /backend/controllers/productsController.js: -------------------------------------------------------------------------------- 1 | const Product = require("../models/ProductModel"); 2 | const asyncHandler = require("express-async-handler"); 3 | 4 | const getProducts = asyncHandler(async (req, res) => { 5 | const products = await Product.find({}); 6 | // throw new Error("Some Eror"); 7 | res.json(products); 8 | }); 9 | 10 | const getProduct = asyncHandler(async (req, res) => { 11 | const product = await Product.findById(req.params.id); 12 | if (product) { 13 | res.json(product); 14 | } else { 15 | res.status(404).json({ message: "Product Not Found" }); 16 | } 17 | }); 18 | 19 | module.exports = { getProducts, getProduct }; 20 | -------------------------------------------------------------------------------- /backend/routes/UsersRoute.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | authController, 4 | getUserProfile, 5 | registerUser, 6 | updateUserProfile, 7 | } = require("../controllers/usersController"); 8 | const { protect } = require("../middlewares/authMiddleware"); 9 | 10 | const router = express.Router(); 11 | 12 | //user registration 13 | router.route("/").post(registerUser); 14 | 15 | //post email and password auth 16 | router.post("/login", authController); 17 | 18 | //get user profile Private Route 19 | router 20 | .route("/profile") 21 | .get(protect, getUserProfile) 22 | .put(protect, updateUserProfile); 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import store from "./store"; 5 | import "./index.css"; 6 | import App from "./App"; 7 | import reportWebVitals from "./reportWebVitals"; 8 | import "./bootstrap.min.css"; 9 | 10 | ReactDOM.render( 11 | 12 | 13 | , 14 | document.getElementById("root") 15 | ); 16 | 17 | // If you want to start measuring performance in your app, pass a function 18 | // to log results (for example: reportWebVitals(console.log)) 19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 20 | reportWebVitals(); 21 | -------------------------------------------------------------------------------- /frontend/src/constants/orderConstant.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 | 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_PAY_REQUEST = "ORDER_PAY_REQUEST"; 10 | export const ORDER_PAY_SUCCESS = "ORDER_PAY_SUCCESS"; 11 | export const ORDER_PAY_FAIL = "ORDER_PAY_FAIL"; 12 | export const ORDER_PAY_RESET = "ORDER_PAY_RESET"; 13 | 14 | export const ORDER_LIST_MY_REQUEST = "ORDER_LIST_MY_REQUEST"; 15 | export const ORDER_LIST_MY_SUCCESS = "ORDER_LIST_MY_SUCCESS"; 16 | export const ORDER_LIST_MY_FAIL = "ORDER_LIST_MY_FAIL"; 17 | export const ORDER_LIST_MY_RESET = "ORDER_LIST_MY_RESET"; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shopping-app", 3 | "version": "1.0.0", 4 | "description": "mern app", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "server": "nodemon backend/server", 9 | "client": "npm start --prefix frontend", 10 | "dev": "concurrently \"npm run server\" \"npm run client\" ", 11 | "data:import": "node backend/seeder.js", 12 | "data:destroy": "node backend/seeder.js -d" 13 | }, 14 | "author": "techinfoyt", 15 | "license": "MIT", 16 | "dependencies": { 17 | "bcryptjs": "^2.4.3", 18 | "colors": "^1.4.0", 19 | "concurrently": "^5.3.0", 20 | "dotenv": "^8.2.0", 21 | "express": "^4.17.1", 22 | "express-async-handler": "^1.1.4", 23 | "jsonwebtoken": "^8.5.1", 24 | "mongodb": "^2.2.16", 25 | "mongoose": "^5.11.8", 26 | "nodemon": "^2.0.6" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/middlewares/authMiddleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const User = require("../models/UserModel"); 3 | const asyncHandler = require("express-async-handler"); 4 | 5 | const protect = asyncHandler(async (req, res, next) => { 6 | let token; 7 | if ( 8 | req.headers.authorization && 9 | req.headers.authorization.startsWith("Bearer") 10 | ) 11 | try { 12 | token = req.headers.authorization.split(" ")[1]; 13 | const decode = jwt.verify(token, process.env.JWT_KEY); 14 | req.user = await User.findById(decode.id).select("-password"); 15 | next(); 16 | } catch (error) { 17 | console.error(error); 18 | res.status(401); 19 | throw new Error("Not Authorized , Token failed"); 20 | } 21 | if (!token) { 22 | res.status(401); 23 | throw new Error("Not Authorized, not token"); 24 | } 25 | }); 26 | module.exports = { protect }; 27 | -------------------------------------------------------------------------------- /frontend/src/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 | -------------------------------------------------------------------------------- /frontend/src/screens/ProductScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Card } from "react-bootstrap"; 3 | import Rating from "../components/Rating"; 4 | import { Link } from "react-router-dom"; 5 | 6 | const ProductScreen = ({ product }) => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {product.name} 17 | 18 | 19 | 20 | 24 | 25 | $ {product.price} 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default ProductScreen; 33 | -------------------------------------------------------------------------------- /backend/models/UserModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const bcrypt = require("bcryptjs"); 3 | const userSchema = mongoose.Schema( 4 | { 5 | name: { 6 | type: String, 7 | required: true, 8 | }, 9 | email: { 10 | type: String, 11 | required: true, 12 | }, 13 | password: { 14 | type: String, 15 | required: true, 16 | }, 17 | isAdmin: { 18 | type: Boolean, 19 | required: true, 20 | default: false, 21 | }, 22 | }, 23 | { 24 | timestamps: true, 25 | } 26 | ); 27 | 28 | userSchema.methods.matchPassword = async function (enterPassword) { 29 | return await bcrypt.compare(enterPassword, this.password); 30 | }; 31 | 32 | //middlware for password 33 | userSchema.pre("save", async function (next) { 34 | if (!this.isModified("password")) { 35 | next(); 36 | } 37 | const salt = await bcrypt.genSalt(10); 38 | this.password = await bcrypt.hash(this.password, salt); 39 | }); 40 | 41 | const User = mongoose.model("User", userSchema); 42 | module.exports = User; 43 | -------------------------------------------------------------------------------- /frontend/src/screens/HomeScreen.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { listProducts } from "../actions/productActions"; 4 | import { Row, Col } from "react-bootstrap"; 5 | import ProductScreen from "./ProductScreen"; 6 | import Loader from "../components/shared/Loader"; 7 | import Message from "../components/shared/Message"; 8 | const HomeScreen = () => { 9 | const dispatch = useDispatch(); 10 | const productList = useSelector((state) => state.productList); 11 | const { loading, error, products } = productList; 12 | 13 | useEffect(() => { 14 | dispatch(listProducts()); 15 | }, [dispatch]); 16 | 17 | return ( 18 | <> 19 | {loading ? ( 20 | 21 | ) : error ? ( 22 | {error} 23 | ) : ( 24 | 25 | {products.map((product) => ( 26 | 27 | 28 | 29 | ))} 30 | 31 | )} 32 | 33 | ); 34 | }; 35 | 36 | export default HomeScreen; 37 | -------------------------------------------------------------------------------- /frontend/src/reducers/productReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | PRODUCT_LIST_REQUEST, 3 | PRODUCT_LIST_SUCCESS, 4 | PRODUCT_LIST_FAILS, 5 | PRODUCT_DETAILS_REQUEST, 6 | PRODUCT_DETAILS_SUCCESS, 7 | PRODUCT_DETAILS_FAILS, 8 | } from "../constants/productConstant"; 9 | 10 | export const productListReducer = (state = { products: [] }, action) => { 11 | switch (action.type) { 12 | case PRODUCT_LIST_REQUEST: 13 | return { loading: true, products: [] }; 14 | case PRODUCT_LIST_SUCCESS: 15 | return { loading: false, products: action.payload }; 16 | case PRODUCT_LIST_FAILS: 17 | return { loading: false, error: action.payload }; 18 | default: 19 | return state; 20 | } 21 | }; 22 | 23 | export const productDetailsReducer = ( 24 | state = { product: { reviews: [] } }, 25 | action 26 | ) => { 27 | switch (action.type) { 28 | case PRODUCT_DETAILS_REQUEST: 29 | return { loading: true, ...state }; 30 | case PRODUCT_DETAILS_SUCCESS: 31 | return { loading: false, product: action.payload }; 32 | case PRODUCT_DETAILS_FAILS: 33 | return { loading: false, error: action.payload }; 34 | default: 35 | return state; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { errorHandler } = require("./middlewares/errorMiddleware"); 3 | require("colors"); 4 | const products = require("./data/products"); 5 | const dotenv = require("dotenv"); 6 | const connectDb = require("./config/config"); 7 | const productRoutes = require("./routes/productsRoute"); 8 | const usersRoutes = require("./routes/UsersRoute"); 9 | const orderRoutes = require("./routes/orderRoute"); 10 | 11 | dotenv.config(); 12 | //connecting to mongodb database 13 | connectDb(); 14 | const app = express(); 15 | //middleware bodyparser 16 | app.use(express.json()); 17 | 18 | //dotenv config 19 | app.get("/", (req, res) => { 20 | res.send("

Welcome to Node Server

"); 21 | }); 22 | 23 | app.use("/api", productRoutes); 24 | app.use("/api/users", usersRoutes); 25 | app.use("/api/orders", orderRoutes); 26 | app.get("/api/config/paypal", (req, res) => { 27 | res.send(process.env.PAYPAL_CLIENT_ID); 28 | }); 29 | 30 | app.use(errorHandler); 31 | 32 | const PORT = 8080; 33 | app.listen(process.env.PORT || PORT, () => { 34 | console.log( 35 | `Server Running in ${process.env.NODE_ENV} Mode on Port ${process.env.PORT}` 36 | .inverse 37 | ); 38 | }); 39 | -------------------------------------------------------------------------------- /frontend/src/reducers/cartReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | CART_ADD_ITEM, 3 | CART_REMOVE_ITEM, 4 | CART_SAVE_PAYMENT_METHOD, 5 | CART_SAVE_SHIPPING_ADDRESS, 6 | } from "../constants/cartConstant"; 7 | 8 | export const cartReducer = (state = { cartItems: [] }, action) => { 9 | switch (action.type) { 10 | case CART_ADD_ITEM: 11 | const item = action.payload; 12 | const existItem = state.cartItems.find((x) => x.product === item.product); 13 | if (existItem) { 14 | return { 15 | ...state, 16 | cartItems: state.cartItems.map((x) => 17 | x.product === existItem.product ? item : x 18 | ), 19 | }; 20 | } else { 21 | return { 22 | ...state, 23 | cartItems: [...state.cartItems, item], 24 | }; 25 | } 26 | case CART_REMOVE_ITEM: 27 | return { 28 | ...state, 29 | cartItems: state.cartItems.filter((x) => x.product !== action.payload), 30 | }; 31 | 32 | case CART_SAVE_SHIPPING_ADDRESS: 33 | return { ...state, shippingAddress: action.payload }; 34 | case CART_SAVE_PAYMENT_METHOD: 35 | return { 36 | ...state, 37 | paymentMethod: action.payload, 38 | }; 39 | default: 40 | return state; 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /frontend/src/actions/productActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { 3 | PRODUCT_LIST_REQUEST, 4 | PRODUCT_LIST_SUCCESS, 5 | PRODUCT_LIST_FAILS, 6 | PRODUCT_DETAILS_REQUEST, 7 | PRODUCT_DETAILS_SUCCESS, 8 | PRODUCT_DETAILS_FAILS, 9 | } from "../constants/productConstant"; 10 | 11 | export const listProducts = () => async (dispatch) => { 12 | try { 13 | dispatch({ type: PRODUCT_LIST_REQUEST }); 14 | const { data } = await axios.get("/api/products"); 15 | dispatch({ 16 | type: PRODUCT_LIST_SUCCESS, 17 | payload: data, 18 | }); 19 | } catch (error) { 20 | dispatch({ 21 | type: PRODUCT_LIST_FAILS, 22 | payload: 23 | error.response && error.response.data.message 24 | ? error.response.data.message 25 | : error.message, 26 | }); 27 | } 28 | }; 29 | 30 | export const listProductDetails = (id) => async (dispatch) => { 31 | try { 32 | dispatch({ type: PRODUCT_DETAILS_REQUEST }); 33 | const { data } = await axios.get(`/api/products/${id}`); 34 | dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data }); 35 | } catch (error) { 36 | dispatch({ 37 | type: PRODUCT_DETAILS_FAILS, 38 | payload: 39 | error.response && error.response.data.message 40 | ? error.response.data.message 41 | : error.message, 42 | }); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "proxy": "http://localhost:8080", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^5.11.4", 8 | "@testing-library/react": "^11.1.0", 9 | "@testing-library/user-event": "^12.1.10", 10 | "axios": "^0.21.1", 11 | "bootstrap": "^4.5.3", 12 | "react": "^17.0.1", 13 | "react-bootstrap": "^1.4.0", 14 | "react-dom": "^17.0.1", 15 | "react-paypal-button-v2": "^2.6.3", 16 | "react-redux": "^7.2.2", 17 | "react-router-bootstrap": "^0.25.0", 18 | "react-router-dom": "^5.2.0", 19 | "react-scripts": "4.0.1", 20 | "redux": "^4.0.5", 21 | "redux-devtools-extension": "^2.13.9", 22 | "redux-thunk": "^2.3.0", 23 | "web-vitals": "^0.2.4" 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 | -------------------------------------------------------------------------------- /frontend/src/actions/cartAction.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/cartConstant"; 8 | 9 | export const addToCart = (id, qty) => async (dispatch, getState) => { 10 | const { data } = await axios.get(`/api/products/${id}`); 11 | dispatch({ 12 | type: CART_ADD_ITEM, 13 | payload: { 14 | product: data._id, 15 | name: data.name, 16 | image: data.image, 17 | price: data.price, 18 | countInStock: data.countInStock, 19 | qty, 20 | }, 21 | }); 22 | 23 | localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems)); 24 | }; 25 | 26 | export const removeFromCart = (id) => (dispatch, getState) => { 27 | dispatch({ 28 | type: CART_REMOVE_ITEM, 29 | payload: id, 30 | }); 31 | 32 | localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems)); 33 | }; 34 | 35 | export const saveShippingAddress = (data) => (dispatch) => { 36 | dispatch({ type: CART_SAVE_SHIPPING_ADDRESS, payload: data }); 37 | localStorage.setItem("shippingAddress", JSON.stringify(data)); 38 | }; 39 | 40 | export const savePaymentMethod = (data) => (dispatch) => { 41 | dispatch({ 42 | type: CART_SAVE_PAYMENT_METHOD, 43 | payload: data, 44 | }); 45 | localStorage.setItem("paymentMethod", JSON.stringify(data)); 46 | }; 47 | -------------------------------------------------------------------------------- /backend/seeder.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const dotenv = require("dotenv"); 3 | require("colors"); 4 | const users = require("./data/users"); 5 | const User = require("./models/UserModel"); 6 | const Product = require("./models/ProductModel"); 7 | const Order = require("./models/OrderModel"); 8 | const products = require("./data/products"); 9 | const connectDb = require("./config/config"); 10 | 11 | dotenv.config(); 12 | connectDb(); 13 | 14 | const importData = async () => { 15 | try { 16 | await Order.deleteMany(); 17 | await Product.deleteMany(); 18 | await User.deleteMany(); 19 | const createUser = await User.insertMany(users); 20 | const adminUser = createUser[0]._id; 21 | const sampleData = products.map((product) => { 22 | return { ...product, user: adminUser }; 23 | }); 24 | await Product.insertMany(sampleData); 25 | console.log("Data Imported!!".green.inverse); 26 | process.exit(); 27 | } catch (error) { 28 | console.log(`${error}`.red.inverse); 29 | process.exit(1); 30 | } 31 | }; 32 | 33 | const dataDestory = async () => { 34 | try { 35 | await Order.deleteMany(); 36 | await Product.deleteMany(); 37 | await User.deleteMany(); 38 | console.log("Data Destory".green.inverse); 39 | process.exit(); 40 | } catch (error) { 41 | console.log(`${error}`.red.inverse); 42 | process.exit(1); 43 | } 44 | }; 45 | 46 | if (process.argv[2] === "-d") { 47 | dataDestory(); 48 | } else { 49 | importData(); 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/components/shared/CheckoutStep.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Nav } from "react-bootstrap"; 3 | import { LinkContainer } from "react-router-bootstrap"; 4 | 5 | const CheckoutStep = ({ step1, step2, step3, step4 }) => { 6 | return ( 7 | <> 8 | 46 | 47 | ); 48 | }; 49 | 50 | export default CheckoutStep; 51 | -------------------------------------------------------------------------------- /backend/models/ProductModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const reviewSchema = mongoose.Schema( 4 | { 5 | name: { 6 | type: String, 7 | required: true, 8 | }, 9 | rating: { 10 | type: Number, 11 | required: true, 12 | }, 13 | comment: { 14 | type: String, 15 | required: true, 16 | }, 17 | }, 18 | { timestamps: true } 19 | ); 20 | 21 | const productSchema = mongoose.Schema( 22 | { 23 | user: { 24 | type: mongoose.Schema.Types.ObjectId, 25 | required: true, 26 | ref: "User", 27 | }, 28 | name: { 29 | type: String, 30 | required: true, 31 | }, 32 | image: { 33 | type: String, 34 | required: true, 35 | }, 36 | brand: { 37 | type: String, 38 | required: true, 39 | }, 40 | category: { 41 | type: String, 42 | required: true, 43 | }, 44 | description: { 45 | type: String, 46 | required: true, 47 | }, 48 | reviews: [reviewSchema], 49 | rating: { 50 | type: Number, 51 | required: true, 52 | default: 0, 53 | }, 54 | numReviews: { 55 | type: Number, 56 | required: true, 57 | defualt: 0, 58 | }, 59 | price: { 60 | type: Number, 61 | required: true, 62 | default: 0, 63 | }, 64 | countInStock: { 65 | type: Number, 66 | required: true, 67 | default: 0, 68 | }, 69 | }, 70 | { timestamps: true } 71 | ); 72 | 73 | const Product = mongoose.model("Product", productSchema); 74 | module.exports = Product; 75 | -------------------------------------------------------------------------------- /frontend/src/screens/PaymentScreen.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Form, Button, Col } from "react-bootstrap"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { savePaymentMethod } from "../actions/cartAction"; 5 | import CheckoutStep from "../components/shared/CheckoutStep"; 6 | const PaymentScreen = ({ history }) => { 7 | const cart = useSelector((state) => state.cart); 8 | const { shippingAddress } = cart; 9 | if (!shippingAddress.address) { 10 | history.push("/shipping"); 11 | } 12 | const dispatch = useDispatch(); 13 | const [paymentMethod, setPaymentMethod] = useState("paypal"); 14 | const submitHandler = (e) => { 15 | e.preventDefault(); 16 | dispatch(savePaymentMethod(paymentMethod)); 17 | history.push("/placeorder"); 18 | }; 19 | return ( 20 | <> 21 | 22 |

Payment Method

23 |
24 | 25 | Select Payment Method 26 | 27 | setPaymentMethod(e.target.value)} 34 | > 35 | 36 | 37 | 40 |
41 | 42 | ); 43 | }; 44 | 45 | export default PaymentScreen; 46 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import { Container } from "react-bootstrap"; 2 | import { BrowserRouter as Router, Route } from "react-router-dom"; 3 | 4 | import "./App.css"; 5 | import Footer from "./components/footer"; 6 | import Header from "./components/Header"; 7 | import CartScreen from "./screens/CartScreen"; 8 | import HomeScreen from "./screens/HomeScreen"; 9 | import ProductDetails from "./screens/ProductDetails"; 10 | import LoginScreen from "./screens/LoginScreen"; 11 | import RegisterScreen from "./screens/RegisterScreen"; 12 | import ProfileScreen from "./screens/ProfileScreen"; 13 | import ShippingScreen from "./screens/ShippingScreen"; 14 | import PaymentScreen from "./screens/PaymentScreen"; 15 | import PlaceOrderScreen from "./screens/PlaceOrderScreen"; 16 | import OrderScreen from "./screens/OrderScreen"; 17 | 18 | function App() { 19 | return ( 20 | 21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |