This commit is contained in:
Anna 2024-01-19 18:32:01 +04:00
parent 536e89eedf
commit 9ed4172b99
63 changed files with 7863 additions and 0 deletions

21
Lab5/lab5/.eslintrc.cjs Normal file
View 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/lab5/.eslintrc.json Normal file
View 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/lab5/.gitignore vendored Normal file
View 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/lab5/README.md Normal file
View 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/lab5/index.html Normal file
View 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>Tokki</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/lab5/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
Lab5/lab5/package.json Normal file
View 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",
"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"
}
}

130
Lab5/lab5/server/data.json Normal file

File diff suppressed because one or more lines are too long

12
Lab5/lab5/src/App.jsx Normal file
View 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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1,121 @@
header nav {
background-color: #ffeaf7;
}
@media (min-width: 768px) {
header nav {
height: 110px;
}
}
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-banner img {
margin-left: 10px;
width: 50%;
object-fit: cover;
object-position: bottom;
}
.product {
margin-bottom: 30px;
padding-right: 5px;
padding-left: 5px;
height: 100%;
}
.image_al {
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.image_al img {
width: 300px;
height: 300px;
object-fit: cover;
border-radius: 7px;
}
.info-price {
display: flex;
flex-flow: row nowrap;
align-items: center;
}
.add-to-cart {
border: none;
width: 35px;
height: 35px;
display: flex;
align-items: center;
justify-content: center;
background-color: #767479;
color: #ffffff;
}
.btn-warning {
height: 35px;
width: 176px;
border-radius: 8px;
margin-right: 10px;
margin-top: 10px;
}
.btn {
height: 35px;
width: 176px;
border-radius: 8px;
margin-right: 10px;
margin-top: 10px;
background-color: #767479;
color: #ffffff !important;
}
.btn:hover {
background-color: #bababa !important;
}
footer {
background-color: #ffeaf7;
}
#image-preview {
max-width: 100%;
max-height: 200px;
width: auto;
height: auto;
}

9
Lab5/lab5/src/axios.js Normal file
View File

@ -0,0 +1,9 @@
import axios from "axios";
import { serverUrl } from "./variables/variables";
const $axios = axios.create({
baseURL: serverUrl
});
export default $axios;

View File

@ -0,0 +1,38 @@
import { useContext, useEffect } from "react";
import { Outlet } from "react-router-dom";
import { OrdersContext } from "../../contexts/ordersContext";
import { BasketContext } from './../../contexts/basketContext';
import { FavouritesContext } from './../../contexts/favouritesContext';
import { UserContext } from './../../contexts/userContext';
import Footer from "./components/Footer";
import Header from "./components/Header";
function Layout() {
const { userId, checkIsAuth } = useContext(UserContext);
const { getBasketAndBasketProducts } = useContext(BasketContext);
const { getFavourites } = useContext(FavouritesContext);
const { getOrders } = useContext(OrdersContext);
useEffect(() => {
getBasketAndBasketProducts();
getFavourites();
getOrders();
}, [userId]);
useEffect(() => {
checkIsAuth();
}, []);
return (
<>
<Header />
<main>
{/* <Outlet /> - место, куда мы подставляем страницы */}
<Outlet />
</main>
<Footer />
</>
);
}
export default Layout;

View File

@ -0,0 +1,9 @@
function Footer() {
return (
<footer className="footer mt-auto d-flex flex-shrink-0 justify-content-center align-items-center">
Tokki © 2023-2024
</footer>
);
}
export default Footer;

View File

@ -0,0 +1,41 @@
import { Link } from "react-router-dom";
import Logo from "../../../assets/Images/Tokki.png";
import { useContext } from "react";
import { UserContext } from './../../../contexts/userContext';
function Header() {
const { userIsAuth, userIsAdmin, signOut } = useContext(UserContext);
console.log(userIsAdmin);
return (
<header>
<nav className="navbar navbar-expand-md">
<div className="container-fluid">
<Link className="navbar-brand" to="/">
<img src={Logo} alt="logo" width="90" />
</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="/company">О компании</Link>
<Link className="nav-link" to="/delivery">Доставка</Link>
{userIsAdmin && <Link className="nav-link" to="/admin">Админка</Link>}
</div>
</div>
<div className="navbar-collapse collapse justify-content-end" id="navbarNav">
<div className="navbar-nav">
<Link className="nav-link" onClick={userIsAuth ? signOut : undefined} to="/login">{userIsAuth ? "Выйти" : "Войти"}</Link>
<Link className="nav-link" style={{ paddingRight: 90 }} to={userIsAuth ? "/basket" : "/login"}>Корзина</Link>
</div>
</div>
</div>
</nav>
</header>
);
}
export default Header;

View File

@ -0,0 +1,40 @@
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 Home from '../screens/Home/Home';
import Registration from "../screens/Registration/Registration";
import Company from './../screens/Company/Company';
import Delivery from './../screens/Delivery/Delivery';
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 RecoveryPassword from './../screens/RecoveryPassword/RecoveryPassword';
// Привязываем компоненты-страницы к путям
function Navigation() {
return (
<BrowserRouter>
<Routes>
{/* Весь сайт оборачиваем в шаблон <Layout /> */}
<Route path="/" element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/admin" element={<Admin />} />
<Route path="/basket" element={<Basket />} />
<Route path="/company" element={<Company />} />
<Route path="/delivery" element={<Delivery />} />
<Route path="/makingAnOrder" element={<MakingAnOrder />} />
<Route path="/pageEdit" element={<PageEdit />} />
<Route path="/personalAccount" element={<PersonalAccount />} />
<Route path="/recoveryPassword" element={<RecoveryPassword />} />
<Route path="/login" element={<Login />} />
<Route path="/registration" element={<Registration />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default Navigation;

View File

@ -0,0 +1,28 @@
import { useContext, useEffect } from "react";
import { Link } from "react-router-dom";
import { CategoriesContext } from './../../../contexts/categoriesContext';
import { ProductsContext } from './../../../contexts/productsContext';
import AdminTable from "./components/AdminTable";
function Admin() {
const { getProducts } = useContext(ProductsContext);
const { getCategories } = useContext(CategoriesContext);
useEffect(() => {
getCategories();
getProducts();
}, []);
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;

View File

@ -0,0 +1,31 @@
import { useContext } from "react";
import AdminTableRow from "./AdminTableRow";
import { ProductsContext } from "../../../../contexts/productsContext";
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;

View File

@ -0,0 +1,41 @@
import { useContext, useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { ProductsContext } from "../../../../contexts/productsContext";
import { CategoriesContext } from "../../../../contexts/categoriesContext";
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-paw text-dark"></i>
</Link>
</td>
<td>
<button className="bg-transparent border-0" onClick={() => deleteProduct(product.id)}>
<i className="fa-solid fa-circle-xmark text-dark"></i>
</button>
</td>
</tr>
);
}
export default AdminTableRow;

View File

@ -0,0 +1,30 @@
import { useContext, useState } from "react";
import { BasketContext } from './../../../contexts/basketContext';
import FullBasket from "./components/FullBasket";
import BasketHistory from './components/BasketHistory';
import BasketModal from './components/BasketModal';
import { OrdersContext } from "../../../contexts/ordersContext";
function Basket() {
const { basketProducts } = useContext(BasketContext);
const { orders } = useContext(OrdersContext);
const isBasketEmpty = !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 font-weight-bold">Корзина</h1>
</div>
{isBasketEmpty ? (<p>В корзине пока ничего нет</p>) : <FullBasket />}
{orders && orders.length != 0 && (
<>
<BasketHistory setCurrentOrder={setCurrentOrder} />
<BasketModal currentOrder={currentOrder} />
</>
)}
</div>
);
}
export default Basket;

View 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;

View 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;

View 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" alt="Koreans" />
</div>
<div className="col-md-3 col-lg-3 col-xl-3">
<p className="lead fw-normal mb-2">{product.name}</p>
</div>
<div className="col-md-3 col-lg-2 col-xl-2 offset-lg-1">
<h5 className="mb-0">Цена {product.price}</h5>
</div>
<div className="col-md-1 col-lg-1 col-xl-1 text-end">
<button className="text-black bg-transparent border-0" onClick={() => deleteProductFromBasket(product.id)}><i className="far fa-circle-xmark"></i></button>
</div>
</div>
</div>
</div>
);
}
export default BasketItem;

View File

@ -0,0 +1,58 @@
import { useContext, useEffect, useState } from "react";
import { OrdersContext } from "../../../../contexts/ordersContext";
function BasketModal({ currentOrder }) {
const { getProductsInOrder } = useContext(OrdersContext);
const [orderProducts, setOrderProducts] = useState(null);
useEffect(() => {
if (!currentOrder) return;
const func = async () => {
const response = await getProductsInOrder(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;

View 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 FullBasket() {
const { basketProducts, getProductsInBasketAndTheirTotalPrice } = 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 getProductsInBasketAndTheirTotalPrice();
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" style={{ color: "#767479", fontSize: "24px" }}>
{totalPrice}
</div>
</div>
<button className="btn" style={{ marginLeft: "25px", marginBottom: "10px" }} onClick={handleCreateOrder}>Оплатить</button>
</>
);
};
export default FullBasket;

View File

@ -0,0 +1,46 @@
function Company() {
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 Company;

View File

@ -0,0 +1,20 @@
function Delivery() {
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>
<h2 className="font-weight-bold" style={{ fontSize: "35px", marginLeft: "10px", marginTop: "10px" }}>Самовывозом</h2>
<p style={{ fontSize: "25px", marginLeft: "10px", marginTop: "10px" }}>Вы&nbsp;всегда сможете забрать заказ самостоятельно в&nbsp;Ульяновске в&nbsp;нашем магазине по&nbsp;адресу Северный Венец&nbsp;32. Время работы 13-21. Когда заказ будет собран и&nbsp;завезен в&nbsp;магазин, Вам на&nbsp;эл. почту придет письмо со&nbsp;статусом заказа &laquo;готов к&nbsp;выдаче&raquo; (проверяйте папку спам), после этого его можно забирать.</p>
<h2 className="font-weight-bold" style={{ fontSize: "35px", marginLeft: "10px", marginTop: "10px" }}>Доставкой Почты России</h2>
<p style={{ fontSize: "25px", marginLeft: "10px", marginTop: "10px" }}>Почта России - пересылка заказа осуществляется до вашего почтового отделения, получение бандероли в отделении Почты России по извещению. Срок доставки зависит от отдаленности вашего региона от Ульяновска. Примерные сроки доставки по РФ около 7-11 дней</p>
</div>
</div>
</>
);
}
export default Delivery;

View File

@ -0,0 +1,28 @@
import { useContext, useEffect } from "react";
import Image from "../../../assets/Images/btc.png";
import { ProductsContext } from './../../../contexts/productsContext';
import Card from "./components/Card";
function Home() {
const { getProducts, products } = useContext(ProductsContext);
useEffect(() => {
getProducts();
}, []);
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" id="catalog">
<img className="mx-auto pb-5" src={Image} alt="banner" width="300" />
{products && products.map(product => <Card product={product} key={product.id} />)}
</div>
</div>
</>
);
}
export default Home;

View File

@ -0,0 +1,55 @@
import { useContext } from "react";
import { ProductType } from './../../../../types/ProductType';
import { BasketContext } from './../../../../contexts/basketContext';
import { FavouritesContext } from "../../../../contexts/favouritesContext";
import { UserContext } from "../../../../contexts/userContext";
function Card({ product }) {
const { userIsAuth } = useContext(UserContext);
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="product d-flex flex-column h-100">
<div className="image_al">
<img src={product.photo} alt="Product Image" className="img-fluid" />
</div>
<div className="info">
<h3 className="card-title mb-2">{product.name}</h3>
<div className="info-price">
<span className="price"><strong>{product.price}$</strong></span>
{userIsAuth && (
<div className="d-flex align-items-center ms-auto">
<button
className="add-to-cart"
onClick={!isBasketProduct ? () => addProductToBasket(product.id) : () => deleteProductFromBasket(product.id)}
>
<i class={`fas ${isBasketProduct ? "fa-cart-shopping" : "fa-cart-plus"}`}></i>
</button>
<button
className="add-to-cart ms-2"
onClick={!isFavouriteProduct ? () => addProductToFavourites(product.id) : () => deleteProductFromFavourites(product.id)}
>
<i className={`fas ${isFavouriteProduct ? "fa-heart-crack" : "fa-heart"}`}></i>
</button>
</div>
)}
</div>
</div>
</div>
</div>
);
}
Card.propTypes = {
product: ProductType.isRequired
}
export default Card;

View File

@ -0,0 +1,60 @@
import { useContext } from "react";
import { useForm } from "react-hook-form";
import { Link, useNavigate } from "react-router-dom";
import { UserContext } from './../../../contexts/userContext';
function Login() {
const { login } = useContext(UserContext);
const navigate = useNavigate();
const { register, handleSubmit } = useForm();
async function onSubmit(data) {
try {
await login(data);
navigate("/");
} catch (error) {
console.log(error);
}
}
return (
<section className="h-100 mt-2">
<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: "#767479" }}>
<div className="card-body p-5">
<h2 className="text-uppercase text-center mb-5">Войти</h2>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-outline mb-4">
<input type="text" id="form3Example3cg" className="form-control form-control-lg" {...register("login")} />
<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" className="btn btn-block text-body mb-0" value="Войти" />
</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;

View File

@ -0,0 +1,131 @@
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={{ borderRadius: "15px", borderColor: "#767479" }}>
<div className="card-body p-5">
<h2 className="text-uppercase 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 id="deliveryTimeOptions">
<select className="form-select" id="timeSlotSelect" name="timeSlot">
<option defaultValue="10:00-10:30">10:00-10:30</option>
<option defaultValue="10:30-11:00">10:30-11:00</option>
<option defaultValue="11:00-11:30">11:00-11:30</option>
<option defaultValue="11:30-12:00">11:30-12:00</option>
<option defaultValue="12:00-12:30">12:00-12:30</option>
<option defaultValue="12:30-13:00">12:30-13:00</option>
<option defaultValue="13:00-13:30">13:00-13:30</option>
<option defaultValue="13:30-14:00">13:30-14:00</option>
<option defaultValue="14:00-14:30">14:00-14:30</option>
<option defaultValue="14:30-15:00">14:30-15:00</option>
<option defaultValue="15:00-15:30">15:00-15:30</option>
<option defaultValue="15:30-16:00">15:30-16:00</option>
<option defaultValue="16:00-16:30">16:00-16:30</option>
<option defaultValue="16:30-17:00">16:30-17:00</option>
<option defaultValue="17:00-17:30">17:00-17:30</option>
<option defaultValue="17:30-18:00">17:30-18:00</option>
<option defaultValue="18:00-18:30">18:00-18:30</option>
<option defaultValue="18:30-19:00">18:30-19:00</option>
<option defaultValue="19:00-19:30">19:00-19:30</option>
<option defaultValue="19:30-20:00">19:30-20:00</option>
<option defaultValue="20:00-20:30">20:00-20:30</option>
<option defaultValue="20:30-21:00">20:30-21:00</option>
<option defaultValue="21:00-21:30">21:00-21:30</option>
<option defaultValue="21:30-22:00">21:30-22:00</option>
<option defaultValue="22:00-22:30">22:00-22:30</option>
<option defaultValue="22:30-23:00">22:30-23:00</option>
</select>
<label htmlFor="timeSlotSelect">Выберите время доставки:</label>
</div>
</div>
<label className="form-check-label" htmlFor="creditCard">Вариант оплаты</label>
<div className="form-check mb-4">
<input className="form-check-input" type="radio" name="paymentMethod" id="cash" defaultValue="cash" />
<label className="form-check-label" htmlFor="cash">Наличные</label>
</div>
<div className="form-check mb-4">
<input className="form-check-input" type="radio" name="paymentMethod" id="creditCard"
defaultValue="creditCard" />
<label className="form-check-label" htmlFor="creditCard">Оплата картой</label>
</div>
<div className="form-outline mb-4" id="creditCardDetails" style={{ display: "none" }}>
<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>
<div className="d-flex justify-content-center">
<button className="btn btn" type="button" id="saveButton">Оформить</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
export default MakingAnOrder;

View 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, getCategories } = 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();
getCategories();
}, [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 (
<>
<h1 className="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>
</>
);
}
export default PageEdit;

View File

@ -0,0 +1,61 @@
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: "#767479" }}>
<div className="card-body p-5">
<h2 className="text-uppercase 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="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" type="button" id="saveButton">Сохранить</button>
</div>
<div className="d-flex justify-content-center">
<Link className="btn btn" type="button" to="/">Выйти</Link>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
export default PersonalAccount;

View File

@ -0,0 +1,44 @@
import { Link } from "react-router-dom";
function RecoveryPassword() {
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: "#767479" }}>
<div className="card-body p-5">
<h2 className="text-uppercase text-center mb-5">Восстановление пароля</h2>
<h4 className="text-black text-center mb-5">Введите свой адрес электронной почты, и мы вышлем вам
электронное письмо с инструкциями по сбросу вашего пароля</h4>
<form>
<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="d-flex justify-content-center">
<button type="button" className="btn btn-block text-body mb-0">Сбросить пароль</button>
</div>
<p className="text-center text-muted mb-0"><Link to="/login"
className="fw-bold text-body"><u>Войти</u></Link></p>
<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 RecoveryPassword;

View File

@ -0,0 +1,62 @@
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 { registration } = useContext(UserContext);
const navigate = useNavigate();
const { register, handleSubmit } = useForm();
async function onSubmit(data) {
try {
await registration(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: "#767479" }}>
<div className="card-body p-5">
<h2 className="text-uppercase text-center mb-5">Создать учетную запись</h2>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-outline mb-4">
<input type="text" id="form3Example3cg" className="form-control form-control-lg" {...register("login")} />
<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" className="btn btn-block text-body mb-0" value="Регистрация" />
</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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@ -0,0 +1,116 @@
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 { userId } = useContext(UserContext);
const { getProductById } = useContext(ProductsContext);
async function getBasketAndBasketProducts() {
if (!userId) {
return;
}
const bId = await BasketService.getBasketId(userId);
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 getProductsInBasketAndTheirTotalPrice() {
if (!basketProducts || basketProducts.length == 0) {
return [];
}
const arr = [];
let total = 0;
for (let i = 0; i < basketProducts.length; i++) {
const basketProduct = basketProducts[i];
const product = await getProductById(basketProduct.productId);
arr.push(product);
total += product.price;
}
return [arr, total];
}
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 {
const answer = confirm("Вы точно хотите удалить товар из корзины?");
if (!answer) {
return;
} else {
const productToDelete = basketProducts.find(bp => bp.productId == productId);
const deleteBasketProductResponse = await BasketService.deleteBasketProduct(productToDelete.id);
setBasketProducts(basketProducts.filter(bp => bp.productId != productId));
}
} catch (error) {
console.log(error);
}
}
async function clearBasket() {
if (!basketProducts || basketProducts.length == 0) {
return;
}
for (let i = 0; i < basketProducts.length; i++) {
const basketProduct = basketProducts[i];
await BasketService.deleteBasketProduct(basketProduct.id);
}
setBasketProducts([]);
}
return {
basketId,
basketProducts,
getBasketAndBasketProducts,
isProductInBasket,
addProductToBasket,
deleteProductFromBasket,
getProductsInBasketAndTheirTotalPrice,
clearBasket
};
}

View File

@ -0,0 +1,45 @@
import { useState, } from 'react';
import CategoryService from "../services/CategoryService";
export function useCategories() {
const [categories, setCategories] = useState(null);
async function getCategories() {
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 c = categories.find(category => category.id == id);
if (c) {
return c;
} else {
const response = await CategoryService.getById(id);
setCategories([...categories, response]);
return response;
}
} catch (error) {
console.log(error);
return {};
}
}
return {
categories,
getCategories,
getCategoryById
};
}

View File

@ -0,0 +1,68 @@
import { useContext, useState } from 'react';
import { UserContext } from "../contexts/userContext";
import FavouritesService from "../services/FavouritesService";
export function useFavourites() {
const [favourites, setFavourites] = useState(null);
const { userId } = useContext(UserContext);
// Функции
async function getFavourites() {
if (favourites || !userId) return;
try {
const response = await FavouritesService.getAll(userId);
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, 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);
const deleteFavouriteProductResponse = await FavouritesService.deleteFavouriteProduct(productToDelete.id);
setFavourites(favourites.filter(favourite => favourite.productId != productId));
} catch (error) {
console.log(error);
}
}
// Функции END
return {
favourites,
getFavourites,
isProductInFavourites,
addProductToFavourites,
deleteProductFromFavourites,
};
}

View File

@ -0,0 +1,59 @@
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 { userId } = useContext(UserContext);
const { clearBasket } = useContext(BasketContext);
const { getProductById } = useContext(ProductsContext);
async function createOrder(data, productsIds) {
const response = await OrderService.create({ ...data, userId: userId });
const orderId = response.id;
for (let i = 0; i < productsIds.length; i++) {
const orderCreateResponse = await OrdersProductService.create(orderId, productsIds[i]);
}
setOrders([...(orders || []), response]);
const clearBasketProductsResponse = await clearBasket();
return orderId;
}
async function getOrders() {
if (!userId) return;
try {
const response = await OrderService.getAll(userId);
setOrders(response);
} catch (error) {
console.log(error);
}
}
async function getProductsInOrder(orderId) {
const response = await OrdersProductService.getByOrderId(orderId);
const products = [];
for (let index = 0; index < response.length; index++) {
const orderProduct = response[index];
const product = await getProductById(orderProduct.productId);
products.push(product);
}
return products;
}
return {
orders,
createOrder,
getOrders,
getProductsInOrder
};
}

View File

@ -0,0 +1,68 @@
import { useState } from 'react';
import ProductService from "../services/ProductService";
export function useProducts() {
const [products, setProducts] = useState(null);
async function getProducts() {
try {
const response = await ProductService.getAll();
setProducts(response);
} catch (error) {
console.log(error);
}
}
async function deleteProduct(id) {
const answer = confirm("Вы действительно хотите удалить товар?");
if (answer == true) {
const response = await ProductService.delete(id);
setProducts(products.filter(p => p.id != id));
}
}
async function getProductById(id) {
try {
if (!products || products.length == 0) {
const response = await ProductService.getById(id);
setProducts([response]);
return response;
}
const existingProduct = products.find(product => product.id == id);
if (existingProduct) return existingProduct;
const response = await ProductService.getById(id);
setProducts([...products, response]);
return response;
} catch (error) {
console.log(error);
return {};
}
}
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 createProduct(data) {
const response = await ProductService.create(data);
setProducts([...(products || []), response]);
}
return {
products,
getProducts,
getProductById,
deleteProduct,
createProduct,
updateProduct
};
}

View File

@ -0,0 +1,54 @@
import { useState, } from 'react';
import UserService from "../services/UserService";
import BasketService from "../services/BasketService";
export function useUser() {
const [user, setUser] = useState(null);
function signOut() {
setUser(null);
localStorage.removeItem("user");
}
function checkIsAuth() {
const uuser = localStorage.getItem('user');
if (user) {
return;
}
if (uuser) {
const t = JSON.parse(uuser);
setUser(t);
}
}
async function login(data) {
try {
const response = await UserService.login(data);
setUser(response);
localStorage.setItem("user", JSON.stringify(response));
} catch (error) {
throw new Error(error);
}
}
async function registration(data) {
try {
const response = await UserService.registration({...data, "role": "user"});
const basketResponse = await BasketService.createBasket(response.id);
setUser(response);
localStorage.setItem("user", JSON.stringify(response));
} catch (error) {
throw new Error(error);
}
}
return {
userId: user ? user.id : null,
userIsAdmin: user ? user.role === "admin" : false,
userIsAuth: user ? user.id : false,
login,
registration,
signOut,
checkIsAuth
};
}

9
Lab5/lab5/src/main.jsx Normal file
View File

@ -0,0 +1,9 @@
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>,
);

View File

@ -0,0 +1,31 @@
import $axios from "../axios";
class BasketService {
createBasket = async (userId) => {
await $axios.post(`/baskets`, { userId });
}
getBasketId = async (userId) => {
const response = await $axios.get(`/baskets?userId=${userId}`);
if (response.data.length == 0) {
return null;
}
return response.data[0].id;
}
getBasketProducts = async (basketId) => {
const response = await $axios.get(`/basket_products?basketId=${basketId}`);
return response.data;
}
addProductToBasket = async (data) => {
const response = await $axios.post("/basket_products", data);
return response.data;
}
deleteBasketProduct = async (id) => {
await $axios.delete(`basket_products/${id}`);
}
}
export default new BasketService();

View File

@ -0,0 +1,15 @@
import $axios from '../axios';
class CategoryService {
getAll = async () => {
const response = await $axios.get("/categories");
return response.data;
}
getById = async (id) => {
const response = await $axios.get(`/categories/${id}`);
return response.data;
}
}
export default new CategoryService();

View File

@ -0,0 +1,19 @@
import $axios from '../axios';
class FavouritesService {
getAll = async (userId) => {
const response = await $axios.get(`/favourites?userId=${userId}`);
return response.data;
}
addProductToFavourites = async (data) => {
const response = await $axios.post("/favourites", data);
return response.data;
}
deleteFavouriteProduct = async (id) => {
await $axios.delete(`/favourites/${id}`);
}
}
export default new FavouritesService();

View File

@ -0,0 +1,15 @@
import $axios from "../axios";
class OrderService {
getAll = async (userId) => {
const response = await $axios.get(`/orders?userId=${userId}`);
return response.data;
}
create = async (data) => {
const response = await $axios.post("/orders", data);
return response.data;
}
}
export default new OrderService();

View File

@ -0,0 +1,14 @@
import $axios from "../axios";
class OrdersProductService {
create = async (orderId, productId) => {
await $axios.post("/order_products", { orderId, productId });
}
getByOrderId = async (orderId) => {
const response = await $axios.get(`/order_products?orderId=${orderId}`);
return response.data;
}
}
export default new OrdersProductService();

View File

@ -0,0 +1,29 @@
import $axios from '../axios';
class ProductService {
getAll = async () => {
const response = await $axios.get(`/products?_limit=10`);
return response.data;
}
getById = async (id) => {
const response = await $axios.get(`/products/${id}`);
return response.data;
}
delete = async (id) => {
await $axios.delete(`/products/${id}`);
}
create = async (data) => {
const response = await $axios.post('/products', data);
return response.data;
}
update = async (data) => {
const response = await $axios.put(`/products/${data.id}`, data);
return response.data;
}
}
export default new ProductService();

View File

@ -0,0 +1,20 @@
import $axios from "../axios";
class UserService {
registration = async (data) => {
const response = await $axios.post("/users", data);
const user = response.data;
return user;
}
login = async (data) => {
const response = await $axios.get(`/users?login=${data.login}`);
if (response.data.length == 0) {
throw new Error("User not found");
}
return response.data[0];
}
}
export default new UserService();

View File

@ -0,0 +1,6 @@
import PropTypes from 'prop-types';
export const CategoryType = PropTypes.shape({
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
});

View 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,
});

View File

@ -0,0 +1 @@
export const serverUrl = "http://localhost:3000";

17
Lab5/lab5/vite.config.js Normal file
View 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'),
},
},
});

Binary file not shown.