├── .babelrc ├── .gitignore ├── README.md ├── Screenshots ├── add-product.png ├── admin-products.png ├── cart.png ├── main.png ├── main2.png ├── payment.png ├── placeorder.png ├── product-details.png ├── register.png ├── shipping.png └── signin.png ├── backend ├── config.js ├── data.js ├── models │ ├── productModel.js │ └── usermodel.js ├── routes │ ├── productRoute.js │ └── userRoute.js ├── server.js └── util.js ├── frontend ├── .gitignore ├── README.md ├── debug.log ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── images │ │ ├── w1.jpg │ │ ├── w2.jpg │ │ ├── w3.jpg │ │ ├── w4.jpg │ │ ├── wf1.jpg │ │ ├── wf2.jpg │ │ ├── wf3.jpg │ │ └── wf4.jpg │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── Images │ └── logo.png │ ├── Screens │ ├── CartScreen.js │ ├── HomeScreen.js │ ├── PaymentScreen.js │ ├── PlaceOrderScreen.js │ ├── ProductScreen.js │ ├── ProductsScreen.js │ ├── RegisterScreen.js │ ├── ShippingScreen.js │ └── SigninScreen.js │ ├── actions │ ├── CartAction.js │ ├── productActions.js │ └── userAction.js │ ├── components │ ├── CheckoutSteps.js │ └── Corousel.js │ ├── constants │ ├── cartConstants.js │ ├── productConstants.js │ └── userConstants.js │ ├── data.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── reducers │ ├── cartReducers.js │ ├── productReducers.js │ └── userReducer.js │ ├── serviceWorker.js │ ├── setupTests.js │ └── store.js ├── package-lock.json ├── package.json └── template ├── index.html └── style.css /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets":[ 3 | [ 4 | "@babel/preset-env" 5 | ] 6 | ] 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # E-Commerce Website using MERN Stack. 2 | A E-Commerce website developed using ReactJS for the frontend, NodeJs for the backend, MongoDB as database. 3 | 4 | 5 | ## Table of contents 6 | * [Prerequisites](#prerequisites) 7 | * [Requirements](#requirements) 8 | * [Technologies](#technologies) 9 | * [Features](#features) 10 | * [Screenshots](#screenshots) 11 | * [Contact](#contact) 12 | 13 | 14 | ## Prerequisites 15 | - Text Editor ([VS Code](https://code.visualstudio.com/download) , [Atom](https://atom.io/), [Brackets](http://brackets.io/), etc.) 16 | - Node.js and npm - [install here](https://www.npmjs.com/get-npm) 17 | - MongoDB - [install here](https://docs.mongodb.com/manual/installation/) 18 | 19 | 20 | ## Requirements 21 | To run this project, install it locally using npm: 22 | 23 | - git clone git@github.com:suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS.git 24 | ``` 25 | $ cd E-Commerce-Website-using-ReactJS-NodeJS 26 | ``` 27 | - To Run Backend 28 | - open terminal 29 | ``` 30 | $ npm install 31 | $ npm start 32 | ``` 33 | - To Run Frontend 34 | - open new terminal 35 | ``` 36 | $ cd frontend 37 | $ npm install 38 | $ npm start 39 | ``` 40 | 41 | ## Technologies 42 | Project is created with: 43 | * HTML5 and CSS3: Semantic Elements, CSS Grid, Flexbox 44 | * React: Components, Props, Events, Hooks, Router, Axios, React-Bootstrap 45 | * Redux: Store, Reducers, Actions 46 | * Node & Express: Web API, Body Parser, File Upload, JWT 47 | * MongoDB: Mongoose 48 | * Development: ESLint, Babel, Git, Github, 49 | * Deployment: 50 | 51 | ## Features 52 | List of features ready and TODOs for future development 53 | * User Login, Signup, User Authentication. 54 | * Admin Login. 55 | * Add, Edit, Remove Products. (Only Admin) 56 | * Add to Cart. 57 | 58 | To-do list: 59 | * Sorting 60 | * Search 61 | 62 | ## Screenshots 63 | 64 | ### Home Page 65 | This is the home page of e-commerce. It shows a list of products. It also uses React-Bootstrap Corousel for corousel. 66 | 67 | ![Main](Screenshots/main.png) 68 | 69 | ________________________________________________________ 70 | 71 | ### Product Details Page 72 | When the user clicks on a product it takes you to the product details page. 73 | 74 | ![Product Details](Screenshots/product-details.png) 75 | ________________________________________________________ 76 | 77 | ### Cart 78 | Shopping Cart is the heart of any e-commerce website. I have created a user-friendly shopping cart using React and Redux. 79 | 80 | ![Cart](Screenshots/cart.png) 81 | ________________________________________________________ 82 | 83 | ### Register 84 | I have created forms for getting user info and save them in the database. 85 | 86 | ![Register](Screenshots/register.png) 87 | ________________________________________________________ 88 | 89 | ### Sign-In 90 | Sign in page for user to sign in. 91 | 92 | ![SignIn](Screenshots/signin.png) 93 | ________________________________________________________ 94 | 95 | ## Ordering Products 96 | 97 | Sign in page for user to sign in. 98 | 99 | ### Shipping Screen 100 | ![Shipping](Screenshots/shipping.png) 101 | ________________________________________________________ 102 | ### Payment Screen 103 | ![Payment](Screenshots/payment.png) 104 | ________________________________________________________ 105 | ### Place Order Screen 106 | ![Place Order](Screenshots/placeorder.png) 107 | ________________________________________________________ 108 | 109 | ### Admin 110 | Admin should be able to define products and update the count in stock whenever they like. This page is about managing ECommerce products. 111 | 112 | ## View Products (admin) 113 | 114 | ![Admin View Products](Screenshots/admin-products.png) 115 | ________________________________________________________ 116 | 117 | ### Add Products (admin) 118 | 119 | ![Admin Add products](Screenshots/add-product.png) 120 | ________________________________________________________ 121 | 122 | 123 | ## Contact 124 | [Suhas Suhas](https://www.suhassalian.netlify.com/) - feel free to contact me! 125 | -------------------------------------------------------------------------------- /Screenshots/add-product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/add-product.png -------------------------------------------------------------------------------- /Screenshots/admin-products.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/admin-products.png -------------------------------------------------------------------------------- /Screenshots/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/cart.png -------------------------------------------------------------------------------- /Screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/main.png -------------------------------------------------------------------------------- /Screenshots/main2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/main2.png -------------------------------------------------------------------------------- /Screenshots/payment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/payment.png -------------------------------------------------------------------------------- /Screenshots/placeorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/placeorder.png -------------------------------------------------------------------------------- /Screenshots/product-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/product-details.png -------------------------------------------------------------------------------- /Screenshots/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/register.png -------------------------------------------------------------------------------- /Screenshots/shipping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/shipping.png -------------------------------------------------------------------------------- /Screenshots/signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/Screenshots/signin.png -------------------------------------------------------------------------------- /backend/config.js: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | 3 | dotenv.config(); 4 | 5 | export default { 6 | MONGODB_URL: process.env.MONGODB_URL || "mongodb://localhost/amazon", 7 | JWT_SECRET: process.env.JWT_SECRET || "somethingsecret" 8 | }; 9 | -------------------------------------------------------------------------------- /backend/data.js: -------------------------------------------------------------------------------- 1 | // export default { 2 | // products: [ 3 | // { 4 | // _id: "1", 5 | // name: "Nike Men's Regular fit T-Shirt", 6 | // category: "Shirt", 7 | // image: "/images/d1.jpg", 8 | // price: 35, 9 | // brand: " Nike", 10 | // rating: 3.0, 11 | // numReviews: 13, 12 | // countInStock: 6 13 | // }, 14 | // { 15 | // _id: "2", 16 | // name: "Nike Men's Track Pants", 17 | // category: "Pants", 18 | // image: "/images/d2.jpg", 19 | // price: 50, 20 | // brand: " Nike", 21 | // rating: 4.2, 22 | // numReviews: 66, 23 | // countInStock: 8 24 | // }, 25 | // { 26 | // _id: "3", 27 | // name: "Nike Men's Track Pants Gray", 28 | // category: "Pants", 29 | // image: "/images/d3.jpg", 30 | // price: 70, 31 | // brand: " Nike", 32 | // rating: 4, 33 | // numReviews: 8, 34 | // countInStock: 8 35 | // }, 36 | // { 37 | // _id: "4", 38 | // name: "Nike Girl's Plain Regular fit T-Shirt", 39 | // category: "Pants", 40 | // image: "/images/d4.jpg", 41 | // price: 40, 42 | // brand: " Nike", 43 | // rating: 3.5, 44 | // numReviews: 18, 45 | // countInStock: 9 46 | // } 47 | // ] 48 | // }; 49 | -------------------------------------------------------------------------------- /backend/models/productModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const productSchema = new mongoose.Schema({ 4 | name: { type: String, required: true }, 5 | image: { type: String, required: true }, 6 | brand: { type: String, required: true }, 7 | price: { type: Number, default: 0, required: true }, 8 | category: { type: String, required: true }, 9 | countInStock: { type: Number, default: 0, required: true }, 10 | description: { type: String, required: true }, 11 | rating: { type: Number, default: 0, required: true }, 12 | numReviews: { type: Number, default: 0, required: true } 13 | }); 14 | 15 | const productModel = mongoose.model("Product", productSchema); 16 | 17 | export default productModel; 18 | -------------------------------------------------------------------------------- /backend/models/usermodel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSchema = new mongoose.Schema({ 4 | name: { type: String, required: true }, 5 | email: { 6 | type: String, 7 | required: true, 8 | unique: true, 9 | index: true, 10 | dropDups: true 11 | }, 12 | password: { type: String, required: true }, 13 | isAdmin: { type: Boolean, required: true, default: false } 14 | }); 15 | 16 | const userModel = mongoose.model("User", userSchema); 17 | 18 | export default userModel; 19 | -------------------------------------------------------------------------------- /backend/routes/productRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import Product from "../models/productModel"; 3 | import { isAuth, isAdmin } from "../util"; 4 | 5 | const router = express.Router(); 6 | 7 | router.get("/", async (req, res) => { 8 | const products = await Product.find({}); 9 | res.send(products); 10 | }); 11 | router.get("/:id", async (req, res) => { 12 | const product = await Product.findOne({ _id: req.params.id }); 13 | if (product) { 14 | res.send(product); 15 | } else { 16 | res.status(404).send({ message: "404 product not found" }); 17 | } 18 | res.send(product); 19 | }); 20 | 21 | router.post("/", async (req, res) => { 22 | const product = new Product({ 23 | name: req.body.name, 24 | price: req.body.price, 25 | image: req.body.image, 26 | brand: req.body.brand, 27 | category: req.body.category, 28 | countInStock: req.body.countInStock, 29 | description: req.body.description, 30 | rating: req.body.rating, 31 | numReviews: req.body.numReviews 32 | }); 33 | const newProduct = await product.save(); 34 | if (newProduct) { 35 | return res 36 | .status(201) 37 | .send({ message: "New Product Created", data: newProduct }); 38 | } 39 | return res.status(500).send({ message: "Error in Creating Product." }); 40 | }); 41 | 42 | router.put("/:id", async (req, res) => { 43 | const productId = req.params.id; 44 | const product = await Product.findById({ _id: productId }); 45 | if (product) { 46 | product.name = req.body.name; 47 | product.price = req.body.price; 48 | product.image = req.body.image; 49 | product.brand = req.body.brand; 50 | product.category = req.body.category; 51 | product.countInStock = req.body.countInStock; 52 | product.description = req.body.description; 53 | const updatedProduct = await product.save(); 54 | if (updatedProduct) { 55 | return res 56 | .status(200) 57 | .send({ message: "Product Updated", data: updatedProduct }); 58 | } 59 | } 60 | return res.status(500).send({ message: "Error in Updating Product." }); 61 | }); 62 | router.delete("/:id", isAuth, isAdmin, async (req, res) => { 63 | const deletedProduct = await Product.findById(req.params.id); 64 | if (deletedProduct) { 65 | await deletedProduct.remove(); 66 | res.send({ message: "Product Deleted" }); 67 | } else { 68 | res.send("Error in deleting Product."); 69 | } 70 | }); 71 | 72 | export default router; 73 | -------------------------------------------------------------------------------- /backend/routes/userRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import User from "../models/userModel"; 3 | import { getToken } from "../util"; 4 | 5 | const router = express.Router(); 6 | 7 | router.post("/signin", async (req, res) => { 8 | const signinUser = await User.findOne({ 9 | email: req.body.email, 10 | password: req.body.password 11 | }); 12 | if (signinUser) { 13 | res.send({ 14 | _id: signinUser.id, 15 | name: signinUser.name, 16 | email: signinUser.email, 17 | isAdmin: signinUser.isAdmin, 18 | token: getToken(signinUser) 19 | }); 20 | } else { 21 | res.status(401).send({ message: "Invalid Email or Password." }); 22 | } 23 | }); 24 | router.post("/register", async (req, res) => { 25 | const user = new User({ 26 | name: req.body.name, 27 | email: req.body.email, 28 | password: req.body.password 29 | }); 30 | const newUser = await user.save(); 31 | if (newUser) { 32 | res.send({ 33 | _id: newUser.id, 34 | name: newUser.name, 35 | email: newUser.email, 36 | isAdmin: newUser.isAdmin, 37 | token: getToken(newUser) 38 | }); 39 | } else { 40 | res.status(401).send({ message: "Invalid Userdata." }); 41 | } 42 | }); 43 | 44 | router.get("/createadmin", async (req, res) => { 45 | try { 46 | const user = new User({ 47 | name: "suhas", 48 | email: "suhas@gmail.com", 49 | password: "1234", 50 | isAdmin: true 51 | }); 52 | const newUser = await user.save(); 53 | res.send(newUser); 54 | } catch (error) { 55 | res.send({ message: error.message }); 56 | } 57 | }); 58 | 59 | export default router; 60 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import config from "./config"; 3 | import dotenv from "dotenv"; 4 | import mongoose from "mongoose"; 5 | import bodyParser from "body-parser"; 6 | import userRoute from "./routes/userRoute"; 7 | import productRoute from "./routes/productRoute"; 8 | 9 | const path = require("path"); 10 | 11 | dotenv.config(); 12 | 13 | const PORT = process.env.PORT || 5000; 14 | const mongodbUrl = config.MONGODB_URL; 15 | 16 | mongoose 17 | .connect(mongodbUrl, { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true, 20 | useCreateIndex: true 21 | }) 22 | .catch(error => console.log(error.reason)); 23 | 24 | const app = express(); 25 | 26 | app.use(bodyParser.json()); 27 | app.use("/api/users", userRoute); 28 | app.use("/api/products", productRoute); 29 | 30 | // Serve static assests if in production 31 | 32 | if (process.env.NODE_ENV === "production") { 33 | app.use(express.static("client/build")); 34 | 35 | app.get("*", (req, res) => { 36 | res.sendFile(path.resolve(__dirname, "client", "build", "index.html")); 37 | }); 38 | } 39 | 40 | app.listen(PORT, () => 41 | console.log( 42 | "************************************************** \n The Server has started at : http://localhost:5000" 43 | ) 44 | ); 45 | -------------------------------------------------------------------------------- /backend/util.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import config from "./config"; 3 | const getToken = user => { 4 | return jwt.sign( 5 | { 6 | _id: user._id, 7 | name: user.name, 8 | email: user.email, 9 | isAdmin: user.isAdmin 10 | }, 11 | config.JWT_SECRET, 12 | { 13 | expiresIn: "48h" 14 | } 15 | ); 16 | }; 17 | 18 | const isAuth = (req, res, next) => { 19 | const token = req.headers.authorization; 20 | if (token) { 21 | const onlyToken = token.slice(7, token.length); 22 | jwt.verify(onlyToken, config.JWT_SECRET, (err, decode) => { 23 | if (err) { 24 | return res.status(401).send({ msg: "Invalid Token" }); 25 | } 26 | req.user = decode; 27 | next(); 28 | return; 29 | }); 30 | 0; 31 | } else { 32 | return res.status(401).send({ msg: "Token is not supplied." }); 33 | } 34 | }; 35 | const isAdmin = (req, res, next) => { 36 | if (req.user && req.user.isAdmin) { 37 | return next(); 38 | } 39 | return res.status(401).send({ msg: "Admin Token is not valid" }); 40 | }; 41 | 42 | export { getToken, isAuth, isAdmin }; 43 | -------------------------------------------------------------------------------- /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.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 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | 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. 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /frontend/debug.log: -------------------------------------------------------------------------------- 1 | [0710/104803.603:ERROR:process_info.cc(329)] VirtualQueryEx: Access is denied. (0x5) 2 | [0710/104803.604:ERROR:process_info.cc(556)] ReadMemoryInfo failed 3 | [0710/104803.604:ERROR:scoped_process_suspend.cc(40)] NtResumeProcess: An attempt was made to access an exiting process. (0xc000010a) 4 | [0710/105742.914:ERROR:process_info.cc(329)] VirtualQueryEx: Access is denied. (0x5) 5 | [0710/105742.915:ERROR:process_info.cc(556)] ReadMemoryInfo failed 6 | [0710/105742.915:ERROR:scoped_process_suspend.cc(40)] NtResumeProcess: An attempt was made to access an exiting process. (0xc000010a) 7 | [0710/111326.919:ERROR:process_info.cc(329)] VirtualQueryEx: Access is denied. (0x5) 8 | [0710/111326.920:ERROR:process_info.cc(556)] ReadMemoryInfo failed 9 | [0710/111326.948:ERROR:process_memory_win.cc(39)] Failed to initialize ProcessInfo. 10 | [0710/111326.948:ERROR:scoped_process_suspend.cc(40)] NtResumeProcess: An attempt was made to access an exiting process. (0xc000010a) 11 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "proxy": "http://localhost:5000", 4 | "secure": false, 5 | "version": "0.1.0", 6 | "private": true, 7 | "dependencies": { 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.5.0", 10 | "@testing-library/user-event": "^7.2.1", 11 | "@types/js-cookie": "^2.2.6", 12 | "axios": "^0.19.2", 13 | "js-cookie": "^2.2.1", 14 | "react": "^16.13.1", 15 | "react-dom": "^16.13.1", 16 | "react-redux": "^7.2.0", 17 | "react-router-dom": "^5.2.0", 18 | "react-scripts": "3.4.1", 19 | "redux": "^4.0.5", 20 | "redux-thunk": "^2.3.0" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": "react-app" 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/images/w1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/w1.jpg -------------------------------------------------------------------------------- /frontend/public/images/w2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/w2.jpg -------------------------------------------------------------------------------- /frontend/public/images/w3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/w3.jpg -------------------------------------------------------------------------------- /frontend/public/images/w4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/w4.jpg -------------------------------------------------------------------------------- /frontend/public/images/wf1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/wf1.jpg -------------------------------------------------------------------------------- /frontend/public/images/wf2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/wf2.jpg -------------------------------------------------------------------------------- /frontend/public/images/wf3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/wf3.jpg -------------------------------------------------------------------------------- /frontend/public/images/wf4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/images/wf4.jpg -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Origami 34 | 35 | 36 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/public/logo512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./App.css"; 3 | 4 | import { BrowserRouter, Route, Link } from "react-router-dom"; 5 | import { useSelector } from "react-redux"; 6 | 7 | import HomeScreen from "./screens/HomeScreen"; 8 | import ProductScreen from "./screens/ProductScreen"; 9 | import CartScreen from "./screens/CartScreen"; 10 | import SigninScreen from "./screens/SigninScreen"; 11 | import RegisterScreen from "./screens/RegisterScreen"; 12 | import ProductsScreen from "./screens/ProductsScreen"; 13 | import ShipppingScreen from "./screens/ShippingScreen"; 14 | import PaymentScreen from "./screens/PaymentScreen"; 15 | import PlaceOrderScreen from "./screens/PlaceOrderScreen"; 16 | 17 | import logo from "./Images/logo.png"; 18 | 19 | function App() { 20 | const userSignin = useSelector(state => state.userSignin); 21 | const { userInfo } = userSignin; 22 | const openmenu = () => { 23 | document.querySelector(".sidebar").classList.add("open"); 24 | }; 25 | const closemenu = () => { 26 | document.querySelector(".sidebar").classList.remove("open"); 27 | }; 28 | return ( 29 | 30 |
31 |
32 |
33 | 34 | 35 | Origami 36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 | shopping_cart 44 | 45 | 46 | 47 | 48 | {userInfo ? ( 49 | {userInfo.name} 50 | ) : ( 51 | 52 | login 53 | 54 | )} 55 | 56 |
57 |
58 | 81 |
82 |
83 | 84 | 85 | 86 | 90 | 91 | 92 | 93 | 94 | 95 |
96 |
97 | 98 |
99 |
100 | ); 101 | } 102 | 103 | export default App; 104 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/src/Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/75dcf6918cc10b07501d806514f5f3efc54010f1/frontend/src/Images/logo.png -------------------------------------------------------------------------------- /frontend/src/Screens/CartScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { addToCart, removeFromCart } from "../actions/cartAction"; 4 | import { Link } from "react-router-dom"; 5 | 6 | function CartScreen(props) { 7 | const cart = useSelector(state => state.cart); 8 | const { cartItems } = cart; 9 | const productId = props.match.params.id; 10 | const qty = props.location.search 11 | ? Number(props.location.search.split("=")[1]) 12 | : 1; 13 | const dispatch = useDispatch(); 14 | const removeFromCartHandler = productId => { 15 | dispatch(removeFromCart(productId)); 16 | }; 17 | useEffect(() => { 18 | window.scrollTo(0, 0); 19 | if (productId) { 20 | dispatch(addToCart(productId, qty)); 21 | } 22 | }, []); 23 | const checkoutHandler = () => { 24 | props.history.push("/signin?redirect=shipping"); 25 | }; 26 | 27 | return ( 28 |
29 |
30 |
    31 |
  • 32 |

    Your Cart

    33 |
    Price
    34 |
  • 35 | {cartItems.length === 0 ? ( 36 |
    37 |

    Cart is Empty.

    38 |
    39 | ) : ( 40 | cartItems.map(item => ( 41 |
  • 42 |
    43 | product 44 |
    45 |
    46 |
    47 | 48 | {item.name} 49 | 50 |
    51 |
    52 | Quantity : 53 | 68 | 79 |
    80 |
    81 |
    ${item.price}
    82 |
  • 83 | )) 84 | )} 85 |
86 |
87 |
88 |

89 | Subtotal ({cartItems.reduce((a, c) => a + c.qty, 0)} items) 90 | : ${cartItems.reduce((a, c) => a + c.price * c.qty, 0)} 91 |

92 | 99 |
100 |
101 | ); 102 | } 103 | 104 | export default CartScreen; 105 | -------------------------------------------------------------------------------- /frontend/src/Screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { listProducts } from "../actions/productActions"; 5 | 6 | import Corousel from "../components/Corousel"; 7 | 8 | function HomeScreen(props) { 9 | const productList = useSelector(state => state.productList); 10 | const { products, loading, error } = productList; 11 | const dispatch = useDispatch(); 12 | 13 | useEffect(() => { 14 | window.scrollTo(0, 0); 15 | dispatch(listProducts()); 16 | return () => { 17 | }; 18 | }, []); 19 | 20 | return ( 21 |
22 | 23 | {loading ? ( 24 |
loading...
25 | ) : error ? ( 26 |
{error}
27 | ) : ( 28 |
    29 | {products.map(product => ( 30 |
  • 31 |
    32 | 33 | product 38 | 39 | 40 |
    41 | {product.name} 42 |
    43 | 44 |
    45 | {product.brand} 46 |
    47 |
    48 | ${product.price} 49 |
    50 |
    51 | {/* {product.rating} Stars ({product.numReviews} Reviews) */} 52 |
    53 |
    54 |
  • 55 | ))} 56 |
57 | )} 58 |
59 | ); 60 | } 61 | 62 | export default HomeScreen; 63 | -------------------------------------------------------------------------------- /frontend/src/Screens/PaymentScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { savePayment } from "../actions/cartAction"; 5 | import CheckoutSteps from "../components/CheckoutSteps"; 6 | 7 | function PaymentScreen(props) { 8 | const [paymentMethod, setPaymentMethod] = useState(""); 9 | 10 | const dispatch = useDispatch(); 11 | 12 | const submitHandler = e => { 13 | e.preventDefault(); 14 | dispatch(savePayment(paymentMethod)); 15 | props.history.push("placeorder"); 16 | }; 17 | return ( 18 |
19 | 20 |
21 |
22 |
    23 |
  • 24 |

    Payment

    25 |
  • 26 |
  • 27 | setPaymentMethod(e.target.value)} 32 | value="paypal" 33 | > 34 | 35 |
  • 36 |
  • 37 | 40 |
  • 41 |
42 |
43 |
44 |
45 | ); 46 | } 47 | export default PaymentScreen; 48 | -------------------------------------------------------------------------------- /frontend/src/Screens/PlaceOrderScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { addToCart, removeFromCart } from "../actions/cartAction"; 4 | import { Link } from "react-router-dom"; 5 | import CheckoutSteps from "../components/CheckoutSteps"; 6 | 7 | function PlaceOrderScreen(props) { 8 | const cart = useSelector(state => state.cart); 9 | const { cartItems, shipping, payment } = cart; 10 | if (!shipping.address) { 11 | props.history.push("/shipping"); 12 | } else if (!payment) { 13 | props.history.push("/payment"); 14 | } 15 | const itemsPrice = cartItems.reduce((a, c) => a + c.price * c.qty, 0); 16 | const shippingPrice = itemsPrice > 100 ? 0 : 10; 17 | const taxPrice = 0.15 * itemsPrice; 18 | const totalPrice = itemsPrice + shippingPrice + taxPrice; 19 | 20 | const dispatch = useDispatch(); 21 | 22 | useEffect(() => { 23 | window.scrollTo(0, 0); 24 | }, []); 25 | const placeOrderHandler = () => { 26 | props.history.push("/"); 27 | alert("Order placed successfully."); 28 | }; 29 | 30 | return ( 31 |
32 | 33 | 34 |
35 |
36 |
37 |

Shipping

38 |
39 | {cart.shipping.address},{cart.shipping.city}, 40 | {cart.shipping.postalCode},{cart.shipping.country}, 41 |
42 |
43 |
44 |

Payment

45 |
Payment Method : {cart.payment.paymentMethod}
46 |
47 |
48 |
    49 |
  • 50 |

    Shopping Cart

    51 |
    Price
    52 |
  • 53 | {cartItems.length === 0 ? ( 54 |
    Cart is Empty.
    55 | ) : ( 56 | cartItems.map(item => ( 57 |
  • 58 |
    59 | product 63 |
    64 |
    65 |
    66 | 72 | {item.name} 73 | 74 |
    75 |
    Quantity : {item.qty}
    76 |
    77 |
    78 | ${item.price} 79 |
    80 |
  • 81 | )) 82 | )} 83 |
84 |
85 |
86 |
87 |
    88 |
  • 89 | 95 |
  • 96 |
  • 97 |

    Order Summary

    98 |
  • 99 |
  • 100 |
    Items
    101 |
    ${itemsPrice}
    102 |
  • 103 |
  • 104 |
    Shipping
    105 |
    ${shippingPrice}
    106 |
  • 107 |
  • 108 |
    Tax
    109 |
    ${taxPrice}
    110 |
  • 111 |
  • 112 |
    Order Total
    113 |
    ${totalPrice}
    114 |
  • 115 |
116 |
117 |
118 |
119 | ); 120 | } 121 | 122 | export default PlaceOrderScreen; 123 | -------------------------------------------------------------------------------- /frontend/src/Screens/ProductScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { detailsProduct } from "../actions/productActions"; 5 | 6 | function ProductScreen(props) { 7 | const [qty, setQty] = useState(1); 8 | const productDetails = useSelector(state => state.productDetails); 9 | const { product, loading, error } = productDetails; 10 | const dispatch = useDispatch(); 11 | 12 | useEffect(() => { 13 | window.scrollTo(0, 0) 14 | dispatch(detailsProduct(props.match.params.id)); 15 | return () => { 16 | // 17 | }; 18 | }, []); 19 | 20 | const handleAddToCart = () => { 21 | props.history.push("/cart/" + props.match.params.id + "?qty=" + qty); 22 | }; 23 | 24 | return ( 25 |
26 |
27 | 28 | arrow_back 29 | 30 |
31 | {loading ? ( 32 |
Loading...
33 | ) : error ? ( 34 |
{error}
35 | ) : ( 36 |
37 |
38 | product 39 |
40 |
41 |
    42 |
  • 43 |

    {product.brand}

    44 |
  • 45 |
  • 46 |

    {product.name}

    47 |
  • 48 |
  • 49 | {product.rating} Stars ({product.numReviews}{" "} 50 | Reviews) 51 |
  • 52 |
  • 53 | ${product.price} 54 |
  • 55 |
  • 56 |
    {product.description}
    57 |
  • 58 |
  • 59 | Net Qty : 60 | 75 |
  • 76 |
  • 77 | {product.countInStock > 0 78 | ? "In Stock" 79 | : "Unavailble"} 80 |
  • 81 |
  • 82 | {product.countInStock > 0 && ( 83 | 89 | )} 90 | {console.log(product.category)} 91 |
  • 92 |
93 |
94 |
95 | )} 96 |
97 | ); 98 | } 99 | export default ProductScreen; 100 | -------------------------------------------------------------------------------- /frontend/src/Screens/ProductsScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { 4 | saveProduct, 5 | listProducts, 6 | deleteProduct 7 | } from "../actions/productActions"; 8 | 9 | function ProductsScreen(props) { 10 | const [modalVisible, setModalVisible] = useState(false); 11 | const [id, setId] = useState(""); 12 | const [name, setName] = useState(""); 13 | const [price, setPrice] = useState(""); 14 | const [image, setImage] = useState(""); 15 | const [brand, setBrand] = useState(""); 16 | const [category, setCategory] = useState(""); 17 | const [countInStock, setCountInStock] = useState(""); 18 | const [description, setDescription] = useState(""); 19 | const productList = useSelector(state => state.productList); 20 | const { loading, products, error } = productList; 21 | 22 | const productSave = useSelector(state => state.productSave); 23 | const { 24 | loading: loadingSave, 25 | success: successSave, 26 | error: errorSave 27 | } = productSave; 28 | 29 | const productDelete = useSelector(state => state.productDelete); 30 | const { 31 | loading: loadingDelete, 32 | success: successDelete, 33 | error: errorDelete 34 | } = productDelete; 35 | 36 | const dispatch = useDispatch(); 37 | 38 | useEffect(() => { 39 | if (successSave) { 40 | setModalVisible(false); 41 | } 42 | dispatch(listProducts()); 43 | return () => { 44 | // 45 | }; 46 | }, [successSave, successDelete]); 47 | 48 | const openModal = product => { 49 | setModalVisible(true); 50 | setId(product._id); 51 | setName(product.name); 52 | setPrice(product.price); 53 | setDescription(product.description); 54 | setImage(product.image); 55 | setBrand(product.brand); 56 | setCategory(product.category); 57 | setCountInStock(product.countInStock); 58 | }; 59 | 60 | const submitHandler = e => { 61 | e.preventDefault(); 62 | dispatch( 63 | saveProduct({ 64 | _id: id, 65 | name, 66 | price, 67 | image, 68 | brand, 69 | category, 70 | countInStock, 71 | description 72 | }) 73 | ); 74 | }; 75 | 76 | const deleteHandler = product => { 77 | dispatch(deleteProduct(product._id)); 78 | }; 79 | 80 | return ( 81 |
82 |
83 |

Products

84 | 90 |
91 | {modalVisible && ( 92 |
93 |
94 |
    95 |
  • 96 |

    Create Product

    97 |
  • 98 |
  • 99 | {loadingSave &&
    Loading...
    } 100 | {errorSave &&
    {errorSave}
    } 101 |
  • 102 |
  • 103 | 104 | setName(e.target.value)} 110 | > 111 |
  • 112 |
  • 113 | 114 | setPrice(e.target.value)} 120 | > 121 |
  • 122 |
  • 123 | 124 | setImage(e.target.value)} 130 | > 131 |
  • 132 |
  • 133 | 134 | setBrand(e.target.value)} 140 | > 141 |
  • 142 |
  • 143 | 144 | setCategory(e.target.value)} 150 | > 151 |
  • 152 |
  • 153 | 156 | 162 | setCountInStock(e.target.value) 163 | } 164 | > 165 |
  • 166 |
  • 167 | 168 | 177 |
  • 178 |
  • 179 | 185 |
  • 186 |
  • 187 | 194 |
  • 195 |
196 |
197 |
198 | )} 199 | 200 |
201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | {products.map(product => ( 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 237 | 238 | ))} 239 | 240 |
IDNamePriceCategoryBrandCount In StockAction
{product._id} {product.name}{product.price}{product.category}{product.brand}{product.countInStock} 223 | 229 | {" "} 230 | 236 |
241 |
242 |
243 | ); 244 | } 245 | export default ProductsScreen; 246 | -------------------------------------------------------------------------------- /frontend/src/Screens/RegisterScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { register } from "../actions/userAction"; 5 | 6 | function RegisterScreen(props) { 7 | const [name, setName] = useState(""); 8 | const [email, setEmail] = useState(""); 9 | const [password, setPassword] = useState(""); 10 | const [rePassword, setRePassword] = useState(""); 11 | const userRegister = useSelector(state => state.userRegister); 12 | const { loading, userInfo, error } = userRegister; 13 | const dispatch = useDispatch(); 14 | const redirect = props.location.search 15 | ? props.location.search.split("=")[1] 16 | : "/"; 17 | 18 | useEffect(() => { 19 | window.scrollTo(0, 0); 20 | if (userInfo) { 21 | props.history.push(redirect); 22 | } 23 | return () => { 24 | // 25 | }; 26 | }, [userInfo]); 27 | 28 | const submitHandler = e => { 29 | e.preventDefault(); 30 | dispatch(register(name, email, password)); 31 | }; 32 | return ( 33 |
34 |
35 |
    36 |
  • 37 |

    Create Account

    38 |
  • 39 |
  • 40 | {loading &&
    Loading...
    } 41 | {error &&
    {error}
    } 42 |
  • 43 |
  • 44 | 45 | setName(e.target.value)} 50 | > 51 |
  • 52 |
  • 53 | 54 | setEmail(e.target.value)} 59 | > 60 |
  • 61 |
  • 62 | 63 | setPassword(e.target.value)} 68 | > 69 |
  • 70 |
  • 71 | 72 | setRePassword(e.target.value)} 77 | > 78 |
  • 79 |
  • 80 | 83 |
  • 84 |
  • Already Have an account?
  • 85 |
  • 86 | 94 | Sign-In 95 | 96 |
  • 97 |
98 |
99 |
100 | ); 101 | } 102 | export default RegisterScreen; 103 | -------------------------------------------------------------------------------- /frontend/src/Screens/ShippingScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { saveShipping } from "../actions/cartAction"; 5 | import CheckoutSteps from "../components/CheckoutSteps"; 6 | 7 | function ShippingScreen(props) { 8 | const [address, setAddress] = useState(""); 9 | const [city, setCity] = useState(""); 10 | const [postalCode, setPostalCode] = useState(""); 11 | const [country, setCountry] = useState(""); 12 | 13 | const dispatch = useDispatch(); 14 | 15 | const submitHandler = e => { 16 | e.preventDefault(); 17 | dispatch(saveShipping({ address, city, postalCode, country })); 18 | props.history.push("payment"); 19 | }; 20 | return ( 21 |
22 | 23 |
24 |
25 |
    26 |
  • 27 |

    Shipping

    28 |
  • 29 |
  • 30 | 31 | setAddress(e.target.value)} 36 | > 37 |
  • 38 |
  • 39 | 40 | setCity(e.target.value)} 45 | > 46 |
  • 47 |
  • 48 | 49 | setPostalCode(e.target.value)} 54 | > 55 |
  • 56 |
  • 57 | 58 | setCountry(e.target.value)} 63 | > 64 |
  • 65 | 66 |
  • 67 | 70 |
  • 71 |
72 |
73 |
74 |
75 | ); 76 | } 77 | export default ShippingScreen; 78 | -------------------------------------------------------------------------------- /frontend/src/Screens/SigninScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { signin } from "../actions/userAction"; 5 | 6 | function SigninScreen(props) { 7 | const [email, setEmail] = useState(""); 8 | const [password, setPassword] = useState(""); 9 | const userSignin = useSelector(state => state.userSignin); 10 | const { loading, userInfo, error } = userSignin; 11 | const dispatch = useDispatch(); 12 | const redirect = props.location.search 13 | ? props.location.search.split("=")[1] 14 | : "/"; 15 | 16 | useEffect(() => { 17 | window.scrollTo(0, 0); 18 | if (userInfo) { 19 | props.history.push(redirect); 20 | } 21 | return () => { 22 | }; 23 | }, [userInfo]); 24 | 25 | const submitHandler = e => { 26 | e.preventDefault(); 27 | dispatch(signin(email, password)); 28 | }; 29 | return ( 30 |
31 |
32 |
    33 |
  • 34 |

    Sign-In

    35 |
  • 36 |
  • 37 | {loading &&
    Loading...
    } 38 | {error &&
    {error}
    } 39 |
  • 40 |
  • 41 | 42 | setEmail(e.target.value)} 47 | > 48 |
  • 49 |
  • 50 | 51 | setPassword(e.target.value)} 56 | > 57 |
  • 58 |
  • 59 | 62 |
  • 63 |
  • New to Origami?
  • 64 |
  • 65 | 73 | Create your Origami account. 74 | 75 |
  • 76 |
77 |
78 |
79 | ); 80 | } 81 | export default SigninScreen; 82 | -------------------------------------------------------------------------------- /frontend/src/actions/CartAction.js: -------------------------------------------------------------------------------- 1 | import Axios from "axios"; 2 | import Cookie from "js-cookie"; 3 | import { 4 | CART_ADD_ITEM, 5 | CART_REMOVE_ITEM, 6 | CART_SAVE_SHIPPING, 7 | CART_SAVE_PAYMENT 8 | } from "../constants/cartConstants"; 9 | 10 | const addToCart = (productId, qty) => async (dispatch, getState) => { 11 | try { 12 | const { data } = await Axios.get("/api/products/" + productId); 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 | const { 25 | cart: { cartItems } 26 | } = getState(); 27 | Cookie.set("cartItems", JSON.stringify(cartItems)); 28 | } catch (error) {} 29 | }; 30 | 31 | const removeFromCart = productId => (dispatch, getState) => { 32 | dispatch({ 33 | type: CART_REMOVE_ITEM, 34 | payload: productId 35 | }); 36 | const { 37 | cart: { cartItems } 38 | } = getState(); 39 | Cookie.set("cartItems", JSON.stringify(cartItems)); 40 | }; 41 | 42 | const saveShipping = data => dispatch => { 43 | dispatch({ type: CART_SAVE_SHIPPING, payload: data }); 44 | }; 45 | const savePayment = data => dispatch => { 46 | dispatch({ type: CART_SAVE_PAYMENT, payload: data }); 47 | }; 48 | 49 | export { addToCart, removeFromCart, saveShipping, savePayment }; 50 | -------------------------------------------------------------------------------- /frontend/src/actions/productActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | PRODUCT_LIST_REQUEST, 3 | PRODUCT_LIST_SUCCESS, 4 | PRODUCT_LIST_FAIL, 5 | PRODUCT_DETAILS_REQUEST, 6 | PRODUCT_DETAILS_SUCCESS, 7 | PRODUCT_DETAILS_FAIL, 8 | PRODUCT_SAVE_REQUEST, 9 | PRODUCT_SAVE_SUCCESS, 10 | PRODUCT_SAVE_FAIL, 11 | PRODUCT_DELETE_SUCCESS, 12 | PRODUCT_DELETE_FAIL, 13 | PRODUCT_DELETE_REQUEST 14 | } from "../constants/productConstants"; 15 | import axios from "axios"; 16 | 17 | const listProducts = () => async dispatch => { 18 | try { 19 | dispatch({ type: PRODUCT_LIST_REQUEST }); 20 | const { data } = await axios.get("/api/products"); 21 | dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data }); 22 | } catch (error) { 23 | dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message }); 24 | } 25 | }; 26 | 27 | const saveProduct = product => async (dispatch, getState) => { 28 | try { 29 | dispatch({ type: PRODUCT_SAVE_REQUEST, payload: product }); 30 | const { 31 | userSignin: { userInfo } 32 | } = getState(); 33 | if (!product._id) { 34 | const { data } = await axios.post("/api/products", product, { 35 | headers: { 36 | Authorization: "Bearer" + userInfo.token 37 | } 38 | }); 39 | 40 | dispatch({ type: PRODUCT_SAVE_SUCCESS, payload: data }); 41 | } else { 42 | const { data } = await axios.put( 43 | "/api/products/" + product._id, 44 | product, 45 | { 46 | headers: { 47 | Authorization: "Bearer" + userInfo.token 48 | } 49 | } 50 | ); 51 | dispatch({ type: PRODUCT_SAVE_SUCCESS, payload: data }); 52 | } 53 | } catch (error) { 54 | dispatch({ type: PRODUCT_SAVE_FAIL, payload: error.message }); 55 | } 56 | }; 57 | 58 | const deleteProduct = productId => async (dispatch, getState) => { 59 | try { 60 | const { 61 | userSignin: { userInfo } 62 | } = getState(); 63 | dispatch({ type: PRODUCT_DELETE_REQUEST, payload: productId }); 64 | const { data } = await axios.delete("/api/products/" + productId, { 65 | Authorization: "Bearer " + userInfo.token 66 | }); 67 | dispatch({ 68 | type: PRODUCT_DELETE_SUCCESS, 69 | payload: data, 70 | success: true 71 | }); 72 | } catch (error) { 73 | dispatch({ type: PRODUCT_DELETE_FAIL, payload: error.message }); 74 | } 75 | }; 76 | const detailsProduct = productId => async dispatch => { 77 | try { 78 | dispatch({ type: PRODUCT_DETAILS_REQUEST, payload: productId }); 79 | const { data } = await axios.get("/api/products/" + productId); 80 | dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data }); 81 | } catch (error) { 82 | dispatch({ type: PRODUCT_DETAILS_FAIL, payload: error.message }); 83 | } 84 | }; 85 | 86 | export { listProducts, detailsProduct, saveProduct, deleteProduct }; 87 | -------------------------------------------------------------------------------- /frontend/src/actions/userAction.js: -------------------------------------------------------------------------------- 1 | import Axios from "axios"; 2 | import Cookie from "js-cookie"; 3 | import { 4 | USER_SIGNIN_REQUEST, 5 | USER_SIGNIN_SUCCESS, 6 | USER_SIGNIN_FAIL, 7 | USER_REGISTER_FAIL, 8 | USER_REGISTER_SUCCESS, 9 | USER_REGISTER_REQUEST 10 | } from "../constants/userConstants"; 11 | 12 | const signin = (email, password) => async dispatch => { 13 | dispatch({ type: USER_SIGNIN_REQUEST, payload: { email, password } }); 14 | try { 15 | const { data } = await Axios.post("/api/users/signin", { 16 | email, 17 | password 18 | }); 19 | dispatch({ type: USER_SIGNIN_SUCCESS, payload: data }); 20 | Cookie.set("userInfo", JSON.stringify(data)); 21 | } catch (error) { 22 | dispatch({ type: USER_SIGNIN_FAIL, payload: error.message }); 23 | } 24 | }; 25 | const register = (name, email, password) => async dispatch => { 26 | dispatch({ 27 | type: USER_REGISTER_REQUEST, 28 | payload: { name, email, password } 29 | }); 30 | try { 31 | const { data } = await Axios.post("/api/users/register", { 32 | name, 33 | email, 34 | password 35 | }); 36 | dispatch({ type: USER_REGISTER_SUCCESS, payload: data }); 37 | Cookie.set("userInfo", JSON.stringify(data)); 38 | } catch (error) { 39 | dispatch({ type: USER_REGISTER_FAIL, payload: error.message }); 40 | } 41 | }; 42 | 43 | export { signin, register }; 44 | -------------------------------------------------------------------------------- /frontend/src/components/CheckoutSteps.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function CheckoutSteps(props) { 4 | return ( 5 |
6 |
Signin
7 |
Shipping
8 |
Payment
9 |
Place Order
10 |
11 | ); 12 | } 13 | export default CheckoutSteps; 14 | -------------------------------------------------------------------------------- /frontend/src/components/Corousel.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Carousel } from "react-bootstrap"; 3 | 4 | import logo from "../Images/logo.png"; 5 | 6 | function Coursel() { 7 | return ( 8 | 9 | 10 | 11 | Third slide 16 | 17 | 18 |

19 | Discover your Passion 20 |

21 |

22 |
23 |
24 | 25 | Third slide 30 | 31 | 32 |

33 | Show your Style 34 |

35 |
36 |
37 | 38 | Third slide 43 | 44 | 45 |

46 | Live your Dream 47 |

48 |
49 |
50 | 51 | First slide 56 | 57 | 58 |

Because time flies,

59 |

But the memories last forever.

60 |
61 |
62 |
63 | ); 64 | } 65 | 66 | export default Coursel; 67 | -------------------------------------------------------------------------------- /frontend/src/constants/cartConstants.js: -------------------------------------------------------------------------------- 1 | export const CART_ADD_ITEM = "CART_ADD_ITEM"; 2 | export const CART_REMOVE_ITEM = "CART_REMOVE_ITEM"; 3 | 4 | export const CART_SAVE_SHIPPING = "CART_SAVE_SHIPPING"; 5 | 6 | export const CART_SAVE_PAYMENT = "CART_SAVE_PAYMENT"; 7 | -------------------------------------------------------------------------------- /frontend/src/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_SAVE_REQUEST = "PRODUCT_SAVE_REQUEST"; 10 | export const PRODUCT_SAVE_SUCCESS = "PRODUCT_SAVE_SUCCESS"; 11 | export const PRODUCT_SAVE_FAIL = "PRODUCT_SAVE_FAIL"; 12 | 13 | export const PRODUCT_DELETE_REQUEST = "PRODUCT_DELETE_REQUEST"; 14 | export const PRODUCT_DELETE_SUCCESS = "PRODUCT_DELETE_SUCCESS"; 15 | export const PRODUCT_DELETE_FAIL = "PRODUCT_DELETE_FAIL"; 16 | -------------------------------------------------------------------------------- /frontend/src/constants/userConstants.js: -------------------------------------------------------------------------------- 1 | export const USER_SIGNIN_REQUEST = "USER_SIGNIN_REQUEST"; 2 | export const USER_SIGNIN_SUCCESS = "USER_SIGNIN_SUCCESS"; 3 | export const USER_SIGNIN_FAIL = "USER_SIGNIN_FAIL"; 4 | 5 | export const USER_REGISTER_REQUEST = "USER_REGISTER_REQUEST"; 6 | export const USER_REGISTER_SUCCESS = "USER_REGISTER_SUCCESS"; 7 | export const USER_REGISTER_FAIL = "USER_REGISTER_FAIL"; 8 | -------------------------------------------------------------------------------- /frontend/src/data.js: -------------------------------------------------------------------------------- 1 | // export default { 2 | // products: [ 3 | // { 4 | // _id: "1", 5 | // name: "Nike Men's Regular fit T-Shirt", 6 | // category: "Shirt", 7 | // image: "/images/d1.jpg", 8 | // price: 35, 9 | // brand: " Nike", 10 | // rating: 3.0, 11 | // numReviews: 13 12 | // }, 13 | // { 14 | // _id: "2", 15 | // name: "Nike Men's Track Pants", 16 | // category: "Pants", 17 | // image: "/images/d2.jpg", 18 | // price: 50, 19 | // brand: " Nike", 20 | // rating: 4.2, 21 | // numReviews: 66 22 | // }, 23 | // { 24 | // _id: "3", 25 | // name: "Nike Men's Track Pants Gray", 26 | // category: "Pants", 27 | // image: "/images/d3.jpg", 28 | // price: 70, 29 | // brand: " Nike", 30 | // rating: 4, 31 | // numReviews: 8 32 | // }, 33 | // { 34 | // _id: "4", 35 | // name: "Nike Girl's Plain Regular fit T-Shirt", 36 | // category: "Pants", 37 | // image: "/images/d4.jpg", 38 | // price: 40, 39 | // brand: " Nike", 40 | // rating: 3.5, 41 | // numReviews: 18 42 | // } 43 | // ] 44 | // }; 45 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Pacifico&display=swap"); 2 | @import url("https://fonts.googleapis.com/css2?family=Yeseva+One&display=swap"); 3 | @import url("https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;0,900;1,400;1,500;1,900&family=Vollkorn+SC:wght@400;600;700;900&family=Vollkorn:ital,wght@0,500;1,400;1,500&display=swap"); 4 | 5 | /* fonts */ 6 | /* font-family: Pacifico; 7 | font-family: Yeseva One; 8 | font-family: Playfair; 9 | font-family: Vollkorn; 10 | font-family: Vollkorn VC; */ 11 | 12 | html { 13 | font-size: 62.5%; 14 | box-sizing: border-box; 15 | } 16 | body { 17 | font: 1.6rem Helvetica; 18 | height: 100%; 19 | margin: 0%; 20 | } 21 | button { 22 | font-size: 1.6rem; 23 | } 24 | .full-width { 25 | width: 100%; 26 | } 27 | #root { 28 | height: 100%; 29 | } 30 | ul { 31 | padding: 0; 32 | } 33 | a, 34 | link { 35 | text-decoration: none !important; 36 | } 37 | .grid-container { 38 | display: grid; 39 | grid-template-areas: 40 | "header" 41 | "main" 42 | "footer"; 43 | grid-template-columns: 1fr; 44 | grid-template-rows: 5rem 1fr 5rem; 45 | height: 100%; 46 | } 47 | .header { 48 | grid-area: header; 49 | background-color: rgb(255, 255, 255); 50 | color: rgb(0, 0, 0); 51 | display: flex; 52 | justify-content: space-between; 53 | align-items: center; 54 | padding: 0.5rem; 55 | position: -webkit-sticky; /* Safari */ 56 | position: sticky; 57 | top: 0; 58 | z-index: 9; 59 | box-shadow: 5px 0.1px 10px rgb(212, 175, 55); 60 | } 61 | .logo { 62 | height: 50px; 63 | } 64 | .brand a { 65 | color: black; 66 | font-size: 2.5rem; 67 | font-weight: bold; 68 | text-decoration: none; 69 | font-family: "Pacifico", cursive; 70 | } 71 | .header-links span { 72 | margin-right: 2rem; 73 | } 74 | .header-links a span { 75 | font-size: 3.5rem; 76 | color: rgb(0, 0, 0); 77 | text-decoration: none; 78 | transition: 0.3s; 79 | margin: 0; 80 | padding: 0; 81 | } 82 | .header-links a span:hover { 83 | color: rgb(212, 175, 55); 84 | } 85 | .main { 86 | grid-area: main; 87 | } 88 | .footer { 89 | grid-area: footer; 90 | background-color: rgb(255, 255, 255); 91 | color: rgb(0, 0, 0); 92 | display: flex; 93 | justify-content: center; 94 | align-items: center; 95 | box-shadow: 5px 0.1px 10px rgb(212, 175, 55); 96 | } 97 | a { 98 | text-decoration: none; 99 | transition: 0.3s; 100 | font-size: 1.7rem; 101 | } 102 | a:hover { 103 | color: rgb(212, 175, 55); 104 | } 105 | /* Home Screen */ 106 | .products { 107 | display: flex; 108 | justify-content: center; 109 | align-items: center; 110 | flex-wrap: wrap; 111 | z-index: 0; 112 | background-color: #f8f8f8; 113 | } 114 | .products li { 115 | list-style: none; 116 | padding: 0; 117 | flex: 0 1 30rem; 118 | margin: 1rem; 119 | height: 40rem; 120 | /* border-bottom: 0.1rem #c0c0c0 solid; */ 121 | background-color: #ffffff; 122 | } 123 | .product { 124 | display: flex; 125 | flex-direction: column; 126 | justify-content: space-between; 127 | height: 100%; 128 | margin: 0.5rem; 129 | align-items: center; 130 | transition: 0.3s; 131 | } 132 | .product:hover { 133 | /* border: .1px solid rgb(200, 200, 200); */ 134 | box-shadow: 1px 1px 10px rgb(212, 175, 55); 135 | } 136 | .product-name { 137 | font-size: 2rem; 138 | font-weight: bold; 139 | color: black; 140 | text-align: center; 141 | transition: 0.3s; 142 | } 143 | .product-name:hover { 144 | color: rgb(212, 175, 55); 145 | } 146 | .product-brand { 147 | font-size: 1.2rem; 148 | color: #808080; 149 | } 150 | .product-price { 151 | font-size: 2.5rem; 152 | font-weight: bold; 153 | } 154 | .product-image { 155 | max-width: 24rem; 156 | max-height: 24rem; 157 | transition: 0.5s; 158 | } 159 | .product-image:hover { 160 | max-width: 25rem; 161 | max-height: 25rem; 162 | } 163 | .product-rating { 164 | margin-bottom: 1rem; 165 | } 166 | /* SideBar */ 167 | .brand button { 168 | font-size: 3rem; 169 | padding: 0.5rem; 170 | background: none; 171 | border: none; 172 | color: #000000; 173 | cursor: pointer; 174 | } 175 | .sidebar { 176 | position: fixed; 177 | transform: translate(-30rem); 178 | width: 30rem; 179 | background-color: rgba(0, 0, 0, 0.95); 180 | height: 100%; 181 | transition: all 0.5s; 182 | z-index: 10; 183 | } 184 | .open { 185 | transform: translate(0); 186 | } 187 | .sidebar-close-button { 188 | border: 0.1rem rgba(0, 0, 0, 0); 189 | background-color: rgba(255, 255, 255, 0); 190 | /* color: rgb(255, 254, 254); */ 191 | width: 3rem; 192 | height: 3rem; 193 | padding: 0.5rem; 194 | font-size: 4.5rem; 195 | padding-top: 0; 196 | cursor: pointer; 197 | position: absolute; 198 | right: 2rem; 199 | top: 0.1rem; 200 | /* outline: none; */ 201 | } 202 | .sidebar-close-button span { 203 | font-size: 3rem; 204 | color: rgb(255, 255, 255); 205 | } 206 | .sidebar-close-button span:hover { 207 | color: rgb(212, 175, 55); 208 | } 209 | .sidebar-close-button::focus { 210 | outline: none; 211 | } 212 | 213 | .sidebar h3 { 214 | color: white; 215 | padding: 1.1rem; 216 | font-size: 30px; 217 | justify-content: center; 218 | border-bottom: 0.1px solid rgba(255, 255, 255, 0.397); 219 | } 220 | 221 | .sidebar ul { 222 | list-style: none; 223 | margin: 1rem; 224 | padding: 1rem 0 0 1rem; 225 | } 226 | .sidebar ul li { 227 | list-style: none; 228 | margin: 1rem; 229 | padding-bottom: 0.1rem; 230 | color: white; 231 | } 232 | .sidebar ul li a { 233 | color: white; 234 | font-weight: 400; 235 | } 236 | .sidebar ul li a:hover { 237 | color: rgb(212, 175, 55); 238 | } 239 | 240 | /* Product details */ 241 | /* 242 | font-family: Pacifico; 243 | font-family: Yeseva One; 244 | font-family: Playfair; 245 | font-family: Vollkorn; 246 | font-family: Vollkorn VC; 247 | */ 248 | 249 | .details { 250 | display: flex; 251 | align-items: flex-start; 252 | flex-wrap: wrap; 253 | padding: 1rem; 254 | font-family: Vollkorn VC; 255 | font-size: 2rem; 256 | position: relative; 257 | min-height: 100vh; 258 | } 259 | .details-image { 260 | flex: 1 1 30rem; 261 | display: flex; 262 | justify-content: center; 263 | align-items: center; 264 | align-content: center; 265 | } 266 | .details-image img { 267 | max-width: 35rem; 268 | width: 80%; 269 | transition: 0.5s; 270 | } 271 | .details-image img:hover { 272 | max-width: 38rem; 273 | width: 82%; 274 | transition: 0.3s; 275 | } 276 | 277 | .details-info { 278 | flex: 1 1 30rem; 279 | height: 100%; 280 | padding-left: 10rem; 281 | padding-right: 10rem; 282 | } 283 | .details-info ul, 284 | .details-action ul { 285 | list-style-type: none; 286 | padding: none; 287 | } 288 | .details-info li, 289 | .details-action li { 290 | margin: 1rem; 291 | } 292 | .back-to-result a span { 293 | font-size: 3.5rem; 294 | padding: 0.5rem 0 0 1rem; 295 | color: black; 296 | transition: 0.3s; 297 | } 298 | .back-to-result a span:hover { 299 | color: rgb(212, 175, 55); 300 | } 301 | 302 | .details-info ul li h1 { 303 | font-family: Vollkorn VC; 304 | font-size: 3rem; 305 | } 306 | .details-info ul li:nth-child(4) { 307 | font-size: 2rem; 308 | margin-top: 5rem; 309 | } 310 | .details-info ul li:nth-child(5) { 311 | font-size: 1.5rem; 312 | color: rgb(97, 97, 97); 313 | margin-bottom: 4rem; 314 | } 315 | .details-info ul li:nth-child(7) { 316 | margin-top: 2rem; 317 | font-size: 1.4rem; 318 | color: rgb(97, 97, 97); 319 | } 320 | .details-info ul li:last-child { 321 | display: flex; 322 | flex-direction: column; 323 | } 324 | .button { 325 | padding: 1rem; 326 | border: 0.2rem #000000 solid; 327 | cursor: pointer; 328 | border-radius: 0.5rem; 329 | background-color: rgb(212, 175, 55); 330 | transition: 0.3s; 331 | } 332 | .button:hover { 333 | /* border: 0.2rem #000000 solid; */ 334 | background-color: rgb(255, 255, 255); 335 | } 336 | .button:hover { 337 | border: 0.1rem #404040 solid; 338 | } 339 | .button.primary { 340 | background-color: #f0c040; 341 | } 342 | .button.secondary { 343 | background-color: #f0f0f0; 344 | } 345 | .text-center { 346 | text-align: center; 347 | } 348 | 349 | /* Cart Screen */ 350 | 351 | .cart { 352 | display: flex; 353 | flex-wrap: wrap; 354 | margin: 1rem; 355 | align-items: flex-start; 356 | position: relative; 357 | min-height: 100vh; 358 | } 359 | .cart-list { 360 | flex: 3 1 60rem; 361 | } 362 | .cart-action { 363 | flex: 1 1 20rem; 364 | background-color: #f8f8f8; 365 | border-radius: 0.5rem; 366 | padding: 1rem; 367 | } 368 | .cart-list-container { 369 | padding: 0; 370 | list-style-type: none; 371 | padding: 1rem; 372 | } 373 | 374 | .cart-list-container li { 375 | display: flex; 376 | justify-content: space-between; 377 | align-items: flex-end; 378 | padding-bottom: 1rem; 379 | margin-bottom: 1rem; 380 | border-bottom: 0.1rem #808080 solid; 381 | } 382 | .cart-list-container li img { 383 | max-width: 10rem; 384 | max-height: 10rem; 385 | } 386 | .cart-list-container li:first-child { 387 | display: flex; 388 | justify-content: space-between; 389 | align-items: flex; 390 | } 391 | 392 | .cart-image { 393 | flex: 1 1; 394 | } 395 | .cart-name { 396 | flex: 8 1; 397 | } 398 | .cart-price { 399 | flex: 1 1; 400 | font-size: 2.5rem; 401 | text-align: right; 402 | } 403 | .cart-button{ 404 | width: 10rem; 405 | margin: .1rem; 406 | border: 0.2rem #000000 solid; 407 | cursor: pointer; 408 | border-radius: 0.5rem; 409 | background-color: rgb(212, 175, 55); 410 | transition: 0.3s; 411 | float: right; 412 | margin-right: 5rem; 413 | } 414 | .cart-button:hover{ 415 | border: 0.2rem #000000 solid; 416 | background-color: rgb(255, 255, 255); 417 | } 418 | 419 | /* Signin */ 420 | 421 | .form { 422 | display: flex; 423 | justify-content: center; 424 | align-items: center; 425 | height: 100%; 426 | position: relative; 427 | min-height: 90vh; 428 | } 429 | .form-container { 430 | display: flex; 431 | flex-direction: column; 432 | width: 32rem; 433 | padding: 2rem; 434 | border: 0.2rem #f0f0f0 solid; 435 | border-radius: 0.5rem; 436 | list-style: none; 437 | } 438 | 439 | .form-container li { 440 | display: flex; 441 | flex-direction: column; 442 | margin-bottom: 1rem; 443 | margin-top: 1rem; 444 | } 445 | input { 446 | padding: 1rem; 447 | border: 0.1rem #c0c0c0 solid; 448 | border-radius: 0.5rem; 449 | } 450 | 451 | /* Products */ 452 | .content{ 453 | position: relative; 454 | min-height: 100vh; 455 | } 456 | .product-header { 457 | display: flex; 458 | justify-content: space-between; 459 | align-items: flex-start; 460 | } 461 | .content-margined { 462 | margin: 1rem; 463 | } 464 | .table { 465 | width: 100%; 466 | } 467 | th { 468 | text-align: left; 469 | } 470 | tbody > tr:nth-child(odd) { 471 | background-color: #f0f0f0; 472 | } 473 | .product-button{ 474 | width: 5.6rem; 475 | margin: .1rem; 476 | border: 0.2rem #000000 solid; 477 | cursor: pointer; 478 | border-radius: 0.5rem; 479 | background-color: rgb(212, 175, 55); 480 | transition: 0.3s; 481 | } 482 | .product-button:hover{ 483 | border: 0.2rem #000000 solid; 484 | background-color: rgb(255, 255, 255); 485 | } 486 | 487 | 488 | /* Checkout steps */ 489 | 490 | .checkout-steps { 491 | display: flex; 492 | justify-content: space-around; 493 | width: 40rem; 494 | margin: 1rem auto; 495 | } 496 | .checkout-steps > div { 497 | border-top: 0.3rem #c0c0c0 solid; 498 | color: #c0c0c0; 499 | flex: 1 1; 500 | padding-top: 1rem; 501 | } 502 | .checkout-steps > div.active { 503 | border-top: 0.3rem #f08000 solid; 504 | color: #f08000; 505 | } 506 | 507 | /* place order */ 508 | 509 | .placeorder { 510 | display: flex; 511 | flex-wrap: wrap; 512 | padding: 1rem; 513 | justify-content: space-between; 514 | } 515 | 516 | .placeorder-info { 517 | flex: 3 1 60rem; 518 | } 519 | .placeorder-action { 520 | flex: 3 1 20rem; 521 | border: 0.1rem #c0c0c0 solid; 522 | border-radius: 0.5rem; 523 | background-color: #fcfcfc; 524 | padding: 1rem; 525 | } 526 | .placeorder-info > div { 527 | border: 0.1rem #c0c0c0 solid; 528 | border-radius: 0.5rem; 529 | background-color: #fcfcfc; 530 | padding: 1rem; 531 | margin: 1rem; 532 | } 533 | .placeorder-info > div:first-child { 534 | margin-top: 0; 535 | } 536 | .placeorder-action > ul { 537 | padding: 0; 538 | list-style-type: none; 539 | } 540 | .placeorder-action > ul > li { 541 | display: flex; 542 | justify-content: space-between; 543 | margin-bottom: 1rem; 544 | } 545 | .placeorder-action > ul > li:last-child { 546 | font-size: 2rem; 547 | font-weight: bold; 548 | color: #f08000; 549 | border-top: 0.1px #c0c0c0 solid; 550 | padding-top: 0.5rem; 551 | } 552 | 553 | /* Courasel */ 554 | .corousel { 555 | /* font-family: 'Yeseva One', cursive; */ 556 | font-family: "Playfair Display", serif; 557 | /* font-style: italic; */ 558 | font-weight: 400; 559 | } 560 | .corousel .item { 561 | height: 500px; 562 | } 563 | .corousel .item .corousel-img { 564 | position: absolute; 565 | top: -9999px; 566 | left: -9999px; 567 | right: -9999px; 568 | bottom: -9999px; 569 | margin: auto; 570 | } 571 | .corousel .item .caption h1 { 572 | font-size: 7.5rem; 573 | text-shadow: 1px 1px 2px black; 574 | } 575 | .corousel .item .caption p { 576 | padding: 0; 577 | margin: 0; 578 | font-size: 45px; 579 | text-shadow: 2px 2px 2px black; 580 | } 581 | span { 582 | color: rgb(212, 175, 55); 583 | } 584 | .caption img { 585 | height: 35rem; 586 | opacity: 0.8; 587 | padding: 0; 588 | margin: 0; 589 | transition: 0.5s; 590 | } 591 | .caption img:hover { 592 | /* height: 38rem; */ 593 | opacity: 1; 594 | padding: 0; 595 | margin: 0; 596 | -webkit-filter: drop-shadow(5px 5px 5px #222); 597 | filter: drop-shadow(3px 3px 3px rgb(0, 0, 0)); 598 | } 599 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Provider } from "react-redux"; 3 | import ReactDOM from "react-dom"; 4 | import "./index.css"; 5 | import App from "./App"; 6 | import * as serviceWorker from "./serviceWorker"; 7 | import store from "./store"; 8 | // import "../../node_modules/bootstrap/dist/css/bootstrap.css" 9 | 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById("root") 16 | ); 17 | 18 | serviceWorker.unregister(); 19 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/reducers/cartReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | CART_ADD_ITEM, 3 | CART_REMOVE_ITEM, 4 | CART_SAVE_SHIPPING, 5 | CART_SAVE_PAYMENT 6 | } from "../constants/cartConstants"; 7 | 8 | function cartReducer( 9 | state = { cartItems: [], shipping: {}, payment: {} }, 10 | action 11 | ) { 12 | switch (action.type) { 13 | case CART_ADD_ITEM: 14 | const item = action.payload; 15 | const product = state.cartItems.find( 16 | x => x.product === item.product 17 | ); 18 | if (product) { 19 | return { 20 | cartItems: state.cartItems.map(x => 21 | x.product === product.product ? item : x 22 | ) 23 | }; 24 | } 25 | return { cartItems: [...state.cartItems, item] }; 26 | case CART_REMOVE_ITEM: 27 | return { 28 | cartItems: state.cartItems.filter( 29 | x => x.product !== action.payload 30 | ) 31 | }; 32 | case CART_SAVE_SHIPPING: 33 | return { ...state, shipping: action.payload }; 34 | case CART_SAVE_PAYMENT: 35 | return { ...state, payment: action.payload }; 36 | default: 37 | return state; 38 | } 39 | } 40 | 41 | export { cartReducer }; 42 | -------------------------------------------------------------------------------- /frontend/src/reducers/productReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | PRODUCT_LIST_REQUEST, 3 | PRODUCT_LIST_SUCCESS, 4 | PRODUCT_LIST_FAIL, 5 | PRODUCT_DETAILS_REQUEST, 6 | PRODUCT_DETAILS_SUCCESS, 7 | PRODUCT_DETAILS_FAIL, 8 | PRODUCT_SAVE_REQUEST, 9 | PRODUCT_SAVE_SUCCESS, 10 | PRODUCT_SAVE_FAIL, 11 | PRODUCT_DELETE_REQUEST, 12 | PRODUCT_DELETE_SUCCESS, 13 | PRODUCT_DELETE_FAIL 14 | } from "../constants/productConstants"; 15 | 16 | function productListReducer(state = { products: [] }, action) { 17 | switch (action.type) { 18 | case PRODUCT_LIST_REQUEST: 19 | return { loading: true, products: [] }; 20 | case PRODUCT_LIST_SUCCESS: 21 | return { loading: false, products: action.payload }; 22 | case PRODUCT_LIST_FAIL: 23 | return { loading: false, error: action.payload }; 24 | default: 25 | return state; 26 | } 27 | } 28 | function productSaveReducer(state = { product: { reviews: [] } }, action) { 29 | switch (action.type) { 30 | case PRODUCT_SAVE_REQUEST: 31 | return { loading: true }; 32 | case PRODUCT_SAVE_SUCCESS: 33 | return { loading: false, success: true, product: action.payload }; 34 | case PRODUCT_SAVE_FAIL: 35 | return { loading: false, error: action.payload }; 36 | default: 37 | return state; 38 | } 39 | } 40 | function productDeleteReducer(state = { product: { reviews: [] } }, action) { 41 | switch (action.type) { 42 | case PRODUCT_DELETE_REQUEST: 43 | return { loading: true }; 44 | case PRODUCT_DELETE_SUCCESS: 45 | return { loading: false, product: action.payload, success: true }; 46 | case PRODUCT_DELETE_FAIL: 47 | return { loading: false, error: action.payload }; 48 | default: 49 | return state; 50 | } 51 | } 52 | function productDetailsReducer(state = { product: { reviews: [] } }, action) { 53 | switch (action.type) { 54 | case PRODUCT_DETAILS_REQUEST: 55 | return { loading: true }; 56 | case PRODUCT_DETAILS_SUCCESS: 57 | return { loading: false, product: action.payload }; 58 | case PRODUCT_DETAILS_FAIL: 59 | return { loading: false, error: action.payload }; 60 | default: 61 | return state; 62 | } 63 | } 64 | export { 65 | productListReducer, 66 | productDetailsReducer, 67 | productSaveReducer, 68 | productDeleteReducer 69 | }; 70 | -------------------------------------------------------------------------------- /frontend/src/reducers/userReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | USER_SIGNIN_REQUEST, 3 | USER_SIGNIN_SUCCESS, 4 | USER_SIGNIN_FAIL, 5 | USER_REGISTER_REQUEST, 6 | USER_REGISTER_SUCCESS, 7 | USER_REGISTER_FAIL 8 | } from "../constants/userConstants"; 9 | 10 | function userSigninReducer(state = {}, action) { 11 | switch (action.type) { 12 | case USER_SIGNIN_REQUEST: 13 | return { loading: true }; 14 | case USER_SIGNIN_SUCCESS: 15 | return { loading: false, userInfo: action.payload }; 16 | case USER_SIGNIN_FAIL: 17 | return { loading: false, error: action.payload }; 18 | default: 19 | return state; 20 | } 21 | } 22 | function userRegisterReducer(state = {}, action) { 23 | switch (action.type) { 24 | case USER_REGISTER_REQUEST: 25 | return { loading: true }; 26 | case USER_REGISTER_SUCCESS: 27 | return { loading: false, userInfo: action.payload }; 28 | case USER_REGISTER_FAIL: 29 | return { loading: false, error: action.payload }; 30 | default: 31 | return state; 32 | } 33 | } 34 | export { userSigninReducer, userRegisterReducer }; 35 | -------------------------------------------------------------------------------- /frontend/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /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/extend-expect'; 6 | -------------------------------------------------------------------------------- /frontend/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, combineReducers, compose } from "redux"; 2 | import { 3 | productListReducer, 4 | productDetailsReducer, 5 | productSaveReducer, 6 | productDeleteReducer 7 | } from "./reducers/productReducers"; 8 | import { userSigninReducer, userRegisterReducer } from "./reducers/userReducer"; 9 | import { cartReducer } from "./reducers/cartReducers"; 10 | import thunk from "redux-thunk"; 11 | import * as Cookie from "js-cookie"; 12 | 13 | const cartItems = Cookie.getJSON("cartItems") || []; 14 | const userInfo = Cookie.getJSON("userInfo") || null; 15 | 16 | const initialState = { cart: { cartItems, shipping:{}, payment:{} }, userSignin: { userInfo } }; 17 | const reducer = combineReducers({ 18 | cart: cartReducer, 19 | userSignin: userSigninReducer, 20 | userRegister: userRegisterReducer, 21 | productList: productListReducer, 22 | productDetails: productDetailsReducer, 23 | productSave: productSaveReducer, 24 | productDelete: productDeleteReducer 25 | }); 26 | 27 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 28 | const store = createStore( 29 | reducer, 30 | initialState, 31 | composeEnhancers(applyMiddleware(thunk)) 32 | ); 33 | export default store; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "engines": { 7 | "node": "12.x", 8 | "npm": "6.x" 9 | }, 10 | "scripts": { 11 | "start": "nodemon --watch backend --exec babel-node backend/server.js", 12 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install cd client && npm run build cd client" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS.git" 17 | }, 18 | "author": "Suhas Salian", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS/issues" 22 | }, 23 | "homepage": "https://github.com/suhassalian27/E-Commerce-Website-using-ReactJS-NodeJS#readme", 24 | "dependencies": { 25 | "@babel/cli": "^7.10.4", 26 | "@babel/core": "^7.10.4", 27 | "@babel/node": "^7.10.4", 28 | "@babel/preset-env": "^7.10.4", 29 | "body-parser": "^1.19.0", 30 | "bootstrap": "^4.5.0", 31 | "dotenv": "^8.2.0", 32 | "express": "^4.17.1", 33 | "jsonwebtoken": "^8.5.1", 34 | "mongodb": "^3.5.9", 35 | "mongoose": "^5.9.22", 36 | "nodemon": "^2.0.4", 37 | "react-bootstrap": "^1.2.2" 38 | } 39 | } -------------------------------------------------------------------------------- /template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | 17 | 18 |
19 | 23 |
24 | 38 |
39 |
40 |
    41 |
  • 42 |
    43 | product 48 | 53 |
    Nike
    54 |
    $60
    55 |
    56 | 4.5 Stars (10 Reviews) 57 |
    58 |
    59 |
  • 60 |
  • 61 |
    62 | product 67 | 72 |
    Nike
    73 |
    $60
    74 |
    75 | 4.5 Stars (10 Reviews) 76 |
    77 |
    78 |
  • 79 |
  • 80 |
    81 | product 86 | 91 |
    Nike
    92 |
    $60
    93 |
    94 | 4.5 Stars (10 Reviews) 95 |
    96 |
    97 |
  • 98 |
  • 99 |
    100 | product 105 | 110 |
    Nike
    111 |
    $60
    112 |
    113 | 4.5 Stars (10 Reviews) 114 |
    115 |
    116 |
  • 117 |
  • 118 |
    119 | product 124 | 129 |
    Nike
    130 |
    $60
    131 |
    132 | 4.5 Stars (10 Reviews) 133 |
    134 |
    135 |
  • 136 |
  • 137 |
    138 | product 143 | 148 |
    Nike
    149 |
    $60
    150 |
    151 | 4.5 Stars (10 Reviews) 152 |
    153 |
    154 |
  • 155 |
156 |
157 |
158 |
159 | All right reserved 160 |
161 |
162 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /template/style.css: -------------------------------------------------------------------------------- 1 | html{ 2 | font-size: 62.5%; 3 | box-sizing: border-box; 4 | } 5 | body{ 6 | font: 1.6rem Helvetica; 7 | height: 100vh; 8 | margin: 0%; 9 | } 10 | .grid-container{ 11 | display: grid; 12 | grid-template-areas: 13 | "header" 14 | "main" 15 | "footer"; 16 | grid-template-columns: 1fr; 17 | grid-template-rows: 5rem 1fr 5rem; 18 | height: 100%; 19 | } 20 | .header{ 21 | grid-area: header; 22 | background-color: rgb(29, 42, 54); 23 | color: white; 24 | display: flex; 25 | justify-content: space-between; 26 | align-items: center; 27 | padding: .5rem; 28 | } 29 | .brand a{ 30 | color: white; 31 | font-size: 2.5rem; 32 | font-weight: bold; 33 | text-decoration: none; 34 | } 35 | .header-links a{ 36 | color: white; 37 | text-decoration: none; 38 | } 39 | .header-links a:hover{ 40 | color: rgb(255, 136, 0); 41 | } 42 | .main{ 43 | grid-area: main; 44 | } 45 | .footer{ 46 | grid-area: footer; 47 | background-color: rgb(29, 42, 54); 48 | color: white; 49 | display: flex; 50 | justify-content: center; 51 | align-items: center; 52 | } 53 | a{ 54 | text-decoration: none; 55 | } 56 | a:hover{ 57 | color: rgb(255, 136, 0); 58 | } 59 | /* Home Screen */ 60 | .products{ 61 | display: flex; 62 | justify-content: center; 63 | align-items: center; 64 | flex-wrap: wrap; 65 | } 66 | .products li{ 67 | list-style: none; 68 | padding: 0; 69 | flex: 0 1 34rem; 70 | margin: 1 rem; 71 | height: 50rem; 72 | border-bottom: .1rem #c0c0c0 solid; 73 | } 74 | .product{ 75 | display: flex; 76 | flex-direction: column; 77 | justify-content: space-between; 78 | height: 100%; 79 | margin: .5rem; 80 | } 81 | .product-name{ 82 | font-size: 2rem; 83 | font-weight: bold; 84 | 85 | } 86 | .product-brand{ 87 | font-size:1.2rem; 88 | color: #808080; 89 | } 90 | .product-price{ 91 | font-size: 2.5rem; 92 | font-weight: bold; 93 | } 94 | .product-image{ 95 | max-width: 34rem; 96 | max-height: 34rem; 97 | } 98 | .product-rating{ 99 | margin-bottom: 1rem; 100 | } 101 | /* SideBar */ 102 | .brand button{ 103 | font-size: 3rem; 104 | padding: .5rem; 105 | background: none; 106 | border:none; 107 | color: #ffffff; 108 | cursor:pointer; 109 | } 110 | .sidebar{ 111 | position: fixed; 112 | transform: translate(-30rem); 113 | width: 30rem; 114 | background-color: white; 115 | height: 100%; 116 | } 117 | .open{ 118 | transform: translate(0); 119 | } 120 | .sidebar-close-button{ 121 | border-radius: 50%; 122 | border: .1 rem black; 123 | width: 3rem; 124 | height: 3rem; 125 | padding: .5rem; 126 | font-size: 2rem; 127 | padding-top: 0; 128 | cursor: pointer; 129 | position: absolute; 130 | right: 1rem; 131 | top: 1.5rem; 132 | } 133 | .sidebar ul{ 134 | list-style: none; 135 | } --------------------------------------------------------------------------------