├── front
├── .env
├── src
│ ├── assets
│ │ ├── star.png
│ │ ├── emptyBasket.jpg
│ │ └── shopping-basket.png
│ ├── utils
│ │ └── consts.js
│ ├── http
│ │ ├── index.js
│ │ ├── userApi.js
│ │ ├── ordersAPI.js
│ │ └── deviceAPI.js
│ ├── components
│ │ ├── DeviceList.js
│ │ ├── NavBar
│ │ │ ├── preesent-components
│ │ │ │ ├── falseAuth.js
│ │ │ │ └── trueAuth.js
│ │ │ ├── NavBar.js
│ │ │ └── BasketNavBar.js
│ │ ├── ratingStars.js
│ │ ├── BrandBar.js
│ │ ├── AppRouter.js
│ │ ├── Pages.js
│ │ ├── TypeBar.js
│ │ ├── deviceItem.js
│ │ ├── modals
│ │ │ ├── CreateType.js
│ │ │ ├── CreateBrand.js
│ │ │ ├── DeleteBrandOrType.js
│ │ │ └── CreateDevice.js
│ │ ├── oneItemInBasket.js
│ │ └── itemOneorderInAdmin.js
│ ├── index.js
│ ├── store
│ │ ├── UserStore.js
│ │ ├── DeviceStore.js
│ │ └── BasketStore.js
│ ├── pages
│ │ ├── BasketCard.js
│ │ ├── Ordering.js
│ │ ├── Shop.js
│ │ ├── OneOrder.js
│ │ ├── Auth.js
│ │ ├── DevicePage.js
│ │ ├── Orders.js
│ │ ├── Admin.js
│ │ └── DevicePageEdit.js
│ ├── routes.js
│ └── App.js
├── public
│ └── index.html
└── package.json
├── .gitignore
├── DB-models-v1.jpg
├── DB-models-v2.jpg
└── backend
├── functions
└── getUserFromToken.js
├── db
└── db.js
├── middleware
├── ErrorHandlingMiddleware.js
├── authMiddleware.js
├── checkDeleteDeviceFromBasket.js
├── checkRoleMiddleware.js
└── checkAddRatingMiddleware.js
├── routes
├── typeRouter.js
├── userRouter.js
├── brandRouter.js
├── ratingRouter.js
├── basketRouter.js
├── ordersRouter.js
├── deviceRouter.js
└── index.js
├── error
└── apiError.js
├── package.json
├── controllers
├── typeController.js
├── brandController.js
├── userController.js
├── ratingController.js
├── basketController.js
├── ordersController.js
└── deviceController.js
├── index.js
└── models
└── models.js
/front/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_API_URL='http://localhost:5000/'
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | backend/.env
4 | backend/static/*
--------------------------------------------------------------------------------
/DB-models-v1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shkulipa/online-store/HEAD/DB-models-v1.jpg
--------------------------------------------------------------------------------
/DB-models-v2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shkulipa/online-store/HEAD/DB-models-v2.jpg
--------------------------------------------------------------------------------
/front/src/assets/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shkulipa/online-store/HEAD/front/src/assets/star.png
--------------------------------------------------------------------------------
/front/src/assets/emptyBasket.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shkulipa/online-store/HEAD/front/src/assets/emptyBasket.jpg
--------------------------------------------------------------------------------
/front/src/assets/shopping-basket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shkulipa/online-store/HEAD/front/src/assets/shopping-basket.png
--------------------------------------------------------------------------------
/backend/functions/getUserFromToken.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 |
3 | function getUserFromToken(headersAuth) {
4 | const token = headersAuth.split(' ')[1];
5 | return jwt.verify(token, process.env.SECRET_KEY);
6 | }
7 |
8 | module.exports = getUserFromToken;
9 |
10 |
--------------------------------------------------------------------------------
/backend/db/db.js:
--------------------------------------------------------------------------------
1 | const {Sequelize} = require('sequelize');
2 |
3 | module.exports = new Sequelize(
4 | process.env.DATABASE_DB,
5 | process.env.USER_DB,
6 | process.env.PASSWORD_DB,
7 | {
8 | dialect: 'postgres',
9 | host: process.env.HOST_DB,
10 | port: process.env.PORT_DB,
11 | }
12 | );
--------------------------------------------------------------------------------
/backend/middleware/ErrorHandlingMiddleware.js:
--------------------------------------------------------------------------------
1 | const ApiError = require('./../error/apiError');
2 |
3 | module.exports = function (err, req, res, next) {
4 | if(err instanceof ApiError) {
5 | return res.status(err.status).json({message: err.message})
6 | }
7 |
8 | return res.status(500).json({message: "Unexpected error"})
9 | }
10 |
--------------------------------------------------------------------------------
/front/src/utils/consts.js:
--------------------------------------------------------------------------------
1 | export const ADMIN_ROUTE = '/admin';
2 | export const LOGIN_ROUTE = '/login';
3 | export const REGISTRATION_ROUTE = '/auth';
4 | export const SHOP_ROUTE = '/';
5 | export const BASKET_ROUTE = '/basket';
6 | export const DEVICE_ROUTE = '/device';
7 | export const ORDERS_ROUTE = '/orders';
8 | export const DEVICE_EDIT_ROUTE = '/device/edit';
9 | export const ORDERING_ROUTE = '/checkout';
10 |
11 |
12 |
--------------------------------------------------------------------------------
/backend/routes/typeRouter.js:
--------------------------------------------------------------------------------
1 | const Router = require('express');
2 | const router = new Router();
3 | const typeController = require('./../controllers/typeController');
4 | const checkRole = require('../middleware/checkRoleMiddleware');
5 |
6 | router
7 | .post('/', checkRole("ADMIN"), typeController.create)
8 | .get('/', typeController.getAll)
9 | .delete('/:id', checkRole("ADMIN"), typeController.delete);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/backend/routes/userRouter.js:
--------------------------------------------------------------------------------
1 | const Router = require('express');
2 | const router = new Router();
3 | const userController = require('./../controllers/userController');
4 | const authMiddleware = require('./../middleware/authMiddleware');
5 |
6 | router
7 | .post('/registration', userController.registration)
8 | .post('/login', userController.login)
9 | .get('/auth', authMiddleware, userController.check);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/backend/routes/brandRouter.js:
--------------------------------------------------------------------------------
1 | const Router = require('express');
2 | const router = new Router();
3 | const brandController = require('./../controllers/brandController');
4 | const checkRole = require('../middleware/checkRoleMiddleware');
5 |
6 | router
7 | .post('/', checkRole("ADMIN"), brandController.create)
8 | .get('/', brandController.getAll)
9 | .delete('/:id', checkRole("ADMIN"), brandController.delete);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/backend/error/apiError.js:
--------------------------------------------------------------------------------
1 | class ApiError extends Error {
2 | constructor(status, message) {
3 | super();
4 | this.status = status;
5 | this.message = message;
6 | }
7 |
8 | static badRequest(message) {
9 | return new ApiError(404, message);
10 | }
11 |
12 | static internal(message) {
13 | return new ApiError(500, message);
14 | }
15 |
16 | static forbidden(message) {
17 | return new ApiError(403, message);
18 | }
19 | }
20 |
21 | module.exports = ApiError;
22 |
--------------------------------------------------------------------------------
/backend/routes/ratingRouter.js:
--------------------------------------------------------------------------------
1 | const Router = require('express');
2 | const router = new Router();
3 | const authMiddleware = require('./../middleware/authMiddleware');
4 | const checkAddRatingMiddleware = require('./../middleware/checkAddRatingMiddleware');
5 | const ratingController = require('./../controllers/ratingController')
6 |
7 | router
8 | .post('/', authMiddleware, checkAddRatingMiddleware, ratingController.addRating)
9 | .post('/check-rating', authMiddleware, ratingController.checkRating);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/front/src/http/index.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const $host = axios.create({
4 | baseURL: process.env.REACT_APP_API_URL,
5 | responseType: "json",
6 | })
7 |
8 | const $authHost = axios.create({
9 | baseURL: process.env.REACT_APP_API_URL,
10 | responseType: "json",
11 | })
12 |
13 | const authInterceptor = config => {
14 | config.headers.authorization = `Bearer ${localStorage.getItem('token')}`
15 | return config
16 | }
17 |
18 | $authHost.interceptors.request.use(authInterceptor)
19 |
20 | export {
21 | $host,
22 | $authHost
23 | }
24 |
--------------------------------------------------------------------------------
/backend/middleware/authMiddleware.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 |
3 | module.exports = function (req, res, next) {
4 | if(req.method === "OPTIONS") {
5 | next()
6 | }
7 | try {
8 | const token = req.headers.authorization.split(' ')[1];
9 | if(!token) {
10 | return res.status(401).json({message: "Not authorization"});
11 | }
12 | req.user = jwt.verify(token, process.env.SECRET_KEY);
13 | next();
14 | } catch (e) {
15 | res.status(401).json({message: "Not authorization"});
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/front/src/components/DeviceList.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {observer} from "mobx-react-lite";
3 | import {Context} from "../index";
4 | import {Row} from "react-bootstrap";
5 | import DeviceItem from "./deviceItem";
6 |
7 | const DeviceList = observer(() => {
8 | const {device} = useContext(Context);
9 |
10 | return (
11 |
12 | {device.devices.map(device =>
13 |
14 | )}
15 |
16 | );
17 | });
18 |
19 | export default DeviceList;
20 |
--------------------------------------------------------------------------------
/backend/routes/basketRouter.js:
--------------------------------------------------------------------------------
1 | const Router = require('express');
2 | const router = new Router();
3 | const BasketController = require('./../controllers/basketController');
4 | const authMiddleware = require('./../middleware/authMiddleware');
5 | const checkDeleteDeviceFromBasket = require('./../middleware/checkDeleteDeviceFromBasket');
6 |
7 | router
8 | .post('/', authMiddleware, BasketController.addDevice)
9 | .get('/', authMiddleware, BasketController.getDevices)
10 | .delete('/:id', authMiddleware, checkDeleteDeviceFromBasket, BasketController.deleteDevice);
11 |
12 | module.exports = router;
13 |
--------------------------------------------------------------------------------
/backend/routes/ordersRouter.js:
--------------------------------------------------------------------------------
1 | const Router = require('express');
2 | const router = new Router();
3 |
4 | const ordersController = require('./../controllers/ordersController');
5 | const checkRole = require('./../middleware/checkRoleMiddleware');
6 |
7 | router
8 | .post('/', ordersController.create)
9 | .get('/', checkRole("ADMIN"), ordersController.getAll)
10 | .get('/:id', checkRole("ADMIN"), ordersController.getOne)
11 | .put('/', checkRole("ADMIN"), ordersController.updateOrder)
12 | .delete('/', checkRole("ADMIN"), ordersController.deleteOrder);
13 |
14 |
15 | module.exports = router;
16 |
--------------------------------------------------------------------------------
/backend/routes/deviceRouter.js:
--------------------------------------------------------------------------------
1 | const Router = require('express');
2 | const router = new Router();
3 | const deviceController = require('./../controllers/deviceController');
4 | const checkRole = require('../middleware/checkRoleMiddleware');
5 |
6 | router
7 | .post('/', deviceController.create)
8 | .get('/', deviceController.getAll)
9 | .get('/search', deviceController.getSearchAllDeviceByName)
10 | .get('/:id', deviceController.getOne)
11 | .delete('/:id', checkRole("ADMIN"), deviceController.delete)
12 | .put('/:id', checkRole("ADMIN"), deviceController.update)
13 |
14 | module.exports = router;
15 |
--------------------------------------------------------------------------------
/front/src/components/NavBar/preesent-components/falseAuth.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import {Button, Nav} from "react-bootstrap";
4 | import {LOGIN_ROUTE} from "../../../utils/consts";
5 | import {NavLink} from "react-router-dom";
6 | import BasketNavBar from "../BasketNavBar";
7 |
8 | const FalseAuth = () => {
9 | return (
10 |
16 | );
17 | };
18 |
19 | export default FalseAuth;
20 |
--------------------------------------------------------------------------------
/front/src/index.js:
--------------------------------------------------------------------------------
1 | import React, {createContext} from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | import UserStore from "./store/UserStore";
6 | import DeviceStore from "./store/DeviceStore";
7 | import BasketStoreStore from "./store/BasketStore";
8 |
9 | export const Context = createContext(null);
10 |
11 | ReactDOM.render(
12 |
19 |
20 | ,
21 | document.getElementById('root')
22 | );
23 |
--------------------------------------------------------------------------------
/backend/middleware/checkDeleteDeviceFromBasket.js:
--------------------------------------------------------------------------------
1 | const {Basket, BasketDevice} = require('./../models/models');
2 | const jwt = require('jsonwebtoken');
3 |
4 | module.exports = async function (req, res, next) {
5 | try {
6 | const {id} = req.params;
7 | const user = req.user;
8 | const userBasket = await Basket.findOne({where: {userId: user.id}});
9 | const deviceItem = await BasketDevice.findOne({where: {basketId: userBasket.id, deviceId: id}});
10 |
11 | if(deviceItem) {
12 | return next();
13 | }
14 | return res.json("Device didn't find in basket of user");
15 | } catch (e) {
16 | res.json(e);
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/front/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
16 | React App
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "nodemon index.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcrypt": "^5.0.1",
15 | "cors": "^2.8.5",
16 | "dotenv": "^10.0.0",
17 | "express": "^4.17.1",
18 | "express-fileupload": "^1.2.1",
19 | "jsonwebtoken": "^8.5.1",
20 | "pg": "^8.6.0",
21 | "pg-hstore": "^2.3.4",
22 | "sequelize": "^6.6.2",
23 | "uuid": "^8.3.2"
24 | },
25 | "devDependencies": {
26 | "nodemon": "^2.0.7"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/backend/routes/index.js:
--------------------------------------------------------------------------------
1 | const Router = require('express');
2 | const router = new Router();
3 | const deviceRouter = require('./deviceRouter');
4 | const brandRouter = require('./brandRouter');
5 | const typeRouter = require('./typeRouter');
6 | const userRouter = require('./userRouter');
7 | const basketRouter = require('./basketRouter');
8 | const ratingRouter = require('./ratingRouter');
9 | const ordersRouter = require('./ordersRouter');
10 |
11 | router.use('/user', userRouter)
12 | router.use('/type', typeRouter)
13 | router.use('/brand', brandRouter)
14 | router.use('/device', deviceRouter)
15 | router.use('/basket', basketRouter)
16 | router.use('/rating', ratingRouter)
17 | router.use('/orders', ordersRouter)
18 |
19 | module.exports = router;
20 |
--------------------------------------------------------------------------------
/front/src/http/userApi.js:
--------------------------------------------------------------------------------
1 | import {$authHost, $host} from "./index";
2 | import jwt_decode from "jwt-decode";
3 |
4 | export const registration = async (email, password) => {
5 | const {data} = await $host.post('api/user/registration', {email, password, role: 'ADMIN'});
6 | localStorage.setItem('token', data.token);
7 | return jwt_decode(data.token);
8 | }
9 |
10 | export const login = async (email, password) => {
11 | const {data} = await $host.post('api/user/login', {email, password});
12 | localStorage.setItem('token', data.token);
13 | return jwt_decode(data.token);
14 | }
15 |
16 | export const check = async () => {
17 | const {data} = await $authHost.get('api/user/auth');
18 | localStorage.setItem('token', data.token);
19 | return jwt_decode(data.token);
20 | }
21 |
--------------------------------------------------------------------------------
/front/src/components/ratingStars.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Rating } from '@material-ui/lab';
3 |
4 | const RatingStars = ({ratingChanged, ratingVal, isAuth, isAccessRating}) => {
5 | if(isAuth && isAccessRating) {
6 | return (
7 | {
10 | ratingChanged(newValue);
11 | }}
12 | precision={1}
13 | size="large"
14 | />
15 | )
16 | } else {
17 | return (
18 |
24 | )
25 | }
26 | };
27 |
28 | export default RatingStars;
29 |
--------------------------------------------------------------------------------
/backend/middleware/checkRoleMiddleware.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 |
3 | module.exports = function(role) {
4 | return function (req, res, next) {
5 | if (req.method === "OPTIONS") {
6 | next()
7 | }
8 | try {
9 | const token = req.headers.authorization.split(' ')[1];
10 | if (!token) {
11 | return res.status(401).json({message: "Not authorization"});
12 | }
13 | const decoded = jwt.verify(token, process.env.SECRET_KEY);
14 | if(decoded.role !== role) {
15 | return res.status(403).json({message: "You haven't access"});
16 | };
17 | req.user = decoded;
18 | next();
19 | } catch (e) {
20 | res.status(401).json({message: "Not authorization"});
21 | }
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/front/src/components/NavBar/NavBar.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {observer} from "mobx-react-lite";
3 | import {NavLink} from "react-router-dom";
4 |
5 | import {Context} from "../../index";
6 |
7 | import {Container, Navbar} from "react-bootstrap";
8 | import {SHOP_ROUTE} from "../../utils/consts";
9 | import TrueAuth from "./preesent-components/trueAuth";
10 | import FalseAuth from "./preesent-components/falseAuth";
11 |
12 | const NavBar = observer(() => {
13 | const {user} = useContext(Context);
14 |
15 | return (
16 |
17 |
18 | Магазин -70%
19 | {user.isAuth ? : }
20 |
21 |
22 | );
23 | });
24 |
25 | export default NavBar;
26 |
--------------------------------------------------------------------------------
/front/src/components/BrandBar.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {observer} from "mobx-react-lite";
3 | import {Context} from "../index";
4 | import {Card, Row} from "react-bootstrap";
5 |
6 | const BrandBar = observer(() => {
7 | const {device} = useContext(Context);
8 | return (
9 |
10 | {device.brands.map(brand =>
11 | device.setSelectedBrand(brand)}
17 | >
18 | {brand.name}
19 |
20 | )}
21 |
22 | );
23 | });
24 |
25 | export default BrandBar;
26 |
--------------------------------------------------------------------------------
/front/src/components/AppRouter.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {Switch, Route, Redirect} from 'react-router-dom';
3 |
4 | import {authRouters, publicRouters} from "../routes";
5 | import {SHOP_ROUTE} from "../utils/consts";
6 | import {Context} from "../index";
7 |
8 | const AppRouter = () => {
9 | const {user} = useContext(Context);
10 |
11 | return (
12 |
13 | {user.isAuth && user.User.role === "ADMIN" && authRouters.map( ({path, Component}) => {
14 | return
15 | })}
16 | {publicRouters.map( ({path, Component}) => {
17 | return
18 | })}
19 |
20 |
21 | );
22 | };
23 |
24 | export default AppRouter;
25 |
--------------------------------------------------------------------------------
/front/src/components/NavBar/BasketNavBar.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {Image} from "react-bootstrap";
3 | import shop_cart from "../../assets/shopping-basket.png";
4 | import {NavLink} from "react-router-dom";
5 | import {Context} from "../../index";
6 | import {observer} from "mobx-react-lite";
7 | import {BASKET_ROUTE} from "../../utils/consts";
8 |
9 | const BasketNavBar = observer(() => {
10 | const {basket} = useContext(Context);
11 |
12 | return (
13 |
14 |
15 |
16 | {basket.Price} RUB
17 |
18 |
19 | );
20 | });
21 | export default BasketNavBar;
22 |
23 |
24 |
--------------------------------------------------------------------------------
/front/src/components/Pages.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {observer} from "mobx-react-lite";
3 | import {Context} from "../index";
4 | import {Pagination} from "react-bootstrap";
5 |
6 | const Pages = observer(() => {
7 | const {device} = useContext(Context);
8 | const pageCount = Math.ceil(device.totalCount / device.limit);
9 | const pages = [];
10 |
11 | for (let i = 0; i < pageCount; i++) {
12 | pages.push(i + 1);
13 | }
14 |
15 | return (
16 |
17 | {pages.map(page =>
18 | device.setPage(page)}
22 | >
23 | {page}
24 |
25 | )}
26 |
27 | );
28 | });
29 |
30 | export default Pages;
31 |
--------------------------------------------------------------------------------
/backend/middleware/checkAddRatingMiddleware.js:
--------------------------------------------------------------------------------
1 | const {Rating, Device} = require('./../models/models');
2 |
3 | const jwt = require('jsonwebtoken');
4 |
5 | module.exports = async function (req, res, next) {
6 | try {
7 | const {deviceId} = req.body;
8 | const token = req.headers.authorization.split(' ')[1];
9 | const user = jwt.verify(token, process.env.SECRET_KEY);
10 | const checkRating = await Rating.findOne({where: {deviceId, userId: user.id}});
11 | const checkDevices = await Device.findOne({where: {id: deviceId}});
12 |
13 | if (!checkDevices) {
14 | return res.json("Product doesn't existing in data base");
15 | } else if(checkRating && checkDevices) {
16 | return res.json("You have left a rating for this product");
17 | }
18 | return next();
19 | } catch (e) {
20 | return res.status(401).json("Something going wrong in checkAddRatingMiddleware.js");
21 | }
22 | };
23 |
24 |
--------------------------------------------------------------------------------
/front/src/store/UserStore.js:
--------------------------------------------------------------------------------
1 | import {makeAutoObservable} from "mobx";
2 | import jwt_decode from "jwt-decode";
3 |
4 | export default class UserStore {
5 | constructor() {
6 | this._isAuth = false;
7 | this._user = {};
8 | makeAutoObservable(this);
9 | this.checkValidToken = this.checkValidToken.bind(this);
10 | }
11 |
12 | checkValidToken() {
13 | let isExpired = false;
14 | const token = localStorage.getItem('token');
15 | const decodedToken = jwt_decode(token);
16 | const dateNow = new Date();
17 |
18 | if (decodedToken.exp < dateNow.getTime()){
19 | isExpired = true;
20 | }
21 |
22 | return isExpired;
23 | }
24 |
25 | setIsAuth(bool) {
26 | this._isAuth = bool;
27 | }
28 |
29 | setUser(user) {
30 | this._user = user;
31 | }
32 |
33 | get isAuth() {
34 | return this._isAuth;
35 | }
36 |
37 | get User() {
38 | return this._user;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/backend/controllers/typeController.js:
--------------------------------------------------------------------------------
1 | const {Type} = require('./../models/models');
2 |
3 | class TypeController {
4 | async create(req, res) {
5 | const {name} = req.body;
6 | const type = await Type.create({name})
7 | return res.json(type)
8 | }
9 |
10 | async getAll(req, res) {
11 | const types = await Type.findAll()
12 | return res.json(types);
13 | }
14 |
15 | async delete(req, res) {
16 | try {
17 | const {id} = req.params;
18 | await Type.findOne({where:{id}})
19 | .then( async data => {
20 | if(data) {
21 | await Type.destroy({where:{id}}).then(() => {
22 | return res.json("Type deleted");
23 | })
24 | } else {
25 | return res.json("This Type doesn't exist in DB");
26 | }
27 | })
28 | } catch (e) {
29 | return res.json(e);
30 | }
31 | }
32 | }
33 |
34 | module.exports = new TypeController();
35 |
--------------------------------------------------------------------------------
/backend/controllers/brandController.js:
--------------------------------------------------------------------------------
1 | const {Brand} = require('./../models/models');
2 |
3 | class BranController {
4 | async create(req, res) {
5 | const {name} = req.body;
6 |
7 | const brand = await Brand.create({name});
8 | return res.json(brand);
9 | }
10 |
11 | async getAll(req, res) {
12 | const types = await Brand.findAll();
13 | return res.json(types);
14 | }
15 |
16 | async delete(req, res) {
17 | try {
18 | const {id} = req.params;
19 |
20 | await Brand.findOne({where:{id}})
21 | .then( async data => {
22 | if(data) {
23 | await Brand.destroy({where:{id}}).then(() => {
24 | return res.json("Brand deleted");
25 | })
26 | } else {
27 | return res.json("This Brand doesn't exist in DB");
28 | }
29 | })
30 | } catch (e) {
31 | return res.json(e);
32 | }
33 | }
34 | }
35 |
36 | module.exports = new BranController();
37 |
--------------------------------------------------------------------------------
/backend/index.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | const host = process.env.HOST;
3 | const port = process.env.PORT;
4 |
5 | const express = require("express");
6 | const app = express();
7 | const cors = require("cors");
8 | const fileUpload = require('express-fileupload');
9 | const path = require('path');
10 |
11 | const sequelize = require('./db/db');
12 | const models = require("./models/models");
13 |
14 | const router = require('./routes/index');
15 | const errorHandler = require('./middleware/ErrorHandlingMiddleware');
16 |
17 | //middleware
18 | app.use(cors());
19 | app.use(express.json());
20 | app.use(express.static(path.resolve(__dirname, 'static')));
21 | app.use(fileUpload({}));
22 | app.use('/api', router);
23 |
24 | //Handler error, last middleware
25 | app.use(errorHandler);
26 |
27 |
28 | const start = async () => {
29 | try {
30 | await sequelize.authenticate();
31 | await sequelize.sync();
32 |
33 | app.listen(port, () => {
34 | console.log(`App listening at http://${host}:${port}`);
35 | });
36 | } catch (e) {
37 | console.error(e);
38 | }
39 | }
40 | start();
41 |
42 |
43 |
--------------------------------------------------------------------------------
/front/src/http/ordersAPI.js:
--------------------------------------------------------------------------------
1 | import {$authHost, $host} from "./index";
2 |
3 | export const sendOrder = async ({auth, mobile, basket}) => {
4 | if(auth) {
5 | const {data} = await $authHost({method:'POST', url: 'api/orders', data: {mobile, basket}})
6 | return data;
7 | } else {
8 | const {data} = await $host({method:'POST', url: 'api/orders', data: {mobile, basket}});
9 | return data;
10 | }
11 | }
12 |
13 | export const fetchOrders = async ({limit, page, complete}) => {
14 | const {data} = await $authHost.get(`api/orders?limit=${limit}&page=${page}&complete=${complete}`);
15 | return data;
16 | }
17 |
18 | export const fetchChangeStatusOrder = async ({complete, id}) => {
19 | const {data} = await $authHost.put('api/orders', {complete, id});
20 | return data;
21 | }
22 |
23 | export const fetchDeleteOrder = async ({id}) => {
24 | const {data} = await $authHost({method:'DELETE', url: 'api/orders', data: {id}});
25 | return data;
26 | }
27 |
28 | export const getOneOrderDevices = async (id) => {
29 | const {data} = await $authHost.get('api/orders/' + id);
30 | return data;
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/front/src/components/TypeBar.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {observer} from "mobx-react-lite";
3 | import {Context} from "../index";
4 | import {ListGroup} from "react-bootstrap";
5 |
6 | const TypeBar = observer(() => {
7 | const {device} = useContext(Context);
8 |
9 | const getAllDevices = () => {
10 | device.setSelectedType("all");
11 | device.setSelectedBrand("all");
12 | }
13 |
14 | return (
15 |
16 |
21 | All
22 |
23 | {device.types.map(type =>
24 | device.setSelectedType(type)}
29 | >
30 | {type.name}
31 |
32 | )}
33 |
34 | );
35 | });
36 |
37 | export default TypeBar;
38 |
--------------------------------------------------------------------------------
/front/src/components/deviceItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Card, Col, Image} from "react-bootstrap";
3 |
4 | import star from './../assets/star.png';
5 | import {useHistory} from 'react-router-dom';
6 | import {DEVICE_ROUTE} from "../utils/consts";
7 |
8 | const DeviceItem = ({device}) => {
9 | const history = useHistory();
10 | console.log(device);
11 | return (
12 | history.push(DEVICE_ROUTE + '/' + device.id)}>
13 |
18 |
19 |
20 |
{device && device.brand.name}
21 |
22 |
{device.rating}
23 |
24 |
25 |
26 | {device.name}
27 |
28 |
29 | );
30 | };
31 |
32 | export default DeviceItem;
33 |
--------------------------------------------------------------------------------
/front/src/pages/BasketCard.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {observer} from "mobx-react-lite";
3 |
4 |
5 | import {Context} from "../index";
6 | import {Button, Col, Image, Row} from "react-bootstrap";
7 | import OneItemInBasket from "../components/oneItemInBasket";
8 |
9 | import emptyBasket from "./../assets/emptyBasket.jpg";
10 | import {ORDERING_ROUTE} from "../utils/consts";
11 | import {NavLink} from "react-router-dom";
12 |
13 | const BasketCard = observer(() => {
14 | const {basket} = useContext(Context);
15 |
16 | if(basket.Basket.length === 0) {
17 | return (
18 |
19 |
20 | Empty shopping basket
21 |
22 | )
23 | }
24 |
25 | return (
26 | <>
27 |
28 |
29 |
30 |
31 |
32 |
33 | {basket.Basket.map(device => )}
34 |
35 |
36 | >
37 | );
38 | });
39 |
40 | export default BasketCard;
41 |
--------------------------------------------------------------------------------
/front/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "front",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.11.4",
7 | "@material-ui/lab": "^4.0.0-alpha.58",
8 | "@testing-library/jest-dom": "^5.11.4",
9 | "@testing-library/react": "^11.1.0",
10 | "@testing-library/user-event": "^12.1.10",
11 | "axios": "^0.21.1",
12 | "bootstrap": "^4.6.0",
13 | "jwt-decode": "^3.1.2",
14 | "mobx": "^6.3.2",
15 | "mobx-react-lite": "^3.2.0",
16 | "node-sass": "^4.14.1",
17 | "react": "^17.0.2",
18 | "react-bootstrap": "^1.6.1",
19 | "react-dom": "^17.0.2",
20 | "react-rating-stars-component": "^2.2.0",
21 | "react-router-dom": "^5.2.0",
22 | "react-scripts": "4.0.3",
23 | "web-vitals": "^1.0.1"
24 | },
25 | "scripts": {
26 | "start": "react-scripts start",
27 | "build": "react-scripts build",
28 | "test": "react-scripts test",
29 | "eject": "react-scripts eject"
30 | },
31 | "eslintConfig": {
32 | "extends": [
33 | "react-app",
34 | "react-app/jest"
35 | ]
36 | },
37 | "browserslist": {
38 | "production": [
39 | ">0.2%",
40 | "not dead",
41 | "not op_mini all"
42 | ],
43 | "development": [
44 | "last 1 chrome version",
45 | "last 1 firefox version",
46 | "last 1 safari version"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/front/src/components/modals/CreateType.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import {Button, Form, Modal} from "react-bootstrap";
3 | import {createType} from "../../http/deviceAPI";
4 |
5 | const CreateType = ({show, onHide}) => {
6 | const [value, setValue] = useState('');
7 | const addType = () => {
8 | createType({name: value}).then(() => {
9 | setValue('')
10 | onHide();
11 | });
12 | };
13 |
14 | return (
15 |
21 |
22 |
23 | Add new Type
24 |
25 |
26 |
27 | setValue(e.target.value)}
31 | value={value}
32 | />
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default CreateType;
44 |
--------------------------------------------------------------------------------
/front/src/components/modals/CreateBrand.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import {Button, Form, Modal} from "react-bootstrap";
3 | import {createBrand} from "../../http/deviceAPI";
4 |
5 | const CreateBrand = ({show, onHide}) => {
6 | const [value, setValue] = useState('');
7 | const addBrand = () => {
8 | createBrand({name: value}).then(data => {
9 | setValue('')
10 | onHide();
11 | });
12 | };
13 |
14 | return (
15 |
21 |
22 |
23 | Add new Brand
24 |
25 |
26 |
27 | setValue(e.target.value)}
32 | />
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default CreateBrand;
44 |
--------------------------------------------------------------------------------
/front/src/pages/Ordering.js:
--------------------------------------------------------------------------------
1 | import React, {useContext, useState} from 'react';
2 | import {Button, Col, Form, Row} from "react-bootstrap";
3 | import {Context} from "../index";
4 | import {sendOrder} from "../http/ordersAPI";
5 | import {useHistory} from "react-router-dom";
6 | import {SHOP_ROUTE} from "../utils/consts";
7 |
8 | const Ordering = () => {
9 | const {basket, user} = useContext(Context);
10 | const [phone, setPhone] = useState(null);
11 | const history = useHistory();
12 |
13 | const buy = () => {
14 | let order = {
15 | mobile: phone,
16 | basket: basket.Basket
17 | }
18 |
19 | if(user.isAuth) {
20 | order.auth = true;
21 | }
22 |
23 | sendOrder(order).then(data => {
24 | console.log(data);
25 | basket.setDeleteAllDeviceFromBasket();
26 | history.push(SHOP_ROUTE);
27 | });
28 | }
29 | return (
30 | <>
31 | setPhone(e.target.value)}
36 | />
37 |
38 |
39 |
40 |
41 |
42 |
43 | >
44 | );
45 | };
46 |
47 | export default Ordering;
48 |
--------------------------------------------------------------------------------
/front/src/components/NavBar/preesent-components/trueAuth.js:
--------------------------------------------------------------------------------
1 | import {Button, Nav} from "react-bootstrap";
2 | import React, {useContext} from "react";
3 | import {Context} from "../../../index";
4 | import {useHistory} from "react-router-dom";
5 | import {ADMIN_ROUTE, ORDERS_ROUTE} from "../../../utils/consts";
6 | import BasketNavBar from "../BasketNavBar";
7 |
8 | const TrueAuth = () => {
9 | const {user, basket} = useContext(Context);
10 | const history = useHistory();
11 |
12 | const logOut = () => {
13 | user.setUser({});
14 | user.setIsAuth(false);
15 | localStorage.removeItem('token');
16 | basket.resetBasket();
17 | }
18 |
19 | return (
20 |
44 | );
45 | };
46 |
47 | export default TrueAuth;
48 |
--------------------------------------------------------------------------------
/front/src/routes.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADMIN_ROUTE,
3 | BASKET_ROUTE, DEVICE_EDIT_ROUTE,
4 | DEVICE_ROUTE,
5 | LOGIN_ROUTE, ORDERING_ROUTE,
6 | ORDERS_ROUTE,
7 | REGISTRATION_ROUTE,
8 | SHOP_ROUTE
9 | } from './utils/consts';
10 |
11 | import Admin from "./pages/Admin";
12 | import Orders from "./pages/Orders";
13 | import Shop from "./pages/Shop";
14 | import Auth from "./pages/Auth";
15 | import DevicePage from "./pages/DevicePage";
16 | import BasketCard from "./pages/BasketCard";
17 | import OneOrder from "./pages/OneOrder";
18 | import DevicePageEdit from "./pages/DevicePageEdit";
19 | import Ordering from "./pages/Ordering";
20 |
21 |
22 | export const authRouters = [
23 | {
24 | path: ADMIN_ROUTE,
25 | Component: Admin
26 | },
27 | {
28 | path: ORDERS_ROUTE,
29 | Component: Orders
30 | },
31 | {
32 | path: ORDERS_ROUTE + '/:id',
33 | Component: OneOrder
34 | },
35 | {
36 | path: DEVICE_EDIT_ROUTE + '/:id',
37 | Component: DevicePageEdit
38 | },
39 |
40 | ];
41 |
42 | export const publicRouters = [
43 | {
44 | path: ORDERING_ROUTE,
45 | Component: Ordering
46 | },
47 | {
48 | path: SHOP_ROUTE,
49 | Component: Shop
50 | },
51 | {
52 | path: LOGIN_ROUTE,
53 | Component: Auth
54 | },
55 | {
56 | path: REGISTRATION_ROUTE,
57 | Component: Auth
58 | },
59 | {
60 | path: DEVICE_ROUTE + '/:id',
61 | Component: DevicePage
62 | },
63 | {
64 | path: BASKET_ROUTE,
65 | Component: BasketCard
66 | },
67 | ];
68 |
--------------------------------------------------------------------------------
/front/src/store/DeviceStore.js:
--------------------------------------------------------------------------------
1 | import {makeAutoObservable} from "mobx";
2 |
3 | export default class DeviceStore {
4 | constructor() {
5 | this._types = [];
6 | this._brands = [];
7 | this._devices = [];
8 | this._selectedType = {};
9 | this._selectedBrand = {};
10 | this._page = 1;
11 | this._totalCount = 0;
12 | this._limit = 9;
13 | makeAutoObservable(this);
14 | }
15 |
16 | setSelectedType(selectedType) {
17 | this.setPage(1);
18 | this._selectedType = selectedType;
19 | }
20 | setSelectedBrand(selectedBrand) {
21 | this.setPage(1);
22 | this._selectedBrand = selectedBrand;
23 | }
24 | setTypes(types) {
25 | this._types = types;
26 | }
27 | setBrands(brands) {
28 | this._brands = brands;
29 | }
30 | setDevices(devices) {
31 | this._devices = devices;
32 | }
33 | setPage(page) {
34 | this._page = page;
35 | }
36 | setTotalCount(totalCount) {
37 | this._totalCount = totalCount;
38 | }
39 |
40 | get types() {
41 | return this._types;
42 | }
43 | get brands() {
44 | return this._brands;
45 | }
46 | get devices() {
47 | return this._devices;
48 | }
49 | get selectedType() {
50 | return this._selectedType;
51 | }
52 | get selectedBrand() {
53 | return this._selectedBrand;
54 | }
55 | get page() {
56 | return this._page;
57 | }
58 | get totalCount() {
59 | return this._totalCount;
60 | }
61 | get limit() {
62 | return this._limit;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/front/src/App.js:
--------------------------------------------------------------------------------
1 | import React, {useContext, useEffect, useState} from "react";
2 | import {BrowserRouter} from "react-router-dom";
3 | import {observer} from "mobx-react-lite";
4 |
5 | import AppRouter from "./components/AppRouter";
6 | import NavBar from "./components/NavBar/NavBar";
7 | import {Container, Spinner} from "react-bootstrap";
8 | import {Context} from "./index";
9 | import {check} from "./http/userApi";
10 | import {getDeviceFromBasket} from "./http/deviceAPI";
11 |
12 | const App = observer(() => {
13 | const {user, basket} = useContext(Context);
14 | const [loading, setLoading] = useState(false);
15 |
16 | //check authorization
17 | useEffect(() => {
18 | if(localStorage.getItem('token')) {
19 | setLoading(true);
20 | check().then(data => {
21 | user.setUser(data);
22 | user.setIsAuth(true);
23 | }).finally(() => {
24 | setLoading(false);
25 | })
26 | }
27 | }, [user]);
28 |
29 |
30 | //Loading Basket
31 | useEffect(() => {
32 | if(user.isAuth === false) {
33 | basket.setDeleteAllDeviceFromBasket();
34 | const savedBasket = JSON.parse(localStorage.getItem("basket"));
35 | for (let key in savedBasket) {
36 | basket.setBasket(savedBasket[key]);
37 | }
38 | } else if(user.isAuth === true){
39 | basket.setDeleteAllDeviceFromBasket();
40 | getDeviceFromBasket().then(data => {
41 | for (let key in data) {
42 | basket.setBasket(data[key], true);
43 | }
44 | })
45 | }
46 | }, [basket, user.isAuth]);
47 |
48 | if(loading) {
49 | return
50 | }
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
59 | );
60 | });
61 | export default App;
62 |
--------------------------------------------------------------------------------
/backend/controllers/userController.js:
--------------------------------------------------------------------------------
1 | const ApiError = require('./../error/apiError');
2 | const bcrypt = require('bcrypt');
3 | const {User, Basket} = require('./../models/models');
4 | const jwt = require('jsonwebtoken');
5 |
6 | const generateJwt = (id, email, role) => {
7 | return jwt.sign(
8 | {id, email, role},
9 | process.env.SECRET_KEY,
10 | {expiresIn: '24h'}
11 | );
12 | }
13 |
14 | class UserController {
15 | async registration(req, res, next) {
16 | const {email, password, role} = req.body;
17 | if (!email || !password) {
18 | return next(ApiError.badRequest('Not valid email or password'));
19 | }
20 |
21 | const candidate = await User.findOne({where: {email}});
22 | if(candidate) {
23 | return next(ApiError.badRequest('User with this email already exists'));
24 | }
25 |
26 | const hashPassword = await bcrypt.hash(password, 5);
27 | const user = await User.create({email, role, password: hashPassword});
28 | const basket = await Basket.create({userId: user.id});
29 | const token = generateJwt(user.id, user.email, user.role);
30 | return res.json({token});
31 | }
32 |
33 |
34 | async login(req, res, next) {
35 | const {email, password} = req.body;
36 | const user = await User.findOne({where: {email}});
37 | if(!user) {
38 | return next(ApiError.internal('User with this name didn\'t find'));
39 | }
40 | let comparePassword = bcrypt.compareSync(password, user.password);
41 | if(!comparePassword) {
42 | return next(ApiError.internal('Not valid password'));
43 | }
44 | const token = generateJwt(user.id, user.email, user.role);
45 | return res.json({token});
46 | }
47 |
48 |
49 | async check(req, res) {
50 | const token = generateJwt(req.user.id, req.user.email, req.user.role)
51 | return res.json({token})
52 | }}
53 |
54 |
55 | module.exports = new UserController();
56 |
--------------------------------------------------------------------------------
/backend/controllers/ratingController.js:
--------------------------------------------------------------------------------
1 | const {Rating, Device} = require('./../models/models');
2 | const jwt = require('jsonwebtoken');
3 |
4 | class RatingController {
5 | async addRating(req, res) {
6 | try {
7 | const {rate, deviceId} = req.body;
8 | const token = req.headers.authorization.split(' ')[1];
9 | const user = jwt.verify(token, process.env.SECRET_KEY);
10 | await Rating.create({rate, deviceId, userId: user.id});
11 |
12 | let rating = await Rating.findAndCountAll({
13 | where: {
14 | deviceId
15 | },
16 | });
17 |
18 | let allRating = 0;
19 | let middleRating;
20 | rating.rows.forEach(item => allRating += item.rate);
21 | middleRating = Number(allRating) / Number(rating.count);
22 |
23 | await Device.update(
24 | {rating: middleRating},
25 | {where: {id: deviceId}}
26 | );
27 |
28 | return res.json("Rating success added");
29 | } catch (e) {
30 | console.error(e);
31 | }
32 | }
33 |
34 | async checkRating(req, res) {
35 | try {
36 | const {deviceId} = req.body;
37 | const token = req.headers.authorization.split(' ')[1];
38 | const user = jwt.verify(token, process.env.SECRET_KEY);
39 | const checkRating = await Rating.findOne({where: {deviceId, userId: user.id}});
40 | const checkDevices = await Device.findOne({where: {id: deviceId}});
41 | if (!checkDevices) {
42 | return res.json({allow: false});
43 | } else if(checkRating && checkDevices) {
44 | return res.json({allow: false});
45 | }
46 | return res.json({allow: true});
47 | } catch (e) {
48 | return res.status(401).json("Something going wrong in checkAddRatingMiddleware.js");
49 | }
50 | }
51 | }
52 |
53 | module.exports = new RatingController();
54 |
--------------------------------------------------------------------------------
/front/src/pages/Shop.js:
--------------------------------------------------------------------------------
1 | import React, {useContext, useEffect} from 'react';
2 | import {Col, Container, Row} from "react-bootstrap";
3 | import TypeBar from "../components/TypeBar";
4 | import BrandBar from "../components/BrandBar";
5 | import DeviceList from "../components/DeviceList";
6 | import {observer} from "mobx-react-lite";
7 | import {Context} from "../index";
8 | import {fetchBrands, fetchDevice, fetchTypes} from "../http/deviceAPI";
9 | import Pages from "../components/Pages";
10 |
11 | const Shop = observer(() => {
12 | const {device} = useContext(Context);
13 |
14 | useEffect(() => {
15 | fetchTypes().then(data => device.setTypes(data));
16 | fetchBrands().then(data => device.setBrands(data));
17 | fetchDevice(null, null, 1, 9).then(data => {
18 | device.setDevices(data.rows);
19 | device.setTotalCount(data.count);
20 | });
21 | }, []);
22 |
23 | useEffect(
24 | () => {
25 | if(device.selectedType === "all") {
26 | fetchDevice(null, device.selectedBrand.id, device.page, 9).then(data => {
27 | device.setDevices(data.rows);
28 | device.setTotalCount(data.count);
29 | });
30 | } else {
31 | fetchDevice(device.selectedType.id, device.selectedBrand.id, device.page, 9).then(data => {
32 | device.setDevices(data.rows);
33 | device.setTotalCount(data.count);
34 | });
35 | }
36 | }, [device.page, device.selectedType, device.selectedBrand],
37 | );
38 |
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 | });
54 |
55 | export default Shop;
56 |
--------------------------------------------------------------------------------
/front/src/pages/OneOrder.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import {Col, Container, Image, Row, Spinner} from "react-bootstrap";
3 | import {useParams} from "react-router-dom";
4 | import {getOneOrderDevices} from "../http/ordersAPI";
5 |
6 | const OneOrder = () => {
7 | const {id} = useParams();
8 | const [loading, setLoading] = useState(true);
9 | const [order, setOrder] = useState([]);
10 |
11 | useEffect(() => {
12 | getOneOrderDevices(id).then(data => {
13 | setOrder(data);
14 | setLoading(false);
15 | console.log(order);
16 | })
17 | }, []);
18 |
19 | if(loading) {
20 | return
21 | }
22 |
23 | //Format date (createdAt)
24 | const formatDate = (propsDate) => {
25 | const date = new Date(Date.parse(propsDate));
26 | const options = {
27 | weekday: "short",
28 | hour: 'numeric',
29 | minute: 'numeric',
30 | year: 'numeric',
31 | month: 'numeric',
32 | day: 'numeric',
33 | timezone: 'UTC'
34 | };
35 | return date.toLocaleString("en-US", options);
36 | }
37 |
38 | return (
39 |
40 | Order id: {id}
41 | Complete: {order?.descr.complete ? "Completed" : "Not complete"}
42 | User: {order?.descr.userId ? order.descr.userId : "User didn't register"}
43 | Created: {formatDate(order?.descr.createdAt)}
44 | {order?.descr.complete ? formatDate(order.descr.complete.updatedAt) : false }
45 | Mobile: {order?.descr.mobile}
46 |
47 |
48 | {order?.devices.map( ({count,descr}, i) => {
49 | return (
50 |
51 |
52 |
53 |
54 |
55 | Brand: {descr.brand.name}
56 | Type: {descr.type.name}
57 | Name: {descr.name}
58 | Price: {descr.price} RUB
59 | Count: {count}
60 | Total price: {count * descr.price} RUB
61 |
62 |
63 | )
64 | })}
65 |
66 |
67 | );
68 | };
69 |
70 | export default OneOrder;
71 |
--------------------------------------------------------------------------------
/front/src/http/deviceAPI.js:
--------------------------------------------------------------------------------
1 | import {$authHost, $host} from "./index";
2 |
3 | export const createType = async (type) => {
4 | const {data} = await $authHost.post('api/type', type);
5 | return data;
6 | }
7 |
8 | export const fetchTypes = async () => {
9 | const {data} = await $host.get('api/type');
10 | return data;
11 | }
12 |
13 | export const deleteType = async (id) => {
14 | const {data} = await $authHost({method:'DELETE', url:'api/type/'+id});
15 | return data;
16 | }
17 |
18 | export const createBrand = async (brand) => {
19 | const {data} = await $authHost.post('api/brand', brand);
20 | return data;
21 | }
22 |
23 | export const fetchBrands = async () => {
24 | const {data} = await $host.get('api/brand');
25 | return data;
26 | }
27 |
28 | export const deleteBrand = async (id) => {
29 | const {data} = await $authHost({method:'DELETE', url:'api/brand/'+id});
30 | return data;
31 | }
32 |
33 | export const createDevice = async (brand) => {
34 | const {data} = await $authHost.post('api/device', brand);
35 | return data;
36 | }
37 |
38 | export const fetchDevice = async (typeId, brandId, page, limit = 9) => {
39 | const {data} = await $host.get('api/device', {params: {
40 | typeId, brandId, page, limit
41 | }});
42 | return data;
43 | }
44 |
45 | export const fetchOneDevice = async (id) => {
46 | const {data} = await $host.get(`api/device/${id}`);
47 | return data;
48 | }
49 |
50 | export const fetchDeleteDevice = async (id) => {
51 | const {data} = await $authHost({method:'DELETE', url:`api/device/${id}`});
52 | return data;
53 | }
54 |
55 | export const updateDevices = async (id, body) => {
56 | const {data} = await $authHost({method:'PUT', url:`api/device/${id}`, data: body});
57 | return data;
58 | }
59 |
60 | export const getAllDevicesInAdminPage = async (name, page = 1, filter = "All") => {
61 | const {data} = await $authHost({method:'GET', url:`api/device/search?page=${page}&name=${name}&filter=${filter}`});
62 | return data;
63 | }
64 |
65 | export const addDeviceToBasket = async (device) => {
66 | const {data} = await $authHost.post('api/basket', device);
67 | return data;
68 | }
69 |
70 | export const getDeviceFromBasket = async () => {
71 | const {data} = await $authHost.get('api/basket');
72 | return data;
73 | }
74 |
75 | export const deleteDeviceFromBasket = async (id) => {
76 | const {data} = await $authHost.delete(`api/basket/${id}`);
77 | return data;
78 | }
79 |
80 | export const addRating = async (body) => {
81 | const {data} = await $authHost.post('api/rating', body);
82 | return data;
83 | }
84 |
85 | export const checkRating = async (body) => {
86 | const {data} = await $authHost.post('api/rating/check-rating', body);
87 | return data;
88 | }
89 |
--------------------------------------------------------------------------------
/backend/controllers/basketController.js:
--------------------------------------------------------------------------------
1 | const {Basket, BasketDevice, Device, DeviceInfo} = require('./../models/models');
2 | const jwt = require('jsonwebtoken');
3 | const { Op } = require("sequelize");
4 |
5 | class BasketController {
6 | async addDevice(req, res) {
7 | try {
8 | const {id} = req.body;
9 | const token = req.headers.authorization.split(' ')[1];
10 | const user = jwt.verify(token, process.env.SECRET_KEY);
11 | const basket = await Basket.findOne({where: {userId: user.id}});
12 | await BasketDevice.create({basketId : basket.id, deviceId: id});
13 | return res.json("Product added in card");
14 | } catch (e) {
15 | console.error(e);
16 | }
17 | }
18 |
19 | async getDevices(req, res) {
20 | try {
21 | const token = req.headers.authorization.split(' ')[1];
22 | const user = jwt.verify(token, process.env.SECRET_KEY);
23 | const {id} = await Basket.findOne({where: {userId: user.id}});
24 | const basket = await BasketDevice.findAll({where: {basketId: id}});
25 |
26 | const basketArr = [];
27 | for(let i = 0; i < basket.length; i++) {
28 | const basketDevice = await Device.findOne({
29 | where: {
30 | id: basket[i].deviceId,
31 |
32 | },
33 | include: {
34 | model: DeviceInfo, as: "info",
35 | where: {
36 | deviceId: basket[i].deviceId,
37 | [Op.or]: [
38 | {
39 | deviceId: {
40 | [Op.not]: null
41 | }
42 | }
43 | ],
44 | },
45 | required: false}
46 | });
47 | basketArr.push(basketDevice);
48 | }
49 |
50 | return res.json(basketArr);
51 | } catch (e) {
52 | console.error(e);
53 | }
54 | }
55 |
56 | async deleteDevice(req, res) {
57 | try {
58 | const {id} = req.params;
59 | const user = req.user;
60 |
61 | await Basket.findOne({where: {userId: user.id}}).then(async userBasket => {
62 | if(userBasket.userId === user.id) {
63 | await BasketDevice.destroy({where: {basketId: userBasket.id, deviceId: id}})
64 | }
65 | return res.json(`You haven't access for delete the device(${id}) from basket that didn't belong to you`);
66 | });
67 | return res.json("Product deleted form your card");
68 | } catch (e) {
69 | console.error(e);
70 | }
71 | }
72 | }
73 |
74 | module.exports = new BasketController();
75 |
--------------------------------------------------------------------------------
/front/src/store/BasketStore.js:
--------------------------------------------------------------------------------
1 | import {makeAutoObservable} from "mobx";
2 | import {deleteDeviceFromBasket} from "../http/deviceAPI";
3 |
4 | export default class BasketStoreStore {
5 | constructor() {
6 | this._totalPrice = 0;
7 | this._basket = [];
8 | makeAutoObservable(this);
9 | }
10 |
11 | async setDeleteItemBasket(device, isAuth = false) {
12 | if(isAuth) {
13 | await deleteDeviceFromBasket(device.id).then(() => {
14 | this._basket = this._basket.filter(item => item.id !== device.id);
15 | this._totalPrice -= device.price * device.count;
16 | });
17 | } else {
18 | this._basket = this._basket.filter(item => item.id !== device.id);
19 | this._totalPrice -= device.price * device.count;
20 |
21 | localStorage.setItem("basket", JSON.stringify(this._basket));
22 | }
23 | }
24 |
25 | setBasket(item, isAuth = false) {
26 | const checkDeviceInBasket = this._basket.findIndex(device => device.id === item.id);
27 | if(checkDeviceInBasket < 0) {
28 | this._basket = [...this._basket, { count: 1, ...item}];
29 | let totalPrice = 0;
30 | this._basket.forEach(device => totalPrice += Number(device.price * device.count));
31 | this._totalPrice = totalPrice;
32 | }
33 |
34 | if(!isAuth) {
35 | localStorage.setItem("basket", JSON.stringify(this._basket));
36 | }
37 | }
38 |
39 | setDeleteAllDeviceFromBasket() {
40 | this._totalPrice = 0;
41 | return this._basket = [];
42 | }
43 |
44 | setCountDevice(deviceId, action, isAuth = false) {
45 | const itemInd = this._basket.findIndex(item => item.id === deviceId);
46 | const itemInState = this._basket.find(device => device.id === deviceId);
47 | if (action === "+") {
48 | const newItem = {
49 | ...itemInState,
50 | count: ++itemInState.count
51 | }
52 | this._basket = [...this._basket.slice(0, itemInd), newItem, ...this._basket.slice(itemInd + 1)]
53 | } else {
54 | const newItem = {
55 | ...itemInState,
56 | count: itemInState.count === 1 ? 1 : --itemInState.count
57 | }
58 | this._basket = [...this._basket.slice(0, itemInd), newItem, ...this._basket.slice(itemInd + 1)]
59 | }
60 |
61 | if(!isAuth) {
62 | localStorage.setItem("basket", JSON.stringify(this._basket));
63 | }
64 |
65 | let totalPrice = 0;
66 | this._basket.forEach(device => totalPrice += Number(device.price * device.count));
67 | this._totalPrice = totalPrice;
68 | }
69 |
70 | resetBasket() {
71 | this._basket = [];
72 | this._totalPrice = 0;
73 | localStorage.removeItem('basket');
74 | }
75 |
76 |
77 | get Basket() {
78 | return this._basket;
79 | }
80 |
81 | get Price() {
82 | return this._totalPrice;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/front/src/pages/Auth.js:
--------------------------------------------------------------------------------
1 | import React, {useContext, useState} from 'react';
2 | import {NavLink, useLocation, useHistory} from "react-router-dom";
3 |
4 | import {Button, Card, Container, Form, Row} from "react-bootstrap";
5 | import {LOGIN_ROUTE, REGISTRATION_ROUTE, SHOP_ROUTE} from "../utils/consts";
6 | import {login, registration} from "../http/userApi";
7 | import {observer} from "mobx-react-lite";
8 | import {Context} from "../index";
9 |
10 | const Auth = observer(() => {
11 | const {user} = useContext(Context);
12 | const history = useHistory();
13 | const location = useLocation();
14 | const isLogin = location.pathname === LOGIN_ROUTE;
15 | const [email, setEmail] = useState('');
16 | const [password, setPassword] = useState('');
17 |
18 | const click = async () => {
19 | try {
20 | let data;
21 | if(isLogin) {
22 | data = await login(email, password);
23 | } else {
24 | data = await registration(email, password);
25 | }
26 | user.setUser(data);
27 | user.setIsAuth(true);
28 | history.push(SHOP_ROUTE);
29 | } catch (e) {
30 | alert(e.response.data.message);
31 | }
32 | };
33 |
34 | return (
35 |
39 |
40 | {isLogin ? "Authorization" : "Registration"}
41 | setEmail(e.target.value)}
47 | />
48 | setPassword(e.target.value)}
53 | type="password"
54 | />
55 |
56 | {isLogin ?
57 |
58 | Haven't account? Sign In
59 |
60 | :
61 |
62 | Has account? Login
63 |
64 | }
65 |
66 |
73 |
74 |
75 |
76 |
77 |
78 | );
79 | });
80 |
81 | export default Auth;
82 |
--------------------------------------------------------------------------------
/backend/models/models.js:
--------------------------------------------------------------------------------
1 | const sequelize = require('./../db/db');
2 | const {DataTypes} = require('sequelize');
3 |
4 | const User = sequelize.define('user', {
5 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
6 | email: {type: DataTypes.STRING, unique: true},
7 | password: {type: DataTypes.STRING},
8 | role: {type: DataTypes.STRING, defaultValue: "USER"},
9 | });
10 |
11 | const Basket = sequelize.define('basket', {
12 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
13 | });
14 |
15 | const BasketDevice = sequelize.define('basket_device', {
16 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
17 | deviceId: {type: DataTypes.INTEGER},
18 | });
19 |
20 | const Device = sequelize.define('device', {
21 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
22 | name: {type: DataTypes.STRING, unique: true, allowNull: false},
23 | price: {type: DataTypes.INTEGER, allowNull: false},
24 | rating: {type: DataTypes.INTEGER, defaultValue: 0},
25 | img: {type: DataTypes.STRING, allowNull: false},
26 | });
27 |
28 | const Type = sequelize.define('type', {
29 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
30 | name: {type: DataTypes.STRING, unique: true, allowNull: false},
31 | });
32 |
33 | const Brand = sequelize.define('brand', {
34 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
35 | name: {type: DataTypes.STRING, unique: true, allowNull: false},
36 | });
37 |
38 | const Rating = sequelize.define('rating', {
39 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
40 | rate: {type: DataTypes.INTEGER, allowNull: false},
41 | });
42 |
43 | const DeviceInfo = sequelize.define('device_info', {
44 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
45 | title: {type: DataTypes.STRING, allowNull: false},
46 | description: {type: DataTypes.STRING, allowNull: false},
47 | });
48 |
49 | const TypeBrand = sequelize.define('type_brand', {
50 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
51 | })
52 |
53 | const Orders = sequelize.define('orders', {
54 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
55 | complete: {type: DataTypes.BOOLEAN, defaultValue: false},
56 | mobile: {type: DataTypes.STRING(25), allowNull: false},
57 | userId: {type: DataTypes.INTEGER, allowNull: true},
58 | })
59 |
60 | const OrderDevice = sequelize.define('order_device', {
61 | id: {type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true},
62 | deviceId: {type: DataTypes.INTEGER, allowNull: false},
63 | orderId: {type: DataTypes.INTEGER, allowNull: false},
64 | count: {type: DataTypes.INTEGER, allowNull: false},
65 | })
66 |
67 | User.hasOne(Basket);
68 | Basket.belongsTo(User);
69 |
70 | User.hasMany(Rating);
71 | Rating.belongsTo(User);
72 |
73 | User.hasMany(Orders);
74 | Orders.belongsTo(User,
75 | {
76 | foreignKey: { name: 'userId' },
77 | onDelete: 'CASCADE',
78 | }
79 | );
80 |
81 | Orders.hasMany(OrderDevice);
82 | OrderDevice.belongsTo(Orders,
83 | {
84 | foreignKey: { name: 'orderId' },
85 | onDelete: 'CASCADE',
86 | onUpdate: 'CASCADE',
87 | }
88 | );
89 |
90 | Basket.hasMany(BasketDevice);
91 | BasketDevice.belongsTo(Basket);
92 |
93 | Type.hasMany(Device);
94 | Device.belongsTo(Type);
95 |
96 | Type.hasMany(Device);
97 | Device.belongsTo(Type);
98 |
99 | Brand.hasMany(Device);
100 | Device.belongsTo(Brand);
101 |
102 | Device.hasMany(Rating);
103 | Rating.belongsTo(Device);
104 |
105 | Device.hasMany(DeviceInfo, {as: 'info'});
106 | DeviceInfo.belongsTo(Device);
107 |
108 | Type.belongsToMany(Brand, {through: TypeBrand});
109 | Brand.belongsToMany(Type, {through: TypeBrand});
110 |
111 |
112 | module.exports = {
113 | User,
114 | Basket,
115 | BasketDevice,
116 | Device,
117 | Type,
118 | Brand,
119 | Rating,
120 | TypeBrand,
121 | DeviceInfo,
122 | Orders,
123 | OrderDevice
124 | }
125 |
126 |
--------------------------------------------------------------------------------
/front/src/pages/DevicePage.js:
--------------------------------------------------------------------------------
1 | import React, {useContext, useEffect, useState} from 'react';
2 | import {Button, Card, Col, Container, Image, Row} from "react-bootstrap";
3 | import bigStar from './../assets/star.png';
4 | import {useParams} from 'react-router-dom';
5 | import {addDeviceToBasket, addRating, checkRating, fetchOneDevice} from "../http/deviceAPI";
6 | import {Context} from "../index";
7 | import {observer} from "mobx-react-lite";
8 | import RatingStars from "../components/ratingStars";
9 |
10 | const DevicePage = observer(() => {
11 | const {user, basket} = useContext(Context);
12 | const [device, setDevice] = useState({info: []});
13 | const [resRate, setResRate] = useState("");
14 | const [isAccessRating, setSsAccessRating] = useState(false);
15 | const {id} = useParams();
16 |
17 |
18 | useEffect( () => {
19 | fetchOneDevice(id).then(data => setDevice(data));
20 | if(user.isAuth) {
21 | checkRating({deviceId: id}).then(res => setSsAccessRating(res.allow));
22 | }
23 | },[id, resRate]);
24 |
25 | const isDeviceInBasket = () => {
26 | const findDevice = basket.Basket.findIndex(item => Number(item.id) === Number(device.id));
27 | return findDevice < 0;
28 | }
29 |
30 | const addDeviceInBasket = (device) => {
31 | if(user.isAuth) {
32 | addDeviceToBasket(device).then(() => basket.setBasket(device, true))
33 | } else {
34 | basket.setBasket(device);
35 | }
36 | }
37 |
38 | const ratingChanged = (rate) => {
39 | addRating({
40 | rate,
41 | deviceId: id
42 | }).then(res => {
43 | setResRate(res);
44 | });
45 | };
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | {device.name}
56 |
60 | {device?.rating || 0}
61 |
62 |
68 | {resRate}
69 |
70 |
71 |
72 |
76 | {device?.price || 0} RUB
77 | { isDeviceInBasket() ?
78 |
79 | :
80 |
81 | }
82 |
83 |
84 |
85 |
86 |
87 | Characteristics
88 | {device.info.map( (info, index) =>
89 |
90 | {info.title}: {info.description}
91 |
92 | )}
93 |
94 |
95 | );
96 | });
97 |
98 | export default DevicePage;
99 |
100 |
101 | /**
102 | * @param {{rating}} rating of device
103 | * @param {{price}} price of device
104 | */
105 |
--------------------------------------------------------------------------------
/front/src/pages/Orders.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import {Col, Container, Dropdown, ListGroup, Pagination, Row, Spinner} from "react-bootstrap";
3 | import {fetchOrders} from "../http/ordersAPI";
4 | import ItemOneOrderInAdmin from "../components/itemOneorderInAdmin";
5 |
6 | const Orders = () => {
7 | const [loading, setLoading] = useState(false);
8 | const [orders, setOrders] = useState([]);
9 | const [currentPage, setCurrentPage] = useState(1);
10 | const [count, setCount] = useState(0);
11 | const [filter, setFilter] = useState("All");
12 | const [rerender, setRerender] = useState(false);
13 |
14 | //pagination
15 | const limit = 5;
16 | const pageCount = Math.ceil(Number(count) / limit);
17 | const pages = [];
18 |
19 | useEffect(() => {
20 | fetchOrders({limit, page: 1}).then(data => {
21 | setOrders(data);
22 | setLoading(false);
23 | setCount(data.count);
24 | })
25 | }, []);
26 |
27 | useEffect(() => {
28 | setLoading(true);
29 | fetchOrders({limit, page: currentPage}).then(data => {
30 | setOrders(data);
31 | setLoading(false);
32 | })
33 | }, [currentPage]);
34 |
35 | useEffect(() => {
36 | setLoading(true);
37 | fetchOrders({limit, page: 1, complete: filter}).then(data => {
38 | setOrders(data);
39 | setLoading(false);
40 | setCount(data.count);
41 | setCurrentPage(1);
42 | })
43 | }, [filter]);
44 |
45 | //re-render after change status, or delete some order
46 | useEffect(() => {
47 | setLoading(true);
48 | fetchOrders({limit, page: currentPage, complete: filter}).then(data => {
49 | setOrders(data);
50 | setLoading(false);
51 | setCount(data.count);
52 | setCurrentPage(1);
53 | })
54 | }, [rerender]);
55 |
56 | const reRender = () => {
57 | setRerender(!rerender);
58 | }
59 |
60 | if(loading) {
61 | return
62 | }
63 |
64 | for (let number = 1; number < pageCount + 1; number++) {
65 | pages.push(
66 | setCurrentPage(number)}>
67 | {number}
68 |
69 | );
70 | }
71 |
72 | return (
73 |
74 |
75 |
76 | Filter:
77 |
78 |
79 | {filter}
80 |
81 |
82 |
83 | {filter === "all" ? All : setFilter("all")}>All}
84 | {filter === "completed" ? Completed : setFilter("completed")}>Completed}
85 | {filter === "not-completed" ? Not Completed : setFilter("not-completed")}>Not Completed}
86 |
87 |
88 |
89 |
90 |
91 | {orders.rows?.map( ({id, complete, mobile, createdAt, updatedAt, userId}) =>
92 | )}
101 |
102 |
103 | {pages}
104 |
105 |
106 | );
107 | };
108 |
109 | export default Orders;
110 |
--------------------------------------------------------------------------------
/front/src/components/oneItemInBasket.js:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react';
2 | import {Button, Card, Col, Image, Row} from "react-bootstrap";
3 | import {Context} from "../index";
4 | import {NavLink} from "react-router-dom";
5 |
6 | const OneItemInBasket = ({device}) => {
7 | const {basket, user} = useContext(Context);
8 |
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Title: {device.name}
20 |
21 |
22 |
23 |
24 |
25 | Description:
26 | {device.info && device.info.length !== 0? device.info.map((info, i) => {
27 |
28 | if(i % 2 === 0 ) {
29 | return (
30 |
31 |
32 | {info.title}
33 |
34 |
35 | {info.description}
36 |
37 |
38 | );
39 | } else {
40 | return (
41 |
42 |
43 | {info.title}
44 |
45 |
46 | {info.description}
47 |
48 |
49 | );
50 | }
51 |
52 | }) : "Description absent"}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | {user.isAuth ?
62 | :
63 | }
64 |
65 |
66 |
67 |
68 | Count:
69 |
70 |
71 |
72 |
73 |
74 | basket.setCountDevice(Number(e.target.value))} value={device.count}/>
75 |
76 |
77 |
78 |
79 |
80 | Price: {device.price * device.count} RUB
81 |
82 |
83 |
84 |
85 |
86 |
87 | )};
88 |
89 | export default OneItemInBasket;
90 |
--------------------------------------------------------------------------------
/front/src/components/modals/DeleteBrandOrType.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import {Button, Dropdown, Modal} from "react-bootstrap";
3 | import {deleteBrand, deleteType, fetchBrands, fetchTypes} from "../../http/deviceAPI";
4 |
5 | const DeleteBrandOrType = ({show, onHide, showSuccessMsgFunc}) => {
6 | const [brandOrType, setBrandOrType] = useState("Brand");
7 | const [brands, setBrands] = useState([]);
8 | const [types, setTypes] = useState([]);
9 | const [selectBrand, setSelectBrand] = useState({name: "A Brand not selected"});
10 | const [selectType, setSelectType] = useState({name: "A type not selected"});
11 | const [showMsgErr, setShowMsgErr] = useState(false);
12 | const [msgErr, setMsgErr] = useState('');
13 |
14 | useEffect(() => {
15 | fetchTypes().then(data => setTypes(data));
16 | fetchBrands().then(data => setBrands(data));
17 | }, []);
18 |
19 | const Delete = async () => {
20 | if(brandOrType === "Brand") {
21 | if(selectBrand.name !== "A Brand not selected") {
22 | await deleteBrand(selectBrand.id).then(data => {
23 | showSuccessMsgFunc(data);
24 | onHide();
25 | setSelectBrand({name: "A Brand not selected"});
26 | });
27 | } else {
28 | setMsgErr("Please choose Brand");
29 | setShowMsgErr(true);
30 | }
31 | } else {
32 | if(selectType.name !== "A Type not selected") {
33 | await deleteType(selectType.id).then(data => {
34 | showSuccessMsgFunc(data);
35 | onHide();
36 | setSelectType({name: "A type not selected"});
37 | });
38 | } else {
39 | setMsgErr("Please choose Type");
40 | setShowMsgErr(true);
41 | }
42 | }
43 | };
44 |
45 | useEffect(() => setShowMsgErr(false), [selectType, selectBrand, brandOrType])
46 |
47 | return (
48 |
54 |
55 |
56 | Delete Type or Brand
57 |
58 |
59 |
60 | {showMsgErr &&
61 | <>
62 | {msgErr}
63 | >
64 | }
65 |
66 | Choose Category:
67 |
68 |
69 | {brandOrType}
70 |
71 |
72 |
73 | {brandOrType === "Brand" ? Brand : setBrandOrType("Brand")}>Brand}
74 | {brandOrType === "Type" ? Type : setBrandOrType("Type")}>Type}
75 |
76 |
77 |
78 | Choose item of {brandOrType === "Brand" ? "Brand" : "Type"}
79 |
80 |
81 | {brandOrType === "Brand" ? selectBrand.name : selectType.name}
82 |
83 |
84 |
85 | {brandOrType === "Brand" ?
86 | brands.map(({id, name}) =>
87 | selectBrand.name === name ? {name} : setSelectBrand({id, name})}>{name}
88 | )
89 | :
90 | types.map(({id, name}) =>
91 | selectType.name === name ? {name} : setSelectType({id, name})}>{name}
92 | )
93 | }
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | );
106 | };
107 |
108 | export default DeleteBrandOrType;
109 |
--------------------------------------------------------------------------------
/backend/controllers/ordersController.js:
--------------------------------------------------------------------------------
1 | const {Orders, OrderDevice, Device, Brand, Type} = require('./../models/models');
2 | const ApiError = require('../error/apiError');
3 | const jwt = require('jsonwebtoken');
4 |
5 | class OrdersController {
6 | async create(req, res) {
7 | const auth = req.headers.authorization || "";
8 | const {mobile, basket} = req.body;
9 |
10 | try {
11 | let parseDevices = [];
12 | for (let key of basket) {
13 | parseDevices.push(key.id)
14 | }
15 |
16 | const isDeviceInDB = await Device.findAndCountAll({
17 | where: {id: parseDevices},
18 | attributes: ["id"]
19 | });
20 |
21 | if(isDeviceInDB.count === parseDevices.length) { //if all devices was found in DB
22 | const row = {mobile};
23 | if(auth) {
24 | const token = auth.split(' ')[1];
25 | const {id} = jwt.verify(token, process.env.SECRET_KEY);
26 | row.userId = id;
27 | }
28 |
29 | await Orders.create(row).then(order => {
30 | const {id} = order.get();
31 | parseDevices.forEach( async (deviceId, i) => {
32 |
33 | await OrderDevice.create({
34 | orderId: id,
35 | deviceId,
36 | count: basket[i].count
37 | });
38 | });
39 | });
40 | } else { //send msg about devices that didnt found in DB
41 | const notFoundIdDevices = [];
42 | const arrDevices = []; //found id
43 | isDeviceInDB.rows.forEach(item => arrDevices.push(item.id));
44 | parseDevices.forEach(deviceId => {
45 | if(!arrDevices.includes(deviceId)) {
46 | notFoundIdDevices.push(deviceId);
47 | }
48 | });
49 | return ApiError.badRequest(res.json(`Some Devices of id(${notFoundIdDevices.join(', ')}) not exist in DB`));
50 | }
51 |
52 | return res.json("Thank you for you order! We will contact you shortly");
53 | } catch (e) {
54 | return res.json(e);
55 | }
56 | }
57 |
58 | async updateOrder(req, res) {
59 | try {
60 | const { complete, id } = req.body;
61 |
62 | await Orders.findOne({where:{id}})
63 | .then( async data => {
64 | if(data) {
65 | await Orders.update({complete}, {where:{id}} ).then(() => {
66 | return res.json("Order updated");
67 | })
68 | } else {
69 | return res.json("This order doesn't exist in DB");
70 | }
71 | })
72 | } catch (e) {
73 | return res.json("Updated didn't complete because was error: " + e);
74 | }
75 |
76 | }
77 |
78 | async deleteOrder(req, res) {
79 | try {
80 | const { id } = req.body;
81 |
82 | await Orders.findOne({where:{id}})
83 | .then( async data => {
84 | if(data) {
85 | await Orders.destroy({where:{id}}).then(() => {
86 | return res.json("Order deleted");
87 | })
88 | } else {
89 | return res.json("This order doesn't exist in DB");
90 | }
91 | })
92 | } catch (e) {
93 | return res.json("Delete didn't complete because was error: " + e);
94 | }
95 | }
96 |
97 | async getAll(req, res) {
98 | let {limit, page, complete} = req.query;
99 | page = page || 1;
100 | limit = limit || 7;
101 | let offset = page * limit - limit;
102 | let devices;
103 | if(complete === "not-completed") {
104 | devices = await Orders.findAndCountAll({where:{complete: false}, limit, offset});
105 | } else if(complete === "completed") {
106 | devices = await Orders.findAndCountAll({where:{complete: true}, limit, offset});
107 | } else {
108 | devices = await Orders.findAndCountAll({limit, offset});
109 | }
110 |
111 | return res.json(devices);
112 | }
113 |
114 | async getOne(req, res) {
115 | const {id} = req.params;
116 | const order = {};
117 | try {
118 | let devices;
119 | let infoDevices = [];
120 | await Orders.findOne({where:{id}}).then(async data => {
121 | order.descr = data;
122 | devices = await OrderDevice.findAll({
123 | attributes: ["deviceId", "count"],
124 | where:{orderId: data.id},
125 | });
126 |
127 | for (let device of devices) {
128 | await Device.findOne({
129 | attributes: ["name", "img", "price"],
130 | where: {id: device.deviceId},
131 | include: [
132 | {
133 | attributes: ["name"],
134 | model: Type
135 | },
136 | {
137 | attributes: ["name"],
138 | model: Brand
139 | },
140 | ]
141 | }).then(async item => {
142 | let newObj = {
143 | descr: item,
144 | count: device.count
145 | }
146 | infoDevices.push(newObj);
147 | });
148 | }
149 | order.devices = infoDevices;
150 |
151 | return res.json(order);
152 | }).catch(() => {
153 | return res.json("Order doesn't exist in data base");
154 | });
155 |
156 | } catch (e) {
157 | return res.json("Delete didn't complete because was error: " + e);
158 | }
159 | }
160 | }
161 |
162 | module.exports = new OrdersController();
163 |
--------------------------------------------------------------------------------
/front/src/components/modals/CreateDevice.js:
--------------------------------------------------------------------------------
1 | import React, {useContext, useEffect, useState} from 'react';
2 | import {Context} from "../../index";
3 | import {Button, Col, Dropdown, Form, Modal, Row} from "react-bootstrap";
4 | import {createDevice, fetchBrands, fetchTypes} from "../../http/deviceAPI";
5 | import {observer} from "mobx-react-lite";
6 |
7 | const CreateDevice = observer(({show, onHide}) => {
8 | const {device} = useContext(Context);
9 | const [name, setName] = useState('');
10 | const [price, setPrice] = useState(0);
11 | const [file, setFile] = useState(null);
12 | const [info, setInfo] = useState([]);
13 |
14 | useEffect(() => {
15 | fetchTypes().then(data => device.setTypes(data));
16 | fetchBrands().then(data => device.setBrands(data));
17 | }, []);
18 |
19 | const addInfo = () => {
20 | setInfo([...info, {title: '', description: '', number: Date.now()}]);
21 | };
22 |
23 | const deleteInfo = (number) => {
24 | setInfo(info.filter(item => item.number !== number));
25 | };
26 |
27 | const changeInfo = (key, value, number) => {
28 | setInfo(info.map(i => i.number === number ? {...i, [key]: value} : i));
29 | };
30 |
31 | const selectFile = e => {
32 | setFile(e.target.files[0]);
33 | };
34 |
35 | const addDevice = () => {
36 | const formData = new FormData();
37 | formData.append('name', name);
38 | formData.append('price', `${price}`);
39 | formData.append('img', file);
40 | formData.append('brandId', device.selectedBrand.id);
41 | formData.append('typeId', device.selectedType.id);
42 | formData.append('info', JSON.stringify(info));
43 | createDevice(formData).then(() => onHide());
44 | }
45 |
46 | return (
47 |
53 |
54 |
55 | Add new Device
56 |
57 |
58 |
59 | setName(e.target.value)}
89 | className="mt-3"
90 | placeholder="Input name your device..."
91 | />
92 | setPrice(Number(e.target.value))}
95 | className="mt-3"
96 | placeholder="Input name price for the device..."
97 | type="number"
98 | />
99 |
104 |
105 |
111 | {info.map(item =>
112 |
113 |
114 | changeInfo('title', e.target.value, item.number)}
118 | />
119 |
120 |
121 | changeInfo('description', e.target.value, item.number)}
125 | />
126 |
127 |
128 |
134 |
135 |
136 | )}
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | );
145 | });
146 |
147 | export default CreateDevice;
148 |
--------------------------------------------------------------------------------
/front/src/components/itemOneorderInAdmin.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import {Button, Col, ListGroup, Modal, Row} from "react-bootstrap";
3 | import {NavLink} from "react-router-dom";
4 | import {fetchChangeStatusOrder, fetchDeleteOrder} from "../http/ordersAPI";
5 | import {ORDERS_ROUTE} from "../utils/consts";
6 |
7 | const ItemOneOrderInAdmin = ({id, complete, mobile, createdAt, updatedAt, userId, reRender}) => {
8 | const [modalDelete, setShowDelete] = useState(false);
9 | const [modalStatus, setShowStatus] = useState(false);
10 |
11 | //modal delete
12 | const handleCloseDelete = () => setShowDelete(false);
13 | const handleShowDelete = () => setShowDelete(true);
14 | const deleteOrder = () => {
15 | fetchDeleteOrder({id}).then(() => {
16 | setShowStatus(false);
17 | setTimeout(() => reRender(), 250);
18 | })
19 | }
20 |
21 | //modal status
22 | const handleCloseStatus = () => setShowStatus(false);
23 | const handleShowStatus = () => setShowStatus(true);
24 | const changeStatusOrder = () => {
25 | fetchChangeStatusOrder({complete: !complete, id}).then(() => {
26 | setShowStatus(false);
27 | setTimeout(() => reRender(), 250);
28 | })
29 | }
30 |
31 | //Format date (createdAt)
32 | const formatDate = (propsDate) => {
33 | const date = new Date(Date.parse(propsDate));
34 | const options = {
35 | weekday: "short",
36 | hour: 'numeric',
37 | minute: 'numeric',
38 | year: 'numeric',
39 | month: 'numeric',
40 | day: 'numeric',
41 | timezone: 'UTC'
42 | };
43 | return date.toLocaleString("en-US", options);
44 | }
45 |
46 | return (
47 | <>
48 |
49 |
50 |
51 |
52 |
53 | id: {id}
54 |
55 |
56 |
57 |
58 | Phone: {mobile}
59 |
60 |
61 |
62 |
63 | Order created: {formatDate(createdAt)}
64 |
65 |
66 | {complete ?
67 |
68 | Order completed: {formatDate(updatedAt)}
69 |
70 |
: false}
71 |
72 |
73 | {userId ? "Buyer: Registered" : "Buyer: Not registered"}
74 |
75 |
76 |
77 |
78 | Status: {complete ? "Completed" : "In progress"}
79 |
80 |
81 |
82 |
83 |
84 |
85 | {complete ?
86 |
87 | :
88 | }
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | {/*modal confirm change status*/}
99 |
100 |
101 | Please confirm
102 |
103 |
104 | Do you want changes status for this order(id: {id}), from {complete ? '\'Completed\'' : '\'In progress\''} to {complete ? '\'In progress\'' : '\'Completed\''}?
105 |
106 | Data:
107 |
108 | - mobile: {mobile}
109 | - Order CreatedAt: {formatDate(createdAt)}
110 | {complete ? `Order completed: ${formatDate(updatedAt)}` : false}
111 | - Status: {complete ? 'Completed' : `In progress`}
112 | - {userId ? 'Buyer registered' : `Buyer not registered`}
113 |
114 |
115 |
116 |
119 |
122 |
123 |
124 |
125 | {/*modal confirm delete order*/}
126 |
127 |
128 | Please confirm
129 |
130 |
131 | Do you want delete this order(id: {id})?
132 |
133 | Data:
134 |
135 | - mobile: {mobile}
136 | - Order CreatedAt: {formatDate(createdAt)}
137 | {complete ? `Order completed: ${formatDate(updatedAt)}` : false}
138 | - Status: {complete ? 'Completed' : `In progress`}
139 | - {userId ? 'Buyer registered' : `Buyer not registered`}
140 |
141 |
142 |
143 |
146 |
149 |
150 |
151 | >
152 | )
153 | };
154 |
155 | export default ItemOneOrderInAdmin;
156 |
--------------------------------------------------------------------------------
/front/src/pages/Admin.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import {
3 | Button,
4 | Col,
5 | Container,
6 | Dropdown,
7 | Form,
8 | Image,
9 | InputGroup,
10 | ListGroup,
11 | Pagination,
12 | Row
13 | } from "react-bootstrap";
14 |
15 | import CreateDevice from "../components/modals/CreateDevice";
16 | import CreateBrand from "../components/modals/CreateBrand";
17 | import CreateType from "../components/modals/CreateType";
18 | import {getAllDevicesInAdminPage} from "../http/deviceAPI";
19 | import {NavLink} from "react-router-dom";
20 | import {DEVICE_EDIT_ROUTE} from "../utils/consts";
21 | import DeleteBrandOrType from "../components/modals/DeleteBrandOrType";
22 |
23 | const Admin = () => {
24 | const [brandVisible, setBrandVisible] = useState(false);
25 | const [typeVisible, setTypeVisible] = useState(false);
26 | const [deviceVisible, setDeviceVisible] = useState(false);
27 | const [deleteBrandOrType, setDeleteBrandOrType] = useState(false);
28 |
29 | const [searchDevice, setSearchDevice] = useState('');
30 | const [searchedDevice, setSearchedDevice] = useState([]);
31 | const [filter, setFilter] = useState("All");
32 | const [currentPage, setCurrentPage] = useState(1);
33 | const [count, setCount] = useState(1);
34 |
35 | const [successMsg, setSuccessMsg] = useState('');
36 | const [showSuccessMsg, setShowSuccessMsg] = useState(false);
37 |
38 | //pagination
39 | const limit = 5;
40 | const pageCount = Math.ceil(Number(count) / limit);
41 | const pages = [];
42 | for (let number = 1; number < pageCount + 1; number++) {
43 | pages.push(
44 | setCurrentPage(number)}>
45 | {number}
46 |
47 | );
48 | }
49 |
50 |
51 | useEffect(() => {
52 | getAllDevicesInAdminPage(searchDevice, currentPage, filter).then(({count, rows}) => {
53 | setSearchedDevice(rows);
54 | setCount(count)
55 | })
56 | }, [currentPage])
57 |
58 | useEffect(() => {
59 | getAllDevicesInAdminPage(searchDevice, 1, filter).then(({count, rows}) => {
60 | setSearchedDevice(rows);
61 | setCount(count);
62 | setCurrentPage(1);
63 | })
64 | }, [filter, successMsg])
65 |
66 |
67 | const fetchDevice = () => {
68 | getAllDevicesInAdminPage(searchDevice, currentPage, filter).then(({count, rows}) => {
69 | setSearchedDevice(rows);
70 | setCount(count)
71 | })
72 | };
73 |
74 | const showSuccessMsgFunc = (msg) => {
75 | setSuccessMsg(msg);
76 | setShowSuccessMsg(true);
77 | setTimeout(() => setShowSuccessMsg(false), 5000);
78 | }
79 |
80 | return (
81 |
82 | {showSuccessMsg && {successMsg}
}
83 |
90 |
97 |
104 |
111 | setDeviceVisible(false)}/>
112 | setBrandVisible(false)}/>
113 | setTypeVisible(false)}/>
114 | setDeleteBrandOrType(false)} showSuccessMsgFunc={showSuccessMsgFunc}/>
115 |
116 |
117 |
118 | {filter}
119 |
120 |
121 |
122 | {filter === "All" ? All : setFilter("All")}>All}
123 | {filter === "Without Brand or Type" ? Without Brand or Type : setFilter("Without Brand or Type")}>Without Brand or Type}
124 |
125 |
126 |
127 |
128 | setSearchDevice(e.target.value)}
133 | placeholder="Input device name..."
134 | />
135 |
142 |
143 |
144 |
145 | {searchedDevice && searchedDevice.map( ({id, img, brand, type, price, name}) => {
146 | return (
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 | id: {id}
156 |
157 |
158 |
159 |
160 | Name: {name}
161 |
162 |
163 |
164 |
165 | Price: {price}
166 |
167 |
168 |
169 |
170 | Brand: {brand.name}
171 |
172 |
173 |
174 |
175 | Type: {type.name}
176 |
177 |
178 |
179 |
180 | Edit
181 |
182 |
183 |
184 | )
185 | })}
186 |
187 |
188 |
189 | {searchedDevice && searchedDevice.length > 0 ? pages : false}
190 |
191 |
192 | );
193 | };
194 |
195 | export default Admin;
196 |
--------------------------------------------------------------------------------
/backend/controllers/deviceController.js:
--------------------------------------------------------------------------------
1 | const { Op } = require("sequelize");
2 | const uuid = require('uuid');
3 | const path = require('path');
4 | const {Device, DeviceInfo, Type, Brand, OrderDevice, BasketDevice} = require('../models/models');
5 | const apiError = require('./../error/apiError');
6 |
7 | class DeviceController {
8 | async create(req, res, next) {
9 | try {
10 | let {name, price, brandId, typeId, info} = req.body;
11 | const {img} = req.files;
12 | let fileName = uuid.v4() + ".jpg";
13 | img.mv(path.resolve(__dirname, '..', 'static', fileName));
14 | const device = await Device.create({
15 | name,
16 | price,
17 | brandId,
18 | typeId,
19 | img: fileName
20 | });
21 |
22 | if(info) {
23 | info = JSON.parse(info);
24 | info.forEach(i => {
25 | DeviceInfo.create({
26 | title: i.title,
27 | description: i.description,
28 | deviceId: device.id
29 | })
30 | })
31 | }
32 |
33 | return res.json(device);
34 | } catch (e) {
35 | next(apiError.badRequest(e.message));
36 | }
37 |
38 | }
39 |
40 | async getAll(req, res,next) {
41 | try {
42 | let {brandId, typeId, limit, page} = req.query;
43 | page = page || 1
44 | limit = limit || 9
45 | let offset = page * limit - limit
46 | let devices;
47 | if (!brandId && !typeId) {
48 | devices = await Device.findAndCountAll({
49 | include: [
50 | {model: Brand},
51 | {model: Type},
52 | ],
53 | limit,
54 | offset})
55 | }
56 | if (brandId && !typeId) {
57 | devices = await Device.findAndCountAll({
58 | where:{brandId},
59 | include: [
60 | {model: Brand},
61 | {model: Type},
62 | ],
63 | limit,
64 | offset
65 | })}
66 | if (!brandId && typeId) {
67 | devices = await Device.findAndCountAll({
68 | where:{typeId},
69 | include: [
70 | {model: Brand},
71 | {model: Type},
72 | ],
73 | limit,
74 | offset
75 | })}
76 | if (brandId && typeId) {
77 | devices = await Device.findAndCountAll({
78 | where:{typeId, brandId},
79 | include: [
80 | {model: Brand},
81 | {model: Type},
82 | ],
83 | limit,
84 | offset
85 | })}
86 | return res.json(devices)
87 | } catch (e) {
88 | next(apiError.badRequest(e.message));
89 | }
90 | }
91 |
92 | async getSearchAllDeviceByName(req, res, next) {
93 | try {
94 | let {limit, page, name, filter} = req.query;
95 |
96 | page = page || 1;
97 | limit = limit || 7;
98 | let offset = page * limit - limit
99 | if(filter === "All") {
100 | const devices = await Device.findAndCountAll({
101 | attributes: ["name", "price", "img", "id"],
102 | where:
103 | {
104 | name: {
105 | [Op.like]: `%${name}%`
106 | }
107 | },
108 | include: [
109 | {
110 | attributes: ["name"],
111 | model: Brand
112 | },
113 | {
114 | attributes: ["name"],
115 | model: Type
116 | },
117 | ],
118 | limit,
119 | offset,
120 | })
121 |
122 | return res.json(devices);
123 | } else {
124 | const devices = await Device.findAndCountAll({
125 | attributes: ["name", "price", "img", "id", "brandId", "typeId"],
126 | where:
127 | {
128 | name: {
129 | [Op.like]: `%${name}%`
130 | },
131 | [Op.or]: [
132 | {
133 | brandId: null,
134 | },
135 | {
136 | typeId: null,
137 | },
138 | ],
139 | },
140 | include: [
141 | {
142 | attributes: ["name"],
143 | model: Brand
144 | },
145 | {
146 | attributes: ["name"],
147 | model: Type
148 | },
149 | ],
150 | limit,
151 | offset,
152 | })
153 |
154 |
155 | return res.json(devices);
156 | }
157 | } catch (e) {
158 | next(apiError.badRequest(e.message));
159 | }
160 | }
161 |
162 | async getOne(req, res, next) {
163 | try {
164 | const {id} = req.params;
165 | let devices = await Device.findOne({
166 | where: {id},
167 | include: [
168 | {model: DeviceInfo, as: 'info'},
169 | {model: Type},
170 | {model: Brand},
171 | ]
172 | });
173 | return res.json(devices);
174 | } catch (e) {
175 | next(apiError.badRequest(e.message));
176 | }
177 | }
178 |
179 | async delete(req, res) {
180 | try {
181 | const {id} = req.params;
182 | await Device.findOne({where:{id}})
183 | .then( async data => {
184 | if(data) {
185 | await Device.destroy({where:{id}}).then(() => {
186 | return res.json("Device deleted");
187 | })
188 | } else {
189 | return res.json("This Device doesn't exist in DB");
190 | }
191 |
192 | await OrderDevice.destroy({where:{deviceId: id}})
193 | await BasketDevice.destroy({where:{deviceId: id}})
194 | })
195 | } catch (e) {
196 | return res.json(e);
197 | }
198 | }
199 |
200 | async update(req, res) {
201 | try {
202 | const {id} = req.params;
203 | const {brandId, typeId, name, price, info} = req.body;
204 |
205 | await Device.findOne({where:{id}})
206 | .then( async data => {
207 | if(data) {
208 | let newVal = {};
209 | brandId ? newVal.brandId = brandId : false;
210 | typeId ? newVal.typeId = typeId : false;
211 | name ? newVal.name = name : false;
212 | price ? newVal.price = price : false;
213 |
214 | if(req.files) {
215 | const {img} = req.files;
216 | const type = img.mimetype.split('/')[1];
217 | let fileName = uuid.v4() + `.${type}`;
218 | img.mv(path.resolve(__dirname, '..', 'static', fileName));
219 | newVal.img = fileName;
220 | }
221 |
222 | if(info) {
223 | const parseInfo = JSON.parse(info);
224 | for (const item of parseInfo) {
225 | await DeviceInfo.findOne({where:{id: item.id}}).then( async data => {
226 | if(data) {
227 | await DeviceInfo.update({
228 | title: item.title,
229 | description: item.description
230 | }, {where:{id: item.id}})
231 | } else {
232 | await DeviceInfo.create({
233 | title: item.title,
234 | description: item.description,
235 | deviceId: id
236 | })
237 | }
238 | })
239 | }
240 | }
241 |
242 | await Device.update({
243 | ...newVal
244 | }, {where:{id}} ).then(() => {
245 | return res.json("Device updated");
246 | })
247 | } else {
248 | return res.json("This Device doesn't exist in DB");
249 | }
250 | })
251 | } catch (e) {
252 | return res.json(e);
253 | }
254 | }
255 | }
256 |
257 | module.exports = new DeviceController();
258 |
--------------------------------------------------------------------------------
/front/src/pages/DevicePageEdit.js:
--------------------------------------------------------------------------------
1 | import React, {useContext, useEffect, useState} from 'react';
2 | import {Button, Col, Container, Dropdown, Form, Image, Modal, Row} from "react-bootstrap";
3 | import {useParams, useHistory} from 'react-router-dom';
4 | import {fetchDeleteDevice, fetchOneDevice, updateDevices} from "../http/deviceAPI";
5 | import {Context} from "../index";
6 | import {ADMIN_ROUTE} from "../utils/consts";
7 |
8 |
9 | const DevicePageEdit = () => {
10 | const {device} = useContext(Context);
11 | const history = useHistory();
12 | const {id} = useParams();
13 | const [deviceCurr, setDeviceCurr] = useState({});
14 | const [showMsg, setShowMsg] = useState(false);
15 | const [msg, setMsg] = useState("");
16 |
17 | const [selectBrand, setSelectBrand] = useState({});
18 | const [selectType, setSelectType] = useState({});
19 | const [name, setName] = useState("");
20 | const [price, setPrice] = useState(0);
21 | const [img, setImg] = useState("");
22 | const [imgFile, setImgFile] = useState(null);
23 | const [info, setInfo] = useState([]);
24 |
25 | const [isDisabledPutBtn, setDisabledPutBtn] = useState(true);
26 |
27 | const deleteDevice = () => {
28 | fetchDeleteDevice(id).then(() => {
29 | history.push(ADMIN_ROUTE);
30 | })
31 | }
32 |
33 | const [show, setShow] = useState(false);
34 | const handleClose = () => setShow(false);
35 | const handleShow = () => setShow(true);
36 |
37 | const imgHandler = e => {
38 | e.preventDefault();
39 |
40 | const reader = new FileReader();
41 | reader.onload = () => {
42 | setImg(reader.result);
43 | };
44 | reader.readAsDataURL(e.target.files[0]);
45 | setImgFile(e.target.files[0]);
46 | }
47 |
48 | //info
49 | const addInfo = () => {
50 | setInfo([...info, {title: '', description: '', id: Date.now()}]);
51 | };
52 |
53 | const deleteInfo = (number) => {
54 | setInfo(info.filter(item => item.number !== number));
55 | };
56 |
57 | const changeInfo = (key, value, number) => {
58 | setInfo(info.map(i => i.id === number ? {...i, [key]: value} : i));
59 | };
60 |
61 | const putDevice = () => {
62 | const formData = new FormData();
63 | formData.append('name', name);
64 | formData.append('price', `${price}`);
65 | formData.append('img', imgFile);
66 | formData.append('brandId', selectBrand.id);
67 | formData.append('typeId', selectType.id);
68 | formData.append('info', JSON.stringify(info));
69 | updateDevices(id, formData).then(data => {
70 | setShowMsg(true);
71 | setMsg(data);
72 | setTimeout(() => setShowMsg(true), 5000)
73 | });
74 | }
75 |
76 | const checkInfo = () => {
77 | let isInfoEmpty = true;
78 | info.forEach(item => {
79 | for(let key in item) {
80 | if(key === "title" || key === "description") {
81 | if(!item[key]) {
82 | isInfoEmpty = false;
83 | }
84 | }
85 | }
86 | });
87 | return isInfoEmpty;
88 | }
89 |
90 | useEffect(() => {
91 | let checkInfoVal = false;
92 | if(deviceCurr.info && deviceCurr.info.length !== info.length) {
93 | checkInfoVal = checkInfo();
94 | }
95 |
96 | if(deviceCurr && deviceCurr.brand && deviceCurr.type) {
97 | if(deviceCurr.brand.name !== selectBrand.name ||
98 | deviceCurr.type.name !== selectType.name ||
99 | deviceCurr.name !== name ||
100 | deviceCurr.price !== price ||
101 | checkInfoVal ||
102 | img
103 | ) {
104 | setDisabledPutBtn(false);
105 | } else {
106 | setDisabledPutBtn(true);
107 | }
108 | }
109 | }, [name, selectBrand, selectType, price, img, info]);
110 |
111 | useEffect(() => {
112 | fetchOneDevice(id).then(data => {
113 | setDeviceCurr(data);
114 | setSelectBrand(data.brand);
115 | setSelectType(data.type);
116 | setName(data.name);
117 | setPrice(data.price);
118 | setInfo(data.info)
119 | });
120 | }, [id]);
121 |
122 | return (
123 |
124 | {showMsg &&
125 | {msg}
126 |
}
127 |
128 |
129 |
130 |
131 |
132 | id:
133 |
134 |
135 | {deviceCurr.id}
136 |
137 |
138 | {/*Brand*/}
139 |
140 |
141 | Brand:
142 |
143 |
144 |
145 | {selectBrand.name || "Choose Brand"}
146 |
147 | {device.brands.map(brand => {
148 | return brand.name === selectBrand.name ?
149 |
153 | {brand.name}
154 |
155 | :
156 | setSelectBrand(brand)}
159 | >
160 | {brand.name}
161 |
162 |
163 | })}
164 |
165 |
166 |
167 |
168 | {/*Type*/}
169 |
170 |
171 | Types:
172 |
173 |
174 |
175 | {selectType.name || "Choose Type"}
176 |
177 | {device.types.map(type => {
178 | return type.name === selectType.name ?
179 |
183 | {type.name}
184 |
185 | :
186 | setSelectType(type)}
189 | >
190 | {type.name}
191 |
192 |
193 | })}
194 |
195 |
196 |
197 |
198 | {/*Name*/}
199 |
200 |
201 | Name:
202 |
203 |
204 | setName(e.target.value)}
208 | />
209 |
210 |
211 | {name.length === 0 && Please input name of device}
212 |
213 |
214 | {/*Name*/}
215 |
216 |
217 | Price:
218 |
219 |
220 | setPrice(Number(e.target.value))}
224 | />
225 |
226 |
227 | {price === 0 && Please input price of device}
228 |
229 |
230 |
231 | {/*Name*/}
232 |
233 |
234 | Current Img:
235 |
236 |
237 | {img &&
238 | New Img:
239 |
240 | }
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 | {/*Characteristics*/}
251 |
252 | Characteristics
253 |
259 | {info.map((item, index) =>
260 |
261 |
262 | changeInfo('title', e.target.value, item.id)}
266 | />
267 | {!info[index].title && Please input name}
268 |
269 |
270 | changeInfo('description', e.target.value, item.id)}
274 | />
275 | {!info[index].description && Please input description}
276 |
277 |
278 |
284 |
285 |
286 | )}
287 |
288 |
289 |
290 |
291 | {isDisabledPutBtn ? : }
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 | Delete this device {deviceCurr.id}?
301 |
302 |
303 |
306 |
309 |
310 |
311 |
312 | );
313 | };
314 |
315 | export default DevicePageEdit;
316 |
317 |
--------------------------------------------------------------------------------