├── backend ├── .gitignore ├── controllers │ ├── authController.js │ ├── productController.js │ └── uploadController.js ├── index.js ├── middlewares │ └── auth.js ├── models │ ├── Product.js │ └── User.js ├── package-lock.json ├── package.json └── public │ └── images │ ├── 1671795213121sunglasses1PNG.png │ ├── 1671795213121sunglasses2PNG.png │ ├── 1671795723033tshirt1.png │ ├── 1671795723033tshirt2.png │ ├── 1671795895201tshirt1.png │ ├── 1671795895201tshirt2.png │ ├── 1671796096274tshirt1.png │ ├── 1671796096274tshirt2.png │ ├── 1671811896470insta.png │ ├── 1671811900336insta.png │ ├── 1671811900336realestateui.png │ ├── 1671812610573sunglasses1PNG.png │ ├── 1671812610573sunglasses2PNG.png │ ├── 1671812959741sunglasses1PNG.png │ ├── 1671812959741sunglasses2PNG.png │ ├── 1671813117206sunglasses1PNG.png │ ├── 1671813117206sunglasses2PNG.png │ ├── 1671813129683sunglasses1PNG.png │ ├── 1671813129683sunglasses2PNG.png │ ├── 1671813156394sunglasses1PNG.png │ ├── 1671813156394sunglasses2PNG.png │ ├── 1671813178862sunglasses1PNG.png │ ├── 1671813178862sunglasses2PNG.png │ ├── 1671813231158sunglasses1PNG.png │ ├── 1671813231158sunglasses2PNG.png │ ├── 1671816385699sunglasses1PNG.png │ ├── 1671816385699sunglasses2PNG.png │ ├── 1671816416290phoneimg1.png │ ├── 1671816416290phoneimg2.png │ ├── 1671816477176headphonesimg1.png │ ├── 1671816477176headphonesimg2.png │ ├── 1672345567830tshirt1.png │ └── 1672345567830tshirt2.png └── client ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.css ├── App.js ├── assets ├── sunglasses1.avif ├── sunglasses1PNG.png ├── sunglasses2.avif └── sunglasses2PNG.png ├── components ├── addressPage │ ├── AddressPage.jsx │ └── addressPage.module.css ├── cart │ ├── Cart.jsx │ └── cart.module.css ├── checkout │ ├── Checkout.jsx │ └── checkout.module.css ├── create │ ├── Create.jsx │ └── create.module.css ├── final │ ├── Final.jsx │ └── final.module.css ├── footer │ ├── Footer.jsx │ └── footer.module.css ├── home │ ├── Home.jsx │ └── home.module.css ├── list │ ├── List.jsx │ └── list.module.css ├── login │ ├── Login.jsx │ └── login.module.css ├── navbar │ ├── Navbar.jsx │ └── navbar.module.css ├── productCard │ ├── ProductCard.jsx │ └── productCard.module.css ├── productDetail │ ├── ProductDetail.jsx │ └── productDetail.module.css └── register │ ├── Register.jsx │ └── register.module.css ├── helpers └── numToStars.js ├── index.js └── redux ├── addressSlice.js ├── authSlice.js ├── cartSlice.js └── store.js /backend/.gitignore: -------------------------------------------------------------------------------- 1 | /.env 2 | /node_modules -------------------------------------------------------------------------------- /backend/controllers/authController.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/User"); 2 | const bcrypt = require('bcrypt'); 3 | const jwt = require("jsonwebtoken"); 4 | const JWT_SECRET = "123456"; 5 | 6 | const authController = require("express").Router(); 7 | 8 | authController.post("/register", async (req, res) => { 9 | try { 10 | const isExisting = await User.findOne({ email: req.body.email }); 11 | if (isExisting) { 12 | return res.status(404).json({ msg: "User already registered" }); 13 | } 14 | 15 | if (req.body.username === "" || req.body.email === "" || req.body.password === "") { 16 | return res.status(500).json({ msg: "All fields must be populated" }); 17 | } 18 | 19 | const hashedPassword = await bcrypt.hash(req.body.password, 10); 20 | 21 | const user = await User.create({ ...req.body, password: hashedPassword }); 22 | await user.save(); 23 | 24 | 25 | const {password, ...others} = user._doc 26 | const token = createToken(user); 27 | 28 | return res.status(201).json({ others, token }); 29 | } catch (error) { 30 | return res.status(500).json(error); 31 | } 32 | }); 33 | 34 | authController.post("/login", async (req, res) => { 35 | const { email, password } = req.body; 36 | 37 | if (email === "" || password === "") { 38 | return res.status(500).json({ msg: "All fields must be populated" }); 39 | } 40 | 41 | try { 42 | const user = await User.findOne({ email }); 43 | if (!user) { 44 | return res.status(404).json({ msg: "Invalid credentials" }); 45 | } 46 | const comparePass = await bcrypt.compare(req.body.password, user.password); 47 | if (!comparePass) { 48 | return res.status(404).json({ msg: "Invalid credentials" }); 49 | } 50 | 51 | const {password, ...others} = user._doc 52 | const token = createToken(user); 53 | 54 | return res.status(200).json({ others, token }); 55 | } catch (error) { 56 | return res.status(500).json(error); 57 | } 58 | }); 59 | 60 | const createToken = (user) => { 61 | const payload = { 62 | id: user._id.toString(), 63 | email: user.email, 64 | }; 65 | 66 | const token = jwt.sign(payload, JWT_SECRET); 67 | 68 | return token; 69 | }; 70 | 71 | module.exports = authController 72 | -------------------------------------------------------------------------------- /backend/controllers/productController.js: -------------------------------------------------------------------------------- 1 | const verifyToken = require("../middlewares/auth"); 2 | const Product = require("../models/Product"); 3 | const productController = require("express").Router(); 4 | 5 | // get all products 6 | productController.get("/", async (req, res) => { 7 | try { 8 | let products; 9 | products = await Product.find({}); 10 | if (!products) { 11 | return res.status(404).json({ msg: "Error occured" }); 12 | } 13 | 14 | return res.status(200).json(products); 15 | } catch (error) { 16 | return res.status(500).json(error); 17 | } 18 | }); 19 | 20 | // get a product 21 | productController.get("/:id", async (req, res) => { 22 | try { 23 | let product; 24 | product = await Product.findById(req.params.id); 25 | if (!product) { 26 | return res.status(404).json({ msg: "Error occured" }); 27 | } 28 | 29 | return res.status(200).json(product); 30 | } catch (error) { 31 | return res.status(500).json(error); 32 | } 33 | }); 34 | 35 | // create a product 36 | productController.post("/", verifyToken, async (req, res) => { 37 | try { 38 | const product = await Product.create({...req.body}); 39 | 40 | await product.save(); 41 | return res.status(200).json(product); 42 | } catch (error) { 43 | return res.status(500).json(error); 44 | } 45 | }); 46 | 47 | module.exports = productController; 48 | -------------------------------------------------------------------------------- /backend/controllers/uploadController.js: -------------------------------------------------------------------------------- 1 | const uploadController = require("express").Router(); 2 | 3 | const multer = require("multer"); 4 | const verifyToken = require("../middlewares/auth"); 5 | 6 | const storage = multer.diskStorage({ 7 | destination: (req, file, cb) => { 8 | cb(null, "public/images"); 9 | }, 10 | filename: (req, file, cb) => { 11 | cb(null, req.body.filename); 12 | }, 13 | }); 14 | 15 | const upload = multer({ 16 | storage: storage, 17 | }); 18 | 19 | uploadController.post("/firstImg", verifyToken, upload.single("firstImg"), async (req, res) => { 20 | try { 21 | return res.status(200).json("File uploded successfully"); 22 | } catch (error) { 23 | console.error(error); 24 | } 25 | }); 26 | 27 | 28 | uploadController.post("/secondImg", verifyToken, upload.single("secondImg"), async (req, res) => { 29 | try { 30 | return res.status(200).json("File uploded successfully"); 31 | } catch (error) { 32 | console.error(error); 33 | } 34 | }); 35 | 36 | module.exports = uploadController; 37 | -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const express = require("express"); 3 | const mongoose = require("mongoose"); 4 | const cors = require("cors"); 5 | const authController = require("./controllers/authController"); 6 | const productController = require("./controllers/productController"); 7 | const uploadController = require("./controllers/uploadController"); 8 | const app = express(); 9 | 10 | // db connecting 11 | mongoose.connect(process.env.MONGO_URL, () => console.log("Db is connected")); 12 | 13 | // middlewares 14 | app.use(cors()); 15 | app.use(express.json()); 16 | app.use(express.urlencoded({ extended: true })); 17 | // to serve images inside public folder 18 | app.use('/images', express.static('public/images')); 19 | 20 | app.use("/auth", authController); 21 | app.use("/product", productController); 22 | app.use('/upload', uploadController) 23 | 24 | const port = process.env.PORT || 5000; 25 | 26 | app.listen(port, () => console.log("Server has been started")); 27 | -------------------------------------------------------------------------------- /backend/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | const verifyToken = async(req, res, next) => { 4 | if(!req.headers.authorization) return res.status(403).json({msg: 'Not authorized. No token'}) 5 | 6 | if(req.headers.authorization.startsWith("Bearer ")){ 7 | const token = req.headers.authorization.split(' ')[1] // get the second el which is the token itself(without the "Bearer" text) 8 | jwt.verify(token, process.env.JWT_SECRET, (err, data) => { 9 | if(err) return res.status(403).json({msg: 'Wrong or expired token.'}) 10 | else { 11 | req.user = data // an object with only the user id as its property 12 | next() 13 | } 14 | }) 15 | } else { 16 | return res.status(403).json({msg: 'Not authorized. No token'}) 17 | } 18 | } 19 | 20 | module.exports = verifyToken 21 | -------------------------------------------------------------------------------- /backend/models/Product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const ProductSchema = new mongoose.Schema( 4 | { 5 | title: { type: String, required: true, unique: true }, 6 | desc: { type: String, required: true }, 7 | firstImg: { type: String, required: true }, 8 | secondImg: { type: String, required: true }, 9 | price: { type: Number, required: true }, 10 | stars: {type: Number, default: 4} 11 | }, 12 | { timestamps: true } 13 | ); 14 | 15 | module.exports = mongoose.model("Product", ProductSchema); -------------------------------------------------------------------------------- /backend/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const UserSchema = new mongoose.Schema( 4 | { 5 | username: { 6 | type: String, 7 | required: true, 8 | unique: true, 9 | }, 10 | email: { 11 | type: String, 12 | required: true, 13 | unique: true, 14 | }, 15 | password: { 16 | type: String, 17 | required: true, 18 | }, 19 | }, 20 | { timestamps: true } 21 | ); 22 | 23 | module.exports = mongoose.model("User", UserSchema); 24 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcrypt": "^5.1.0", 14 | "cookie-parser": "^1.4.6", 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.0.3", 17 | "express": "^4.18.2", 18 | "jsonwebtoken": "^8.5.1", 19 | "mongoose": "^6.7.0", 20 | "multer": "^1.4.5-lts.1", 21 | "nodemailer": "^6.8.0", 22 | "nodemon": "^2.0.20" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/public/images/1671795213121sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671795213121sunglasses1PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671795213121sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671795213121sunglasses2PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671795723033tshirt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671795723033tshirt1.png -------------------------------------------------------------------------------- /backend/public/images/1671795723033tshirt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671795723033tshirt2.png -------------------------------------------------------------------------------- /backend/public/images/1671795895201tshirt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671795895201tshirt1.png -------------------------------------------------------------------------------- /backend/public/images/1671795895201tshirt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671795895201tshirt2.png -------------------------------------------------------------------------------- /backend/public/images/1671796096274tshirt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671796096274tshirt1.png -------------------------------------------------------------------------------- /backend/public/images/1671796096274tshirt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671796096274tshirt2.png -------------------------------------------------------------------------------- /backend/public/images/1671811896470insta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671811896470insta.png -------------------------------------------------------------------------------- /backend/public/images/1671811900336insta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671811900336insta.png -------------------------------------------------------------------------------- /backend/public/images/1671811900336realestateui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671811900336realestateui.png -------------------------------------------------------------------------------- /backend/public/images/1671812610573sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671812610573sunglasses1PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671812610573sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671812610573sunglasses2PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671812959741sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671812959741sunglasses1PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671812959741sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671812959741sunglasses2PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813117206sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813117206sunglasses1PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813117206sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813117206sunglasses2PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813129683sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813129683sunglasses1PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813129683sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813129683sunglasses2PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813156394sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813156394sunglasses1PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813156394sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813156394sunglasses2PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813178862sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813178862sunglasses1PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813178862sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813178862sunglasses2PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813231158sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813231158sunglasses1PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671813231158sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671813231158sunglasses2PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671816385699sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671816385699sunglasses1PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671816385699sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671816385699sunglasses2PNG.png -------------------------------------------------------------------------------- /backend/public/images/1671816416290phoneimg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671816416290phoneimg1.png -------------------------------------------------------------------------------- /backend/public/images/1671816416290phoneimg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671816416290phoneimg2.png -------------------------------------------------------------------------------- /backend/public/images/1671816477176headphonesimg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671816477176headphonesimg1.png -------------------------------------------------------------------------------- /backend/public/images/1671816477176headphonesimg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1671816477176headphonesimg2.png -------------------------------------------------------------------------------- /backend/public/images/1672345567830tshirt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1672345567830tshirt1.png -------------------------------------------------------------------------------- /backend/public/images/1672345567830tshirt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/backend/public/images/1672345567830tshirt2.png -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.1", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-icons": "^4.7.1", 13 | "react-redux": "^8.0.5", 14 | "react-router-dom": "^6.6.0", 15 | "react-scripts": "5.0.1", 16 | "redux-persist": "^6.0.0", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 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 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | React App 22 | 23 | 24 | 25 |
26 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | *{ 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | font-family: 'Roboto'; 6 | user-select: none; 7 | } 8 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | import Navbar from "./components/navbar/Navbar"; 3 | import {Routes, Route} from 'react-router-dom' 4 | import Footer from "./components/footer/Footer"; 5 | import Home from "./components/home/Home"; 6 | import Cart from "./components/cart/Cart"; 7 | import ProductDetail from "./components/productDetail/ProductDetail"; 8 | import Create from "./components/create/Create"; 9 | import Login from "./components/login/Login"; 10 | import Register from "./components/register/Register"; 11 | import AddressPage from "./components/addressPage/AddressPage"; 12 | import { useSelector } from "react-redux"; 13 | import Checkout from './components/checkout/Checkout'; 14 | import Final from './components/final/Final'; 15 | 16 | function App() { 17 | const {user} = useSelector((state) => state.auth) 18 | 19 | return ( 20 |
21 | 22 | 23 | : } /> 24 | : } /> 25 | : } /> 26 | : } /> 27 | : } /> 28 | : } /> 29 | : } /> 30 | : }/> 31 | : } /> 32 | 33 |
35 | ); 36 | } 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /client/src/assets/sunglasses1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/client/src/assets/sunglasses1.avif -------------------------------------------------------------------------------- /client/src/assets/sunglasses1PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/client/src/assets/sunglasses1PNG.png -------------------------------------------------------------------------------- /client/src/assets/sunglasses2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/client/src/assets/sunglasses2.avif -------------------------------------------------------------------------------- /client/src/assets/sunglasses2PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/ecommerce-fullstack/2947a2cca34b13279b187da89bd491a350050c9d/client/src/assets/sunglasses2PNG.png -------------------------------------------------------------------------------- /client/src/components/addressPage/AddressPage.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useState } from "react"; 3 | import { useNavigate } from "react-router-dom"; 4 | import { useDispatch } from "react-redux"; 5 | import { submitAddress } from "../../redux/addressSlice"; 6 | import classes from "./addressPage.module.css"; 7 | 8 | const AddressPage = () => { 9 | const [addressData, setAddressData] = useState({}); 10 | const [errorMsg, setErrorMsg] = useState(false); 11 | const dispatch = useDispatch(); 12 | const navigate = useNavigate(); 13 | 14 | const handleState = (e) => { 15 | setAddressData((prev) => { 16 | return { ...prev, [e.target.name]: e.target.value }; 17 | }); 18 | }; 19 | 20 | 21 | const handleSubmit = (e) => { 22 | e.preventDefault(); 23 | // check form 24 | const isEmpty = Object.values(addressData).some((v) => !v); 25 | const isFilled = Object.values(addressData).length < 5 26 | if (isFilled || isEmpty) { 27 | setErrorMsg((prev) => true); 28 | setTimeout(() => { 29 | setErrorMsg((prev) => false); 30 | }, 2500); 31 | return; 32 | } 33 | dispatch(submitAddress(addressData)); 34 | navigate("/checkout"); 35 | }; 36 | 37 | return ( 38 |
39 |
40 |

Address and Details

41 |
42 | 48 | 54 | 60 | 66 | 72 | 75 |
76 | {errorMsg && All fields must filled!} 77 |
78 |
79 | ); 80 | }; 81 | 82 | export default AddressPage; 83 | -------------------------------------------------------------------------------- /client/src/components/addressPage/addressPage.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: calc(100vh - 265px); 4 | margin-top: 150px; 5 | } 6 | 7 | .wrapper { 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | } 12 | 13 | form { 14 | display: flex; 15 | flex-direction: column; 16 | gap: 12px; 17 | margin-top: 35px; 18 | } 19 | 20 | form > input { 21 | outline: none; 22 | border: none; 23 | padding-left: 8px; 24 | border-bottom: 1px solid #888; 25 | height: 45px; 26 | width: 300px; 27 | font-size: 18px; 28 | } 29 | 30 | form > input:focus { 31 | border-color: #333; 32 | } 33 | 34 | .submitBtn { 35 | width: 75%; 36 | margin: 0 auto; 37 | margin-top: 30px; 38 | outline: none; 39 | font-size: 18px; 40 | border: 1px solid transparent; 41 | background-color: rgb(73, 73, 224); 42 | color: #fff; 43 | padding: 0.75rem 1rem; 44 | border-radius: 20px; 45 | cursor: pointer; 46 | } 47 | 48 | .submitBtn:hover { 49 | background-color: #fff; 50 | color: rgb(73, 73, 224); 51 | border-color: rgb(73, 73, 224); 52 | } 53 | 54 | .errorMsg{ 55 | height: 70px; 56 | width: 250px; 57 | text-align: center; 58 | border-radius: 20px; 59 | line-height: 1.5rem; 60 | padding: 0.75rem 1.5rem; 61 | background-color: #f00; 62 | color: #fff; 63 | position: absolute; 64 | top: 3rem; 65 | right: 3rem; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | } 70 | -------------------------------------------------------------------------------- /client/src/components/cart/Cart.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classes from "./cart.module.css"; 3 | import { Link } from "react-router-dom"; 4 | import { BsFillTrashFill } from "react-icons/bs"; 5 | import { CiCircleRemove } from "react-icons/ci"; 6 | import { useDispatch, useSelector } from "react-redux"; 7 | import { 8 | removeProduct, 9 | emptyCart, 10 | toggleShowCart, 11 | } from "../../redux/cartSlice"; 12 | 13 | const Cart = () => { 14 | const { products } = useSelector((state) => state.cart); 15 | const dispatch = useDispatch(); 16 | let total = 0; 17 | products?.length > 0 && 18 | products?.map( 19 | (product) => (total += Number(product.price) * Number(product.quantity)) 20 | ); 21 | 22 | const removeFromCart = (id) => { 23 | dispatch(removeProduct({ id })); 24 | }; 25 | 26 | const resetCart = () => { 27 | dispatch(emptyCart()); 28 | }; 29 | 30 | const handleCloseCart = () => { 31 | dispatch(toggleShowCart()) 32 | } 33 | 34 | return ( 35 |
36 |
37 | {total > 0 &&

Cart Items

} 38 |
39 | {products?.length === 0 ? ( 40 |

No products yet in cart.

41 | ) : ( 42 | products?.map((product) => ( 43 |
44 | 45 | 50 | 51 |
52 |

{product.title}

53 | 54 | {product.quantity} x $ {product.price} 55 | 56 |
57 | removeFromCart(product.id)} 59 | className={classes.trashIcon} 60 | /> 61 |
62 | )) 63 | )} 64 |
65 | {total > 0 && ( 66 | <> 67 |
68 | Subtotal 69 | 70 | $ {Number(total).toFixed(2)} 71 | 72 |
73 | Proceed to checkout 74 | 75 | )} 76 |
77 | {total > 0 && ( 78 |
79 | Reset Cart 80 |
81 | )} 82 | 86 |
87 | ); 88 | }; 89 | 90 | export default Cart; 91 | -------------------------------------------------------------------------------- /client/src/components/cart/cart.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: absolute; 3 | top: 1rem; 4 | right: 1rem; 5 | /* width: 400px; 6 | min-height: 600px; 7 | overflow-y: auto; */ 8 | -webkit-box-shadow: 0px 0px 7px -5px rgba(0, 0, 0, 0.75); 9 | box-shadow: 0px 0px 7px -5px rgba(0, 0, 0, 0.75); 10 | background-color: #fff; 11 | z-index: 3; 12 | } 13 | 14 | .wrapper { 15 | max-height: 500px; 16 | padding: 1.5rem 2.5rem; 17 | width: 375px; 18 | overflow-y: auto; 19 | position: sticky; 20 | } 21 | 22 | .title { 23 | text-align: center; 24 | margin-bottom: 30px; 25 | } 26 | 27 | .cartItems { 28 | display: flex; 29 | flex-direction: column; 30 | gap: 35px; 31 | } 32 | 33 | .cartItem { 34 | display: flex; 35 | gap: 10px; 36 | margin: 0.5rem 0; 37 | width: 250px; 38 | position: relative; 39 | } 40 | 41 | .priceAndTitle { 42 | display: flex; 43 | flex-direction: column; 44 | justify-content: space-between; 45 | } 46 | 47 | .productTitle { 48 | text-transform: capitalize; 49 | font-size: 18px; 50 | color: #777; 51 | margin-bottom: 5px; 52 | } 53 | 54 | .price > span { 55 | font-size: 15px; 56 | } 57 | 58 | .img { 59 | height: 90px; 60 | object-fit: cover; 61 | } 62 | 63 | .trashIcon { 64 | position: absolute; 65 | top: 0; 66 | right: 0; 67 | font-size: 20px; 68 | min-width: 20px; 69 | min-height: 20px; 70 | cursor: pointer; 71 | transition: 250ms all ease-in-out; 72 | } 73 | 74 | .trashIcon:hover { 75 | transform: translateY(-2px); 76 | } 77 | 78 | .subtotal { 79 | display: flex; 80 | align-items: center; 81 | justify-content: space-between; 82 | margin-top: 1.5rem; 83 | font-size: 20px; 84 | } 85 | 86 | .checkoutBtn { 87 | margin: 1rem 0; 88 | text-decoration: none; 89 | background-color: rgb(57, 57, 206); 90 | color: #fff; 91 | border-radius: 12px; 92 | width: 175px; 93 | padding: 0.4rem 0.8rem; 94 | cursor: pointer; 95 | display: flex; 96 | align-items: center; 97 | justify-content: center; 98 | border: 1px solid transparent; 99 | transition: 150ms all; 100 | } 101 | 102 | .checkoutBtn:hover { 103 | color: rgb(57, 57, 206); 104 | background-color: #fff; 105 | border-color: rgb(57, 57, 206); 106 | } 107 | 108 | .resetCart { 109 | position: sticky; 110 | bottom: 0.25rem; 111 | left: 0.25rem; 112 | color: #f00; 113 | font-size: 19px; 114 | font-weight: 500; 115 | background-color: transparent; 116 | cursor: pointer; 117 | padding: 1.5rem; 118 | } 119 | 120 | .subtotal { 121 | margin-top: 2.5rem; 122 | } 123 | 124 | .noProducts { 125 | text-align: center; 126 | font-weight: bold; 127 | font-size: 28px; 128 | } 129 | 130 | .removeIcon { 131 | position: absolute; 132 | top: 0.5rem; 133 | right: 1rem; 134 | font-size: 28px; 135 | cursor: pointer; 136 | transition: 150ms all ease-in-out; 137 | } 138 | 139 | .removeIcon:hover { 140 | fill: #555; 141 | } 142 | 143 | /* dollar sign */ 144 | .totalPrice span { 145 | font-size: 16px; 146 | } 147 | -------------------------------------------------------------------------------- /client/src/components/checkout/Checkout.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { Link } from "react-router-dom"; 4 | import classes from "./checkout.module.css"; 5 | 6 | const Checkout = () => { 7 | const { address } = useSelector((state) => state.address); 8 | const { products } = useSelector((state) => state.cart); 9 | 10 | function totalPriceProducts() { 11 | let totalPrice = 0; 12 | products.map((product) => (totalPrice += (product.price * product.quantity))); 13 | return totalPrice.toFixed(2); 14 | } 15 | 16 | return ( 17 |
18 |
19 |
20 |

Address Data

21 |
22 | {Object.entries(address).map(([key, value]) => ( 23 |
24 |

{key}:

25 | {value} 26 |
27 | ))} 28 |
29 |
30 |
31 |

Products

32 |
33 | {products.map((product) => ( 34 |
35 | 36 | 41 | 42 |
43 |

{product.title}

44 | 45 | {product.quantity} x $ {product.price} 46 | 47 |
48 |
49 | ))} 50 |
51 | 52 | Total price of products:{" "} 53 |
${totalPriceProducts()}
54 |
55 |
56 | 57 | Order 58 | 59 |
60 |
61 | ); 62 | }; 63 | 64 | export default Checkout; 65 | -------------------------------------------------------------------------------- /client/src/components/checkout/checkout.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: calc(100vh - 250px); 4 | margin-top: 100px; 5 | } 6 | 7 | .wrapper { 8 | height: 100%; 9 | width: 100%; 10 | display: flex; 11 | justify-content: center; 12 | gap: 100px; 13 | } 14 | 15 | h1{ 16 | margin-bottom: 30px; 17 | } 18 | 19 | /* top */ 20 | .top { 21 | display: flex; 22 | flex-direction: column; 23 | align-items: flex-start; 24 | } 25 | 26 | .addressData{ 27 | margin-left: 8px; 28 | } 29 | 30 | .info{ 31 | display: flex; 32 | align-items: center; 33 | gap: 4px; 34 | margin: 1rem 0; 35 | font-size: 18px; 36 | } 37 | 38 | .info > h3{ 39 | text-transform: capitalize; 40 | } 41 | 42 | /* bottom */ 43 | .products{ 44 | display: flex; 45 | flex-direction: column; 46 | gap: 10px; 47 | max-height: 375px; 48 | overflow-y: auto; 49 | overflow-x: hidden; 50 | } 51 | 52 | .product { 53 | display: flex; 54 | gap: 10px; 55 | margin: 0.5rem 0; 56 | width: 250px; 57 | position: relative; 58 | } 59 | 60 | .priceAndTitle { 61 | display: flex; 62 | flex-direction: column; 63 | justify-content: space-between; 64 | } 65 | 66 | .productTitle { 67 | text-transform: capitalize; 68 | font-size: 18px; 69 | color: #777; 70 | margin-bottom: 5px; 71 | } 72 | 73 | .price > span { 74 | font-size: 15px; 75 | } 76 | 77 | .img { 78 | height: 90px; 79 | object-fit: cover; 80 | } 81 | 82 | .totalPriceMsg{ 83 | display: flex; 84 | align-items: center; 85 | margin-top: 12px; 86 | gap: 6px; 87 | font-size: 20px; 88 | } 89 | 90 | .totalPrice{ 91 | color: #444; 92 | font-size: 18px; 93 | font-weight: bold; 94 | } 95 | 96 | /* order btn */ 97 | .orderBtn{ 98 | text-decoration: none; 99 | border: 1px solid transparent; 100 | background-color: rgb(235, 170, 50); 101 | color: #fff; 102 | position: absolute; 103 | left: 30vw; 104 | bottom: 25vh; 105 | margin: 0 auto; 106 | width: 200px; 107 | padding: 0.75rem 1rem; 108 | border-radius: 20px; 109 | text-align: center; 110 | cursor: pointer; 111 | } 112 | 113 | .orderBtn:hover{ 114 | background-color: #fff; 115 | border-color: rgb(235, 170, 50); 116 | color: rgb(235, 170, 50); 117 | } -------------------------------------------------------------------------------- /client/src/components/create/Create.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import {AiOutlineCloseCircle} from 'react-icons/ai' 4 | import classes from "./create.module.css"; 5 | 6 | const Create = () => { 7 | const [title, setTitle] = useState(""); 8 | const [desc, setDesc] = useState(""); 9 | const [firstImg, setFirstImg] = useState(""); 10 | const [secondImg, setSecondImg] = useState(""); 11 | const [price, setPrice] = useState(0); 12 | const [stars, setStars] = useState(0); 13 | const navigate = useNavigate(); 14 | 15 | const onChangeFileFirst = (e) => { 16 | setFirstImg(e.target.files[0]); 17 | }; 18 | 19 | const onChangeFileSecond = (e) => { 20 | setSecondImg(e.target.files[0]); 21 | }; 22 | 23 | const handleCreateProduct = async (e) => { 24 | e.preventDefault(); 25 | 26 | try { 27 | const formData = new FormData(); 28 | const formData2 = new FormData(); 29 | 30 | let filename1 = null; 31 | let filename2 = null; 32 | if (firstImg && secondImg) { 33 | filename1 = Date.now() + firstImg.name; 34 | filename2 = Date.now() + secondImg.name; 35 | // for first img 36 | formData.append("filename", filename1); 37 | formData.append("firstImg", firstImg); 38 | // for second img 39 | formData2.append("filename", filename2); 40 | formData2.append("secondImg", secondImg); 41 | 42 | await fetch(`http://localhost:5000/upload/firstImg`, { 43 | method: "POST", 44 | body: formData, 45 | }); 46 | 47 | await fetch(`http://localhost:5000/upload/secondImg`, { 48 | method: "POST", 49 | body: formData2, 50 | }); 51 | } 52 | 53 | // upload product and navigate to product 54 | const res = await fetch("http://localhost:5000/product", { 55 | headers: { 56 | "Content-Type": "application/json", 57 | }, 58 | method: "POST", 59 | body: JSON.stringify({ 60 | title, 61 | desc, 62 | firstImg: filename1, 63 | secondImg: filename2, 64 | price, 65 | stars, 66 | }), 67 | }); 68 | const product = await res.json(); 69 | 70 | navigate(`/productDetail/${product?._id}`); 71 | } catch (error) { 72 | console.error(error); 73 | } 74 | }; 75 | 76 | const handleCloseImg = (numberImg) => { 77 | if(numberImg === 'first'){ 78 | setFirstImg(prev => null) 79 | } else if(numberImg === 'second'){ 80 | setSecondImg(prev => null) 81 | } 82 | } 83 | 84 | return ( 85 |
86 |
87 |

Create product

88 |
89 |
90 | 91 | setTitle(e.target.value)} 94 | className={classes.input} 95 | type="text" 96 | placeholder="title..." 97 | /> 98 |
99 |
100 | 101 |

102 | setDesc(e.target.value)} 105 | className={classes.input} 106 | type="text" 107 | placeholder="description..." 108 | /> 109 |
110 |
111 | 114 | 123 | {firstImg &&

{firstImg.name} handleCloseImg('first')} className={classes.closeIcon}/>

} 124 |
125 |
126 | 129 | 138 | {secondImg && 139 |

{secondImg.name} handleCloseImg('second')} className={classes.closeIcon}/>

140 | } 141 |
142 |
143 | 144 | setPrice(e.target.value)} 148 | className={classes.input} 149 | type="number" 150 | placeholder="price..." 151 | /> 152 |
153 |
154 | 155 | setStars(e.target.value)} 161 | className={classes.input} 162 | type="number" 163 | placeholder="stars..." 164 | /> 165 |
166 |
167 | 170 |
171 |
172 |
173 |
174 | ); 175 | }; 176 | 177 | export default Create; 178 | -------------------------------------------------------------------------------- /client/src/components/create/create.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: calc(100vh - 75px); 3 | width: 100%; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | padding-bottom: 5rem; 8 | } 9 | 10 | .wrapper { 11 | display: flex; 12 | flex-direction: column; 13 | border: 1px solid #444; 14 | border-radius: 22px; 15 | padding: 1.5rem; 16 | } 17 | 18 | .title { 19 | text-align: center; 20 | margin-bottom: 1.5rem; 21 | font-size: 26px; 22 | font-weight: bold; 23 | } 24 | 25 | .inputWrapper { 26 | width: 100%; 27 | display: flex; 28 | align-items: center; 29 | justify-content: flex-end; 30 | margin-bottom: 0.5rem; 31 | } 32 | 33 | .inputWrapper > label { 34 | margin-right: 15px; 35 | width: 33%; 36 | } 37 | 38 | .inputWrapperImgFirst { 39 | width: 400px; 40 | display: flex; 41 | align-items: center; 42 | margin-bottom: 0.5rem; 43 | } 44 | 45 | .inputWrapperImgFirst span { 46 | display: inline-block; 47 | padding: 8px 16px; 48 | background-color: teal; 49 | color: #fff; 50 | border: 1px solid transparent; 51 | cursor: pointer; 52 | transition: 150ms all; 53 | border-radius: 16px; 54 | margin: 0.1rem 2.5rem; 55 | } 56 | 57 | .inputWrapperImgFirst span:hover { 58 | padding: 8px 16px; 59 | background-color: #fff; 60 | color: teal; 61 | border-color: teal; 62 | } 63 | 64 | .inputWrapperImgSecond { 65 | width: 400px; 66 | display: flex; 67 | align-items: center; 68 | margin-bottom: 0.5rem; 69 | } 70 | 71 | .inputWrapperImgSecond span { 72 | display: inline-block; 73 | padding: 8px 16px; 74 | background-color: teal; 75 | color: #fff; 76 | border: 1px solid transparent; 77 | cursor: pointer; 78 | transition: 150ms all; 79 | border-radius: 16px; 80 | margin: 0.1rem 2.5rem; 81 | margin-left: 1rem; 82 | } 83 | 84 | .inputWrapperImgSecond span:hover { 85 | padding: 8px 16px; 86 | background-color: #fff; 87 | color: teal; 88 | border-color: teal; 89 | } 90 | 91 | .imageName{ 92 | position: absolute; 93 | right: 61.75rem; 94 | display: flex; 95 | align-items: center; 96 | gap: 6px; 97 | font-size: 14px; 98 | } 99 | 100 | .closeIcon{ 101 | cursor: pointer; 102 | font-size: 16px; 103 | } 104 | 105 | .input { 106 | padding: 0.5rem; 107 | width: 77.5%; 108 | border-radius: 8px; 109 | border: 1px solid rgb(170, 165, 165); 110 | } 111 | 112 | .input:focus { 113 | outline: none; 114 | border-color: rgb(99, 92, 92); 115 | } 116 | 117 | .buttonWrapper { 118 | display: flex; 119 | align-items: center; 120 | justify-content: center; 121 | margin-top: 2px; 122 | } 123 | 124 | .submitBtn { 125 | width: 50%; 126 | background-color: #cea600; 127 | color: #fff; 128 | padding: 0.75rem 1.25rem; 129 | border-radius: 12px; 130 | border: 1px solid transparent; 131 | cursor: pointer; 132 | transition: 150ms all; 133 | margin-top: 1rem; 134 | } 135 | 136 | .submitBtn:hover { 137 | background-color: #fff; 138 | color: #cea600; 139 | border-color: #cea600; 140 | } 141 | -------------------------------------------------------------------------------- /client/src/components/final/Final.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classes from './final.module.css' 3 | 4 | const Final = () => { 5 | return ( 6 |
7 |
8 |

You have successfully made an order!

9 |

Delivery is usually between 2-4 days

10 |
11 |
12 | ) 13 | } 14 | 15 | export default Final -------------------------------------------------------------------------------- /client/src/components/final/final.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | width: 100%; 3 | height: calc(100vh - 105px); 4 | } 5 | 6 | .wrapper > h2{ 7 | text-align: center; 8 | margin-top: 40px; 9 | } 10 | 11 | .wrapper > p{ 12 | text-align: center; 13 | margin-top: 20px; 14 | color: #666; 15 | font-size: 18px; 16 | } -------------------------------------------------------------------------------- /client/src/components/footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { AiOutlineFacebook, AiOutlineInstagram, AiOutlineTwitter } from "react-icons/ai"; 3 | import classes from "./footer.module.css"; 4 | 5 | const Footer = () => { 6 | return ( 7 |
8 |
9 |
10 |

FAQ

11 | What we sell 12 | How can you order 13 | What currency we accept 14 | Privacy policy 15 |
16 |
17 |

About

18 |

19 | Lorem ipsum, dolor sit amet consectetur adipisicing elit. 20 | Repudiandae magnam molestias dolorem praesentium itaque rem, iste, 21 | distinctio, voluptate doloribus corporis omnis facere dolores 22 | reiciendis! Debitis maxime necessitatibus assumenda molestiae ex. 23 |

24 |
25 |
26 |

Contact

27 |
28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default Footer; 39 | -------------------------------------------------------------------------------- /client/src/components/footer/footer.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | width: 100%; 3 | height: 100px; 4 | margin-bottom: 50px; 5 | margin-top: 200px; 6 | } 7 | 8 | .wrapper{ 9 | width: 75%; 10 | height: 100%; 11 | margin: 0 auto; 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | } 16 | 17 | .column{ 18 | flex: 1; 19 | display: flex; 20 | flex-direction: column; 21 | gap: 12px; 22 | } 23 | 24 | .column > span{ 25 | color: #666; 26 | font-size: 17px; 27 | } 28 | 29 | .column > p{ 30 | color: #666; 31 | font-size: 17px; 32 | } 33 | 34 | .aboutParagraph{ 35 | max-width: 500px; 36 | } 37 | 38 | .column:last-child{ 39 | align-items: flex-end; 40 | } 41 | 42 | .column:last-child > h2{ 43 | margin-right: 15px; 44 | } 45 | 46 | .column:last-child > .icons{ 47 | display: flex; 48 | gap: 10px; 49 | } 50 | 51 | .icons{ 52 | font-size: 30px; 53 | } -------------------------------------------------------------------------------- /client/src/components/home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useEffect } from "react"; 3 | import { useState } from "react"; 4 | import List from "../list/List"; 5 | import classes from "./home.module.css"; 6 | 7 | const Home = () => { 8 | const [products, setProducts] = useState([]); 9 | const [error, setError] = useState("") 10 | 11 | useEffect(() => { 12 | const fetchProducts = async () => { 13 | try { 14 | const res = await fetch(`http://localhost:5000/product`); 15 | const data = await res.json(); 16 | console.log(data) 17 | setProducts(data); 18 | } catch (error) { 19 | setError(prev => error) 20 | console.error(error); 21 | } 22 | }; 23 | fetchProducts(); 24 | }, []); 25 | 26 | return ( 27 |
28 | {!error && } 29 | {error &&

No products or server is not responding

} 30 |
31 | ); 32 | }; 33 | 34 | export default Home; 35 | -------------------------------------------------------------------------------- /client/src/components/home/home.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | padding-bottom: 2.5rem; 3 | } -------------------------------------------------------------------------------- /client/src/components/list/List.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classes from "./list.module.css"; 3 | import ProductCard from "../productCard/ProductCard"; 4 | 5 | const List = ({ products }) => { 6 | 7 | return ( 8 |
9 |
10 | {products?.length > 0 &&

Best products on the market

} 11 |
12 | {(products?.length === 0) ? ( 13 |

No products yet!

14 | ) : ( 15 | products?.map((product) => ( 16 | 17 | )) 18 | )} 19 |
20 |
21 |
22 | ); 23 | }; 24 | 25 | export default List; 26 | -------------------------------------------------------------------------------- /client/src/components/list/list.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | margin-top: 50px; 3 | height: fit-content; 4 | } 5 | 6 | .wrapper{ 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | } 12 | 13 | .title{ 14 | text-align: center; 15 | } 16 | 17 | .productsContainer{ 18 | display: flex; 19 | flex-wrap: wrap; 20 | width: 60%; 21 | gap: 30px; 22 | margin-top: 50px; 23 | } 24 | 25 | .noProductsMsg{ 26 | width: 100%; 27 | text-align: center; 28 | font-size: 36px; 29 | } -------------------------------------------------------------------------------- /client/src/components/login/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { Link, useNavigate } from 'react-router-dom' 4 | import { login } from '../../redux/authSlice' 5 | import classes from './login.module.css' 6 | 7 | const Login = () => { 8 | const [email, setEmail] = useState("") 9 | const [password, setPassword] = useState("") 10 | const [error, setError] = useState(false) 11 | const dispatch = useDispatch() 12 | const navigate = useNavigate() 13 | 14 | const handleLogin = async(e) => { 15 | e.preventDefault() 16 | 17 | try { 18 | console.log(email, password) 19 | const res = await fetch(`http://localhost:5000/auth/login`, { 20 | headers: { 21 | 'Content-Type': 'application/json' 22 | }, 23 | method: 'POST', 24 | body: JSON.stringify({email, password}) 25 | }) 26 | if(res.status === 404){ 27 | throw new Error("Wrong credentials") 28 | } 29 | const data = await res.json() 30 | dispatch(login(data)) 31 | navigate('/') 32 | } catch (error) { 33 | setError(prev => true) 34 | setTimeout(() => { 35 | setError(prev => false) 36 | }, 2500) 37 | console.error(error) 38 | } 39 | } 40 | 41 | return ( 42 |
43 |
44 |

Login

45 |
46 | 49 | 52 | 53 | Don't have an account?

Register now

54 |
55 | {error && 56 |
57 | Wrong credentials! Try different ones. 58 |
59 | } 60 |
61 |
62 | ) 63 | } 64 | 65 | export default Login -------------------------------------------------------------------------------- /client/src/components/login/login.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | height: 100vh; 3 | width: 100vw; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | background-color: rgb(106, 106, 194); 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | z-index: 999; 12 | } 13 | 14 | .wrapper{ 15 | display: flex; 16 | flex-direction: column; 17 | align-items: center; 18 | justify-content: center; 19 | padding: 2rem; 20 | border: 1px solid rgb(160, 146, 146); 21 | height: 400px; 22 | width: 325px; 23 | background-color: #f4f4f4; 24 | border-radius: 20px; 25 | } 26 | 27 | .wrapper h2{ 28 | margin-bottom: 20px; 29 | color: rgb(58, 54, 54); 30 | font-weight: bold; 31 | background-color: #f4f4f4; 32 | } 33 | 34 | .wrapper form{ 35 | display: flex; 36 | flex-direction: column; 37 | align-items: center; 38 | justify-content: center; 39 | gap: 20px; 40 | background-color: #f4f4f4; 41 | } 42 | 43 | .wrapper form input{ 44 | padding: 0.75rem 1.25rem; 45 | background-color: transparent; 46 | border: 2px solid rgb(173, 171, 171); 47 | border-radius: 12px; 48 | background-color: #f4f4f4; 49 | } 50 | 51 | .wrapper form input:focus{ 52 | outline: none; 53 | border-width: 2px; 54 | border-color: #333; 55 | } 56 | 57 | .submitBtn{ 58 | outline: none; 59 | border: 1px solid transparent; 60 | padding: 0.75rem 2rem; 61 | border-radius: 20px; 62 | background-color: rgb(106, 106, 194); 63 | color: #fff; 64 | cursor: pointer; 65 | margin: 6px 0; 66 | margin-bottom: 3px; 67 | } 68 | 69 | .submitBtn:hover{ 70 | border-color: rgb(106, 106, 194); 71 | background-color: #fff; 72 | color: rgb(106, 106, 194); 73 | } 74 | 75 | .wrapper form a{ 76 | text-decoration: none; 77 | color: inherit; 78 | margin-top: 15px; 79 | background-color: #f4f4f4; 80 | } 81 | 82 | .register{ 83 | width: 100px; 84 | border-bottom: 1px solid #999; 85 | padding-bottom: 0.2rem; 86 | margin: 0 auto; 87 | margin-top: 10px; 88 | text-align: center; 89 | background-color: #f4f4f4; 90 | } 91 | 92 | .register:hover{ 93 | color: #666; 94 | } 95 | 96 | .errorMessage{ 97 | height: 70px; 98 | width: 250px; 99 | text-align: center; 100 | border-radius: 20px; 101 | line-height: 1.5rem; 102 | padding: 0.75rem 1.5rem; 103 | background-color: #f00; 104 | color: #fff; 105 | position: absolute; 106 | top: 5rem; 107 | right: 5rem; 108 | } -------------------------------------------------------------------------------- /client/src/components/navbar/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classes from "./navbar.module.css"; 3 | import { AiOutlineShoppingCart } from "react-icons/ai"; 4 | import { Link, useNavigate } from "react-router-dom"; 5 | import Cart from "../cart/Cart"; 6 | import { useDispatch, useSelector } from "react-redux"; 7 | import {toggleShowCart} from '../../redux/cartSlice' 8 | import { login } from "../../redux/authSlice"; 9 | 10 | const Navbar = () => { 11 | const dispatch = useDispatch(); 12 | const navigate = useNavigate() 13 | const { showCart, products } = useSelector((state) => state.cart); 14 | const { user } = useSelector((state) => state.auth); 15 | 16 | const handleLogout = () => { 17 | dispatch(login()) 18 | navigate('/login') 19 | } 20 | 21 | const handleCloseCart = () => { 22 | if(showCart){ 23 | dispatch(toggleShowCart()) 24 | } 25 | } 26 | 27 | return ( 28 |
29 |
30 | 31 |

WebDevMania

32 | 33 |
34 | 35 | Create product 36 | 37 | {user?.username} 38 | Logout 39 |
dispatch(toggleShowCart())} 42 | > 43 | 44 | {products?.length} 45 |
46 |
47 | {showCart && } 48 |
49 |
50 | ); 51 | }; 52 | 53 | export default Navbar; 54 | -------------------------------------------------------------------------------- /client/src/components/navbar/navbar.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | padding: 0.5rem 1rem; 3 | height: 75px; 4 | width: 100%; 5 | border-bottom: 1px solid rgb(158, 138, 138); 6 | } 7 | 8 | .wrapper{ 9 | height: 100%; 10 | width: 100%; 11 | display: flex; 12 | align-items: center; 13 | justify-content: space-between; 14 | } 15 | 16 | .title{ 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .wrapper > a{ 22 | text-decoration: none; 23 | color: inherit; 24 | font-size: 18px; 25 | } 26 | 27 | .right{ 28 | display: flex; 29 | align-items: center; 30 | gap: 20px; 31 | } 32 | 33 | .right > *{ 34 | cursor: pointer; 35 | } 36 | 37 | .right a{ 38 | text-decoration: none; 39 | color: inherit; 40 | } 41 | 42 | .username{ 43 | text-transform: capitalize; 44 | cursor: default; 45 | } 46 | 47 | .logoutBtn:hover{ 48 | color: #666; 49 | transition: 150ms all; 50 | } 51 | 52 | .cartContainer{ 53 | position: relative; 54 | font-size: 18px; 55 | } 56 | 57 | .cartNumber{ 58 | position: absolute; 59 | right: -0.75rem; 60 | top: -0.75rem; 61 | width: 20px; 62 | height: 20px; 63 | border-radius: 50%; 64 | background-color: rgb(58, 58, 224); 65 | color: #fff; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | } -------------------------------------------------------------------------------- /client/src/components/productCard/ProductCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import classes from './productCard.module.css' 4 | import sunglasses1 from '../../assets/sunglasses1.avif' 5 | 6 | const ProductCard = ({ product }) => { 7 | // const data = [ 8 | // {id: crypto.randomUUID(), title: 'product 1', desc: 'best product', stars: 5, price: 99.99, photo: sunglasses}, 9 | // {id: crypto.randomUUID(), title: 'product 1', desc: 'best product', stars: 5, price: 99.99, photo: sunglasses}, 10 | // {id: crypto.randomUUID(), title: 'product 1', desc: 'best product', stars: 5, price: 99.99, photo: sunglasses}, 11 | // {id: crypto.randomUUID(), title: 'product 1', desc: 'best product', stars: 5, price: 99.99, photo: sunglasses}, 12 | // {id: crypto.randomUUID(), title: 'product 1', desc: 'best product', stars: 5, price: 99.99, photo: sunglasses}, 13 | // ] 14 | 15 | return ( 16 |
17 | 18 | 19 |
20 |

{product.title}

21 | ${Number(product?.price).toFixed(2)} 22 |
23 | 24 |
25 | ) 26 | } 27 | 28 | export default ProductCard -------------------------------------------------------------------------------- /client/src/components/productCard/productCard.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | cursor: pointer; 3 | -webkit-box-shadow: 0px 0px 7px -5px rgba(0, 0, 0, 0.9); 4 | box-shadow: 0px 0px 7px -5px rgba(0, 0, 0, 0.9); 5 | padding: 1rem; 6 | transition: 250ms all ease-in-out; 7 | } 8 | 9 | .container:hover{ 10 | scale: 1.035; 11 | } 12 | 13 | .container > a{ 14 | text-decoration: none; 15 | color: inherit; 16 | } 17 | 18 | .productImg{ 19 | height: 250px; 20 | min-width: 100%; 21 | object-fit: cover; 22 | width: auto; 23 | } 24 | 25 | .productInfo{ 26 | display: flex; 27 | flex-direction: column; 28 | gap: 5px; 29 | } 30 | 31 | .productTitle{ 32 | text-transform: capitalize; 33 | margin-top: 10px; 34 | } 35 | 36 | .productPrice{ 37 | color: rgb(48, 48, 227); 38 | font-size: 18px; 39 | } 40 | 41 | .productPrice span{ 42 | font-size: 15px; 43 | } -------------------------------------------------------------------------------- /client/src/components/productDetail/ProductDetail.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import classes from "./productDetail.module.css"; 3 | import { BsCartFill } from "react-icons/bs"; 4 | import { useState } from "react"; 5 | import { AiFillHeart } from "react-icons/ai"; 6 | import { GiScales } from "react-icons/gi"; 7 | import { useDispatch } from "react-redux"; 8 | import { addProduct } from "../../redux/cartSlice"; 9 | import { useParams } from "react-router-dom"; 10 | import numToStars from "../../helpers/numToStars"; 11 | 12 | const ProductDetail = () => { 13 | const [product, setProduct] = useState(""); 14 | const [currentImage, setCurrentImage] = useState(""); 15 | const [quantityProduct, setQuantityProduct] = useState(1); 16 | const dispatch = useDispatch(); 17 | const { id } = useParams(); 18 | 19 | useEffect(() => { 20 | const fetchProduct = async () => { 21 | try { 22 | const res = await fetch(`http://localhost:5000/product/${id}`); 23 | const data = await res.json(); 24 | setProduct((prev) => data); 25 | setCurrentImage((prev) => data.firstImg); 26 | } catch (error) { 27 | console.error(error); 28 | } 29 | }; 30 | fetchProduct(); 31 | }, [id]); 32 | 33 | const addQuantity = () => { 34 | setQuantityProduct((prev) => prev + 1); 35 | }; 36 | 37 | const removeQuantity = () => { 38 | setQuantityProduct((prev) => (prev === 1 ? 1 : prev - 1)); 39 | }; 40 | 41 | const addProductToCart = () => { 42 | dispatch( 43 | addProduct({ 44 | quantity: quantityProduct, 45 | title: product.title, 46 | desc: product?.desc, 47 | price: product?.price, 48 | id: product?._id, 49 | mainImg: product?.firstImg, 50 | }) 51 | ); 52 | setQuantityProduct((prev) => 1); 53 | }; 54 | 55 | return ( 56 |
57 |
58 |
59 |
60 | 65 |
66 |
67 |
68 |

{product?.title}

69 |

{product?.desc}

70 |

71 | $ {product?.price} 72 |

73 |
74 | 77 | Quantity: {quantityProduct} 78 | 81 |
82 |
83 | 84 | ADD TO CART 85 |
86 |
87 |
88 | 89 | ADD TO WISHLIST 90 |
91 |
92 | {product?.stars && ( 93 |
94 | Review: 95 |
{numToStars(product.stars)}(14)
96 |
97 | )} 98 |
99 | setCurrentImage((prev) => product?.firstImg)} 104 | /> 105 | setCurrentImage((prev) => product?.secondImg)} 110 | /> 111 |
112 |
113 |
114 |

Materials and maintenaince

115 |
116 |

What is the product about

117 |
118 |

Help and Contacts

119 |
120 |

FAQ

121 |
122 |
123 |
124 |
125 | ); 126 | }; 127 | 128 | export default ProductDetail; 129 | -------------------------------------------------------------------------------- /client/src/components/productDetail/productDetail.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-top: 30px; 3 | padding: 1rem 2.5rem; 4 | padding-bottom: 4rem; 5 | width: 100%; 6 | height: calc(100vh - 105px); 7 | margin-bottom: 50px; 8 | } 9 | 10 | .wrapper { 11 | height: 100%; 12 | width: 70%; 13 | margin: 0 auto; 14 | display: flex; 15 | justify-content: center; 16 | gap: 100px; 17 | } 18 | 19 | /* left */ 20 | .left { 21 | flex: 1; 22 | display: flex; 23 | gap: 25px; 24 | } 25 | 26 | .images { 27 | display: flex; 28 | gap: 25px; 29 | } 30 | 31 | .firstPhoto, 32 | .secondPhoto { 33 | height: 100px; 34 | width: 100px; 35 | object-fit: cover; 36 | cursor: pointer; 37 | margin-top: 30px; 38 | } 39 | 40 | .mainImg { 41 | height: 500px; 42 | width: 450px; 43 | object-fit: cover; 44 | } 45 | 46 | /* right */ 47 | .right { 48 | flex: 1; 49 | } 50 | 51 | .title { 52 | font-size: 30px; 53 | font-weight: bold; 54 | margin-bottom: 30px; 55 | } 56 | 57 | .desc { 58 | font-size: 18px; 59 | color: #555; 60 | font-weight: 500; 61 | text-align: justify; 62 | margin-bottom: 30px; 63 | } 64 | 65 | .price { 66 | color: rgb(59, 59, 216); 67 | font-size: 22px; 68 | } 69 | 70 | .price > span { 71 | font-size: 18px; 72 | } 73 | 74 | .quantity { 75 | margin: 1.5rem 0; 76 | } 77 | 78 | .minusBtn, 79 | .plusBtn { 80 | height: 45px; 81 | width: 45px; 82 | border: 1px solid #333; 83 | outline: none; 84 | cursor: pointer; 85 | background-color: transparent; 86 | color: green; 87 | font-size: 20px; 88 | } 89 | 90 | .minusBtn:hover, 91 | .plusBtn:hover { 92 | background-color: rgb(242, 242, 242); 93 | } 94 | 95 | .quantityNumber { 96 | margin: 0 1rem; 97 | font-size: 20px; 98 | font-weight: 500; 99 | } 100 | 101 | .addToCartBtn { 102 | display: flex; 103 | justify-content: center; 104 | align-items: center; 105 | gap: 20px; 106 | padding: 0.5rem 1rem; 107 | background-color: rgb(71, 71, 199); 108 | color: #fff; 109 | width: 200px; 110 | border-radius: 6px; 111 | border: 1px solid transparent; 112 | margin: 3rem 0; 113 | margin-bottom: 2.5rem; 114 | } 115 | 116 | .addToCartBtn:hover { 117 | border-color: rgb(71, 71, 199); 118 | background-color: #fff; 119 | color: rgb(71, 71, 199); 120 | cursor: pointer; 121 | } 122 | 123 | .addToCartBtn:hover > .cartIcon { 124 | background-color: #ffff; 125 | } 126 | 127 | .cartIcon { 128 | background-color: rgb(71, 71, 199); 129 | } 130 | 131 | .wishlistCompareBtns { 132 | display: flex; 133 | align-items: center; 134 | gap: 50px; 135 | } 136 | 137 | .addToWishlist { 138 | display: flex; 139 | align-items: center; 140 | gap: 10px; 141 | font-size: 18px; 142 | } 143 | 144 | .addToCompare { 145 | display: flex; 146 | align-items: center; 147 | gap: 10px; 148 | font-size: 18px; 149 | } 150 | 151 | .review { 152 | margin-top: 50px; 153 | display: flex; 154 | align-items: center; 155 | gap: 30px; 156 | } 157 | 158 | .review span { 159 | font-size: 22px; 160 | font-weight: 500; 161 | text-transform: capitalize; 162 | } 163 | 164 | .review .stars { 165 | font-size: 18px; 166 | } 167 | 168 | .stars { 169 | display: flex; 170 | align-items: center; 171 | } 172 | 173 | .additionalInfo { 174 | margin-top: 30px; 175 | } 176 | 177 | .additionalInfo > hr { 178 | width: 400px; 179 | color: #999; 180 | margin: 0.75rem 0; 181 | } 182 | 183 | .additionalInfo > hr:first-child { 184 | margin: 40px 0; 185 | } 186 | 187 | .additionalInfo > p { 188 | font-weight: 500; 189 | margin-left: 10px; 190 | text-transform: uppercase; 191 | color: #777; 192 | } 193 | -------------------------------------------------------------------------------- /client/src/components/register/Register.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useState } from "react"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | import classes from "./register.module.css"; 5 | import { useDispatch } from "react-redux"; 6 | import { register } from "../../redux/authSlice"; 7 | 8 | const Register = () => { 9 | const [username, setUsername] = useState(""); 10 | const [email, setEmail] = useState(""); 11 | const [password, setPassword] = useState(""); 12 | const [confirmPass, setConfirmPass] = useState(""); 13 | const [error, setError] = useState(false) 14 | const dispatch = useDispatch(); 15 | const navigate = useNavigate(); 16 | 17 | const handleRegister = async (e) => { 18 | e.preventDefault(); 19 | 20 | if (confirmPass !== password) return; 21 | 22 | try { 23 | const res = await fetch(`http://localhost:5000/auth/register`, { 24 | headers: { 25 | "Content-Type": "application/json", 26 | }, 27 | method: "POST", 28 | body: JSON.stringify({ username, email, password }), 29 | }); 30 | if(res.status === 404){ 31 | throw new Error("Wrong credentials") 32 | } 33 | const data = await res.json(); 34 | navigate("/"); 35 | dispatch(register(data)); 36 | } catch (error) { 37 | setError((prev) => true); 38 | setTimeout(() => { 39 | setError((prev) => false); 40 | }, 2500); 41 | console.error(error); 42 | } 43 | }; 44 | 45 | return ( 46 |
47 |
48 |

Register

49 |
50 | 58 | 66 | 74 | 82 | 83 | 84 | Already have an account?

Login now

85 | 86 |
87 | {error && 88 |
89 | Wrong credentials! Try different ones. 90 |
91 | } 92 |
93 |
94 | ); 95 | }; 96 | 97 | export default Register; 98 | -------------------------------------------------------------------------------- /client/src/components/register/register.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100vh; 3 | width: 100vw; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | background-color: rgb(106, 106, 194); 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | z-index: 999; 12 | } 13 | 14 | .wrapper { 15 | display: flex; 16 | flex-direction: column; 17 | align-items: center; 18 | justify-content: center; 19 | padding: 2rem; 20 | border: 1px solid rgb(160, 146, 146); 21 | height: 450px; 22 | width: 375px; 23 | background-color: #f4f4f4; 24 | border-radius: 20px; 25 | 26 | } 27 | 28 | .wrapper h2 { 29 | margin-bottom: 20px; 30 | color: rgb(58, 54, 54); 31 | font-weight: bold; 32 | background-color: #f4f4f4; 33 | 34 | } 35 | 36 | .wrapper form { 37 | display: flex; 38 | flex-direction: column; 39 | align-items: center; 40 | justify-content: center; 41 | gap: 12px; 42 | background-color: #f4f4f4; 43 | 44 | } 45 | 46 | .wrapper form input { 47 | padding: 0.75rem 1.25rem; 48 | background-color: transparent; 49 | border: 2px solid rgb(173, 171, 171); 50 | border-radius: 12px; 51 | background-color: #f4f4f4; 52 | overflow: hidden; 53 | } 54 | 55 | .wrapper form input:focus { 56 | outline: none; 57 | border-width: 2px; 58 | border-color: #333; 59 | } 60 | 61 | .submitBtn { 62 | outline: none; 63 | border: 1px solid transparent; 64 | padding: 0.75rem 2rem; 65 | border-radius: 20px; 66 | background-color: rgb(106, 106, 194); 67 | color: #fff; 68 | cursor: pointer; 69 | margin: 12px 0; 70 | margin-bottom: 3px; 71 | } 72 | 73 | .submitBtn:hover { 74 | border-color: rgb(106, 106, 194); 75 | background-color: #fff; 76 | color: rgb(106, 106, 194); 77 | } 78 | 79 | .wrapper form a { 80 | text-decoration: none; 81 | color: inherit; 82 | background-color: #f4f4f4; 83 | 84 | } 85 | 86 | .login { 87 | width: 100px; 88 | border-bottom: 1px solid #999; 89 | padding-bottom: 0.2rem; 90 | margin: 0 auto; 91 | margin-top: 3px; 92 | text-align: center; 93 | background-color: #f4f4f4; 94 | 95 | } 96 | 97 | .login:hover { 98 | color: #666; 99 | } 100 | 101 | .errorMessage{ 102 | height: 70px; 103 | width: 250px; 104 | text-align: center; 105 | border-radius: 20px; 106 | line-height: 1.5rem; 107 | padding: 0.75rem 1.5rem; 108 | background-color: #f00; 109 | color: #fff; 110 | position: absolute; 111 | top: 5rem; 112 | right: 5rem; 113 | } -------------------------------------------------------------------------------- /client/src/helpers/numToStars.js: -------------------------------------------------------------------------------- 1 | const { AiOutlineStar, AiFillStar } = require("react-icons/ai"); 2 | 3 | const numToStars = (value) => { 4 | if (Number(value) === 0) { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | } else if (Number(value) === 1) { 15 | return ( 16 | <> 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } else if (Number(value) === 2) { 25 | return ( 26 | <> 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } else if (Number(value) === 3) { 35 | return ( 36 | <> 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | } else if (Number(value) === 4) { 45 | return ( 46 | <> 47 | 48 | 49 | 50 | 51 | 52 | 53 | ); 54 | } else if (Number(value) === 5) { 55 | return ( 56 | <> 57 | 58 | 59 | 60 | 61 | 62 | 63 | ); 64 | } 65 | }; 66 | 67 | export default numToStars -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import { BrowserRouter } from "react-router-dom"; 5 | import { persistor, store } from "./redux/store"; 6 | import { Provider } from "react-redux"; 7 | import { PersistGate } from "redux-persist/integration/react"; 8 | 9 | const root = ReactDOM.createRoot(document.getElementById("root")); 10 | root.render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /client/src/redux/addressSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | const initialState = { 4 | address: {}, 5 | } 6 | 7 | export const addressSlice = createSlice({ 8 | name: 'address', 9 | initialState, 10 | reducers: { 11 | submitAddress(state, action) { 12 | state.address = action.payload 13 | }, 14 | emptyAddress(state){ 15 | state.address = {} 16 | } 17 | }, 18 | }) 19 | 20 | export const { submitAddress } = addressSlice.actions 21 | 22 | export default addressSlice.reducer -------------------------------------------------------------------------------- /client/src/redux/authSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | const initialState = { 4 | user: null, 5 | token: null 6 | } 7 | 8 | export const authSlice = createSlice({ 9 | name: 'auth', 10 | initialState, 11 | reducers: { 12 | login(state, action) { 13 | localStorage.clear() 14 | state.user = action?.payload?.others 15 | state.token = action?.payload?.token 16 | }, 17 | register(state, action) { 18 | localStorage.clear() 19 | state.user = action?.payload?.others 20 | state.token = action?.payload?.token 21 | 22 | }, 23 | logout(state, action) { 24 | state.user = null 25 | state.token = null 26 | localStorage.clear() 27 | }, 28 | }, 29 | }) 30 | 31 | export const { login, register, logout } = authSlice.actions 32 | 33 | export default authSlice.reducer -------------------------------------------------------------------------------- /client/src/redux/cartSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | const initialState = { 4 | products: [], 5 | showCart: false 6 | } 7 | 8 | export const cartSlice = createSlice({ 9 | name: 'cart', 10 | initialState, 11 | reducers: { 12 | addProduct: (state, action) => { 13 | const product = state.products.find((product) => product.id === action.payload.id); 14 | if (product) { 15 | product.quantity += action.payload.quantity; 16 | } else { 17 | state.products.push(action.payload); 18 | } 19 | }, 20 | removeProduct: (state, action) => { 21 | state.products = state.products.filter((product) => product.id !== action.payload.id) 22 | }, 23 | emptyCart: (state) => { 24 | state.products = [] 25 | }, 26 | toggleShowCart: (state) => { 27 | state.showCart = !state.showCart 28 | } 29 | }, 30 | }) 31 | 32 | export const { addProduct, removeProduct, emptyCart, toggleShowCart } = cartSlice.actions 33 | 34 | export default cartSlice.reducer -------------------------------------------------------------------------------- /client/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { combineReducers, configureStore } from "@reduxjs/toolkit"; 2 | import { 3 | persistStore, 4 | persistReducer, 5 | FLUSH, 6 | REHYDRATE, 7 | PAUSE, 8 | PERSIST, 9 | PURGE, 10 | REGISTER, 11 | } from "redux-persist"; 12 | import storage from "redux-persist/lib/storage"; 13 | import cartSlice from "./cartSlice"; 14 | import authSlice from "./authSlice"; 15 | import addressSlice from "./addressSlice"; 16 | 17 | const persistConfig = { 18 | key: "root", 19 | version: 1, 20 | storage, 21 | }; 22 | 23 | const reducers = combineReducers({ 24 | cart: cartSlice, 25 | auth: authSlice, 26 | address: addressSlice 27 | }) 28 | 29 | const persistedReducer = persistReducer(persistConfig, reducers); 30 | 31 | export const store = configureStore({ 32 | reducer: persistedReducer, 33 | middleware: (getDefaultMiddleware) => 34 | getDefaultMiddleware({ 35 | serializableCheck: { 36 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], 37 | }, 38 | }), 39 | }); 40 | 41 | export let persistor = persistStore(store); --------------------------------------------------------------------------------