├── 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 |
34 |
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 |
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 |
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 |
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 |
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 |
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);
--------------------------------------------------------------------------------