laba5
This commit is contained in:
parent
51d0c67bdd
commit
4fbf84e1a3
21
lab5/.eslintrc.cjs
Normal file
21
lab5/.eslintrc.cjs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react/jsx-runtime',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
|
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||||
|
settings: { react: { version: '18.2' } },
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
},
|
||||||
|
}
|
21
lab5/.eslintrc.json
Normal file
21
lab5/.eslintrc.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": "airbnb-base",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 12,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"quotes": "off",
|
||||||
|
"indent": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-use-before-define": "off",
|
||||||
|
"no-alert": "off",
|
||||||
|
"no-restricted-globals": "off",
|
||||||
|
"quote-props": "off"
|
||||||
|
}
|
||||||
|
}
|
24
lab5/.gitignore
vendored
Normal file
24
lab5/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
8
lab5/README.md
Normal file
8
lab5/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# React + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
17
lab5/index.html
Normal file
17
lab5/index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Lab. 5</title>
|
||||||
|
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||||
|
<link href="node_modules/@fortawesome/fontawesome-free/css/all.min.css" rel="stylesheet" />
|
||||||
|
<link rel="stylesheet" href="./src/assets/styles/style.css">
|
||||||
|
<script type="module" src="node_modules/bootstrap/dist/js/bootstrap.min.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body class="d-flex flex-column h-100" style="min-height: 100vh;">
|
||||||
|
<div id="root" class="d-flex flex-column h-100" style="flex: 1 0 auto"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
5663
lab5/package-lock.json
generated
Normal file
5663
lab5/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
lab5/package.json
Normal file
40
lab5/package.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "project",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"server": "json-server -w server/data.json -p 8081",
|
||||||
|
"app": "npm-run-all --parallel dev server"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"axios": "^1.6.5",
|
||||||
|
"bootstrap": "^5.3.2",
|
||||||
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
|
"json-server": "^0.17.4",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-form": "^7.49.3",
|
||||||
|
"react-router-dom": "^6.19.0",
|
||||||
|
"url": "^0.11.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.2.37",
|
||||||
|
"@types/react-dom": "^18.2.15",
|
||||||
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
|
"eslint": "^8.53.0",
|
||||||
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.4",
|
||||||
|
"vite": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
108
lab5/server/data.json
Normal file
108
lab5/server/data.json
Normal file
File diff suppressed because one or more lines are too long
12
lab5/src/App.jsx
Normal file
12
lab5/src/App.jsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Navigation from "./components/navigation/Navigation";
|
||||||
|
import Contexts from "./contexts/Contexts";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<Contexts>
|
||||||
|
<Navigation />
|
||||||
|
</Contexts>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
BIN
lab5/src/assets/Images/flower.jpg
Normal file
BIN
lab5/src/assets/Images/flower.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 136 KiB |
BIN
lab5/src/assets/Images/logo.png
Normal file
BIN
lab5/src/assets/Images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
BIN
lab5/src/assets/Images/rose.jpg
Normal file
BIN
lab5/src/assets/Images/rose.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
60
lab5/src/assets/styles/style.css
Normal file
60
lab5/src/assets/styles/style.css
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
header nav {
|
||||||
|
background-color: #e8fae0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
header nav {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header nav a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand img {
|
||||||
|
margin-left: 235px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.navbar-brand img {
|
||||||
|
margin-left: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 577px) and (max-width: 992px) {
|
||||||
|
.navbar-brand img {
|
||||||
|
margin-left: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 993px) and (max-width: 1200px) {
|
||||||
|
.navbar-brand img {
|
||||||
|
margin-left: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1201px) {
|
||||||
|
.navbar-brand img {
|
||||||
|
margin-left: 235px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background-color: #7bca8c !important;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 35px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: #FFFFFF !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background-color: #e8fae0;
|
||||||
|
color: #686464;
|
||||||
|
}
|
9
lab5/src/axios.js
Normal file
9
lab5/src/axios.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
import { serverPath } from "./variables/variables";
|
||||||
|
|
||||||
|
const $axios = axios.create({
|
||||||
|
baseURL: serverPath
|
||||||
|
});
|
||||||
|
|
||||||
|
export default $axios;
|
9
lab5/src/components/layout/Footer.jsx
Normal file
9
lab5/src/components/layout/Footer.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="footer mt-auto d-flex flex-shrink-0 justify-content-center align-items-center">
|
||||||
|
Все права защищены © 2023-2024
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Footer;
|
73
lab5/src/components/layout/Header.jsx
Normal file
73
lab5/src/components/layout/Header.jsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import Logo from "../../assets/Images/logo.png";
|
||||||
|
import { UserContext } from '../../contexts/userContext';
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
function Header() {
|
||||||
|
const { isAuth, isAdmin, logout } = useContext(UserContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<nav className="navbar navbar-expand-md">
|
||||||
|
<div className="container-fluid">
|
||||||
|
<Link className="navbar-brand" to="/">
|
||||||
|
<img src={Logo} alt="logo" width="128" />
|
||||||
|
</Link>
|
||||||
|
<button className="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span className="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div className="navbar-collapse collapse justify-content-start" id="navbarNav">
|
||||||
|
<div className="navbar-nav">
|
||||||
|
<Link className="nav-link" to="/">Каталог</Link>
|
||||||
|
<Link className="nav-link" to="/aboutUs">О нас</Link>
|
||||||
|
<Link className="nav-link" to="/paymentAndDelivery">Оплата и доставка</Link>
|
||||||
|
<Link className="nav-link" to="/contacts">Контакты</Link>
|
||||||
|
<HeaderInput />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="navbar-collapse collapse justify-content-end" id="navbarNav">
|
||||||
|
<div className="navbar-nav">
|
||||||
|
<Link
|
||||||
|
className="btn"
|
||||||
|
to="/login"
|
||||||
|
onClick={isAuth ? logout : undefined}
|
||||||
|
>
|
||||||
|
{isAuth ? "Выйти" : "Войти"}
|
||||||
|
</Link>
|
||||||
|
<Link className="btn" to="/basket">Корзина</Link>
|
||||||
|
<Link className="btn" to="/orders">Заказы</Link>
|
||||||
|
{isAdmin && (<Link className="btn" to="/admin">Админка</Link>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HeaderInput() {
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Функции
|
||||||
|
function handleEnterDown(e) {
|
||||||
|
if (e.key !== 'Enter') return;
|
||||||
|
|
||||||
|
const value = inputRef.current.value;
|
||||||
|
if (!value) return;
|
||||||
|
|
||||||
|
navigate(`/search?term=${value}`);
|
||||||
|
}
|
||||||
|
// Функции END
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="d-flex mb-2 mb-sm-0" role="search" onSubmit={(e) => e.preventDefault()}>
|
||||||
|
<input className="form-control" type="search" placeholder="Поиск товара..." aria-label="Search" onKeyDown={handleEnterDown} ref={inputRef} />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header;
|
37
lab5/src/components/layout/Layout.jsx
Normal file
37
lab5/src/components/layout/Layout.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
import Header from "./Header";
|
||||||
|
import Footer from "./Footer";
|
||||||
|
import { useContext, useEffect } from "react";
|
||||||
|
import { UserContext } from './../../contexts/userContext';
|
||||||
|
import { BasketContext } from './../../contexts/basketContext';
|
||||||
|
import { FavouritesContext } from './../../contexts/favouritesContext';
|
||||||
|
import { OrdersContext } from "../../contexts/ordersContext";
|
||||||
|
|
||||||
|
function Layout() {
|
||||||
|
const { id, checkIsAuth } = useContext(UserContext);
|
||||||
|
const { fetchBasketAndProducts } = useContext(BasketContext);
|
||||||
|
const { fetchFavourites } = useContext(FavouritesContext);
|
||||||
|
const { fetchOrders } = useContext(OrdersContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchBasketAndProducts();
|
||||||
|
fetchFavourites();
|
||||||
|
fetchOrders();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkIsAuth();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<main>
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
42
lab5/src/components/navigation/Navigation.jsx
Normal file
42
lab5/src/components/navigation/Navigation.jsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||||
|
import Layout from '../layout/Layout';
|
||||||
|
import Admin from "../screens/Admin/Admin";
|
||||||
|
import Basket from "../screens/Basket/Basket";
|
||||||
|
import Contacts from "../screens/Contacts/Contacts";
|
||||||
|
import Home from '../screens/Home/Home';
|
||||||
|
import PaymentAndDelivery from "../screens/PaymentAndDelivery/PaymentAndDelivery";
|
||||||
|
import Registration from "../screens/Registration/Registration";
|
||||||
|
import AboutUs from './../screens/AboutUs/AboutUs';
|
||||||
|
import Login from './../screens/Login/Login';
|
||||||
|
import MakingAnOrder from './../screens/MakingAnOrder/MakingAnOrder';
|
||||||
|
import PageEdit from './../screens/PageEdit/PageEdit';
|
||||||
|
import PersonalAccount from './../screens/PersonalAccount/PersonalAccount';
|
||||||
|
import Search from '../screens/Search/Search';
|
||||||
|
|
||||||
|
// Привязываем компоненты-страницы к путям
|
||||||
|
function Navigation() {
|
||||||
|
return (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
{/* Весь сайт оборачиваем в шаблон <Layout /> */}
|
||||||
|
<Route path="/" element={<Layout />}>
|
||||||
|
<Route path="/aboutUs" element={<AboutUs />} />
|
||||||
|
<Route path="/admin" element={<Admin />} />
|
||||||
|
<Route path="/basket" element={<Basket />} />
|
||||||
|
<Route path="/contacts" element={<Contacts />} />
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/paymentAndDelivery" element={<PaymentAndDelivery />} />
|
||||||
|
<Route path="/makingAnOrder" element={<MakingAnOrder />} />
|
||||||
|
<Route path="/pageEdit" element={<PageEdit />} />
|
||||||
|
<Route path="/personalAccount" element={<PersonalAccount />} />
|
||||||
|
<Route path="/search" element={<Search />} />
|
||||||
|
|
||||||
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route path="/registration" element={<Registration />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Navigation;
|
46
lab5/src/components/screens/AboutUs/AboutUs.jsx
Normal file
46
lab5/src/components/screens/AboutUs/AboutUs.jsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
function AboutUs() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-flex justify-content-center align-items-center">
|
||||||
|
<h1 className="text font-weight-bold">О компании</h1>
|
||||||
|
</div>
|
||||||
|
<div className="container">
|
||||||
|
<div className="row">
|
||||||
|
<h2 className="font-weight-bold" style={{ fontSize: "35px", marginLeft: "10px", marginTop: "10px" }}>Наши
|
||||||
|
преимущества:</h2>
|
||||||
|
<ul>
|
||||||
|
<li className="font-weight-bold" style={{ fontSize: "20px", marginLeft: "10px", marginTop: "10px" }}> привозим
|
||||||
|
официально и напрямую из Южной Кореи</li>
|
||||||
|
<li className="font-weight-bold" style={{ fontSize: "20px", marginLeft: "10px", marginTop: "10px" }}>
|
||||||
|
качественная упаковка и обслуживание</li>
|
||||||
|
<li className="font-weight-bold" style={{ fontSize: "20px", marginLeft: "10px", marginTop: "10px" }}> быстрая
|
||||||
|
отправка</li>
|
||||||
|
<li className="font-weight-bold" style={{ fontSize: "20px", marginLeft: "10px", marginTop: "10px" }}> быстрая
|
||||||
|
доставка (в среднем 8 дней по России)</li>
|
||||||
|
<li className="font-weight-bold" style={{ fontSize: "20px", marginLeft: "10px", marginTop: "10px" }}> удобная
|
||||||
|
оплата через сайт</li>
|
||||||
|
<li className="font-weight-bold" style={{ fontSize: "20px", marginLeft: "10px", marginTop: "10px" }}> большой
|
||||||
|
ассортимент в наличии в Ульяновске</li>
|
||||||
|
<li className="font-weight-bold" style={{ fontSize: "20px", marginLeft: "10px", marginTop: "10px" }}> никаких
|
||||||
|
доплат из-за веса, количества, выбора версии и т.д.</li>
|
||||||
|
<li className="font-weight-bold" style={{ fontSize: "20px", marginLeft: "10px", marginTop: "10px" }}> после
|
||||||
|
оплаты заказа на эл. почту Вам приходит чек, подтверждающий Вашу покупку</li>
|
||||||
|
<li className="font-weight-bold" style={{ fontSize: "20px", marginLeft: "10px", marginTop: "10px" }}> работаем
|
||||||
|
почти 8 лет</li>
|
||||||
|
</ul>
|
||||||
|
<p></p>
|
||||||
|
<p></p>
|
||||||
|
<p></p>
|
||||||
|
<p style={{ fontSize: "25px", marginLeft: "10px", marginTop: "10px" }}>За время работы
|
||||||
|
магазина, мы уже продали более 60 000 альбомов.</p>
|
||||||
|
<p style={{ fontSize: "25px", marginLeft: "10px", marginTop: "10px" }}>Также у нас есть
|
||||||
|
физический магазин в Ульяновске, где весь товар можно увидеть в живую!</p>
|
||||||
|
<p style={{ fontSize: "25px", marginLeft: "10px", marginTop: "10px" }}>Адрес магазина:
|
||||||
|
Ульяновск, ул. Северный Венец 32. Время работы: каждый день с 13:00 до 21:00</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AboutUs;
|
28
lab5/src/components/screens/Admin/Admin.jsx
Normal file
28
lab5/src/components/screens/Admin/Admin.jsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useContext, useEffect } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { ProductsContext } from "../../../contexts/productsContext";
|
||||||
|
import { CategoriesContext } from './../../../contexts/categoriesContext';
|
||||||
|
import AdminTable from "./AdminTable";
|
||||||
|
|
||||||
|
function Admin() {
|
||||||
|
const { fetchProducts } = useContext(ProductsContext);
|
||||||
|
const { fetchCategories } = useContext(CategoriesContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchCategories();
|
||||||
|
fetchProducts();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container-fluid text-center p-2">
|
||||||
|
<h1 className="text-center font-weight-bold">Панель администратора</h1>
|
||||||
|
<div className="btn-group" role="group">
|
||||||
|
<Link className="btn" to="/pageEdit">Добавить товар</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AdminTable />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Admin;
|
31
lab5/src/components/screens/Admin/AdminTable.jsx
Normal file
31
lab5/src/components/screens/Admin/AdminTable.jsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { ProductsContext } from "../../../contexts/productsContext";
|
||||||
|
import AdminTableRow from "./AdminTableRow";
|
||||||
|
|
||||||
|
function AdminTable() {
|
||||||
|
const { products } = useContext(ProductsContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-100">
|
||||||
|
<h2 className="text-center font-weight-bold" style={{ paddingTop: "19px" }}>Таблица данных</h2>
|
||||||
|
<table id="items-table" className="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col" className="w-25">Название</th>
|
||||||
|
<th scope="col" className="w-25">Вид</th>
|
||||||
|
<th scope="col" className="w-25">Цена</th>
|
||||||
|
<th scope="col" className="w-25">Фото</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{products && products.map(product => <AdminTableRow product={product} key={product.id} />)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminTable;
|
41
lab5/src/components/screens/Admin/AdminTableRow.jsx
Normal file
41
lab5/src/components/screens/Admin/AdminTableRow.jsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { CategoriesContext } from "../../../contexts/categoriesContext";
|
||||||
|
import { ProductsContext } from "../../../contexts/productsContext";
|
||||||
|
|
||||||
|
function AdminTableRow({ product }) {
|
||||||
|
const { deleteProduct } = useContext(ProductsContext);
|
||||||
|
const { getCategoryById } = useContext(CategoriesContext);
|
||||||
|
const [category, setCategory] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function getCategory() {
|
||||||
|
const response = await getCategoryById(product.categoryId);
|
||||||
|
setCategory(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategory();
|
||||||
|
}, [product]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>{product.id}</td>
|
||||||
|
<td>{product.name}</td>
|
||||||
|
<td>{category && category.name}</td>
|
||||||
|
<td>{product.price}</td>
|
||||||
|
<td><img src={product.photo} alt="" style={{ maxWidth: "150px", maxHeight: "150px" }} className="w-100" /></td>
|
||||||
|
<td>
|
||||||
|
<Link to={`/pageEdit?id=${product.id}`}>
|
||||||
|
<i className="fa-solid fa-pencil"></i>
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button className="bg-transparent border-0" onClick={() => deleteProduct(product.id)}>
|
||||||
|
<i className="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminTableRow;
|
24
lab5/src/components/screens/Basket/Basket.jsx
Normal file
24
lab5/src/components/screens/Basket/Basket.jsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useContext, useState } from "react";
|
||||||
|
import { BasketContext } from './../../../contexts/basketContext';
|
||||||
|
import BasketWithItems from "./BasketWithItems";
|
||||||
|
import BasketHistory from "./BasketHistory";
|
||||||
|
import BasketModal from "./BasketModal";
|
||||||
|
|
||||||
|
function Basket() {
|
||||||
|
const { basketProducts } = useContext(BasketContext);
|
||||||
|
const isCartFull = basketProducts && basketProducts.length != 0;
|
||||||
|
const [currentOrder, setCurrentOrder] = useState(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<div className="d-flex justify-content-center align-items-center">
|
||||||
|
<h1 className="text-success font-weight-bold">Корзина</h1>
|
||||||
|
</div>
|
||||||
|
{isCartFull ? <BasketWithItems /> : (<p>Корзина пуста...</p>)}
|
||||||
|
<BasketHistory setCurrentOrder={setCurrentOrder} />
|
||||||
|
<BasketModal currentOrder={currentOrder} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Basket;
|
25
lab5/src/components/screens/Basket/BasketHistory.jsx
Normal file
25
lab5/src/components/screens/Basket/BasketHistory.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { OrdersContext } from './../../../contexts/ordersContext';
|
||||||
|
import BasketHistoryRow from "./BasketHistoryRow";
|
||||||
|
|
||||||
|
function BasketHistory({ setCurrentOrder }) {
|
||||||
|
const { orders } = useContext(OrdersContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="table-responsive mt-5">
|
||||||
|
<table className="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">ID заказа</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="parent">
|
||||||
|
{orders && orders.map(order => <BasketHistoryRow setCurrentOrder={setCurrentOrder} key={order.id} order={order} />)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasketHistory;
|
21
lab5/src/components/screens/Basket/BasketHistoryRow.jsx
Normal file
21
lab5/src/components/screens/Basket/BasketHistoryRow.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
function BasketHistoryRow({ order, setCurrentOrder }) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>{order.id}</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#exampleModal"
|
||||||
|
onClick={() => setCurrentOrder({ id: order.id, date: order.date })}
|
||||||
|
>
|
||||||
|
Открыть детали
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasketHistoryRow;
|
29
lab5/src/components/screens/Basket/BasketItem.jsx
Normal file
29
lab5/src/components/screens/Basket/BasketItem.jsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { BasketContext } from "../../../contexts/basketContext";
|
||||||
|
|
||||||
|
function BasketItem({ product }) {
|
||||||
|
const { deleteProductFromBasket } = useContext(BasketContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card rounded-3 mb-4">
|
||||||
|
<div className="card-body p-4">
|
||||||
|
<div className="row d-flex justify-content-between align-items-center">
|
||||||
|
<div className="col-md-2 col-lg-2 col-xl-2">
|
||||||
|
<img src={product.photo} className="img-fluid rounded-3 w-100" alt="Flower" style={{ maxWidth: "200px", maxHeight: "200px" }} />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-auto col-lg-3 col-xl-3">
|
||||||
|
<p className="lead fw-normal mb-2">{product.name}</p>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-auto col-lg-2 col-xl-2 offset-lg-1">
|
||||||
|
<h5 className="mb-0">Цена {product.price}₽</h5>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-auto col-lg-0 col-xl-0 text-end">
|
||||||
|
<button className="text-muted btn" onClick={() => deleteProductFromBasket(product.id)}><i className="fas fa-delete-left fa-lg"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BasketItem;
|
62
lab5/src/components/screens/Basket/BasketModal.jsx
Normal file
62
lab5/src/components/screens/Basket/BasketModal.jsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { OrdersContext } from "../../../contexts/ordersContext";
|
||||||
|
|
||||||
|
function BasketModal({ currentOrder }) {
|
||||||
|
//позволяет получать продукты заказа по id заказа
|
||||||
|
const { getOrderProducts } = useContext(OrdersContext);
|
||||||
|
//состояние для таких продуктов
|
||||||
|
const [orderProducts, setOrderProducts] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentOrder) return;
|
||||||
|
|
||||||
|
const func = async () => {
|
||||||
|
// Получение нужных продуктов
|
||||||
|
const response = await getOrderProducts(currentOrder.id);
|
||||||
|
// Обновление состояния
|
||||||
|
setOrderProducts(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
func();
|
||||||
|
}, [currentOrder]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal fade" id="exampleModal" tabIndex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="exampleModalLabel">Детали заказа</h1>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p><b>ID Заказа:</b> {currentOrder ? currentOrder.id : null}</p>
|
||||||
|
<p><b>Дата заказа:</b> {currentOrder ? new Date(currentOrder.date).toLocaleString('ru-RU') : null}</p>
|
||||||
|
<p><b>Товары в заказе:</b></p>
|
||||||
|
<table className="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Название товара</th>
|
||||||
|
<th scope="col">Фото</th>
|
||||||
|
<th scope="col">Цена</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="parent">
|
||||||
|
{orderProducts && orderProducts.map(op => {
|
||||||
|
return (
|
||||||
|
<tr key={op.id}>
|
||||||
|
<td>{op.name}</td>
|
||||||
|
<td><img src={op.photo} alt="" style={{ maxWidth: "100px", maxHeight: "100px" }} /></td>
|
||||||
|
<td>{op.price}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasketModal;
|
49
lab5/src/components/screens/Basket/BasketWithItems.jsx
Normal file
49
lab5/src/components/screens/Basket/BasketWithItems.jsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { BasketContext } from "../../../contexts/basketContext";
|
||||||
|
import { OrdersContext } from './../../../contexts/ordersContext';
|
||||||
|
import BasketItem from './BasketItem';
|
||||||
|
|
||||||
|
function BasketWithItems() {
|
||||||
|
const { basketProducts, getBasketProductsWithTotalPrice } = useContext(BasketContext);
|
||||||
|
const [products, setProducts] = useState(null);
|
||||||
|
const [totalPrice, setTotalPrice] = useState(0);
|
||||||
|
|
||||||
|
const { createOrder } = useContext(OrdersContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDataForCart();
|
||||||
|
}, [basketProducts]);
|
||||||
|
|
||||||
|
async function getDataForCart() {
|
||||||
|
const [prods, total] = await getBasketProductsWithTotalPrice();
|
||||||
|
setProducts(prods);
|
||||||
|
setTotalPrice(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCreateOrder() {
|
||||||
|
const productsIds = products.map(product => product.id);
|
||||||
|
const orderId = await createOrder({ date: Date.now() }, productsIds);
|
||||||
|
alert(`Заказ №${orderId} создан!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-10">
|
||||||
|
{products && products.map(bp => <BasketItem product={bp} key={bp.id} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex justify-content-start align-items-center">
|
||||||
|
<div className="text-dark font-weight-bold" style={{ fontSize: "24px" }}>
|
||||||
|
Сумма заказа:
|
||||||
|
</div>
|
||||||
|
<div className="text-end text-success" style={{ fontSize: "24px" }}>
|
||||||
|
{totalPrice} ₽
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button className="btn" style={{ marginLeft: "25px", marginBottom: "10px" }} onClick={handleCreateOrder}>Оплатить</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasketWithItems;
|
22
lab5/src/components/screens/Contacts/Contacts.jsx
Normal file
22
lab5/src/components/screens/Contacts/Contacts.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
function Contacts() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-flex justify-content-center align-items-center">
|
||||||
|
<h1 className="text-success font-weight-bold">Контакты</h1>
|
||||||
|
</div>
|
||||||
|
<div className="container">
|
||||||
|
<div className="row">
|
||||||
|
<div className="div d-flex justify-content-center">
|
||||||
|
<iframe src="https://yandex.ru/map-widget/v1/?um=constructor%3Afa2d61fb6063802ddf9f646ff254a8764a6dfd399031b696dbda0a7c93474211&source=constructor" className="img-fluid" style={{width: "980px", height: "505px"}}></iframe>
|
||||||
|
</div>
|
||||||
|
<a href="tel:+79273456567" className="text-dark" style={{fontSize: "25px", marginLeft: "10px", marginTop: "10px"}}>+7 (927) 345 65 67</a>
|
||||||
|
<h2 className="fw-bold" style={{ fontSize: "35px", marginLeft: "10px", marginTop: "10px" }}>г.Ульяновск, ул.Гончарова, 40</h2>
|
||||||
|
<p style={{fontSize: "25px", marginLeft: "10px", marginTop: "10px"}}>Пн-Пт: 9:00 - 20:00</p>
|
||||||
|
<p style={{fontSize: "25px", marginLeft: "10px", marginTop: "10px"}}>Сб-Вс: 10:00 - 19:00</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Contacts;
|
44
lab5/src/components/screens/Home/Card.jsx
Normal file
44
lab5/src/components/screens/Home/Card.jsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { ProductType } from '../../../types/ProductType';
|
||||||
|
import { BasketContext } from '../../../contexts/basketContext';
|
||||||
|
import { FavouritesContext } from "../../../contexts/favouritesContext";
|
||||||
|
|
||||||
|
function Card({ product }) {
|
||||||
|
const { isProductInBasket, addProductToBasket, deleteProductFromBasket } = useContext(BasketContext);
|
||||||
|
const isBasketProduct = isProductInBasket(product.id);
|
||||||
|
|
||||||
|
const { isProductInFavourites, addProductToFavourites, deleteProductFromFavourites } = useContext(FavouritesContext);
|
||||||
|
const isFavouriteProduct = isProductInFavourites(product.id)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="col-lg-3 col-md-4 col-sm-6 col-12 mb-4">
|
||||||
|
<div className="card">
|
||||||
|
<img src={product.photo} className="card-img-top" alt="Product Image" />
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title">{product.name}</h5>
|
||||||
|
</div>
|
||||||
|
<div className="card-footer">
|
||||||
|
<div className="text-success font-weight-bold">Цена {product.price}₽</div>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
onClick={!isBasketProduct ? () => addProductToBasket(product.id) : () => deleteProductFromBasket(product.id)}
|
||||||
|
>
|
||||||
|
{isBasketProduct ? "Удалить из корзины" : "В корзину"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
onClick={!isFavouriteProduct ? () => addProductToFavourites(product.id) : () => deleteProductFromFavourites(product.id)}
|
||||||
|
>
|
||||||
|
{isFavouriteProduct ? "Удалить из избранных" : "Добавить в избранное"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Card.propTypes = {
|
||||||
|
product: ProductType.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Card;
|
26
lab5/src/components/screens/Home/Home.jsx
Normal file
26
lab5/src/components/screens/Home/Home.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useContext, useEffect } from "react";
|
||||||
|
import { ProductsContext } from './../../../contexts/productsContext';
|
||||||
|
import Card from "./Card";
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
const { fetchProducts, products } = useContext(ProductsContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchProducts();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-flex justify-content-center align-items-center">
|
||||||
|
<h1 className="text-success font-weight-bold">Каталог</h1>
|
||||||
|
</div>
|
||||||
|
<div className="container">
|
||||||
|
<div className="row" id="catalog">
|
||||||
|
{products && products.map(product => <Card product={product} key={product.id} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
60
lab5/src/components/screens/Login/Login.jsx
Normal file
60
lab5/src/components/screens/Login/Login.jsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { UserContext } from './../../../contexts/userContext';
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
function Login() {
|
||||||
|
const { authorization } = useContext(UserContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { register, handleSubmit } = useForm();
|
||||||
|
|
||||||
|
async function onSubmit(data) {
|
||||||
|
try {
|
||||||
|
await authorization(data);
|
||||||
|
navigate("/");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="h-100">
|
||||||
|
<div className="mask d-flex align-items-center h-100 gradient-custom-3">
|
||||||
|
<div className="container h-100">
|
||||||
|
<div className="row d-flex justify-content-center align-items-center h-100">
|
||||||
|
<div className="col-12 col-md-9 col-lg-7 col-xl-6">
|
||||||
|
<div className="card" style={{ borderRadius: "15px", borderColor: "border-color:rgb(95, 206, 123)" }}>
|
||||||
|
<div className="card-body p-5">
|
||||||
|
<h2 className="text-success text-center mb-5">Войти</h2>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="email" id="form3Example3cg" className="form-control form-control-lg" {...register("email")} />
|
||||||
|
<label className="form-label" htmlFor="form3Example3cg">Ваш адрес электронной почты</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="password" id="form3Example4cg" className="form-control form-control-lg" {...register("password")} />
|
||||||
|
<label className="form-label" htmlFor="form3Example4cg">Пароль</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-center">
|
||||||
|
<input type="submit" value="Войти" className="btn" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-center text-muted mb-0">У вас нет аккаунта? <Link to="/registration"
|
||||||
|
className="fw-bold text-body"><u>Регистрация</u></Link></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
87
lab5/src/components/screens/MakingAnOrder/MakingAnOrder.jsx
Normal file
87
lab5/src/components/screens/MakingAnOrder/MakingAnOrder.jsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
function MakingAnOrder() {
|
||||||
|
return (
|
||||||
|
<section className="h-100">
|
||||||
|
<div className="mask d-flex align-items-center h-100 gradient-custom-3">
|
||||||
|
<div className="container h-100">
|
||||||
|
<div className="row d-flex justify-content-center align-items-center h-100">
|
||||||
|
<div className="col-12 col-md-9 col-lg-7 col-xl-6">
|
||||||
|
<div className="card" style="border-radius: 15px; border-color:#7bca8c;">
|
||||||
|
<div className="card-body p-5">
|
||||||
|
<h2 className="text-success text-center mb-5">Оформление заказа</h2>
|
||||||
|
<form>
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="text" id="form3Example1cg" className="form-control form-control-lg" />
|
||||||
|
<label className="form-label" htmlFor="form3Example1cg">Ваше имя</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="tel" id="form3Example5cg" className="form-control form-control-lg" />
|
||||||
|
<label className="form-label" htmlFor="form3Example5cg">Номер телефона</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="form-check-label" htmlFor="creditCard">Вариант получения</label>
|
||||||
|
|
||||||
|
<div className="form-check mb-4">
|
||||||
|
<input className="form-check-input" type="radio" name="deliveryMethod" id="selfPickup"
|
||||||
|
defaultValue="selfPickup" />
|
||||||
|
<label className="form-check-label" htmlFor="selfPickup">Самовывоз</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-check mb-4">
|
||||||
|
<input className="form-check-input" type="radio" name="deliveryMethod" id="delivery" defaultValue="delivery" />
|
||||||
|
<label className="form-check-label" htmlFor="delivery">Доставка</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-outline mb-4" id="deliveryAddress" style="display: none;">
|
||||||
|
<input type="text" id="form3ExampleAddress" className="form-control form-control-lg" />
|
||||||
|
<label className="form-label" htmlFor="form3ExampleAddress">Ваш адрес доставки</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="row mb-4">
|
||||||
|
<div className="col">
|
||||||
|
<div className="form-outline">
|
||||||
|
<input type="text" id="formNameOnCard" className="form-control" />
|
||||||
|
<label className="form-label" htmlFor="formNameOnCard">Имя держателя карты</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col">
|
||||||
|
<div className="form-outline">
|
||||||
|
<input type="text" id="formCardNumber" className="form-control" />
|
||||||
|
<label className="form-label" htmlFor="formCardNumber">Номер карты</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row mb-4">
|
||||||
|
<div className="col-3">
|
||||||
|
<div className="form-outline">
|
||||||
|
<input type="text" id="formExpiration" className="form-control" />
|
||||||
|
<label className="form-label" htmlFor="formExpiration">Срок действия</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-3">
|
||||||
|
<div className="form-outline">
|
||||||
|
<input type="text" id="formCVV" className="form-control" />
|
||||||
|
<label className="form-label" htmlFor="formCVV">CVV</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-center">
|
||||||
|
<button className="btn" style="margin-bottom: 20px;" type="button" id="saveButton">Оформить</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MakingAnOrder;
|
125
lab5/src/components/screens/PageEdit/PageEdit.jsx
Normal file
125
lab5/src/components/screens/PageEdit/PageEdit.jsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { Link, useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
import { CategoriesContext } from './../../../contexts/categoriesContext';
|
||||||
|
import { ProductsContext } from './../../../contexts/productsContext';
|
||||||
|
|
||||||
|
function PageEdit() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [searchParams, _] = useSearchParams();
|
||||||
|
const id = +searchParams.get("id");
|
||||||
|
|
||||||
|
const isUpdatePage = !!id;
|
||||||
|
|
||||||
|
const { getProductById, createProduct, updateProduct } = useContext(ProductsContext);
|
||||||
|
const [product, setProduct] = useState(null);
|
||||||
|
|
||||||
|
const { categories, fetchCategories } = useContext(CategoriesContext);
|
||||||
|
|
||||||
|
const { register, handleSubmit, setValue, reset } = useForm();
|
||||||
|
|
||||||
|
const [file, setFile] = useState(null);
|
||||||
|
const previewPhotoRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchProduct() {
|
||||||
|
const product = await getProductById(id);
|
||||||
|
setProduct(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!id) fetchProduct();
|
||||||
|
fetchCategories();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isUpdatePage || !product) {
|
||||||
|
reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue("price", product.price);
|
||||||
|
setValue("categoryId", product.categoryId);
|
||||||
|
setValue("name", product.name);
|
||||||
|
}, [product]);
|
||||||
|
|
||||||
|
// /////
|
||||||
|
function getBase64Image(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = error => reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFileInputChange(e) {
|
||||||
|
const ffile = await getBase64Image(e.target.files[0]);
|
||||||
|
previewPhotoRef.current.src = ffile;
|
||||||
|
setFile(ffile);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onFormSubmit(data) {
|
||||||
|
if (!file && !isUpdatePage) return alert("Вы не установили фотографию для товара!");
|
||||||
|
|
||||||
|
if (isUpdatePage) {
|
||||||
|
await updateProduct({
|
||||||
|
...data,
|
||||||
|
id: product.id,
|
||||||
|
photo: (file || product.photo)
|
||||||
|
});
|
||||||
|
navigate("/admin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await createProduct({
|
||||||
|
...data,
|
||||||
|
photo: file
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate("/admin");
|
||||||
|
}
|
||||||
|
//////
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
<h1 className="text-success text-center font-weight-bold">{isUpdatePage ? "Обновление" : "Создание"} товара</h1>
|
||||||
|
<div className="text-center">
|
||||||
|
<img
|
||||||
|
id="image-preview"
|
||||||
|
src={isUpdatePage && product ? product.photo : "https://via.placeholder.com/200"}
|
||||||
|
ref={previewPhotoRef}
|
||||||
|
style={{ maxWidth: "200px", maxHeight: "200px" }}
|
||||||
|
className="rounded rounded-circle"
|
||||||
|
alt="placeholder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<form id="items-form" className="needs-validation" noValidate onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
<div className="mb-2">
|
||||||
|
<label className="form-label">Название</label>
|
||||||
|
<input className="form-control" type="text" required {...register("name")} />
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<label htmlFor="item" className="form-label">Категории</label>
|
||||||
|
<select id="item" className="form-select" name="selected" required {...register("categoryId", { setValueAs: value => +value })}>
|
||||||
|
{categories && categories.map(category => <option value={category.id} key={category.id}>{category.name}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<label className="form-label" htmlFor="price">Цена</label>
|
||||||
|
<input id="price" name="price" className="form-control" type="number" defaultValue="0.00" min="1000.00" step="0.50" {...register("price", { setValueAs: value => +value })}
|
||||||
|
required />
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<label className="form-label" htmlFor="image">Изображение</label>
|
||||||
|
<input id="image" type="file" name="image" className="form-control" accept="image/*" onChange={handleFileInputChange} />
|
||||||
|
</div>
|
||||||
|
<Link to="/admin" className="btn">Назад</Link>
|
||||||
|
<button type="submit" className="btn">{isUpdatePage ? "Обновить" : "Создать"}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageEdit;
|
@ -0,0 +1,20 @@
|
|||||||
|
function PaymentAndDelivery() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-flex justify-content-center align-items-center">
|
||||||
|
<h1 className="text-success font-weight-bold">Оплата и доставка</h1>
|
||||||
|
</div>
|
||||||
|
<h4 className="text-success" style={{ fontSize: "25px", marginLeft: "50px", marginTop: "100px" }}>Способ оплаты</h4>
|
||||||
|
<p className="text-black" style={{ fontSize: "25px", marginLeft: "50px", marginTop: "40px" }}>Картой онлайн</p>
|
||||||
|
<h2 className="text-success" style={{ fontSize: "25px", marginLeft: "50px", marginTop: "70px" }}>Доставка</h2>
|
||||||
|
<p className="text-black" style={{ fontSize: "25px", marginLeft: "50px", marginTop: "40px" }}>Заказ на доставку
|
||||||
|
принимается не менее, чем за 3 часа</p>
|
||||||
|
<p className="text-black" style={{fontSize: "25px", marginLeft: "50px", marginTop: "50px"}}>Минимальная стоимость букета
|
||||||
|
на доставку - 1500 р</p>
|
||||||
|
<p className="text-black" style={{fontSize: "25px", marginLeft: "50px", marginTop: "50px"}}>Доставим ваш букет бесплатно
|
||||||
|
с 10:00 до 19:00</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaymentAndDelivery;
|
@ -0,0 +1,60 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
function PersonalAccount() {
|
||||||
|
return (
|
||||||
|
<section className="h-100">
|
||||||
|
<div className="mask d-flex align-items-center h-100 gradient-custom-3">
|
||||||
|
<div className="container h-100">
|
||||||
|
<div className="row d-flex justify-content-center align-items-center h-100">
|
||||||
|
<div className="col-12 col-md-9 col-lg-7 col-xl-6">
|
||||||
|
<div className="card" style={{ borderRadius: "15px", borderColor: "border-color:rgb(95, 206, 123)" }}>
|
||||||
|
<div className="card-body p-5">
|
||||||
|
<h2 className="text-center text-success mb-5">Личный кабинет</h2>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="text" id="form3Example1cg" className="form-control form-control-lg" />
|
||||||
|
<label className="form-label" htmlFor="form3Example1cg">Ваше имя</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="text" id="form3Example1cg" className="form-control form-control-lg" />
|
||||||
|
<label className="form-label" htmlFor="form3Example1cg">Ваша фамилия</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="email" id="form3Example3cg" className="form-control form-control-lg" />
|
||||||
|
<label className="form-label" htmlFor="form3Example3cg">Ваш адрес электронной почты</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="date" id="form3Example4cg" className="form-control form-control-lg" />
|
||||||
|
<label className="form-label" htmlFor="form3Example4cg">Дата рождения</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-outline mb-2">
|
||||||
|
<input type="tel" id="form3Example5cg" className="form-control form-control-lg" />
|
||||||
|
<label className="form-label" htmlFor="form3Example5cg">Номер телефона</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-center">
|
||||||
|
<button className="btn btn-outline-light" type="button" id="saveButton">Сохранить</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-center">
|
||||||
|
<Link className="btn btn-outline-light" type="button" to="/">Выйти</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PersonalAccount;
|
59
lab5/src/components/screens/Registration/Registration.jsx
Normal file
59
lab5/src/components/screens/Registration/Registration.jsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { UserContext } from "../../../contexts/userContext";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
function Registration() {
|
||||||
|
const { reg } = useContext(UserContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { register, handleSubmit } = useForm();
|
||||||
|
|
||||||
|
async function onSubmit(data) {
|
||||||
|
try {
|
||||||
|
await reg(data);
|
||||||
|
navigate("/");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="h-100">
|
||||||
|
<div className="mask d-flex align-items-center h-100 gradient-custom-3">
|
||||||
|
<div className="container h-100">
|
||||||
|
<div className="row d-flex justify-content-center align-items-center h-100">
|
||||||
|
<div className="col-12 col-md-9 col-lg-7 col-xl-6">
|
||||||
|
<div className="card" style={{ borderRadius: "15px", borderColor: "border-color:rgb(95, 206, 123)" }}>
|
||||||
|
<div className="card-body p-5">
|
||||||
|
<h2 className="text-success text-center mb-5">Регистрация</h2>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="email" id="form3Example3cg" className="form-control form-control-lg" {...register("email")} />
|
||||||
|
<label className="form-label" htmlFor="form3Example3cg">Ваш адрес электронной почты</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-outline mb-4">
|
||||||
|
<input type="password" id="form3Example4cg" className="form-control form-control-lg" {...register("email")} />
|
||||||
|
<label className="form-label" htmlFor="form3Example4cg">Пароль</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-center">
|
||||||
|
<input type="submit" value="Регистрация" className="btn" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-center text-muted mb-0">У вас уже есть учетная запись? <Link to="/login"
|
||||||
|
className="fw-bold text-body"><u>Войдите здесь</u></Link></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Registration;
|
62
lab5/src/components/screens/Search/OrderModal.jsx
Normal file
62
lab5/src/components/screens/Search/OrderModal.jsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { OrdersContext } from "../../../contexts/ordersContext";
|
||||||
|
|
||||||
|
function OrderModal({ currentOrder }) {
|
||||||
|
// Достали функцию, которая позволяет получать продукты заказа по id заказа
|
||||||
|
const { getOrderProducts } = useContext(OrdersContext);
|
||||||
|
// Делаем состояние для таких продуктов
|
||||||
|
const [orderProducts, setOrderProducts] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentOrder) return;
|
||||||
|
|
||||||
|
const func = async () => {
|
||||||
|
// Получили нужные продукты
|
||||||
|
const response = await getOrderProducts(currentOrder.id);
|
||||||
|
// Обновили состояние
|
||||||
|
setOrderProducts(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
func();
|
||||||
|
}, [currentOrder]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal fade" id="exampleModal" tabIndex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="exampleModalLabel">Детали заказа</h1>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p><b>ID Заказа:</b> {currentOrder ? currentOrder.id : null}</p>
|
||||||
|
<p><b>Дата заказа:</b> {currentOrder ? new Date(currentOrder.date).toLocaleString('ru-RU') : null}</p>
|
||||||
|
<p><b>Товары в заказе:</b></p>
|
||||||
|
<table className="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Название товара</th>
|
||||||
|
<th scope="col">Фото</th>
|
||||||
|
<th scope="col">Цена</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="parent">
|
||||||
|
{orderProducts && orderProducts.map(op => {
|
||||||
|
return (
|
||||||
|
<tr key={op.id}>
|
||||||
|
<td>{op.name}</td>
|
||||||
|
<td><img src={op.photo} alt="" style={{ maxWidth: "100px", maxHeight: "100px" }} /></td>
|
||||||
|
<td>{op.price}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderModal;
|
39
lab5/src/components/screens/Search/Search.jsx
Normal file
39
lab5/src/components/screens/Search/Search.jsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
import Card from "../Home/Card";
|
||||||
|
import { ProductsContext } from './../../../contexts/productsContext';
|
||||||
|
|
||||||
|
function Search() {
|
||||||
|
const [searchParams, _] = useSearchParams();
|
||||||
|
const term = searchParams.get("term");
|
||||||
|
|
||||||
|
const { getProductsBySearchTerm } = useContext(ProductsContext);
|
||||||
|
const [productsByTerm, setProductsByTerm] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function func() {
|
||||||
|
const response = await getProductsBySearchTerm(term);
|
||||||
|
setProductsByTerm(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
func();
|
||||||
|
}, [term]);
|
||||||
|
|
||||||
|
console.log(productsByTerm);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="d-flex justify-content-center align-items-center">
|
||||||
|
<h1 className="text-success font-weight-bold">Поиск по запросу: {term}</h1>
|
||||||
|
</div>
|
||||||
|
<div className="container">
|
||||||
|
<div className="row" id="catalog">
|
||||||
|
{productsByTerm.length !== 0 ? productsByTerm.map(p => <Card product={p} key={p.id} />) : <p>Таких нет..</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Search;
|
26
lab5/src/contexts/Contexts.jsx
Normal file
26
lab5/src/contexts/Contexts.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import BasketProvider from './basketContext';
|
||||||
|
import CategoriesProvider from "./categoriesContext";
|
||||||
|
import FavouritesProvider from "./favouritesContext";
|
||||||
|
import OrdersProvider from "./ordersContext";
|
||||||
|
import ProductsProvider from "./productsContext";
|
||||||
|
import UserProvider from "./userContext";
|
||||||
|
|
||||||
|
const Contexts = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<UserProvider>
|
||||||
|
<ProductsProvider>
|
||||||
|
<CategoriesProvider>
|
||||||
|
<BasketProvider>
|
||||||
|
<OrdersProvider>
|
||||||
|
<FavouritesProvider>
|
||||||
|
{children}
|
||||||
|
</FavouritesProvider>
|
||||||
|
</OrdersProvider>
|
||||||
|
</BasketProvider>
|
||||||
|
</CategoriesProvider>
|
||||||
|
</ProductsProvider>
|
||||||
|
</UserProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Contexts;
|
16
lab5/src/contexts/basketContext.jsx
Normal file
16
lab5/src/contexts/basketContext.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import { useBasket } from "../hooks/useBasket";
|
||||||
|
|
||||||
|
export const BasketContext = createContext(null);
|
||||||
|
|
||||||
|
const BasketProvider = ({ children }) => {
|
||||||
|
const data = useBasket();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BasketContext.Provider value={data}>
|
||||||
|
{children}
|
||||||
|
</BasketContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasketProvider;
|
17
lab5/src/contexts/categoriesContext.jsx
Normal file
17
lab5/src/contexts/categoriesContext.jsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import { useCategories } from "../hooks/useCategories";
|
||||||
|
|
||||||
|
export const CategoriesContext = createContext(null);
|
||||||
|
|
||||||
|
const CategoriesProvider = ({ children }) => {
|
||||||
|
const data = useCategories();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CategoriesContext.Provider value={data}>
|
||||||
|
{children}
|
||||||
|
</CategoriesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default CategoriesProvider;
|
16
lab5/src/contexts/favouritesContext.jsx
Normal file
16
lab5/src/contexts/favouritesContext.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import { useFavourites } from "../hooks/useFavourites";
|
||||||
|
|
||||||
|
export const FavouritesContext = createContext(null);
|
||||||
|
|
||||||
|
const FavouritesProvider = ({ children }) => {
|
||||||
|
const data = useFavourites();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FavouritesContext.Provider value={data}>
|
||||||
|
{children}
|
||||||
|
</FavouritesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FavouritesProvider;
|
17
lab5/src/contexts/ordersContext.jsx
Normal file
17
lab5/src/contexts/ordersContext.jsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import { useOrders } from "../hooks/useOrders";
|
||||||
|
|
||||||
|
export const OrdersContext = createContext(null);
|
||||||
|
|
||||||
|
const OrdersProvider = ({ children }) => {
|
||||||
|
const data = useOrders();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OrdersContext.Provider value={data}>
|
||||||
|
{children}
|
||||||
|
</OrdersContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default OrdersProvider;
|
22
lab5/src/contexts/productsContext.jsx
Normal file
22
lab5/src/contexts/productsContext.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import { useProducts } from "../hooks/useProducts";
|
||||||
|
|
||||||
|
// Наш контекст (грубо говоря ключ к нашим значениям)
|
||||||
|
// Т.е. мы знаем, что в ProductsContext ничего кроме логики с продуктами
|
||||||
|
// лежать не будет
|
||||||
|
export const ProductsContext = createContext(null);
|
||||||
|
|
||||||
|
// Штука, в которую мы оборачиваем весь сайт
|
||||||
|
// чтобы получить данные в любом компоненте
|
||||||
|
const ProductsProvider = ({ children }) => {
|
||||||
|
const productsData = useProducts();
|
||||||
|
|
||||||
|
return (
|
||||||
|
// В value прокидываем словарь с данными, которые мы получили из хука useProducts ( return {products, ...} )
|
||||||
|
<ProductsContext.Provider value={productsData}>
|
||||||
|
{children}
|
||||||
|
</ProductsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductsProvider;
|
16
lab5/src/contexts/userContext.jsx
Normal file
16
lab5/src/contexts/userContext.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import { useUser } from "../hooks/useUser";
|
||||||
|
|
||||||
|
export const UserContext = createContext(null);
|
||||||
|
|
||||||
|
const UserProvider = ({ children }) => {
|
||||||
|
const userData = useUser();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={userData}>
|
||||||
|
{children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserProvider;
|
95
lab5/src/hooks/useBasket.js
Normal file
95
lab5/src/hooks/useBasket.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { useContext, useState } from 'react';
|
||||||
|
|
||||||
|
import { ProductsContext } from "../contexts/productsContext";
|
||||||
|
import { UserContext } from "../contexts/userContext";
|
||||||
|
import BasketService from "../services/BasketService";
|
||||||
|
|
||||||
|
export function useBasket() {
|
||||||
|
const [basketId, setBasketId] = useState(null);
|
||||||
|
const [basketProducts, setBasketProducts] = useState(null);
|
||||||
|
const { id } = useContext(UserContext);
|
||||||
|
const { getProductById } = useContext(ProductsContext);
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchBasketAndProducts() {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const bId = await BasketService.getBasketId(id);
|
||||||
|
setBasketId(bId);
|
||||||
|
|
||||||
|
if (!bId) return;
|
||||||
|
|
||||||
|
const bProducts = await BasketService.getBasketProducts(bId);
|
||||||
|
setBasketProducts(bProducts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isProductInBasket(productId) {
|
||||||
|
if (!basketProducts || basketProducts.length == 0) return false;
|
||||||
|
|
||||||
|
const flag = !!basketProducts.find(bp => bp.productId == productId);
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addProductToBasket(productId) {
|
||||||
|
try {
|
||||||
|
const data = { basketId, productId };
|
||||||
|
const response = await BasketService.addProductToBasket(data);
|
||||||
|
|
||||||
|
if (!basketProducts || basketProducts.length == 0) setBasketProducts([response]);
|
||||||
|
else setBasketProducts([...basketProducts, response]);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteProductFromBasket(productId) {
|
||||||
|
if (!basketProducts || basketProducts.length == 0) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!confirm("Вы точно хотите удалить товар из корзины?")) return;
|
||||||
|
|
||||||
|
const productToDelete = basketProducts.find(bp => bp.productId == productId);
|
||||||
|
await BasketService.deleteBasketProduct(productToDelete.id);
|
||||||
|
// Убираю из состояния продуктов корзины конкретный продукт
|
||||||
|
setBasketProducts(basketProducts.filter(bp => bp.productId !== productId));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getBasketProductsWithTotalPrice() {
|
||||||
|
if (!basketProducts || basketProducts.length == 0) return [];
|
||||||
|
|
||||||
|
const arr = [];
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < basketProducts.length; i++) {
|
||||||
|
const product = await getProductById(basketProducts[i].productId);
|
||||||
|
arr.push(product);
|
||||||
|
total += product.price;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [arr, total];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearBasketProducts() {
|
||||||
|
if (!basketProducts || basketProducts.length == 0) return;
|
||||||
|
|
||||||
|
for (let i = 0; i < basketProducts.length; i++) {
|
||||||
|
await BasketService.deleteBasketProduct(basketProducts[i].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBasketProducts([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
basketId,
|
||||||
|
basketProducts,
|
||||||
|
fetchBasketAndProducts,
|
||||||
|
isProductInBasket,
|
||||||
|
addProductToBasket,
|
||||||
|
deleteProductFromBasket,
|
||||||
|
getBasketProductsWithTotalPrice,
|
||||||
|
clearBasketProducts
|
||||||
|
};
|
||||||
|
}
|
45
lab5/src/hooks/useCategories.js
Normal file
45
lab5/src/hooks/useCategories.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useState, } from 'react';
|
||||||
|
|
||||||
|
import CategoryService from "../services/CategoryService";
|
||||||
|
|
||||||
|
export function useCategories() {
|
||||||
|
const [categories, setCategories] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchCategories() {
|
||||||
|
try {
|
||||||
|
const response = await CategoryService.getAll();
|
||||||
|
setCategories(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCategoryById(id) {
|
||||||
|
try {
|
||||||
|
if (!categories || categories.length == 0) {
|
||||||
|
const response = await CategoryService.getById(id);
|
||||||
|
setCategories([response]);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingCategory = categories.find(category => category.id == id);
|
||||||
|
// Если он есть, то мы его возвращаем
|
||||||
|
if (existingCategory) return existingCategory;
|
||||||
|
|
||||||
|
// 3 случай. Продукта не оказалось в данных, к-ые уже загружены
|
||||||
|
const response = await CategoryService.getById(id);
|
||||||
|
setCategories([...categories, response]);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
categories,
|
||||||
|
fetchCategories,
|
||||||
|
getCategoryById
|
||||||
|
};
|
||||||
|
}
|
60
lab5/src/hooks/useFavourites.js
Normal file
60
lab5/src/hooks/useFavourites.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { useContext, useState } from 'react';
|
||||||
|
|
||||||
|
import { UserContext } from "../contexts/userContext";
|
||||||
|
import FavouritesService from "../services/FavouritesService";
|
||||||
|
|
||||||
|
export function useFavourites() {
|
||||||
|
const [favourites, setFavourites] = useState(null);
|
||||||
|
const { id } = useContext(UserContext);
|
||||||
|
|
||||||
|
// Функции
|
||||||
|
async function fetchFavourites() {
|
||||||
|
if (favourites || !id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await FavouritesService.getAll(id);
|
||||||
|
setFavourites(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isProductInFavourites(productId) {
|
||||||
|
if (!favourites || favourites.length === 0) return false;
|
||||||
|
const flag = !!favourites.find(favourite => favourite.productId === productId);
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addProductToFavourites(productId) {
|
||||||
|
try {
|
||||||
|
const data = { userId: id, productId };
|
||||||
|
const response = await FavouritesService.addProductToFavourites(data);
|
||||||
|
|
||||||
|
if (!favourites || favourites.length === 0) setFavourites([response]);
|
||||||
|
else setFavourites([...favourites, response]);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteProductFromFavourites(productId) {
|
||||||
|
if (!favourites || favourites.length === 0) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const productToDelete = favourites.find(favourite => favourite.productId === productId);
|
||||||
|
await FavouritesService.deleteFavouriteProduct(productToDelete.id);
|
||||||
|
setFavourites(favourites.filter(favourite => favourite.productId !== productId));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Функции END
|
||||||
|
|
||||||
|
return {
|
||||||
|
favourites,
|
||||||
|
fetchFavourites,
|
||||||
|
isProductInFavourites,
|
||||||
|
addProductToFavourites,
|
||||||
|
deleteProductFromFavourites,
|
||||||
|
};
|
||||||
|
}
|
63
lab5/src/hooks/useOrders.js
Normal file
63
lab5/src/hooks/useOrders.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { useContext, useState, } from 'react';
|
||||||
|
|
||||||
|
import { BasketContext } from "../contexts/basketContext";
|
||||||
|
import { ProductsContext } from "../contexts/productsContext";
|
||||||
|
import { UserContext } from "../contexts/userContext";
|
||||||
|
import OrderService from "../services/OrderService";
|
||||||
|
import OrdersProductService from "../services/OrdersProductService";
|
||||||
|
|
||||||
|
export function useOrders() {
|
||||||
|
const [orders, setOrders] = useState(null);
|
||||||
|
const { id } = useContext(UserContext);
|
||||||
|
const { clearBasketProducts } = useContext(BasketContext);
|
||||||
|
const { getProductById } = useContext(ProductsContext);
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchOrders() {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await OrderService.getAll(id);
|
||||||
|
setOrders(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOrder(data, productsIds) {
|
||||||
|
const response = await OrderService.create({ ...data, userId: id });
|
||||||
|
|
||||||
|
const orderId = response.id;
|
||||||
|
|
||||||
|
for (let i = 0; i < productsIds.length; i++) {
|
||||||
|
await OrdersProductService.create(orderId, productsIds[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(orders, response);
|
||||||
|
|
||||||
|
setOrders([...(orders || []), response]);
|
||||||
|
await clearBasketProducts();
|
||||||
|
return orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOrderProducts(orderId) {
|
||||||
|
const response = await OrdersProductService.getByOrderId(orderId);
|
||||||
|
console.log("response:", response);
|
||||||
|
const products = [];
|
||||||
|
|
||||||
|
for (let index = 0; index < response.length; index++) {
|
||||||
|
const product = await getProductById(response[index].productId);
|
||||||
|
products.push(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
return products;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
orders,
|
||||||
|
createOrder,
|
||||||
|
fetchOrders,
|
||||||
|
getOrderProducts
|
||||||
|
};
|
||||||
|
}
|
84
lab5/src/hooks/useProducts.js
Normal file
84
lab5/src/hooks/useProducts.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import ProductService from "../services/ProductService";
|
||||||
|
|
||||||
|
export function useProducts() {
|
||||||
|
// State - состояния (от англ.) (= переменные)
|
||||||
|
const [products, setProducts] = useState(null);
|
||||||
|
|
||||||
|
async function fetchProducts() {
|
||||||
|
try {
|
||||||
|
const response = await ProductService.getAll();
|
||||||
|
setProducts(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getProductById(id) {
|
||||||
|
try {
|
||||||
|
// 1 случай. Продуктов вообще нет
|
||||||
|
// !false = true
|
||||||
|
if (!products || products.length == 0) {
|
||||||
|
const response = await ProductService.getById(id);
|
||||||
|
// Создали массив продуктов потому раньше его либо не было,
|
||||||
|
// либо он был пустой
|
||||||
|
// и загрузили туда продукт
|
||||||
|
setProducts([response]);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 случай. Продукты уже загружены, но не факт, что есть нужный нам продукт
|
||||||
|
const existingProduct = products.find(product => product.id == id);
|
||||||
|
// Если он есть, то мы его возвращаем
|
||||||
|
if (existingProduct) return existingProduct;
|
||||||
|
|
||||||
|
// 3 случай. Продукта не оказалось в данных, к-ые уже загружены
|
||||||
|
const response = await ProductService.getById(id);
|
||||||
|
setProducts([...products, response]);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteProduct(id) {
|
||||||
|
if (!confirm("Вы действительно хотите удалить товар?")) return;
|
||||||
|
|
||||||
|
await ProductService.delete(id);
|
||||||
|
setProducts(products.filter(p => p.id !== id));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createProduct(data) {
|
||||||
|
const response = await ProductService.create(data);
|
||||||
|
setProducts([...(products || []), response]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateProduct(data) {
|
||||||
|
const response = await ProductService.update(data);
|
||||||
|
const prods = products.map(p => {
|
||||||
|
if (p.id == response.id) return response;
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
|
||||||
|
setProducts(prods);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getProductsBySearchTerm(searchTerm) {
|
||||||
|
if (searchTerm === "" || !products || products.length === 0) return [];
|
||||||
|
|
||||||
|
return await ProductService.getBySearchTerm(searchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Возвращаем словарь с состоянием и нужными функциями
|
||||||
|
return {
|
||||||
|
products,
|
||||||
|
fetchProducts,
|
||||||
|
getProductById,
|
||||||
|
deleteProduct,
|
||||||
|
createProduct,
|
||||||
|
updateProduct,
|
||||||
|
getProductsBySearchTerm
|
||||||
|
};
|
||||||
|
}
|
55
lab5/src/hooks/useUser.js
Normal file
55
lab5/src/hooks/useUser.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { useState, } from 'react';
|
||||||
|
import UserService from "../services/UserService";
|
||||||
|
import BasketService from "../services/BasketService";
|
||||||
|
|
||||||
|
export function useUser() {
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
|
||||||
|
async function authorization(data) {
|
||||||
|
try {
|
||||||
|
const response = await UserService.login(data);
|
||||||
|
setUser(response);
|
||||||
|
localStorage.setItem("user", JSON.stringify(response));
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reg(data) {
|
||||||
|
try {
|
||||||
|
const response = await UserService.registration({...data, "role": "user"});
|
||||||
|
await BasketService.createBasket(response.id);
|
||||||
|
setUser(response);
|
||||||
|
localStorage.setItem("user", JSON.stringify(response));
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
setUser(null);
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIsAuth() {
|
||||||
|
if (user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuser = localStorage.getItem('user');
|
||||||
|
if (uuser) {
|
||||||
|
const t = JSON.parse(uuser);
|
||||||
|
setUser(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user ? user.id : null,
|
||||||
|
isAdmin: Boolean(user && user.role == "admin"),
|
||||||
|
isAuth: user && user.id,
|
||||||
|
authorization,
|
||||||
|
reg,
|
||||||
|
logout,
|
||||||
|
checkIsAuth
|
||||||
|
};
|
||||||
|
}
|
15
lab5/src/main.jsx
Normal file
15
lab5/src/main.jsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Гайд: как сдать лабу
|
||||||
|
// 0, Открываем консоль
|
||||||
|
// 1. npm i
|
||||||
|
// 2. npm run dev (CTRL + C для остановки)
|
||||||
|
// 3. Когда попросят линтер - npm run lint
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
40
lab5/src/services/BasketService.js
Normal file
40
lab5/src/services/BasketService.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import $axios from "../axios";
|
||||||
|
|
||||||
|
class BasketService {
|
||||||
|
#basketUrl = "/baskets";
|
||||||
|
#basketProductUrl = "/basket_products";
|
||||||
|
|
||||||
|
createBasket = async (userId) => {
|
||||||
|
await $axios.post(`${this.#basketUrl}`, { userId });
|
||||||
|
}
|
||||||
|
|
||||||
|
getBasketId = async (userId) => {
|
||||||
|
const response = await $axios.get(`${this.#basketUrl}?userId=${userId}`);
|
||||||
|
if (response.data.length == 0) return null;
|
||||||
|
return response.data[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBasketProducts = async (basketId) => {
|
||||||
|
const response = await $axios.get(`${this.#basketProductUrl}?basketId=${basketId}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
addProductToBasket = async (data) => {
|
||||||
|
try {
|
||||||
|
const response = await $axios.post(this.#basketProductUrl, data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteBasketProduct = async (id) => {
|
||||||
|
try {
|
||||||
|
await $axios.delete(`${this.#basketProductUrl}/${id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new BasketService();
|
17
lab5/src/services/CategoryService.js
Normal file
17
lab5/src/services/CategoryService.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import $axios from '../axios';
|
||||||
|
|
||||||
|
class CategoryService {
|
||||||
|
#path = "/categories";
|
||||||
|
|
||||||
|
getAll = async () => {
|
||||||
|
const response = await $axios.get(this.#path);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getById = async (id) => {
|
||||||
|
const response = await $axios.get(`${this.#path}/${id}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new CategoryService();
|
29
lab5/src/services/FavouritesService.js
Normal file
29
lab5/src/services/FavouritesService.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import $axios from '../axios';
|
||||||
|
|
||||||
|
class FavouritesService {
|
||||||
|
#path = "/favourites";
|
||||||
|
|
||||||
|
getAll = async (userId) => {
|
||||||
|
const response = await $axios.get(`${this.#path}?userId=${userId}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
addProductToFavourites = async (data) => {
|
||||||
|
try {
|
||||||
|
const response = await $axios.post(this.#path, data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFavouriteProduct = async (id) => {
|
||||||
|
try {
|
||||||
|
await $axios.delete(`${this.#path}/${id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new FavouritesService();
|
17
lab5/src/services/OrderService.js
Normal file
17
lab5/src/services/OrderService.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import $axios from "../axios";
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
url = "/orders";
|
||||||
|
|
||||||
|
getAll = async (userId) => {
|
||||||
|
const response = await $axios.get(`${this.url}?userId=${userId}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
create = async (data) => {
|
||||||
|
const response = await $axios.post(this.url, data);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new OrderService();
|
16
lab5/src/services/OrdersProductService.js
Normal file
16
lab5/src/services/OrdersProductService.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import $axios from "../axios";
|
||||||
|
|
||||||
|
class OrdersProductService {
|
||||||
|
url = "/order_products";
|
||||||
|
|
||||||
|
create = async (orderId, productId) => {
|
||||||
|
await $axios.post(this.url, { orderId, productId });
|
||||||
|
}
|
||||||
|
|
||||||
|
getByOrderId = async (orderId) => {
|
||||||
|
const response = await $axios.get(`${this.url}?orderId=${orderId}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new OrdersProductService();
|
36
lab5/src/services/ProductService.js
Normal file
36
lab5/src/services/ProductService.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import $axios from '../axios';
|
||||||
|
|
||||||
|
class ProductService {
|
||||||
|
#path = "/products";
|
||||||
|
|
||||||
|
getAll = async () => {
|
||||||
|
const response = await $axios.get(`${this.#path}?_limit=10`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getById = async (id) => {
|
||||||
|
const response = await $axios.get(`${this.#path}/${id}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete = async (id) => {
|
||||||
|
await $axios.delete(`${this.#path}/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
create = async (data) => {
|
||||||
|
const response = await $axios.post(this.#path, data);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
update = async (data) => {
|
||||||
|
const response = await $axios.put(`${this.#path}/${data.id}`, data);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBySearchTerm = async (term) => {
|
||||||
|
const response = await $axios.get(`${this.#path}?name_like=${term}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ProductService();
|
20
lab5/src/services/UserService.js
Normal file
20
lab5/src/services/UserService.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import $axios from "../axios";
|
||||||
|
|
||||||
|
class UserService {
|
||||||
|
url = "/users";
|
||||||
|
|
||||||
|
registration = async (data) => {
|
||||||
|
const response = await $axios.post(this.url, data);
|
||||||
|
const user = response.data;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
login = async (data) => {
|
||||||
|
const response = await $axios.get(`${this.url}?email=${data.email}`);
|
||||||
|
if (response.data.length == 0) throw new Error("User not found");
|
||||||
|
|
||||||
|
return response.data[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UserService();
|
6
lab5/src/types/CategoryType.js
Normal file
6
lab5/src/types/CategoryType.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
export const CategoryType = PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
});
|
9
lab5/src/types/ProductType.js
Normal file
9
lab5/src/types/ProductType.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
export const ProductType = PropTypes.shape({
|
||||||
|
id: PropTypes.number.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
price: PropTypes.number.isRequired,
|
||||||
|
photo: PropTypes.string.isRequired,
|
||||||
|
categoryId: PropTypes.number.isRequired,
|
||||||
|
});
|
1
lab5/src/variables/variables.js
Normal file
1
lab5/src/variables/variables.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const serverPath = "http://localhost:8081";
|
17
lab5/vite.config.js
Normal file
17
lab5/vite.config.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
BIN
отчеты/5 лаб.docx
Normal file
BIN
отчеты/5 лаб.docx
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user