├── .gitignore
├── LICENSE
├── README.md
├── client
├── .eslintrc.cjs
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ ├── favicon.png
│ └── vite.svg
├── src
│ ├── App.jsx
│ ├── App.scss
│ ├── assets
│ │ ├── about.jpg
│ │ ├── contact-bg.jpg
│ │ ├── contact.jpg
│ │ ├── hero.jpg
│ │ ├── logo.png
│ │ ├── pnf.jpg
│ │ ├── pnf.png
│ │ ├── policy.jpg
│ │ ├── preview.png
│ │ └── react.svg
│ ├── components
│ │ ├── Form
│ │ │ ├── CategoryForm.jsx
│ │ │ ├── CategoryForm.scss
│ │ │ ├── SearchInput.jsx
│ │ │ └── SearchInput.scss
│ │ ├── Layout
│ │ │ ├── AdminMenu
│ │ │ │ ├── AdminMenu.jsx
│ │ │ │ └── AdminMenu.scss
│ │ │ ├── Footer
│ │ │ │ ├── Footer.jsx
│ │ │ │ └── Footer.scss
│ │ │ ├── Header
│ │ │ │ ├── Header.jsx
│ │ │ │ └── Header.scss
│ │ │ ├── Layout.jsx
│ │ │ └── UserMenu
│ │ │ │ ├── UserMenu.jsx
│ │ │ │ └── UserMenu.scss
│ │ ├── Prices
│ │ │ └── Prices.jsx
│ │ ├── Routes
│ │ │ ├── AdminRoute.jsx
│ │ │ └── Private.jsx
│ │ └── Spinner
│ │ │ ├── Spinner.jsx
│ │ │ └── Spinner.scss
│ ├── context
│ │ ├── auth.jsx
│ │ ├── cart.jsx
│ │ └── search.jsx
│ ├── hooks
│ │ └── useCategory.jsx
│ ├── index.scss
│ ├── main.jsx
│ └── pages
│ │ ├── About
│ │ ├── About.jsx
│ │ └── About.scss
│ │ ├── AdminDashboard
│ │ ├── AdminDashboard.jsx
│ │ └── AdminDashboard.scss
│ │ ├── AdminOrders
│ │ ├── AdminOrders.jsx
│ │ └── AdminOrders.scss
│ │ ├── Cart
│ │ ├── CartPage.jsx
│ │ └── CartPage.scss
│ │ ├── Categories
│ │ ├── Categories.jsx
│ │ └── Categories.scss
│ │ ├── CategoryProduct
│ │ ├── CategoryProduct.jsx
│ │ └── CategoryProduct.scss
│ │ ├── Contact
│ │ ├── Contact.jsx
│ │ └── Contact.scss
│ │ ├── CreateCategory
│ │ ├── CreateCategory.jsx
│ │ └── CreateCategory.scss
│ │ ├── CreateProduct
│ │ ├── CreateProduct.jsx
│ │ └── CreateProduct.scss
│ │ ├── Dashboard
│ │ ├── Dashboard.jsx
│ │ └── Dashboard.scss
│ │ ├── ForgotPassword
│ │ ├── ForgotPassword.jsx
│ │ └── ForgotPassword.scss
│ │ ├── Home
│ │ ├── Home.jsx
│ │ └── Home.scss
│ │ ├── Login
│ │ ├── Login.jsx
│ │ └── Login.scss
│ │ ├── Orders
│ │ ├── Orders.jsx
│ │ └── Orders.scss
│ │ ├── PageNotFound
│ │ ├── PageNotFound.jsx
│ │ └── PageNotFound.scss
│ │ ├── Policy
│ │ ├── Policy.jsx
│ │ └── Policy.scss
│ │ ├── ProductDetails.jsx
│ │ ├── ProductDetails.jsx
│ │ └── ProductDetails.scss
│ │ ├── Products
│ │ ├── Products.jsx
│ │ └── Products.scss
│ │ ├── Profile
│ │ ├── Profile.jsx
│ │ └── Profile.scss
│ │ ├── Register
│ │ ├── Register.jsx
│ │ └── Register.scss
│ │ ├── Search
│ │ ├── Search.jsx
│ │ └── Search.scss
│ │ ├── UpdateProduct
│ │ ├── UpdateProduct.jsx
│ │ └── UpdateProduct.scss
│ │ └── Users
│ │ ├── Users.jsx
│ │ └── Users.scss
└── vite.config.js
├── config
└── db.js
├── controllers
├── authController.js
├── categoryController.js
└── productController.js
├── helpers
└── authHelper.js
├── middlewares
└── authMiddleware.js
├── models
├── categoryModel.js
├── orderModel.js
├── productModel.js
└── userModel.js
├── package-lock.json
├── package.json
├── routes
├── authRoute.js
├── categoryRoutes.js
└── productRoutes.js
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore node_modules directory
2 | node_modules/
3 |
4 | # Ignore .env file
5 | .env
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2023] [Muhammad Usama]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Minimal E-Commerce Store (MERN Stack)
2 |
3 | 
4 |
5 | Welcome to our Minimal E-Commerce Store built using the MERN (MongoDB, Express, React, Node.js) stack. This application provides a user-friendly and efficient platform for browsing, searching, and purchasing products. It includes features such as user authentication, product categorization, shopping cart management, secure payment processing, and more.
6 |
7 | ## Features
8 |
9 | - User Registration and Authentication
10 | - Browse Products by Categories
11 | - Product Details and Descriptions
12 | - Add and Manage Products in the Shopping Cart
13 | - Secure Payment Processing through Braintree
14 | - User Profile Management
15 | - Admin Dashboard for Product and Category Management
16 |
17 | ## Deployment
18 |
19 | The application has been successfully deployed and can be accessed at [Live Demo](https://drab-gold-narwhal-gown.cyclic.cloud/).
20 |
21 | ## Technologies Used
22 |
23 | - Frontend: React, Vite
24 | - Backend: Node.js, Express.js
25 | - Database: MongoDB
26 | - Payment Integration: Braintree
27 | - Styling: SCSS
28 | - State Management: Context API
29 | - Routing: React Router
30 | - Authentication: JWT (JSON Web Tokens)
31 | - Deployment: Cyclic
32 |
33 | ## Main Repo
34 |
35 | ```
36 | git clone https://github.com/alphadev97/ecommerce-mern-project
37 | ```
38 |
39 | ## Deployed Repo
40 |
41 | ```
42 | git clone https://github.com/alphadev97/production-ecommerce-mern.git
43 | ```
44 |
45 | ## .env Variables
46 |
47 | ```
48 | PORT =
49 | DEV_MODE =
50 | MONGO_URL =
51 | JWT_SECRET =
52 | BRAINTREE_MERCHANT_ID =
53 | BRAINTREE_PUBLIC_KEY =
54 | BRAINTREE_PRIVATE_KEY =
55 | ```
56 |
57 | ## Contributing
58 |
59 | Contributions are welcome! Please create a pull request or open an issue if you encounter any bugs or have suggestions for improvements.
60 |
61 | ## License
62 |
63 | This project is licensed under the [MIT License](./LICENSE).
64 |
--------------------------------------------------------------------------------
/client/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Alpha97 - ECommerce
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "start": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "antd": "^5.8.2",
14 | "axios": "^1.4.0",
15 | "braintree-web-drop-in-react": "^1.2.1",
16 | "moment": "^2.29.4",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0",
19 | "react-helmet": "^6.1.0",
20 | "react-hot-toast": "^2.4.1",
21 | "react-icons": "^4.10.1",
22 | "react-router-dom": "^6.14.2",
23 | "react-toastify": "^9.1.3",
24 | "sass": "^1.64.1"
25 | },
26 | "devDependencies": {
27 | "@types/react": "^18.2.15",
28 | "@types/react-dom": "^18.2.7",
29 | "@vitejs/plugin-react-swc": "^3.3.2",
30 | "eslint": "^8.45.0",
31 | "eslint-plugin-react": "^7.32.2",
32 | "eslint-plugin-react-hooks": "^4.6.0",
33 | "eslint-plugin-react-refresh": "^0.4.3",
34 | "vite": "^4.4.9"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/client/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/public/favicon.png
--------------------------------------------------------------------------------
/client/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Routes, Route } from "react-router-dom";
2 | import Home from "./pages/Home/Home";
3 | import About from "./pages/About/About";
4 | import Contact from "./pages/Contact/Contact";
5 | import Policy from "./pages/Policy/Policy";
6 | import PageNotFound from "./pages/PageNotFound/PageNotFound";
7 | import Register from "./pages/Register/Register";
8 | import Login from "./pages/Login/Login";
9 | import Dashboard from "./pages/Dashboard/Dashboard";
10 | import { PrivateRoute } from "./components/Routes/Private";
11 | import ForgotPassword from "./pages/ForgotPassword/ForgotPassword";
12 | import { AdminRoute } from "./components/Routes/AdminRoute";
13 | import AdminDashboard from "./pages/AdminDashboard/AdminDashboard";
14 | import CreateCategory from "./pages/CreateCategory/CreateCategory";
15 | import CreateProduct from "./pages/CreateProduct/CreateProduct";
16 | import Users from "./pages/Users/Users";
17 | import Orders from "./pages/Orders/Orders";
18 | import Profile from "./pages/Profile/Profile";
19 | import Products from "./pages/Products/Products";
20 | import UpdateProduct from "./pages/UpdateProduct/UpdateProduct";
21 | import Search from "./pages/Search/Search";
22 | import ProductDetails from "./pages/ProductDetails.jsx/ProductDetails";
23 | import Categories from "./pages/Categories/Categories";
24 | import CategoryProduct from "./pages/CategoryProduct/CategoryProduct";
25 | import CartPage from "./pages/Cart/CartPage";
26 | import AdminOrders from "./pages/AdminOrders/AdminOrders";
27 |
28 | function App() {
29 | return (
30 | <>
31 |
32 | } />
33 | } />
34 | } />
35 | } />
36 | } />
37 | } />
38 | }>
39 | } />
40 | } />
41 | } />
42 |
43 |
44 | }>
45 | } />
46 | } />
47 | } />
48 | } />
49 | } />
50 | } />
51 | } />
52 |
53 |
54 | } />
55 | } />
56 | } />
57 | } />
58 | } />
59 | } />
60 | } />
61 |
62 | >
63 | );
64 | }
65 |
66 | export default App;
67 |
--------------------------------------------------------------------------------
/client/src/App.scss:
--------------------------------------------------------------------------------
1 | // Variables
2 |
3 | // Colors
4 | $polic-blue: #173f8c;
5 | $white: rgb(240, 240, 240);
6 | $green: rgb(61, 209, 2);
7 | $green-dark: rgb(20, 66, 2);
8 | $gargo-gas: #ffbc47;
9 | $orange: #f45050;
10 |
11 | // Fonts
12 | $font-assistant: "Assistant", sans-serif;
13 | $font-ubuntu: "Ubuntu", sans-serif;
14 |
--------------------------------------------------------------------------------
/client/src/assets/about.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/assets/about.jpg
--------------------------------------------------------------------------------
/client/src/assets/contact-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/assets/contact-bg.jpg
--------------------------------------------------------------------------------
/client/src/assets/contact.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/assets/contact.jpg
--------------------------------------------------------------------------------
/client/src/assets/hero.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/assets/hero.jpg
--------------------------------------------------------------------------------
/client/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/assets/logo.png
--------------------------------------------------------------------------------
/client/src/assets/pnf.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/assets/pnf.jpg
--------------------------------------------------------------------------------
/client/src/assets/pnf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/assets/pnf.png
--------------------------------------------------------------------------------
/client/src/assets/policy.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/assets/policy.jpg
--------------------------------------------------------------------------------
/client/src/assets/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/assets/preview.png
--------------------------------------------------------------------------------
/client/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/Form/CategoryForm.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./CategoryForm.scss";
3 |
4 | const CategoryForm = ({ handleSubmit, value, setValue }) => {
5 | return (
6 | <>
7 |
16 | >
17 | );
18 | };
19 |
20 | export default CategoryForm;
21 |
--------------------------------------------------------------------------------
/client/src/components/Form/CategoryForm.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | form {
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: center;
8 |
9 | input {
10 | width: 50%;
11 | height: 2rem;
12 | padding: 1rem;
13 | margin: 1rem 0 1rem 1rem;
14 | border: 3px solid rgb(100, 99, 99);
15 | border-radius: 1rem;
16 | font-family: app.$font-assistant;
17 | font-size: 1rem;
18 | }
19 |
20 | button {
21 | height: 2rem;
22 | width: 50%;
23 | margin-left: 0.5rem;
24 | border-radius: 1rem;
25 | font-family: app.$font-assistant;
26 | font-size: 1rem;
27 | font-weight: 600;
28 | background-color: app.$gargo-gas;
29 | cursor: pointer;
30 | border: 3px solid app.$gargo-gas;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/client/src/components/Form/SearchInput.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./SearchInput.scss";
3 | import { useSearch } from "../../context/search";
4 | import axios from "axios";
5 | import { useNavigate } from "react-router-dom";
6 |
7 | const SearchInput = () => {
8 | const [values, setValues] = useSearch();
9 | const navigate = useNavigate();
10 |
11 | const handleSubmit = async (e) => {
12 | e.preventDefault();
13 | try {
14 | const { data } = await axios.get(
15 | `http://localhost:8080/api/v1/product/search/${values.keyword}`
16 | );
17 | setValues({ ...values, results: data });
18 | navigate("/search");
19 | } catch (error) {
20 | console.log(error);
21 | }
22 | };
23 |
24 | return (
25 |
26 |
35 |
36 | );
37 | };
38 |
39 | export default SearchInput;
40 |
--------------------------------------------------------------------------------
/client/src/components/Form/SearchInput.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .search-form {
4 | display: flex;
5 | align-items: center;
6 | border-radius: 4px;
7 | padding: 8px;
8 | display: flex;
9 | flex-direction: row;
10 | margin-right: 2rem;
11 |
12 | input {
13 | border: none;
14 | outline: none;
15 | padding: 8px;
16 | font-size: 16px;
17 | width: 300px;
18 |
19 | &::placeholder {
20 | color: #999;
21 | }
22 | }
23 |
24 | button {
25 | border: none;
26 | outline: none;
27 | padding: 8px 16px;
28 | background-color: app.$gargo-gas;
29 | color: app.$polic-blue;
30 | border-radius: 4px;
31 | cursor: pointer;
32 | margin-left: 8px;
33 | font-size: 16px;
34 | width: 20%;
35 |
36 | &:hover {
37 | background-color: #0056b3;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/client/src/components/Layout/AdminMenu/AdminMenu.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./AdminMenu.scss";
3 | import { NavLink } from "react-router-dom";
4 |
5 | const AdminMenu = () => {
6 | return (
7 | <>
8 |
9 |
10 |
Admin Panel
11 |
15 | Create Category
16 |
17 |
21 | Create Product
22 |
23 |
27 | All Products
28 |
29 |
33 | Orders
34 |
35 |
39 | Users
40 |
41 |
42 |
43 | >
44 | );
45 | };
46 |
47 | export default AdminMenu;
48 |
--------------------------------------------------------------------------------
/client/src/components/Layout/AdminMenu/AdminMenu.scss:
--------------------------------------------------------------------------------
1 | @use "../../../App.scss" as app;
2 |
3 | .text-center {
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | text-align: center;
8 | font-family: app.$font-assistant;
9 |
10 | .list-group {
11 | display: flex;
12 | flex-direction: column;
13 |
14 | border: 1px solid rgb(136, 136, 136);
15 | margin-right: 1rem;
16 |
17 | > h4 {
18 | font-family: app.$font-ubuntu;
19 | font-size: 1.5rem;
20 | margin-bottom: 0.5rem;
21 | padding: 1rem 0;
22 | }
23 |
24 | .list-group-item {
25 | padding: 1rem 2rem;
26 | text-decoration: none;
27 | font-size: 1.2rem;
28 | border-top: 1px solid rgb(136, 136, 136);
29 | transition: all 0.5s ease;
30 | color: app.$polic-blue;
31 |
32 | &:hover {
33 | background-color: app.$polic-blue;
34 | color: white;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/client/src/components/Layout/Footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Footer.scss";
3 | import logo from "../../../assets/logo.png";
4 | import { Link } from "react-router-dom";
5 |
6 | const Footer = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
We Provide Best Deals & Products
15 |
16 |
17 |
18 |
Important Links
19 |
20 |
21 | Home
22 |
23 |
24 | About
25 |
26 |
27 | Contact
28 |
29 |
30 | Policy
31 |
32 |
33 |
34 |
35 |
36 |
Contact Us
37 | Email
38 | Github
39 | Linkedin
40 |
41 |
42 |
43 |
© Copyright Protected 2023, Made With ❤️ By Muhammad Usama
44 |
45 |
46 | );
47 | };
48 |
49 | export default Footer;
50 |
--------------------------------------------------------------------------------
/client/src/components/Layout/Footer/Footer.scss:
--------------------------------------------------------------------------------
1 | @use "../../../App.scss" as app;
2 |
3 | .footer {
4 | display: flex;
5 | align-items: top;
6 | justify-content: space-evenly;
7 | background-color: app.$polic-blue;
8 | padding: 3rem 0;
9 | color: app.$white;
10 |
11 | .footer-left {
12 | display: flex;
13 | flex-direction: column;
14 |
15 | > a {
16 | > img {
17 | width: 100%;
18 | height: 6rem;
19 | }
20 | }
21 |
22 | > p {
23 | font-family: app.$font-assistant;
24 | font-size: 1rem;
25 | font-weight: 500;
26 | }
27 | }
28 |
29 | .footer-center {
30 | > h2 {
31 | font-family: app.$font-ubuntu;
32 | font-size: 1.5rem;
33 | margin-bottom: 1rem;
34 | }
35 |
36 | > ul {
37 | list-style: none;
38 |
39 | > li {
40 | > a {
41 | text-decoration: none;
42 | color: app.$white;
43 | font-family: app.$font-assistant;
44 | font-size: 1.2rem;
45 | font-weight: 500;
46 | }
47 | }
48 | }
49 | }
50 |
51 | .footer-right {
52 | font-family: app.$font-assistant;
53 | }
54 | }
55 |
56 | .copyright {
57 | display: flex;
58 | padding: 1rem 0;
59 | align-items: center;
60 | justify-content: center;
61 |
62 | p {
63 | color: app.$polic-blue;
64 | font-family: app.$font-assistant;
65 | font-weight: 500;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/client/src/components/Layout/Header/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink, Link } from "react-router-dom";
3 | import logo from "../../../assets/logo.png";
4 | import "./Header.scss";
5 | import { useAuth } from "../../../context/auth";
6 | import toast from "react-hot-toast";
7 | import SearchInput from "../../Form/SearchInput";
8 | import useCategory from "../../../hooks/useCategory";
9 | import { useCart } from "../../../context/cart";
10 | import { Badge } from "antd";
11 |
12 | const Header = () => {
13 | const [auth, setAuth] = useAuth();
14 | const [cart] = useCart();
15 | const categories = useCategory();
16 |
17 | const handleLogOut = () => {
18 | setAuth({
19 | ...auth,
20 | user: null,
21 | token: "",
22 | });
23 | localStorage.removeItem("auth");
24 | toast.success("Logout Successfully");
25 | };
26 |
27 | return (
28 | <>
29 |
30 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Home
42 |
43 |
44 |
45 |
46 | Categories
47 |
48 |
49 | All Categories
50 | {categories?.map((c) => (
51 |
52 | {c.name}
53 |
54 | ))}
55 |
56 |
57 |
58 | {!auth.user ? (
59 | <>
60 |
61 |
62 | Register
63 |
64 |
65 |
66 |
67 | Login
68 |
69 |
70 | >
71 | ) : (
72 | <>
73 |
74 |
75 | {auth?.user?.name}
76 |
77 |
78 |
84 | Dashboard
85 |
86 |
91 | Logout
92 |
93 |
94 |
95 | >
96 | )}
97 |
98 |
99 |
100 | Cart
101 |
102 |
103 |
104 |
105 |
106 |
107 | >
108 | );
109 | };
110 |
111 | export default Header;
112 |
--------------------------------------------------------------------------------
/client/src/components/Layout/Header/Header.scss:
--------------------------------------------------------------------------------
1 | @use "../../../App.scss" as app;
2 |
3 | .header {
4 | display: flex;
5 | background-color: app.$polic-blue;
6 | justify-content: space-between;
7 | align-items: center;
8 | padding: 0 1rem;
9 | font-family: app.$font-assistant;
10 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
11 |
12 | .header-left {
13 | > a {
14 | > img {
15 | width: 100%;
16 | height: 5rem;
17 | }
18 | }
19 | }
20 |
21 | .header-center {
22 | width: 70%;
23 | .navbar {
24 | display: flex;
25 | align-items: center;
26 |
27 | .navbar-item {
28 | list-style: none;
29 |
30 | .link {
31 | text-decoration: none;
32 | color: app.$white;
33 | font-weight: 600;
34 | font-size: 1rem;
35 | transition: all 0.5s ease;
36 | text-transform: uppercase;
37 | font-family: app.$font-ubuntu;
38 | padding: 1rem 1.5rem;
39 |
40 | &:hover {
41 | background-color: app.$gargo-gas;
42 | color: app.$polic-blue;
43 | border-radius: 10px;
44 | box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px,
45 | rgba(0, 0, 0, 0.22) 0px 10px 10px;
46 | }
47 | }
48 | }
49 | }
50 | }
51 | }
52 |
53 | .dropdown {
54 | position: relative;
55 | display: inline-block;
56 | .link {
57 | text-decoration: none;
58 | align-items: center;
59 | justify-content: center;
60 | color: app.$white;
61 | font-weight: 600;
62 | font-size: 1rem;
63 | transition: all 0.5s ease;
64 | text-transform: uppercase;
65 | font-family: app.$font-ubuntu;
66 | padding: 1rem 1.5rem;
67 | }
68 | }
69 |
70 | .dropdown-content {
71 | display: none;
72 | position: absolute;
73 | background-color: #f9f9f9;
74 | min-width: 160px;
75 | box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
76 | padding: 1rem 1rem;
77 | z-index: 1;
78 |
79 | .d-link {
80 | text-decoration: none;
81 | font-family: app.$font-ubuntu;
82 | color: app.$polic-blue;
83 | }
84 | }
85 |
86 | .dropdown:hover .dropdown-content {
87 | display: grid;
88 | gap: 1rem;
89 | }
90 |
--------------------------------------------------------------------------------
/client/src/components/Layout/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Header from "./Header/Header";
3 | import Footer from "./Footer/Footer";
4 | import { Helmet } from "react-helmet";
5 |
6 | import { Toaster } from "react-hot-toast";
7 |
8 | const Layout = ({ children, title, description, keywords, author }) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 | {title}
17 |
18 |
19 |
20 |
21 | {children}
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | Layout.defaultProps = {
29 | title: "Alpha97 | E-Commerce",
30 | description: "Alpha97 | E-Commerce is a MERN Stack application",
31 | keywords: "mern, react, express, node, mongodb, alpha97, muhammad usama",
32 | author: "Muhammad Usama - Alpha97",
33 | };
34 |
35 | export default Layout;
36 |
--------------------------------------------------------------------------------
/client/src/components/Layout/UserMenu/UserMenu.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./UserMenu.scss";
3 | import { NavLink } from "react-router-dom";
4 |
5 | const UserMenu = () => {
6 | return (
7 | <>
8 | <>
9 |
10 |
11 |
Dashboard
12 |
16 | Profile
17 |
18 |
22 | Orders
23 |
24 |
25 |
26 | >
27 | >
28 | );
29 | };
30 |
31 | export default UserMenu;
32 |
--------------------------------------------------------------------------------
/client/src/components/Layout/UserMenu/UserMenu.scss:
--------------------------------------------------------------------------------
1 | @use "../../../App.scss" as app;
2 |
3 | .text-center {
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | text-align: center;
8 | font-family: app.$font-assistant;
9 |
10 | .list-group {
11 | display: flex;
12 | flex-direction: column;
13 |
14 | border: 1px solid rgb(136, 136, 136);
15 | margin-right: 1rem;
16 |
17 | > h4 {
18 | font-family: app.$font-ubuntu;
19 | font-size: 1.5rem;
20 | margin-bottom: 0.5rem;
21 | padding: 1rem 0;
22 | }
23 |
24 | .list-group-item {
25 | padding: 1rem 2rem;
26 | text-decoration: none;
27 | font-size: 1.2rem;
28 | border-top: 1px solid rgb(136, 136, 136);
29 | transition: all 0.5s ease;
30 | color: app.$polic-blue;
31 |
32 | &:hover {
33 | background-color: app.$polic-blue;
34 | color: white;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/client/src/components/Prices/Prices.jsx:
--------------------------------------------------------------------------------
1 | export const Prices = [
2 | {
3 | _id: 0,
4 | name: "$0 to 19",
5 | array: [0, 19],
6 | },
7 | {
8 | _id: 1,
9 | name: "$20 to 39",
10 | array: [20, 39],
11 | },
12 | {
13 | _id: 2,
14 | name: "$40 to 59",
15 | array: [40, 59],
16 | },
17 | {
18 | _id: 3,
19 | name: "$60 to 79",
20 | array: [60, 79],
21 | },
22 | {
23 | _id: 4,
24 | name: "$80 to 99",
25 | array: [80, 99],
26 | },
27 | {
28 | _id: 4,
29 | name: "$100 or more",
30 | array: [100, 9999],
31 | },
32 | ];
33 |
--------------------------------------------------------------------------------
/client/src/components/Routes/AdminRoute.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { useAuth } from "../../context/auth";
3 | import { Outlet } from "react-router-dom";
4 | import axios from "axios";
5 | import Spinner from "../Spinner/Spinner";
6 |
7 | export const AdminRoute = () => {
8 | const [ok, setOk] = useState(false);
9 | const [auth, setAuth] = useAuth();
10 |
11 | useEffect(() => {
12 | const authCheck = async () => {
13 | const res = await axios.get(
14 | "http://localhost:8080/api/v1/auth/admin-auth"
15 | );
16 | if (res.data.ok) {
17 | setOk(true);
18 | } else {
19 | setOk(false);
20 | }
21 | };
22 | if (auth?.token) authCheck();
23 | }, [auth?.token]);
24 |
25 | return ok ? : ;
26 | };
27 |
--------------------------------------------------------------------------------
/client/src/components/Routes/Private.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { useAuth } from "../../context/auth";
3 | import { Outlet } from "react-router-dom";
4 | import axios from "axios";
5 | import Spinner from "../Spinner/Spinner";
6 |
7 | export const PrivateRoute = () => {
8 | const [ok, setOk] = useState(false);
9 | const [auth, setAuth] = useAuth();
10 |
11 | useEffect(() => {
12 | const authCheck = async () => {
13 | const res = await axios.get(
14 | "http://localhost:8080/api/v1/auth/user-auth"
15 | );
16 | if (res.data.ok) {
17 | setOk(true);
18 | } else {
19 | setOk(false);
20 | }
21 | };
22 | if (auth?.token) authCheck();
23 | }, [auth?.token]);
24 |
25 | return ok ? : ;
26 | };
27 |
--------------------------------------------------------------------------------
/client/src/components/Spinner/Spinner.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useNavigate, useLocation } from "react-router-dom";
3 | import "./Spinner.scss";
4 |
5 | const Spinner = ({ path = "login" }) => {
6 | const [count, setCount] = useState(3);
7 | const navigate = useNavigate();
8 | const location = useLocation();
9 |
10 | useEffect(() => {
11 | const interval = setInterval(() => {
12 | setCount((prevValue) => --prevValue);
13 | }, 1000);
14 | count === 0 &&
15 | navigate(`/${path}`, {
16 | state: location.pathname,
17 | });
18 | return () => clearInterval(interval);
19 | }, [count, navigate, location, path]);
20 |
21 | return (
22 | <>
23 |
24 |
Loading...
25 |
redirecting to you in {count} second!
26 |
27 | >
28 | );
29 | };
30 |
31 | export default Spinner;
32 |
--------------------------------------------------------------------------------
/client/src/components/Spinner/Spinner.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .body {
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | height: 100vh;
9 | margin: 0;
10 | background-color: app.$polic-blue;
11 |
12 | h1 {
13 | color: app.$gargo-gas;
14 | }
15 | }
16 |
17 | .loader {
18 | color: app.$gargo-gas;
19 | font-size: 90px;
20 | text-indent: -9999em;
21 | overflow: hidden;
22 | width: 1em;
23 | height: 1em;
24 | border-radius: 50%;
25 | margin: 72px auto;
26 | position: relative;
27 | -webkit-transform: translateZ(0);
28 | -ms-transform: translateZ(0);
29 | transform: translateZ(0);
30 | -webkit-animation: load6 1.7s infinite ease, round 1.7s infinite ease;
31 | animation: load6 1.7s infinite ease, round 1.7s infinite ease;
32 | }
33 | @-webkit-keyframes load6 {
34 | 0% {
35 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
36 | 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
37 | }
38 | 5%,
39 | 95% {
40 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
41 | 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
42 | }
43 | 10%,
44 | 59% {
45 | box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em,
46 | -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em,
47 | -0.297em -0.775em 0 -0.477em;
48 | }
49 | 20% {
50 | box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em,
51 | -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em,
52 | -0.749em -0.34em 0 -0.477em;
53 | }
54 | 38% {
55 | box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em,
56 | -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em,
57 | -0.82em -0.09em 0 -0.477em;
58 | }
59 | 100% {
60 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
61 | 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
62 | }
63 | }
64 | @keyframes load6 {
65 | 0% {
66 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
67 | 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
68 | }
69 | 5%,
70 | 95% {
71 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
72 | 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
73 | }
74 | 10%,
75 | 59% {
76 | box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em,
77 | -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em,
78 | -0.297em -0.775em 0 -0.477em;
79 | }
80 | 20% {
81 | box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em,
82 | -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em,
83 | -0.749em -0.34em 0 -0.477em;
84 | }
85 | 38% {
86 | box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em,
87 | -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em,
88 | -0.82em -0.09em 0 -0.477em;
89 | }
90 | 100% {
91 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em,
92 | 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
93 | }
94 | }
95 | @-webkit-keyframes round {
96 | 0% {
97 | -webkit-transform: rotate(0deg);
98 | transform: rotate(0deg);
99 | }
100 | 100% {
101 | -webkit-transform: rotate(360deg);
102 | transform: rotate(360deg);
103 | }
104 | }
105 | @keyframes round {
106 | 0% {
107 | -webkit-transform: rotate(0deg);
108 | transform: rotate(0deg);
109 | }
110 | 100% {
111 | -webkit-transform: rotate(360deg);
112 | transform: rotate(360deg);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/client/src/context/auth.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useContext, createContext } from "react";
2 | import axios from "axios";
3 |
4 | const AuthContext = createContext();
5 |
6 | const AuthProvider = ({ children }) => {
7 | const [auth, setAuth] = useState({
8 | user: null,
9 | token: "",
10 | });
11 |
12 | // Default axios
13 | axios.defaults.headers.common["Authorization"] = auth?.token;
14 |
15 | useEffect(() => {
16 | const data = localStorage.getItem("auth");
17 |
18 | if (data) {
19 | const parseData = JSON.parse(data);
20 | setAuth({
21 | ...auth,
22 | user: parseData.user,
23 | token: parseData.token,
24 | });
25 | }
26 | // eslint-disable-next-line
27 | }, []);
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | };
34 |
35 | // Custom Hook
36 | const useAuth = () => useContext(AuthContext);
37 |
38 | export { useAuth, AuthProvider };
39 |
--------------------------------------------------------------------------------
/client/src/context/cart.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useContext, createContext, useEffect } from "react";
2 |
3 | const CartContext = createContext();
4 |
5 | const CartProvider = ({ children }) => {
6 | const [cart, setCart] = useState([]);
7 |
8 | useEffect(() => {
9 | let existingCartItems = localStorage.getItem("cart");
10 | if (existingCartItems) setCart(JSON.parse(existingCartItems));
11 | }, []);
12 |
13 | return (
14 |
15 | {children}
16 |
17 | );
18 | };
19 |
20 | // Custom Hook
21 | const useCart = () => useContext(CartContext);
22 |
23 | export { useCart, CartProvider };
24 |
--------------------------------------------------------------------------------
/client/src/context/search.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useContext, createContext } from "react";
2 |
3 | const SearchContext = createContext();
4 |
5 | const SearchProvider = ({ children }) => {
6 | const [auth, setAuth] = useState({
7 | keyword: "",
8 | results: [],
9 | });
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | };
17 |
18 | // Custom Hook
19 | const useSearch = () => useContext(SearchContext);
20 |
21 | export { useSearch, SearchProvider };
22 |
--------------------------------------------------------------------------------
/client/src/hooks/useCategory.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import axios from "axios";
3 |
4 | export default function useCategory() {
5 | const [categories, setCategories] = useState([]);
6 |
7 | // get category
8 | const getCategories = async () => {
9 | try {
10 | const { data } = await axios.get(
11 | `http://localhost:8080/api/v1/category/get-category`
12 | );
13 | setCategories(data?.category);
14 | } catch (error) {
15 | console.log(error);
16 | }
17 | };
18 |
19 | useEffect(() => {
20 | getCategories();
21 | }, []);
22 |
23 | return categories;
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/index.scss:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Assistant:wght@200;300;400;500;600;700;800&display=swap");
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | }
8 |
9 | /* font-family: 'Assistant', sans-serif;
10 | font-family: 'Ubuntu', sans-serif; */
11 |
--------------------------------------------------------------------------------
/client/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import "./index.scss";
5 | import { BrowserRouter } from "react-router-dom";
6 | import { AuthProvider } from "./context/auth.jsx";
7 | import { SearchProvider } from "./context/search.jsx";
8 | import { CartProvider } from "./context/cart.jsx";
9 |
10 | ReactDOM.createRoot(document.getElementById("root")).render(
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/client/src/pages/About/About.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Layout from "../../components/Layout/Layout";
3 | import aboutImg from "../../assets/about.jpg";
4 | import "./About.scss";
5 |
6 | const About = () => {
7 | return (
8 |
9 | About Us
10 |
11 |
12 |
13 |
14 |
15 |
16 |
About Alpha97 | E-Commerce
17 |
18 | Lorem ipsum dolor sit amet consectetur adipiscing, elit sed
19 | venenatis varius sapien quam dignissim, leo tempus nostra velit{" "}
20 |
21 | facilisis. Ad mi eros dapibus eget sagittis per enim bibendum
22 | rhoncus vehicula, mus fringilla cras gravida sollicitudin lectus{" "}
23 |
24 | morbi convallis. Vivamus id nec tristique faucibus lacinia egestas{" "}
25 |
26 | curabitur class aliquet convallis, varius arcu semper aenean dis non{" "}
27 |
28 | nulla ultricies nullam, a sagittis sapien diam taciti nisl fringilla{" "}
29 |
30 | massa sodales.
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default About;
39 |
--------------------------------------------------------------------------------
/client/src/pages/About/About.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .ah1 {
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | padding: 5rem 0;
8 | background-image: url(../../assets/contact-bg.jpg);
9 | background-position: center;
10 | background-size: cover;
11 | box-shadow: inset 0 0 0 2000px rgba(60, 72, 107, 0.507);
12 | font-size: 2rem;
13 | font-family: app.$font-ubuntu;
14 | color: white;
15 | }
16 |
17 | .about {
18 | display: flex;
19 | margin: 2rem;
20 | gap: 2rem;
21 | align-items: center;
22 | justify-content: center;
23 |
24 | .about-left {
25 | > img {
26 | width: 100%;
27 | height: 30rem;
28 | }
29 | }
30 |
31 | .about-right {
32 | > h2 {
33 | font-family: app.$font-ubuntu;
34 | font-size: 1.5rem;
35 | }
36 |
37 | > p {
38 | display: flex;
39 | margin: 1rem auto;
40 | padding: 1rem 0.3rem;
41 | font-family: app.$font-assistant;
42 | font-size: 1.2rem;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/client/src/pages/AdminDashboard/AdminDashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./AdminDashboard.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import AdminMenu from "../../components/Layout/AdminMenu/AdminMenu";
5 | import { useAuth } from "../../context/auth";
6 |
7 | const AdminDashboard = () => {
8 | const [auth] = useAuth();
9 |
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
Admin Name: {auth?.user?.name}
20 | Admin Email: {auth?.user?.email}
21 | Admin Contact: {auth?.user?.phone}
22 |
23 |
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default AdminDashboard;
31 |
--------------------------------------------------------------------------------
/client/src/pages/AdminDashboard/AdminDashboard.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .admin-container {
4 | margin: 1rem;
5 | .row {
6 | display: flex;
7 | flex-direction: row;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/client/src/pages/AdminOrders/AdminOrders.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import axios from "axios";
3 | import "./AdminOrders.scss";
4 | import AdminMenu from "../../components/Layout/AdminMenu/AdminMenu";
5 | import Layout from "../../components/Layout/Layout";
6 | import toast from "react-hot-toast";
7 | import { useAuth } from "../../context/auth";
8 | import moment from "moment";
9 | import { Select } from "antd";
10 | const { Option } = Select;
11 |
12 | const AdminOrders = () => {
13 | const [status, setStatus] = useState([
14 | "Not Process",
15 | "Processing",
16 | "Shipped",
17 | "Delievered",
18 | "Cancel",
19 | ]);
20 | const [changeStatus, setChangeStatus] = useState("");
21 | const [orders, setOrders] = useState([]);
22 | const [auth, setAuth] = useAuth();
23 |
24 | const getOrders = async () => {
25 | try {
26 | const { data } = await axios(
27 | "http://localhost:8080/api/v1/auth/all-orders"
28 | );
29 | setOrders(data);
30 | } catch (error) {
31 | console.log(error);
32 | }
33 | };
34 |
35 | useEffect(() => {
36 | if (auth?.token) getOrders();
37 | }, [auth?.token]);
38 |
39 | const handleChange = async (orderId, value) => {
40 | try {
41 | const { data } = await axios.put(
42 | `http://localhost:8080/api/v1/auth/order-status/${orderId}`,
43 | { status: value }
44 | );
45 | getOrders();
46 | } catch (error) {
47 | console.log(error);
48 | }
49 | };
50 |
51 | return (
52 |
53 |
54 |
57 |
58 |
All Orders
59 | {orders?.map((o, i) => {
60 | return (
61 |
62 |
63 |
64 |
65 | #
66 | Status
67 | Buyer
68 | Date
69 | Payment
70 | Quantity
71 |
72 |
73 |
74 |
75 | {i + 1}
76 |
77 | handleChange(o._id, value)}
80 | defaultValue={o?.status}
81 | >
82 | {status.map((s, i) => (
83 |
84 | {s}
85 |
86 | ))}
87 |
88 |
89 | {o?.buyer?.name}
90 | {moment(o?.createdAt).fromNow()}
91 | {o?.payment.success ? "Success" : "Faild"}
92 | {o?.products?.length}
93 |
94 |
95 |
96 |
97 | {o?.products?.map((p, i) => (
98 |
99 |
100 |
106 |
107 |
108 |
{p.name}
109 |
{p.description.substring(0, 30)}
110 |
${p.price}
111 |
112 |
113 | ))}
114 |
115 |
116 | );
117 | })}
118 |
119 |
120 |
121 | );
122 | };
123 |
124 | export default AdminOrders;
125 |
--------------------------------------------------------------------------------
/client/src/pages/AdminOrders/AdminOrders.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .admin-orders {
4 | display: flex;
5 | flex-direction: row;
6 | margin: 1rem;
7 |
8 | .left {
9 | display: flex;
10 | }
11 |
12 | .right {
13 | width: 80%;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/client/src/pages/Cart/CartPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "./CartPage.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import { useCart } from "../../context/cart";
5 | import { useAuth } from "../../context/auth";
6 | import { useNavigate } from "react-router-dom";
7 | import DropIn from "braintree-web-drop-in-react";
8 | import axios from "axios";
9 | import { toast } from "react-hot-toast";
10 |
11 | const CartPage = () => {
12 | const [auth, setAuth] = useAuth();
13 | const [cart, setCart] = useCart();
14 | const [clientToken, setClientToken] = useState("");
15 | const [instance, setInstance] = useState("");
16 | const [loading, setLoading] = useState(false);
17 | const navigate = useNavigate();
18 |
19 | // total price
20 | const totalPrice = () => {
21 | try {
22 | let total = 0;
23 | cart?.map((item) => {
24 | total = total + item.price;
25 | });
26 | return total.toLocaleString("en-US", {
27 | style: "currency",
28 | currency: "USD",
29 | });
30 | } catch (error) {
31 | console.log(error);
32 | }
33 | };
34 |
35 | // delete item
36 | const removeCartItem = (pid) => {
37 | try {
38 | let myCart = [...cart];
39 | let index = myCart.findIndex((item) => item._id === pid);
40 | myCart.splice(index, 1);
41 | setCart(myCart);
42 | localStorage.setItem("cart", JSON.stringify(myCart));
43 | } catch (error) {
44 | console.log(error);
45 | }
46 | };
47 |
48 | // get payment gateway token
49 | const getToken = async () => {
50 | try {
51 | const { data } = await axios.get(
52 | "http://localhost:8080/api/v1/product/braintree/token"
53 | );
54 | setClientToken(data?.clientToken);
55 | } catch (error) {
56 | console.log(error);
57 | }
58 | };
59 | useEffect(() => {
60 | getToken();
61 | }, [auth?.token]);
62 |
63 | // handle payment
64 |
65 | const handlePayment = async () => {
66 | try {
67 | setLoading(true);
68 | const { nonce } = await instance.requestPaymentMethod();
69 | const { data } = await axios.post(
70 | "http://localhost:8080/api/v1/product/braintree/payment",
71 | {
72 | nonce,
73 | cart,
74 | }
75 | );
76 | setLoading(false);
77 | localStorage.removeItem("cart");
78 | setCart([]);
79 | navigate("/dashboard/user/orders");
80 | toast.success("Payment Completed Successfully ");
81 | } catch (error) {
82 | console.log(error);
83 | setLoading(false);
84 | }
85 | };
86 |
87 | return (
88 |
89 |
90 |
91 |
92 |
{`Hello ${auth?.token && auth?.user?.name}`}
93 |
94 | {cart?.length
95 | ? `You have ${cart.length} items in your cart ${
96 | auth?.token ? "" : "please login to checkout"
97 | }`
98 | : "Your cart is empty"}
99 |
100 |
101 |
102 |
103 |
104 | {cart?.map((p) => (
105 |
106 |
107 |
113 |
114 |
115 |
{p.name}
116 |
{p.description.substring(0, 30)}
117 |
${p.price}
118 |
removeCartItem(p._id)}>Remove
119 |
120 |
121 | ))}
122 |
123 |
124 |
Cart Summary
125 |
Total | Checkout | Payment
126 |
127 |
Total: {totalPrice()}
128 | {auth?.user?.address ? (
129 | <>
130 |
131 |
Current Address
132 | {auth?.user?.address}
133 | navigate("/dashboard/user/profile")}>
134 | Update Address
135 |
136 |
137 | >
138 | ) : (
139 |
140 | {auth?.token ? (
141 | navigate("/dashboard/user/profile")}>
142 | Update Address
143 |
144 | ) : (
145 | navigate("/login", { state: "/cart" })}
147 | >
148 | Please Login to Checkout
149 |
150 | )}
151 |
152 | )}
153 |
154 | {!clientToken || !auth?.token || !cart?.length ? (
155 | ""
156 | ) : (
157 | <>
158 | setInstance(instance)}
166 | />
167 |
171 | {loading ? "Processing...." : "Make Payment"}
172 |
173 | >
174 | )}
175 |
176 |
177 |
178 |
179 |
180 | );
181 | };
182 |
183 | export default CartPage;
184 |
--------------------------------------------------------------------------------
/client/src/pages/Cart/CartPage.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .cart-container {
4 | display: flex;
5 | flex-direction: column;
6 |
7 | .cart-details {
8 | display: flex;
9 | justify-content: center;
10 | padding: 1rem 0;
11 | }
12 |
13 | .cart {
14 | padding: 2rem 0;
15 | width: 100%;
16 | display: flex;
17 | flex-direction: row;
18 | justify-content: space-evenly;
19 |
20 | .cart-item {
21 | width: 50%;
22 | display: flex;
23 | flex-direction: column;
24 |
25 | .items {
26 | display: flex;
27 | flex-direction: row;
28 | gap: 1rem;
29 | margin: 1rem 1rem;
30 | // border: 1px solid app.$polic-blue;
31 | padding: 1rem;
32 | border-radius: 1rem;
33 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
34 |
35 | .item-det {
36 | display: flex;
37 | flex-direction: column;
38 | justify-content: center;
39 |
40 | > h4 {
41 | font-family: app.$font-ubuntu;
42 | font-size: 2rem;
43 | padding: 1rem 0;
44 | }
45 |
46 | > p {
47 | font-family: app.$font-assistant;
48 | font-size: 1.5rem;
49 | font-weight: 500;
50 | }
51 |
52 | > button {
53 | width: 40%;
54 | padding: 0.5rem 0.6rem;
55 | background-color: app.$polic-blue;
56 | color: white;
57 | cursor: pointer;
58 | font-family: app.$font-ubuntu;
59 | border: none;
60 | }
61 | }
62 | }
63 | }
64 |
65 | .checkout {
66 | width: 50%;
67 |
68 | // buttons
69 | button {
70 | font-family: app.$font-assistant;
71 | font-size: 1rem;
72 | margin-top: 1rem;
73 | padding: 0.5rem 1rem;
74 | cursor: pointer;
75 | border: none;
76 | background-color: app.$polic-blue;
77 | color: white;
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/client/src/pages/Categories/Categories.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Link } from "react-router-dom";
3 | import "./Categories.scss";
4 | import Layout from "../../components/Layout/Layout";
5 | import useCategory from "../../hooks/useCategory";
6 |
7 | const Categories = () => {
8 | const categories = useCategory();
9 | return (
10 |
11 |
12 |
13 | {categories.map((c) => (
14 |
15 |
16 | {c.name}
17 |
18 |
19 | ))}
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default Categories;
27 |
--------------------------------------------------------------------------------
/client/src/pages/Categories/Categories.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .container {
4 | display: flex;
5 | font-family: app.$font-assistant;
6 | justify-content: center;
7 | margin: 2rem 0;
8 |
9 | .category {
10 | display: flex;
11 | gap: 1rem;
12 | justify-content: space-evenly;
13 |
14 | .cat-btn {
15 | .cat-link {
16 | padding: 0.5rem 1rem;
17 | border: none;
18 | background-color: app.$gargo-gas;
19 | border-radius: 0.2rem;
20 | text-decoration: none;
21 | font-size: 1rem;
22 | color: black;
23 | font-weight: 500;
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/client/src/pages/CategoryProduct/CategoryProduct.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "./CategoryProduct.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import axios from "axios";
5 | import { useParams, useNavigate } from "react-router-dom";
6 |
7 | const CategoryProduct = () => {
8 | const params = useParams();
9 | const navigate = useNavigate();
10 | const [products, setProducts] = useState([]);
11 | const [category, setCategory] = useState([]);
12 |
13 | useEffect(() => {
14 | if (params?.slug) getProductByCategory();
15 | }, [params?.slug]);
16 |
17 | const getProductByCategory = async () => {
18 | try {
19 | const { data } = await axios.get(
20 | `http://localhost:8080/api/v1/product/product-category/${params.slug}`
21 | );
22 | setProducts(data?.products);
23 | setCategory(data?.category);
24 | } catch (error) {
25 | console.log(error);
26 | }
27 | };
28 |
29 | return (
30 |
31 |
32 |
Category - {category?.name}
33 |
{products.length} results found
34 |
35 | {products?.map((p) => (
36 |
37 |
41 |
42 |
{p.name}
43 |
{p.description.substring(0, 30)}
44 |
${p.price}
45 |
46 |
47 | navigate(`/product/${p.slug}`)}>
48 | More Details
49 |
50 | Add To Cart
51 |
52 |
53 | ))}
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default CategoryProduct;
61 |
--------------------------------------------------------------------------------
/client/src/pages/CategoryProduct/CategoryProduct.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .container {
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 |
9 | > h4 {
10 | font-family: app.$font-ubuntu;
11 | font-size: 1rem;
12 | }
13 |
14 | > h6 {
15 | font-family: app.$font-assistant;
16 | font-size: 1rem;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/client/src/pages/Contact/Contact.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Layout from "../../components/Layout/Layout";
3 | import "./Contact.scss";
4 | import contactImg from "../../assets/contact.jpg";
5 | import { BiMap, BiPhoneCall, BiLink } from "react-icons/bi";
6 | import { MdEmail } from "react-icons/md";
7 |
8 | const Contact = () => {
9 | return (
10 |
11 | Contact Us
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Feel Free To Contact Us!
19 |
20 | 123 Any Street, Anywhere City, ST, 12345.
21 |
22 |
23 | +123 456 789
24 |
25 |
26 | hello@anywebsite.com
27 |
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default Contact;
35 |
--------------------------------------------------------------------------------
/client/src/pages/Contact/Contact.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .ch1 {
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | padding: 5rem 0;
8 | background-image: url(../../assets/contact-bg.jpg);
9 | background-position: center;
10 | background-size: cover;
11 | box-shadow: inset 0 0 0 2000px rgba(60, 72, 107, 0.507);
12 | font-size: 2rem;
13 | font-family: app.$font-ubuntu;
14 | color: white;
15 | }
16 |
17 | .contact {
18 | display: flex;
19 | margin: 2rem auto;
20 | gap: 2rem;
21 | align-items: center;
22 | justify-content: center;
23 |
24 | .contact-left {
25 | > img {
26 | width: 100%;
27 | height: 30rem;
28 | }
29 | }
30 |
31 | .contact-right {
32 | > h2 {
33 | font-family: app.$font-ubuntu;
34 | font-size: 1.5rem;
35 | }
36 |
37 | .cList {
38 | display: flex;
39 | margin: 1rem auto;
40 | padding: 1rem 0.3rem;
41 | font-family: app.$font-assistant;
42 | font-size: 1.2rem;
43 | align-items: center;
44 | background-color: app.$polic-blue;
45 | border-radius: 1rem;
46 | color: white;
47 |
48 | > svg {
49 | color: app.$gargo-gas;
50 | margin-right: 1rem;
51 | font-size: 1.5rem;
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/client/src/pages/CreateCategory/CreateCategory.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import "./CreateCategory.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import AdminMenu from "../../components/Layout/AdminMenu/AdminMenu";
5 | import toast from "react-hot-toast";
6 | import axios from "axios";
7 | import CategoryForm from "../../components/Form/CategoryForm";
8 | import { Modal } from "antd";
9 |
10 | const CreateCategory = () => {
11 | const [categories, setCategories] = useState([]);
12 | const [name, setName] = useState("");
13 | const [visible, setVisible] = useState(false);
14 | const [selected, setSelected] = useState(null);
15 | const [updatedName, setUpdatedName] = useState("");
16 |
17 | // handle form
18 | const handleSubmit = async (e) => {
19 | e.preventDefault();
20 | try {
21 | const { data } = await axios.post(
22 | "http://localhost:8080/api/v1/category/create-category",
23 | { name }
24 | );
25 | if (data.success) {
26 | toast.success(`${name} is created!`);
27 | getAllCategory();
28 | } else {
29 | toast.error(data.message);
30 | }
31 | } catch (error) {
32 | console.log(error);
33 | toast.error("Something went wrong un input form");
34 | }
35 | };
36 |
37 | // get all categories
38 | const getAllCategory = async () => {
39 | try {
40 | const { data } = await axios.get(
41 | "http://localhost:8080/api/v1/category/get-category"
42 | );
43 | if (data?.success) {
44 | setCategories(data?.category);
45 | }
46 | } catch (error) {
47 | console.log(error);
48 | toast.error("Something went wrong in getting category");
49 | }
50 | };
51 |
52 | useEffect(() => {
53 | getAllCategory();
54 | }, []);
55 |
56 | // update category
57 |
58 | const handleUpdate = async (e) => {
59 | e.preventDefault();
60 | try {
61 | const { data } = await axios.put(
62 | `http://localhost:8080/api/v1/category/update-category/${selected._id}`,
63 | { name: updatedName }
64 | );
65 | if (data.success) {
66 | toast.success(`${updatedName} is updated!`);
67 | setSelected(null);
68 | setUpdatedName("");
69 | setVisible(false);
70 | getAllCategory();
71 | } else {
72 | toast.error(data.message);
73 | }
74 | } catch (error) {
75 | toast.error("Something went wrong");
76 | }
77 | };
78 |
79 | // delete category
80 |
81 | const handleDelete = async (pId) => {
82 | try {
83 | const { data } = await axios.delete(
84 | `http://localhost:8080/api/v1/category/delete-category/${pId}`,
85 | { name: updatedName }
86 | );
87 | if (data.success) {
88 | toast.success(`Category is Deleted!`);
89 | getAllCategory();
90 | } else {
91 | toast.error(data.message);
92 | }
93 | } catch (error) {
94 | toast.error("Something went wrong");
95 | }
96 | };
97 |
98 | return (
99 |
100 |
101 |
102 |
105 |
106 |
107 |
Create Category
108 |
109 |
114 |
115 |
Manage Category
116 |
117 |
118 |
119 |
120 |
121 | Name
122 | Action
123 |
124 |
125 |
126 | {categories?.map((c) => (
127 | <>
128 |
129 | {c.name}
130 |
131 | {
134 | setVisible(true);
135 | setUpdatedName(c.name);
136 | setSelected(c);
137 | }}
138 | >
139 | Edit
140 |
141 | {
144 | handleDelete(c._id);
145 | }}
146 | >
147 | Delete
148 |
149 |
150 |
151 | >
152 | ))}
153 |
154 |
155 |
156 |
157 |
setVisible(false)}
159 | footer={null}
160 | visible={visible}
161 | >
162 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | );
174 | };
175 |
176 | export default CreateCategory;
177 |
--------------------------------------------------------------------------------
/client/src/pages/CreateCategory/CreateCategory.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .container {
4 | margin: 1rem;
5 | .row {
6 | display: flex;
7 | flex-direction: row;
8 |
9 | .row-right {
10 | font-family: app.$font-assistant;
11 | width: 100%;
12 | .card {
13 | > h1 {
14 | font-family: app.$font-ubuntu;
15 | padding: 1rem;
16 | text-align: center;
17 | }
18 | }
19 | }
20 | }
21 | }
22 |
23 | .table {
24 | // sets
25 |
26 | $gl-ms: "screen and (max-width: 23.5em)"; // up to 360px
27 | $gl-xs: "screen and (max-width: 35.5em)"; // up to 568px
28 | $gl-sm: "screen and (max-width: 48em)"; // max 768px
29 | $gl-md: "screen and (max-width: 64em)"; // max 1024px
30 | $gl-lg: "screen and (max-width: 80em)"; // max 1280px
31 |
32 | // table style
33 |
34 | table {
35 | border-spacing: 1;
36 | border-collapse: collapse;
37 | background: white;
38 | justify-content: center;
39 | align-items: center;
40 | overflow: hidden;
41 | max-width: 800px;
42 | width: 100%;
43 | margin: 0 auto;
44 | position: relative;
45 |
46 | * {
47 | position: relative;
48 | }
49 |
50 | td,
51 | th {
52 | padding-left: 8px;
53 | }
54 |
55 | thead tr {
56 | height: 60px;
57 | background: app.$polic-blue;
58 | font-size: 16px;
59 | color: white;
60 | }
61 |
62 | tbody tr {
63 | height: 48px;
64 | }
65 |
66 | td,
67 | th {
68 | font-family: app.$font-assistant;
69 | text-align: left;
70 | font-size: 1.5rem;
71 | border: 1px solid black;
72 | }
73 | }
74 |
75 | .form {
76 | margin: 1rem;
77 | }
78 |
79 | .btn {
80 | font-size: 1rem;
81 | padding: 0.5rem 2rem;
82 | background-color: app.$gargo-gas;
83 | border: 1px solid black;
84 | cursor: pointer;
85 | margin-right: 1rem;
86 | }
87 |
88 | .btn-danger {
89 | font-size: 1rem;
90 | padding: 0.5rem 2rem;
91 | background-color: red;
92 | border: 1px solid black;
93 | color: white;
94 | cursor: pointer;
95 | }
96 |
97 | @media #{$gl-xs} {
98 | table {
99 | display: block;
100 | > *,
101 | tr,
102 | td,
103 | th {
104 | display: block;
105 | }
106 |
107 | thead {
108 | display: none;
109 | }
110 | tbody tr {
111 | height: auto;
112 | padding: 8px 0;
113 | td {
114 | padding-left: 45%;
115 | margin-bottom: 12px;
116 | &:last-child {
117 | margin-bottom: 0;
118 | }
119 | &:before {
120 | position: absolute;
121 | font-weight: 700;
122 | width: 40%;
123 | left: 10px;
124 | top: 0;
125 | }
126 |
127 | &:nth-child(1):before {
128 | content: "Code";
129 | }
130 | &:nth-child(2):before {
131 | content: "Stock";
132 | }
133 | &:nth-child(3):before {
134 | content: "Cap";
135 | }
136 | &:nth-child(4):before {
137 | content: "Inch";
138 | }
139 | &:nth-child(5):before {
140 | content: "Box Type";
141 | }
142 | }
143 | }
144 | }
145 | }
146 |
147 | // body style
148 |
149 | body {
150 | background: #9bc86a;
151 | font: 400 14px "Calibri", "Arial";
152 | padding: 20px;
153 | }
154 |
155 | blockquote {
156 | color: white;
157 | text-align: center;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/client/src/pages/CreateProduct/CreateProduct.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "./CreateProduct.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import AdminMenu from "../../components/Layout/AdminMenu/AdminMenu";
5 | import toast from "react-hot-toast";
6 | import axios from "axios";
7 | import { Select } from "antd";
8 | import { useNavigate } from "react-router-dom";
9 |
10 | const { Option } = Select;
11 |
12 | const CreateProduct = () => {
13 | const navigate = useNavigate();
14 | const [categories, setCategories] = useState([]);
15 | const [photo, setPhoto] = useState("");
16 | const [name, setName] = useState("");
17 | const [description, setDescription] = useState("");
18 | const [price, setPrice] = useState("");
19 | const [category, setCategory] = useState("");
20 | const [quantity, setQuantity] = useState("");
21 | const [shipping, setShipping] = useState("");
22 |
23 | // Get all category
24 |
25 | const getAllCategory = async () => {
26 | try {
27 | const { data } = await axios.get(
28 | "http://localhost:8080/api/v1/category/get-category"
29 | );
30 | if (data?.success) {
31 | setCategories(data?.category);
32 | }
33 | } catch (error) {
34 | console.log(error);
35 | toast.error("Something went wrong in getting category");
36 | }
37 | };
38 |
39 | // create product function
40 |
41 | const handleCreate = async (e) => {
42 | e.preventDefault();
43 | try {
44 | const productData = new FormData();
45 | productData.append("name", name);
46 | productData.append("description", description);
47 | productData.append("price", price);
48 | productData.append("quantity", quantity);
49 | productData.append("category", category);
50 | productData.append("photo", photo);
51 | productData.append("shipping", shipping);
52 | const { data } = axios.post(
53 | "http://localhost:8080/api/v1/product/create-product",
54 | productData
55 | );
56 | if (data?.success) {
57 | toast.error(data?.message);
58 | } else {
59 | toast.success("Product Created Successfully");
60 | navigate("/dashboard/admin/products");
61 | }
62 | } catch (error) {
63 | console.log(error);
64 | toast.error("Something went wrong");
65 | }
66 | };
67 |
68 | useEffect(() => {
69 | getAllCategory();
70 | }, []);
71 |
72 | return (
73 |
74 |
75 |
76 |
79 |
80 |
81 |
Create Product
82 |
83 |
{
90 | setCategory(value);
91 | }}
92 | >
93 | {categories?.map((c) => (
94 |
95 | {c.name}
96 |
97 | ))}
98 |
99 |
100 |
101 |
102 | {photo ? photo.name : "Upload Photo"}
103 | setPhoto(e.target.files[0])}
108 | hidden
109 | />
110 |
111 |
112 |
113 | {photo && (
114 |
115 |
120 |
121 | )}
122 |
123 |
124 | setName(e.target.value)}
129 | />
130 |
131 |
132 |
141 |
142 | setPrice(e.target.value)}
147 | />
148 |
149 |
150 | setQuantity(e.target.value)}
155 | />
156 |
157 |
158 | {
165 | setShipping(value);
166 | }}
167 | >
168 | No
169 | Yes
170 |
171 |
172 |
173 | Create Product
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | );
182 | };
183 |
184 | export default CreateProduct;
185 |
--------------------------------------------------------------------------------
/client/src/pages/CreateProduct/CreateProduct.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .container {
4 | margin: 1rem;
5 | .row {
6 | display: flex;
7 | flex-direction: row;
8 | }
9 |
10 | .row-product {
11 | font-family: app.$font-assistant;
12 | width: 100%;
13 |
14 | .card-product {
15 | display: flex;
16 | flex-direction: column;
17 | align-items: center;
18 |
19 | .select {
20 | width: 100%;
21 | border: 1px solid rgb(124, 124, 124);
22 | margin-top: 1rem;
23 | border-radius: 1rem;
24 | }
25 | }
26 | }
27 | }
28 |
29 | .category {
30 | width: 70%;
31 |
32 | .photo {
33 | margin: 1rem 0;
34 |
35 | label {
36 | display: flex;
37 | border: 1px solid rgb(124, 124, 124);
38 | height: 38px;
39 | align-items: center;
40 | text-align: center;
41 | padding: 0 11px;
42 | background-color: app.$polic-blue;
43 | color: white;
44 | border-radius: 1rem;
45 | cursor: pointer;
46 | input {
47 | width: 70%;
48 | }
49 | }
50 | }
51 | }
52 |
53 | .product-input {
54 | display: flex;
55 |
56 | input {
57 | width: 100%;
58 | height: 38px;
59 | padding: 0 11px;
60 | margin-bottom: 1rem;
61 | border-radius: 1rem;
62 | border: 1px solid rgb(124, 124, 124);
63 | }
64 |
65 | textarea {
66 | padding: 10px 11px;
67 | margin-bottom: 1rem;
68 | width: 100%;
69 | height: 10rem;
70 | border-radius: 1rem;
71 | border: 1px solid rgb(124, 124, 124);
72 | }
73 |
74 | button {
75 | width: 100%;
76 | height: 38px;
77 | padding: 0 11px;
78 | margin: 1rem 0;
79 | border-radius: 1rem;
80 | border: 1px solid rgb(124, 124, 124);
81 | cursor: pointer;
82 | background-color: app.$gargo-gas;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/client/src/pages/Dashboard/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Dashboard.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import UserMenu from "../../components/Layout/UserMenu/UserMenu";
5 | import { useAuth } from "../../context/auth";
6 |
7 | const Dashboard = () => {
8 | const [auth] = useAuth();
9 |
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
{auth?.user?.name}
20 | {auth?.user?.email}
21 | {auth?.user?.address}
22 |
23 |
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default Dashboard;
31 |
--------------------------------------------------------------------------------
/client/src/pages/Dashboard/Dashboard.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .dash-container {
4 | margin: 1rem;
5 | .row {
6 | display: flex;
7 | flex-direction: row;
8 |
9 | .row-left {
10 | display: flex;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/pages/ForgotPassword/ForgotPassword.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import "./ForgotPassword.scss";
3 | import { useNavigate } from "react-router-dom";
4 | import Layout from "../../components/Layout/Layout";
5 | import toast from "react-hot-toast";
6 | import axios from "axios";
7 |
8 | const ForgotPassword = () => {
9 | const [email, setEmail] = useState("");
10 | const [newPassword, setNewPassword] = useState("");
11 | const [answer, setAnswer] = useState("");
12 |
13 | const navigate = useNavigate();
14 |
15 | // Form Function
16 |
17 | const handleSubmit = async (e) => {
18 | e.preventDefault();
19 | try {
20 | const res = await axios.post(
21 | "http://localhost:8080/api/v1/auth/forgot-password",
22 | {
23 | email,
24 | newPassword,
25 | answer,
26 | }
27 | );
28 |
29 | if (res.data.success) {
30 | toast.success(res.data && res.data.message);
31 |
32 | navigate("/login");
33 | } else {
34 | toast.error(res.data.message);
35 | }
36 | } catch (error) {
37 | console.log(error);
38 | toast.error("Something Went Wrong");
39 | }
40 | };
41 |
42 | return (
43 |
44 |
76 |
77 | );
78 | };
79 |
80 | export default ForgotPassword;
81 |
--------------------------------------------------------------------------------
/client/src/pages/ForgotPassword/ForgotPassword.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alphadev97/ecommerce-mern-project/49614fc171dfd9be12f7ab3ae4a8496add7be461/client/src/pages/ForgotPassword/ForgotPassword.scss
--------------------------------------------------------------------------------
/client/src/pages/Home/Home.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Layout from "../../components/Layout/Layout";
3 | import "./Home.scss";
4 | import axios from "axios";
5 | import { Checkbox, Radio } from "antd";
6 | import { Prices } from "../../components/Prices/Prices.jsx";
7 | import { useNavigate } from "react-router-dom";
8 | import { useCart } from "../../context/cart";
9 | import { toast } from "react-hot-toast";
10 | import logo from "../../assets/hero.jpg";
11 |
12 | const Home = () => {
13 | const navigate = useNavigate();
14 | const [cart, setCart] = useCart();
15 | const [products, setProducts] = useState([]);
16 | const [categories, setCategories] = useState([]);
17 | const [checked, setChecked] = useState([]);
18 | const [radio, setRadio] = useState([]);
19 | const [total, setTotal] = useState(0);
20 | const [page, setPage] = useState(1);
21 | const [loading, setLoading] = useState(false);
22 |
23 | // get all categories
24 | const getAllCategory = async () => {
25 | try {
26 | const { data } = await axios.get(
27 | "http://localhost:8080/api/v1/category/get-category"
28 | );
29 | if (data?.success) {
30 | setCategories(data?.category);
31 | }
32 | } catch (error) {
33 | console.log(error);
34 | }
35 | };
36 |
37 | // Filter by category
38 |
39 | const handleFilter = (value, id) => {
40 | let all = [...checked];
41 | if (value) {
42 | all.push(id);
43 | } else {
44 | all = all.filter((c) => c !== id);
45 | }
46 | setChecked(all);
47 | };
48 |
49 | useEffect(() => {
50 | getAllCategory();
51 | getTotal();
52 | }, []);
53 |
54 | // Get all products
55 | const getAllProducts = async () => {
56 | try {
57 | setLoading(true);
58 | const { data } = await axios.get(
59 | `http://localhost:8080/api/v1/product/product-get/${page}`
60 | );
61 | setLoading(false);
62 | setProducts(data.products);
63 | } catch (error) {
64 | setLoading(false);
65 | console.log(error);
66 | }
67 | };
68 |
69 | // get total count
70 | const getTotal = async () => {
71 | try {
72 | const { data } = await axios.get(
73 | "http://localhost:8080/api/v1/product/product-count"
74 | );
75 | setTotal(data?.total);
76 | } catch (error) {
77 | console.log(error);
78 | }
79 | };
80 |
81 | useEffect(() => {
82 | if (!checked.length || !radio.length) getAllProducts();
83 | }, [checked.length, radio.length]);
84 |
85 | useEffect(() => {
86 | if (checked.length || radio.length) filterProduct();
87 | }, [checked, radio]);
88 |
89 | useEffect(() => {
90 | if (page === 1) return;
91 | loadMore();
92 | }, [page]);
93 |
94 | // load more
95 | const loadMore = async () => {
96 | try {
97 | setLoading(true);
98 | const { data } = await axios.get(
99 | `http://localhost:8080/api/v1/product/product-get/${page}`
100 | );
101 | setLoading(false);
102 | setProducts([...products, ...data?.products]);
103 | } catch (error) {
104 | console.log(error);
105 | setLoading(false);
106 | }
107 | };
108 |
109 | // get filtered prducts
110 | const filterProduct = async () => {
111 | try {
112 | const { data } = await axios.post(
113 | "http://localhost:8080/api/v1/product/product-filter",
114 | { checked, radio }
115 | );
116 | setProducts(data?.products);
117 | } catch (error) {
118 | console.log(error);
119 | }
120 | };
121 | return (
122 |
123 |
124 |
125 |
Alpha97 E-Commerce
126 |
127 | Crafted on the MERN Stack, our E-Commerce store embodies simplicity
128 | and sophistication. Merging MongoDB, Express.js, React, and Node.js,
129 | this platform seamlessly unites powerful database management, robust
130 | back-end processes, dynamic front-end interfaces, and efficient
131 | server-side operations. Through this convergence, we've established
132 | a feature-rich and user-friendly online store that ensures seamless
133 | shopping experiences across a wide array of products.
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
Filter By Category
143 |
144 | {categories?.map((c) => (
145 | handleFilter(e.target.checked, c._id)}
148 | >
149 | {c.name}
150 |
151 | ))}
152 |
153 | {/* Price Filter */}
154 |
Filter By Price
155 |
156 |
setRadio(e.target.value)}>
157 | {Prices?.map((p) => (
158 |
159 | {p.name}
160 |
161 | ))}
162 |
163 |
164 |
165 | window.location.reload()}
168 | >
169 | Reset Filters
170 |
171 |
172 |
173 |
174 |
All Products
175 |
176 | {products?.map((p) => (
177 |
178 |
182 |
183 |
{p.name}
184 |
${p.price}
185 |
{p.description.substring(0, 30)}
186 |
187 |
188 | navigate(`/product/${p.slug}`)}>
189 | More Details
190 |
191 | {
193 | setCart([...cart, p]);
194 | localStorage.setItem(
195 | "cart",
196 | JSON.stringify([...cart, p])
197 | );
198 | toast.success("Item added to cart");
199 | }}
200 | >
201 | Add To Cart
202 |
203 |
204 |
205 | ))}
206 |
207 |
208 | {products && products.length < total && (
209 | {
212 | e.preventDefault();
213 | setPage(page + 1);
214 | }}
215 | >
216 | {loading ? "Loading...." : "Load More"}
217 |
218 | )}
219 |
220 |
221 |
222 |
223 | );
224 | };
225 |
226 | export default Home;
227 |
--------------------------------------------------------------------------------
/client/src/pages/Home/Home.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .hero {
4 | display: flex;
5 | flex-direction: row;
6 | background-color: white;
7 | justify-content: center;
8 | align-items: center;
9 | padding: 1rem;
10 | height: 80vh;
11 |
12 | .hero-left {
13 | display: flex;
14 | flex-direction: column;
15 | width: 40%;
16 | padding-left: 2rem;
17 |
18 | > h1 {
19 | font-family: app.$font-ubuntu;
20 | font-size: 3rem;
21 | color: app.$polic-blue;
22 | padding-bottom: 2rem;
23 | }
24 |
25 | > p {
26 | font-family: app.$font-assistant;
27 | font-size: 1.2rem;
28 | font-weight: 600;
29 | }
30 | }
31 |
32 | .hero-right {
33 | display: flex;
34 | align-items: center;
35 | justify-content: center;
36 | width: 60%;
37 |
38 | > img {
39 | width: 600px;
40 | height: 600px;
41 | }
42 | }
43 | }
44 |
45 | .home {
46 | display: flex;
47 | padding: 1rem;
48 |
49 | .left {
50 | width: 20%;
51 |
52 | .cat-filter {
53 | margin: 1rem 0;
54 | display: flex;
55 | flex-direction: column;
56 | gap: 1rem;
57 | }
58 | }
59 |
60 | .right {
61 | width: 80%;
62 |
63 | > h1 {
64 | display: flex;
65 | align-items: center;
66 | justify-content: center;
67 | }
68 | }
69 | }
70 |
71 | .card-container {
72 | display: grid;
73 | grid-template-columns: repeat(3, 1fr);
74 | gap: 1rem;
75 | padding: 1rem;
76 |
77 | .card {
78 | background-color: app.$white;
79 | border-radius: 10px;
80 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
81 | overflow: hidden;
82 |
83 | img {
84 | width: 100%;
85 | height: 200px;
86 | object-fit: contain;
87 | }
88 |
89 | .card-body {
90 | display: flex;
91 | justify-content: space-evenly;
92 | flex-wrap: wrap;
93 | padding: 2rem;
94 | text-align: center;
95 |
96 | .card-title {
97 | font-family: app.$font-assistant;
98 | font-size: 2rem;
99 | font-weight: 600;
100 | padding: 1rem;
101 | }
102 |
103 | .card-price {
104 | font-family: app.$font-ubuntu;
105 | font-size: 1.5rem;
106 | font-weight: 600;
107 | padding: 1rem;
108 | }
109 |
110 | .card-text {
111 | font-family: app.$font-assistant;
112 | font-size: 1rem;
113 | color: black;
114 | padding: 1rem;
115 | }
116 | }
117 | .btn {
118 | display: flex;
119 | justify-content: space-between;
120 | padding: 1rem;
121 |
122 | button {
123 | flex: 1;
124 | padding: 0.5rem 1rem;
125 | border: none;
126 | border-radius: 5px;
127 | background-color: app.$gargo-gas;
128 | color: app.$white;
129 | font-size: 1rem;
130 | cursor: pointer;
131 | transition: background-color 0.3s ease;
132 |
133 | &:hover {
134 | background-color: app.$polic-blue;
135 | }
136 |
137 | &:nth-child(1) {
138 | margin-right: 0.5rem;
139 | }
140 |
141 | &:nth-child(2) {
142 | margin-left: 0.5rem;
143 | background-color: app.$polic-blue;
144 | &:hover {
145 | background-color: app.$green-dark;
146 | }
147 | }
148 | }
149 | }
150 | }
151 | }
152 |
153 | .reset-btn {
154 | padding: 0.5rem 0;
155 | font-family: app.$font-assistant;
156 | font-size: 1.2rem;
157 | background-color: app.$polic-blue;
158 | color: white;
159 | cursor: pointer;
160 | border: none;
161 | border-radius: 0.5rem;
162 | }
163 |
164 | .pag {
165 | display: flex;
166 | padding: 1rem 0;
167 | justify-content: center;
168 |
169 | .pag-btn {
170 | width: 15rem;
171 | padding: 0.5rem 0;
172 | font-family: app.$font-assistant;
173 | font-size: 1.2rem;
174 | background-color: app.$polic-blue;
175 | color: white;
176 | cursor: pointer;
177 | border: none;
178 | border-radius: 0.5rem;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/client/src/pages/Login/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import "./Login.scss";
3 | import { useNavigate, useLocation } from "react-router-dom";
4 | import Layout from "../../components/Layout/Layout";
5 | import toast from "react-hot-toast";
6 | import axios from "axios";
7 | import { useAuth } from "../../context/auth";
8 |
9 | const Login = () => {
10 | const [email, setEmail] = useState("");
11 | const [password, setPassword] = useState("");
12 | const [auth, setAuth] = useAuth();
13 |
14 | const navigate = useNavigate();
15 | const location = useLocation();
16 |
17 | // Form Function
18 |
19 | const handleSubmit = async (e) => {
20 | e.preventDefault();
21 | try {
22 | const res = await axios.post("http://localhost:8080/api/v1/auth/login", {
23 | email,
24 | password,
25 | });
26 |
27 | if (res.data.success) {
28 | toast.success(res.data && res.data.message);
29 | setAuth({
30 | ...auth,
31 | user: res.data.user,
32 | token: res.data.token,
33 | });
34 | localStorage.setItem("auth", JSON.stringify(res.data));
35 | navigate(location.state || "/");
36 | } else {
37 | toast.error(res.data.message);
38 | }
39 | } catch (error) {
40 | console.log(error);
41 | toast.error("Something Went Wrong");
42 | }
43 | };
44 |
45 | return (
46 |
47 |
83 |
84 | );
85 | };
86 |
87 | export default Login;
88 |
--------------------------------------------------------------------------------
/client/src/pages/Login/Login.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .login {
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | height: 90vh;
8 | flex-direction: column;
9 | background-image: linear-gradient(
10 | to right top,
11 | #3c486b,
12 | #8b5a8b,
13 | #db6a84,
14 | #ff9661,
15 | #f9d949
16 | );
17 |
18 | > form {
19 | width: 50%;
20 | background-color: white;
21 | padding: 1.5rem;
22 | border-radius: 0.5rem;
23 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
24 |
25 | > h1 {
26 | font-family: app.$font-ubuntu;
27 | margin-bottom: 1rem;
28 | }
29 | .login-container {
30 | display: flex;
31 | flex-direction: column;
32 | gap: 1rem;
33 | width: 50%;
34 |
35 | > input {
36 | width: 100%;
37 | padding: 0.5rem;
38 | font-size: 1rem;
39 | font-family: app.$font-assistant;
40 | outline: none;
41 | border: 1px solid rgba(0, 0, 0, 0.308);
42 | border-radius: 0.2rem;
43 | margin: 0;
44 | }
45 |
46 | .login-btn {
47 | width: 100%;
48 | padding: 0rem 2rem;
49 | margin: 0;
50 | &:hover {
51 | background-color: app.$polic-blue;
52 | color: white;
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/client/src/pages/Orders/Orders.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "./Orders.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import UserMenu from "../../components/Layout/UserMenu/UserMenu";
5 | import axios from "axios";
6 | import { useAuth } from "../../context/auth";
7 | import moment from "moment";
8 |
9 | const Orders = () => {
10 | const [orders, setOrders] = useState([]);
11 | const [auth, setAuth] = useAuth();
12 |
13 | const getOrders = async () => {
14 | try {
15 | const { data } = await axios("http://localhost:8080/api/v1/auth/orders");
16 | setOrders(data);
17 | } catch (error) {
18 | console.log(error);
19 | }
20 | };
21 |
22 | useEffect(() => {
23 | if (auth?.token) getOrders();
24 | }, [auth?.token]);
25 |
26 | return (
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
Your Orders
36 | {orders?.map((o, i) => {
37 | return (
38 |
39 |
40 |
41 |
42 | #
43 | Status
44 | Buyer
45 | Date
46 | Payment
47 | Quantity
48 |
49 |
50 |
51 |
52 | {i + 1}
53 | {o?.status}
54 | {o?.buyer?.name}
55 | {moment(o?.createdAt).fromNow()}
56 | {o?.payment.success ? "Success" : "Faild"}
57 | {o?.products?.length}
58 |
59 |
60 |
61 |
62 | {o?.products?.map((p, i) => (
63 |
64 |
65 |
71 |
72 |
73 |
{p.name}
74 |
{p.description.substring(0, 30)}
75 |
${p.price}
76 |
77 |
78 | ))}
79 |
80 |
81 | );
82 | })}
83 |
84 |
85 |
86 |
87 |
88 | );
89 | };
90 |
91 | export default Orders;
92 |
--------------------------------------------------------------------------------
/client/src/pages/Orders/Orders.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .order-container {
4 | margin: 1rem;
5 | .row {
6 | display: flex;
7 | flex-direction: row;
8 |
9 | .row-left {
10 | width: 20%;
11 | }
12 |
13 | .row-right {
14 | width: 80%;
15 | }
16 | }
17 | }
18 |
19 | /* Apply basic styling to the table */
20 | .styled-table {
21 | font-family: app.$font-assistant;
22 | width: 100%;
23 | margin: auto;
24 | border-collapse: collapse;
25 | border: 1px solid #ccc;
26 | background-color: #f8f8f8;
27 | }
28 |
29 | /* Style the table header */
30 | .styled-table th {
31 | background-color: #333;
32 | color: white;
33 | padding: 10px;
34 | text-align: left;
35 | }
36 |
37 | /* Style the table rows */
38 | .styled-table td {
39 | border: 1px solid #ddd;
40 | padding: 10px;
41 | }
42 |
43 | /* Apply alternate row background color */
44 | .styled-table tbody tr:nth-child(even) {
45 | background-color: #f2f2f2;
46 | }
47 |
48 | /* Hover effect on rows */
49 | .styled-table tbody tr:hover {
50 | background-color: #e0e0e0;
51 | }
52 |
--------------------------------------------------------------------------------
/client/src/pages/PageNotFound/PageNotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Layout from "../../components/Layout/Layout";
3 | import { Link } from "react-router-dom";
4 | import pnf from "../../assets/pnf.jpg";
5 | import "./PageNotFound.scss";
6 |
7 | const PageNotFound = () => {
8 | return (
9 |
10 |
11 |
404
12 |
13 |
Looks Like You Are Lost
14 |
15 | Go Back
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default PageNotFound;
23 |
--------------------------------------------------------------------------------
/client/src/pages/PageNotFound/PageNotFound.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .pnf {
4 | display: flex;
5 | min-height: 90vh;
6 | flex-direction: column;
7 | align-items: center;
8 | justify-content: center;
9 | font-family: app.$font-ubuntu;
10 |
11 | > h1 {
12 | font-size: 3rem;
13 | }
14 |
15 | > img {
16 | width: 20rem;
17 | height: 20rem;
18 | }
19 |
20 | .pnf-btn {
21 | margin: 1rem;
22 | text-decoration: none;
23 | padding: 1rem 2rem;
24 | background-color: app.$gargo-gas;
25 | border-radius: 5px;
26 | color: app.$polic-blue;
27 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/client/src/pages/Policy/Policy.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Layout from "../../components/Layout/Layout";
3 | import policyImg from "../../assets/policy.jpg";
4 | import "./Policy.scss";
5 |
6 | const Policy = () => {
7 | return (
8 |
9 | Policy
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Our Policy
17 |
18 | Lorem ipsum dolor sit amet consectetur adipiscing, elit sed
19 | venenatis varius sapien quam dignissim, leo tempus nostra velit
20 |
21 | facilisis. Ad mi eros dapibus eget sagittis per enim bibendum
22 | rhoncus vehicula, mus fringilla cras gravida sollicitudin lectus
23 |
24 | morbi convallis. Vivamus id nec tristique faucibus lacinia egestas
25 |
26 | curabitur class aliquet convallis, varius arcu semper aenean dis non
27 |
28 | nulla ultricies nullam, a sagittis sapien diam taciti nisl fringilla
29 |
30 | massa sodales.
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default Policy;
39 |
--------------------------------------------------------------------------------
/client/src/pages/Policy/Policy.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .ph1 {
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | padding: 5rem 0;
8 | background-image: url(../../assets/contact-bg.jpg);
9 | background-position: center;
10 | background-size: cover;
11 | box-shadow: inset 0 0 0 2000px rgba(60, 72, 107, 0.507);
12 | font-size: 2rem;
13 | font-family: app.$font-ubuntu;
14 | color: white;
15 | }
16 |
17 | .policy {
18 | display: flex;
19 | margin: 2rem auto;
20 | gap: 2rem;
21 | align-items: center;
22 | justify-content: center;
23 |
24 | .policy-left {
25 | > img {
26 | width: 100%;
27 | height: 30rem;
28 | }
29 | }
30 |
31 | .policy-right {
32 | > h2 {
33 | font-family: app.$font-ubuntu;
34 | font-size: 1.5rem;
35 | }
36 |
37 | > p {
38 | display: flex;
39 | margin: 1rem auto;
40 | padding: 1rem 0.3rem;
41 | font-family: app.$font-assistant;
42 | font-size: 1.2rem;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/client/src/pages/ProductDetails.jsx/ProductDetails.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "./ProductDetails.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import axios from "axios";
5 | import { useParams, useNavigate } from "react-router-dom";
6 |
7 | const ProductDetails = () => {
8 | const params = useParams();
9 | const navigate = useNavigate();
10 | const [product, setProduct] = useState({});
11 | const [relatedProducts, setRelatedProducts] = useState([]);
12 |
13 | // initial product details
14 | useEffect(() => {
15 | if (params?.slug) getProduct();
16 | }, [params?.slug]);
17 |
18 | // get product
19 | const getProduct = async () => {
20 | try {
21 | const { data } = await axios.get(
22 | `http://localhost:8080/api/v1/product/get-product/${params.slug}`
23 | );
24 | setProduct(data?.product);
25 | getSimiliarProduct(data?.product._id, data?.product.category._id);
26 | } catch (error) {
27 | console.log(error);
28 | }
29 | };
30 |
31 | // get similiar product
32 | const getSimiliarProduct = async (pid, cid) => {
33 | try {
34 | const { data } = await axios.get(
35 | `http://localhost:8080/api/v1/product/related-product/${pid}/${cid}`
36 | );
37 | setRelatedProducts(data?.products);
38 | } catch (error) {
39 | console.log(error);
40 | }
41 | };
42 |
43 | return (
44 |
45 |
46 |
47 |
51 |
52 |
53 |
Product Details
54 | Name: {product.name}
55 | Description: {product.description}
56 | Price: {product.price}
57 | Category: {product.category?.name}
58 | Add To Cart
59 |
60 |
61 |
62 |
63 |
Simliar Product
64 |
65 | {relatedProducts.length < 1 &&
No Simliar Product Found
}
66 |
67 |
68 | {relatedProducts?.map((p) => (
69 |
70 |
74 |
75 |
{p.name}
76 |
{p.description.substring(0, 30)}
77 |
${p.price}
78 |
79 |
80 | navigate(`/product/${p.slug}`)}>
81 | More Details
82 |
83 | navigate("/cart")}>Add To Cart
84 |
85 |
86 | ))}
87 |
88 |
89 |
90 | );
91 | };
92 |
93 | export default ProductDetails;
94 |
--------------------------------------------------------------------------------
/client/src/pages/ProductDetails.jsx/ProductDetails.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .product-container {
4 | display: flex;
5 | padding: 1rem;
6 |
7 | .pr-left {
8 | display: flex;
9 | width: 50%;
10 | align-items: center;
11 | justify-content: center;
12 |
13 | img {
14 | width: 400px;
15 | height: 400px;
16 | }
17 | }
18 |
19 | .pr-right {
20 | display: flex;
21 | flex-direction: column;
22 | width: 50%;
23 | gap: 1rem;
24 |
25 | h1 {
26 | font-family: app.$font-ubuntu;
27 | }
28 |
29 | h6 {
30 | font-family: app.$font-assistant;
31 | font-size: 1rem;
32 | }
33 |
34 | button {
35 | width: 150px;
36 | padding: 0.5rem 2rem;
37 | cursor: pointer;
38 | border: none;
39 | background-color: app.$polic-blue;
40 | color: white;
41 | border-radius: 0.2rem;
42 | font-size: 1rem;
43 | }
44 | }
45 | }
46 |
47 | .sim-prod {
48 | display: flex;
49 | padding: 1rem;
50 | flex-direction: column;
51 | margin: 1rem 0;
52 | }
53 |
--------------------------------------------------------------------------------
/client/src/pages/Products/Products.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "./Products.scss";
3 | import AdminMenu from "../../components/Layout/AdminMenu/AdminMenu";
4 | import Layout from "../../components/Layout/Layout";
5 | import axios from "axios";
6 | import toast from "react-hot-toast";
7 | import { Link } from "react-router-dom";
8 |
9 | const Products = () => {
10 | const [products, setProducts] = useState([]);
11 |
12 | // Get all products
13 |
14 | const getAllProducts = async () => {
15 | try {
16 | const { data } = await axios.get(
17 | "http://localhost:8080/api/v1/product/get-product"
18 | );
19 | setProducts(data.products);
20 | } catch (error) {
21 | console.log(error);
22 | toast.error("Something went wrong");
23 | }
24 | };
25 |
26 | // lifecycle method
27 |
28 | useEffect(() => {
29 | getAllProducts();
30 | }, []);
31 |
32 | return (
33 |
34 |
35 |
36 |
39 |
40 |
All Products List
41 |
42 | {products?.map((p) => (
43 |
47 |
48 |
52 |
53 |
{p.name}
54 |
{p.description}
55 |
56 |
57 |
58 | ))}
59 |
60 |
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | export default Products;
68 |
--------------------------------------------------------------------------------
/client/src/pages/Products/Products.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .products_container {
4 | margin: 1rem;
5 | .row {
6 | display: flex;
7 | flex-direction: row;
8 | }
9 | }
10 |
11 | .product-link {
12 | text-decoration: none;
13 | color: black;
14 | }
15 |
16 | .card-container {
17 | display: grid;
18 | grid-template-columns: repeat(3, 1fr); // Display 3 products per row
19 | gap: 1rem; // Gap between products
20 | padding: 1rem; // Add some padding to the container if desired
21 |
22 | .card {
23 | background-color: app.$white;
24 | border-radius: 10px;
25 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
26 | overflow: hidden;
27 |
28 | img {
29 | width: 100%;
30 | height: 200px; // Set the desired image height
31 | object-fit: contain;
32 | }
33 |
34 | .card-body {
35 | padding: 1rem;
36 | text-align: center;
37 |
38 | .card-title {
39 | font-size: 1.2rem;
40 | font-weight: 600;
41 | }
42 |
43 | .card-text {
44 | font-size: 1rem;
45 | color: app.$polic-blue;
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/client/src/pages/Profile/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "./Profile.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import UserMenu from "../../components/Layout/UserMenu/UserMenu";
5 | import { useAuth } from "../../context/auth";
6 | import toast from "react-hot-toast";
7 | import axios from "axios";
8 |
9 | const Profile = () => {
10 | // context
11 | const [auth, setAuth] = useAuth();
12 |
13 | // state
14 | const [name, setName] = useState("");
15 | const [email, setEmail] = useState("");
16 | const [password, setPassword] = useState("");
17 | const [phone, setPhone] = useState("");
18 | const [address, setAddress] = useState("");
19 |
20 | // get user data
21 | useEffect(() => {
22 | const { email, name, phone, address } = auth?.user;
23 | setName(name);
24 | setPhone(phone);
25 | setEmail(email);
26 | setAddress(address);
27 | }, [auth?.user]);
28 |
29 | // Form Function
30 | const handleSubmit = async (e) => {
31 | e.preventDefault();
32 | try {
33 | const { data } = await axios.put(
34 | "http://localhost:8080/api/v1/auth/profile",
35 | {
36 | name,
37 | email,
38 | password,
39 | phone,
40 | address,
41 | }
42 | );
43 | if (data?.error) {
44 | toast.error(data?.error);
45 | } else {
46 | setAuth({ ...auth, user: data?.updatedUser });
47 | let ls = localStorage.getItem("auth");
48 | ls = JSON.parse(ls);
49 | ls.user = data.updatedUser;
50 | localStorage.setItem("auth", JSON.stringify(ls));
51 | toast.success("Profile Updated Successfully");
52 | }
53 | } catch (error) {
54 | console.log(error);
55 | toast.error("Something Went Wrong");
56 | }
57 | };
58 |
59 | return (
60 |
61 |
62 |
63 |
64 |
65 |
66 |
112 |
113 |
114 |
115 | );
116 | };
117 |
118 | export default Profile;
119 |
--------------------------------------------------------------------------------
/client/src/pages/Profile/Profile.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .profile-container {
4 | margin: 1rem;
5 | .row {
6 | display: flex;
7 | flex-direction: row;
8 |
9 | .row-left {
10 | width: 20%;
11 | }
12 |
13 | .row-right {
14 | width: 80%;
15 |
16 | .update {
17 | display: flex;
18 | align-items: center;
19 | justify-content: center;
20 | height: 90vh;
21 | flex-direction: column;
22 | background-image: linear-gradient(
23 | to right top,
24 | #3c486b,
25 | #8b5a8b,
26 | #db6a84,
27 | #ff9661,
28 | #f9d949
29 | );
30 |
31 | form {
32 | background-color: white;
33 | padding: 1.5rem;
34 | border-radius: 0.5rem;
35 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
36 | width: 90%;
37 |
38 | > h1 {
39 | font-family: app.$font-ubuntu;
40 | margin-bottom: 1rem;
41 | }
42 | .container {
43 | display: flex;
44 | flex-direction: column;
45 | gap: 1rem;
46 | width: 90%;
47 |
48 | input {
49 | padding: 0.5rem;
50 | font-size: 1rem;
51 | font-family: app.$font-assistant;
52 | outline: none;
53 | border: 1px solid rgba(0, 0, 0, 0.308);
54 | border-radius: 0.2rem;
55 | }
56 |
57 | button {
58 | padding: 0.5rem;
59 | font-size: 1rem;
60 | font-family: app.$font-assistant;
61 | outline: none;
62 | cursor: pointer;
63 | background-color: app.$polic-blue;
64 | color: white;
65 | border-radius: 0.2rem;
66 | transition: all 0.5s ease;
67 |
68 | &:hover {
69 | background-color: app.$gargo-gas;
70 | border: 1px solid app.$polic-blue;
71 | color: app.$polic-blue;
72 | }
73 | }
74 | }
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/client/src/pages/Register/Register.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import "./Register.scss";
3 | import { useNavigate } from "react-router-dom";
4 | import Layout from "../../components/Layout/Layout";
5 | import toast from "react-hot-toast";
6 | import axios from "axios";
7 |
8 | const Register = () => {
9 | const [name, setName] = useState("");
10 | const [email, setEmail] = useState("");
11 | const [password, setPassword] = useState("");
12 | const [phone, setPhone] = useState("");
13 | const [address, setAddress] = useState("");
14 | const [answer, setAnswer] = useState("");
15 | const navigate = useNavigate();
16 |
17 | // Form Function
18 |
19 | const handleSubmit = async (e) => {
20 | e.preventDefault();
21 | try {
22 | const res = await axios.post(
23 | "http://localhost:8080/api/v1/auth/register",
24 | {
25 | name,
26 | email,
27 | password,
28 | phone,
29 | address,
30 | answer,
31 | }
32 | );
33 |
34 | if (res.data.success) {
35 | toast.success(res.data.message);
36 | navigate("/login");
37 | } else {
38 | toast.error(res.data.message);
39 | }
40 | } catch (error) {
41 | console.log(error);
42 | toast.error("Something Went Wrong");
43 | }
44 | };
45 |
46 | return (
47 |
48 |
104 |
105 | );
106 | };
107 |
108 | export default Register;
109 |
--------------------------------------------------------------------------------
/client/src/pages/Register/Register.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .register-container {
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | height: 90vh;
8 | flex-direction: column;
9 | background-image: linear-gradient(
10 | to right top,
11 | #3c486b,
12 | #8b5a8b,
13 | #db6a84,
14 | #ff9661,
15 | #f9d949
16 | );
17 |
18 | > form {
19 | width: 50%;
20 | background-color: white;
21 | padding: 1.5rem;
22 | border-radius: 0.5rem;
23 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;
24 |
25 | > h1 {
26 | font-family: app.$font-ubuntu;
27 | margin-bottom: 1rem;
28 | }
29 | .container {
30 | display: flex;
31 | flex-direction: column;
32 | width: 50%;
33 | gap: 1rem;
34 |
35 | > input {
36 | width: 100%;
37 | padding: 0.5rem;
38 | font-size: 1rem;
39 | font-family: app.$font-assistant;
40 | outline: none;
41 | border: 1px solid rgba(0, 0, 0, 0.308);
42 | border-radius: 0.2rem;
43 | margin: 0;
44 | }
45 |
46 | > button {
47 | width: 100%;
48 | padding: 0rem 2rem;
49 | margin: 0;
50 | &:hover {
51 | background-color: app.$polic-blue;
52 | color: white;
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/client/src/pages/Search/Search.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Search.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import { useSearch } from "../../context/search";
5 |
6 | const Search = () => {
7 | const [values, setValues] = useSearch();
8 | return (
9 |
10 |
11 |
12 |
Search Results
13 |
14 | {values?.results.length < 1
15 | ? "No Products Found"
16 | : `Found ${values?.results.length}`}
17 |
18 |
19 |
20 | {values?.results.map((p) => (
21 |
22 |
26 |
27 |
{p.name}
28 |
{p.description.substring(0, 30)}
29 |
${p.price}
30 |
31 |
32 | More Details
33 | Add To Cart
34 |
35 |
36 | ))}
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default Search;
44 |
--------------------------------------------------------------------------------
/client/src/pages/Search/Search.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .container {
4 | .text-center {
5 | display: flex;
6 | flex-direction: column;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/client/src/pages/UpdateProduct/UpdateProduct.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "./UpdateProduct.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import AdminMenu from "../../components/Layout/AdminMenu/AdminMenu";
5 | import toast from "react-hot-toast";
6 | import axios from "axios";
7 | import { Select } from "antd";
8 | import { useNavigate, useParams } from "react-router-dom";
9 |
10 | const UpdateProduct = () => {
11 | const navigate = useNavigate();
12 | const params = useParams();
13 | const [categories, setCategories] = useState([]);
14 | const [photo, setPhoto] = useState("");
15 | const [name, setName] = useState("");
16 | const [description, setDescription] = useState("");
17 | const [price, setPrice] = useState("");
18 | const [category, setCategory] = useState("");
19 | const [quantity, setQuantity] = useState("");
20 | const [shipping, setShipping] = useState("");
21 | const [id, setId] = useState("");
22 |
23 | // Get single product
24 |
25 | const getSingleProduct = async () => {
26 | try {
27 | const { data } = await axios.get(
28 | `http://localhost:8080/api/v1/product/get-product/${params.slug}`
29 | );
30 | setName(data.product.name);
31 | setId(data.product._id);
32 | setDescription(data.product.description);
33 | setPrice(data.product.price);
34 | setCategory(data.product.category._id);
35 | setQuantity(data.product.quantity);
36 | setShipping(data.product.shipping);
37 | } catch (error) {
38 | console.log(error);
39 | }
40 | };
41 |
42 | useEffect(() => {
43 | getSingleProduct();
44 | //eslint-disable-next-line
45 | }, []);
46 |
47 | // Get all category
48 |
49 | const getAllCategory = async () => {
50 | try {
51 | const { data } = await axios.get(
52 | "http://localhost:8080/api/v1/category/get-category"
53 | );
54 | if (data?.success) {
55 | setCategories(data?.category);
56 | }
57 | } catch (error) {
58 | console.log(error);
59 | toast.error("Something went wrong in getting category");
60 | }
61 | };
62 |
63 | // create product function
64 |
65 | const handleUpdate = async (e) => {
66 | e.preventDefault();
67 | try {
68 | const productData = new FormData();
69 | productData.append("name", name);
70 | productData.append("description", description);
71 | productData.append("price", price);
72 | productData.append("quantity", quantity);
73 | productData.append("category", category);
74 | photo && productData.append("photo", photo);
75 | productData.append("shipping", shipping);
76 | const { data } = axios.put(
77 | `http://localhost:8080/api/v1/product/update-product/${id}`,
78 | productData
79 | );
80 | if (data?.success) {
81 | toast.error(data?.message);
82 | } else {
83 | toast.success("Product Updated Successfully");
84 | navigate("/dashboard/admin/products");
85 | }
86 | } catch (error) {
87 | console.log(error);
88 | toast.error("Something went wrong");
89 | }
90 | };
91 |
92 | useEffect(() => {
93 | getAllCategory();
94 | }, []);
95 |
96 | // Delete product
97 |
98 | const handleDelete = async () => {
99 | try {
100 | let answer = window.prompt(
101 | "Are you sure you want to delete this product?"
102 | );
103 |
104 | if (!answer) return;
105 | const { data } = axios.delete(
106 | `http://localhost:8080/api/v1/product/delete-product/${id}`
107 | );
108 | toast.success("Product Deleted Successfully");
109 | navigate("/dashboard/admin/products");
110 | } catch (error) {
111 | console.log(error);
112 | toast.error("Something went wrong");
113 | }
114 | };
115 |
116 | return (
117 |
118 |
119 |
120 |
123 |
124 |
125 |
Update Product
126 |
127 |
{
134 | setCategory(value);
135 | }}
136 | value={category}
137 | >
138 | {categories?.map((c) => (
139 |
140 | {c.name}
141 |
142 | ))}
143 |
144 |
145 |
146 |
147 | {photo ? photo.name : "Upload Photo"}
148 | setPhoto(e.target.files[0])}
153 | hidden
154 | />
155 |
156 |
157 |
158 | {photo ? (
159 |
160 |
165 |
166 | ) : (
167 |
168 |
173 |
174 | )}
175 |
176 |
177 | setName(e.target.value)}
182 | />
183 |
184 |
185 |
194 |
195 | setPrice(e.target.value)}
200 | />
201 |
202 |
203 | setQuantity(e.target.value)}
208 | />
209 |
210 |
211 | {
218 | setShipping(value);
219 | }}
220 | value={shipping ? "Yes" : "No"}
221 | >
222 | No
223 | Yes
224 |
225 |
226 |
227 | Update Product
228 |
229 |
230 |
231 | Delete Product
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 | );
241 | };
242 |
243 | export default UpdateProduct;
244 |
--------------------------------------------------------------------------------
/client/src/pages/UpdateProduct/UpdateProduct.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .container {
4 | margin: 1rem;
5 | .row {
6 | display: flex;
7 | flex-direction: row;
8 | }
9 |
10 | .row-product {
11 | font-family: app.$font-assistant;
12 | width: 100%;
13 |
14 | .card-product {
15 | display: flex;
16 | flex-direction: column;
17 | align-items: center;
18 |
19 | .select {
20 | width: 100%;
21 | border: 1px solid rgb(124, 124, 124);
22 | margin-top: 1rem;
23 | border-radius: 1rem;
24 | }
25 | }
26 | }
27 | }
28 |
29 | .category {
30 | width: 70%;
31 |
32 | .photo {
33 | margin: 1rem 0;
34 |
35 | label {
36 | display: flex;
37 | border: 1px solid rgb(124, 124, 124);
38 | height: 38px;
39 | align-items: center;
40 | text-align: center;
41 | padding: 0 11px;
42 | background-color: app.$polic-blue;
43 | color: white;
44 | border-radius: 1rem;
45 | cursor: pointer;
46 | input {
47 | width: 70%;
48 | }
49 | }
50 | }
51 | }
52 |
53 | .product-input {
54 | display: flex;
55 |
56 | input {
57 | width: 100%;
58 | height: 38px;
59 | padding: 0 11px;
60 | margin-bottom: 1rem;
61 | border-radius: 1rem;
62 | border: 1px solid rgb(124, 124, 124);
63 | }
64 |
65 | textarea {
66 | padding: 10px 11px;
67 | margin-bottom: 1rem;
68 | width: 100%;
69 | height: 10rem;
70 | border-radius: 1rem;
71 | border: 1px solid rgb(124, 124, 124);
72 | }
73 |
74 | button {
75 | width: 100%;
76 | height: 38px;
77 | padding: 0 11px;
78 | margin: 1rem 0;
79 | border-radius: 1rem;
80 | border: 1px solid rgb(124, 124, 124);
81 | cursor: pointer;
82 | background-color: app.$gargo-gas;
83 | }
84 |
85 | .delete {
86 | background-color: red;
87 | color: white;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/client/src/pages/Users/Users.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Users.scss";
3 | import Layout from "../../components/Layout/Layout";
4 | import AdminMenu from "../../components/Layout/AdminMenu/AdminMenu";
5 |
6 | const Users = () => {
7 | return (
8 |
9 |
10 |
11 |
14 |
15 |
16 |
Users
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default Users;
26 |
--------------------------------------------------------------------------------
/client/src/pages/Users/Users.scss:
--------------------------------------------------------------------------------
1 | @use "../../App.scss" as app;
2 |
3 | .container {
4 | margin: 1rem;
5 | .row {
6 | display: flex;
7 | flex-direction: row;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/client/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/config/db.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import colors from "colors";
3 |
4 | const connectDB = async () => {
5 | try {
6 | const conn = await mongoose.connect(process.env.MONGO_URL);
7 | console.log(
8 | `Connected to MongoDB Database ${conn.connection.host}`.bgMagenta.white
9 | );
10 | } catch (error) {
11 | console.log(`Error in MongoDB ${error}`.bgRed.white);
12 | }
13 | };
14 |
15 | export default connectDB;
16 |
--------------------------------------------------------------------------------
/controllers/authController.js:
--------------------------------------------------------------------------------
1 | import { comparePassword, hashPassword } from "../helpers/authHelper.js";
2 | import userModel from "../models/userModel.js";
3 | import orderModel from "../models/orderModel.js";
4 | import JWT from "jsonwebtoken";
5 |
6 | export const registerController = async (req, res) => {
7 | try {
8 | const { name, email, password, phone, address, answer } = req.body;
9 |
10 | // Validation
11 | if (!name) {
12 | return res.send({ message: "Name Is Required!" });
13 | }
14 | if (!email) {
15 | return res.send({ message: "Email Is Required!" });
16 | }
17 | if (!password) {
18 | return res.send({ message: "Password Is Required!" });
19 | }
20 | if (!phone) {
21 | return res.send({ message: "Phone Number Is Required!" });
22 | }
23 | if (!address) {
24 | return res.send({ message: "Address Is Required!" });
25 | }
26 | if (!answer) {
27 | return res.send({ message: "Answer Is Required!" });
28 | }
29 |
30 | // Check User
31 | const existingUser = await userModel.findOne({ email });
32 |
33 | // Existing user
34 | if (existingUser) {
35 | return res.status(200).send({
36 | success: false,
37 | message: "Already registered, please login",
38 | });
39 | }
40 |
41 | // Register User
42 | const hashedPassword = await hashPassword(password);
43 |
44 | // Save
45 | const user = await new userModel({
46 | name,
47 | email,
48 | phone,
49 | address,
50 | password: hashedPassword,
51 | answer,
52 | }).save();
53 |
54 | res.status(201).send({
55 | success: true,
56 | message: "User Register Successfully",
57 | user,
58 | });
59 | } catch (error) {
60 | console.log(error);
61 | res.status(500).send({
62 | success: false,
63 | message: "Error in registration",
64 | error,
65 | });
66 | }
67 | };
68 |
69 | // POST LOGIN
70 | export const loginController = async (req, res) => {
71 | try {
72 | const { email, password } = req.body;
73 |
74 | // Validation
75 |
76 | if (!email || !password) {
77 | return res.status(404).send({
78 | success: false,
79 | message: "Invalid email or password",
80 | });
81 | }
82 |
83 | // Check User
84 | const user = await userModel.findOne({ email });
85 |
86 | if (!user) {
87 | return res.status(404).send({
88 | success: false,
89 | message: "Email is not registered",
90 | });
91 | }
92 |
93 | const match = await comparePassword(password, user.password);
94 |
95 | if (!match) {
96 | return res.status(200).send({
97 | success: false,
98 | message: "Invalid Password",
99 | });
100 | }
101 |
102 | // Token
103 | const token = await JWT.sign({ _id: user._id }, process.env.JWT_SECRET, {
104 | expiresIn: "7d",
105 | });
106 |
107 | res.status(200).send({
108 | success: true,
109 | message: "Login successfully",
110 | user: {
111 | name: user.name,
112 | email: user.email,
113 | phone: user.phone,
114 | address: user.address,
115 | role: user.role,
116 | },
117 | token,
118 | });
119 | } catch (error) {
120 | console.log(error);
121 | res.status(500).send({
122 | success: false,
123 | message: "Error in login",
124 | error,
125 | });
126 | }
127 | };
128 |
129 | // forgotPasswordController
130 |
131 | export const forgotPasswordController = async (req, res) => {
132 | try {
133 | const { email, answer, newPassword } = req.body;
134 |
135 | if (!email) {
136 | res.status(400).send({ message: "Email is required!" });
137 | }
138 |
139 | if (!answer) {
140 | res.status(400).send({ message: "Answer is required!" });
141 | }
142 |
143 | if (!newPassword) {
144 | res.status(400).send({ message: "New Password is required!" });
145 | }
146 |
147 | // Check
148 | const user = await userModel.findOne({ email, answer });
149 |
150 | // Validation
151 | if (!user) {
152 | return res.status(404).send({
153 | success: false,
154 | message: "Wrong Email or Answer",
155 | });
156 | }
157 |
158 | const hashed = await hashPassword(newPassword);
159 |
160 | await userModel.findByIdAndUpdate(user._id, { password: hashed });
161 |
162 | res.status(200).send({
163 | success: true,
164 | message: "Password Reset Successfully",
165 | });
166 | } catch (error) {
167 | console.log(error);
168 | res.status(500).send({
169 | success: false,
170 | message: "Something Went Wrong",
171 | error,
172 | });
173 | }
174 | };
175 |
176 | // test controller
177 | export const testController = (req, res) => {
178 | res.send("Protected Route");
179 | };
180 |
181 | // update profile
182 | export const updateProfileController = async (req, res) => {
183 | try {
184 | const { name, email, password, address, phone } = req.body;
185 | const user = await userModel.findById(req.user._id);
186 |
187 | // password
188 | if (password && password.length < 6) {
189 | return res.json({ error: "Password is required and 6 character long" });
190 | }
191 |
192 | const hashedPassword = password ? await hashPassword(password) : undefined;
193 |
194 | const updatedUser = await userModel.findByIdAndUpdate(
195 | req.user._id,
196 | {
197 | name: name || user.name,
198 | password: hashedPassword || user.password,
199 | phone: phone || user.phone,
200 | address: address || user.address,
201 | },
202 | { new: true }
203 | );
204 |
205 | res.status(200).send({
206 | success: true,
207 | message: "Profile update successfully",
208 | updatedUser,
209 | });
210 | } catch (error) {
211 | console.log(error);
212 | res.status(400).send({
213 | success: false,
214 | message: "Error while updating profile",
215 | error,
216 | });
217 | }
218 | };
219 |
220 | // orders
221 | export const getOrdersController = async (req, res) => {
222 | try {
223 | const orders = await orderModel
224 | .find({ buyer: req.user._id })
225 | .populate("products", "-photo")
226 | .populate("buyer", "name");
227 | res.json(orders);
228 | } catch (error) {
229 | console.log(error);
230 | res.status(500).send({
231 | success: false,
232 | message: "Error while getting orders",
233 | error,
234 | });
235 | }
236 | };
237 |
238 | // all orders
239 | export const getAllOrdersController = async (req, res) => {
240 | try {
241 | const orders = await orderModel
242 | .find({})
243 | .populate("products", "-photo")
244 | .populate("buyer", "name")
245 | .sort({ createdAt: "-1" });
246 | res.json(orders);
247 | } catch (error) {
248 | console.log(error);
249 | res.status(500).send({
250 | success: false,
251 | message: "Error while getting orders",
252 | error,
253 | });
254 | }
255 | };
256 |
257 | // order status
258 | export const orderStatusController = async (req, res) => {
259 | try {
260 | const { orderId } = req.params;
261 | const { status } = req.body;
262 | const orders = await orderModel.findByIdAndUpdate(
263 | orderId,
264 | { status },
265 | { new: true }
266 | );
267 | res.json(orders);
268 | } catch (error) {
269 | console.log(error);
270 | res.status(500).send({
271 | success: false,
272 | message: "Error while updating orders",
273 | error,
274 | });
275 | }
276 | };
277 |
--------------------------------------------------------------------------------
/controllers/categoryController.js:
--------------------------------------------------------------------------------
1 | import categoryModel from "../models/categoryModel.js";
2 | import slugify from "slugify";
3 |
4 | export const createCategoryController = async (req, res) => {
5 | try {
6 | const { name } = req.body;
7 | if (!name) {
8 | return res.status(401).send({ message: "Name is required" });
9 | }
10 |
11 | const existingCategory = await categoryModel.findOne({ name });
12 |
13 | if (existingCategory) {
14 | return res.status(200).send({
15 | success: true,
16 | message: "Category Already Exists",
17 | });
18 | }
19 |
20 | const category = await new categoryModel({
21 | name,
22 | slug: slugify(name),
23 | }).save();
24 | res.status(201).send({
25 | success: true,
26 | message: "New Category Created.",
27 | category,
28 | });
29 | } catch (error) {
30 | console.log(error);
31 | res.status(500).send({
32 | success: false,
33 | error,
34 | message: "Error in Category",
35 | });
36 | }
37 | };
38 |
39 | // update category
40 |
41 | export const updateCategoryController = async (req, res) => {
42 | try {
43 | const { name } = req.body;
44 | const { id } = req.params;
45 |
46 | const category = await categoryModel.findByIdAndUpdate(
47 | id,
48 | { name, slug: slugify(name) },
49 | { new: true }
50 | );
51 | res.status(200).send({
52 | success: true,
53 | message: "Category Updated Sucessfully",
54 | category,
55 | });
56 | } catch (error) {
57 | console.log(error);
58 | res.status(500).send({
59 | success: false,
60 | error,
61 | message: "Error while Updating Category",
62 | });
63 | }
64 | };
65 |
66 | // Get all categories
67 |
68 | export const categoryController = async (req, res) => {
69 | try {
70 | const category = await categoryModel.find();
71 | res.status(200).send({
72 | success: true,
73 | message: "All categories list",
74 | category,
75 | });
76 | } catch (error) {
77 | console.log(error);
78 | res.status(500).send({
79 | success: false,
80 | error,
81 | message: "Error while getting all categories",
82 | });
83 | }
84 | };
85 |
86 | // Get single category
87 |
88 | export const singleCategoryController = async (req, res) => {
89 | try {
90 | const category = await categoryModel.findOne({ slug: req.params.slug });
91 | res.status(200).send({
92 | success: true,
93 | message: "Get Single Category Successfull",
94 | category,
95 | });
96 | } catch (error) {
97 | console.log(error);
98 | res.status(500).send({
99 | success: false,
100 | error,
101 | message: "Error while getting single category",
102 | });
103 | }
104 | };
105 |
106 | // Delete category
107 | export const deleteCategoryController = async (req, res) => {
108 | try {
109 | const { id } = req.params;
110 | await categoryModel.findByIdAndDelete(id);
111 | res.status(200).send({
112 | success: true,
113 | message: "Category deleted successfully",
114 | });
115 | } catch (error) {
116 | console.log(error);
117 | res.status(500).send({
118 | success: false,
119 | error,
120 | message: "Error while deleting category",
121 | });
122 | }
123 | };
124 |
--------------------------------------------------------------------------------
/controllers/productController.js:
--------------------------------------------------------------------------------
1 | import slugify from "slugify";
2 | import productModel from "../models/productModel.js";
3 | import orderModel from "../models/orderModel.js";
4 | import fs from "fs";
5 | import categoryModel from "./../models/categoryModel.js";
6 | import braintree from "braintree";
7 | import dotenv from "dotenv";
8 |
9 | dotenv.config();
10 |
11 | // Payment gateway
12 | var gateway = new braintree.BraintreeGateway({
13 | environment: braintree.Environment.Sandbox,
14 | merchantId: process.env.BRAINTREE_MERCHANT_ID,
15 | publicKey: process.env.BRAINTREE_PUBLIC_KEY,
16 | privateKey: process.env.BRAINTREE_PRIVATE_KEY,
17 | });
18 |
19 | export const createProductController = async (req, res) => {
20 | try {
21 | const { name, slug, description, price, category, quantity, shipping } =
22 | req.fields;
23 | const { photo } = req.files;
24 |
25 | // Validation
26 | switch (true) {
27 | case !name:
28 | return res.status(500).send({ error: "Name is required!" });
29 | case !description:
30 | return res.status(500).send({ error: "Description is required!" });
31 | case !price:
32 | return res.status(500).send({ error: "Price is required!" });
33 | case !category:
34 | return res.status(500).send({ error: "Category is required!" });
35 | case !quantity:
36 | return res.status(500).send({ error: "Quantity is required!" });
37 | case photo && photo.size > 1000000:
38 | return res
39 | .status(500)
40 | .send({ error: "Photo is required and should be less then 1mb!" });
41 | }
42 |
43 | const products = new productModel({ ...req.fields, slug: slugify(name) });
44 | if (photo) {
45 | products.photo.data = fs.readFileSync(photo.path);
46 | products.photo.contentType = photo.type;
47 | }
48 | await products.save();
49 | res.status(201).send({
50 | success: true,
51 | message: "Product created successfully",
52 | products,
53 | });
54 | } catch (error) {
55 | console.log(error);
56 | res.status(500).send({
57 | success: false,
58 | error,
59 | message: "Error in creating product",
60 | });
61 | }
62 | };
63 |
64 | // get all products
65 | export const getProductController = async (req, res) => {
66 | try {
67 | const products = await productModel
68 | .find({})
69 | .populate("category")
70 | .select("-photo")
71 | .limit(20)
72 | .sort({ createdAt: -1 });
73 | res.status(200).send({
74 | success: true,
75 | message: "All Products",
76 | totalCount: products.length,
77 | products,
78 | });
79 | } catch (error) {
80 | console.log(error);
81 | res.status(500).send({
82 | success: false,
83 | message: "Error in Get all products",
84 | error: error.message,
85 | });
86 | }
87 | };
88 |
89 | // get single product
90 | export const getSingleProductController = async (req, res) => {
91 | try {
92 | const product = await productModel
93 | .findOne({ slug: req.params.slug })
94 | .select("-photo")
95 | .populate("category");
96 | res.status(200).send({
97 | success: true,
98 | message: "Single product fetched",
99 | product,
100 | });
101 | } catch (error) {
102 | console.log(error);
103 | res.status(500).send({
104 | success: false,
105 | message: "Error in getting single product",
106 | error,
107 | });
108 | }
109 | };
110 |
111 | // get photo
112 | export const productPhotoController = async (req, res) => {
113 | try {
114 | const product = await productModel.findById(req.params.pid).select("photo");
115 | if (product.photo.data) {
116 | res.set("Content-type", product.photo.contentType);
117 | return res.status(200).send(product.photo.data);
118 | }
119 | } catch (error) {
120 | console.log(error);
121 | res.status(500).send({
122 | success: false,
123 | message: "Error in getting product photo",
124 | error,
125 | });
126 | }
127 | };
128 |
129 | // delete product
130 | export const deleteProductController = async (req, res) => {
131 | try {
132 | await productModel.findByIdAndDelete(req.params.pid).select("-photo");
133 | res.status(200).send({
134 | success: true,
135 | message: "Product deleted successfully",
136 | });
137 | } catch (error) {
138 | console.log(error);
139 | res.status(500).send({
140 | success: false,
141 | message: "Error while deleting product",
142 | error,
143 | });
144 | }
145 | };
146 |
147 | // update product
148 | export const updateProductController = async (req, res) => {
149 | try {
150 | const { name, slug, description, price, category, quantity, shipping } =
151 | req.fields;
152 | const { photo } = req.files;
153 |
154 | // Validation
155 | switch (true) {
156 | case !name:
157 | return res.status(500).send({ error: "Name is required!" });
158 | case !description:
159 | return res.status(500).send({ error: "Description is required!" });
160 | case !price:
161 | return res.status(500).send({ error: "Price is required!" });
162 | case !category:
163 | return res.status(500).send({ error: "Category is required!" });
164 | case !quantity:
165 | return res.status(500).send({ error: "Quantity is required!" });
166 | case photo && photo.size > 1000000:
167 | return res
168 | .status(500)
169 | .send({ error: "Photo is required and should be less then 1mb!" });
170 | }
171 |
172 | const products = await productModel.findByIdAndUpdate(
173 | req.params.pid,
174 | { ...req.fields, slug: slugify(name) },
175 | { new: true }
176 | );
177 | if (photo) {
178 | products.photo.data = fs.readFileSync(photo.path);
179 | products.photo.contentType = photo.type;
180 | }
181 | await products.save();
182 | res.status(201).send({
183 | success: true,
184 | message: "Product updated successfully",
185 | products,
186 | });
187 | } catch (error) {
188 | console.log(error);
189 | res.status(500).send({
190 | success: false,
191 | error,
192 | message: "Error in updating product",
193 | });
194 | }
195 | };
196 |
197 | // filters
198 |
199 | export const productFiltersController = async (req, res) => {
200 | try {
201 | const { checked, radio } = req.body;
202 | let args = {};
203 | if (checked.length > 0) args.category = checked;
204 | if (radio.length) args.price = { $gte: radio[0], $lte: radio[1] };
205 | const products = await productModel.find(args);
206 | res.status(200).send({
207 | success: true,
208 | message: "Products Filtered Successfully",
209 | products,
210 | });
211 | } catch (error) {
212 | console.log(error);
213 | res.status(400).send({
214 | success: false,
215 | message: "Error While Filtering Products",
216 | error,
217 | });
218 | }
219 | };
220 |
221 | // product count
222 |
223 | export const productCountController = async (req, res) => {
224 | try {
225 | const total = await productModel.find({}).estimatedDocumentCount();
226 | res.status(200).send({
227 | success: true,
228 | total,
229 | });
230 | } catch (error) {
231 | console.log(error);
232 | res.status(400).send({
233 | message: "Error in product count",
234 | error,
235 | success: false,
236 | });
237 | }
238 | };
239 |
240 | // product list base on page
241 |
242 | export const productListController = async (req, res) => {
243 | try {
244 | const perPage = 6;
245 | const page = req.params.page ? req.params.page : 1;
246 | const products = await productModel
247 | .find({})
248 | .select("-photo")
249 | .skip((page - 1) * perPage)
250 | .limit(perPage)
251 | .sort({ createdAt: -1 });
252 |
253 | res.status(200).send({
254 | success: true,
255 | products,
256 | });
257 | } catch (error) {
258 | console.log(error);
259 | res.status(400).send({
260 | success: false,
261 | message: "Error in per page ctrl",
262 | error,
263 | });
264 | }
265 | };
266 |
267 | // search product
268 | export const searchProductController = async (req, res) => {
269 | try {
270 | const { keyword } = req.params;
271 | const results = await productModel
272 | .find({
273 | $or: [
274 | { name: { $regex: keyword, $options: "i" } },
275 | { description: { $regex: keyword, $options: "i" } },
276 | ],
277 | })
278 | .select("-photo");
279 | res.json(results);
280 | } catch (error) {
281 | console.log(error);
282 | res.status(400).send({
283 | success: false,
284 | message: "Error in search product API",
285 | error,
286 | });
287 | }
288 | };
289 |
290 | // similiar products
291 | export const relatedProductController = async (req, res) => {
292 | try {
293 | const { pid, cid } = req.params;
294 | const products = await productModel
295 | .find({
296 | category: cid,
297 | _id: { $ne: pid },
298 | })
299 | .select("-photo")
300 | .limit(3)
301 | .populate("category");
302 | res.status(200).send({
303 | success: true,
304 | products,
305 | });
306 | } catch (error) {
307 | console.log(error);
308 | res.status(400).send({
309 | success: false,
310 | message: "Error while getting related product",
311 | error,
312 | });
313 | }
314 | };
315 |
316 | // Get product by category
317 | export const productCategoryController = async (req, res) => {
318 | try {
319 | const category = await categoryModel.findOne({ slug: req.params.slug });
320 | const products = await productModel.find({ category }).populate("category");
321 | res.status(200).send({
322 | success: true,
323 | category,
324 | products,
325 | });
326 | } catch (error) {
327 | console.log(error);
328 | res.status(400).send({
329 | success: false,
330 | message: "Error while getting products",
331 | error,
332 | });
333 | }
334 | };
335 |
336 | // payment gateway api
337 | // token
338 | export const braintreeTokenController = async (req, res) => {
339 | try {
340 | gateway.clientToken.generate({}, function (err, response) {
341 | if (err) {
342 | res.status(500).send(err);
343 | } else {
344 | res.send(response);
345 | }
346 | });
347 | } catch (error) {
348 | console.log(error);
349 | }
350 | };
351 |
352 | // payments
353 | export const braintreePaymentController = async (req, res) => {
354 | try {
355 | const { cart, nonce } = req.body;
356 | let total = 0;
357 | cart.map((i) => {
358 | total += i.price;
359 | });
360 | let newTransaction = gateway.transaction.sale(
361 | {
362 | amount: total,
363 | paymentMethodNonce: nonce,
364 | options: {
365 | submitForSettlement: true,
366 | },
367 | },
368 | function (error, result) {
369 | if (result) {
370 | const order = new orderModel({
371 | products: cart,
372 | payment: result,
373 | buyer: req.user._id,
374 | }).save();
375 | res.json({ ok: true });
376 | } else {
377 | res.status(500).send(error);
378 | }
379 | }
380 | );
381 | } catch (error) {
382 | console.log(error);
383 | }
384 | };
385 |
--------------------------------------------------------------------------------
/helpers/authHelper.js:
--------------------------------------------------------------------------------
1 | import bcrypt from "bcryptjs";
2 |
3 | export const hashPassword = async (password) => {
4 | try {
5 | const saltRounds = 10;
6 | const hashedPassword = await bcrypt.hash(password, saltRounds);
7 | return hashedPassword;
8 | } catch (error) {
9 | console.log(error);
10 | }
11 | };
12 |
13 | export const comparePassword = async (password, hashedPassword) => {
14 | return bcrypt.compare(password, hashedPassword);
15 | };
16 |
--------------------------------------------------------------------------------
/middlewares/authMiddleware.js:
--------------------------------------------------------------------------------
1 | import JWT from "jsonwebtoken";
2 | import userModel from "../models/userModel.js";
3 |
4 | // Protected Route Token Base
5 |
6 | export const requireSignIn = async (req, res, next) => {
7 | try {
8 | const decode = JWT.verify(
9 | req.headers.authorization,
10 | process.env.JWT_SECRET
11 | );
12 | req.user = decode;
13 | next();
14 | } catch (error) {
15 | console.log(error);
16 | }
17 | };
18 |
19 | // Admin Access
20 | export const isAdmin = async (req, res, next) => {
21 | try {
22 | const user = await userModel.findById(req.user._id);
23 |
24 | if (user.role !== 1) {
25 | res.status(401).send({
26 | success: false,
27 | message: "UnAuthroized Access",
28 | });
29 | } else {
30 | next();
31 | }
32 | } catch (error) {
33 | console.log(error);
34 | res.status(401).send({
35 | success: false,
36 | error,
37 | message: "Error in admin middleware",
38 | });
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/models/categoryModel.js:
--------------------------------------------------------------------------------
1 | import mongoose, { mongo } from "mongoose";
2 |
3 | const categorySchema = new mongoose.Schema({
4 | name: {
5 | type: String,
6 | // required: true,
7 | // unique: true,
8 | },
9 | slug: {
10 | type: String,
11 | lowercase: true,
12 | },
13 | });
14 |
15 | export default mongoose.model("Category", categorySchema);
16 |
--------------------------------------------------------------------------------
/models/orderModel.js:
--------------------------------------------------------------------------------
1 | import mongoose, { mongo } from "mongoose";
2 |
3 | const orderSchema = new mongoose.Schema(
4 | {
5 | products: [
6 | {
7 | type: mongoose.ObjectId,
8 | ref: "Products",
9 | },
10 | ],
11 | payment: {},
12 | buyer: {
13 | type: mongoose.ObjectId,
14 | ref: "users",
15 | },
16 | status: {
17 | type: String,
18 | default: "Not Process",
19 | enum: ["Not Process", "Processing", "Shipped", "Delievered", "Cancel"],
20 | },
21 | },
22 | { timestamps: true }
23 | );
24 |
25 | export default mongoose.model("Order", orderSchema);
26 |
--------------------------------------------------------------------------------
/models/productModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const productSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | slug: {
10 | type: String,
11 | required: true,
12 | },
13 | description: {
14 | type: String,
15 | required: true,
16 | },
17 | price: {
18 | type: Number,
19 | require: true,
20 | },
21 | category: {
22 | type: mongoose.ObjectId,
23 | ref: "Category",
24 | required: true,
25 | },
26 | quantity: {
27 | type: Number,
28 | required: true,
29 | },
30 | photo: {
31 | data: Buffer,
32 | contentType: String,
33 | },
34 | shipping: {
35 | type: Boolean,
36 | },
37 | },
38 | { timestamps: true }
39 | );
40 |
41 | export default mongoose.model("Products", productSchema);
42 |
--------------------------------------------------------------------------------
/models/userModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const userSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | trim: true,
9 | },
10 | email: {
11 | type: String,
12 | required: true,
13 | unique: true,
14 | },
15 | password: {
16 | type: String,
17 | required: true,
18 | },
19 | phone: {
20 | type: String,
21 | required: true,
22 | },
23 | address: {
24 | type: {},
25 | required: true,
26 | },
27 | answer: {
28 | type: String,
29 | required: true,
30 | },
31 | role: {
32 | type: Number,
33 | default: 0,
34 | },
35 | },
36 | { timestamps: true }
37 | );
38 |
39 | export default mongoose.model("users", userSchema);
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ecommerce-mern-project",
3 | "version": "1.0.0",
4 | "description": "MERN Stack E-Commerce Project with REST API",
5 | "main": "server.js",
6 | "type": "module",
7 | "scripts": {
8 | "start": "node server.js",
9 | "server": "nodemon server.js",
10 | "client": "npm run start --prefix ./client",
11 | "dev": "concurrently \"npm run start\" \"npm run client\""
12 | },
13 | "keywords": [],
14 | "author": "Muhammad Usama | Alpha97",
15 | "license": "MIT",
16 | "dependencies": {
17 | "bcryptjs": "^2.4.3",
18 | "braintree": "^3.16.0",
19 | "colors": "^1.4.0",
20 | "concurrently": "^8.2.0",
21 | "cors": "^2.8.5",
22 | "dotenv": "^16.3.1",
23 | "express": "^4.18.2",
24 | "express-formidable": "^1.2.0",
25 | "jsonwebtoken": "^9.0.1",
26 | "mongoose": "^7.4.0",
27 | "morgan": "^1.10.0",
28 | "nodemon": "^3.0.1",
29 | "slugify": "^1.6.6"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/routes/authRoute.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | registerController,
4 | loginController,
5 | testController,
6 | forgotPasswordController,
7 | updateProfileController,
8 | getOrdersController,
9 | getAllOrdersController,
10 | orderStatusController,
11 | } from "../controllers/authController.js";
12 | import { isAdmin, requireSignIn } from "../middlewares/authMiddleware.js";
13 |
14 | // Router Object
15 | const router = express.Router();
16 |
17 | // Routing
18 | // REGISTER || METHOD POST
19 | router.post("/register", registerController);
20 |
21 | // LOGIN || METHOD POST
22 | router.post("/login", loginController);
23 |
24 | // Forgot Password || POST
25 | router.post("/forgot-password", forgotPasswordController);
26 |
27 | // test route
28 | router.get("/test", requireSignIn, isAdmin, testController);
29 |
30 | // Protected user route auth
31 | router.get("/user-auth", requireSignIn, (req, res) => {
32 | res.status(200).send({ ok: true });
33 | });
34 |
35 | // Protected admin route auth
36 | router.get("/admin-auth", requireSignIn, isAdmin, (req, res) => {
37 | res.status(200).send({ ok: true });
38 | });
39 |
40 | // update profile
41 | router.put("/profile", requireSignIn, updateProfileController);
42 |
43 | // orders
44 | router.get("/orders", requireSignIn, getOrdersController);
45 |
46 | // all orders
47 | router.get("/all-orders", requireSignIn, isAdmin, getAllOrdersController);
48 |
49 | // order status update
50 | router.put(
51 | "/order-status/:orderId",
52 | requireSignIn,
53 | isAdmin,
54 | orderStatusController
55 | );
56 |
57 | export default router;
58 |
--------------------------------------------------------------------------------
/routes/categoryRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { isAdmin, requireSignIn } from "../middlewares/authMiddleware.js";
3 | import {
4 | categoryController,
5 | createCategoryController,
6 | deleteCategoryController,
7 | singleCategoryController,
8 | updateCategoryController,
9 | } from "../controllers/categoryController.js";
10 |
11 | const router = express.Router();
12 |
13 | //routes
14 | // create category
15 | router.post(
16 | "/create-category",
17 | requireSignIn,
18 | isAdmin,
19 | createCategoryController
20 | );
21 |
22 | // update category
23 | router.put(
24 | "/update-category/:id",
25 | requireSignIn,
26 | isAdmin,
27 | updateCategoryController
28 | );
29 |
30 | // get all category
31 | router.get("/get-category", categoryController);
32 |
33 | // single cartegory
34 | router.get("/single-category/:slug", singleCategoryController);
35 |
36 | // delet category
37 | router.delete(
38 | "/delete-category/:id",
39 | requireSignIn,
40 | isAdmin,
41 | deleteCategoryController
42 | );
43 |
44 | export default router;
45 |
--------------------------------------------------------------------------------
/routes/productRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { isAdmin, requireSignIn } from "../middlewares/authMiddleware.js";
3 | import {
4 | braintreePaymentController,
5 | braintreeTokenController,
6 | createProductController,
7 | deleteProductController,
8 | getProductController,
9 | getSingleProductController,
10 | productCategoryController,
11 | productCountController,
12 | productFiltersController,
13 | productListController,
14 | productPhotoController,
15 | relatedProductController,
16 | searchProductController,
17 | updateProductController,
18 | } from "../controllers/productController.js";
19 | import formidable from "express-formidable";
20 |
21 | const router = express.Router();
22 |
23 | //routes
24 | router.post(
25 | "/create-product",
26 | requireSignIn,
27 | isAdmin,
28 | formidable(),
29 | createProductController
30 | );
31 |
32 | // update product
33 | router.put(
34 | "/update-product/:pid",
35 | requireSignIn,
36 | isAdmin,
37 | formidable(),
38 | updateProductController
39 | );
40 |
41 | // Get products
42 | router.get("/get-product", getProductController);
43 |
44 | // Get single products
45 | router.get("/get-product/:slug", getSingleProductController);
46 |
47 | // get photo
48 | router.get("/product-photo/:pid", productPhotoController);
49 |
50 | // delete product
51 | router.delete("/delete-product/:pid", deleteProductController);
52 |
53 | // filter product
54 | router.post("/product-filter", productFiltersController);
55 |
56 | // product count
57 | router.get("/product-count", productCountController);
58 |
59 | // Product per page
60 | router.get("/product-get/:page", productListController);
61 |
62 | // search product
63 | router.get("/search/:keyword", searchProductController);
64 |
65 | // similiar product
66 | router.get("/related-product/:pid/:cid", relatedProductController);
67 |
68 | // category wise product
69 | router.get("/product-category/:slug", productCategoryController);
70 |
71 | // payments route
72 | // token
73 | router.get("/braintree/token", braintreeTokenController);
74 |
75 | // payments
76 | router.post("/braintree/payment", requireSignIn, braintreePaymentController);
77 |
78 | export default router;
79 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import colors from "colors";
3 | import dotenv from "dotenv";
4 | import morgan from "morgan";
5 | import connectDB from "./config/db.js";
6 | import authRoutes from "./routes/authRoute.js";
7 | import categoryRoutes from "./routes/categoryRoutes.js";
8 | import productRoutes from "./routes/productRoutes.js";
9 | import cors from "cors";
10 | import path from "path";
11 | import { fileURLToPath } from "url";
12 |
13 | // Config .env
14 | dotenv.config();
15 |
16 | // Database config
17 | connectDB();
18 |
19 | //es module fix
20 | const __filename = fileURLToPath(import.meta.url);
21 | const __dirname = path.dirname(__filename);
22 |
23 | // Rest Object
24 | const app = express();
25 |
26 | // Middlewares
27 | app.use(cors());
28 | app.use(express.json());
29 | app.use(morgan("dev"));
30 | app.use(express.static(path.join(__dirname, "./client/dist")));
31 |
32 | // Routes
33 | app.use("/api/v1/auth", authRoutes);
34 | app.use("/api/v1/category", categoryRoutes);
35 | app.use("/api/v1/product", productRoutes);
36 |
37 | // Rest API
38 | app.use("*", function (req, res) {
39 | res.sendFile(path.join(__dirname, "./client/dist/index.html"));
40 | });
41 |
42 | // Port
43 | const PORT = process.env.PORT || 8080;
44 |
45 | // App Listen
46 | app.listen(PORT, () => {
47 | console.log(
48 | `Server is running on ${process.env.DEV_MODE} mode on port ${PORT}`.bgCyan
49 | .white
50 | );
51 | });
52 |
--------------------------------------------------------------------------------