├── server ├── README.md ├── .gitignore ├── client │ ├── public │ │ ├── robots.txt │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── assets │ │ │ ├── admin.png │ │ │ ├── user.png │ │ │ ├── full-page.png │ │ │ ├── images │ │ │ │ ├── logo.png │ │ │ │ ├── upload.png │ │ │ │ ├── Capture.png │ │ │ │ ├── upload-1.png │ │ │ │ ├── banner-1.webp │ │ │ │ ├── banner-2.webp │ │ │ │ ├── banner-3.webp │ │ │ │ ├── camera-1.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 │ │ │ │ ├── camera-15webp.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 │ │ │ │ └── protected-area.webp │ │ │ └── index.js │ │ ├── redux │ │ │ ├── constants │ │ │ │ ├── cartConstants.js │ │ │ │ ├── productConstants.js │ │ │ │ ├── orderConstants.js │ │ │ │ └── userConstants.js │ │ │ ├── store.js │ │ │ ├── reducers │ │ │ │ ├── index.js │ │ │ │ ├── cartReducer.js │ │ │ │ ├── userReducer.js │ │ │ │ ├── productReducer.js │ │ │ │ └── orderReducer.js │ │ │ └── actions │ │ │ │ └── cartActions.js │ │ ├── styles │ │ │ ├── styles.scss │ │ │ └── core │ │ │ │ ├── utils │ │ │ │ ├── _index.scss │ │ │ │ ├── _fonts.scss │ │ │ │ ├── _functions.scss │ │ │ │ ├── _variables.scss │ │ │ │ └── _mixins.scss │ │ │ │ ├── _chart.scss │ │ │ │ ├── _not-found.scss │ │ │ │ ├── _my-orders.scss │ │ │ │ ├── _activation-email.scss │ │ │ │ ├── _checkout.scss │ │ │ │ ├── _company-area.scss │ │ │ │ ├── _process-order.scss │ │ │ │ ├── _checkout-payment.scss │ │ │ │ ├── _loader.scss │ │ │ │ ├── _mobile-menu.scss │ │ │ │ ├── _home-admin.scss │ │ │ │ ├── core.scss │ │ │ │ ├── _section-title.scss │ │ │ │ ├── _news-letter.scss │ │ │ │ ├── _protected-area.scss │ │ │ │ ├── _order-details.scss │ │ │ │ ├── _discount-area.scss │ │ │ │ ├── _user-list-item.scss │ │ │ │ ├── _product-detail.scss │ │ │ │ ├── _products.scss │ │ │ │ ├── _banner.scss │ │ │ │ ├── _home-banner.scss │ │ │ │ ├── _all-products.scss │ │ │ │ ├── _footer.scss │ │ │ │ ├── _contact.scss │ │ │ │ ├── _add-product.scss │ │ │ │ ├── _register.scss │ │ │ │ ├── _header.scss │ │ │ │ ├── _cart.scss │ │ │ │ ├── _reset.scss │ │ │ │ └── _profile.scss │ │ ├── pages │ │ │ ├── NotFound │ │ │ │ └── NotFound.jsx │ │ │ ├── Manager │ │ │ │ ├── AdminDashboard │ │ │ │ │ ├── AdminDashboard.jsx │ │ │ │ │ ├── UserList │ │ │ │ │ │ └── UserList.jsx │ │ │ │ │ ├── HomeAdmin │ │ │ │ │ │ └── HomeAdmin.jsx │ │ │ │ │ ├── AllProducts │ │ │ │ │ │ └── AllProducts.jsx │ │ │ │ │ └── EditUser │ │ │ │ │ │ └── EditUser.jsx │ │ │ │ └── UserDashboard │ │ │ │ │ ├── UserDashboard.jsx │ │ │ │ │ ├── MyOrders │ │ │ │ │ └── Myorders.jsx │ │ │ │ │ ├── OrderDetails │ │ │ │ │ └── OrderDetails.jsx │ │ │ │ │ └── Sidebar │ │ │ │ │ └── Sidebar.jsx │ │ │ ├── Home │ │ │ │ └── Home.jsx │ │ │ ├── PrivateRoute │ │ │ │ └── PrivateRoute.jsx │ │ │ ├── Checkout │ │ │ │ └── Checkout.jsx │ │ │ ├── Shop │ │ │ │ ├── Shop.jsx │ │ │ │ └── SingleItem │ │ │ │ │ └── SingleItem.jsx │ │ │ ├── Cart │ │ │ │ └── Cart.jsx │ │ │ ├── ActivationEmail │ │ │ │ └── ActivationEmail.js │ │ │ ├── CartList │ │ │ │ └── CartList.jsx │ │ │ ├── index.js │ │ │ ├── CheckoutPayment │ │ │ │ └── CheckoutPayment.jsx │ │ │ ├── ProductDetail │ │ │ │ └── ProductDetail.jsx │ │ │ ├── Contact │ │ │ │ └── Contact.jsx │ │ │ ├── ForgotPassword │ │ │ │ └── ForgotPassword.jsx │ │ │ └── Login │ │ │ │ └── Login.jsx │ │ ├── components │ │ │ ├── Loading │ │ │ │ └── Loading.jsx │ │ │ ├── SectionTitle │ │ │ │ └── SectionTitle.jsx │ │ │ ├── CartSummary │ │ │ │ └── CartSummary.jsx │ │ │ ├── Loader │ │ │ │ └── Loader.jsx │ │ │ ├── CompanyArea │ │ │ │ └── CompanyArea.jsx │ │ │ ├── ProductRating │ │ │ │ └── ProductRating.jsx │ │ │ ├── Chart │ │ │ │ └── Chart.jsx │ │ │ ├── NewsLetter │ │ │ │ └── NewsLetter.jsx │ │ │ ├── Banner │ │ │ │ └── Banner.jsx │ │ │ ├── ProtectedArea │ │ │ │ └── ProtectedArea.jsx │ │ │ ├── DiscountArea │ │ │ │ └── DiscountArea.jsx │ │ │ ├── index.js │ │ │ ├── HomeBanner │ │ │ │ └── HomeBanner.jsx │ │ │ ├── MobileMenu │ │ │ │ └── MobileMenu.jsx │ │ │ └── Products │ │ │ │ ├── Products.jsx │ │ │ │ └── SingleProduct │ │ │ │ └── SingleProduct.jsx │ │ ├── index.js │ │ ├── utils │ │ │ ├── checkTokenExp.js │ │ │ └── validation.js │ │ ├── fakeData.js │ │ └── App.js │ ├── .gitignore │ ├── package.json │ └── README.md ├── config │ ├── database.js │ ├── generateToken.js │ └── sendMail.js ├── routes │ ├── uploadRoutes.js │ ├── productRoutes.js │ ├── authRoutes.js │ ├── orderRoutes.js │ └── userRoutes.js ├── middlewares │ ├── authAdmin.js │ ├── auth.js │ ├── uploadImage.js │ └── errorHandler.js ├── models │ ├── productModel.js │ ├── userModels.js │ └── orderModels.js ├── services │ └── CustomErrorHandler.js ├── package.json ├── server.js └── controllers │ ├── uploadController.js │ ├── productController.js │ └── orderController.js └── README.md /server/README.md: -------------------------------------------------------------------------------- 1 | # mern-camera-website 2 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | .env -------------------------------------------------------------------------------- /server/client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/client/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/public/logo.png -------------------------------------------------------------------------------- /server/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/public/favicon.ico -------------------------------------------------------------------------------- /server/client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/public/logo192.png -------------------------------------------------------------------------------- /server/client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/public/logo512.png -------------------------------------------------------------------------------- /server/client/src/assets/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/admin.png -------------------------------------------------------------------------------- /server/client/src/assets/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/user.png -------------------------------------------------------------------------------- /server/client/src/assets/full-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/full-page.png -------------------------------------------------------------------------------- /server/client/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/logo.png -------------------------------------------------------------------------------- /server/client/src/assets/images/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/upload.png -------------------------------------------------------------------------------- /server/client/src/assets/images/Capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/Capture.png -------------------------------------------------------------------------------- /server/client/src/assets/images/upload-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/upload-1.png -------------------------------------------------------------------------------- /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/styles/styles.scss: -------------------------------------------------------------------------------- 1 | // Import Bootstrap source files 2 | @import "~bootstrap/scss/bootstrap"; 3 | 4 | @import "./core/core.scss"; 5 | -------------------------------------------------------------------------------- /server/client/src/assets/images/banner-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/banner-1.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/banner-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/banner-2.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/banner-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/banner-3.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/camera-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/camera-1.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/camera-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/camera-2.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/camera-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/camera-3.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/camera-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/camera-4.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/camera-5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/camera-5.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/camera-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/camera-6.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/camera-7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/camera-7.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/camera-8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/camera-8.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/company-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/company-1.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/company-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/company-2.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/company-3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/company-3.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/company-4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/company-4.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/company-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/company-5.png -------------------------------------------------------------------------------- /server/client/src/assets/images/company-6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/company-6.webp -------------------------------------------------------------------------------- /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/assets/images/camera-15webp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/camera-15webp.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/discount-area.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/server/client/src/assets/images/home-banner-6.webp -------------------------------------------------------------------------------- /server/client/src/assets/images/protected-area.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdhossin/mern-camera-website/HEAD/server/client/src/assets/images/protected-area.webp -------------------------------------------------------------------------------- /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/pages/NotFound/NotFound.jsx: -------------------------------------------------------------------------------- 1 | const NotFound = () => ( 2 |
3 |

404, Page Not Found!

4 |
5 | ); 6 | 7 | export default NotFound; 8 | -------------------------------------------------------------------------------- /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/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/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/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/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/_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/.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/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/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/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/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/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/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/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/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/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/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/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/src/components/Loader/Loader.jsx: -------------------------------------------------------------------------------- 1 | const Loader = (props) => { 2 | const { inline, backdrop } = props; 3 | 4 | return ( 5 |
10 |
15 |
16 | ); 17 | }; 18 | 19 | Loader.defaultProps = { 20 | inline: false, 21 | backdrop: false, 22 | }; 23 | 24 | export default Loader; 25 | -------------------------------------------------------------------------------- /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/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/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 | company 14 | 15 | ))} 16 | 17 |
18 | ); 19 | 20 | export default CompanyArea; 21 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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 | First slide 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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/_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/_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/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/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/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/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/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/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/_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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | ![plot](./server/client/src/assets/full-page.png) 25 | 26 | # Admin Dashboard interface - 27 | 28 | ![plot](./server/client/src/assets/admin.png) 29 | 30 | # User Dashboard interface - 31 | 32 | ![plot](./server/client/src/assets/user.png) 33 | -------------------------------------------------------------------------------- /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/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 |