├── README.md
└── server
├── .gitignore
├── README.md
├── client
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo.png
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.js
│ ├── assets
│ ├── admin.png
│ ├── full-page.png
│ ├── images
│ │ ├── Capture.png
│ │ ├── banner-1.webp
│ │ ├── banner-2.webp
│ │ ├── banner-3.webp
│ │ ├── camera-1.webp
│ │ ├── camera-15webp.webp
│ │ ├── camera-2.webp
│ │ ├── camera-3.webp
│ │ ├── camera-4.webp
│ │ ├── camera-5.webp
│ │ ├── camera-6.webp
│ │ ├── camera-7.webp
│ │ ├── camera-8.webp
│ │ ├── company-1.webp
│ │ ├── company-2.webp
│ │ ├── company-3.webp
│ │ ├── company-4.webp
│ │ ├── company-5.png
│ │ ├── company-6.webp
│ │ ├── discount-area.webp
│ │ ├── home-banner-1.webp
│ │ ├── home-banner-2.webp
│ │ ├── home-banner-3.webp
│ │ ├── home-banner-4.webp
│ │ ├── home-banner-5.webp
│ │ ├── home-banner-6.webp
│ │ ├── logo.png
│ │ ├── protected-area.webp
│ │ ├── upload-1.png
│ │ └── upload.png
│ ├── index.js
│ └── user.png
│ ├── components
│ ├── Banner
│ │ └── Banner.jsx
│ ├── CartSummary
│ │ └── CartSummary.jsx
│ ├── Chart
│ │ └── Chart.jsx
│ ├── CompanyArea
│ │ └── CompanyArea.jsx
│ ├── DiscountArea
│ │ └── DiscountArea.jsx
│ ├── Footer
│ │ └── Footer.jsx
│ ├── Header
│ │ └── Header.jsx
│ ├── HomeBanner
│ │ └── HomeBanner.jsx
│ ├── Loader
│ │ └── Loader.jsx
│ ├── Loading
│ │ └── Loading.jsx
│ ├── MobileMenu
│ │ └── MobileMenu.jsx
│ ├── NewsLetter
│ │ └── NewsLetter.jsx
│ ├── ProductRating
│ │ └── ProductRating.jsx
│ ├── Products
│ │ ├── Products.jsx
│ │ └── SingleProduct
│ │ │ └── SingleProduct.jsx
│ ├── ProtectedArea
│ │ └── ProtectedArea.jsx
│ ├── SectionTitle
│ │ └── SectionTitle.jsx
│ └── index.js
│ ├── fakeData.js
│ ├── index.js
│ ├── pages
│ ├── ActivationEmail
│ │ └── ActivationEmail.js
│ ├── Cart
│ │ └── Cart.jsx
│ ├── CartList
│ │ └── CartList.jsx
│ ├── Checkout
│ │ └── Checkout.jsx
│ ├── CheckoutPayment
│ │ └── CheckoutPayment.jsx
│ ├── Contact
│ │ └── Contact.jsx
│ ├── ForgotPassword
│ │ └── ForgotPassword.jsx
│ ├── Home
│ │ └── Home.jsx
│ ├── Login
│ │ └── Login.jsx
│ ├── Manager
│ │ ├── AdminDashboard
│ │ │ ├── AddProduct
│ │ │ │ └── AddProduct.jsx
│ │ │ ├── AdminDashboard.jsx
│ │ │ ├── AllProducts
│ │ │ │ └── AllProducts.jsx
│ │ │ ├── EditUser
│ │ │ │ └── EditUser.jsx
│ │ │ ├── HomeAdmin
│ │ │ │ └── HomeAdmin.jsx
│ │ │ ├── OrderList
│ │ │ │ └── OrderList.jsx
│ │ │ ├── ProcessOrder
│ │ │ │ └── ProcessOrder.jsx
│ │ │ ├── Sidebar
│ │ │ │ └── Sidebar.jsx
│ │ │ └── UserList
│ │ │ │ ├── UserList.jsx
│ │ │ │ └── UserListItem
│ │ │ │ └── UserListItem.jsx
│ │ └── UserDashboard
│ │ │ ├── MyOrders
│ │ │ └── Myorders.jsx
│ │ │ ├── OrderDetails
│ │ │ └── OrderDetails.jsx
│ │ │ ├── Profile
│ │ │ └── Profile.jsx
│ │ │ ├── Sidebar
│ │ │ └── Sidebar.jsx
│ │ │ └── UserDashboard.jsx
│ ├── NotFound
│ │ └── NotFound.jsx
│ ├── PrivateRoute
│ │ └── PrivateRoute.jsx
│ ├── ProductDetail
│ │ └── ProductDetail.jsx
│ ├── Register
│ │ └── Register.jsx
│ ├── ResetPassword
│ │ └── ResetPassword.jsx
│ ├── Shop
│ │ ├── Shop.jsx
│ │ └── SingleItem
│ │ │ └── SingleItem.jsx
│ └── index.js
│ ├── redux
│ ├── actions
│ │ ├── cartActions.js
│ │ ├── orderActions.js
│ │ ├── productActions.js
│ │ └── userActions.js
│ ├── constants
│ │ ├── cartConstants.js
│ │ ├── orderConstants.js
│ │ ├── productConstants.js
│ │ └── userConstants.js
│ ├── reducers
│ │ ├── cartReducer.js
│ │ ├── index.js
│ │ ├── orderReducer.js
│ │ ├── productReducer.js
│ │ └── userReducer.js
│ └── store.js
│ ├── styles
│ ├── core
│ │ ├── _activation-email.scss
│ │ ├── _add-product.scss
│ │ ├── _all-products.scss
│ │ ├── _banner.scss
│ │ ├── _cart.scss
│ │ ├── _chart.scss
│ │ ├── _checkout-payment.scss
│ │ ├── _checkout.scss
│ │ ├── _company-area.scss
│ │ ├── _contact.scss
│ │ ├── _discount-area.scss
│ │ ├── _footer.scss
│ │ ├── _header.scss
│ │ ├── _home-admin.scss
│ │ ├── _home-banner.scss
│ │ ├── _loader.scss
│ │ ├── _mobile-menu.scss
│ │ ├── _my-orders.scss
│ │ ├── _news-letter.scss
│ │ ├── _not-found.scss
│ │ ├── _order-details.scss
│ │ ├── _process-order.scss
│ │ ├── _product-detail.scss
│ │ ├── _products.scss
│ │ ├── _profile.scss
│ │ ├── _protected-area.scss
│ │ ├── _register.scss
│ │ ├── _reset.scss
│ │ ├── _section-title.scss
│ │ ├── _sidebar.scss
│ │ ├── _user-list-item.scss
│ │ ├── core.scss
│ │ └── utils
│ │ │ ├── _fonts.scss
│ │ │ ├── _functions.scss
│ │ │ ├── _index.scss
│ │ │ ├── _mixins.scss
│ │ │ └── _variables.scss
│ └── styles.scss
│ └── utils
│ ├── checkTokenExp.js
│ └── validation.js
├── config
├── database.js
├── generateToken.js
└── sendMail.js
├── controllers
├── authController.js
├── orderController.js
├── productController.js
└── uploadController.js
├── middlewares
├── auth.js
├── authAdmin.js
├── errorHandler.js
└── uploadImage.js
├── models
├── orderModels.js
├── productModel.js
└── userModels.js
├── package-lock.json
├── package.json
├── routes
├── authRoutes.js
├── orderRoutes.js
├── productRoutes.js
├── uploadRoutes.js
└── userRoutes.js
├── server.js
└── services
└── CustomErrorHandler.js
/README.md:
--------------------------------------------------------------------------------
1 | # Mern-camera-website using React + Redux + Sass + Node js + express js + Mongodb, Stripe payment gateway etc.
2 |
3 | - Register, login with validation form and logout.
4 | - Quick login with Google
5 | - For authentication used google OAuth
6 | - Forgot password, reset password and register a new account by Email verification.
7 | - Update user information (name, password and avatar)
8 | - Shopping cart functionality
9 | - Implemented stripe payment gateway
10 | - Implemented user dashboard where user see her profile and he see her orders and what is the status of the individual orders
11 | - Restful apis
12 | - Impelemeted JSON web token for website security and also use refresh token for more secure
13 | - Implemented Admin panel where admin manage all orders, update orders, remove orders, manage all users, update user role, upadate user, add new product, update product, remove product etc
14 | - Admin panel have user User Analytics, sales status etc
15 | - Responsive for all devices like mobile, tablet, laptop, desktop etc
16 | - Used tecnologies:
17 | - Fronted: React js, Reudx, Sass, React-bootstrap, React-router-dom, Framer-motion etc
18 | - Backend: Node js, Express js, Mongodb, JSON web token, cloudinary etc
19 |
20 | # Here is the Demo - (https://mern-camera-shop.herokuapp.com/)
21 |
22 | # Website Interface -
23 |
24 | 
25 |
26 | # Admin Dashboard interface -
27 |
28 | 
29 |
30 | # User Dashboard interface -
31 |
32 | 
33 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | .env
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # mern-camera-website
2 |
--------------------------------------------------------------------------------
/server/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/server/client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/server/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@redux-devtools/extension": "^3.2.2",
7 | "@testing-library/jest-dom": "^5.16.4",
8 | "@testing-library/react": "^12.1.4",
9 | "@testing-library/user-event": "^13.5.0",
10 | "axios": "^0.26.1",
11 | "bootstrap": "^5.1.3",
12 | "framer-motion": "^6.3.0",
13 | "jwt-decode": "^3.1.2",
14 | "react": "^18.0.0",
15 | "react-bootstrap": "^2.2.3",
16 | "react-dom": "^18.0.0",
17 | "react-google-login": "^5.2.2",
18 | "react-icons": "^4.3.1",
19 | "react-redux": "^7.2.8",
20 | "react-router-dom": "^6.3.0",
21 | "react-scripts": "5.0.0",
22 | "react-stripe-checkout": "^2.6.3",
23 | "react-toast-notifications": "^2.5.1",
24 | "recharts": "^2.1.9",
25 | "redux": "^4.1.2",
26 | "redux-thunk": "^2.4.1",
27 | "sass": "^1.50.0",
28 | "web-vitals": "^2.1.4"
29 | },
30 | "scripts": {
31 | "start": "react-scripts start",
32 | "build": "react-scripts build",
33 | "test": "react-scripts test",
34 | "eject": "react-scripts eject"
35 | },
36 | "eslintConfig": {
37 | "extends": [
38 | "react-app",
39 | "react-app/jest"
40 | ]
41 | },
42 | "browserslist": {
43 | "production": [
44 | ">0.2%",
45 | "not dead",
46 | "not op_mini all"
47 | ],
48 | "development": [
49 | "last 1 chrome version",
50 | "last 1 firefox version",
51 | "last 1 safari version"
52 | ]
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/server/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/public/favicon.ico
--------------------------------------------------------------------------------
/server/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 | ICAM - Camera Website
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/server/client/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/public/logo.png
--------------------------------------------------------------------------------
/server/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/public/logo192.png
--------------------------------------------------------------------------------
/server/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/public/logo512.png
--------------------------------------------------------------------------------
/server/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/server/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/server/client/src/App.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { Routes, Route, Navigate } from "react-router-dom";
3 | import { Header } from "./components";
4 | import {
5 | ForgotPassword,
6 | Home,
7 | Login,
8 | Register,
9 | ActivationEmail,
10 | ResetPassword,
11 | NotFound,
12 | PrivateRoute,
13 | UserDashboard,
14 | Profile,
15 | Myorders,
16 | AdminDashboard,
17 | HomeAdmin,
18 | EditUser,
19 | AddProduct,
20 | AllProducts,
21 | Shop,
22 | ProductDetail,
23 | CheckoutPayment,
24 | OrderDetails,
25 | OrderList,
26 | ProcessOrder,
27 | Contact,
28 | } from "./pages";
29 | import { refreshToken } from "./redux/actions/userActions";
30 | import { useDispatch } from "react-redux";
31 | import { ToastProvider } from "react-toast-notifications";
32 | import { useSelector } from "react-redux";
33 | import "./styles/styles.scss";
34 |
35 | function App() {
36 | const dispatch = useDispatch();
37 |
38 | useEffect(() => {
39 | dispatch(refreshToken());
40 | }, [dispatch]);
41 |
42 | const user = useSelector((state) => state?.userLogin?.userInfo);
43 | return (
44 |
45 |
46 |
47 | } />
48 | : }
51 | />
52 | } />
53 | } />
54 |
55 | :
59 | }
60 | >
61 |
62 | } />
63 |
67 |
68 |
69 | }
70 | />
71 |
72 | :
76 | }
77 | />
78 |
79 | } />
80 | } />
81 |
82 | {user?.access_token && user?.user?.role === 0 && (
83 |
87 |
88 |
89 | }
90 | >
91 | } />
92 | } />
93 | } />
94 |
95 | )}
96 |
97 | {user?.access_token && user?.user?.role === 1 && (
98 |
102 |
103 |
104 | }
105 | >
106 | } />
107 | } />
108 | } />
109 | } />
110 | } />
111 |
112 | } />
113 |
114 | } />
115 | } />
116 |
117 | )}
118 |
119 | } />
120 |
121 |
122 | );
123 | }
124 |
125 | export default App;
126 |
--------------------------------------------------------------------------------
/server/client/src/assets/admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/admin.png
--------------------------------------------------------------------------------
/server/client/src/assets/full-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/full-page.png
--------------------------------------------------------------------------------
/server/client/src/assets/images/Capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/Capture.png
--------------------------------------------------------------------------------
/server/client/src/assets/images/banner-1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/banner-1.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/banner-2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/banner-2.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/banner-3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/banner-3.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/camera-1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/camera-1.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/camera-15webp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/camera-15webp.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/camera-2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/camera-2.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/camera-3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/camera-3.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/camera-4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/camera-4.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/camera-5.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/camera-5.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/camera-6.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/camera-6.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/camera-7.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/camera-7.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/camera-8.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/camera-8.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/company-1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/company-1.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/company-2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/company-2.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/company-3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/company-3.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/company-4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/company-4.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/company-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/company-5.png
--------------------------------------------------------------------------------
/server/client/src/assets/images/company-6.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/company-6.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/discount-area.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/discount-area.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/home-banner-1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/home-banner-1.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/home-banner-2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/home-banner-2.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/home-banner-3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/home-banner-3.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/home-banner-4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/home-banner-4.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/home-banner-5.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/home-banner-5.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/home-banner-6.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/home-banner-6.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/logo.png
--------------------------------------------------------------------------------
/server/client/src/assets/images/protected-area.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/protected-area.webp
--------------------------------------------------------------------------------
/server/client/src/assets/images/upload-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/upload-1.png
--------------------------------------------------------------------------------
/server/client/src/assets/images/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/images/upload.png
--------------------------------------------------------------------------------
/server/client/src/assets/index.js:
--------------------------------------------------------------------------------
1 | import companyOne from "./images/company-1.webp";
2 | import companyTwo from "./images/company-2.webp";
3 | import companyThree from "./images/company-3.webp";
4 | import companyFour from "./images/company-4.webp";
5 | import companyFive from "./images/company-5.png";
6 | import companySix from "./images/company-6.webp";
7 | import bannerOne from "./images/banner-1.webp";
8 | import bannerTwo from "./images/banner-2.webp";
9 | import bannerThree from "./images/banner-3.webp";
10 | import logo from "./images/logo.png";
11 |
12 | export {
13 | companyFive,
14 | companyFour,
15 | companyOne,
16 | companyThree,
17 | companyTwo,
18 | companySix,
19 | bannerOne,
20 | bannerThree,
21 | bannerTwo,
22 | logo,
23 | };
24 |
--------------------------------------------------------------------------------
/server/client/src/assets/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdhossin/mern-camera-website/c453ed35ae975a5ddffe714ecc8a369e199a7cef/server/client/src/assets/user.png
--------------------------------------------------------------------------------
/server/client/src/components/Banner/Banner.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { Carousel } from "react-bootstrap";
3 | import { bannerData } from "../../fakeData";
4 |
5 | const Banner = () => (
6 |
7 | {bannerData.map((banner, i) => (
8 |
9 |
10 |
11 | {banner.title}
12 | {banner.desc}
13 |
14 |
15 |
16 |
17 |
18 |
19 | ))}
20 |
21 | );
22 |
23 | export default Banner;
24 |
--------------------------------------------------------------------------------
/server/client/src/components/CartSummary/CartSummary.jsx:
--------------------------------------------------------------------------------
1 | import { Col, Container, Row } from "react-bootstrap";
2 |
3 | const CartSummary = ({ cartTotal }) => (
4 |
5 |
6 |
7 |
8 | SubTotal
9 |
10 |
11 | ${cartTotal}
12 |
13 |
14 |
15 |
16 | );
17 |
18 | export default CartSummary;
19 |
--------------------------------------------------------------------------------
/server/client/src/components/Chart/Chart.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | LineChart,
3 | Line,
4 | XAxis,
5 | CartesianGrid,
6 | Tooltip,
7 | ResponsiveContainer,
8 | } from "recharts";
9 |
10 | const Chart = ({ title, data, dataKey, grid }) => (
11 |
12 |
{title}
13 |
14 |
15 |
16 |
17 |
18 | {grid && }
19 |
20 |
21 |
22 | );
23 |
24 | export default Chart;
25 |
--------------------------------------------------------------------------------
/server/client/src/components/CompanyArea/CompanyArea.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { compnayData } from "../../fakeData";
3 |
4 | const CompanyArea = () => (
5 |
6 |
11 | {compnayData.map((company, i) => (
12 |
13 |
14 |
15 | ))}
16 |
17 |
18 | );
19 |
20 | export default CompanyArea;
21 |
--------------------------------------------------------------------------------
/server/client/src/components/DiscountArea/DiscountArea.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { motion } from "framer-motion";
3 |
4 | const DiscountArea = () => (
5 |
6 |
11 |
12 |
13 |
WE'RE AT 71% OF OUR GOAL!
14 |
Discount For All Orders Over $100
15 |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 |
24 | export default DiscountArea;
25 |
--------------------------------------------------------------------------------
/server/client/src/components/HomeBanner/HomeBanner.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { motion } from "framer-motion";
3 |
4 | const HomeBanner = () => (
5 |
6 |
7 |
12 |
13 |
14 |
Smart Protection
15 | Monitor and control your home anytime, anywhere.
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
Full Protection
29 | Featured Security Camera to stay protected
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 |
39 | export default HomeBanner;
40 |
--------------------------------------------------------------------------------
/server/client/src/components/Loader/Loader.jsx:
--------------------------------------------------------------------------------
1 | const Loader = (props) => {
2 | const { inline, backdrop } = props;
3 |
4 | return (
5 |
16 | );
17 | };
18 |
19 | Loader.defaultProps = {
20 | inline: false,
21 | backdrop: false,
22 | };
23 |
24 | export default Loader;
25 |
--------------------------------------------------------------------------------
/server/client/src/components/Loading/Loading.jsx:
--------------------------------------------------------------------------------
1 | import { Spinner } from "react-bootstrap";
2 |
3 | const Loading = () => (
4 | <>
5 |
6 | >
7 | );
8 |
9 | export default Loading;
10 |
--------------------------------------------------------------------------------
/server/client/src/components/MobileMenu/MobileMenu.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { AiOutlineClose } from "react-icons/ai";
3 |
4 | const MobileMenu = ({ setMobileOpen }) => (
5 | <>
6 |
7 |
setMobileOpen(false)}
10 | />
11 |
12 |
13 |
14 |
15 | -
16 | setMobileOpen(false)}
20 | >
21 | Home
22 |
23 |
24 | -
25 | setMobileOpen(false)}
29 | >
30 | Shop
31 |
32 |
33 |
34 | -
35 | setMobileOpen(false)}
39 | >
40 | Contact
41 |
42 |
43 |
44 |
45 |
46 |
47 | >
48 | );
49 |
50 | export default MobileMenu;
51 |
--------------------------------------------------------------------------------
/server/client/src/components/NewsLetter/NewsLetter.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 |
3 | const NewsLetter = () => (
4 |
5 |
10 |
11 |
NewsLetter
12 |
13 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eos, autem.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 |
24 | export default NewsLetter;
25 |
--------------------------------------------------------------------------------
/server/client/src/components/ProductRating/ProductRating.jsx:
--------------------------------------------------------------------------------
1 | import { AiOutlineStar, AiFillStar } from "react-icons/ai";
2 | const ProductRating = ({ ratingValue }) => {
3 | let rating = [];
4 |
5 | for (let i = 0; i < 5; i++) {
6 | rating.push();
7 | }
8 | if (ratingValue && ratingValue > 0) {
9 | for (let i = 0; i <= ratingValue - 1; i++) {
10 | rating[i] = (
11 |
18 | );
19 | }
20 | }
21 | return <>{rating}>;
22 | };
23 |
24 | export default ProductRating;
25 |
--------------------------------------------------------------------------------
/server/client/src/components/Products/Products.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import SectionTitle from "../SectionTitle/SectionTitle";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { getAllProduct } from "../../redux/actions/productActions";
5 | import Loader from "../Loader/Loader";
6 | import SingleProduct from "./SingleProduct/SingleProduct";
7 |
8 | const Products = () => {
9 | const dispatch = useDispatch();
10 | const productsData = useSelector((state) => state.allProducts);
11 | const { products, loading, error } = productsData;
12 |
13 | useEffect(() => {
14 | dispatch(getAllProduct());
15 | }, [dispatch]);
16 | return (
17 |
18 |
19 |
20 |
21 | {loading ? (
22 |
23 | ) : error ? (
24 |
25 | {error}
26 |
27 | ) : (
28 | <>
29 | {products?.map((product) => (
30 |
31 | ))}
32 | >
33 | )}
34 |
35 | {products?.length === 0 && (
36 |
37 | No product found.
38 |
39 | )}
40 |
41 | );
42 | };
43 |
44 | export default Products;
45 |
--------------------------------------------------------------------------------
/server/client/src/components/Products/SingleProduct/SingleProduct.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { useToasts } from "react-toast-notifications";
3 | import { FiShoppingCart } from "react-icons/fi";
4 | import { Link } from "react-router-dom";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { addItemsToCart } from "../../../redux/actions/cartActions";
7 | import ProductRating from "../../ProductRating/ProductRating";
8 |
9 | const SingleProduct = ({ product }) => {
10 | const { cartItems } = useSelector((state) => state.cart);
11 | const { addToast } = useToasts();
12 | const addOrNot = cartItems?.find((item) => item.product === product._id);
13 | const dispatch = useDispatch();
14 | const addToCartHandler = () => {
15 | dispatch(addItemsToCart(product?._id, 1, addToast));
16 | };
17 | return (
18 |
23 |
24 |
25 |
26 |
27 |
28 |
{product.name}
29 |
30 |
${product.price}
31 |
32 |
33 | {product?.Stock && product?.Stock > 0 ? (
34 |
42 | ) : (
43 |
46 | )}
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default SingleProduct;
54 |
--------------------------------------------------------------------------------
/server/client/src/components/ProtectedArea/ProtectedArea.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { motion } from "framer-motion";
3 |
4 | const ProtectedArea = () => (
5 |
6 |
11 |
12 |
13 |
14 |
Protect it now
15 |
Is your business protected?
16 |
Access Gibson to stay protected
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 |
25 | export default ProtectedArea;
26 |
--------------------------------------------------------------------------------
/server/client/src/components/SectionTitle/SectionTitle.jsx:
--------------------------------------------------------------------------------
1 | const SectionTitle = ({ title, desc }) => (
2 |
3 |
{desc}
4 |
{title}
5 |
6 | );
7 |
8 | export default SectionTitle;
9 |
--------------------------------------------------------------------------------
/server/client/src/components/index.js:
--------------------------------------------------------------------------------
1 | import Banner from "./Banner/Banner";
2 | import Header from "./Header/Header";
3 | import MobileMenu from "./MobileMenu/MobileMenu";
4 | import CartSummary from "./CartSummary/CartSummary";
5 | import CompanyArea from "./CompanyArea/CompanyArea";
6 | import DiscountArea from "./DiscountArea/DiscountArea";
7 | import Footer from "./Footer/Footer";
8 | import HomeBanner from "./HomeBanner/HomeBanner";
9 | import NewsLetter from "./NewsLetter/NewsLetter";
10 | import Products from "./Products/Products";
11 | import ProtectedArea from "./ProtectedArea/ProtectedArea";
12 | import SectionTitle from "./SectionTitle/SectionTitle";
13 | import ProductRating from "./ProductRating/ProductRating";
14 | import Loading from "./Loading/Loading";
15 | import Loader from "./Loader/Loader";
16 |
17 | export {
18 | Header,
19 | MobileMenu,
20 | Banner,
21 | CartSummary,
22 | CompanyArea,
23 | DiscountArea,
24 | Footer,
25 | HomeBanner,
26 | NewsLetter,
27 | Products,
28 | ProtectedArea,
29 | SectionTitle,
30 | ProductRating,
31 | Loading,
32 | Loader,
33 | };
34 |
--------------------------------------------------------------------------------
/server/client/src/fakeData.js:
--------------------------------------------------------------------------------
1 | import {
2 | bannerOne,
3 | bannerThree,
4 | bannerTwo,
5 | companyFive,
6 | companyFour,
7 | companyOne,
8 | companyThree,
9 | companyTwo,
10 | } from "./assets";
11 |
12 | export const bannerData = [
13 | {
14 | id: 1,
15 | imgPath: bannerOne,
16 | title: "Durable Night Vision Cameras",
17 | desc: "Global Camera Solutions",
18 | },
19 | {
20 | id: 2,
21 | imgPath: bannerTwo,
22 | title: "One Stop Security Solutions",
23 | desc: "For Your Home and Bussiness Security",
24 | },
25 | {
26 | id: 3,
27 | imgPath: bannerThree,
28 | title: "Get A CCTV Security Package",
29 | desc: "For Your Home and Bussiness Security",
30 | },
31 | ];
32 |
33 | export const productData = [
34 | {
35 | id: 1,
36 | img: "https://cdn.shopify.com/s/files/1/0040/0323/3892/products/p25_884c8d96-f418-47e0-b886-7140ab07f302_600x.jpg?v=1543492312",
37 | name: "Night Vision HD",
38 | price: 599,
39 | rating: 2,
40 | },
41 | {
42 | id: 2,
43 | img: "https://cdn.shopify.com/s/files/1/0040/0323/3892/products/p24_d15f04c0-2b14-4f18-8e15-d5c0dea23359_600x.jpg?v=1543492224",
44 | name: "Wireless Bullet HD",
45 | price: 895,
46 | rating: 3,
47 | },
48 | {
49 | id: 3,
50 | img: "https://cdn.shopify.com/s/files/1/0040/0323/3892/products/p23_808cb461-fbd0-4255-b6f2-130b64555a81_600x.jpg?v=1543492090",
51 | name: "Waterproof Bullet HD",
52 | price: 1199,
53 | rating: 4,
54 | },
55 | {
56 | id: 4,
57 | img: "https://cdn.shopify.com/s/files/1/0040/0323/3892/products/p6_59b37852-71c3-461e-ac66-1822d36cc4be_600x.jpg?v=1544420375",
58 | name: "Bullet HD Lens",
59 | price: 1299,
60 | rating: 1,
61 | },
62 | {
63 | id: 5,
64 | img: "https://cdn.shopify.com/s/files/1/0040/0323/3892/products/p21_dc3139bb-bc56-4313-91c9-2f55f9c3a2ac_600x.jpg?v=1543491728",
65 | name: "Wireless HD Camera",
66 | price: 755,
67 | rating: 2,
68 | },
69 | {
70 | id: 6,
71 | img: "https://cdn.shopify.com/s/files/1/0040/0323/3892/products/p18_975e9c57-5014-4ddc-9889-7bf3f4a22cc8_600x.jpg?v=1543491463",
72 | name: "Surveillance 3D",
73 | price: 855,
74 | rating: 5,
75 | },
76 | ];
77 |
78 | export const compnayData = [
79 | {
80 | id: 1,
81 | img: companyOne,
82 | },
83 | {
84 | id: 2,
85 | img: companyTwo,
86 | },
87 | {
88 | id: 3,
89 | img: companyThree,
90 | },
91 | {
92 | id: 4,
93 | img: companyFour,
94 | },
95 | {
96 | id: 5,
97 | img: companyFive,
98 | },
99 | ];
100 |
--------------------------------------------------------------------------------
/server/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import { BrowserRouter } from "react-router-dom";
4 | import { Provider } from "react-redux";
5 |
6 | import App from "./App";
7 | import store from "./redux/store";
8 |
9 | const root = ReactDOM.createRoot(document.getElementById("root"));
10 | root.render(
11 |
12 |
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/server/client/src/pages/ActivationEmail/ActivationEmail.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import { Link, useParams } from "react-router-dom";
4 | import { useToasts } from "react-toast-notifications";
5 |
6 | const ActivationEmail = () => {
7 | const { activation_token } = useParams();
8 | const [error, setError] = useState("");
9 | const [success, setSuccess] = useState("");
10 | const { addToast } = useToasts();
11 | useEffect(() => {
12 | if (activation_token) {
13 | const activationEmail = async () => {
14 | try {
15 | const res = await axios.post(
16 | "https://mern-camera-shop.herokuapp.com/api/active",
17 | {
18 | activation_token,
19 | }
20 | );
21 | setSuccess(res.data.message);
22 | setError("");
23 | } catch (error) {
24 | setSuccess("");
25 | error.response &&
26 | setError(
27 | error.response.data.message
28 | ? error.response.data.message
29 | : error.message
30 | );
31 | }
32 | };
33 | activationEmail();
34 | }
35 | }, [activation_token, error.response, error.message]);
36 |
37 | useEffect(() => {
38 | if (error) {
39 | addToast(error, { appearance: "error", autoDismiss: true });
40 | } else if (success) {
41 | addToast(success, {
42 | appearance: "success",
43 | autoDismiss: true,
44 | });
45 | }
46 | }, [success, error, addToast]);
47 | return (
48 |
49 |
50 |
{error && error}
51 |
{success && success}
52 |
53 |
54 |
55 | {" "}
56 |
57 |
58 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default ActivationEmail;
65 |
--------------------------------------------------------------------------------
/server/client/src/pages/Cart/Cart.jsx:
--------------------------------------------------------------------------------
1 | import { AiOutlineClose } from "react-icons/ai";
2 | import { BiShoppingBag } from "react-icons/bi";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { CartSummary } from "../../components";
5 | import {
6 | addItemsToCart,
7 | removeItemsFromCart,
8 | } from "../../redux/actions/cartActions";
9 | import CartList from "../CartList/CartList";
10 | import CheckOut from "../Checkout/Checkout";
11 |
12 | const Cart = ({ setCartOpen }) => {
13 | const dispatch = useDispatch();
14 | const { cartItems } = useSelector((state) => state.cart);
15 |
16 | const cartTotal = cartItems.reduce(
17 | (acc, item) => acc + item.quantity * item.price,
18 | 0
19 | );
20 |
21 | const qtyChangeHandler = (id, quantity) => {
22 | dispatch(addItemsToCart(id, quantity));
23 | };
24 |
25 | const deleteCartItems = (id) => {
26 | dispatch(removeItemsFromCart(id));
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 | Shopping Cart ({cartItems?.length})
34 |
35 |
setCartOpen(false)}
38 | />
39 |
40 | {cartItems.length > 0 ? (
41 |
42 |
47 |
48 | ) : (
49 |
50 |
51 |
Your shopping cart is empty
52 |
53 | )}
54 | {cartItems.length > 0 && (
55 |
56 |
57 |
58 |
59 | )}
60 |
61 | );
62 | };
63 |
64 | export default Cart;
65 |
--------------------------------------------------------------------------------
/server/client/src/pages/CartList/CartList.jsx:
--------------------------------------------------------------------------------
1 | import { AiOutlineClose } from "react-icons/ai";
2 |
3 | const CartList = ({ cartItems, qtyChangeHandler, deleteCartItems }) => (
4 |
5 | {cartItems?.map((item, index) => (
6 |
7 |

12 |
13 |
28 |
29 |
30 |
31 |
42 |
43 | ${item.price * item.quantity}
44 |
45 |
46 |
47 |
48 |
49 | ))}
50 |
51 | );
52 |
53 | export default CartList;
54 |
--------------------------------------------------------------------------------
/server/client/src/pages/Checkout/Checkout.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const CheckOut = ({ setCartOpen }) => (
4 |
5 |
6 |
7 |
10 |
11 |
12 |
19 |
20 |
21 |
22 | );
23 |
24 | export default CheckOut;
25 |
--------------------------------------------------------------------------------
/server/client/src/pages/CheckoutPayment/CheckoutPayment.jsx:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux";
2 | import StripeCheckout from "react-stripe-checkout";
3 | import { createOrder } from "../../redux/actions/orderActions";
4 |
5 | const CheckoutPayment = () => {
6 | const dispatch = useDispatch();
7 | const { cartItems } = useSelector((state) => state.cart);
8 | const { user } = useSelector((state) => state.userLogin?.userInfo);
9 |
10 | const subtotal = cartItems.reduce(
11 | (acc, item) => acc + item.quantity * item.price,
12 | 0
13 | );
14 |
15 | const shippingCharges = subtotal < 1000 ? 0 : 100;
16 | const tax = subtotal * 0.18;
17 | const totalPrice = Math.round(subtotal + tax + shippingCharges);
18 |
19 | const order = {
20 | orderItems: cartItems,
21 | shippingPrice: shippingCharges,
22 | totalPrice: totalPrice,
23 | };
24 |
25 | const tokenHandler = (token) => {
26 | order.shippingInfo = token.card;
27 | order.email = token.email;
28 | order.id = token.id;
29 | order.paymentInfo = {
30 | id: token.id,
31 | status: "succeeded",
32 | };
33 | if (token) {
34 | dispatch(createOrder(order));
35 | }
36 | };
37 |
38 | return (
39 |
40 |
41 |
Payment Info
42 |
43 |
Subtotal:
44 |
${subtotal.toFixed(2)}
45 |
46 |
47 |
Shipping Charges:
48 |
${shippingCharges.toFixed(2)}
49 |
50 |
51 |
Tax :
52 |
${tax.toFixed(2)}
53 |
54 |
55 |
56 |
Total Price :
57 |
${totalPrice.toFixed(2)}
58 |
59 |
60 |
71 |
74 |
75 |
76 |
77 | );
78 | };
79 |
80 | export default CheckoutPayment;
81 |
--------------------------------------------------------------------------------
/server/client/src/pages/Contact/Contact.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { IoCallOutline } from "react-icons/io5";
3 | import { AiOutlineMail } from "react-icons/ai";
4 | import { FiSend } from "react-icons/fi";
5 | import Footer from "../../components/Footer/Footer";
6 | const Contact = () => (
7 |
8 |
9 |
10 |
Contact Us
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
Phone
24 |
010-00001222
25 |
002-00003333
26 |
27 |
28 |
33 |
36 | Email
37 | icam@gamil.com
38 | support@gmail.com
39 |
40 |
41 |
46 |
47 |
48 |
49 | Address
50 | No: 58 A, East Madison Street,
51 | Baltimore, MD, USA 4508
52 |
53 |
54 |
55 |
56 | Contact Form
57 |
62 |
88 |
93 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | );
112 |
113 | export default Contact;
114 |
--------------------------------------------------------------------------------
/server/client/src/pages/ForgotPassword/ForgotPassword.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import { Link } from "react-router-dom";
4 | import { useToasts } from "react-toast-notifications";
5 | import { Spinner } from "react-bootstrap";
6 | import { isEmail } from "../../utils/validation";
7 |
8 | const ForgotPassword = () => {
9 | const [data, setData] = useState({
10 | email: "",
11 | error: "",
12 | success: "",
13 | });
14 |
15 | const { addToast } = useToasts();
16 | const { email, error, success } = data;
17 | const [loading, setLoading] = useState(false);
18 |
19 | const handleChangeInput = (e) => {
20 | const { name, value } = e.target;
21 | setData({ ...data, [name]: value, error: "", success: "" });
22 | };
23 |
24 | const forgotPassword = async () => {
25 | if (!isEmail(email))
26 | return setData({ ...data, error: "Invalid emails.", success: "" });
27 |
28 | try {
29 | setLoading(true);
30 | const res = await axios.post(
31 | "https://mern-camera-shop.herokuapp.com/api/user/forgot_password",
32 | { email }
33 | );
34 | setLoading(false);
35 | setData({ ...data, error: "", success: res.data.message });
36 | } catch (error) {
37 | setLoading(false);
38 | error.response.data.message &&
39 | setData({
40 | ...data,
41 | error:
42 | error.response && error.response.data.message
43 | ? error.response.data.message
44 | : error.message,
45 | success: "",
46 | });
47 | }
48 | };
49 |
50 | useEffect(() => {
51 | if (error) {
52 | addToast(error, { appearance: "error", autoDismiss: true });
53 | setData({
54 | email: "",
55 | error: "",
56 | success: "",
57 | });
58 | } else if (success) {
59 | addToast(success, {
60 | appearance: "success",
61 | autoDismiss: true,
62 | });
63 | setData({
64 | email: "",
65 | error: "",
66 | success: "",
67 | });
68 | }
69 | }, [error, success, addToast]);
70 | return (
71 |
72 |
73 |
74 |
Forgot your password?
75 |
76 | We will send you an email to reset your password.
77 |
78 |
79 |
119 |
120 |
121 |
122 | );
123 | };
124 |
125 | export default ForgotPassword;
126 |
--------------------------------------------------------------------------------
/server/client/src/pages/Home/Home.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Banner,
3 | CompanyArea,
4 | DiscountArea,
5 | Footer,
6 | HomeBanner,
7 | NewsLetter,
8 | Products,
9 | ProtectedArea,
10 | } from "../../components";
11 |
12 | const Home = () => (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 |
25 | export default Home;
26 |
--------------------------------------------------------------------------------
/server/client/src/pages/Login/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Spinner } from "react-bootstrap";
3 | import { Link, useLocation, useNavigate } from "react-router-dom";
4 | import { GoogleLogin } from "react-google-login";
5 | import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import { googleLogin, login } from "../../redux/actions/userActions";
8 | import { useToasts } from "react-toast-notifications";
9 | import { USER_LOGIN_RESET } from "../../redux/constants/userConstants";
10 | const Login = () => {
11 | const navigate = useNavigate();
12 | let location = useLocation();
13 | const { addToast } = useToasts();
14 | const [newUser, setNewUser] = useState({ email: "", password: "" });
15 | const dispatch = useDispatch();
16 | const redirect = location.state?.path || "/";
17 |
18 | const { email, password } = newUser;
19 |
20 | const [typePass, setTypePass] = useState(false);
21 |
22 | const userLogin = useSelector((state) => state?.userLogin);
23 |
24 | const { loading, error, userInfo } = userLogin;
25 |
26 | const handleChangeInput = (e) => {
27 | setNewUser({ ...newUser, [e.target.name]: e.target.value });
28 | };
29 |
30 | const handleSubmit = (e) => {
31 | e.preventDefault();
32 |
33 | dispatch(login(email, password));
34 | };
35 |
36 | const responseGoogle = async (response) => {
37 | try {
38 | dispatch(googleLogin(response.tokenId));
39 | } catch (error) {
40 | alert(error?.message);
41 | }
42 | };
43 |
44 | useEffect(() => {
45 | if (error) {
46 | dispatch({ type: USER_LOGIN_RESET });
47 | addToast(error, { appearance: "error", autoDismiss: true });
48 | } else if (userInfo) {
49 | if (userInfo.message !== undefined) {
50 | addToast(userInfo?.message, {
51 | appearance: "success",
52 | autoDismiss: true,
53 | });
54 | }
55 | navigate(redirect, { replace: true });
56 | }
57 | }, [userInfo, error, addToast, navigate, dispatch, redirect]);
58 |
59 | return (
60 |
61 |
62 |
63 |
Login
64 |
65 |
124 |
125 |
126 |
127 | );
128 | };
129 |
130 | export default Login;
131 |
--------------------------------------------------------------------------------
/server/client/src/pages/Manager/AdminDashboard/AdminDashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Outlet } from "react-router-dom";
3 | import Sidebar from "./Sidebar/Sidebar";
4 |
5 | const AdminDashboard = () => (
6 | <>
7 |
8 |
9 |
10 | {/* used nested route data show here */}
11 |
12 |
13 |
14 | >
15 | );
16 |
17 | export default AdminDashboard;
18 |
--------------------------------------------------------------------------------
/server/client/src/pages/Manager/AdminDashboard/AllProducts/AllProducts.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import { Link } from "react-router-dom";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import {
6 | getAllProduct,
7 | deleteProduct,
8 | } from "../../../../redux/actions/productActions";
9 | import { Loader } from "../../../../components";
10 |
11 | const AllProducts = () => {
12 | const [callback, setCallback] = useState(false);
13 | const dispatch = useDispatch();
14 | const productsData = useSelector((state) => state.allProducts);
15 | const { products, loading, error } = productsData;
16 | const token = useSelector((state) => state.userLogin?.userInfo?.access_token);
17 |
18 | useEffect(() => {
19 | dispatch(getAllProduct());
20 | }, [dispatch, callback]);
21 |
22 | const deleteHandler = async (id, public_id) => {
23 | try {
24 | if (window.confirm("are you sure?")) {
25 | const destroyImg = axios.post(
26 | "https://mern-camera-shop.herokuapp.com/api/destroy",
27 | { public_id },
28 | {
29 | headers: { Authorization: token },
30 | }
31 | );
32 | await destroyImg;
33 | dispatch(deleteProduct(token, id));
34 | setCallback(!callback);
35 | alert("Product Deleted Successfully");
36 | }
37 | } catch (error) {
38 | alert(
39 | error.response && error.response.data.message
40 | ? error.response.data.message
41 | : error.message
42 | );
43 | }
44 | };
45 | return (
46 |
47 | All Products
48 |
49 |
50 | {loading ? (
51 |
52 | ) : error ? (
53 |
54 | {error}
55 |
56 | ) : (
57 | <>
58 | {products?.map((product) => (
59 |
60 |

61 |
62 |
63 |
{product.name}
64 |
65 |
${product.price}
66 |
Stock : {product.Stock}
67 |
68 |
{product.description.slice(0, 30)}...
69 |
70 |
74 | Edit
75 |
76 |
84 |
85 |
86 |
87 | ))}
88 | >
89 | )}
90 |
91 | {products?.length === 0 && (
92 |
93 | No product found.
94 |
95 | )}
96 |
97 | );
98 | };
99 |
100 | export default AllProducts;
101 |
--------------------------------------------------------------------------------
/server/client/src/pages/Manager/AdminDashboard/EditUser/EditUser.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import { useSelector } from "react-redux";
4 | import { useNavigate, useParams } from "react-router-dom";
5 | import { BsFillArrowLeftCircleFill } from "react-icons/bs";
6 | import { useToasts } from "react-toast-notifications";
7 | import { Spinner } from "react-bootstrap";
8 | const EditUser = () => {
9 | const navigate = useNavigate();
10 | const { id } = useParams();
11 | const { addToast } = useToasts();
12 |
13 | const [editUser, setEditUser] = useState([]);
14 | const [checkAdmin, setCheckAdmin] = useState(false);
15 | const [error, setError] = useState(false);
16 | const [success, setSuccess] = useState(false);
17 | const [num, setNum] = useState(0);
18 | const [loading, setLoading] = useState(false);
19 |
20 | const { users } = useSelector((state) => state.userList);
21 | const { userInfo } = useSelector((state) => state.userLogin);
22 |
23 | useEffect(() => {
24 | if (users.length !== 0) {
25 | users.forEach((user) => {
26 | if (user._id === id) {
27 | setEditUser(user);
28 | setCheckAdmin(user.role === 1 ? true : false);
29 | }
30 | });
31 | } else {
32 | navigate("/dashboard/users");
33 | }
34 | }, [users, id, navigate]);
35 |
36 | const handleUpdate = async () => {
37 | try {
38 | if (num % 2 !== 0) {
39 | setLoading(true);
40 | const res = await axios.patch(
41 | `https://mern-camera-shop.herokuapp.com/api/admin/update_role/${editUser._id}`,
42 | {
43 | role: checkAdmin ? 1 : 0,
44 | },
45 | {
46 | headers: { Authorization: userInfo.access_token },
47 | }
48 | );
49 |
50 | setError("");
51 | setSuccess(res.data.message);
52 | setNum(0);
53 | setLoading(false);
54 | }
55 | } catch (err) {
56 | setLoading(false);
57 | setSuccess("");
58 | err.response &&
59 | setError(
60 | error.response && error.response.data.message
61 | ? error.response.data.message
62 | : error.message
63 | );
64 | }
65 | };
66 |
67 | const handleCheck = () => {
68 | setSuccess("");
69 | setError("");
70 | setCheckAdmin(!checkAdmin);
71 | setNum(num + 1);
72 | };
73 |
74 | useEffect(() => {
75 | if (error) {
76 | addToast(error, { appearance: "error", autoDismiss: true });
77 | } else if (success) {
78 | addToast(success, {
79 | appearance: "success",
80 | autoDismiss: true,
81 | });
82 | navigate("/dashboard/users");
83 | }
84 | }, [addToast, error, success, navigate]);
85 |
86 | return (
87 |
88 |
89 |
96 |
97 |
98 |
99 |
Edit User
100 |
101 |
102 |
103 |
109 |
110 |
111 |
112 |
113 |
119 |
120 |
121 |
122 |
128 |
129 |
130 |
131 |
138 |
139 |
140 | );
141 | };
142 |
143 | export default EditUser;
144 |
--------------------------------------------------------------------------------
/server/client/src/pages/Manager/AdminDashboard/HomeAdmin/HomeAdmin.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useMemo, useState } from "react";
2 | import axios from "axios";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { getAllProduct } from "../../../../redux/actions/productActions";
5 | import { getAllOrders } from "../../../../redux/actions/orderActions";
6 | import { userList } from "../../../../redux/actions/userActions";
7 | import Chart from "../../../../components/Chart/Chart";
8 | const HomeAdmin = () => {
9 | const dispatch = useDispatch();
10 | const [userStats, setUserStats] = useState([]);
11 |
12 | const productData = useSelector((state) => state.allProducts);
13 | const { products } = productData;
14 | const { orders } = useSelector((state) => state.allOrders);
15 |
16 | const { access_token } = useSelector((state) => state.userLogin?.userInfo);
17 |
18 | const { users } = useSelector((state) => state.userList);
19 |
20 | useEffect(() => {
21 | dispatch(getAllProduct());
22 | dispatch(getAllOrders());
23 | dispatch(userList());
24 | }, [dispatch]);
25 |
26 | let totalAmount = 0;
27 | orders &&
28 | orders.forEach((item) => {
29 | totalAmount += item.totalPrice;
30 | });
31 |
32 | const MONTHS = useMemo(
33 | () => [
34 | "Jan",
35 | "Feb",
36 | "Mar",
37 | "Apr",
38 | "May",
39 | "Jun",
40 | "Jul",
41 | "Agu",
42 | "Sep",
43 | "Oct",
44 | "Nov",
45 | "Dec",
46 | ],
47 | []
48 | );
49 |
50 | useEffect(() => {
51 | const getStats = async () => {
52 | try {
53 | const config = {
54 | headers: {
55 | "Content-Type": "application/json",
56 | Authorization: access_token,
57 | },
58 | };
59 | const res = await axios.get(
60 | "https://mern-camera-shop.herokuapp.com/api/admin/stats",
61 | config
62 | );
63 | res.data.map((item) =>
64 | setUserStats((prev) => [
65 | ...prev,
66 | { name: MONTHS[item._id - 1], "Active User": item.total },
67 | ])
68 | );
69 | } catch (error) {
70 | console.log(error?.messge);
71 | }
72 | };
73 | getStats();
74 | }, [MONTHS, access_token]);
75 |
76 | return (
77 |
78 |
79 |
80 |
81 |
Total Sales
82 | ${Math.round(totalAmount)}
83 |
84 |
85 |
86 |
All Products
87 | {products && products?.length}
88 |
89 |
90 |
Total Orders
91 | {orders && orders?.length}
92 |
93 |
94 |
Active Users
95 | {users && users?.length}
96 |
97 |
98 |
99 |
105 |
106 |
107 | );
108 | };
109 |
110 | export default HomeAdmin;
111 |
--------------------------------------------------------------------------------
/server/client/src/pages/Manager/AdminDashboard/UserList/UserList.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { useNavigate } from "react-router-dom";
4 | import Loading from "../../../../components/Loading/Loading";
5 | import { userList } from "../../../../redux/actions/userActions";
6 | import UserListItem from "./UserListItem/UserListItem";
7 |
8 | const UserList = () => {
9 | const dispatch = useDispatch();
10 | const navigate = useNavigate();
11 | const [callback, setCallback] = useState(false);
12 |
13 | const { loading, error, users } = useSelector((state) => state.userList);
14 | const { userInfo } = useSelector((state) => state.userLogin);
15 |
16 | useEffect(() => {
17 | if (userInfo && userInfo?.user.role === 1) {
18 | dispatch(userList());
19 | } else {
20 | navigate("/");
21 | }
22 | }, [dispatch, navigate, userInfo, callback]);
23 | return (
24 | <>
25 |
26 |
27 |
28 |
Users
29 |
30 | {loading ? (
31 |
32 | ) : error ? (
33 | {error}
34 | ) : (
35 |
40 | )}
41 |
42 |
43 | >
44 | );
45 | };
46 |
47 | export default UserList;
48 |
--------------------------------------------------------------------------------
/server/client/src/pages/Manager/UserDashboard/MyOrders/Myorders.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useToasts } from "react-toast-notifications";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { Link } from "react-router-dom";
5 | import { BiEdit } from "react-icons/bi";
6 | import { Table } from "react-bootstrap";
7 | import { clearErrors, myOrders } from "../../../../redux/actions/orderActions";
8 | import Loader from "../../../../components/Loader/Loader";
9 |
10 | const MyOrders = () => {
11 | const dispatch = useDispatch();
12 | const { addToast } = useToasts();
13 | const { loading, error, orders } = useSelector((state) => state.myOrders);
14 |
15 | useEffect(() => {
16 | if (error) {
17 | addToast(error, { appearance: "error", autoDismiss: true });
18 | dispatch(clearErrors());
19 | }
20 | dispatch(myOrders());
21 | }, [dispatch, error, addToast]);
22 |
23 | return (
24 |
25 | My Orders
26 |
27 |
28 |
29 |
30 | Order Id |
31 | Quantity |
32 | Price |
33 | Status |
34 | Actions |
35 |
36 |
37 |
38 | {loading ? (
39 |
40 |
41 |
42 | |
43 |
44 | ) : error ? (
45 | {error}
46 | ) : (
47 | <>
48 | {orders?.map(({ _id, orderStatus, totalPrice, orderItems }) => (
49 |
50 | #{_id} |
51 | {orderItems?.length} |
52 | {totalPrice.toFixed(2)} |
53 |
54 |
55 | |
56 |
57 | {" "}
58 |
59 | {" "}
60 |
67 |
68 | |
69 |
70 | ))}
71 | >
72 | )}
73 |
74 |
75 |
76 |
83 | {orders?.length === 0 && "Your order is empty."}
84 |
85 |
86 | );
87 | };
88 |
89 | export default MyOrders;
90 |
--------------------------------------------------------------------------------
/server/client/src/pages/Manager/UserDashboard/OrderDetails/OrderDetails.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useToasts } from "react-toast-notifications";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { Link, useParams } from "react-router-dom";
5 | import {
6 | clearErrors,
7 | getOrderDetails,
8 | } from "../../../../redux/actions/orderActions";
9 | import Loader from "../../../../components/Loader/Loader";
10 |
11 | const OrderDetails = () => {
12 | const { order, error, loading } = useSelector((state) => state.orderDetails);
13 | const dispatch = useDispatch();
14 | const { orderId } = useParams();
15 | const { addToast } = useToasts();
16 |
17 | useEffect(() => {
18 | if (error) {
19 | addToast(error, { appearance: "error", autoDismiss: true });
20 | dispatch(clearErrors());
21 | }
22 | dispatch(getOrderDetails(orderId));
23 | }, [dispatch, error, addToast, orderId]);
24 | return (
25 | <>
26 | {loading ? (
27 |
28 | ) : error ? (
29 | {error}
30 | ) : (
31 | <>
32 |
33 |
34 |
35 | Order Id: #{order && order._id}
36 |
37 |
38 |
Shipping Info
39 |
40 |
Name:
41 |
{order.user && order.user.name}
42 |
43 |
44 |
45 |
Address:
46 |
47 | {order.shippingInfo &&
48 | `${order.shippingInfo.address_line1}, ${order.shippingInfo.address_city}, ${order.shippingInfo.address_zip}, ${order.shippingInfo.address_country}`}
49 |
50 |
51 |
52 |
53 |
Payment
54 |
55 |
63 | {order.paymentInfo &&
64 | order.paymentInfo.status === "succeeded"
65 | ? "PAID"
66 | : "NOT PAID"}
67 |
68 |
69 |
70 |
71 |
Amount:
72 |
${order.totalPrice && order.totalPrice}
73 |
74 |
75 |
76 |
77 |
Order Status
78 |
79 |
86 | {order.orderStatus && order.orderStatus}
87 |
88 |
89 |
90 |
91 |
92 |
93 |
Order Items:
94 |
95 | {order.orderItems &&
96 | order.orderItems.map((item) => (
97 |
98 |

99 |
100 | {item.name}
101 | {" "}
102 |
103 | {item.quantity} X ${item.price} ={" "}
104 | ${item.price * item.quantity}
105 |
106 |
107 | ))}
108 |
109 |
110 |
111 | >
112 | )}
113 | >
114 | );
115 | };
116 |
117 | export default OrderDetails;
118 |
--------------------------------------------------------------------------------
/server/client/src/pages/Manager/UserDashboard/Sidebar/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { VscChromeClose } from "react-icons/vsc";
3 | import { Link, useNavigate } from "react-router-dom";
4 | import { AiOutlineMenu } from "react-icons/ai";
5 | import { BiLogOutCircle, BiShoppingBag } from "react-icons/bi";
6 | import { BsPerson } from "react-icons/bs";
7 | import { useDispatch, useSelector } from "react-redux";
8 | import { useToasts } from "react-toast-notifications";
9 | import { logout } from "../../../../redux/actions/userActions";
10 | import { USER_LOGOUT_RESET } from "../../../../redux/constants/userConstants";
11 |
12 | const Sidebar = () => {
13 | const [currentLink, setCurrentLink] = useState(1);
14 | const [navbarState, setNavbarState] = useState(false);
15 | const width = navbarState ? "60%" : "0%";
16 | const navigate = useNavigate();
17 |
18 | const dispatch = useDispatch();
19 | const { addToast } = useToasts();
20 |
21 | const user = useSelector((state) => state.userLogin);
22 | const { userInfo } = user;
23 |
24 | const logoutUser = useSelector((state) => state.userLogout);
25 | const { userLogout, error } = logoutUser;
26 |
27 | const handleLogout = () => {
28 | if (!userInfo?.access_token) return;
29 | dispatch(logout(userInfo?.access_token));
30 | };
31 |
32 | useEffect(() => {
33 | if (error) {
34 | dispatch({ type: USER_LOGOUT_RESET });
35 | // addToast(error, { appearance: "error", autoDismiss: true });
36 | } else if (userLogout) {
37 | dispatch({ type: USER_LOGOUT_RESET });
38 |
39 | // addToast(userLogout?.message, {
40 | // appearance: "success",
41 | // autoDismiss: true,
42 | // });
43 |
44 | navigate("/");
45 | }
46 | }, [userLogout, error, addToast, dispatch, navigate]);
47 |
48 | return (
49 | <>
50 |
51 |
52 |
53 | Dashboard
54 |
55 |
56 | {navbarState ? (
57 |
setNavbarState(false)} />
58 | ) : (
59 | {
61 | e.stopPropagation();
62 | setNavbarState(true);
63 | }}
64 | />
65 | )}
66 |
67 |
68 |
69 | - setCurrentLink(1)}
72 | >
73 |
74 |
75 | Profile
76 |
77 |
78 | - setCurrentLink(2)}
81 | >
82 |
83 |
84 | My Orders
85 |
86 |
87 |
88 | -
89 |
90 | Logout
91 |
92 |
93 |
94 |
95 |
96 |
101 |
102 |
103 | - setCurrentLink(1)}
106 | >
107 | setNavbarState(false)}>
108 |
109 | Profile
110 |
111 |
112 | - setCurrentLink(2)}
115 | >
116 | setNavbarState(false)}
119 | >
120 |
121 | My Orders
122 |
123 |
124 |
125 | - setNavbarState(false), handleLogout)}
128 | >
129 |
130 | Logout
131 |
132 |
133 |
134 |
135 | >
136 | );
137 | };
138 |
139 | export default Sidebar;
140 |
--------------------------------------------------------------------------------
/server/client/src/pages/Manager/UserDashboard/UserDashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Outlet } from "react-router-dom";
3 | import Sidebar from "./Sidebar/Sidebar";
4 |
5 | const AdminDashboard = () => (
6 | <>
7 |
8 |
9 |
10 | {/* used nested route data show here */}
11 |
12 |
13 |
14 | >
15 | );
16 |
17 | export default AdminDashboard;
18 |
--------------------------------------------------------------------------------
/server/client/src/pages/NotFound/NotFound.jsx:
--------------------------------------------------------------------------------
1 | const NotFound = () => (
2 |
3 |
404, Page Not Found!
4 |
5 | );
6 |
7 | export default NotFound;
8 |
--------------------------------------------------------------------------------
/server/client/src/pages/PrivateRoute/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import { Navigate, useLocation } from "react-router-dom";
3 | import Loading from "../../components/Loading/Loading";
4 |
5 | const PrivateRoute = ({ children }) => {
6 | const user = useSelector((state) => state?.userLogin);
7 | const { userInfo, loading } = user;
8 | let location = useLocation();
9 | if (loading) {
10 | return ;
11 | }
12 |
13 | if (!userInfo?.access_token) {
14 | return ;
15 | }
16 | return children;
17 | };
18 |
19 | export default PrivateRoute;
20 |
--------------------------------------------------------------------------------
/server/client/src/pages/ProductDetail/ProductDetail.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { FiShoppingCart } from "react-icons/fi";
4 | import { useParams } from "react-router-dom";
5 | import { useToasts } from "react-toast-notifications";
6 | import { getProductById } from "../../redux/actions/productActions";
7 | import { addItemsToCart } from "../../redux/actions/cartActions";
8 | import { Footer, Loader, ProductRating } from "../../components";
9 |
10 | const ProductDetail = () => {
11 | const { productId } = useParams();
12 | const dispatch = useDispatch();
13 | const { product, loading } = useSelector((state) => state.productById);
14 |
15 | const { cartItems } = useSelector((state) => state.cart);
16 |
17 | const addOrNot = cartItems?.find((item) => item.product === product._id);
18 |
19 | const { addToast } = useToasts();
20 |
21 | useEffect(() => {
22 | dispatch(getProductById(productId));
23 | }, [productId, dispatch]);
24 |
25 | const [quantity, setQuantity] = useState(1);
26 |
27 | const addToCartHandler = () => {
28 | dispatch(addItemsToCart(productId, quantity, addToast));
29 | };
30 |
31 | return (
32 | <>
33 |
34 | {loading ? (
35 |
36 | ) : (
37 |
38 |
39 |

40 |
41 |
42 |
43 |
{product.name}
44 |
45 |
46 |
47 | ${product.price}.00
48 |
49 |
50 |
51 |
(
52 | {product.ratings})
53 |
54 |
55 |
56 |
Status: {product.Stock < 0 ? "Out Of Stock" : "In Stock"}
57 |
58 |
59 |
60 |
71 |
72 | {product?.Stock && product?.Stock > 0 ? (
73 |
81 | ) : (
82 |
85 | )}
86 |
87 |
88 |
89 |
90 |
{product.description}
91 |
92 |
93 |
94 | )}
95 |
96 |
97 | >
98 | );
99 | };
100 |
101 | export default ProductDetail;
102 |
--------------------------------------------------------------------------------
/server/client/src/pages/Shop/Shop.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import { SectionTitle, Footer, Loader } from "../../components";
4 | import { getAllProduct } from "../../redux/actions/productActions";
5 | import SingleItem from "./SingleItem/SingleItem";
6 |
7 | const Shop = () => {
8 | const dispatch = useDispatch();
9 | const productsData = useSelector((state) => state.allProducts);
10 |
11 | const { products, loading, error } = productsData;
12 |
13 | useEffect(() => {
14 | dispatch(getAllProduct());
15 | }, [dispatch]);
16 | return (
17 | <>
18 |
19 |
20 |
21 |
22 | {loading ? (
23 |
24 | ) : error ? (
25 |
28 | {error}
29 |
30 | ) : (
31 | <>
32 | {products?.map((product) => (
33 |
34 | ))}
35 | >
36 | )}
37 |
38 | {products?.length === 0 && (
39 |
40 | No product found.
41 |
42 | )}
43 |
44 |
45 | >
46 | );
47 | };
48 |
49 | export default Shop;
50 |
--------------------------------------------------------------------------------
/server/client/src/pages/Shop/SingleItem/SingleItem.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { FiShoppingCart } from "react-icons/fi";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { Link } from "react-router-dom";
5 | import { useToasts } from "react-toast-notifications";
6 | import { ProductRating } from "../../../components";
7 | import { addItemsToCart } from "../../../redux/actions/cartActions";
8 |
9 | const SingleItem = ({ product }) => {
10 | const { addToast } = useToasts();
11 | const dispatch = useDispatch();
12 |
13 | const { cartItems } = useSelector((state) => state.cart);
14 | const addOrNot = cartItems?.find((item) => item.product === product._id);
15 |
16 | const addToCartHandler = () => {
17 | dispatch(addItemsToCart(product?._id, 1, addToast));
18 | };
19 |
20 | return (
21 |
26 |
27 |
28 |
29 |
30 |
31 |
{product.name}
32 |
33 |
${product.price}
34 |
35 |
36 | {product?.Stock && product?.Stock > 0 ? (
37 |
45 | ) : (
46 |
49 | )}
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default SingleItem;
57 |
--------------------------------------------------------------------------------
/server/client/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import Home from "./Home/Home";
2 | import Register from "./Register/Register";
3 | import Login from "./Login/Login";
4 | import Cart from "./Cart/Cart";
5 | import CartList from "./CartList/CartList";
6 | import ForgotPassword from "./ForgotPassword/ForgotPassword";
7 | import ActivationEmail from "./ActivationEmail/ActivationEmail";
8 | import ResetPassword from "./ResetPassword/ResetPassword";
9 | import NotFound from "./NotFound/NotFound";
10 | import Loading from "../components/Loading/Loading";
11 | import PrivateRoute from "./PrivateRoute/PrivateRoute";
12 | import Shop from "./Shop/Shop";
13 | import ProductDetail from "./ProductDetail/ProductDetail";
14 | import Contact from "./Contact/Contact";
15 | // user dashboard
16 | import UserDashboard from "./Manager/UserDashboard/UserDashboard";
17 | import Profile from "./Manager/UserDashboard/Profile/Profile";
18 | import Myorders from "./Manager/UserDashboard/MyOrders/Myorders";
19 |
20 | // admin dashboard
21 | import AdminDashboard from "./Manager/AdminDashboard/AdminDashboard";
22 | import HomeAdmin from "./Manager/AdminDashboard/HomeAdmin/HomeAdmin";
23 | import UserList from "./Manager/AdminDashboard/UserList/UserList";
24 | import UserListItem from "./Manager/AdminDashboard/UserList/UserListItem/UserListItem";
25 | import EditUser from "./Manager/AdminDashboard/EditUser/EditUser";
26 | import AddProduct from "./Manager/AdminDashboard/AddProduct/AddProduct";
27 | import AllProducts from "./Manager/AdminDashboard/AllProducts/AllProducts";
28 |
29 | import CheckOut from "./Checkout/Checkout";
30 | import CheckoutPayment from "./CheckoutPayment/CheckoutPayment";
31 | import OrderDetails from "./Manager/UserDashboard/OrderDetails/OrderDetails";
32 | import OrderList from "./Manager/AdminDashboard/OrderList/OrderList";
33 | import ProcessOrder from "./Manager/AdminDashboard/ProcessOrder/ProcessOrder";
34 | export {
35 | Home,
36 | Register,
37 | Login,
38 | Cart,
39 | CartList,
40 | ForgotPassword,
41 | ActivationEmail,
42 | ResetPassword,
43 | NotFound,
44 | Loading,
45 | PrivateRoute,
46 | UserDashboard,
47 | Profile,
48 | Myorders,
49 | AdminDashboard,
50 | HomeAdmin,
51 | UserList,
52 | UserListItem,
53 | EditUser,
54 | AddProduct,
55 | AllProducts,
56 | Shop,
57 | ProductDetail,
58 | CheckOut,
59 | CheckoutPayment,
60 | OrderDetails,
61 | OrderList,
62 | ProcessOrder,
63 | Contact,
64 | };
65 |
--------------------------------------------------------------------------------
/server/client/src/redux/actions/cartActions.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { ADD_TO_CART, REMOVE_CART_ITEM } from "../constants/cartConstants";
3 |
4 | // Add to Cart
5 | export const addItemsToCart =
6 | (id, quantity, addToast) => async (dispatch, getState) => {
7 | try {
8 | const { data } = await axios.get(
9 | `https://mern-camera-shop.herokuapp.com/api/products/${id}`
10 | );
11 |
12 | dispatch({
13 | type: ADD_TO_CART,
14 | payload: {
15 | product: data._id,
16 | name: data.name,
17 | price: data.price,
18 | image: data.images.url,
19 | stock: data.Stock,
20 | quantity,
21 | },
22 | });
23 |
24 | if (addToast) {
25 | addToast("Added To Cart", { appearance: "success", autoDismiss: true });
26 | }
27 |
28 | localStorage.setItem(
29 | "cartItems",
30 | JSON.stringify(getState().cart.cartItems)
31 | );
32 | } catch (error) {
33 | addToast(
34 | error.response && error.response.data.message
35 | ? error.response.data.message
36 | : error.message,
37 | { appearance: "error", autoDismiss: true }
38 | );
39 | }
40 | };
41 |
42 | // REMOVE FROM CART
43 | export const removeItemsFromCart = (id) => async (dispatch, getState) => {
44 | dispatch({
45 | type: REMOVE_CART_ITEM,
46 | payload: id,
47 | });
48 |
49 | localStorage.setItem("cartItems", JSON.stringify(getState().cart.cartItems));
50 | };
51 |
--------------------------------------------------------------------------------
/server/client/src/redux/constants/cartConstants.js:
--------------------------------------------------------------------------------
1 | export const ADD_TO_CART = "ADD_TO_CART";
2 | export const REMOVE_CART_ITEM = "REMOVE_CART_ITEM";
3 |
--------------------------------------------------------------------------------
/server/client/src/redux/constants/orderConstants.js:
--------------------------------------------------------------------------------
1 | export const CREATE_ORDER_REQUEST = "CREATE_ORDER_REQUEST";
2 | export const CREATE_ORDER_SUCCESS = "CREATE_ORDER_SUCCESS";
3 | export const CREATE_ORDER_FAIL = "CREATE_ORDER_FAIL";
4 |
5 | export const MY_ORDERS_REQUEST = "MY_ORDERS_REQUEST";
6 | export const MY_ORDERS_SUCCESS = "MY_ORDERS_SUCCESS";
7 | export const MY_ORDERS_FAIL = "MY_ORDERS_FAIL";
8 |
9 | export const ALL_ORDERS_REQUEST = "ALL_ORDERS_REQUEST";
10 | export const ALL_ORDERS_SUCCESS = "ALL_ORDERS_SUCCESS";
11 | export const ALL_ORDERS_FAIL = "ALL_ORDERS_FAIL";
12 |
13 | export const UPDATE_ORDER_REQUEST = "UPDATE_ORDER_REQUEST";
14 | export const UPDATE_ORDER_SUCCESS = "UPDATE_ORDER_SUCCESS";
15 | export const UPDATE_ORDER_RESET = "UPDATE_ORDER_RESET";
16 | export const UPDATE_ORDER_FAIL = "UPDATE_ORDER_FAIL";
17 |
18 | export const DELETE_ORDER_REQUEST = "DELETE_ORDER_REQUEST";
19 | export const DELETE_ORDER_SUCCESS = "DELETE_ORDER_SUCCESS";
20 | export const DELETE_ORDER_RESET = "DELETE_ORDER_RESET";
21 | export const DELETE_ORDER_FAIL = "DELETE_ORDER_FAIL";
22 |
23 | export const ORDER_DETAILS_REQUEST = "ORDER_DETAILS_REQUEST";
24 | export const ORDER_DETAILS_SUCCESS = "ORDER_DETAILS_SUCCESS";
25 | export const ORDER_DETAILS_FAIL = "ORDER_DETAILS_FAIL";
26 |
27 | export const CLEAR_ERRORS = "CLEAR_ERRORS";
28 |
--------------------------------------------------------------------------------
/server/client/src/redux/constants/productConstants.js:
--------------------------------------------------------------------------------
1 | export const CREATE_PRODUCT_REQUEST = "CREATE_PRODUCT_REQUEST";
2 | export const CREATE_PRODUCT_SUCCESS = "CREATE_PRODUCT_SUCCESS";
3 | export const CREATE_PRODUCT_FAIL = "CREATE_PRODUCT_FAIL";
4 | export const CREATE_PRODUCT_RESET = "CREATE_PRODUCT_RESET";
5 |
6 | export const PRODUCT_DELETE_REQUEST = "PRODUCT_DELETE_REQUEST";
7 | export const PRODUCT_DELETE_SUCCESS = "PRODUCT_DELETE_SUCCESS";
8 | export const PRODUCT_DELETE_FAIL = "PRODUCT_DELETE_FAIL";
9 | export const PRODUCT_DELETE_RESET = "PRODUCT_DELETE_RESET";
10 |
11 | export const PRODUCT_BY_ID_REQUEST = "PRODUCT_BY_ID_REQUEST";
12 | export const PRODUCT_BY_ID_SUCCESS = "PRODUCT_BY_ID_SUCCESS";
13 | export const PRODUCT_BY_ID_FAIL = "PRODUCT_BY_ID_FAIL";
14 | export const PRODUCT_BY_ID_RESET = "PRODUCT_BY_ID_RESET";
15 |
16 | export const PRODUCT_UPDATE_REQUEST = "PRODUCT_UPDATE_REQUEST";
17 | export const PRODUCT_UPDATE_SUCCESS = "PRODUCT_UPDATE_SUCCESS";
18 | export const PRODUCT_UPDATE_FAIL = "PRODUCT_UPDATE_FAIL";
19 | export const PRODUCT_UPDATE_RESET = "PRODUCT_UPDATE_RESET";
20 |
21 | export const ALL_PRODUCTS_LOADING = "ALL_PRODUCTS_LOADING";
22 | export const ALL_PRODUCTS_SUCCESS = "ALL_PRODUCTS_SUCCESS";
23 | export const ALL_PRODUCTS_FAIL = "ALL_PRODUCTS_FAIL";
24 |
--------------------------------------------------------------------------------
/server/client/src/redux/constants/userConstants.js:
--------------------------------------------------------------------------------
1 | export const USER_LOGIN_REQUEST = "USER_LOGIN_REQUEST";
2 | export const USER_LOGIN_SUCCESS = "USER_LOGIN_SUCCESS";
3 | export const USER_LOGIN_FAIL = "USER_LOGIN_FAIL";
4 | export const USER_LOGIN_RESET = "USER_LOGIN_RESET";
5 |
6 | export const USER_LOGOUT = "USER_LOGOUT";
7 |
8 | export const USER_LOGOUT_REQUEST = "USER_LOGOUT_REQUEST";
9 | export const USER_LOGOUT_SUCCESS = "USER_LOGOUT_SUCCESS";
10 | export const USER_LOGOUT_FAIL = "USER_LOGOUT_FAIL";
11 | export const USER_LOGOUT_RESET = "USER_LOGOUT_RESET";
12 |
13 | export const USER_REGISTER_REQUEST = "USER_REGISTER_REQUEST";
14 | export const USER_REGISTER_SUCCESS = "USER_REGISTER_SUCCESS";
15 | export const USER_REGISTER_FAIL = "USER_REGISTER_FAIL";
16 | export const USER_REGISTER_RESET = "USER_REGISTER_RESET";
17 |
18 | export const USER_UPDATE_REQUEST = "USER_LOGIN_REQUEST";
19 | export const USER_UPDATE_SUCCESS = "USER_LOGIN_SUCCESS";
20 | export const USER_UPDATE_FAIL = "USER_LOGIN_FAIL";
21 |
22 | export const USER_DETAILS_REQUEST = "USER_DETAILS_REQUEST";
23 | export const USER_DETAILS_SUCCESS = "USER_DETAILS_SUCCESS";
24 | export const USER_DETAILS_FAIL = "USER_DETAILS_FAIL";
25 |
26 | export const USER_PROFILE_UPDATE_REQUEST = "USER_PROFILE_UPDATE_REQUEST";
27 | export const USER_PROFILE_UPDATE_SUCCESS = "USER_PROFILE_UPDATE_SUCCESS";
28 | export const USER_PROFILE_UPDATE_FAIL = "USER_PROFILE_UPDATE_FAIL";
29 | export const USER_PROFILE_RESET = "USER_PROFILE_RESET";
30 |
31 | export const USER_LIST_REQUEST = "USER_LIST_REQUEST";
32 | export const USER_LIST_SUCCESS = "USER_LIST_SUCCESS";
33 | export const USER_LIST_FAIL = "USER_LIST_FAIL";
34 |
35 | export const USER_DELETE_REQUEST = "USER_DELETE_REQUEST";
36 | export const USER_DELETE_SUCCESS = "USER_DELETE_SUCCESS";
37 | export const USER_DELETE_FAIL = "USER_DELETE_FAIL";
38 | export const USER_DELETE_RESET = "USER_DELETE_RESET";
39 |
40 | export const USER_EDIT_REQUEST = "USER_EDIT_REQUEST";
41 | export const USER_EDIT_SUCCESS = "USER_EDIT_SUCCESS";
42 | export const USER_EDIT_FAIL = "USER_EDIT_FAIL";
43 | export const USER_EDIT_RESET = "USER_EDIT_RESET";
44 |
--------------------------------------------------------------------------------
/server/client/src/redux/reducers/cartReducer.js:
--------------------------------------------------------------------------------
1 | import { ADD_TO_CART, REMOVE_CART_ITEM } from "../constants/cartConstants";
2 |
3 | export const cartReducer = (state = { cartItems: [] }, action) => {
4 | switch (action.type) {
5 | case ADD_TO_CART:
6 | const item = action.payload;
7 |
8 | // check if the product item already have
9 | const isItemExist = state.cartItems.find(
10 | (i) => i.product === item.product
11 | );
12 |
13 | // if exist run if block
14 | if (isItemExist) {
15 | return {
16 | ...state,
17 | cartItems: state.cartItems.map((i) =>
18 | i.product === isItemExist.product ? item : i
19 | ),
20 | };
21 |
22 | // if dont exist run else block
23 | } else {
24 | return {
25 | ...state,
26 | cartItems: [...state.cartItems, item],
27 | };
28 | }
29 |
30 | case REMOVE_CART_ITEM:
31 | return {
32 | ...state,
33 | cartItems: state.cartItems.filter((i) => i.product !== action.payload),
34 | };
35 |
36 | default:
37 | return state;
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/server/client/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import {
3 | userListReducer,
4 | userLoginReducer,
5 | userLogoutReducer,
6 | userRegisterReducer,
7 | } from "./userReducer";
8 | import {
9 | createProductReducer,
10 | productByIdReducer,
11 | productReducer,
12 | } from "./productReducer";
13 | import { cartReducer } from "./cartReducer";
14 | import {
15 | allOrdersReducer,
16 | myOrdersReducer,
17 | newOrderReducer,
18 | orderDetailsReducer,
19 | orderReducer,
20 | } from "./orderReducer";
21 |
22 | export default combineReducers({
23 | userLogin: userLoginReducer,
24 | userRegister: userRegisterReducer,
25 | userLogout: userLogoutReducer,
26 | userList: userListReducer,
27 | createProduct: createProductReducer,
28 | allProducts: productReducer,
29 | cart: cartReducer,
30 | productById: productByIdReducer,
31 | newOrder: newOrderReducer,
32 | myOrders: myOrdersReducer,
33 | orderDetails: orderDetailsReducer,
34 | allOrders: allOrdersReducer,
35 | // update order reducer
36 | order: orderReducer,
37 | });
38 |
--------------------------------------------------------------------------------
/server/client/src/redux/reducers/orderReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | CREATE_ORDER_REQUEST,
3 | CREATE_ORDER_SUCCESS,
4 | CREATE_ORDER_FAIL,
5 | MY_ORDERS_REQUEST,
6 | MY_ORDERS_SUCCESS,
7 | MY_ORDERS_FAIL,
8 | ALL_ORDERS_REQUEST,
9 | ALL_ORDERS_SUCCESS,
10 | ALL_ORDERS_FAIL,
11 | UPDATE_ORDER_REQUEST,
12 | UPDATE_ORDER_SUCCESS,
13 | UPDATE_ORDER_FAIL,
14 | UPDATE_ORDER_RESET,
15 | DELETE_ORDER_REQUEST,
16 | DELETE_ORDER_SUCCESS,
17 | DELETE_ORDER_FAIL,
18 | DELETE_ORDER_RESET,
19 | ORDER_DETAILS_REQUEST,
20 | ORDER_DETAILS_SUCCESS,
21 | ORDER_DETAILS_FAIL,
22 | CLEAR_ERRORS,
23 | } from "../constants/orderConstants";
24 |
25 | // create new order reducer
26 | export const newOrderReducer = (state = {}, action) => {
27 | switch (action.type) {
28 | case CREATE_ORDER_REQUEST:
29 | return {
30 | ...state,
31 | loading: true,
32 | };
33 |
34 | case CREATE_ORDER_SUCCESS:
35 | return {
36 | loading: false,
37 | order: action.payload,
38 | };
39 |
40 | case CREATE_ORDER_FAIL:
41 | return {
42 | loading: false,
43 | error: action.payload,
44 | };
45 | case CLEAR_ERRORS:
46 | return {
47 | ...state,
48 | error: null,
49 | };
50 |
51 | default:
52 | return state;
53 | }
54 | };
55 |
56 | // get user all orders
57 | export const myOrdersReducer = (state = { orders: [] }, action) => {
58 | switch (action.type) {
59 | case MY_ORDERS_REQUEST:
60 | return {
61 | ...state,
62 | loading: true,
63 | };
64 |
65 | case MY_ORDERS_SUCCESS:
66 | return {
67 | loading: false,
68 | orders: action.payload,
69 | };
70 |
71 | case MY_ORDERS_FAIL:
72 | return {
73 | loading: false,
74 | error: action.payload,
75 | };
76 | case CLEAR_ERRORS:
77 | return {
78 | ...state,
79 | error: null,
80 | };
81 |
82 | default:
83 | return state;
84 | }
85 | };
86 |
87 | // get single order detials reducer
88 | export const orderDetailsReducer = (state = { order: {} }, action) => {
89 | switch (action.type) {
90 | case ORDER_DETAILS_REQUEST:
91 | return {
92 | ...state,
93 | loading: true,
94 | };
95 |
96 | case ORDER_DETAILS_SUCCESS:
97 | return {
98 | loading: false,
99 | order: action.payload,
100 | };
101 |
102 | case ORDER_DETAILS_FAIL:
103 | return {
104 | loading: false,
105 | error: action.payload,
106 | };
107 | case CLEAR_ERRORS:
108 | return {
109 | ...state,
110 | error: null,
111 | };
112 |
113 | default:
114 | return state;
115 | }
116 | };
117 |
118 | // get all order reducers only admin can get
119 | export const allOrdersReducer = (state = { orders: [] }, action) => {
120 | switch (action.type) {
121 | case ALL_ORDERS_REQUEST:
122 | return {
123 | ...state,
124 | loading: true,
125 | };
126 |
127 | case ALL_ORDERS_SUCCESS:
128 | return {
129 | loading: false,
130 | orders: action.payload,
131 | };
132 |
133 | case ALL_ORDERS_FAIL:
134 | return {
135 | loading: false,
136 | error: action.payload,
137 | };
138 | case CLEAR_ERRORS:
139 | return {
140 | ...state,
141 | error: null,
142 | };
143 |
144 | default:
145 | return state;
146 | }
147 | };
148 |
149 | // update order reducer only can admin do
150 | export const orderReducer = (state = {}, action) => {
151 | switch (action.type) {
152 | case UPDATE_ORDER_REQUEST:
153 | case DELETE_ORDER_REQUEST:
154 | return {
155 | ...state,
156 | loading: true,
157 | };
158 |
159 | case UPDATE_ORDER_SUCCESS:
160 | return {
161 | ...state,
162 | loading: false,
163 | isUpdated: action.payload,
164 | };
165 |
166 | case DELETE_ORDER_SUCCESS:
167 | return {
168 | ...state,
169 | loading: false,
170 | isDeleted: action.payload,
171 | };
172 |
173 | case UPDATE_ORDER_FAIL:
174 | case DELETE_ORDER_FAIL:
175 | return {
176 | ...state,
177 | loading: false,
178 | error: action.payload,
179 | };
180 | case UPDATE_ORDER_RESET:
181 | return {
182 | ...state,
183 | isUpdated: false,
184 | };
185 |
186 | case DELETE_ORDER_RESET:
187 | return {
188 | ...state,
189 | isDeleted: false,
190 | };
191 | case CLEAR_ERRORS:
192 | return {
193 | ...state,
194 | error: null,
195 | };
196 |
197 | default:
198 | return state;
199 | }
200 | };
201 |
--------------------------------------------------------------------------------
/server/client/src/redux/reducers/productReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | ALL_PRODUCTS_FAIL,
3 | ALL_PRODUCTS_LOADING,
4 | ALL_PRODUCTS_SUCCESS,
5 | CREATE_PRODUCT_FAIL,
6 | CREATE_PRODUCT_REQUEST,
7 | CREATE_PRODUCT_RESET,
8 | CREATE_PRODUCT_SUCCESS,
9 | PRODUCT_BY_ID_FAIL,
10 | PRODUCT_BY_ID_REQUEST,
11 | PRODUCT_BY_ID_RESET,
12 | PRODUCT_BY_ID_SUCCESS,
13 | PRODUCT_DELETE_FAIL,
14 | PRODUCT_DELETE_REQUEST,
15 | PRODUCT_DELETE_RESET,
16 | PRODUCT_DELETE_SUCCESS,
17 | } from "../constants/productConstants";
18 |
19 | // create and update product action
20 | export const createProductReducer = (state = {}, action) => {
21 | switch (action.type) {
22 | case CREATE_PRODUCT_REQUEST:
23 | return {
24 | ...state,
25 | loading: true,
26 | };
27 |
28 | case CREATE_PRODUCT_SUCCESS:
29 | return {
30 | loading: false,
31 | products: action.payload,
32 | };
33 |
34 | case CREATE_PRODUCT_FAIL:
35 | return {
36 | loading: false,
37 | error: action.payload,
38 | };
39 | case CREATE_PRODUCT_RESET:
40 | return {};
41 |
42 | default:
43 | return state;
44 | }
45 | };
46 | const initState = {
47 | products: [],
48 | };
49 |
50 | // get all product
51 | export const productReducer = (state = initState, action) => {
52 | switch (action.type) {
53 | case ALL_PRODUCTS_LOADING:
54 | return {
55 | loading: true,
56 | ...state,
57 | };
58 | case ALL_PRODUCTS_SUCCESS:
59 | return {
60 | loading: false,
61 | products: action.payload,
62 | };
63 | case ALL_PRODUCTS_FAIL:
64 | return {
65 | loading: false,
66 | error: action.payload,
67 | };
68 | default:
69 | return state;
70 | }
71 | };
72 |
73 | export const productDeleteReducer = (state = {}, action) => {
74 | switch (action.type) {
75 | case PRODUCT_DELETE_REQUEST:
76 | return {
77 | ...state,
78 | loading: true,
79 | };
80 | case PRODUCT_DELETE_SUCCESS:
81 | return {
82 | loading: false,
83 | success: action.payload,
84 | };
85 | case PRODUCT_DELETE_FAIL:
86 | return {
87 | loading: false,
88 | error: action.payload,
89 | };
90 | case PRODUCT_DELETE_RESET:
91 | return {};
92 |
93 | default:
94 | return state;
95 | }
96 | };
97 |
98 | export const productByIdReducer = (state = { product: {} }, action) => {
99 | switch (action.type) {
100 | case PRODUCT_BY_ID_REQUEST:
101 | return {
102 | ...state,
103 | loading: true,
104 | };
105 | case PRODUCT_BY_ID_SUCCESS:
106 | return {
107 | loading: false,
108 | product: action.payload,
109 | };
110 | case PRODUCT_BY_ID_FAIL:
111 | return {
112 | loading: false,
113 | error: action.payload,
114 | };
115 |
116 | case PRODUCT_BY_ID_RESET:
117 | return {};
118 |
119 | default:
120 | return state;
121 | }
122 | };
123 |
--------------------------------------------------------------------------------
/server/client/src/redux/reducers/userReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | USER_LIST_FAIL,
3 | USER_LIST_REQUEST,
4 | USER_LIST_SUCCESS,
5 | USER_LOGIN_FAIL,
6 | USER_LOGIN_REQUEST,
7 | USER_LOGIN_RESET,
8 | USER_LOGIN_SUCCESS,
9 | USER_LOGOUT_FAIL,
10 | USER_LOGOUT_REQUEST,
11 | USER_LOGOUT_RESET,
12 | USER_LOGOUT_SUCCESS,
13 | USER_REGISTER_FAIL,
14 | USER_REGISTER_REQUEST,
15 | USER_REGISTER_RESET,
16 | USER_REGISTER_SUCCESS,
17 | } from "../constants/userConstants";
18 |
19 | // user login action
20 | export const userLoginReducer = (state = {}, action) => {
21 | switch (action.type) {
22 | case USER_LOGIN_REQUEST:
23 | return {
24 | ...state,
25 | loading: true,
26 | };
27 |
28 | case USER_LOGIN_SUCCESS:
29 | return {
30 | loading: false,
31 | userInfo: action.payload,
32 | };
33 |
34 | case USER_LOGIN_FAIL:
35 | return {
36 | loading: false,
37 | error: action.payload,
38 | };
39 |
40 | case USER_LOGIN_RESET:
41 | return {};
42 |
43 | default:
44 | return state;
45 | }
46 | };
47 |
48 | // user register action
49 | export const userRegisterReducer = (state = {}, action) => {
50 | switch (action.type) {
51 | case USER_REGISTER_REQUEST:
52 | return {
53 | ...state,
54 | loading: true,
55 | };
56 |
57 | case USER_REGISTER_SUCCESS:
58 | return {
59 | loading: false,
60 | userInfo: action.payload,
61 | };
62 |
63 | case USER_REGISTER_FAIL:
64 | return {
65 | loading: false,
66 | error: action.payload,
67 | };
68 |
69 | case USER_REGISTER_RESET:
70 | return {};
71 |
72 | default:
73 | return state;
74 | }
75 | };
76 | // user register action
77 | export const userLogoutReducer = (state = {}, action) => {
78 | switch (action.type) {
79 | case USER_LOGOUT_REQUEST:
80 | return {
81 | ...state,
82 | loading: true,
83 | };
84 |
85 | case USER_LOGOUT_SUCCESS:
86 | return {
87 | loading: false,
88 | userLogout: action.payload,
89 | };
90 |
91 | case USER_LOGOUT_FAIL:
92 | return {
93 | loading: false,
94 | error: action.payload,
95 | };
96 | case USER_LOGOUT_RESET:
97 | return {};
98 |
99 | default:
100 | return state;
101 | }
102 | };
103 |
104 | // only for admin
105 | export const userListReducer = (state = { users: [] }, action) => {
106 | switch (action.type) {
107 | case USER_LIST_REQUEST:
108 | return {
109 | ...state,
110 | loading: true,
111 | };
112 |
113 | case USER_LIST_SUCCESS:
114 | return {
115 | loading: false,
116 | users: action.payload,
117 | };
118 |
119 | case USER_LIST_FAIL:
120 | return {
121 | loading: false,
122 | error: action.payload,
123 | };
124 |
125 | default:
126 | return state;
127 | }
128 | };
129 |
--------------------------------------------------------------------------------
/server/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from "redux";
2 | import thunk from "redux-thunk";
3 | import rootReducer from "./reducers/index";
4 | import { composeWithDevTools } from "@redux-devtools/extension";
5 |
6 | const middleware = [thunk];
7 | let initialState = {
8 | cart: {
9 | cartItems: localStorage.getItem("cartItems")
10 | ? JSON.parse(localStorage.getItem("cartItems"))
11 | : [],
12 | },
13 | };
14 | const store = createStore(
15 | rootReducer,
16 | initialState,
17 | composeWithDevTools(applyMiddleware(...middleware))
18 | );
19 |
20 | export default store;
21 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_activation-email.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .activation {
4 | height: 100vh;
5 | @include flex(center, center);
6 |
7 | &__error {
8 | margin-bottom: 2rem;
9 | color: red;
10 | font-weight: 600;
11 | }
12 |
13 | &__success {
14 | margin-bottom: 2rem;
15 | color: var(--main-color);
16 | font-weight: 600;
17 | }
18 |
19 | button {
20 | width: 200px;
21 | margin: 0 auto;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_add-product.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .addproduct {
4 | margin-bottom: 4rem;
5 | padding-top: 2.5rem;
6 | @media screen and(min-width: 1080px) {
7 | padding-top: 7rem;
8 | }
9 | &__title {
10 | color: #333;
11 | font-size: 28px;
12 | font-weight: 500;
13 | margin-bottom: 2rem;
14 | }
15 |
16 | &__form {
17 | gap: 0;
18 |
19 | @include breakpoint-up("large") {
20 | grid-template-columns: repeat(2, 1fr);
21 | gap: 3rem;
22 | }
23 |
24 | label {
25 | margin-bottom: 0.8rem;
26 | font-weight: 500;
27 | font-size: 15px;
28 | }
29 |
30 | textarea {
31 | font-size: 15px;
32 | width: 100%;
33 | padding: 10px;
34 | resize: vertical;
35 | color: #2c2c2c;
36 | border: 1px solid #e4e4e4 !important;
37 | background: transparent;
38 | box-shadow: none;
39 | }
40 |
41 | &__right {
42 | h4 {
43 | margin-top: 0.8rem;
44 | font-size: 15px;
45 | font-weight: 500;
46 | color: #333;
47 | }
48 | &__box {
49 | display: grid;
50 | margin-top: 20px;
51 | place-items: center;
52 | border: 1px dashed #799cd9;
53 | position: relative;
54 | height: 300px;
55 | width: 300px;
56 | background: #fbfbff;
57 | border-radius: 20px;
58 | &__upload {
59 | #file_up {
60 | width: 100%;
61 | height: 100%;
62 | outline: none;
63 |
64 | &::before {
65 | content: url("../../assets/images/upload-1.png");
66 | position: absolute;
67 | width: 100%;
68 | height: 100%;
69 | top: 0;
70 | left: 0;
71 | background: white;
72 | text-align: center;
73 | cursor: pointer;
74 | @include flex(center, center);
75 | }
76 | }
77 |
78 | #file_img {
79 | width: 300px;
80 | height: 300px;
81 | position: absolute;
82 | top: 0;
83 | left: 0;
84 |
85 | img {
86 | margin-top: 0.5rem;
87 | width: 280px;
88 | height: 280px;
89 | object-fit: contain;
90 | }
91 |
92 | span {
93 | position: absolute;
94 | top: -13px;
95 | right: -13px;
96 | background: white;
97 | border: 1px solid #ddd;
98 | border-radius: 50%;
99 | padding: 6px 10px;
100 | cursor: pointer;
101 | font-weight: 900;
102 | color: crimson;
103 | }
104 | }
105 | }
106 | }
107 | }
108 |
109 | &__button {
110 | width: 160px;
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_all-products.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .allProducts {
4 | padding-bottom: 5rem;
5 | padding-top: 2.5rem;
6 | @media screen and (min-width: 1080px) {
7 | padding-top: 7rem;
8 | }
9 | h2 {
10 | font-weight: 500;
11 | color: #333;
12 | font-size: 28px;
13 | margin-bottom: 3rem;
14 | }
15 |
16 | &__container {
17 | @include breakpoint-up("large") {
18 | grid-template-columns: repeat(2, 1fr);
19 | }
20 | @include breakpoint-up("xlarge") {
21 | grid-template-columns: repeat(3, 1fr);
22 | }
23 | &__item {
24 | transition: all 0.5s ease;
25 |
26 | &__content {
27 | background-color: rgb(244, 244, 244);
28 | padding: 32px;
29 | }
30 |
31 | &-price {
32 | @include flex(center, space-between);
33 | }
34 | img {
35 | max-width: 100%;
36 | }
37 | border: 1px solid #e4e4e4;
38 |
39 | div {
40 | h3 {
41 | font-size: 1.5rem;
42 | font-weight: 700;
43 | color: #2c2c2c;
44 |
45 | transition: var(--transition);
46 | &:hover {
47 | cursor: pointer;
48 | color: var(--hover-color);
49 | }
50 | }
51 |
52 | h5 {
53 | font-size: 18px;
54 | font-weight: 700;
55 | color: var(--main-color);
56 | margin: 1rem 0;
57 | font-family: "Roboto", sans-serif;
58 | }
59 | p {
60 | font-size: 18px;
61 | font-weight: 500;
62 | color: #333;
63 | font-family: "Roboto", sans-serif;
64 | }
65 | }
66 |
67 | &:hover {
68 | border: 1px solid var(--hover-color);
69 | }
70 | }
71 | }
72 |
73 | &__buttons {
74 | margin-top: 0;
75 | padding-top: 0;
76 | @include flex(center, space-between);
77 |
78 | .button {
79 | flex: 1;
80 | height: 45px;
81 | line-height: 45px;
82 | font-size: 15px;
83 | font-weight: 600;
84 | color: #fff;
85 | @include flex(center, center);
86 | }
87 |
88 | .delete {
89 | background-color: var(--hover-color);
90 |
91 | &:hover {
92 | background-color: var(--main-color);
93 | }
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_banner.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .banner {
4 | &__desc {
5 | p {
6 | font-size: 15px;
7 | font-weight: 600;
8 |
9 | color: #fff;
10 | margin-bottom: 0.3rem;
11 | @include breakpoint-up("xlarge") {
12 | margin: 1rem 0;
13 | text-align: start;
14 | font-size: 18px;
15 | }
16 | }
17 |
18 | h1 {
19 | font-size: 1.5rem;
20 | color: #fff;
21 | font-weight: 600;
22 | line-height: 1.2;
23 | @include breakpoint-up("small") {
24 | font-size: 1.8rem;
25 | }
26 | @include breakpoint-up("large") {
27 | font-size: 2rem;
28 | }
29 | @include breakpoint-up("xlarge") {
30 | font-size: 3rem;
31 | }
32 | }
33 |
34 | button {
35 | background-color: var(--main-color);
36 | height: 50px;
37 | line-height: 50px;
38 | width: 180px;
39 | font-size: 16px;
40 | color: #fff;
41 | display: block;
42 | margin: 1rem auto;
43 | transition: all 0.5s ease;
44 | font-weight: 600;
45 | &:hover {
46 | background-color: #fff;
47 | color: #333;
48 | }
49 |
50 | @include breakpoint-up("xlarge") {
51 | margin: 1rem 0;
52 | }
53 | }
54 | }
55 | }
56 |
57 | // override bootstrap style
58 | .carousel-inner {
59 | .carousel-caption {
60 | // position: absolute;
61 | right: 0;
62 | bottom: 1.25rem;
63 | left: 0;
64 | top: 17%;
65 |
66 | @include breakpoint-up("small") {
67 | top: 20%;
68 | }
69 |
70 | @include breakpoint-up("medium") {
71 | top: 25%;
72 | }
73 |
74 | @include breakpoint-up("large") {
75 | top: 37%;
76 | }
77 |
78 | @include breakpoint-up("xlarge") {
79 | right: 40%;
80 | width: 35%;
81 | text-align: start;
82 | top: 33%;
83 | }
84 | }
85 | }
86 |
87 | .carousel-item {
88 | img {
89 | @media screen and (max-width: 575px) {
90 | height: 37vh !important;
91 | }
92 | }
93 | }
94 |
95 | .carousel-indicators {
96 | display: none;
97 | }
98 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_cart.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | // mini cart
4 | .mini-cart {
5 | visibility: hidden;
6 | position: fixed;
7 | top: 0;
8 | right: -100%;
9 | background-color: #fff;
10 | height: 100%;
11 | width: 470px;
12 | z-index: 1000;
13 | transition: all 0.5s ease;
14 |
15 | @include breakpoints-down("small") {
16 | width: 88%;
17 | top: 0;
18 | }
19 | }
20 |
21 | // show hidden cart popup
22 | .mini-cart-open {
23 | .mini-cart {
24 | visibility: visible;
25 | right: 0;
26 | transition: all 0.5s ease;
27 | }
28 |
29 | .dark-overflow {
30 | @include dark-overflow();
31 | @include breakpoints-down("small") {
32 | width: 100%;
33 | top: 0;
34 | }
35 | }
36 | }
37 |
38 | .cart {
39 | height: 100%;
40 | display: flex;
41 | flex-direction: column;
42 |
43 | &__empty {
44 | height: 100%;
45 | @include flex(center, center);
46 | flex-direction: column;
47 | svg {
48 | width: 50px;
49 | height: 50px;
50 | }
51 |
52 | p {
53 | font-weight: 400;
54 | margin-top: 12px;
55 | }
56 | }
57 |
58 | &__checkout {
59 | background-color: #fff;
60 |
61 | .cart-summary {
62 | padding: 10px;
63 | background-color: grey;
64 |
65 | p {
66 | margin-bottom: 0;
67 | }
68 | }
69 | }
70 |
71 | &__header {
72 | border-bottom: var(--border-default);
73 | padding: 22px 20px;
74 |
75 | @include flex(center, space-between);
76 | background-color: #ebeeee;
77 |
78 | &-title {
79 | font-size: 18px;
80 | margin-bottom: 0;
81 | }
82 | &-icon {
83 | font-size: 1.1rem;
84 |
85 | color: #2c2c2c;
86 |
87 | &:hover {
88 | color: var(--font-custom-color);
89 | }
90 |
91 | @include breakpoint-up("large") {
92 | font-size: 1.3rem;
93 | }
94 | }
95 |
96 | &__body {
97 | flex: 1;
98 | overflow-y: auto;
99 | overflow-x: hidden;
100 | max-height: 100%;
101 | background-color: #fff;
102 |
103 | &__list {
104 | padding: 30px 20px 50px;
105 | color: #2c2c2c;
106 | &__item {
107 | display: flex;
108 | align-items: flex-start;
109 | justify-content: space-between;
110 | padding: 20px 0;
111 | &:not(:first-child) {
112 | border-top: 1px solid #ebeeee;
113 | }
114 |
115 | &-img {
116 | display: block;
117 | width: 80px;
118 | height: auto;
119 |
120 | margin: 0 50px 0 0;
121 | padding: 0.5rem;
122 |
123 | @include breakpoints-down("small") {
124 | margin: 0 20px 0 0;
125 | }
126 | @include breakpoints-down("xm") {
127 | margin: 0 10px 0 0;
128 | width: 60px;
129 | }
130 | }
131 |
132 | &__details {
133 | flex: 1;
134 |
135 | &__name {
136 | display: flex;
137 | justify-content: space-between;
138 | align-items: flex-start;
139 |
140 | &-title {
141 | color: #2c2c2c;
142 | font-size: 16px;
143 | font-weight: 400;
144 | }
145 |
146 | &-delete {
147 | color: #2c2c2c;
148 | font-size: 16px;
149 | }
150 | }
151 |
152 | &__price {
153 | &__wrapper {
154 | display: flex;
155 | justify-content: space-between;
156 | align-items: flex-end;
157 |
158 | margin-top: 16px;
159 |
160 | select {
161 | padding: 0.1rem 1rem;
162 | }
163 |
164 | &-price {
165 | font-weight: 400;
166 | font-size: 16px;
167 | color: #2c2c2c;
168 | font-family: "Roboto", sans-serif;
169 | }
170 | }
171 | }
172 | }
173 | }
174 | }
175 | }
176 | }
177 | }
178 |
179 | /* start cart common styles */
180 | .summary-item {
181 | .summary-label {
182 | color: #fff;
183 | font-weight: 500;
184 | text-transform: capitalize;
185 | font-size: 18px;
186 | }
187 |
188 | .summary-value {
189 | color: #fff;
190 | font-size: 20px;
191 | font-weight: 500;
192 | font-family: "Roboto", sans-serif;
193 | }
194 | }
195 | /* end cart common styles */
196 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_chart.scss:
--------------------------------------------------------------------------------
1 | .chart {
2 | box-shadow: 0 8px 25px rgba(0, 0, 0, 0.07);
3 | padding: 20px;
4 |
5 | &__title {
6 | margin-bottom: 22px;
7 | color: #333;
8 | font-weight: 700;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_checkout-payment.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .checkoutPayment {
4 | display: grid;
5 | place-items: center;
6 | height: 100vh;
7 |
8 | &__content {
9 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.07);
10 | padding: 3rem 2rem;
11 | width: 350px;
12 |
13 | @include breakpoints-down("small") {
14 | width: 300px;
15 | }
16 |
17 | h2 {
18 | font-size: 26px;
19 | font-weight: 600;
20 | color: #333;
21 | margin-bottom: 2rem;
22 | text-align: center;
23 | }
24 | > div {
25 | display: flex;
26 | justify-content: space-between;
27 | font-family: "Roboto", sans-serif;
28 |
29 | p,
30 | span {
31 | font-size: 17px;
32 | font-weight: 500;
33 | color: #2c2c2c;
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_checkout.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 | .easy-checkout {
3 | .checkout-actions {
4 | padding: 0 1.5rem 1rem 1.5rem;
5 | text-align: center;
6 | justify-content: space-between;
7 | display: flex;
8 | gap: 1rem;
9 |
10 | @include breakpoints-down("small") {
11 | flex-direction: column;
12 | gap: 0;
13 | }
14 |
15 | .shopping {
16 | flex: 1;
17 | }
18 |
19 | .checkout {
20 | flex: 1;
21 | }
22 | button {
23 | font-size: 14px;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_company-area.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .company {
4 | padding-top: 3rem;
5 | padding-bottom: 5rem;
6 |
7 | &__container {
8 | @include breakpoint-up("medium") {
9 | grid-template-columns: repeat(2, 1fr);
10 | }
11 | @include breakpoint-up("large") {
12 | grid-template-columns: repeat(3, 1fr);
13 | }
14 | @include breakpoint-up("xlarge") {
15 | grid-template-columns: repeat(4, 1fr);
16 | }
17 | @include breakpoint-up("xxlarge") {
18 | grid-template-columns: repeat(5, 1fr);
19 | }
20 | > div {
21 | border: 1px solid #e4e4e4;
22 | border-radius: 5px;
23 | @include flex(center, center);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_contact.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .contacts {
4 | padding-top: 5.5rem;
5 | width: 100%;
6 | padding-bottom: 6rem;
7 | &__header {
8 | height: 130px;
9 | background-color: rgb(243, 244, 246);
10 | @include flex(center, center);
11 | h2 {
12 | font-weight: 500;
13 | color: #333;
14 | font-size: 2rem;
15 | }
16 | }
17 |
18 | &__content {
19 | padding-top: 5rem;
20 | font-family: "Roboto", sans-serif;
21 |
22 | @include breakpoint-up("medium") {
23 | grid-template-columns: repeat(2, 1fr);
24 | }
25 | @include breakpoint-up("xlarge") {
26 | grid-template-columns: repeat(3, 1fr);
27 | }
28 | &__box {
29 | border: 1px solid #e4e4e4;
30 | padding: 2rem;
31 | transition: all 0.5s ease;
32 |
33 | &:hover {
34 | border: 1px solid var(--hover-color);
35 | }
36 |
37 | &__img {
38 | width: 50px;
39 | height: 50px;
40 | color: #fff;
41 | background-color: var(--main-color);
42 | @include flex(center, center);
43 | transition: all 0.5s ease;
44 |
45 | svg {
46 | font-size: 1.4rem;
47 | }
48 |
49 | &:hover {
50 | background-color: var(--hover-color);
51 | }
52 | }
53 |
54 | h4 {
55 | font-weight: 600;
56 | font-size: 22px;
57 | color: #333;
58 | margin-top: 1.5rem;
59 | margin-bottom: 1rem;
60 | }
61 |
62 | p {
63 | font-size: 16px;
64 | font-weight: 500;
65 | color: #2c2c2c;
66 | }
67 | }
68 | }
69 |
70 | &__form {
71 | padding-top: 4rem;
72 |
73 | h2 {
74 | font-size: 26px;
75 | font-weight: 600;
76 | color: #333;
77 | padding-bottom: 2rem;
78 | }
79 |
80 | &__content {
81 | &__inputs {
82 | @include breakpoint-up("large") {
83 | grid-template-columns: repeat(2, 1fr);
84 | }
85 |
86 | @include breakpoint-up("xlarge") {
87 | grid-template-columns: repeat(3, 1fr);
88 | }
89 | }
90 | &__message {
91 | padding-top: 1.5rem;
92 |
93 | textarea {
94 | font-size: 15px;
95 |
96 | width: 100%;
97 |
98 | padding: 10px;
99 | border-radius: 3px;
100 | color: #2c2c2c;
101 | border: 1px solid #e4e4e4 !important;
102 | background: transparent;
103 | box-shadow: none;
104 | }
105 | }
106 | }
107 |
108 | &__button {
109 | padding-top: 1rem;
110 | .button {
111 | width: 200px;
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_discount-area.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .discount-section {
4 | padding-bottom: 5rem;
5 | }
6 | .discount {
7 | height: 100vh;
8 | background: url("../../assets/images/discount-area.webp");
9 | background-repeat: no-repeat;
10 | background-size: cover;
11 | background-position: center center;
12 | position: relative;
13 | @include flex(center, flex-start);
14 |
15 | &__overlay {
16 | position: absolute;
17 | content: "";
18 | background: rgba(19, 59, 102, 0.301);
19 | width: 100%;
20 | height: 100%;
21 | left: 0;
22 | top: 0;
23 | }
24 |
25 | &__content {
26 | z-index: 10;
27 | text-align: center;
28 |
29 | p {
30 | font-size: 1.4rem;
31 | color: #fff;
32 | font-weight: 600;
33 | }
34 |
35 | h2 {
36 | color: var(--main-color);
37 | font-size: 4rem;
38 | font-weight: 700;
39 | line-height: 1.3;
40 |
41 | @include breakpoints-down("medium") {
42 | font-size: 3rem;
43 | }
44 | }
45 |
46 | h5 {
47 | font-size: 1.3rem;
48 | font-weight: 600;
49 | color: #fff;
50 | margin: 1rem 0;
51 | margin-bottom: 1.5rem;
52 | line-height: 1.3;
53 | }
54 | .button {
55 | margin: 0 auto;
56 | width: 200px;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_footer.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 | .footer {
3 | background-color: var(--color-grey);
4 | color: #2c2c2c;
5 |
6 | padding-bottom: 5rem;
7 |
8 | &__container {
9 | row-gap: 2rem;
10 |
11 | @include breakpoint-up("medium") {
12 | grid-template-columns: repeat(2, 1fr);
13 | }
14 | @include breakpoint-up("large") {
15 | grid-template-columns: repeat(3, 1fr);
16 | }
17 | @include breakpoint-up("xlarge") {
18 | grid-template-columns: repeat(5, 1fr);
19 | }
20 |
21 | .logo {
22 | h3 {
23 | margin-top: 1rem;
24 | }
25 | p {
26 | font-size: 0.9rem;
27 | font-weight: 500;
28 | }
29 | }
30 | }
31 |
32 | &__title {
33 | font-size: 22px;
34 | margin-bottom: 0.75rem;
35 | color: var(--main-color);
36 | font-weight: 600;
37 | margin-bottom: 2rem;
38 | position: relative;
39 |
40 | &::after {
41 | content: "";
42 | position: absolute;
43 | bottom: -40%;
44 | left: 8%;
45 | transform: translateX(-50%);
46 | width: 15%;
47 | transition: width 0.5s ease;
48 | height: 2px;
49 | background-color: var(--main-color);
50 | }
51 | }
52 |
53 | &__logo {
54 | display: inline-block;
55 | }
56 |
57 | &__description,
58 | &__links {
59 | font-size: 0.9rem;
60 | font-weight: 500;
61 | }
62 |
63 | &__links {
64 | display: grid;
65 | row-gap: 0.5rem;
66 |
67 | li {
68 | &:hover {
69 | color: var(--hover-color);
70 | }
71 | }
72 | }
73 |
74 | &__link {
75 | color: #333;
76 | }
77 |
78 | &__social {
79 | display: flex;
80 | gap: 1.5rem;
81 | align-items: center;
82 |
83 | li {
84 | svg {
85 | font-size: 22px;
86 | transition: all 0.5s ease;
87 |
88 | &:hover {
89 | color: var(--hover-color);
90 | cursor: pointer;
91 | }
92 | }
93 | }
94 | }
95 |
96 | &__copyright {
97 | margin-top: 5rem;
98 | text-align: center;
99 | font-size: 0.9rem;
100 | color: #333;
101 | }
102 | .icam {
103 | font-size: 0.9rem;
104 | font-weight: 500;
105 | color: #333;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_header.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .header {
4 | position: fixed;
5 | top: 0;
6 | left: 0;
7 | width: 100%;
8 | background-color: var(--color-white);
9 | z-index: 300;
10 | }
11 |
12 | .nav {
13 | height: var(--header-height);
14 |
15 | @include flex(center, space-between);
16 |
17 | @include breakpoints-down("medium") {
18 | margin: 0.5rem 1rem;
19 | }
20 |
21 | &__logo {
22 | @include flex(center, center);
23 | }
24 |
25 | &__menu {
26 | @include breakpoints-down("medium") {
27 | position: fixed;
28 | top: 0;
29 | right: -100%;
30 | width: 100%;
31 | height: 100vh;
32 | padding: 6rem 2rem 3.5rem;
33 | transition: var(--transition);
34 | background-color: var(--color-grey);
35 | background-size: cover;
36 | background-repeat: repeat;
37 | z-index: 999;
38 | }
39 |
40 | &__list {
41 | li {
42 | a {
43 | &:hover {
44 | color: var(--hover-color);
45 | }
46 | }
47 | }
48 |
49 | @include breakpoints-down("medium") {
50 | display: flex;
51 | flex-direction: column;
52 | align-items: center;
53 | row-gap: 2rem;
54 | }
55 | }
56 | }
57 |
58 | &__icons {
59 | @include breakpoints-down("xs") {
60 | padding: 1rem 1.5rem;
61 | }
62 |
63 | .nav-item {
64 | margin-right: 20px !important;
65 | }
66 |
67 | li {
68 | &:not(:last-child) {
69 | margin-right: 20px;
70 | }
71 | a {
72 | font-size: 1.2rem;
73 | }
74 | }
75 |
76 | //
77 | }
78 |
79 | &__list {
80 | @include flex(center, space-between);
81 | }
82 | &__item {
83 | @include breakpoint-up("xlarge") {
84 | &:not(:last-child) {
85 | margin-right: 20px;
86 | }
87 | }
88 | }
89 |
90 | &__link {
91 | padding: 0.4rem;
92 | display: flex;
93 | font-size: 16px;
94 | font-weight: 500;
95 | letter-spacing: 0.5px;
96 | text-align: left;
97 | text-transform: capitalize;
98 | color: #333;
99 | }
100 |
101 | &__close {
102 | font-size: 2rem;
103 | position: absolute;
104 | top: 0.9rem;
105 | right: 1.25rem;
106 | cursor: pointer;
107 | svg {
108 | width: 35px;
109 | height: 35px;
110 | color: #2c2c2c;
111 | margin: 0.5rem 1rem;
112 |
113 | @include breakpoint-up("xlarge") {
114 | display: none;
115 | }
116 | }
117 | }
118 |
119 | &__btns {
120 | display: flex;
121 | align-items: center;
122 | column-gap: 1rem;
123 | @include breakpoint-up("xlarge") {
124 | display: none;
125 | }
126 |
127 | &__toggle {
128 | width: 38px;
129 | height: 38px;
130 | border-radius: 50%;
131 | position: relative;
132 | @include flex(center, center);
133 | background-color: var(--color-white);
134 |
135 | svg {
136 | width: 75%;
137 | height: 75%;
138 | }
139 | @include breakpoint-up("xlarge") {
140 | display: none;
141 | }
142 | }
143 | }
144 |
145 | &__mobileMenu {
146 | @include breakpoint-up("xlarge") {
147 | display: none;
148 | }
149 | }
150 |
151 | //
152 | &__wrapper {
153 | position: relative;
154 |
155 | span {
156 | font-size: 15px;
157 | font-weight: 600;
158 | position: absolute;
159 | top: -7px;
160 | right: -8px;
161 | background-color: #2c2c2c;
162 | width: 23px;
163 | height: 23px;
164 | border-radius: 50%;
165 | @include flex(center, center);
166 | color: #fff;
167 | }
168 | }
169 |
170 | &__dropdown {
171 | &__item {
172 | &:focus {
173 | background-color: transparent !important;
174 | }
175 | }
176 | &-icon {
177 | color: #2c2c2c;
178 | font-size: 1.2rem;
179 | }
180 | }
181 | }
182 |
183 | .scroll-header {
184 | box-shadow: 0 2px 4px hsla(0, 4%, 15%, 0.1);
185 | }
186 | /* Show menu */
187 | .show-menu {
188 | right: 0;
189 | }
190 |
191 | // override default style
192 |
193 | .dropdown-menu {
194 | border: none;
195 | box-shadow: 0 0 10px 0 #e6e5ea;
196 | padding: 10px;
197 | color: #2c2c2c;
198 | }
199 | .dropdown-item {
200 | padding: 0.5rem 1rem;
201 | }
202 | .nav-link {
203 | color: #2c2c2c;
204 | font-weight: 500;
205 | font-size: 13px;
206 | }
207 | .dropdown-toggle::after {
208 | color: #2c2c2c;
209 | // display: none;
210 | }
211 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_home-admin.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .admin {
4 | font-family: "Roboto", sans-serif;
5 | padding: 4rem 0;
6 | @media screen and (min-width: 1080px) {
7 | padding: 7rem 0;
8 | }
9 |
10 | &__container {
11 | &__summary {
12 | @include breakpoint-up("medium") {
13 | grid-template-columns: repeat(2, 1fr);
14 | }
15 | @include breakpoint-up("xlarge") {
16 | grid-template-columns: repeat(3, 1fr);
17 | }
18 | @include breakpoint-up("xxlarge") {
19 | grid-template-columns: repeat(4, 1fr);
20 | }
21 | > div {
22 | background-color: #fff;
23 | border-radius: 5px;
24 | padding: 1.5rem 0.5rem;
25 | text-align: center;
26 | box-shadow: 0 8px 25px rgba(0, 0, 0, 0.07);
27 |
28 | h3 {
29 | font-size: 1.3rem;
30 | font-weight: 400;
31 | color: #2c2c2c;
32 | }
33 | h4 {
34 | font-size: 1.3rem;
35 | font-weight: 700;
36 | margin-top: 1rem;
37 | color: var(--main-color);
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_home-banner.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .homeBanner {
4 | &__container {
5 | height: 100vh;
6 |
7 | @media screen and (min-width: 968px) {
8 | grid-template-columns: repeat(2, 1fr);
9 | }
10 |
11 | &__left {
12 | background: url("../../assets/images/home-banner-5.webp");
13 | background-repeat: no-repeat;
14 | background-size: cover;
15 | background-position: 100% 100%;
16 | position: relative;
17 | @include flex(center, center);
18 |
19 | &__overlay {
20 | position: absolute;
21 | content: "";
22 | background: hsla(41, 89%, 90%, 0.479);
23 | width: 100%;
24 | height: 100%;
25 | left: 0;
26 | top: 0;
27 | }
28 |
29 | > div {
30 | padding: 1.5rem;
31 | z-index: 10;
32 |
33 | @include breakpoint-up("medium") {
34 | padding: 1.5rem 3rem;
35 | }
36 | @include breakpoint-up("large") {
37 | padding: 1.5rem 6rem;
38 | }
39 | @media screen and (min-width: 968px) {
40 | padding: 2rem;
41 | }
42 | @include breakpoint-up("xlarge") {
43 | padding: 3rem;
44 | }
45 |
46 | h3 {
47 | font-size: 1rem;
48 | font-weight: 500;
49 | color: #2c2c2c;
50 |
51 | @include breakpoint-up("large") {
52 | font-size: 1.3rem;
53 | }
54 | }
55 |
56 | h1 {
57 | font-size: 1.6rem;
58 | font-weight: 600;
59 | color: #2c2c2c;
60 | line-height: 1.4;
61 | @include breakpoint-up("large") {
62 | font-size: 2rem;
63 | }
64 |
65 | @media screen and (min-width: 968px) {
66 | margin: 1.5rem 0;
67 | }
68 | @include breakpoint-up("xlarge") {
69 | font-size: 2.5rem;
70 | }
71 | }
72 |
73 | button {
74 | width: 150px;
75 | }
76 | }
77 | }
78 |
79 | .right {
80 | background: url("../../assets/images/home-banner-6.webp");
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_loader.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 | .spinner-container {
3 | height: 100%;
4 | top: 0;
5 | width: 100%;
6 | left: 0;
7 | }
8 |
9 | .spinner-container {
10 | &.overlay {
11 | z-index: 3000;
12 | }
13 |
14 | &.backdrop {
15 | background-color: var(--transparent-white-bg);
16 | }
17 | }
18 |
19 | .spinner {
20 | bottom: 0;
21 | left: 0;
22 | right: 0;
23 | top: 25%;
24 | z-index: 1022;
25 | width: 40px;
26 | height: 40px;
27 | margin: 100px auto;
28 | background-color: var(--theme-light-blue);
29 | border-radius: 100%;
30 | -webkit-animation: sk-scaleout 1s infinite ease-in-out;
31 | animation: sk-scaleout 1s infinite ease-in-out;
32 | }
33 |
34 | @-webkit-keyframes sk-scaleout {
35 | 0% {
36 | -webkit-transform: scale(0);
37 | }
38 | 100% {
39 | -webkit-transform: scale(1);
40 | opacity: 0;
41 | }
42 | }
43 |
44 | @keyframes sk-scaleout {
45 | 0% {
46 | -webkit-transform: scale(0);
47 | transform: scale(0);
48 | }
49 | 100% {
50 | -webkit-transform: scale(1);
51 | transform: scale(1);
52 | opacity: 0;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_mobile-menu.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .mobile {
4 | background-color: #fff;
5 | &__close {
6 | text-align: end;
7 | padding: 22px 20px;
8 |
9 | &-icon {
10 | font-size: 1.5rem;
11 | font-weight: 600;
12 | color: #2c2c2c;
13 |
14 | &:hover {
15 | color: var(--font-custom-color);
16 | }
17 |
18 | @include breakpoint-up("large") {
19 | font-size: 1.3rem;
20 | }
21 | }
22 | }
23 | &__menu {
24 | @include breakpoints-down("medium") {
25 | width: 100%;
26 | height: 100vh;
27 | padding: 6rem 2rem 3.5rem;
28 | transition: var(--transition);
29 | }
30 |
31 | &__list {
32 | li {
33 | a {
34 | font-size: 1.2rem;
35 | letter-spacing: 0.5px;
36 | &:hover {
37 | color: var(--hover-color);
38 | }
39 | }
40 | }
41 |
42 | @include breakpoints-down("medium") {
43 | display: flex;
44 | flex-direction: column;
45 | align-items: center;
46 | row-gap: 2rem;
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_my-orders.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .myorders {
4 | padding: 3rem 0;
5 |
6 | h2 {
7 | font-size: 26px;
8 | font-weight: 500;
9 | color: #333;
10 | margin-bottom: 2rem;
11 | }
12 | @media screen and(min-width: 1080px) {
13 | padding: 7rem 0;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_news-letter.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .newsletter {
4 | padding-top: 3rem;
5 | padding-bottom: 5rem;
6 | }
7 | .news {
8 | height: 200px;
9 | @include flex(center, center);
10 |
11 | &__content {
12 | text-align: center;
13 |
14 | h2 {
15 | font-size: 2rem;
16 | color: #333;
17 | position: relative;
18 | font-weight: 600;
19 | margin-bottom: 1rem;
20 | }
21 | p {
22 | font-size: 15px;
23 | font-weight: 500;
24 | margin-bottom: 3rem;
25 | }
26 | div {
27 | width: 100%;
28 | z-index: 10;
29 | margin: 0 auto;
30 | }
31 |
32 | input {
33 | height: 50px;
34 | background: rgb(244, 244, 244);
35 | display: block;
36 | width: 100%;
37 | border: 1xp solid gray;
38 | font-size: 15px;
39 | color: #333;
40 | padding: 0 15px;
41 | margin-bottom: 18px;
42 | transition: all 0.5s ease;
43 | margin-bottom: 1.5rem;
44 |
45 | &:focus {
46 | box-shadow: 0 0 6px rgb(0 0 0 / 20%);
47 | }
48 | }
49 | .button {
50 | width: 170px;
51 | margin: 0 auto;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_not-found.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .notfound {
4 | @include flex(center, center);
5 | height: 100vh;
6 |
7 | h1 {
8 | font-size: 3rem;
9 | font-weight: 700;
10 | color: #444;
11 | line-height: 1.5;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_order-details.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .orderDetails {
4 | padding: 3rem 0;
5 | font-family: "Roboto", sans-serif;
6 | @media screen and (min-width: 1080px) {
7 | padding: 7rem 0;
8 | }
9 |
10 | @include breakpoint-up("large") {
11 | grid-template-columns: 1fr 1fr;
12 | gap: 4rem;
13 | }
14 | &__container {
15 | h2 {
16 | font-size: 1.5rem;
17 | font-weight: 400;
18 | color: #2c2c2c;
19 | span {
20 | font-size: 1.1rem;
21 | @media screen and (min-width: 456px) {
22 | font-size: 1.5rem;
23 | }
24 | }
25 | }
26 | &__box {
27 | margin-bottom: 1.4rem;
28 | h3 {
29 | font-size: 1.4rem;
30 | color: #2c2c2c;
31 | font-weight: 400;
32 | margin-bottom: 1.5rem;
33 | }
34 |
35 | > div {
36 | display: flex;
37 | justify-content: space-between;
38 | }
39 |
40 | .address {
41 | margin-right: 2rem;
42 | }
43 | }
44 | }
45 |
46 | &__cartItems {
47 | h3 {
48 | margin-bottom: 1.5rem;
49 | }
50 | &__container {
51 | > div {
52 | @include flex(center, space-between);
53 |
54 | img {
55 | width: 60px;
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_process-order.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 | .updateOrderForm {
3 | // margin: 0 3rem;
4 |
5 | background-color: white;
6 |
7 | > div {
8 | display: flex;
9 | width: 100%;
10 | align-items: center;
11 |
12 | > select {
13 | padding: 0.8rem 2rem;
14 | margin: 1.5rem 0;
15 | width: 100%;
16 | box-sizing: border-box;
17 | border: 1px solid rgba(0, 0, 0, 0.267);
18 | border-radius: 4px;
19 | font-weight: 300;
20 | font-size: 16px;
21 | outline: none;
22 | }
23 |
24 | > svg {
25 | position: absolute;
26 | transform: translateX(1vmax);
27 | font-size: 1rem;
28 | color: rgba(0, 0, 0, 0.623);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_product-detail.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .product {
4 | margin-top: 7rem;
5 | &__detail {
6 | @include breakpoint-up("large") {
7 | grid-template-columns: repeat(2, 1fr);
8 | }
9 | &__img {
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | img {
14 | width: 280px;
15 | }
16 | }
17 |
18 | &__info {
19 | &-name {
20 | font-size: 26px;
21 | margin: 1rem 0 0.5rem 0;
22 | color: #2c2c2c;
23 | font-weight: 600;
24 | }
25 |
26 | &-rating {
27 | margin-bottom: 0.5rem;
28 | }
29 |
30 | &-stock {
31 | p {
32 | font-size: 1rem;
33 | margin-bottom: 1rem;
34 | font-weight: 500;
35 | }
36 | }
37 |
38 | &__price {
39 | &-count {
40 | display: block;
41 | font-size: 22px;
42 | color: var(--main-color);
43 | font-family: "Roboto", sans-serif;
44 | margin-top: 0.8rem;
45 | font-weight: 500;
46 | }
47 | }
48 | &__buttons {
49 | display: flex;
50 | align-items: center;
51 |
52 | gap: 1rem;
53 |
54 | &__select {
55 | height: 50px;
56 | padding: 0 1.5rem;
57 | font-size: 1rem;
58 | border: 1px solid #e4e4e4;
59 | border-radius: 2px;
60 | }
61 |
62 | .add {
63 | @include flex(center, center);
64 | }
65 |
66 | .button {
67 | width: 170px;
68 | font-weight: 500;
69 | margin-top: 0;
70 | }
71 | }
72 |
73 | &-desc {
74 | margin: 1rem 0;
75 |
76 | p {
77 | font-size: 16px;
78 | }
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_products.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .shop {
4 | padding-top: 5rem;
5 | padding-bottom: 7rem;
6 | }
7 |
8 | .products {
9 | padding-bottom: 5rem;
10 |
11 | &__container {
12 | margin-top: 5rem;
13 |
14 | @include breakpoint-up("large") {
15 | grid-template-columns: repeat(2, 1fr);
16 | }
17 | @include breakpoint-up("xlarge") {
18 | grid-template-columns: repeat(3, 1fr);
19 | }
20 | &__item {
21 | transition: all 0.5s ease;
22 | img {
23 | max-width: 100%;
24 | }
25 | border: 1px solid #e4e4e4;
26 |
27 | div {
28 | text-align: center;
29 |
30 | background-color: rgb(244, 244, 244);
31 | padding: 15px;
32 |
33 | h3 {
34 | font-size: 22px;
35 | font-weight: 700;
36 | color: #2c2c2c;
37 | transition: var(--transition);
38 | &:hover {
39 | cursor: pointer;
40 | color: var(--hover-color);
41 | }
42 | }
43 |
44 | h5 {
45 | font-size: 20px;
46 | font-weight: 700;
47 | color: var(--main-color);
48 | margin: 1rem 0;
49 | }
50 |
51 | div {
52 | margin-top: 0;
53 | padding-top: 0;
54 | @include flex(center, center);
55 | .button {
56 | width: 170px;
57 | height: 45px;
58 | line-height: 45px;
59 | font-size: 15px;
60 | font-weight: 600;
61 | @include flex(center, center);
62 |
63 | svg {
64 | font-size: 15px;
65 | font-weight: 600;
66 | margin-right: 0.3rem;
67 | }
68 | }
69 | }
70 | }
71 |
72 | &:hover {
73 | border: 1px solid var(--hover-color);
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_profile.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .profile {
4 | padding-top: 2rem;
5 | margin-bottom: 6rem;
6 |
7 | @media screen and (min-width: 1080px) {
8 | padding-top: 7rem;
9 | }
10 | &__title {
11 | margin-bottom: 2rem;
12 | color: #2c2c2c;
13 | font-weight: 500;
14 | }
15 |
16 | &__container {
17 | @include breakpoint-up("medium") {
18 | grid-template-columns: repeat(2, 1fr);
19 | }
20 | label {
21 | margin-bottom: 1rem;
22 | }
23 | &__img {
24 | width: 150px;
25 | height: 150px;
26 | overflow: hidden;
27 | position: relative;
28 | margin: 15px auto;
29 | border: 1px solid #ddd;
30 | border-radius: 50%;
31 | cursor: pointer;
32 | text-align: start;
33 |
34 | &:hover span {
35 | bottom: -15%;
36 | color: #fff;
37 | }
38 | img {
39 | width: 100%;
40 | height: 100%;
41 | display: block;
42 | object-fit: cover;
43 | }
44 |
45 | span {
46 | position: absolute;
47 | color: var(--main-color);
48 | bottom: -100%;
49 | left: 0;
50 | width: 100%;
51 | height: 50%;
52 | z-index: 10;
53 | text-align: center;
54 | font-weight: 400;
55 | text-transform: uppercase;
56 | transition: 0.3s ease-in-out;
57 | background-color: rgba($color: #f1510a, $alpha: 0.5);
58 |
59 | p {
60 | color: var(--main-color);
61 | font-size: 14px;
62 | font-weight: 700;
63 | }
64 |
65 | input {
66 | position: absolute;
67 | top: 0;
68 | left: 0;
69 | width: 100%;
70 | height: 100%;
71 | cursor: pointer;
72 | opacity: 0;
73 | }
74 | }
75 | }
76 |
77 | &__info {
78 | &-name {
79 | margin-bottom: 1.2rem;
80 | }
81 | }
82 |
83 | &__password {
84 | &-warning {
85 | display: block;
86 | color: red;
87 | font-size: 15px;
88 | margin-bottom: 1rem;
89 | }
90 | &-pass {
91 | margin-bottom: 1.2rem;
92 | position: relative;
93 | small {
94 | position: absolute;
95 | top: 70%;
96 | right: 15px;
97 | transform: translateY(-50%);
98 | cursor: pointer;
99 | opacity: 0.5;
100 | svg {
101 | font-size: 16px;
102 | }
103 | }
104 | }
105 |
106 | &-confirm {
107 | position: relative;
108 | small {
109 | position: absolute;
110 | top: 70%;
111 | right: 15px;
112 | transform: translateY(-50%);
113 | cursor: pointer;
114 | opacity: 0.5;
115 | svg {
116 | font-size: 16px;
117 | }
118 | }
119 | }
120 | }
121 | }
122 |
123 | &__button {
124 | width: 160px;
125 | margin-bottom: 3rem;
126 | }
127 |
128 | &__admin {
129 | margin-top: 3rem;
130 | &__users {
131 | &-title {
132 | color: #333;
133 | font-size: 24px;
134 | font-weight: 500;
135 | margin-bottom: 3rem;
136 | }
137 | }
138 | }
139 | }
140 |
141 | /* -------------- Edit User ----------------- */
142 | .edit_user {
143 | margin-bottom: 7rem;
144 | padding-top: 2.5rem;
145 | @media screen and (min-width: 1080px) {
146 | padding-top: 7rem;
147 | }
148 |
149 | .go_back {
150 | background: white;
151 | margin: 20px;
152 | font-size: 1.2rem;
153 | text-align: start;
154 | cursor: pointer;
155 | }
156 | .col-left {
157 | margin-top: 50px;
158 | @include breakpoint-up("large") {
159 | width: 500px;
160 | margin: 0 auto;
161 | }
162 |
163 | .edit {
164 | font-size: 22px;
165 | color: #333;
166 | margin-bottom: 2rem;
167 | font-weight: 600;
168 | }
169 |
170 | .form-group {
171 | label {
172 | margin-bottom: 0.8rem;
173 | }
174 |
175 | margin-bottom: 1rem;
176 | }
177 |
178 | input {
179 | background: #eee;
180 | }
181 |
182 | #isAdmin {
183 | width: 20px;
184 | height: 20px;
185 | transform: translateY(3px);
186 | margin: 10px 0;
187 | }
188 |
189 | .update-btn {
190 | font-size: 14px;
191 | font-weight: 500;
192 | line-height: 1;
193 | padding: 15px 52px;
194 | text-transform: uppercase;
195 | color: #fff;
196 | border: none;
197 | background-color: #404040;
198 | &:hover {
199 | background-color: var(--theme-color);
200 | }
201 | }
202 | }
203 | }
204 |
205 | .edit_user .col-left #isAdmin + label {
206 | display: inline-block;
207 | margin-left: 5px;
208 | }
209 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_protected-area.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .protected-section {
4 | padding-bottom: 5rem;
5 | }
6 | .protected {
7 | height: 100vh;
8 | background: url("../../assets/images/protected-area.webp");
9 | background-repeat: no-repeat;
10 | background-size: cover;
11 | background-position: center center;
12 | position: relative;
13 | @include flex(center, flex-start);
14 |
15 | &__overlay {
16 | position: absolute;
17 | content: "";
18 | background: rgba(19, 59, 102, 0.301);
19 | width: 100%;
20 | height: 100%;
21 | left: 0;
22 | top: 0;
23 | }
24 |
25 | &__content {
26 | z-index: 10;
27 |
28 | p {
29 | font-size: 1.4rem;
30 | color: #fff;
31 | font-weight: 600;
32 | }
33 |
34 | h2 {
35 | color: var(--main-color);
36 | font-size: 4rem;
37 | font-weight: 700;
38 | line-height: 1.3;
39 |
40 | @include breakpoints-down("medium") {
41 | font-size: 3rem;
42 | }
43 | }
44 |
45 | h5 {
46 | font-size: 1.7rem;
47 | font-weight: 600;
48 | color: #fff;
49 | margin: 1rem 0;
50 | margin-bottom: 1.5rem;
51 | line-height: 1.3;
52 | }
53 | .button {
54 | width: 200px;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_register.scss:
--------------------------------------------------------------------------------
1 | @use "./utils" as *;
2 | .contact {
3 | margin-top: 3rem !important;
4 | width: 400px;
5 | margin: 0 auto;
6 |
7 | @include breakpoints-down("small") {
8 | width: 350px;
9 | padding: 43px 20px 50px;
10 | }
11 | //extra medium 375px
12 | @include breakpoints-down("xm") {
13 | width: 300px;
14 | }
15 | @include breakpoints-down("xs") {
16 | width: 280px;
17 | }
18 |
19 | &__title {
20 | margin-bottom: 1.5rem;
21 | font-weight: 600;
22 | color: #2c2c2c;
23 | text-align: center;
24 | margin-bottom: 3rem;
25 |
26 | @include breakpoint-up("xlarge") {
27 | font-size: 2rem;
28 | }
29 | }
30 | // forgot password forgot page
31 | &__desc {
32 | text-align: center;
33 | color: #2c2c2c;
34 | margin-bottom: 1.5rem;
35 | }
36 | &__form {
37 | @include breakpoint-up("medium") {
38 | }
39 |
40 | label {
41 | font-size: 14px;
42 | font-weight: 500;
43 | color: #333;
44 | margin-bottom: 0.5rem;
45 | }
46 |
47 | input {
48 | border: 1px solid #e4e4e4;
49 | font-size: 14px;
50 |
51 | &:focus {
52 | border: 1px solid #e4e4e4;
53 | box-shadow: 0 0 6px rgb(0 0 0 / 20%);
54 | }
55 | }
56 | &__div {
57 | margin: 1rem 0;
58 | position: relative;
59 | small {
60 | position: absolute;
61 | top: 70%;
62 | right: 15px;
63 | transform: translateY(-50%);
64 | cursor: pointer;
65 | opacity: 0.5;
66 | svg {
67 | font-size: 16px;
68 | }
69 | }
70 |
71 | &-tag {
72 | display: block;
73 | margin: 0.5rem 0;
74 | }
75 | }
76 | .social button {
77 | width: 100%;
78 | margin: 10px 0;
79 | height: 50px;
80 | text-transform: capitalize;
81 | box-shadow: 0 2px 4px #777;
82 | margin-top: 1rem;
83 |
84 | span {
85 | display: block;
86 | font-weight: 600;
87 | letter-spacing: 1px;
88 | text-align: center;
89 | color: #fff;
90 | font-size: 14px;
91 | margin-left: 10px;
92 |
93 | @include breakpoint-up("medium") {
94 | margin-left: 60px;
95 | }
96 |
97 | @include breakpoint-up("large") {
98 | margin-left: 95px;
99 | }
100 | }
101 | }
102 |
103 | // forgot text singup page
104 | &__forgot {
105 | text-align: end;
106 | margin-top: 1rem;
107 | a {
108 | transition: var(--transition);
109 |
110 | &:hover {
111 | color: var(--hover-color);
112 | }
113 | }
114 | }
115 |
116 | // reset password forgot page
117 | &__reset {
118 | display: flex;
119 | gap: 1rem;
120 |
121 | &-submit,
122 | &-cancel {
123 | flex: 1;
124 | }
125 | }
126 | }
127 | }
128 |
129 | .pass {
130 | position: relative;
131 | }
132 | .pass small {
133 | position: absolute;
134 | top: 70%;
135 | right: 8px;
136 | transform: translateY(-50%);
137 | cursor: pointer;
138 | opacity: 0.5;
139 | svg {
140 | font-size: 16px;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_reset.scss:
--------------------------------------------------------------------------------
1 | @use "./utils" as *;
2 | *,
3 | *::after,
4 | *::before {
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: border-box;
8 | }
9 | html {
10 | scroll-behavior: smooth;
11 | }
12 |
13 | html {
14 | overflow-x: hidden;
15 | }
16 |
17 | html,
18 | body {
19 | height: 100%;
20 | }
21 |
22 | body {
23 | font-family: var(--body-font);
24 | font-size: rem(14);
25 | font-style: normal;
26 | font-weight: 400;
27 | line-height: 1.5;
28 | background-color: white;
29 | color: #2c2c2c;
30 |
31 | @include breakpoints-down("medium") {
32 | margin-bottom: 3rem;
33 | }
34 | }
35 |
36 | img {
37 | max-width: 100%;
38 | }
39 | /* ====== global styles any section can i use ====== */
40 |
41 | .container-div {
42 | max-width: 968px;
43 | margin-left: 1.5rem;
44 | margin-right: 1.5rem;
45 | //min-width: 1024px
46 | @include breakpoint-up("xlarge") {
47 | margin: 1rem auto;
48 | }
49 | @include breakpoint-up("xxlarge") {
50 | max-width: 1200px;
51 | }
52 | }
53 |
54 | .grid {
55 | display: grid;
56 | gap: 1.25rem;
57 | }
58 |
59 | .main {
60 | overflow: hidden;
61 | }
62 |
63 | /*-- Common Style --*/
64 |
65 | h1,
66 | h2,
67 | h3,
68 | h4,
69 | h5,
70 | h6 {
71 | font-weight: 400;
72 | margin-top: 0;
73 | color: var(--heading-color);
74 | }
75 |
76 | p {
77 | font-size: rem(14);
78 | font-weight: 400;
79 | line-height: rem(24);
80 | margin-bottom: rem(15);
81 | color: #333;
82 | }
83 |
84 | h1 {
85 | font-size: rem(36);
86 | line-height: rem(42);
87 | }
88 |
89 | h2 {
90 | font-size: rem(30);
91 | line-height: rem(36);
92 | }
93 |
94 | h3 {
95 | font-size: rem(24);
96 | line-height: rem(30);
97 | }
98 |
99 | h4 {
100 | font-size: rem(18);
101 | line-height: 24px;
102 | }
103 |
104 | h5 {
105 | font-size: rem(14);
106 | line-height: rem(18);
107 | }
108 |
109 | h6 {
110 | font-size: rem(12);
111 | line-height: rem(14);
112 | }
113 |
114 | p:last-child {
115 | margin-bottom: 0;
116 | }
117 |
118 | a,
119 | a:visited,
120 | a:hover,
121 | a:active {
122 | color: inherit;
123 | }
124 |
125 | a,
126 | button {
127 | line-height: inherit;
128 | cursor: pointer;
129 | text-decoration: none;
130 | color: #000;
131 | }
132 | button {
133 | border: none;
134 | outline: none;
135 | }
136 |
137 | a,
138 | button,
139 | img,
140 | input,
141 | span {
142 | transition: all 0.3s ease 0s;
143 | }
144 |
145 | *:focus {
146 | outline: none !important;
147 | }
148 |
149 | a:hover {
150 | text-decoration: none;
151 | }
152 |
153 | button,
154 | input[type="submit"] {
155 | cursor: pointer;
156 | }
157 |
158 | ul {
159 | margin: 0;
160 | padding: 0;
161 | list-style: outside none none;
162 | }
163 |
164 | .section {
165 | padding: 5rem 0;
166 | }
167 | /*--
168 | - Common Classes
169 | -----------------------------------------*/
170 |
171 | .button {
172 | display: block;
173 | margin-top: 1rem;
174 | text-transform: capitalize;
175 | font-size: 16px;
176 | width: 100%;
177 | background-color: var(--main-color);
178 | color: #fff;
179 | padding: 0;
180 | height: 50px;
181 | line-height: 50px;
182 | transition: var(--transition);
183 |
184 | &:hover {
185 | background-color: var(--hover-color);
186 | }
187 | }
188 |
189 | .button-primary {
190 | display: block;
191 | margin-top: 1rem;
192 | text-transform: capitalize;
193 | font-size: 16px;
194 | width: 100%;
195 | background-color: var(--color-grey);
196 | color: #2c2c2c;
197 | padding: 0;
198 | height: 50px;
199 | line-height: 50px;
200 | transition: var(--transition);
201 |
202 | &:hover {
203 | text-decoration: underline;
204 | color: var(--theme-color);
205 | }
206 | }
207 | .button-secondary {
208 | display: block;
209 | margin-top: 1rem;
210 | text-transform: capitalize;
211 | font-size: 16px;
212 | width: 100%;
213 | background-color: transparent;
214 | border: 1px solid #2c2c2c;
215 | color: #2c2c2c;
216 | padding: 0;
217 | height: 50px;
218 | line-height: 50px;
219 | transition: var(--transition);
220 |
221 | &:hover {
222 | background-color: #2c2c2c;
223 | color: #fff;
224 | }
225 | }
226 |
227 | input {
228 | font-size: 15px;
229 | width: 100%;
230 | height: 50px;
231 | padding-left: 10px;
232 | border-radius: 3px;
233 | color: #2c2c2c;
234 | border: 1px solid #e4e4e4 !important;
235 | background: transparent;
236 | box-shadow: none;
237 | }
238 |
239 | input:focus {
240 | background: transparent;
241 | }
242 |
243 | .redColor {
244 | color: var(--main-color);
245 | }
246 | .greenColor {
247 | color: green;
248 | }
249 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_section-title.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .section-title {
4 | text-align: center;
5 | margin-bottom: 2rem;
6 | h2 {
7 | font-size: 2rem;
8 | color: #333;
9 | position: relative;
10 | font-weight: 600;
11 | &::after {
12 | content: "";
13 | position: absolute;
14 | bottom: -50%;
15 | left: 50%;
16 | transform: translateX(-50%);
17 | width: 50%;
18 | transition: width 0.5s ease;
19 | height: 2px;
20 | background-color: var(--main-color);
21 | }
22 |
23 | @include breakpoint-up("medium") {
24 | font-size: 2.5rem;
25 | &::after {
26 | width: 30%;
27 | }
28 | }
29 | @include breakpoint-up("large") {
30 | &::after {
31 | width: 25%;
32 | }
33 | }
34 | @include breakpoint-up("xlarge") {
35 | &::after {
36 | width: 20%;
37 | }
38 | }
39 | @include breakpoint-up("xxlarge") {
40 | &::after {
41 | width: 15%;
42 | }
43 | }
44 | }
45 | p {
46 | font-size: 1rem;
47 | font-weight: 500;
48 | color: var(--main-color);
49 | margin-top: 2rem;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/_user-list-item.scss:
--------------------------------------------------------------------------------
1 | @use "./utils/" as *;
2 |
3 | .table__container {
4 | margin: 40px auto 0;
5 | &__table {
6 | width: 100%;
7 | border-collapse: collapse;
8 |
9 | thead {
10 | background-color: #2c2c2c;
11 |
12 | tr {
13 | th {
14 | font-size: 16px;
15 | font-weight: 600;
16 | letter-spacing: 0.35px;
17 | color: #fff;
18 | opacity: 1;
19 | padding: 12px;
20 | vertical-align: top;
21 | border: 1px solid #dee2e685;
22 | }
23 | }
24 | }
25 |
26 | tbody {
27 | tr {
28 | .action {
29 | display: flex;
30 | align-items: center;
31 | gap: 1rem;
32 | }
33 | td {
34 | font-size: 14px;
35 | letter-spacing: 0.35px;
36 | font-weight: normal;
37 | color: #2c2c2c;
38 | background-color: #fff;
39 | padding: 10px;
40 | text-align: start;
41 | border: 1px solid #dee2e685;
42 | &:nth-child(4) {
43 | text-align: center;
44 | }
45 |
46 | .edit {
47 | color: #2c2c2c;
48 | font-size: 17px;
49 | }
50 |
51 | .remove {
52 | color: #fff;
53 | background-color: var(--main-color);
54 | margin-left: 1rem;
55 | font-size: 22px;
56 | padding: 0.2rem;
57 | border-radius: 3px;
58 | }
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/core.scss:
--------------------------------------------------------------------------------
1 | // Import core styles
2 | @use "./reset" as *;
3 | @use "./utils/" as *;
4 |
5 | @use "./header";
6 | @use "./mobile-menu";
7 | //home
8 | @use "./banner";
9 | @use "./home-banner";
10 | @use "./protected-area";
11 | @use "./section-title";
12 | @use "./products";
13 | @use "./discount-area";
14 | @use "./news-letter";
15 | @use "./company-area";
16 |
17 | // login and regiser
18 | @use "./register";
19 | @use "./activation-email";
20 |
21 | // cart
22 | @use "./cart";
23 |
24 | @use "./checkout";
25 |
26 | // footer
27 | @use "./footer";
28 |
29 | // not found page
30 | @use "./not-found";
31 |
32 | // user dashboard
33 | @use "./sidebar";
34 | @use "./my-orders";
35 | @use "./order-details";
36 |
37 | // both user and admin dashboard use
38 | @use "./profile";
39 |
40 | // admin dashboard
41 | @use "./user-list-item";
42 |
43 | @use "./add-product";
44 |
45 | @use "./all-products";
46 |
47 | @use "./product-detail";
48 | @use "./process-order";
49 | @use "./home-admin";
50 |
51 | @use "./chart";
52 |
53 | // loader
54 | @use "./loader";
55 |
56 | //
57 | @use "./checkout-payment";
58 |
59 | //
60 |
61 | @use "./contact";
62 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/utils/_fonts.scss:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap");
2 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/utils/_functions.scss:
--------------------------------------------------------------------------------
1 | @use "sass:math";
2 |
3 | @function rem($pixels, $context: 16) {
4 | // math.div(8 /16) * 1rem = .5rem like use this. math.div() take two parameter and calculte useing division return the value of rem
5 | @return (math.div($pixels, $context)) * 1rem;
6 | }
7 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/utils/_index.scss:
--------------------------------------------------------------------------------
1 | @forward "./variables";
2 | @forward "./fonts";
3 |
4 | @forward "./mixins";
5 | @forward "./functions";
6 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/utils/_mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin flex($alignItem, $justifyContent) {
2 | display: flex;
3 | align-items: $alignItem;
4 | justify-content: $justifyContent;
5 | }
6 |
7 | // // max-width = 1024px
8 |
9 | //376px, 576px, 767px, 1024px, 1200px / 1rem
10 |
11 | $breakpoints-up: (
12 | "small": "23.5em",
13 | "medium": "36em",
14 | "large": "47.9375em",
15 | "xlarge": "64em",
16 | "xxlarge": "75em",
17 | );
18 |
19 | //320px, 375px, 576px, 1024px, 1399px
20 |
21 | $breakpoints-down: (
22 | "xs": "20em",
23 | "xm": "23.4375em",
24 | "small": "36em",
25 | "medium": "64em",
26 | );
27 |
28 | @mixin breakpoint-up($size) {
29 | // map-get(key, value)
30 | @media (min-width: map-get($breakpoints-up, $size)) {
31 | @content;
32 | }
33 | }
34 |
35 | @mixin breakpoints-down($size) {
36 | @media (max-width: map-get($breakpoints-down, $size)) {
37 | @content;
38 | }
39 | }
40 |
41 | // dark overflow
42 | @mixin dark-overflow {
43 | background-color: var(--dark-overflow-bg);
44 | cursor: pointer;
45 | height: 100%;
46 | left: 0;
47 | position: fixed;
48 | top: 0;
49 | width: 100%;
50 | z-index: 999;
51 | }
52 | @mixin transition($speed: $layout-transition-speed) {
53 | -webkit-transition: all $speed ease;
54 | -moz-transition: all $speed ease;
55 | -o-transition: all $speed ease;
56 | transition: all $speed ease;
57 | }
58 |
59 | @mixin overlay {
60 | background-color: rgba($color: #000000, $alpha: 0.6);
61 | }
62 |
63 | @mixin appearance($value: none) {
64 | -webkit-appearance: $value;
65 | -moz-appearance: $value;
66 | -ms-appearance: $value;
67 | -o-appearance: $value;
68 | appearance: $value;
69 | }
70 |
71 | @mixin placeholder {
72 | ::-webkit-input-placeholder {
73 | @content;
74 | }
75 |
76 | ::-moz-placeholder {
77 | @content;
78 | }
79 |
80 | :-moz-placeholder {
81 | @content;
82 | }
83 |
84 | :-ms-input-placeholder {
85 | @content;
86 | }
87 | }
88 |
89 | @mixin mobile {
90 | @media only screen and (max-width: 600px) {
91 | @content;
92 | }
93 | }
94 |
95 | @mixin tablet {
96 | @media only screen and (max-width: 1200px) {
97 | @content;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/server/client/src/styles/core/utils/_variables.scss:
--------------------------------------------------------------------------------
1 | /*=============== VARIABLES CSS ===============*/
2 | :root {
3 | // text Colors ---------------
4 | --color-white: #ffffff;
5 | --color-black: #000000;
6 | --color-grey: #f6f7f8;
7 | --color-red: #f6f7f8;
8 | --color-green: #f6f7f8;
9 |
10 | --main-color: #f1510a;
11 | --hover-color: #ffb001;
12 | --heading-color: #333333;
13 | --button-hover-color: #ffb001;
14 |
15 | --dashboard-body-color: rgb(243, 244, 246);
16 | --transparent-white-bg: #f1f1f170;
17 | --theme-light-blue: #4a68aa;
18 |
19 | --header-height: 3.5rem;
20 |
21 | /*========== Font and typography ==========*/
22 | /*.5rem = 8px | 1rem = 16px ...*/
23 | --body-font: "Raleway", sans-serif;
24 | --biggest-font-size: 2rem;
25 | --h2-font-size: 1.25rem;
26 | --h3-font-size: 1.3rem;
27 | --normal-font-size: 0.938rem;
28 | --small-font-size: 0.813rem;
29 | --smaller-font-size: 0.75rem;
30 | /*========== Font weight ==========*/
31 | --font-medium: 500;
32 | --font-semibold: 600;
33 |
34 | /*========== z index ==========*/
35 | --transition: all 400ms ease;
36 | --z-tooltip: 10;
37 | --z-fixed: 100;
38 |
39 | --dark-overflow-bg: #0006;
40 | }
41 |
--------------------------------------------------------------------------------
/server/client/src/styles/styles.scss:
--------------------------------------------------------------------------------
1 | // Import Bootstrap source files
2 | @import "~bootstrap/scss/bootstrap";
3 |
4 | @import "./core/core.scss";
5 |
--------------------------------------------------------------------------------
/server/client/src/utils/checkTokenExp.js:
--------------------------------------------------------------------------------
1 | import jwt_decode from "jwt-decode";
2 |
3 | import axios from "axios";
4 | import { USER_LOGIN_SUCCESS } from "../redux/constants/userConstants";
5 |
6 | export const checkTokenExp = async (token, dispatch) => {
7 | const decoded = jwt_decode(token);
8 | console.log(decoded, "decoded");
9 |
10 | if (decoded.exp >= Date.now() / 1000) return;
11 | const config = {
12 | headers: {
13 | "Content-Type": "application/json",
14 | },
15 | };
16 |
17 | const res = await axios.get(
18 | "https://mern-camera-shop.herokuapp.com/api/refresh_token",
19 | config
20 | );
21 |
22 | dispatch({ type: USER_LOGIN_SUCCESS, payload: res.data });
23 | return res.data.access_token;
24 | };
25 |
--------------------------------------------------------------------------------
/server/client/src/utils/validation.js:
--------------------------------------------------------------------------------
1 | export const validRegister = (user) => {
2 | const { name, email, password, cf_password } = user;
3 |
4 | const errors = [];
5 |
6 | if (!name.trim()) {
7 | errors.push("Please add your name.");
8 | } else if (name.length > 20) {
9 | errors.push("Your name is up to 20 charactor long.");
10 | }
11 |
12 | if (!email.trim()) {
13 | errors.push("Please add your email.");
14 | } else if (!validateEmail(email)) {
15 | errors.push("Email format is incorrect.");
16 | }
17 |
18 | const message = checkPassword(password, cf_password);
19 | if (message) errors.push(message);
20 |
21 | return {
22 | errMsg: errors,
23 | errLength: errors.length,
24 | };
25 | };
26 |
27 | function validateEmail(email) {
28 | const re =
29 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
30 | return re.test(String(email).toLowerCase());
31 | }
32 |
33 | export const checkPassword = (password, cf_password) => {
34 | if (password.length < 6) {
35 | return "Password must be at least 6 chars.";
36 | } else if (password !== cf_password) {
37 | return "Confirm password did not match.";
38 | }
39 | };
40 |
41 | export const isEmpty = (value) => {
42 | if (!value) return true;
43 | return false;
44 | };
45 |
46 | export const isEmail = (email) => {
47 | // eslint-disable-next-line
48 | const re =
49 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
50 | return re.test(email);
51 | };
52 |
53 | export const isLength = (password) => {
54 | if (password.length < 6) return true;
55 | return false;
56 | };
57 |
58 | export const isMatch = (password, cf_password) => {
59 | if (password === cf_password) return true;
60 | return false;
61 | };
62 |
--------------------------------------------------------------------------------
/server/config/database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const URL = process.env.MONGODB_URL;
3 |
4 | const connectDB = async () => {
5 | try {
6 | mongoose.connect(URL, {
7 | useNewUrlParser: true,
8 | useUnifiedTopology: true,
9 | });
10 |
11 | console.log("Database Connected");
12 | } catch (error) {
13 | console.error("Database connection fail");
14 | }
15 | };
16 |
17 | module.exports = connectDB;
18 |
--------------------------------------------------------------------------------
/server/config/generateToken.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 |
3 | exports.generateActiveToken = (payload) => {
4 | // console.log("generate token", process.env.ACTIVE_TOKEN_SECRET);
5 | return jwt.sign(payload, `${process.env.ACTIVE_TOKEN_SECRET}`, {
6 | expiresIn: "5m",
7 | });
8 | };
9 |
10 | exports.generateAccessToken = (payload) => {
11 | return jwt.sign(payload, `${process.env.ACCESS_TOKEN_SECRET}`, {
12 | expiresIn: "15m",
13 | });
14 | };
15 |
16 | exports.generateRefreshToken = (payload, res) => {
17 | const refresh_token = jwt.sign(
18 | payload,
19 | `${process.env.REFRESH_TOKEN_SECRET}`,
20 | {
21 | expiresIn: "30d",
22 | }
23 | );
24 |
25 | res.cookie("refreshtoken", refresh_token, {
26 | // sameSite: "none",
27 | // secure: true,
28 | httpOnly: true,
29 | path: `/api/refresh_token`,
30 | maxAge: 30 * 24 * 60 * 60 * 1000, // 30days
31 | });
32 |
33 | return refresh_token;
34 | };
35 |
--------------------------------------------------------------------------------
/server/config/sendMail.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require("nodemailer");
2 | const { OAuth2Client } = require("google-auth-library");
3 |
4 | const {
5 | MAIL_CLIENT_ID,
6 | MAIL_CLIENT_SECRET,
7 | MAIL_REFRESH_TOKEN,
8 | SENDER_EMAIL_ADDRESS,
9 | } = process.env;
10 | const OAUTH_PLAYGROUND = "https://developers.google.com/oauthplayground";
11 |
12 | const CLIENT_ID = `${MAIL_CLIENT_ID}`;
13 | const CLIENT_SECRET = `${MAIL_CLIENT_SECRET}`;
14 | const REFRESH_TOKEN = `${MAIL_REFRESH_TOKEN}`;
15 | const SENDER_MAIL = `${SENDER_EMAIL_ADDRESS}`;
16 |
17 | // send mail
18 | const sendEmail = async (to, url, txt) => {
19 | const oAuth2Client = new OAuth2Client(
20 | CLIENT_ID,
21 | CLIENT_SECRET,
22 | OAUTH_PLAYGROUND
23 | );
24 |
25 | oAuth2Client.setCredentials({ refresh_token: REFRESH_TOKEN });
26 |
27 | try {
28 | const access_token = await oAuth2Client.getAccessToken();
29 |
30 | const transport = nodemailer.createTransport({
31 | service: "gmail",
32 | auth: {
33 | type: "OAuth2",
34 | user: SENDER_MAIL,
35 | clientId: CLIENT_ID,
36 | clientSecret: CLIENT_SECRET,
37 | refreshToken: REFRESH_TOKEN,
38 | access_token,
39 | },
40 | });
41 |
42 | const mailOptions = {
43 | from: SENDER_MAIL,
44 | to: to,
45 | subject: "CAMERA-SHOP",
46 | html: `
47 |
48 |
Welcome to Camera Shop
49 |
Congratulations! You're almost set to start using QuickShop.
50 | Just click the button below to validate your email address.
51 |
52 |
53 |
${txt}
54 |
55 |
If the button doesn't work for any reason, you can also click on the link below:
56 |
57 |
${url}
58 |
59 | `,
60 | };
61 |
62 | const result = await transport.sendMail(mailOptions);
63 | return result;
64 | } catch (err) {
65 | console.log(err);
66 | }
67 | };
68 |
69 | module.exports = sendEmail;
70 |
--------------------------------------------------------------------------------
/server/controllers/orderController.js:
--------------------------------------------------------------------------------
1 | const Order = require("../models/orderModels");
2 | const Product = require("../models/productModel");
3 | const CustomErrorHandler = require("../services/CustomErrorHandler.js");
4 | const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
5 |
6 | const orderController = {
7 | async newOrder(req, res, next) {
8 | try {
9 | const { shippingInfo, orderItems, email, totalPrice, id, paymentInfo } =
10 | req.body;
11 |
12 | const customer = await stripe.customers.create({
13 | email: email,
14 | source: id,
15 | });
16 |
17 | const myPayment = await stripe.charges.create({
18 | amount: Number(Math.round(totalPrice * 100)),
19 | currency: "usd",
20 | customer: customer.id,
21 | receipt_email: email,
22 | });
23 |
24 | if (myPayment) {
25 | await Order.create({
26 | shippingInfo,
27 | orderItems,
28 | email,
29 | totalPrice,
30 | id,
31 | paymentInfo,
32 | paidAt: Date.now(),
33 | user: req.user._id,
34 | });
35 |
36 | res.status(201).json({
37 | message: "Payment successfully done.",
38 | });
39 | } else {
40 | res
41 | .status(400)
42 | .json({ message: "There's some issue while processing payment" });
43 | }
44 | } catch (error) {
45 | return next(error);
46 | }
47 | },
48 |
49 | async getOrderById(req, res, next) {
50 | let order;
51 | try {
52 | order = await Order.findOne({ _id: req.params.id })
53 | .populate("user", "name email")
54 | .select("-updatedAt -__v");
55 |
56 | if (!order) {
57 | return next(
58 | CustomErrorHandler.badRequest("Order not found with this Id.")
59 | );
60 | }
61 | } catch (err) {
62 | return next(err);
63 | }
64 |
65 | res.json(order);
66 | },
67 | // get user all order
68 | async myOrders(req, res, next) {
69 | let orders;
70 | try {
71 | orders = await Order.find({ user: req.user._id })
72 | .select("-updatedAt -__v")
73 | .sort({ _id: -1 });
74 | } catch (err) {
75 | return next(err);
76 | }
77 |
78 | res.json(orders);
79 | },
80 | // get admin all orders
81 | async getAllOrders(req, res, next) {
82 | try {
83 | const orders = await Order.find();
84 |
85 | let totalAmount = 0;
86 |
87 | orders.forEach((order) => {
88 | totalAmount += order.totalPrice;
89 | });
90 |
91 | res.json({
92 | success: true,
93 | totalAmount,
94 | orders,
95 | });
96 | } catch (err) {
97 | return next(err);
98 | }
99 | },
100 | // admin can do that
101 | async updateOrder(req, res, next) {
102 | try {
103 | const order = await Order.findById(req.params.id);
104 |
105 | if (!order) {
106 | return next(
107 | CustomErrorHandler.badRequest("Order not found with this Id,")
108 | );
109 | }
110 |
111 | if (order.orderStatus === "Delivered") {
112 | return next(
113 | CustomErrorHandler.badRequest("You have already delivered this order")
114 | );
115 | }
116 |
117 | if (req.body.status === "Shipped") {
118 | order?.orderItems?.forEach(async (o) => {
119 | await updateStock(o.product, o.quantity);
120 | });
121 | }
122 | order.orderStatus = req.body.status;
123 |
124 | if (req.body.status === "Delivered") {
125 | order.deliveredAt = Date.now();
126 | }
127 |
128 | await order.save({ validateBeforeSave: false });
129 | res.status(200).json({
130 | success: true,
131 | });
132 | } catch (error) {
133 | return next(error);
134 | }
135 | },
136 |
137 | async deleteOrder(req, res, next) {
138 | try {
139 | try {
140 | await Order.findByIdAndDelete(req.params.id);
141 | res.json({ success: true });
142 | } catch (err) {
143 | return next(err);
144 | }
145 | } catch (err) {
146 | return next(err);
147 | }
148 | },
149 | };
150 |
151 | async function updateStock(id, quantity) {
152 | const product = await Product.findById(id);
153 | product.Stock -= quantity;
154 | await product.save({ validateBeforeSave: false });
155 | }
156 |
157 | module.exports = orderController;
158 |
--------------------------------------------------------------------------------
/server/controllers/productController.js:
--------------------------------------------------------------------------------
1 | const Products = require("../models/productModel");
2 | const CustomErrorHandler = require("../services/CustomErrorHandler");
3 |
4 | const productController = {
5 | async createProduct(req, res, next) {
6 | try {
7 | const { name, description, Stock, price, isActive, images, ratings } =
8 | req.body;
9 |
10 | if (!images) {
11 | return next(CustomErrorHandler.badRequest("You must upload image."));
12 | }
13 |
14 | if (!description || !name) {
15 | return res
16 | .status(400)
17 | .json({ message: "You must enter description & name." });
18 | }
19 |
20 | if (!Stock) {
21 | return res.status(400).json({ message: "You must enter a stock." });
22 | }
23 |
24 | if (!price) {
25 | return res.status(400).json({ message: "You must enter a price." });
26 | }
27 |
28 | const product = new Products({
29 | name,
30 | description,
31 | Stock,
32 | price,
33 | isActive,
34 | images,
35 | ratings,
36 | });
37 |
38 | const savedProduct = await product.save();
39 |
40 | res.status(201).json({
41 | message: `Product has been added successfully!`,
42 | product: savedProduct,
43 | });
44 | } catch (error) {
45 | return next(error);
46 | }
47 | },
48 | async updateProducts(req, res, next) {
49 | try {
50 | const { name, description, Stock, price, isActive, images, ratings } =
51 | req.body;
52 | if (!images) {
53 | return next(CustomErrorHandler.badRequest("No image upload"));
54 | }
55 |
56 | await Products.findOneAndUpdate(
57 | { _id: req.params.id },
58 | {
59 | name,
60 | description,
61 | Stock,
62 | price,
63 | isActive,
64 | images,
65 | ratings,
66 | },
67 | { new: true }
68 | );
69 |
70 | res.status(200).json({ message: "Updated a Product" });
71 | } catch (err) {
72 | return next(err);
73 | }
74 | },
75 | async deleteProducts(req, res, next) {
76 | try {
77 | try {
78 | await Products.findByIdAndDelete(req.params.id);
79 | res.status(200).json({ message: "Deleted a Product" });
80 | } catch (err) {
81 | return next(err);
82 | }
83 | } catch (err) {
84 | return next(err);
85 | }
86 | },
87 | async getByIdProduct(req, res, next) {
88 | let product;
89 | try {
90 | product = await Products.findOne({ _id: req.params.id }).select(
91 | "-updatedAt -__v"
92 | );
93 | } catch (err) {
94 | return next(err);
95 | }
96 |
97 | res.status(200).json(product);
98 | },
99 | async getAllProducts(req, res, next) {
100 | let products;
101 | try {
102 | products = await Products.find()
103 | .select("-updatedAt -__v")
104 | .sort({ _id: -1 });
105 | } catch (err) {
106 | return next(err);
107 | }
108 |
109 | res.status(200).json(products);
110 | },
111 | };
112 |
113 | module.exports = productController;
114 |
--------------------------------------------------------------------------------
/server/controllers/uploadController.js:
--------------------------------------------------------------------------------
1 | const cloudinary = require("cloudinary");
2 | const fs = require("fs");
3 | const CustomErrorHandler = require("../services/CustomErrorHandler");
4 |
5 | cloudinary.config({
6 | cloud_name: process.env.CLOUD_NAME,
7 | api_key: process.env.CLOUD_API_KEY,
8 | api_secret: process.env.CLOUD_API_SECRET,
9 | });
10 |
11 | const uploadController = {
12 | async uploadAvatar(req, res, next) {
13 | console.log(process.env.CLOUD_API_KEY);
14 | try {
15 | const file = req.files.file;
16 |
17 | cloudinary.v2.uploader.upload(
18 | file.tempFilePath,
19 | {
20 | folder: "camera-shop",
21 | },
22 | async (err, result) => {
23 | try {
24 | if (err) throw err;
25 |
26 | removeTmp(file.tempFilePath);
27 |
28 | res.json({
29 | url: result.secure_url,
30 | public_id: result.public_id,
31 | message: "Image upload successfull!",
32 | });
33 | } catch (err) {
34 | return next(err);
35 | }
36 | }
37 | );
38 | } catch (err) {
39 | return next(err);
40 | }
41 | },
42 |
43 | async destroy(req, res, next) {
44 | try {
45 | const { public_id } = req.body;
46 | if (!public_id)
47 | return next(CustomErrorHandler.badRequest("No images Selected"));
48 |
49 | cloudinary.v2.uploader.destroy(public_id, async (err, result) => {
50 | try {
51 | if (err) throw err;
52 | console.log(result);
53 | res.json({ message: "Deleted Image" });
54 | } catch (err) {
55 | return next(err);
56 | }
57 | });
58 | } catch (err) {
59 | return next(err);
60 | }
61 | },
62 | };
63 |
64 | const removeTmp = (path) => {
65 | fs.unlink(path, (err) => {
66 | if (err) throw err;
67 | });
68 | };
69 | module.exports = uploadController;
70 |
--------------------------------------------------------------------------------
/server/middlewares/auth.js:
--------------------------------------------------------------------------------
1 | const Users = require("../models/userModels");
2 | const jwt = require("jsonwebtoken");
3 | const CustomErrorHandler = require("../services/CustomErrorHandler");
4 |
5 | const auth = async (req, res, next) => {
6 | try {
7 | const token = req.header("Authorization");
8 |
9 | if (!token) {
10 | return next(CustomErrorHandler.unAuthorized());
11 | }
12 |
13 | const decoded = jwt.verify(token, `${process.env.ACCESS_TOKEN_SECRET}`);
14 | if (!decoded) {
15 | return next(CustomErrorHandler.unAuthorized());
16 | }
17 |
18 | const user = await Users.findOne({ _id: decoded.id }).select("-password");
19 |
20 | if (!user) {
21 | return next(CustomErrorHandler.badRequest("User does not exist."));
22 | }
23 |
24 | req.user = user;
25 |
26 | next();
27 | } catch (err) {
28 | return next(err);
29 | }
30 | };
31 |
32 | module.exports = auth;
33 |
--------------------------------------------------------------------------------
/server/middlewares/authAdmin.js:
--------------------------------------------------------------------------------
1 | const Users = require("../models/userModels");
2 | const CustomErrorHandler = require("../services/CustomErrorHandler");
3 | const authAdmin = async (req, res, next) => {
4 | try {
5 | const user = await Users.findOne({ _id: req.user.id });
6 | if (user.role === 1) {
7 | next();
8 | } else {
9 | return next(
10 | CustomErrorHandler.unAuthorized("Admin resources access denied.")
11 | );
12 | }
13 | } catch (err) {
14 | return next(err);
15 | }
16 | };
17 |
18 | module.exports = authAdmin;
19 |
--------------------------------------------------------------------------------
/server/middlewares/errorHandler.js:
--------------------------------------------------------------------------------
1 | const Joi = require("joi");
2 | const CustomErrorHandler = require("../services/CustomErrorHandler");
3 |
4 | const { ValidationError } = Joi;
5 |
6 | // error handeling middleware
7 | const errorHandler = (err, req, res, next) => {
8 | let statusCode = 500;
9 |
10 | let data = {
11 | ...err,
12 | message: "Internal server error",
13 | ...(process.env.DEBUG_MODE === "true" && { originalError: err.message }),
14 | };
15 |
16 | if (err.code === 11000) {
17 | statusCode = 400;
18 | data = {
19 | message: err.message,
20 | };
21 | }
22 | // from joi
23 | if (err instanceof ValidationError) {
24 | // 422 mean validation error code
25 | statusCode = 422;
26 | data = {
27 | message: err.message,
28 | };
29 | }
30 |
31 | // from custom err handeler
32 | if (err instanceof CustomErrorHandler) {
33 | statusCode = err.status;
34 | data = {
35 | message: err.message,
36 | };
37 | }
38 |
39 | return res.status(statusCode).json(data);
40 | };
41 |
42 | module.exports = errorHandler;
43 |
--------------------------------------------------------------------------------
/server/middlewares/uploadImage.js:
--------------------------------------------------------------------------------
1 | const fs = require("node:fs/promises");
2 | const CustomErrorHandler = require("../services/CustomErrorHandler");
3 |
4 | module.exports = async function (req, res, next) {
5 | // console.log(req.files);
6 | try {
7 | if (!req.files || Object.keys(req.files).length === 0)
8 | return next(CustomErrorHandler.badRequest("No files were uploaded."));
9 |
10 | const file = req.files.file;
11 |
12 | if (file.size > 1024 * 1024) {
13 | removeTmp(file.tempFilePath);
14 | return next(CustomErrorHandler.badRequest("Size too large."));
15 | } // 1mb max
16 |
17 | if (file.mimetype !== "image/jpeg" && file.mimetype !== "image/png") {
18 | removeTmp(file.tempFilePath);
19 |
20 | return next(CustomErrorHandler.badRequest("File format is incorrect."));
21 | }
22 |
23 | next();
24 | } catch (err) {
25 | return next(err);
26 | }
27 | };
28 |
29 | const removeTmp = (path) => {
30 | fs.unlink(path, (err) => {
31 | if (err) throw err;
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/server/models/orderModels.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const orderSchema = new mongoose.Schema({
4 | shippingInfo: {
5 | type: Object,
6 | },
7 | orderItems: [
8 | {
9 | name: {
10 | type: String,
11 | required: true,
12 | },
13 | price: {
14 | type: Number,
15 | required: true,
16 | },
17 | quantity: {
18 | type: Number,
19 | required: true,
20 | },
21 | image: {
22 | type: String,
23 | required: true,
24 | },
25 | product: {
26 | type: mongoose.Schema.Types.ObjectId,
27 | ref: "Product",
28 | required: true,
29 | },
30 | },
31 | ],
32 | id: {
33 | type: String,
34 | },
35 | email: {
36 | type: String,
37 | required: true,
38 | },
39 | user: {
40 | // create the relation User model means users collection from the databse
41 | type: mongoose.Schema.Types.ObjectId,
42 | ref: "User",
43 | required: true,
44 | },
45 | paymentInfo: {
46 | id: {
47 | type: String,
48 | required: true,
49 | },
50 | status: {
51 | type: String,
52 | required: true,
53 | },
54 | },
55 | paidAt: {
56 | type: Date,
57 | required: true,
58 | },
59 | itemsPrice: {
60 | type: Number,
61 | required: true,
62 | default: 0,
63 | },
64 | taxPrice: {
65 | type: Number,
66 | required: true,
67 | default: 0,
68 | },
69 | shippingPrice: {
70 | type: Number,
71 | required: true,
72 | default: 0,
73 | },
74 | totalPrice: {
75 | type: Number,
76 | required: true,
77 | default: 0,
78 | },
79 | orderStatus: {
80 | type: String,
81 | required: true,
82 | default: "Processing",
83 | },
84 | deliveredAt: Date,
85 | createdAt: {
86 | type: Date,
87 | default: Date.now,
88 | },
89 | });
90 |
91 | module.exports = mongoose.model("Order", orderSchema);
92 |
--------------------------------------------------------------------------------
/server/models/productModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const productSchema = new Schema(
6 | {
7 | name: {
8 | type: String,
9 | trim: true,
10 | required: true,
11 | },
12 |
13 | images: {
14 | type: Object,
15 | required: true,
16 | },
17 |
18 | description: {
19 | type: String,
20 | trim: true,
21 | required: true,
22 | },
23 | Stock: {
24 | type: Number,
25 | required: true,
26 | default: 1,
27 | },
28 | ratings: {
29 | type: Number,
30 | default: 1,
31 | },
32 | price: {
33 | type: Number,
34 | required: true,
35 | },
36 | isActive: {
37 | type: Boolean,
38 | default: true,
39 | },
40 | },
41 | { timestamps: true }
42 | );
43 |
44 | module.exports = mongoose.model("Product", productSchema, "products");
45 |
--------------------------------------------------------------------------------
/server/models/userModels.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("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 | index: true,
14 | unique: true,
15 | sparse: true,
16 | trim: true,
17 | },
18 |
19 | password: {
20 | type: String,
21 | required: true,
22 | },
23 |
24 | avatar: {
25 | type: String,
26 | default:
27 | "https://res.cloudinary.com/devatchannel/image/upload/v1602752402/avatar/avatar_cugq40.png",
28 | },
29 | role: {
30 | type: Number,
31 | default: 0, // 0= user, 1 = admin
32 | },
33 | type: {
34 | type: String,
35 | default: "register", // login
36 | },
37 | rf_token: { type: String, select: false },
38 | },
39 | {
40 | timestamps: true,
41 | }
42 | );
43 |
44 | module.exports = mongoose.model("User", userSchema);
45 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "node server.js",
8 | "server": "nodemon server.js",
9 | "client": "cd client && npm run start",
10 | "server-install": "npm install",
11 | "client-install": "cd client && npm install",
12 | "install-all": "concurrently \"npm run server-install\" \"npm run client-install\"",
13 | "dev": "concurrently \"npm run server\" \"npm run client\"",
14 | "heroku-postbuild": "cd client && npm install && npm run build"
15 | },
16 | "keywords": [],
17 | "author": "",
18 | "license": "ISC",
19 | "dependencies": {
20 | "bcrypt": "^5.0.1",
21 | "cloudinary": "^1.29.0",
22 | "cookie-parser": "^1.4.6",
23 | "cors": "^2.8.5",
24 | "dotenv": "^16.0.0",
25 | "express": "^4.17.3",
26 | "express-fileupload": "^1.3.1",
27 | "google-auth-library": "^7.14.1",
28 | "joi": "^17.6.0",
29 | "jsonwebtoken": "^8.5.1",
30 | "mongoose": "^6.2.10",
31 | "nodemailer": "^6.7.3",
32 | "stripe": "^8.217.0"
33 | },
34 | "devDependencies": {
35 | "concurrently": "^7.1.0",
36 | "nodemon": "^2.0.15"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/routes/authRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const authCtrl = require("../controllers/authController");
3 | const auth = require("../middlewares/auth");
4 |
5 | const router = express.Router();
6 |
7 | // register route
8 | router.post("/register", authCtrl.register);
9 |
10 | // verify and active account route
11 | router.post("/active", authCtrl.activeAccount);
12 |
13 | // login route
14 |
15 | router.post("/login", authCtrl.login);
16 |
17 | // logout route must be authenticated
18 |
19 | router.get("/logout", auth, authCtrl.logout);
20 |
21 | // refresh token route
22 |
23 | router.get("/refresh_token", authCtrl.refreshToken);
24 |
25 | // google login route
26 | router.post("/google_login", authCtrl.googleLogin);
27 |
28 | module.exports = router;
29 |
--------------------------------------------------------------------------------
/server/routes/orderRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const orderController = require("../controllers/orderController");
3 | const auth = require("../middlewares/auth");
4 | const authAdmin = require("../middlewares/authAdmin");
5 | const router = express.Router();
6 |
7 | router.post("/order/new", auth, orderController.newOrder);
8 | router.get("/orders/me", auth, orderController.myOrders);
9 | router.get("/orders/:id", auth, orderController.getOrderById);
10 |
11 | // admin routes
12 | router.get("/admin/orders", [auth, authAdmin], orderController.getAllOrders);
13 |
14 | router
15 | .route("/admin/order/:id")
16 | .put([auth, authAdmin], orderController.updateOrder)
17 | .delete([auth, authAdmin], orderController.deleteOrder);
18 |
19 | module.exports = router;
20 |
--------------------------------------------------------------------------------
/server/routes/productRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const productController = require("../controllers/productController");
3 | const auth = require("../middlewares/auth");
4 | const authAdmin = require("../middlewares/authAdmin");
5 |
6 | const router = express.Router();
7 | // must be authenticated and admin
8 |
9 | router
10 | .route("/products")
11 | .get(productController.getAllProducts)
12 |
13 | .post([auth, authAdmin], productController.createProduct);
14 |
15 | router
16 | .route("/products/:id")
17 | .get(productController.getByIdProduct)
18 | .delete([auth, authAdmin], productController.deleteProducts)
19 | .put([auth, authAdmin], productController.updateProducts);
20 |
21 | module.exports = router;
22 |
--------------------------------------------------------------------------------
/server/routes/uploadRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const uploadController = require("../controllers/uploadController");
3 | const auth = require("../middlewares/auth");
4 | const uploadImage = require("../middlewares/uploadImage");
5 |
6 | const router = express.Router();
7 | // upload image middleware for user
8 | router.post(
9 | "/upload_image",
10 | [auth, uploadImage],
11 | uploadController.uploadAvatar
12 | );
13 |
14 | // delete image
15 | router.post("/destroy", auth, uploadController.destroy);
16 |
17 | module.exports = router;
18 |
--------------------------------------------------------------------------------
/server/routes/userRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const authCtrl = require("../controllers/authController");
3 | const auth = require("../middlewares/auth");
4 | const authAdmin = require("../middlewares/authAdmin");
5 |
6 | const router = express.Router();
7 |
8 | // update user
9 | router.patch("/user/update", auth, authCtrl.updateUser);
10 |
11 | // reset password
12 | router.post("/user/reset", auth, authCtrl.resetPassword);
13 |
14 | // forgot password route
15 |
16 | router.post("/user/forgot_password", authCtrl.forgotPassword);
17 |
18 | // // all user information route only admin can get
19 | router.get("/admin/users", [auth, authAdmin], authCtrl.getAllUser);
20 |
21 | // // update user role only admin
22 | router.patch(
23 | "/admin/update_role/:id",
24 | auth,
25 | authAdmin,
26 | authCtrl.updateUsersRole
27 | );
28 |
29 | // // delete user only can admin
30 | router.delete("/admin/delete/:id", [auth, authAdmin], authCtrl.deleteUser);
31 |
32 | // get user stats per month route
33 | router.get("/admin/stats", [auth, authAdmin], authCtrl.statsUserPerMonth);
34 |
35 | module.exports = router;
36 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | const express = require("express");
3 | const cors = require("cors");
4 | const cookieParser = require("cookie-parser");
5 | const fileUpload = require("express-fileupload");
6 | const path = require("path");
7 | const connectDb = require("./config/database");
8 | const errorHandler = require("./middlewares/errorHandler");
9 | const authRoutes = require("./routes/authRoutes");
10 | const productRoutes = require("./routes/productRoutes");
11 | const userRoutes = require("./routes/userRoutes");
12 | const uploadRoutes = require("./routes/uploadRoutes");
13 | const orderRoutes = require("./routes/orderRoutes");
14 |
15 | const app = express();
16 |
17 | // middleware
18 | app.use(express.json());
19 | app.use(express.urlencoded({ extended: true }));
20 | app.use(cors());
21 | app.use(cookieParser());
22 | app.use(
23 | fileUpload({
24 | useTempFiles: true,
25 | })
26 | );
27 |
28 | // auth routes
29 | app.use("/api", authRoutes);
30 |
31 | // // user routes
32 | app.use("/api", userRoutes);
33 |
34 | // upload routes
35 | app.use("/api", uploadRoutes);
36 | // product routes
37 | app.use("/api", productRoutes);
38 |
39 | // order routes
40 |
41 | app.use("/api", orderRoutes);
42 |
43 | // Database connection
44 | connectDb();
45 |
46 | // delpoy code
47 |
48 | if (process.env.NODE_ENV === "production") {
49 | app.use(express.static("client/build"));
50 | app.get("*", (req, res) => {
51 | res.sendFile(path.resolve(__dirname, "client", "build", "index.html"));
52 | });
53 | }
54 |
55 | // declareing the port here
56 | const PORT = process.env.PORT || 5000;
57 |
58 | // custom error handler
59 | app.use(errorHandler);
60 |
61 | app.listen(PORT, () => {
62 | console.log(`Listening on port ${PORT}`);
63 | });
64 |
--------------------------------------------------------------------------------
/server/services/CustomErrorHandler.js:
--------------------------------------------------------------------------------
1 | // here extends the inbuild javascript class Error thake
2 | class CustomErrorHandler extends Error {
3 | constructor(status, message) {
4 | super();
5 | this.status = status;
6 | this.message = message;
7 | }
8 |
9 | static alreadyExist(message) {
10 | return new CustomErrorHandler(409, message);
11 | }
12 | static badRequest(message) {
13 | return new CustomErrorHandler(400, message);
14 | }
15 |
16 | static unAuthorized(message = "unAuthorized") {
17 | return new CustomErrorHandler(401, message);
18 | }
19 |
20 | static notFound(message = "404 not found") {
21 | return new CustomErrorHandler(404, message);
22 | }
23 |
24 | static serverError(
25 | message = "Your request could not be processed. Please try again."
26 | ) {
27 | return new CustomErrorHandler(500, message);
28 | }
29 | }
30 |
31 | module.exports = CustomErrorHandler;
32 |
--------------------------------------------------------------------------------