├── .github └── workflows │ └── aws.yml ├── .gitignore ├── README.md ├── env ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── _redirects ├── favicon.ico ├── index.html └── manifest.json ├── server ├── .gitignore ├── Utils │ └── nodemailer.js ├── app.js ├── controller │ ├── paginate.js │ ├── productController.js │ └── user.js ├── env.local ├── middleware │ └── auth.js ├── models │ ├── productModel.js │ ├── user.js │ └── valideUser.js ├── package-lock.json ├── package.json └── routes │ ├── productRoute.js │ └── users.js ├── src ├── App.js ├── assets │ ├── commingsoon.gif │ ├── fastLoading.gif │ ├── favicon.ico │ └── homepage.svg ├── components │ ├── Banner.js │ ├── Footer.js │ ├── Header.js │ ├── Model │ │ ├── AuthModel.js │ │ ├── addProduct.js │ │ ├── logoutModel.js │ │ └── productFunctions │ │ │ ├── category.js │ │ │ └── shoeForOption.js │ ├── Product.js │ ├── ProductList.js │ ├── cart.js │ ├── filterProduct │ │ ├── BrandDropdown.js │ │ ├── Category.js │ │ ├── PriceRangeDropdown.js │ │ ├── Search.js │ │ └── pagination.js │ ├── firebase │ │ ├── UploadImage.js │ │ └── index.js │ └── pageNotFound.js ├── data.js ├── index.css ├── index.js ├── pages │ ├── AllProduct.js │ ├── Home.js │ ├── ProductDetails.js │ ├── UserEmailVerification.js │ ├── topProduct.js │ └── wishlist.js ├── statemanagement │ ├── api │ │ ├── AuthenticationApi.js │ │ ├── ShoeApi.js │ │ ├── WishListApi.js │ │ ├── cartApi.js │ │ └── index.js │ ├── slice │ │ ├── AuthenticationSlice │ │ │ └── index.js │ │ ├── ShoeSlice │ │ │ ├── Shoe.js │ │ │ └── index.js │ │ ├── WishList │ │ │ └── index.js │ │ ├── cartSlice │ │ │ └── index.js │ │ └── filterShoes.js │ └── storage │ │ └── index.js └── toastify.js └── tailwind.config.js /.github/workflows/aws.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a new container image to Amazon ECR, 2 | # and then will deploy a new task definition to Amazon ECS, when there is a push to the "Part_1_Shoe_Store" branch. 3 | # 4 | # To use this workflow, you will need to complete the following set-up steps: 5 | # 6 | # 1. Create an ECR repository to store your images. 7 | # For example: `aws ecr create-repository --repository-name my-ecr-repo --region us-east-2`. 8 | # Replace the value of the `ECR_REPOSITORY` environment variable in the workflow below with your repository's name. 9 | # Replace the value of the `AWS_REGION` environment variable in the workflow below with your repository's region. 10 | # 11 | # 2. Create an ECS task definition, an ECS cluster, and an ECS service. 12 | # For example, follow the Getting Started guide on the ECS console: 13 | # https://us-east-2.console.aws.amazon.com/ecs/home?region=us-east-2#/firstRun 14 | # Replace the value of the `ECS_SERVICE` environment variable in the workflow below with the name you set for the Amazon ECS service. 15 | # Replace the value of the `ECS_CLUSTER` environment variable in the workflow below with the name you set for the cluster. 16 | # 17 | # 3. Store your ECS task definition as a JSON file in your repository. 18 | # The format should follow the output of `aws ecs register-task-definition --generate-cli-skeleton`. 19 | # Replace the value of the `ECS_TASK_DEFINITION` environment variable in the workflow below with the path to the JSON file. 20 | # Replace the value of the `CONTAINER_NAME` environment variable in the workflow below with the name of the container 21 | # in the `containerDefinitions` section of the task definition. 22 | # 23 | # 4. Store an IAM user access key in GitHub Actions secrets named `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. 24 | # See the documentation for each action used below for the recommended IAM policies for this IAM user, 25 | # and best practices on handling the access key credentials. 26 | 27 | name: Deploy to Amazon ECS 28 | 29 | on: 30 | push: 31 | branches: [ "Part_1_Shoe_Store" ] 32 | 33 | env: 34 | AWS_REGION: MY_AWS_REGION # set this to your preferred AWS region, e.g. us-west-1 35 | ECR_REPOSITORY: MY_ECR_REPOSITORY # set this to your Amazon ECR repository name 36 | ECS_SERVICE: MY_ECS_SERVICE # set this to your Amazon ECS service name 37 | ECS_CLUSTER: MY_ECS_CLUSTER # set this to your Amazon ECS cluster name 38 | ECS_TASK_DEFINITION: MY_ECS_TASK_DEFINITION # set this to the path to your Amazon ECS task definition 39 | # file, e.g. .aws/task-definition.json 40 | CONTAINER_NAME: MY_CONTAINER_NAME # set this to the name of the container in the 41 | # containerDefinitions section of your task definition 42 | 43 | permissions: 44 | contents: read 45 | 46 | jobs: 47 | deploy: 48 | name: Deploy 49 | runs-on: ubuntu-latest 50 | environment: production 51 | 52 | steps: 53 | - name: Checkout 54 | uses: actions/checkout@v3 55 | 56 | - name: Configure AWS credentials 57 | uses: aws-actions/configure-aws-credentials@v1 58 | with: 59 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 60 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 61 | aws-region: ${{ env.AWS_REGION }} 62 | 63 | - name: Login to Amazon ECR 64 | id: login-ecr 65 | uses: aws-actions/amazon-ecr-login@v1 66 | 67 | - name: Build, tag, and push image to Amazon ECR 68 | id: build-image 69 | env: 70 | ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 71 | IMAGE_TAG: ${{ github.sha }} 72 | run: | 73 | # Build a docker container and 74 | # push it to ECR so that it can 75 | # be deployed to ECS. 76 | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . 77 | docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG 78 | echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT 79 | 80 | - name: Fill in the new image ID in the Amazon ECS task definition 81 | id: task-def 82 | uses: aws-actions/amazon-ecs-render-task-definition@v1 83 | with: 84 | task-definition: ${{ env.ECS_TASK_DEFINITION }} 85 | container-name: ${{ env.CONTAINER_NAME }} 86 | image: ${{ steps.build-image.outputs.image }} 87 | 88 | - name: Deploy Amazon ECS task definition 89 | uses: aws-actions/amazon-ecs-deploy-task-definition@v1 90 | with: 91 | task-definition: ${{ steps.task-def.outputs.task-definition }} 92 | service: ${{ env.ECS_SERVICE }} 93 | cluster: ${{ env.ECS_CLUSTER }} 94 | wait-for-service-stability: true 95 | -------------------------------------------------------------------------------- /.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 | .env 8 | # testing 9 | /coverage 10 | 11 | # misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Shoes Store 2 | All the things like 3 | - Email verification 4 | - Firebase storage 5 | - mongoodb url 6 | - jwt token 7 |
8 | which is need to add on .env file is available on https://youtube.com/playlist?list=PLJ3uCOeGaRaKKNzSJKb1RD5-mO9mQ1qKD 9 |
10 | Don't forget to check out the video before cloning this project. 11 |
12 | Ecommerce Web Shop - Build & Deploy a Modern Shoe Store | React.js, Redux Toolkit, Node, & Tailwind. 13 | Dear Friends, In this Project We are Creating an e-commerce app from scratch. MERN Stack e-commerce project for Beginners. React, Node.js, Redux Toolkit, and Tailwind CSS for Responsive and Full-Stack Shoes Store App Course Tutorial and deploy the project to frontend project to the Netlify app and backend to the Railway app.✅ 14 |
15 | Course Link : https://youtube.com/playlist?list=PLJ3uCOeGaRaKKNzSJKb1RD5-mO9mQ1qKD 16 | -------------------------------------------------------------------------------- /env: -------------------------------------------------------------------------------- 1 | REACT_APP_BASE_URL = 'http://localhost:3000' 2 | REACT_APP_APIKEY = add your firebase api key here 3 | REACT_APP_AUTHDOMAIN = add your firebase auth domain here 4 | REACT_APP_PROJECTID = add your firebase project id here 5 | REACT_APP_STORAGEBUCKET = add your firebase storage bucket here 6 | REACT_APP_MESSAGINGSENDERID = add your firebase messaging sender id here 7 | REACT_APP_APPID = add your firebase app id here 8 | 9 | // Watch this video to create Firebase Storage Bucket: 10 | https://www.youtube.com/watch?v=0E1MM3tBqRo&list=PLJ3uCOeGaRaKKNzSJKb1RD5-mO9mQ1qKD&index=2 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shoe-store", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "firebase": "^9.9.4", 10 | "react": "^18.1.0", 11 | "react-dom": "^18.1.0", 12 | "react-scripts": "5.0.1", 13 | "swiper": "^8.4.5", 14 | "web-vitals": "^2.1.4" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | }, 40 | "devDependencies": { 41 | "@headlessui/react": "^1.6.4", 42 | "@reduxjs/toolkit": "^1.9.0", 43 | "@tailwindcss/forms": "^0.5.3", 44 | "autoprefixer": "^10.4.7", 45 | "axios": "^1.1.3", 46 | "postcss": "^8.4.14", 47 | "react-icons": "^4.4.0", 48 | "react-jwt": "^1.1.7", 49 | "react-redux": "^8.0.5", 50 | "react-router-dom": "^6.3.0", 51 | "react-toastify": "^9.1.1", 52 | "tailwindcss": "^3.0.24" 53 | }, 54 | "main": "postcss.config.js", 55 | "author": "dilip dawadi", 56 | "license": "MIT" 57 | } 58 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dilip-dawadi/shoeStore/46f637932e73f92fb2cfcd22ab13b7d7fd64a5f0/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | Shoe Store 24 | 25 | 26 | 27 | 28 |
29 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /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 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | /.env 15 | .env 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | .pnpm-debug.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /server/Utils/nodemailer.js: -------------------------------------------------------------------------------- 1 | import { createTransport } from "nodemailer"; 2 | 3 | export const sendEmail = async (email, subject, text) => { 4 | try { 5 | const transporter = createTransport({ 6 | host: process.env.HOST, 7 | service: process.env.SERVICE, 8 | port: Number(process.env.EMAIL_PORT), 9 | secure: Boolean(process.env.SECURE), 10 | auth: { 11 | user: process.env.USER, 12 | pass: process.env.PASS, 13 | }, 14 | }); 15 | await transporter.sendMail({ 16 | from: process.env.USER, 17 | to: email, 18 | subject: subject, 19 | text: text, 20 | html: ` 21 | 22 |
31 |

${subject}

32 |

33 | Thank you for registering with us. Please click on the link below to verify your email address. Note: This link will expire in 24 hours

34 | Verify Account 35 |

If you did not register with us, please ignore this email.

36 |
37 | 44 |

© 2022 Shoe Store. All rights reserved.

45 |
46 | 47 | `, 48 | }); 49 | return { status: 200 }; 50 | } catch (error) { 51 | return { status: error.responseCode }; 52 | } 53 | }; 54 | 55 | export const CheckoutEmail = async (subject, user, total, cart, products) => { 56 | try { 57 | const transporter = createTransport({ 58 | host: process.env.HOST, 59 | service: process.env.SERVICE, 60 | port: Number(process.env.EMAIL_PORT), 61 | secure: Boolean(process.env.SECURE), 62 | auth: { 63 | user: process.env.USER, 64 | pass: process.env.PASS, 65 | }, 66 | }); 67 | await transporter.sendMail({ 68 | from: process.env.USER, 69 | to: user.email, 70 | subject: subject, 71 | text: "Thank you for shopping with us", 72 | html: ` 73 | 74 | 75 | 76 | 77 | 78 | 79 | Order Summary 80 | 117 | 118 | 119 |
120 |

Order Confirmation

121 |

122 | Dear ${user.name}, Thank you for shopping with us. Use your order id ${user._id} to track your order. 123 |

124 |

125 | The total amount of your order is ${total}$. 126 |

127 |

Order Summary

128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 141 | 142 | 143 | 144 |
Product NameQuantityPriceAddress
${products.map((item) => { return `${item.title}`; })}${products.map((item) => { 138 | const quantity = cart.find((cartItem) => cartItem.cartId === item._id.toString()).quantity; 139 | return `${quantity}`; 140 | })}${products.map((item) => { return `${item.price}`; })}${user.address}
145 |

Thank you for shopping with us. Your order has been received and is being processed. You will receive a confirmation email once your order has shipped.

146 |

If you did not place this order, please ignore this email.

147 |
148 | 154 |

© 2022 Shoe Store. All rights reserved.

155 |
156 | 157 | `, 158 | }); 159 | return { status: 200 }; 160 | } catch (error) { 161 | console.log(error + "error"); 162 | return { status: error.responseCode }; 163 | } 164 | }; 165 | 166 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import bodyParser from "body-parser"; 3 | import mongoose from "mongoose"; 4 | import cors from "cors"; 5 | import dotenv from "dotenv"; 6 | import userRoutes from "./routes/users.js"; 7 | import shoesPageRoutes from "./routes/productRoute.js"; 8 | import morgan from "morgan"; 9 | import cookieParser from "cookie-parser"; 10 | const app = express(); 11 | app.use(cookieParser()); 12 | 13 | dotenv.config(); 14 | app.use(bodyParser.json({ limit: "30mb", extended: true })); 15 | app.use(bodyParser.urlencoded({ limit: "30mb", extended: true })); 16 | app.use(cors( 17 | { 18 | origin: "http://localhost:3000", 19 | credentials: true, 20 | exposedHeaders: ["set-cookie"], 21 | } 22 | )); 23 | app.use(morgan("dev")); 24 | app.use("/user", userRoutes); 25 | app.use("/shoesPage", shoesPageRoutes); 26 | app.get("/", (req, res) => { 27 | res.send("Hello this is Shoes Store"); 28 | }); 29 | 30 | const PORT = process.env.PORT || 5000; 31 | 32 | mongoose 33 | .connect(process.env.CONNECTION_URL, { 34 | useNewUrlParser: true, 35 | useUnifiedTopology: true, 36 | }) 37 | .then(() => app.listen(PORT, console.log(`Server running ${PORT}`))) 38 | .catch(error => console.log(error)); 39 | 40 | export default app; 41 | -------------------------------------------------------------------------------- /server/controller/paginate.js: -------------------------------------------------------------------------------- 1 | export class APIfeatures { 2 | constructor(query, queryString) { 3 | this.query = query; 4 | this.queryString = queryString; 5 | this.running = query; 6 | this.lounging = query; 7 | this.everyday = query; 8 | this.paginate = queryString; 9 | } 10 | filtering() { 11 | const queryObj = { ...this.queryString }; 12 | if (queryObj.price) { 13 | queryObj.price = queryObj.price.split(" - "); 14 | queryObj.price = { 15 | $gte: parseInt(queryObj.price[0]), 16 | $lte: parseInt(queryObj.price[1]), 17 | }; 18 | } 19 | if (queryObj.brand) { 20 | queryObj.brand = { 21 | $regex: queryObj.brand, 22 | $options: "i", 23 | }; 24 | } 25 | const excludedFields = ["page", "sort", "limit"]; 26 | excludedFields.forEach((el) => delete queryObj[el]); 27 | if (queryObj.brand && queryObj.category && queryObj.price) { 28 | this.query.find(queryObj); 29 | this.queryString = queryObj; 30 | return this; 31 | } else if (!queryObj.brand && queryObj.category && queryObj.price) { 32 | delete queryObj.brand; 33 | this.queryString = queryObj; 34 | this.query.find(queryObj); 35 | return this; 36 | } else if (queryObj.brand && !queryObj.category && queryObj.price) { 37 | delete queryObj.category; 38 | this.queryString = queryObj; 39 | this.query.find(queryObj); 40 | return this; 41 | } else if (queryObj.brand && queryObj.category && !queryObj.price) { 42 | delete queryObj.price; 43 | this.queryString = queryObj; 44 | this.query.find(queryObj); 45 | return this; 46 | } else if (!queryObj.brand && !queryObj.category && queryObj.price) { 47 | delete queryObj.brand; 48 | delete queryObj.category; 49 | this.queryString = queryObj; 50 | this.query.find(queryObj); 51 | return this; 52 | } else if (!queryObj.brand && queryObj.category && !queryObj.price) { 53 | delete queryObj.brand; 54 | delete queryObj.price; 55 | this.queryString = queryObj; 56 | this.query.find(queryObj); 57 | return this; 58 | } else if (queryObj.brand && !queryObj.category && !queryObj.price) { 59 | delete queryObj.category; 60 | delete queryObj.price; 61 | this.queryString = queryObj; 62 | this.query.find(queryObj); 63 | return this; 64 | } else { 65 | this.queryString = {}; 66 | this.query.find(); 67 | return this; 68 | } 69 | } 70 | sorting() { 71 | if (this.queryString.sort) { 72 | const sortBy = this.queryString.sort; 73 | this.query = this.query.sort(sortBy); 74 | } else { 75 | this.query = this.query.sort("-createdAt"); 76 | } 77 | 78 | return this; 79 | } 80 | paginating() { 81 | const page = this.paginate.page || 1; 82 | const limit = this.paginate.limit * 1 || 8; 83 | const skip = (page - 1) * limit; 84 | this.paginate = { skip, limit }; 85 | this.query = this.query.skip(skip).limit(limit); 86 | return this; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /server/controller/productController.js: -------------------------------------------------------------------------------- 1 | import productModel from "../models/productModel.js"; 2 | import { APIfeatures } from "./paginate.js"; 3 | 4 | export const getproductPage = async (req, res) => { 5 | try { 6 | req.query.page = parseInt(req.query.page); 7 | req.query.limit = parseInt(req.query.limit); 8 | const features = new APIfeatures(productModel.find(), req.query).sorting().paginating().filtering() 9 | const data = await features.query; 10 | const paginateRemaining = features.paginate; 11 | const runnning = await productModel.find(features.queryString).find({ shoeFor: "Running" }).skip(paginateRemaining.skip).limit(paginateRemaining.limit); 12 | const lounging = await productModel.find(features.queryString).find({ shoeFor: "Lounging" }).skip(paginateRemaining.skip).limit(paginateRemaining.limit); 13 | const everyday = await productModel.find(features.queryString).find({ shoeFor: "Everyday" }).skip(paginateRemaining.skip).limit(paginateRemaining.limit); 14 | res.status(200).json({ data, runnning, lounging, everyday }); 15 | } catch (error) { 16 | console.log(error); 17 | res.status(404).json({ message: error }); 18 | } 19 | }; 20 | 21 | export const getTopProducts = async (req, res) => { 22 | try { 23 | const data = await productModel.find({}).sort({ sold: -1 }).limit(12); 24 | res.json({ data }); 25 | } catch (error) { 26 | res.status(404).json({ message: error.message }); 27 | } 28 | }; 29 | 30 | export const createproductPage = async (req, res) => { 31 | const { title, description, selectedFile, price, category, quantity, shoeFor, brand } = req.body; 32 | try { 33 | if (!title || !description) { 34 | return res.status(400).json({ 35 | message: "Please provide all required fields", 36 | }); 37 | } 38 | if (!selectedFile) { 39 | return res.status(400).json({ 40 | message: "Please provide a file", 41 | }); 42 | } 43 | if (!price) { 44 | return res.status(400).json({ 45 | message: "Please provide a price", 46 | }); 47 | } 48 | if (!category) { 49 | return res.status(400).json({ 50 | message: "Please provide a category", 51 | }); 52 | } 53 | if (!quantity) { 54 | return res.status(400).json({ 55 | message: "Please provide a quantity", 56 | }); 57 | } 58 | if (!shoeFor) { 59 | return res.status(400).json({ 60 | message: "Please provide a shoeFor", 61 | }); 62 | } 63 | if (!brand) { 64 | return res.status(400).json({ 65 | message: "Please provide a brand", 66 | }); 67 | } 68 | const productPageData = new productModel({ 69 | title, 70 | description, 71 | selectedFile, 72 | price, 73 | category, 74 | quantity, 75 | shoeFor, 76 | brand, 77 | }); 78 | const savedproductPage = await productPageData.save(); 79 | res 80 | .status(200) 81 | .json({ data: savedproductPage, message: `${savedproductPage.title} created successfully` }); 82 | } catch (error) { 83 | res.json({ 84 | message: error.message, 85 | }); 86 | } 87 | }; 88 | 89 | export const updateProductById = async (req, res) => { 90 | const { id } = req.params; 91 | const { title, description, selectedFile, price, category, quantity, shoeFor, brand } = req.body; 92 | try { 93 | if (!title || !description) { 94 | return res.status(400).json({ 95 | message: "Please provide all required fields", 96 | }); 97 | } 98 | if (!selectedFile) { 99 | return res.status(400).json({ 100 | message: "Please provide a file", 101 | }); 102 | } 103 | if (!price) { 104 | return res.status(400).json({ 105 | message: "Please provide a price", 106 | }); 107 | } 108 | if (!category) { 109 | return res.status(400).json({ 110 | message: "Please provide a category", 111 | }); 112 | } 113 | if (!quantity) { 114 | return res.status(400).json({ 115 | message: "Please provide a quantity", 116 | }); 117 | } 118 | if (!shoeFor) { 119 | return res.status(400).json({ 120 | message: "Please provide a shoeFor", 121 | }); 122 | } 123 | if (!brand) { 124 | return res.status(400).json({ 125 | message: "Please provide a brand", 126 | }); 127 | } 128 | const updatedProduct = await productModel.findByIdAndUpdate( 129 | id, 130 | { 131 | title, 132 | description, 133 | selectedFile, 134 | price, 135 | category, 136 | quantity, 137 | shoeFor, 138 | brand, 139 | }, 140 | { new: true } 141 | ); 142 | res.status(200).json({ data: updatedProduct, message: `${updatedProduct.title} updated successfully` }); 143 | } catch (error) { 144 | res.status(404).json({ message: error.message }); 145 | } 146 | }; 147 | 148 | export const getProductById = async (req, res) => { 149 | const { id } = req.params; 150 | try { 151 | const ProductById = await productModel.findById(id); 152 | const title = ProductById.title; 153 | res.json({ data: ProductById, message: "Product " + title }); 154 | } catch (error) { 155 | res.status(404).json({ message: error }); 156 | } 157 | }; 158 | 159 | export const getfilterProduct = async (req, res) => { 160 | try { 161 | const data = await productModel.find({}).select("brand category"); 162 | const pages = await productModel.find().countDocuments(); 163 | const limit = 8; 164 | const totalPages = Math.ceil(pages / limit); 165 | const pageArray = []; 166 | for (let i = 1; i <= totalPages; i++) { 167 | pageArray.push(i); 168 | } 169 | const brand = data.map((item) => item.brand); 170 | const category = data.map((item) => item.category); 171 | const allBrand = brand.reduce((acc, val) => acc.concat(val), []); 172 | const allCategory = category.reduce((acc, val) => acc.concat(val), []); 173 | const brandFilter = allBrand.filter((item) => item !== undefined && item !== null); 174 | const categoryFilter = allCategory.filter((item) => item !== undefined && item !== null); 175 | const brandCapatalize = brandFilter.map((item) => item.charAt(0).toUpperCase() + item.slice(1)); 176 | const categoryCapatalize = categoryFilter.map((item) => item.charAt(0).toUpperCase() + item.slice(1)); 177 | const uniqueBrand = [...new Set(brandCapatalize)]; 178 | const uniqueCategory = [...new Set(categoryCapatalize)]; 179 | res.json({ data: { brand: uniqueBrand, category: uniqueCategory, pageNumbers: pageArray } }); 180 | } catch (error) { 181 | res.status(404).json({ message: error.message }); 182 | } 183 | }; -------------------------------------------------------------------------------- /server/controller/user.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs'; 2 | import jwt from 'jsonwebtoken'; 3 | import User from "../models/user.js"; 4 | import verifyUser from "../models/valideUser.js"; 5 | import { CheckoutEmail, sendEmail } from "../Utils/nodemailer.js"; 6 | import Product from '../models/productModel.js'; 7 | import { APIfeatures } from './paginate.js'; 8 | const generateToken = (data) => { 9 | const { email, name, role, wishlist, number, address, cart } = data; 10 | return jwt.sign({ email, name, role, wishlist, number, address, cart }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN }); 11 | } 12 | const generateSessionToken = (data, res) => { 13 | const { _id, role } = data; 14 | const token = jwt.sign({ _id, role }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN }); 15 | res.cookie('token', token, { 16 | expires: new Date(Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000), 17 | httpOnly: true, 18 | secure: process.env.NODE_ENV === 'production' ? true : false, 19 | sameSite: 'strict' 20 | }); 21 | } 22 | 23 | export const signin = async (req, res) => { 24 | const { email, password } = req.body; 25 | try { 26 | if (!email || !password) { 27 | return res.status(400).json({ message: "Please fill all the fields" }); 28 | } 29 | const existingUser = await User.findOne({ email }); 30 | if (!existingUser) return res.status(404).json({ message: "User doesn't exist." }); 31 | const isPasswordCorrect = await bcrypt.compare(password, existingUser.password); 32 | if (!isPasswordCorrect) return res.status(400).json({ message: "Invalid credentials." }); 33 | const token = generateToken(existingUser); 34 | if (!existingUser?.verifiedUser) { 35 | let checkVerify = await verifyUser.findOne({ userId: existingUser._id }); 36 | if (!checkVerify) { 37 | checkVerify = await new verifyUser({ 38 | userId: existingUser._id, 39 | token: token, 40 | }).save(); 41 | const url = `${process.env.BASE_URL}user/${existingUser._id}/verify/${checkVerify.token}`; 42 | sendEmail(existingUser.email, "Verify Email from Shoes Store", url); 43 | return res.status(355).json({ message: 'Please verify your email' }); 44 | } 45 | return res 46 | .status(355) 47 | .send({ message: "Verify link has already been sent to your email" }); 48 | } 49 | generateSessionToken(existingUser, res); 50 | // check it is morning or evening 51 | const time = new Date().getHours(); 52 | let greeting; 53 | if (time >= 5 && time < 12) { 54 | greeting = "Good Morning"; 55 | } else if (time >= 12 && time < 17) { 56 | greeting = "Good Afternoon"; 57 | } else if (time >= 17 && time < 20) { 58 | greeting = "Good Evening"; 59 | } else { 60 | greeting = "Good Night"; 61 | } 62 | existingUser.role === true ? res.status(200).json({ token, message: `${greeting} & Welcome Admin, ${existingUser.name.split(" ")[0]}` }) : res.status(200).json({ token, message: `${greeting} & Welcome Back, ${existingUser.name.split(" ")[0]}` }); 63 | } catch (err) { 64 | res.status(500).json({ message: err.message }) 65 | } 66 | } 67 | 68 | export const signOut = async (req, res) => { 69 | const { userId } = req; 70 | try { 71 | if (!userId) { 72 | return res.status(400).json({ message: "User not found" }); 73 | } 74 | const userName = await User.findById(userId).select("name"); 75 | res.clearCookie("token"); 76 | res.status(200).json({ message: `Goodbye, ${userName.name.split(" ")[0]}` }); 77 | } catch (err) { 78 | res.status(500).json({ message: err.message }) 79 | } 80 | } 81 | 82 | export const signup = async (req, res) => { 83 | const { email, password, firstName, number, lastName, role, address } = req.body; 84 | try { 85 | let existingUser = await User.findOne({ email }); 86 | if (existingUser) return res.status(400).json({ message: "User already exists." }); 87 | if (firstName === "" || lastName === "" || email === "" || password === "") { 88 | return res.status(400).json({ message: "Please fill all the fields" }); 89 | } else if (password.length < 6) { 90 | return res.status(400).json({ message: "Password must be atleast 6 characters long" }); 91 | } 92 | const hashedPassword = await bcrypt.hash(password, 12); 93 | existingUser = await new User({ 94 | email, 95 | password: hashedPassword, 96 | name: `${firstName} ${lastName}`, 97 | number, 98 | role, 99 | address 100 | }).save(); 101 | const createVerify = await new verifyUser({ 102 | userId: existingUser._id, 103 | token: generateToken(existingUser), 104 | }).save(); 105 | const url = `${process.env.BASE_URL}user/${existingUser._id}/verify/${createVerify.token}`; 106 | const { status } = await sendEmail(existingUser.email, "Verify Email from Shoes Store", url); 107 | if (status < 400) { 108 | res.status(200).json({ message: "User registered successfully. Please verify your email" }); 109 | } else { 110 | res.status(355).json({ message: "email verification fail check your terminal you got error message" }); 111 | } 112 | } catch (error) { 113 | res.json({ 114 | message: error.message 115 | }) 116 | } 117 | }; 118 | 119 | export const getVerified = async (req, res) => { 120 | const { userId, verifyId } = req.params; 121 | try { 122 | const user = await User.findOne({ _id: userId }); 123 | if (!user) return res.status(404).json({ message: "User not found" }); 124 | const Verified = await verifyUser.findOne({ 125 | userId: user._id, 126 | token: verifyId, 127 | }); 128 | if (!Verified) return res.status(404).json({ message: "Invalid verification link" }); 129 | await User.updateOne({ _id: user._id }, { verifiedUser: true }); 130 | await Verified.remove(); 131 | const token = generateToken(user); 132 | generateSessionToken(user, res); 133 | user.role === 1 ? res.status(200).json({ token, message: `Welcome Admin, ${user.name.split(" ")[0]}`, verifyMessage: " Email Verified" }) : res.status(200).json({ token, message: `Welcome Back, ${user.name.split(" ")[0]}`, verifyMessage: " Email Verified" }); 134 | } catch (error) { 135 | res.status(500).send({ message: error.message }); 136 | } 137 | }; 138 | 139 | export const addWishlist = async (req, res) => { 140 | const { id } = req.params; 141 | try { 142 | const user = await User.findById(req.userId); 143 | if (!user) return res.status(404).json({ message: "User not found" }); 144 | const checkWishlist = user.wishlist.find((item) => item === id); 145 | if (checkWishlist) { 146 | return res.status(400).json({ data: user.wishlist, message: "Product already in wishlist" }); 147 | } else { 148 | user.wishlist.push(id); 149 | await user.save(); 150 | const token = generateToken(user); 151 | res.status(200).json({ token, data: user.wishlist, message: "Product added to wishlist" }); 152 | } 153 | } catch (error) { 154 | res.status(500).json({ message: error.message }); 155 | } 156 | }; 157 | 158 | export const removeWishlist = async (req, res) => { 159 | const { id } = req.params; 160 | try { 161 | const user = await User.findById(req.userId); 162 | if (!user) return res.status(404).json({ message: "User not found" }); 163 | const checkWishlist = user.wishlist.find((item) => item === id); 164 | if (!checkWishlist) { 165 | return res.status(400).json({ data: user.wishlist, message: "Product not in wishlist" }); 166 | } 167 | user.wishlist = user.wishlist.filter((item) => item !== id); 168 | await user.save(); 169 | const token = generateToken(user); 170 | res.status(200).json({ token, data: user.wishlist, message: "Product removed from wishlist" }); 171 | } catch (error) { 172 | res.status(500).json({ message: error.message }); 173 | } 174 | }; 175 | 176 | export const getWishlist = async (req, res) => { 177 | try { 178 | const user = await User.findById(req.userId); 179 | if (!user) return res.status(404).json({ message: "User not found" }); 180 | req.query.page = parseInt(req.query.page); 181 | req.query.limit = parseInt(req.query.limit); 182 | const features = new APIfeatures(Product.find({ _id: { $in: user.wishlist } }), req.query).sorting().paginating().filtering() 183 | const data = await features.query; 184 | const token = generateToken(user); 185 | res.status(200).json({ token, data }); 186 | } catch (error) { 187 | res.status(500).json({ message: error.message }); 188 | } 189 | }; 190 | 191 | export const getCart = async (req, res) => { 192 | try { 193 | const user = await User.findById(req.userId); 194 | if (!user) return res.status(404).json({ message: "User not found" }); 195 | const products = await Product.find({ _id: { $in: user.cart.map((item) => item.cartId) } }); 196 | const token = generateToken(user); 197 | res.status(200).json({ token, data: products }); 198 | } catch (error) { 199 | res.status(500).json({ message: error.message }); 200 | } 201 | }; 202 | 203 | export const addCart = async (req, res) => { 204 | const { id } = req.params; 205 | try { 206 | const user = await User.findById(req.userId); 207 | if (!user) return res.status(404).json({ message: "User not found" }); 208 | const checkCart = user.cart.find((item) => item.cartId === id); 209 | if (checkCart) { 210 | return res.status(400).json({ data: user.cart, message: "Product already in cart" }); 211 | } else { 212 | const product = await Product.findById(id); 213 | if (product.quantity === 0) { 214 | return res.status(400).json({ data: user.cart, message: "Product out of stock" }); 215 | } else { 216 | const cartId = id; 217 | const quantity = 1; 218 | user.cart.push({ cartId, quantity }); 219 | await user.save(); 220 | const token = generateToken(user); 221 | res.status(200).json({ token, data: user.cart, message: "Product added to cart" }); 222 | } 223 | } 224 | } catch (error) { 225 | res.status(500).json({ message: error.message }); 226 | } 227 | }; 228 | 229 | export const removeCart = async (req, res) => { 230 | const { id } = req.params; 231 | try { 232 | const user = await User.findById(req.userId); 233 | if (!user) return res.status(404).json({ message: "User not found" }); 234 | const checkCart = user.cart.find((item) => item.cartId === id); 235 | if (!checkCart) { 236 | return res.status(400).json({ data: user.cart, message: "Product not in cart" }); 237 | } else { 238 | user.cart = user.cart.filter((item) => item.cartId !== id); 239 | await user.save(); 240 | const token = generateToken(user); 241 | res.status(200).json({ token, data: user.cart, message: "Product removed from cart" }); 242 | } 243 | } catch (error) { 244 | res.status(500).json({ message: error.message }); 245 | } 246 | }; 247 | 248 | export const cartQuantity = async (req, res) => { 249 | try { 250 | const { id } = req.params; 251 | const { status } = req.body; 252 | const user = await User.findById(req.userId); 253 | if (!user) return res.status(400).json({ message: "User does not exist." }); 254 | const product = await Product.findById(id); 255 | const quantity = user.cart.find((item) => item.cartId === id).quantity; 256 | if (status === "increase") { 257 | if (product.quantity <= quantity) { return res.status(400).json({ message: "Product out of stock" }); } 258 | } 259 | const checkCart = user.cart.find((item) => item.cartId === id); 260 | if (!checkCart) { 261 | return res.status(400).json({ data: user.cart, message: "Product not in cart" }); 262 | } else { 263 | if (status === "increase") { 264 | user.cart = user.cart.map((item) => { 265 | console.log(item); 266 | if (item.cartId === id) { 267 | item.quantity = item.quantity + 1; 268 | } 269 | return item; 270 | }); 271 | } else { 272 | user.cart = user.cart.map((item) => { 273 | if (item.cartId === id) { 274 | if (item.quantity === 1) { 275 | return item; 276 | } else { 277 | item.quantity = item.quantity - 1; 278 | } 279 | } 280 | return item; 281 | }); 282 | } 283 | await User.findByIdAndUpdate({ _id: req.userId }, { 284 | cart: user.cart, 285 | }); 286 | console.log(user.cart); 287 | const token = generateToken(user); 288 | res.status(200).json({ token, data: user.cart, message: "Cart quantity updated" }); 289 | } 290 | } catch (err) { 291 | return res.status(500).json({ message: err.message }) 292 | } 293 | } 294 | 295 | export const checkout = async (req, res) => { 296 | try { 297 | const { total } = req.body; 298 | const user = await User.findById(req.userId); 299 | if (!user) return res.status(404).json({ message: "User not found" }); 300 | const products = await Product.find({ _id: { $in: user.cart.map((item) => item.cartId) } }); 301 | const cart = user.cart; 302 | CheckoutEmail("Order Summary", user, total, cart, products); 303 | const updateUser = await User.findByIdAndUpdate({ _id: req.userId }, { 304 | cart: [], 305 | }); 306 | const token = generateToken(updateUser); 307 | res.status(200).json({ token, message: "Checkout successful, check your email for details" }); 308 | } catch (error) { 309 | res.status(500).json({ message: error.message }); 310 | } 311 | }; -------------------------------------------------------------------------------- /server/env.local: -------------------------------------------------------------------------------- 1 | CONNECTION_URL = https://shoestore12.netlify.app/ 2 | JWT = add secure password 3 | HOST = smtp.gmail.com 4 | SALT = 10 5 | SERVICE = gmail 6 | EMAIL_PORT = 587 7 | SECURE = true 8 | USER = 'your gmail account' 9 | PASS = 'your generate password' 10 | PORT = 5000 11 | BASE_URL = 'your base url of frontend' 12 | 13 | // Watch this video to create CONNECTION_URL, gmail account ( User ) and generate password ( Pass ) 14 | https://www.youtube.com/watch?v=0E1MM3tBqRo 15 | -------------------------------------------------------------------------------- /server/middleware/auth.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | const auth = async (req, res, next) => { 3 | try { 4 | if (!req.cookies.token) { 5 | return res.status(401).json({ message: "Session expired, please login again" }); 6 | } 7 | const cookie = req.cookies.token; 8 | let decodedData; 9 | decodedData = jwt.verify(cookie, process.env.JWT_SECRET); 10 | if (decodedData) { 11 | req.userId = decodedData?._id; 12 | next(); 13 | } else { 14 | return res.clearCookie("token"); 15 | } 16 | } catch (error) { 17 | res.clearCookie("token"); 18 | res.status(440).json({ message: "Sorry, you are not authorized" }); 19 | } 20 | }; 21 | 22 | const checkAdmin = async (req, res, next) => { 23 | try { 24 | if (!req.cookies.token) { 25 | return res.status(401).json({ message: "Session expired, please login again" }); 26 | } 27 | const cookie = req.cookies.token; 28 | let decodedData; 29 | decodedData = jwt.verify(cookie, process.env.JWT_SECRET); 30 | if (decodedData?.role === true) { 31 | req.userId = decodedData?._id; 32 | next(); 33 | } else { 34 | res.status(440).json({ message: "Unauthorized Admin" }); 35 | } 36 | } catch (error) { 37 | res.status(440).json({ message: error.message }); 38 | } 39 | }; 40 | export { auth, checkAdmin }; 41 | -------------------------------------------------------------------------------- /server/models/productModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const ProductSchema = mongoose.Schema({ 4 | selectedFile: { type: [String] }, 5 | title: { type: String }, 6 | description: { 7 | type: String 8 | }, 9 | price: { 10 | type: Number, 11 | default: 0 12 | }, 13 | brand: { 14 | type: String 15 | }, 16 | category: { 17 | type: [String] 18 | }, 19 | shoeFor: { 20 | type: [String] 21 | }, 22 | quantity: { 23 | type: Number, 24 | default: 0 25 | }, 26 | sold: { 27 | type: Number, 28 | default: 0 29 | }, 30 | comments: { 31 | type: Array, 32 | default: [] 33 | } 34 | }, 35 | { 36 | timestamps: true 37 | } 38 | ); 39 | 40 | const Product = mongoose.model('Product', ProductSchema); 41 | 42 | export default Product; -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSchema = mongoose.Schema( 4 | { 5 | name: { 6 | type: String, 7 | required: true, 8 | }, 9 | email: { 10 | type: String, 11 | required: true, 12 | unique: true, 13 | }, 14 | password: { 15 | type: String, 16 | }, 17 | role: { type: Boolean, default: false }, 18 | wishlist: { type: Array, default: [] }, 19 | cart: { type: Array, default: [] }, 20 | number: { type: String }, 21 | address: { type: String }, 22 | verifiedUser: { type: Boolean, default: false }, 23 | }, 24 | { 25 | timestamps: true, 26 | } 27 | ); 28 | 29 | const userDetail = mongoose.model("UserDetails", userSchema); 30 | 31 | export default userDetail; 32 | -------------------------------------------------------------------------------- /server/models/valideUser.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | const Schema = mongoose.Schema; 3 | 4 | const tokenSchema = new Schema({ 5 | userId: { 6 | type: Schema.Types.ObjectId, 7 | required: true, 8 | ref: "UserDetails", 9 | unique: true, 10 | }, 11 | token: { type: String, required: true }, 12 | createdAt: { type: Date, default: Date.now, expires: 1440 }, 13 | }); 14 | const verifyUser = mongoose.model('VerifyedUser', tokenSchema); 15 | 16 | export default verifyUser; -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "nodemon app.js", 9 | "test": "jest" 10 | }, 11 | "jest": { 12 | "testEnvironment": "node" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "bcryptjs": "^2.4.3", 18 | "body-parser": "^1.20.0", 19 | "concurrently": "^7.2.2", 20 | "cookie-parser": "^1.4.6", 21 | "cors": "^2.8.5", 22 | "dotenv": "^16.0.1", 23 | "express": "^4.18.1", 24 | "express-async-handler": "^1.2.0", 25 | "jest": "^28.1.3", 26 | "jsonwebtoken": "^8.5.1", 27 | "mongoose": "^6.3.3", 28 | "morgan": "^1.10.0", 29 | "nodemailer": "^6.7.5", 30 | "nodemon": "^2.0.16", 31 | "supertest": "^6.2.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/routes/productRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { getproductPage, createproductPage, getProductById, getTopProducts, getfilterProduct, updateProductById } from "../controller/productController.js"; 3 | import { checkAdmin } from "../middleware/auth.js"; 4 | 5 | const router = express.Router(); 6 | router.get("/", getproductPage); 7 | router.get("/top", getTopProducts); 8 | router.get("/filter", getfilterProduct); 9 | router.post("/", checkAdmin, createproductPage); 10 | router.patch('/:id', updateProductById) 11 | router.get('/:id', getProductById) 12 | export default router; -------------------------------------------------------------------------------- /server/routes/users.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { signin, signup, signOut, getVerified, addWishlist, removeWishlist, getWishlist, getCart, addCart, removeCart, cartQuantity, checkout } from '../controller/user.js'; 3 | import { auth } from '../middleware/auth.js'; 4 | const router = express.Router(); 5 | router.post('/signin', signin) 6 | router.post('/signup', signup) 7 | router.get('/signout', auth, signOut) 8 | router.get('/:userId/verify/:verifyId', getVerified) 9 | router.post('/wishlist/:id', auth, addWishlist) 10 | router.delete('/wishlist/:id', auth, removeWishlist) 11 | router.get('/wishlist', auth, getWishlist) 12 | router.get('/cart', auth, getCart) 13 | router.post('/cart/:id', auth, addCart) 14 | router.delete('/cart/:id', auth, removeCart) 15 | router.post('/cart/:id/quantity', auth, cartQuantity) 16 | router.post('/checkout', auth, checkout) 17 | export default router; -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { decodeToken } from 'react-jwt'; 3 | 4 | import { Routes, Route, useNavigate } from 'react-router-dom'; 5 | import Footer from './components/Footer'; 6 | import { Header } from './components/Header'; 7 | import PageNotFound from './components/pageNotFound'; 8 | 9 | // import pages 10 | import Home from './pages/Home'; 11 | import Products from './pages/AllProduct'; 12 | import Wishlist from './pages/wishlist'; 13 | import ProductDetails from './pages/ProductDetails'; 14 | import UserVerification from './pages/UserEmailVerification'; 15 | import { NotifyInfo } from './toastify'; 16 | import { useLocation } from 'react-router-dom'; 17 | const App = () => { 18 | const navigate = useNavigate(); 19 | const location = useLocation(); 20 | const token = localStorage.getItem('authenticate'); 21 | React.useEffect(() => { 22 | if (!token) { 23 | navigate(location.pathname); 24 | } else { 25 | const decodeData = decodeToken(token); 26 | const hoursLeft = (decodeData.exp * 1000 - new Date().getTime()) / 1000 / 60 / 60; 27 | if (hoursLeft < 0) { 28 | localStorage.removeItem('authenticate'); 29 | NotifyInfo('Your session has expired. Please login again'); 30 | navigate('/'); 31 | } 32 | if (hoursLeft < 24) { 33 | NotifyInfo('Your session will expire in ' + hoursLeft + ' hours'); 34 | } 35 | } 36 | }, [token, navigate]); 37 | return ( 38 |
39 |
40 | 41 | } /> 42 | } /> 43 | } /> 44 | } /> 45 | } /> 46 | 47 | } /> 48 | 49 |
51 | ); 52 | }; 53 | 54 | export default App; 55 | -------------------------------------------------------------------------------- /src/assets/commingsoon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dilip-dawadi/shoeStore/46f637932e73f92fb2cfcd22ab13b7d7fd64a5f0/src/assets/commingsoon.gif -------------------------------------------------------------------------------- /src/assets/fastLoading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dilip-dawadi/shoeStore/46f637932e73f92fb2cfcd22ab13b7d7fd64a5f0/src/assets/fastLoading.gif -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dilip-dawadi/shoeStore/46f637932e73f92fb2cfcd22ab13b7d7fd64a5f0/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/components/Banner.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from '../assets/homepage.svg'; 3 | import { setCategoryValue } from '../statemanagement/slice/filterShoes'; 4 | import { useDispatch } from 'react-redux'; 5 | import { useNavigate } from 'react-router-dom'; 6 | const Banner = () => { 7 | const dispatch = useDispatch(); 8 | const navigate = useNavigate(); 9 | const ShoeForMen = () => { 10 | dispatch(setCategoryValue('Men')); 11 | navigate('/products'); 12 | }; 13 | const ShoeForWomen = () => { 14 | dispatch(setCategoryValue('Women')); 15 | navigate('/products'); 16 | }; 17 | const ShoeForKids = () => { 18 | dispatch(setCategoryValue('Kids')); 19 | navigate('/products'); 20 | }; 21 | return ( 22 |
23 |
24 |
25 |

26 |

27 | Shoe Store

dream footwear store 28 |

29 |

30 | Shoe Store is a dream footwear store for all the shoe lovers. We have a wide range of shoes for all the occasions. Fluffy sneakers. Cushy slippers. Nights out. Days in. Quick errands. Transcontinental trips. Durable. Comfortable. Planet-friendly. Home or away, we’ve got what you needs to chill the most. 31 |

32 |
33 | 37 | 41 | 45 |
46 |
47 |
48 |
53 | 54 |
55 |
56 |
57 |
58 | ); 59 | }; 60 | 61 | export default Banner; 62 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | const Footer = () => { 3 | return ( 4 |
5 |
6 |
7 |
10 | Shoe store 11 |
12 |

Copyright © 13 | {new Date().getFullYear()} Shoe Store.

14 |

All rights reserved

15 |
16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 |
53 |
54 |
55 |

Company

56 |

Blog

57 |

About Us

58 |

Contact us

59 |
60 |
61 |

Support

62 |

Legal policy

63 |

Status policy

64 |

Privacy policy

65 |
66 |
67 |

Contact

68 |

Shoes Nepal

69 |

Store Location

70 |

Contact Info

71 |
72 |
73 |
74 | ); 75 | }; 76 | 77 | export default Footer; 78 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, useNavigate } from 'react-router-dom'; 3 | import Auth from './Model/AuthModel'; 4 | import { Popover, Transition } from '@headlessui/react' 5 | import { useDispatch } from 'react-redux'; 6 | // react icons 7 | import { IoIosArrowDown } from 'react-icons/io' 8 | import { BiLogOutCircle } from "react-icons/bi"; 9 | import { Fragment } from 'react' 10 | import { logoutUser } from '../statemanagement/slice/AuthenticationSlice'; 11 | import AddProduct from './Model/addProduct'; 12 | import { decodeToken } from 'react-jwt'; 13 | import Cart from './cart'; 14 | export const Header = () => { 15 | const [IsSignup, setIsSignup] = React.useState(true); 16 | const dispatch = useDispatch(); 17 | const navigate = useNavigate(); 18 | const token = localStorage.getItem('authenticate'); 19 | const decodeData = decodeToken(token); 20 | const handleLogout = () => { 21 | dispatch(logoutUser({ navigate })); 22 | }; 23 | return ( 24 |
25 |
26 | 29 | shoe Store 30 | 31 | 42 |
43 | {!token ? ( 44 | <> 45 | 52 | 60 | ) : ( 61 | <> 62 | {decodeData?.role === true && } 63 | 64 |

65 | 66 |

67 | 68 | )} 69 |
70 |
71 | 72 |
73 |
74 |
75 | ); 76 | }; 77 | 78 | const Options = [ 79 | { 80 | name: 'Products', 81 | description: 'Discover the best products for your needs.', 82 | href: '/products', 83 | icon: IconOne, 84 | }, 85 | { 86 | name: 'Wishlist', 87 | description: 'Create your own collection of products.', 88 | href: '/wishlist', 89 | icon: IconTwo, 90 | }, 91 | ] 92 | 93 | export default function PopoverFunction({ 94 | IsSignup, 95 | setIsSignup, 96 | handleLogout, 97 | token, 98 | decodeData 99 | }) { 100 | const [open, setOpen] = React.useState(false) 101 | function openModalDropDown() { 102 | setOpen(!open) 103 | } 104 | function closeModalDropDown() { 105 | setOpen(false) 106 | } 107 | return ( 108 | 109 | <> 110 | 116 | Menu 117 | 122 | 132 | 133 |
134 |
135 | {Options.map((item) => ( 136 | 138 |
139 |
141 |
142 |

143 | {item.name} 144 |

145 |

146 | {item.description} 147 |

148 |
149 | 150 | ))} 151 | {!token ? ( 152 |
153 | 160 | 168 |
169 | ) : ( 170 |
171 | 172 | {decodeData?.role === true && } 173 | 176 |
177 | )} 178 |
179 |
180 | 184 | 185 | 186 | Tips & Tricks 187 | 188 | 189 | 190 | You can add products to your wishlist by clicking on the heart icon 191 | 192 | 193 |
194 |
195 |
196 |
197 | 198 |
199 | ) 200 | } 201 | 202 | function IconOne() { 203 | return ( 204 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | ) 219 | } 220 | 221 | function IconTwo() { 222 | return ( 223 | 230 | 231 | 236 | 243 | 244 | ) 245 | } 246 | 247 | -------------------------------------------------------------------------------- /src/components/Model/AuthModel.js: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from "@headlessui/react"; 2 | import React, { Fragment } from "react"; 3 | import { useNavigate } from "react-router-dom"; 4 | import { useDispatch } from "react-redux"; 5 | import { 6 | loginaUser, 7 | registeraUser, 8 | } from "../../statemanagement/slice/AuthenticationSlice"; 9 | import { NotifyInfo } from "../../toastify"; 10 | export default function Auth({ 11 | IsSignup, 12 | setIsSignup, 13 | text, 14 | closeModalDropDown, 15 | }) { 16 | const [isOpen, setIsOpen] = React.useState(false); 17 | const [checkbox, setCheckbox] = React.useState(false); 18 | const navigate = useNavigate(); 19 | const dispatch = useDispatch(); 20 | const [authData, setAuthData] = React.useState({ 21 | email: "", 22 | firstName: "", 23 | lastName: "", 24 | password: "", 25 | confirmPassword: "", 26 | address: "", 27 | number: "", 28 | }); 29 | function closeModal() { 30 | setIsOpen(false); 31 | window.innerWidth < 768 && closeModalDropDown(); 32 | } 33 | function openModal() { 34 | setIsOpen(true); 35 | } 36 | const handleSubmit = (e) => { 37 | e.preventDefault(); 38 | if (IsSignup) { 39 | if (checkbox === false) { 40 | return NotifyInfo("Please accept the terms and conditions"); 41 | } 42 | } 43 | if (IsSignup) { 44 | if (authData.password !== authData.confirmPassword) { 45 | NotifyInfo("Password and Confirm Password must be same"); 46 | } 47 | return dispatch( 48 | registeraUser({ authData, navigate, closeModal, closeModalDropDown }) 49 | ); 50 | } 51 | dispatch( 52 | loginaUser({ authData, navigate, closeModal, closeModalDropDown }) 53 | ); 54 | }; 55 | const handleChange = (e) => { 56 | setAuthData({ ...authData, [e.target.name]: e.target.value }); 57 | }; 58 | return ( 59 | <> 60 |

61 | {text} 62 |

63 | 64 | 65 | 66 | 75 |
76 | 77 |
78 |
79 | 88 | 89 |
94 | 98 | {IsSignup 99 | ? "Sign up for an account" 100 | : "Log in to your account"} 101 | 102 | {IsSignup && ( 103 | <> 104 |
105 | 106 |
107 | 112 |
113 |
114 | 115 |
116 | 117 |
118 | 123 |
124 |
125 | 126 | )} 127 |
128 | 129 |
130 | 137 |
138 |
139 |
146 | 147 |
148 | { 153 | e.target.type = "text"; 154 | }} 155 | onBlur={(e) => { 156 | e.target.type = "password"; 157 | }} 158 | /> 159 |
160 |
161 | {IsSignup && ( 162 | <> 163 |
164 | 167 |
168 | { 173 | e.target.type = "text"; 174 | }} 175 | onBlur={(e) => { 176 | e.target.type = "password"; 177 | }} 178 | /> 179 |
180 |
181 |
182 | 183 |
184 | 189 |
190 |
191 |
192 | 193 |
194 | 199 |
200 |
201 | 202 | )} 203 |
204 | setCheckbox(!checkbox)} 211 | /> 212 | 234 |
235 | 236 |
237 | 246 | 252 |
253 |

257 | click on the overlay to close the popup window 258 |

259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 | 267 | ); 268 | } 269 | -------------------------------------------------------------------------------- /src/components/Model/addProduct.js: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from '@headlessui/react' 2 | import React, { Fragment } from 'react' 3 | import { useDispatch } from 'react-redux' 4 | import { createShoe } from '../../statemanagement/slice/ShoeSlice' 5 | import UploadImage from '../firebase/UploadImage' 6 | import { GiRunningShoe } from 'react-icons/gi' 7 | import Category from './productFunctions/category' 8 | import ShoeForOption from './productFunctions/shoeForOption' 9 | export default function AddProduct() { 10 | const [isOpen, setIsOpen] = React.useState(false) 11 | const dispatch = useDispatch() 12 | const [AddProductData, setAddProductData] = React.useState({ 13 | title: '', 14 | description: '', 15 | price: '', 16 | category: ["Men", "Women", "Kids"], 17 | shoeFor: ["Lounging", "Everyday", "Running"], 18 | quantity: '', 19 | selectedFile: [], 20 | brand: '', 21 | }) 22 | function closeModal() { 23 | setIsOpen(false) 24 | } 25 | function openModal() { 26 | setIsOpen(true) 27 | } 28 | const handleSubmit = (e) => { 29 | e.preventDefault() 30 | dispatch(createShoe({ AddProductData, closeModal })) 31 | }; 32 | const handleChange = (e) => { 33 | setAddProductData({ ...AddProductData, [e.target.name]: e.target.value }); 34 | }; 35 | const size = window.innerWidth > 768 ? 'md' : 'sm' 36 | return ( 37 | <> 38 | 45 | 46 | 47 | 48 | 57 |
58 | 59 |
60 |
61 | 70 | 71 |
72 | 76 | Add New Shoes 77 | 78 |
79 | 80 |
81 | 82 |
83 |
84 |
85 | 86 |
87 | 89 |
90 |
91 |
92 | 93 |
94 |