LabWork_04-05
24
LabWork_04-05/.eslintrc.cjs
Normal file
@ -0,0 +1,24 @@
|
||||
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 },
|
||||
],
|
||||
'indent': 'off',
|
||||
'no-console': 'off',
|
||||
'arrow-body-style': 'off',
|
||||
'implicit-arrow-linebreak': 'off',
|
||||
},
|
||||
}
|
24
LabWork_04-05/.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
LabWork_04-05/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
|
BIN
LabWork_04-05/Report.docx
Normal file
124
LabWork_04-05/data.json
Normal file
@ -0,0 +1,124 @@
|
||||
{
|
||||
"types": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Protection 4"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Thorns 3"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Sharpness 5"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Fortune 3"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Infinity"
|
||||
}
|
||||
],
|
||||
"lines": [
|
||||
{
|
||||
"typeId": "3",
|
||||
"price": "25.00",
|
||||
"amount": "4",
|
||||
"total": "100.00",
|
||||
"id": 23
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 24
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 25
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 26
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 27
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 28
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 29
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 30
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 31
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 32
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 33
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 34
|
||||
},
|
||||
{
|
||||
"typeId": "5",
|
||||
"price": "10.00",
|
||||
"amount": "1",
|
||||
"total": "10.00",
|
||||
"id": 35
|
||||
},
|
||||
{
|
||||
"typeId": "2",
|
||||
"price": "30.00",
|
||||
"amount": "3",
|
||||
"total": "90.00",
|
||||
"id": 36
|
||||
}
|
||||
]
|
||||
}
|
14
LabWork_04-05/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/public/favicons/favicon.svg" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:regular,500,600,700,800,900&display=swap" rel="stylesheet"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bookshelf</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="h-100 d-flex flex-column"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
14
LabWork_04-05/jsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2020",
|
||||
"jsx": "react",
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
]
|
||||
}
|
6165
LabWork_04-05/package-lock.json
generated
Normal file
41
LabWork_04-05/package.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "labwork-04",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"rest": "json-server data.json",
|
||||
"vite": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "http-server -p 3000 ./dist/",
|
||||
"dev": "npm-run-all --parallel rest vite",
|
||||
"prod": "npm-run-all build --parallel serve rest"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.18.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"axios": "^1.6.1",
|
||||
"bootstrap": "^5.3.2",
|
||||
"react-bootstrap": "^2.9.1",
|
||||
"react-bootstrap-icons": "^1.10.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"http-server": "14.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"json-server": "^0.17.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"vite": "^4.4.5"
|
||||
}
|
||||
}
|
11
LabWork_04-05/public/favicons/favicon.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 438 B |
0
LabWork_04-05/src/App.css
Normal file
24
LabWork_04-05/src/App.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Container } from 'react-bootstrap';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import './App.css';
|
||||
import Header from './components/header/Header.jsx';
|
||||
import Footer from './components/footer/Footer.jsx';
|
||||
|
||||
const App = ({ routes }) => {
|
||||
return (
|
||||
<>
|
||||
<Header routes={routes} />
|
||||
<Container className='wrapper' as='main' fluid>
|
||||
<Outlet />
|
||||
</Container>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
App.propTypes = {
|
||||
routes: PropTypes.array,
|
||||
};
|
||||
|
||||
export default App;
|
BIN
LabWork_04-05/src/assets/armor.png
Normal file
After Width: | Height: | Size: 125 KiB |
BIN
LabWork_04-05/src/assets/avatar.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
LabWork_04-05/src/assets/book.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
LabWork_04-05/src/assets/bookshelf.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
LabWork_04-05/src/assets/cross.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
LabWork_04-05/src/assets/fortune.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
LabWork_04-05/src/assets/infinity.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
LabWork_04-05/src/assets/logo.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
LabWork_04-05/src/assets/pickaxe.png
Normal file
After Width: | Height: | Size: 606 B |
BIN
LabWork_04-05/src/assets/protection.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
LabWork_04-05/src/assets/sharpness.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
LabWork_04-05/src/assets/thorns.png
Normal file
After Width: | Height: | Size: 57 KiB |
40
LabWork_04-05/src/components/api/ApiClient.js
Normal file
@ -0,0 +1,40 @@
|
||||
import axios from 'axios';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
export class HttpError extends Error {
|
||||
constructor(message = '') {
|
||||
super(message);
|
||||
this.name = 'HttpError';
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
toast.error(message, { id: 'HttpError' });
|
||||
}
|
||||
}
|
||||
|
||||
function responseHandler(response) {
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
const data = response?.data;
|
||||
if (!data) {
|
||||
throw new HttpError('API Error. No data!');
|
||||
}
|
||||
return data;
|
||||
}
|
||||
throw new HttpError(`API Error! Invalid status code ${response.status}!`);
|
||||
}
|
||||
|
||||
function responseErrorHandler(error) {
|
||||
if (error === null) {
|
||||
throw new Error('Unrecoverable error!! Error is null!');
|
||||
}
|
||||
toast.error(error.message, { id: 'AxiosError' });
|
||||
return Promise.reject(error.message);
|
||||
}
|
||||
|
||||
export const ApiClient = axios.create({
|
||||
baseURL: 'http://localhost:3000/',
|
||||
timeout: '3000',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
ApiClient.interceptors.response.use(responseHandler, responseErrorHandler);
|
29
LabWork_04-05/src/components/api/ApiService.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { ApiClient } from './ApiClient';
|
||||
|
||||
class ApiService {
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
async getAll(expand) {
|
||||
return ApiClient.get(`${this.url}${expand || ''}`);
|
||||
}
|
||||
|
||||
async get(id, expand) {
|
||||
return ApiClient.get(`${this.url}/${id}${expand || ''}`);
|
||||
}
|
||||
|
||||
async create(body) {
|
||||
return ApiClient.post(this.url, body);
|
||||
}
|
||||
|
||||
async update(id, body) {
|
||||
return ApiClient.put(`${this.url}/${id}`, body);
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
return ApiClient.delete(`${this.url}/${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiService;
|
20
LabWork_04-05/src/components/banner/Banner.css
Normal file
@ -0,0 +1,20 @@
|
||||
#banner {
|
||||
margin: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#banner img.banner_show {
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
transition: opacity 2s, visibility 0s;
|
||||
}
|
||||
|
||||
#banner img.banner_hide {
|
||||
height: 0;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 1s, visibility 0s 1s;
|
||||
}
|
42
LabWork_04-05/src/components/banner/Banner.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import banner1 from '../../assets/protection.png';
|
||||
import banner2 from '../../assets/thorns.png';
|
||||
import banner3 from '../../assets/sharpness.png';
|
||||
import banner4 from '../../assets/fortune.png';
|
||||
import banner5 from '../../assets/infinity.png';
|
||||
import './Banner.css';
|
||||
|
||||
const Banner = () => {
|
||||
const [currentBanner, setCurrentBanner] = useState(0);
|
||||
const banners = [banner1, banner2, banner3, banner4, banner5];
|
||||
|
||||
const getBannerClass = (index) => {
|
||||
return currentBanner === index ? 'banner_show' : 'banner_hide';
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const bannerInterval = setInterval(
|
||||
() => {
|
||||
console.info('Banner changed');
|
||||
let current = currentBanner + 1;
|
||||
if (current === banners.length) {
|
||||
current = 0;
|
||||
}
|
||||
setCurrentBanner(current);
|
||||
},
|
||||
5000,
|
||||
);
|
||||
return () => clearInterval(bannerInterval);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="banner__loop" id="banner" >
|
||||
{
|
||||
banners.map((banner, index) =>
|
||||
<img key={index} className={getBannerClass(index)} src={banner} alt={`banner${index}`} />)
|
||||
}
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
export default Banner;
|
41
LabWork_04-05/src/components/footer/Footer.css
Normal file
@ -0,0 +1,41 @@
|
||||
/* Футер */
|
||||
/* Обертка футера */
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 30px 0px;
|
||||
background-color: #2c2a2a;
|
||||
}
|
||||
.footer__container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Права */
|
||||
.footer__privacy {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Адаптив футера */
|
||||
@media (max-width: 768px) {
|
||||
.footer {
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
.footer__container {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer *> .nav__item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.footer__privacy {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
26
LabWork_04-05/src/components/footer/Footer.jsx
Normal file
@ -0,0 +1,26 @@
|
||||
import './Footer.css';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer className="footer">
|
||||
<div className="footer__container _container">
|
||||
<nav className="footer__nav nav">
|
||||
<ul className="nav__list">
|
||||
<li className="nav__item">
|
||||
<Link to="/about_us" className="nav__link">About us</Link>
|
||||
</li>
|
||||
<li className="nav__item">
|
||||
<Link to="/contact_us" className="nav__link">Contact us</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="footer__privacy">
|
||||
© made by Factorino. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
71
LabWork_04-05/src/components/header/Header.css
Normal file
@ -0,0 +1,71 @@
|
||||
/* Шапка */
|
||||
/* Обертка шапки */
|
||||
.header {
|
||||
background-color: #2c2a2a;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.header__container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Логотип шапки */
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.logo__img img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.logo__nametag {
|
||||
margin-left: 30px;
|
||||
}
|
||||
/* Название логотипа */
|
||||
.nametag__title {
|
||||
font-size: 50px;
|
||||
font-weight: bold;
|
||||
color: #a721fa;
|
||||
}
|
||||
.nametag__subtitle {
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Личный кабинет */
|
||||
.account {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.account__img img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.account__link {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.account__link:hover {
|
||||
color: #a721fa;
|
||||
}
|
||||
|
||||
/* Адаптив шапки */
|
||||
@media (max-width: 768px) {
|
||||
.header__container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nametag__title {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.account__link {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
37
LabWork_04-05/src/components/header/Header.jsx
Normal file
@ -0,0 +1,37 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import './Header.css';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Navigation from '../navigation/Navigation.jsx';
|
||||
import logo from '../../assets/logo.png';
|
||||
import avatar from '../../assets/avatar.png';
|
||||
|
||||
const Header = ({ routes }) => {
|
||||
return (
|
||||
<header className="header">
|
||||
<div className="header__container _container">
|
||||
<div className="header__logo logo">
|
||||
<div className="logo__img">
|
||||
<img src={logo} alt="logo.png" />
|
||||
</div>
|
||||
<div className="logo__nametag">
|
||||
<div className="nametag__title">Bookshelf</div>
|
||||
<div className="nametag__subtitle">Online-library</div>
|
||||
</div>
|
||||
</div>
|
||||
<Navigation routes={routes}></Navigation>
|
||||
<div className="account">
|
||||
<div className="account__img">
|
||||
<img src={avatar} alt="avatar.png" />
|
||||
</div>
|
||||
<Link to="/account" className="account__link">My account</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
Header.propTypes = {
|
||||
routes: PropTypes.array,
|
||||
};
|
||||
|
||||
export default Header;
|
22
LabWork_04-05/src/components/input/Input.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
const Input = ({
|
||||
name, placeholder, value, onChange, className, ...rest
|
||||
}) => {
|
||||
return (
|
||||
<Form.Group controlId={name}>
|
||||
<Form.Control name={name || ''} placeholder={placeholder || ''} value={value || ''} onChange={onChange} className={`${className || ''}`} {...rest} />
|
||||
</Form.Group>
|
||||
);
|
||||
};
|
||||
|
||||
Input.propTypes = {
|
||||
name: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Input;
|
28
LabWork_04-05/src/components/input/Select.jsx
Normal file
@ -0,0 +1,28 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
const Select = ({
|
||||
values, name, value, onChange, className, ...rest
|
||||
}) => {
|
||||
return (
|
||||
<Form.Group controlId={name}>
|
||||
<Form.Select name={name || ''} value={value || ''} onChange={onChange} className={className || ''} {...rest}>
|
||||
<option value=''>Choose the book</option>
|
||||
{
|
||||
values.map((type) => <option key={type.id} value={type.id}>{type.name}</option>)
|
||||
}
|
||||
</Form.Select>
|
||||
</Form.Group>
|
||||
);
|
||||
};
|
||||
|
||||
Select.propTypes = {
|
||||
values: PropTypes.array,
|
||||
name: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Select;
|
82
LabWork_04-05/src/components/lines/form/Form.css
Normal file
@ -0,0 +1,82 @@
|
||||
.form {
|
||||
width: 28%;
|
||||
max-width: 550px;
|
||||
margin-right: 50px;
|
||||
border: solid 5px #a721fa;
|
||||
border-radius: 70px;
|
||||
color: #fff;
|
||||
background-color: #2c2a2a;
|
||||
}
|
||||
.form__title {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
padding-top: 30px;
|
||||
}
|
||||
.form__subtitle {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.main_shop *> .form {
|
||||
margin-right: 0px;
|
||||
}
|
||||
.form__header {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
padding-top: 30px;
|
||||
}
|
||||
.fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 11% 15%;
|
||||
gap: 20px;
|
||||
}
|
||||
.fields__item,
|
||||
.fields__price,
|
||||
.fields__amount,
|
||||
.fields__name,
|
||||
.fields__email
|
||||
.fields__number,
|
||||
.fields__password,
|
||||
.fields__password_repeat {
|
||||
padding: 15px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
.fields__message {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 200px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
.fields__submit {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 30px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background-color: #a721fa;
|
||||
}
|
||||
.fields__submit:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.fields__submit:active {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.form__warning {
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.form {
|
||||
width: 100%;
|
||||
margin-right: 0px;
|
||||
margin-bottom: 50px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
}
|
47
LabWork_04-05/src/components/lines/form/LinesForm.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useLinesItemForm from '../hooks/LinesItemFormHook';
|
||||
import LinesItemForm from './LinesItemForm.jsx';
|
||||
import './Form.css';
|
||||
|
||||
const LinesForm = ({ id }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
item,
|
||||
validated,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
} = useLinesItemForm(id);
|
||||
|
||||
const onSubmit = async (event) => {
|
||||
if (await handleSubmit(event)) {
|
||||
navigate('/account');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form className='main__shop_form form' noValidate validated={validated} onSubmit={onSubmit}>
|
||||
<h2 className="form__title">Enter the title</h2>
|
||||
<h3 className="form__subtitle">and we will find your book</h3>
|
||||
<div className='form__fields fields'>
|
||||
<LinesItemForm item={item} handleChange={handleChange} />
|
||||
<Form.Group className='row justify-content-center m-0 mt-3'>
|
||||
<Button className='fields__submit' type='submit' variant='primary'>
|
||||
Buy books
|
||||
</Button>
|
||||
</Form.Group>
|
||||
<div className="form__warning">With rare exceptions, our library may<br/>not contain the desired book.</div>
|
||||
</div>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
LinesForm.propTypes = {
|
||||
id: PropTypes.string,
|
||||
};
|
||||
|
||||
export default LinesForm;
|
24
LabWork_04-05/src/components/lines/form/LinesItemForm.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import Input from '../../input/Input.jsx';
|
||||
import Select from '../../input/Select.jsx';
|
||||
import useTypes from '../../types/hooks/TypesHook';
|
||||
import './Form.css';
|
||||
|
||||
const LinesItemForm = ({ item, handleChange }) => {
|
||||
const { types } = useTypes();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select values={types} name='typeId' value={item.typeId} onChange={handleChange} className='fields__item' required />
|
||||
<Input name='price' placeholder='Enter the price of the book' value={item.price} onChange={handleChange} className='fields__price' type='number' min='0.00' step='5.00' required />
|
||||
<Input name='amount' placeholder='Enter the amount of books' value={item.amount} onChange={handleChange} className='fields__amount' type='number' min='1' step='1' required />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
LinesItemForm.propTypes = {
|
||||
item: PropTypes.object,
|
||||
handleChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export default LinesItemForm;
|
@ -0,0 +1,34 @@
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import useModal from '../../lines/modal/ModalHook';
|
||||
import LinesApiService from '../service/LinesApiService';
|
||||
|
||||
const useLinesDeleteModal = (linesChangeHandle) => {
|
||||
const { isModalShow, showModal, hideModal } = useModal();
|
||||
const [currentId, setCurrentId] = useState(0);
|
||||
|
||||
const showModalDialog = (id) => {
|
||||
showModal();
|
||||
setCurrentId(id);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
hideModal();
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
await LinesApiService.delete(currentId);
|
||||
linesChangeHandle();
|
||||
toast.success('Элемент успешно удален', { id: 'LinesTable' });
|
||||
onClose();
|
||||
};
|
||||
|
||||
return {
|
||||
isDeleteModalShow: isModalShow,
|
||||
showDeleteModal: showModalDialog,
|
||||
handleDeleteConfirm: onDelete,
|
||||
handleDeleteCancel: onClose,
|
||||
};
|
||||
};
|
||||
|
||||
export default useLinesDeleteModal;
|
28
LabWork_04-05/src/components/lines/hooks/LinesFilterHook.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import useTypes from '../../types/hooks/TypesHook';
|
||||
|
||||
const useTypeFilter = () => {
|
||||
const filterName = 'type';
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const { types } = useTypes();
|
||||
|
||||
const handleFilterChange = (event) => {
|
||||
const type = event.target.value;
|
||||
if (type) {
|
||||
searchParams.set(filterName, event.target.value);
|
||||
} else {
|
||||
searchParams.delete(filterName);
|
||||
}
|
||||
setSearchParams(searchParams);
|
||||
};
|
||||
|
||||
return {
|
||||
types,
|
||||
currentFilter: searchParams.get(filterName) || '',
|
||||
handleFilterChange,
|
||||
};
|
||||
};
|
||||
|
||||
export default useTypeFilter;
|
29
LabWork_04-05/src/components/lines/hooks/LinesHook.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import LinesApiService from '../service/LinesApiService';
|
||||
|
||||
const useLines = (typeFilter) => {
|
||||
const [linesRefresh, setLinesRefresh] = useState(false);
|
||||
const [lines, setLines] = useState([]);
|
||||
const handleLinesChange = () => setLinesRefresh(!linesRefresh);
|
||||
|
||||
const getLines = async () => {
|
||||
let expand = '?_expand=type';
|
||||
if (typeFilter) {
|
||||
expand = `${expand}&typeId=${typeFilter}`;
|
||||
}
|
||||
const data = await LinesApiService.getAll(expand);
|
||||
setLines(data ?? []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getLines();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [linesRefresh, typeFilter]);
|
||||
|
||||
return {
|
||||
lines,
|
||||
handleLinesChange,
|
||||
};
|
||||
};
|
||||
|
||||
export default useLines;
|
@ -0,0 +1,65 @@
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import LinesApiService from '../service/LinesApiService';
|
||||
import useLinesItem from './LinesItemHook';
|
||||
|
||||
const useLinesItemForm = (id, linesChangeHandle) => {
|
||||
const { item, setItem } = useLinesItem(id);
|
||||
|
||||
const [validated, setValidated] = useState(false);
|
||||
|
||||
const resetValidity = () => {
|
||||
setValidated(false);
|
||||
};
|
||||
|
||||
const getLineObject = (formData) => {
|
||||
const typeId = parseInt(formData.typeId, 10);
|
||||
const price = parseFloat(formData.price).toFixed(2);
|
||||
const amount = parseInt(formData.amount, 10);
|
||||
const total = parseFloat(price * amount).toFixed(2);
|
||||
return {
|
||||
typeId: typeId.toString(),
|
||||
price: price.toString(),
|
||||
amount: amount.toString(),
|
||||
total: total.toString(),
|
||||
};
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
const inputName = event.target.name;
|
||||
const inputValue = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
|
||||
setItem({
|
||||
...item,
|
||||
[inputName]: inputValue,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
const form = event.currentTarget;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const body = getLineObject(item);
|
||||
if (form.checkValidity()) {
|
||||
if (id === undefined) {
|
||||
await LinesApiService.create(body);
|
||||
} else {
|
||||
await LinesApiService.update(id, body);
|
||||
}
|
||||
if (linesChangeHandle) linesChangeHandle();
|
||||
toast.success('Элемент успешно сохранен', { id: 'LinesTable' });
|
||||
return true;
|
||||
}
|
||||
setValidated(true);
|
||||
return false;
|
||||
};
|
||||
|
||||
return {
|
||||
item,
|
||||
validated,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
resetValidity,
|
||||
};
|
||||
};
|
||||
|
||||
export default useLinesItemForm;
|
34
LabWork_04-05/src/components/lines/hooks/LinesItemHook.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import LinesApiService from '../service/LinesApiService';
|
||||
|
||||
const useLinesItem = (id) => {
|
||||
const emptyItem = {
|
||||
id: '',
|
||||
typeId: '',
|
||||
price: '0',
|
||||
amount: '0',
|
||||
total: '0',
|
||||
};
|
||||
const [item, setItem] = useState({ ...emptyItem });
|
||||
|
||||
const getItem = async (itemId = undefined) => {
|
||||
if (itemId && itemId > 0) {
|
||||
const data = await LinesApiService.get(itemId);
|
||||
setItem(data);
|
||||
} else {
|
||||
setItem({ ...emptyItem });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getItem(id);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id]);
|
||||
|
||||
return {
|
||||
item,
|
||||
setItem,
|
||||
};
|
||||
};
|
||||
|
||||
export default useLinesItem;
|
26
LabWork_04-05/src/components/lines/modal/Modal.css
Normal file
@ -0,0 +1,26 @@
|
||||
.modal-content {
|
||||
background-color: #2c2a2a;
|
||||
color: #fff;
|
||||
border: solid 1px #fff;
|
||||
}
|
||||
.modal-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.btn-close {
|
||||
color: #fff;
|
||||
}
|
||||
.modal__body {
|
||||
font-size: 14px;
|
||||
}
|
||||
.modal__button_no {
|
||||
background-color: #363434;
|
||||
}
|
||||
.modal__button_no:hover {
|
||||
background-color: #363434;
|
||||
}
|
||||
.modal__button_yes {
|
||||
background-color: #a721fa;
|
||||
}
|
||||
.modal__button_yes:hover {
|
||||
background-color: #a721fa;
|
||||
}
|
42
LabWork_04-05/src/components/lines/modal/ModalConfirm.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Modal } from 'react-bootstrap';
|
||||
import { createPortal } from 'react-dom';
|
||||
import './Modal.css';
|
||||
|
||||
const ModalConfirm = ({
|
||||
show, title, message, onConfirm, onClose,
|
||||
}) => {
|
||||
return createPortal(
|
||||
<Modal className='modal' show={show} backdrop='static' onHide={() => onClose()}>
|
||||
<Modal.Header className='pt-2 pb-2 ps-3 pe-3 modal__header' closeButton>
|
||||
<Modal.Title className='modal__title'>{title}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body className='modal__body'>
|
||||
{message}
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer className='m-0 pt-2 pb-2 ps-3 pe-3 row justify-content-center modal__foter'>
|
||||
<Button variant='secondary' className='col-5 m-0 me-2 modal__button_no'
|
||||
onClick={() => onClose()}>
|
||||
No
|
||||
</Button>
|
||||
<Button variant='primary' className='col-5 m-0 ms-2 modal__button_yes'
|
||||
onClick={() => onConfirm()}>
|
||||
Yes
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>,
|
||||
document.body,
|
||||
);
|
||||
};
|
||||
|
||||
ModalConfirm.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
onConfirm: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ModalConfirm;
|
21
LabWork_04-05/src/components/lines/modal/ModalHook.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
const useModal = () => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const showModalDialog = () => {
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const hideModalDialog = () => {
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
return {
|
||||
isModalShow: showModal,
|
||||
showModal: showModalDialog,
|
||||
hideModal: hideModalDialog,
|
||||
};
|
||||
};
|
||||
|
||||
export default useModal;
|
@ -0,0 +1,5 @@
|
||||
import ApiService from '../../api/ApiService';
|
||||
|
||||
const LinesApiService = new ApiService('lines');
|
||||
|
||||
export default LinesApiService;
|
62
LabWork_04-05/src/components/lines/table/Lines.css
Normal file
@ -0,0 +1,62 @@
|
||||
/* Корзина покупок */
|
||||
.cart {
|
||||
width: 50%;
|
||||
min-height: 600px;
|
||||
padding: 3% 3%;
|
||||
border: solid 5px #a721fa;
|
||||
border-radius: 70px;
|
||||
color: #fff;
|
||||
background-color: #2c2a2a;
|
||||
}
|
||||
.cart__products {
|
||||
max-height: 550px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
gap: 10px;
|
||||
}
|
||||
.cart__row_header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.cart__row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
border-radius: 30px;
|
||||
background-color: #363434;
|
||||
}
|
||||
.cart__column {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
/* Адаптив Account page */
|
||||
@media (max-width: 1240px) {
|
||||
.cart {
|
||||
width: 100%;
|
||||
margin-bottom: 50px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.cart__row {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cart__name {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.cart__row_header {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
48
LabWork_04-05/src/components/lines/table/Lines.jsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import ModalConfirm from '../../lines/modal/ModalConfirm.jsx';
|
||||
import useLinesDeleteModal from '../../lines/hooks/LinesDeleteModalHook';
|
||||
import useTypeFilter from '../hooks/LinesFilterHook';
|
||||
import useLines from '../hooks/LinesHook';
|
||||
import LinesTable from './LinesTable.jsx';
|
||||
import LinesTableRow from './LinesTableRow.jsx';
|
||||
import './Lines.css';
|
||||
|
||||
const Lines = () => {
|
||||
const { currentFilter, } = useTypeFilter();
|
||||
|
||||
const { lines, handleLinesChange } = useLines(currentFilter);
|
||||
|
||||
const {
|
||||
isDeleteModalShow,
|
||||
showDeleteModal,
|
||||
handleDeleteConfirm,
|
||||
handleDeleteCancel,
|
||||
} = useLinesDeleteModal(handleLinesChange);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const showEditPage = (id) => {
|
||||
navigate(`/shop/${id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<LinesTable>
|
||||
{
|
||||
lines.map((line) =>
|
||||
<LinesTableRow key={line.id}
|
||||
line={line}
|
||||
onDelete={() => showDeleteModal(line.id)}
|
||||
onEditInPage={() => showEditPage(line.id)}
|
||||
/>)
|
||||
}
|
||||
</LinesTable>
|
||||
|
||||
<ModalConfirm show={isDeleteModalShow}
|
||||
onConfirm={handleDeleteConfirm} onClose={handleDeleteCancel}
|
||||
title='Delete' message='Delete book?' />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Lines;
|
43
LabWork_04-05/src/components/lines/table/LinesTable.css
Normal file
@ -0,0 +1,43 @@
|
||||
.cart {
|
||||
width: 50%;
|
||||
min-height: 600px;
|
||||
padding: 3% 3%;
|
||||
border: solid 5px #a721fa;
|
||||
border-radius: 70px;
|
||||
color: #fff;
|
||||
background-color: #2c2a2a;
|
||||
}
|
||||
.cart__products {
|
||||
max-height: 550px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
gap: 10px;
|
||||
}
|
||||
.cart__row_header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.cart__column {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
@media (max-width: 1240px) {
|
||||
.cart {
|
||||
width: 100%;
|
||||
margin-bottom: 50px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.cart__row_header {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
29
LabWork_04-05/src/components/lines/table/LinesTable.jsx
Normal file
@ -0,0 +1,29 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import './LinesTable.css';
|
||||
|
||||
const LinesTable = ({ children }) => {
|
||||
return (
|
||||
<div className='main__cart cart'>
|
||||
<table className='cart__table'>
|
||||
<thead className='cart__row_header'>
|
||||
<tr className='cart__row_header'>
|
||||
<th scope='col' className='cart__column'>Name</th>
|
||||
<th scope='col' className='cart__column'>Price</th>
|
||||
<th scope='col' className='cart__column'>Amount</th>
|
||||
<th scope='col' className='cart__column'>Total</th>
|
||||
<th scope='col' className='cart__column'>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className='cart__products'>
|
||||
{children}
|
||||
</tbody >
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
LinesTable.propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default LinesTable;
|
22
LabWork_04-05/src/components/lines/table/LinesTableRow.css
Normal file
@ -0,0 +1,22 @@
|
||||
.cart__row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
border-radius: 30px;
|
||||
background-color: #363434;
|
||||
}
|
||||
.cart__column {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
@media (max-width: 1240px) {
|
||||
.cart__row {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
33
LabWork_04-05/src/components/lines/table/LinesTableRow.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PencilSquare, Trash3 } from 'react-bootstrap-icons';
|
||||
import './LinesTableRow.css';
|
||||
|
||||
const LinesTableRow = ({
|
||||
line, onDelete, onEditInPage,
|
||||
}) => {
|
||||
const handleAnchorClick = (event, action) => {
|
||||
event.preventDefault();
|
||||
action();
|
||||
};
|
||||
|
||||
return (
|
||||
<tr className="cart__row">
|
||||
<td className='cart__column'>{line.type.name}</td>
|
||||
<td className='cart__column'>{parseFloat(line.price).toFixed(2)}</td>
|
||||
<td className='cart__column'>{line.amount}</td>
|
||||
<td className='cart__column'>{parseFloat(line.total).toFixed(2)}</td>
|
||||
<td className='cart__column'>
|
||||
<a href="#" onClick={(event) => handleAnchorClick(event, onEditInPage)}><PencilSquare /></a>
|
||||
<a href="#" onClick={(event) => handleAnchorClick(event, onDelete)}><Trash3 /></a>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
LinesTableRow.propTypes = {
|
||||
line: PropTypes.object,
|
||||
onDelete: PropTypes.func,
|
||||
onEditInPage: PropTypes.func,
|
||||
};
|
||||
|
||||
export default LinesTableRow;
|
44
LabWork_04-05/src/components/navigation/Navigation.css
Normal file
@ -0,0 +1,44 @@
|
||||
/* Навигация шапки */
|
||||
.nav__list {
|
||||
margin: 0px 10px;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
}
|
||||
.header *> .nav__item {
|
||||
display: inline-block;;
|
||||
width: calc(1/6 * 100%);
|
||||
}
|
||||
.nav__item:not(:last-child) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.nav__link {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav__link:hover {
|
||||
color: #a721fa;
|
||||
}
|
||||
|
||||
/* Адаптив навигации */
|
||||
@media (max-width: 768px) {
|
||||
.header *> .nav {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
.header *> .nav__list {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 20%;
|
||||
}
|
||||
|
||||
.header *> .nav__item:not(:last-child) {
|
||||
margin-right: 0px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nav__link {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
33
LabWork_04-05/src/components/navigation/Navigation.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Container, Nav, Navbar } from 'react-bootstrap';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import './Navigation.css';
|
||||
|
||||
const Navigation = ({ routes }) => {
|
||||
const location = useLocation();
|
||||
const pages = routes.filter((route) => Object.prototype.hasOwnProperty.call(route, 'title'));
|
||||
|
||||
return (
|
||||
<Navbar className='header__nav nav'>
|
||||
<Container fluid>
|
||||
<Navbar.Collapse id='main-navbar'>
|
||||
<Nav activeKey={location.pathname}>
|
||||
{pages.map((page) => (
|
||||
<Nav.Item className='nav__item' key={page.path}>
|
||||
<Nav.Link as={Link} className="nav__link" to={page.path ?? '/'}>
|
||||
{page.title}
|
||||
</Nav.Link>
|
||||
</Nav.Item>
|
||||
))}
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
</Navbar>
|
||||
);
|
||||
};
|
||||
|
||||
Navigation.propTypes = {
|
||||
routes: PropTypes.array,
|
||||
};
|
||||
|
||||
export default Navigation;
|
29
LabWork_04-05/src/components/shopTable/Cell.css
Normal file
@ -0,0 +1,29 @@
|
||||
.table__cell {
|
||||
width: 17%;
|
||||
}
|
||||
.table__radio {
|
||||
display: none;
|
||||
}
|
||||
.table__radio:checked + .table__label {
|
||||
background-color: #a721fa;
|
||||
}
|
||||
.table__label {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
line-height: 2.5;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: 30px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* Адаптив Armor page */
|
||||
@media (max-width: 768px) {
|
||||
.table__label {
|
||||
height: 100%;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
font-size: 10px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
}
|
19
LabWork_04-05/src/components/shopTable/Cell.jsx
Normal file
@ -0,0 +1,19 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import './Cell.css';
|
||||
|
||||
const Cell = ({ name , id, label }) => {
|
||||
return (
|
||||
<div className="table__cell">
|
||||
<input className="table__radio" type="radio" name={name} id={id} />
|
||||
<label htmlFor={id} className="table__label">{label}</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Cell.propTypes = {
|
||||
name: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
}
|
||||
|
||||
export default Cell;
|
13
LabWork_04-05/src/components/shopTable/Row.css
Normal file
@ -0,0 +1,13 @@
|
||||
.table__row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
border-radius: 30px;
|
||||
background-color: #363434;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.table__row {
|
||||
border-radius: 30px;
|
||||
}
|
||||
}
|
20
LabWork_04-05/src/components/shopTable/Row.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import Cell from '../shopTable/Cell.jsx';
|
||||
import './Row.css';
|
||||
|
||||
const Row = ({ name, labels }) => {
|
||||
return (
|
||||
<div className="table__row">
|
||||
{labels.map((label, index) =>(
|
||||
<Cell key={index} name={name} id={`${name}${index + 1}`} label={label} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Row.propTypes = {
|
||||
name: PropTypes.string,
|
||||
labels: PropTypes.array,
|
||||
};
|
||||
|
||||
export default Row;
|
24
LabWork_04-05/src/components/shopTable/Table.css
Normal file
@ -0,0 +1,24 @@
|
||||
/* Таблица */
|
||||
.table {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 3%;
|
||||
border: solid 5px #a721fa;
|
||||
border-radius: 70px;
|
||||
color: #fff;
|
||||
background-color: #2c2a2a;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.table {
|
||||
width: 100%;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.table {
|
||||
border-radius: 30px;
|
||||
}
|
||||
}
|
18
LabWork_04-05/src/components/shopTable/Table.jsx
Normal file
@ -0,0 +1,18 @@
|
||||
import Row from '../shopTable/Row.jsx';
|
||||
import './Table.css';
|
||||
|
||||
const Table = () => {
|
||||
return (
|
||||
<div className='main__table table'>
|
||||
<Row name="protection" labels={['Protection 4', 'Protection 3', 'Protection 2', 'Protection 1']} />
|
||||
<Row name="unbreaking" labels={['Unbreaking 3', 'Unbreaking 2', 'Unbreaking 1']} />
|
||||
<Row name="mending" labels={['Mending']} />
|
||||
<Row name="thorns" labels={['Thorns 3', 'Thorns 2', 'Thorns 1']} />
|
||||
<Row name="swift_sneak" labels={['Swift Sneak 2', 'Swift Sneak 1']} />
|
||||
<Row name="feather_falling" labels={['Feather Falling 4', 'Feather Falling 3','Feather Falling 2', 'Feather Falling 1']} />
|
||||
<Row name="soul_speed" labels={['Soul Speed 3', 'Soul Speed 2', 'Soul Speed 1']} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
21
LabWork_04-05/src/components/types/hooks/TypesHook.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import TypesApiService from '../service/TypesApiService';
|
||||
|
||||
const useTypes = () => {
|
||||
const [types, setTypes] = useState([]);
|
||||
|
||||
const getTypes = async () => {
|
||||
const data = await TypesApiService.getAll();
|
||||
setTypes(data ?? []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getTypes();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
types,
|
||||
};
|
||||
};
|
||||
|
||||
export default useTypes;
|
@ -0,0 +1,5 @@
|
||||
import ApiService from '../../api/ApiService';
|
||||
|
||||
const TypesApiService = new ApiService('types');
|
||||
|
||||
export default TypesApiService;
|
161
LabWork_04-05/src/index.css
Normal file
@ -0,0 +1,161 @@
|
||||
/* Обнуление стилей */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
padding-top: 16px;
|
||||
padding-left: 20px;
|
||||
color: #363434;
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol,
|
||||
li {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
table {
|
||||
display: block;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
line-height: 1;
|
||||
font-family: Montserrat;
|
||||
background-color: #363434;
|
||||
}
|
||||
|
||||
/* Полоса прокрутки */
|
||||
::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #2c2a2a;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Обёртка */
|
||||
.wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
._container {
|
||||
max-width: 1680px;
|
||||
margin: 0px auto;
|
||||
padding: 0px 15px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Основной контент */
|
||||
/* Обертка основного контента */
|
||||
.main__container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
padding: 150px 15px;
|
||||
}
|
||||
|
||||
/* Адаптив основного контента */
|
||||
@media (max-width: 1024px) {
|
||||
.main__container {
|
||||
padding: 50px 15px;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Баннер */
|
||||
.banner {
|
||||
width: 30%;
|
||||
min-width: 300px;
|
||||
min-height: 300px;
|
||||
padding-bottom: 30%;
|
||||
margin-bottom: 50px;
|
||||
position: relative;
|
||||
}
|
||||
.banner__square1, .banner__square2 {
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
border-radius: 12%;
|
||||
position: absolute;
|
||||
}
|
||||
.banner__square1 {
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
.banner__square2 {
|
||||
bottom: 0;
|
||||
border: 5px solid #fff;
|
||||
background-color: #a721fa;
|
||||
}
|
||||
.banner__img {
|
||||
padding: 12%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
82
LabWork_04-05/src/main.jsx
Normal file
@ -0,0 +1,82 @@
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
|
||||
import App from './App.jsx';
|
||||
import './index.css'
|
||||
import ErrorPage from "./pages/ErrorPage.jsx";
|
||||
import HomePage from './pages/HomePage.jsx';
|
||||
import ArmorPage from './pages/ArmorPage.jsx';
|
||||
import AccountPage from './pages/AccountPage.jsx';
|
||||
import ShopPage from './pages/ShopPage.jsx';
|
||||
import AboutUsPage from './pages/AbousUsPage.jsx';
|
||||
import ContactUsPage from './pages/ContactUsPage.jsx';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
index: true,
|
||||
path: '/',
|
||||
element: <HomePage />,
|
||||
title: 'Home',
|
||||
},
|
||||
{
|
||||
path: '/armor',
|
||||
element: <ArmorPage />,
|
||||
title: 'Armor',
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
element: <ErrorPage />,
|
||||
title: 'Tools',
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
element: <ErrorPage />,
|
||||
title: 'Sword',
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
element: <ErrorPage />,
|
||||
title: 'Bow',
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
element: <ErrorPage />,
|
||||
title: 'Trident',
|
||||
},
|
||||
{
|
||||
path: '/account',
|
||||
element: <AccountPage />,
|
||||
},
|
||||
{
|
||||
path: '/shop',
|
||||
element: <ShopPage />,
|
||||
},
|
||||
{
|
||||
path: '/about_us',
|
||||
element: <AboutUsPage />,
|
||||
},
|
||||
{
|
||||
path: '/contact_us',
|
||||
element: <ContactUsPage />,
|
||||
},
|
||||
{
|
||||
path: '/shop/:id?',
|
||||
element: <ShopPage />,
|
||||
},
|
||||
];
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <App routes={routes} />,
|
||||
children: routes,
|
||||
errorElement: <ErrorPage />,
|
||||
},
|
||||
]);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</React.StrictMode>,
|
||||
)
|
21
LabWork_04-05/src/pages/AbousUsPage.jsx
Normal file
@ -0,0 +1,21 @@
|
||||
import './css/AboutUsPage.css';
|
||||
|
||||
const AboutUsPage = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="main_about">
|
||||
<div className="main__container _container">
|
||||
<div className="main__about">
|
||||
<p className="main__about_info">
|
||||
Welcome to our online library! We are a team of enthusiasts who are committed to making reading accessible to all. We know how important enchantment books are for Minecraft, but due to the variety of types of enchantments and the vastness of their application, even experienced players cannot intelligently choose the enchantments they need.<br/>
|
||||
Our goal is to make the possibility of enchanting accessible and convenient for everyone, and our book selector can help you with that. Go to the desired section and select the books you need - that is all you need to do!<br/>
|
||||
Our app is the only one of its kind. All ideas are patented and copyrighted. Any borrowing or use of materials from our labor must be negotiated with our managers, otherwise a lawsuit will follow in your side.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutUsPage;
|
27
LabWork_04-05/src/pages/AccountPage.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import Input from '../components/input/Input.jsx';
|
||||
import { Form } from 'react-bootstrap';
|
||||
import Lines from '../components/lines/table/Lines.jsx';
|
||||
import './css/AccountPage.css';
|
||||
|
||||
const AccountPage = () => {
|
||||
return (
|
||||
<main className="main">
|
||||
<div className="main__container _container">
|
||||
<Form className="main__form form needs-validation" noValidate>
|
||||
<h2 className="form__title">Sign in</h2>
|
||||
<h3 className="form__subtitle">in to your account</h3>
|
||||
<div className="form__fields fields">
|
||||
<Input name="number" placeholder="Enter your phone number" className="fields__number" type="number" required/>
|
||||
<Input name="password" placeholder="Enter your password" className="fields__password" type="password" required/>
|
||||
<Input name="password_repeat" placeholder="Repeat your password" className="fields__password_repeat" type="password" required/>
|
||||
<Input name="submit" className="fields__submit" type="submit" value="Sign up" required/>
|
||||
<div className="form__warning">By clicking on the “Sign up” button, you agree to the<br/>processing of personal data</div>
|
||||
</div>
|
||||
</Form>
|
||||
<Lines />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountPage;
|
23
LabWork_04-05/src/pages/ArmorPage.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
import Armor from '../assets/armor.png';
|
||||
import Table from '../components/shopTable/Table';
|
||||
import './css/ArmorPage.css';
|
||||
|
||||
const ArmorPage = () => {
|
||||
return (
|
||||
<main className="main_armor">
|
||||
<div className="main__container _container">
|
||||
<div className="main__banner banner">
|
||||
<div className="banner__square1"></div>
|
||||
<div className="banner__square2">
|
||||
<div className="banner__img">
|
||||
<img src={Armor} alt="armor.png" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArmorPage;
|
75
LabWork_04-05/src/pages/ContactUsPage.jsx
Normal file
@ -0,0 +1,75 @@
|
||||
import Input from '../components/input/Input.jsx';
|
||||
import { Form } from 'react-bootstrap';
|
||||
import './css/ContactUsPage.css';
|
||||
|
||||
const ContactUsPage = () => {
|
||||
return (
|
||||
<main className="main_contact">
|
||||
<div className="main__container _container">
|
||||
<Form className="main__form form needs-validation" action="contact_us.html" method="get" noValidate>
|
||||
<h2 className="form__header">Contact us</h2>
|
||||
<div className="form__fields fields">
|
||||
<Input name="name" placeholder="Enter your name" className="fields__name" type="text" required/>
|
||||
<Input name="email" placeholder="Enter your valid email" className="fields__email" type="email" required/>
|
||||
<Input name="message" placeholder="Enter your message" className="fields__message" type="text" required/>
|
||||
<Input name="submit" className="fields__submit" type="submit" value="Send message" required/>
|
||||
</div>
|
||||
</Form>
|
||||
<div className="main__info info_table">
|
||||
<div className="info_table__row">
|
||||
<div className="info_table__column">
|
||||
<div className="info_table__banner banner">
|
||||
<div className="banner__square1"></div>
|
||||
<div className="banner__square2">
|
||||
<div className="banner__contacts">
|
||||
<h2 className="banner__header">Our main office</h2>
|
||||
<div className="banner__info">SoHo 94<br/> Broadway St New York,<br/> NY 1001</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="info_table__column">
|
||||
<div className="info_table__banner banner">
|
||||
<div className="banner__square1"></div>
|
||||
<div className="banner__square2">
|
||||
<div className="banner__contacts">
|
||||
<h2 className="banner__header">Phone number</h2>
|
||||
<div className="banner__info">234-9876-5400</div>
|
||||
<div className="banner__info">+7 (902) 589-29-14</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="info_table__row">
|
||||
<div className="info_table__column">
|
||||
<div className="info_table__banner banner">
|
||||
<div className="banner__square1"></div>
|
||||
<div className="banner__square2">
|
||||
<div className="banner__contacts">
|
||||
<h2 className="banner__header">FAX</h2>
|
||||
<div className="banner__info">1-234-567-8900</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="info_table__column">
|
||||
<div className="info_table__banner banner">
|
||||
<div className="banner__square1"></div>
|
||||
<div className="banner__square2">
|
||||
<div className="banner__contacts">
|
||||
<h2 className="banner__header">EMAIL</h2>
|
||||
<div className="banner__info">masenkin73@gmail.com</div>
|
||||
<div className="banner__info">masenkin73@xmail.ru</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactUsPage;
|
21
LabWork_04-05/src/pages/ErrorPage.jsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Alert, Button, Container } from 'react-bootstrap';
|
||||
import { useNavigate, useRouteError } from 'react-router-dom';
|
||||
import './css/ErrorPage.css';
|
||||
|
||||
const ErrorPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const error = useRouteError();
|
||||
|
||||
return (
|
||||
<Container fluid className='container'>
|
||||
<Alert className='alert' variant='danger'>
|
||||
{error?.message ?? 'Page not found 404'}
|
||||
</Alert>
|
||||
<iframe className='video' src="https://www.youtube.com/embed/dQw4w9WgXcQ" title="Never Gonna Give You Up" allow="accelerometer autoplay clipboard-write encrypted-media gyroscope picture-in-picture" allowfullscreen></iframe>
|
||||
<Button className='button' variant='primary'
|
||||
onClick={() => navigate(-1)}>Back</Button>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorPage;
|
31
LabWork_04-05/src/pages/HomePage.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import './css/HomePage.css';
|
||||
import Book from '../assets/book.png';
|
||||
import Banner from '../components/banner/Banner.jsx';
|
||||
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<main className="main_index">
|
||||
<div className="main__container _container">
|
||||
<div className="main__product">
|
||||
<div className="main__info info">
|
||||
<div className="info__title">The power of enchantments</div>
|
||||
<div className="info__text">Enchanting is a mechanic that augments armor, tools, weapons, and books with one or more of a variety of enchantments that improve an item is existing abilities or imbue them with additional abilities and uses. A special glint animation appears on items that are enchanted.</div>
|
||||
</div>
|
||||
<Link to="/shop" className="main__button">Start buying</Link>
|
||||
</div>
|
||||
<div className="main__banner banner">
|
||||
<div className="banner__square1"></div>
|
||||
<div className="banner__square2">
|
||||
<div className="banner__img">
|
||||
<img src={Book} alt="book.png" />
|
||||
<Banner />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
26
LabWork_04-05/src/pages/ShopPage.jsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import LinesForm from '../components/lines/form/LinesForm.jsx';
|
||||
import Bookshelf from '../assets/bookshelf.png';
|
||||
import './css/ShopPage.css';
|
||||
|
||||
const ShopPage = () => {
|
||||
const { id } = useParams();
|
||||
|
||||
return (
|
||||
<main className="main_shop">
|
||||
<div className="main__container _container">
|
||||
<div className="main__banner banner">
|
||||
<div className="banner__square1"></div>
|
||||
<div className="banner__square2">
|
||||
<div className="banner__img">
|
||||
<img src={Bookshelf} alt="bookshelf.png"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<LinesForm id={id} />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShopPage;
|
24
LabWork_04-05/src/pages/css/AboutUsPage.css
Normal file
@ -0,0 +1,24 @@
|
||||
/* About us page */
|
||||
/* Информация */
|
||||
.main__about_info {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
background-color: #2c2a2a;
|
||||
border: 5px solid #a721fa;
|
||||
border-radius: 70px;
|
||||
font-size: 32px;
|
||||
padding: 77px 44px;
|
||||
line-height: 1.3;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Адаптив About us page */
|
||||
@media (max-width: 768px) {
|
||||
.main__about_info {
|
||||
width: 100%;
|
||||
margin-bottom: 50px;
|
||||
padding: 30px 20px;
|
||||
border-radius: 30px;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
87
LabWork_04-05/src/pages/css/AccountPage.css
Normal file
@ -0,0 +1,87 @@
|
||||
/* Account page */
|
||||
/* Форма регистрации */
|
||||
.form__title {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
padding-top: 30px;
|
||||
}
|
||||
.form__subtitle {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.fields__number,
|
||||
.fields__password,
|
||||
.fields__password_repeat {
|
||||
padding: 15px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
.form__warning {
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* Корзина покупок */
|
||||
.cart {
|
||||
width: 50%;
|
||||
min-height: 600px;
|
||||
padding: 3% 3%;
|
||||
border: solid 5px #a721fa;
|
||||
border-radius: 70px;
|
||||
color: #fff;
|
||||
background-color: #2c2a2a;
|
||||
}
|
||||
.cart__products {
|
||||
max-height: 550px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
gap: 10px;
|
||||
}
|
||||
.cart__row_header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.cart__row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
border-radius: 30px;
|
||||
background-color: #363434;
|
||||
}
|
||||
.cart__column {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
/* Адаптив Account page */
|
||||
@media (max-width: 1240px) {
|
||||
.cart {
|
||||
width: 100%;
|
||||
margin-bottom: 50px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.cart__row {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cart__name {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.cart__row_header {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
15
LabWork_04-05/src/pages/css/ArmorPage.css
Normal file
@ -0,0 +1,15 @@
|
||||
/* Armor page */
|
||||
/* Баннер */
|
||||
.main_armor *> .banner__square1 {
|
||||
left: 0;
|
||||
}
|
||||
.main_armor *> .banner__square2 {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
box-shadow: -10px -10px 10px 10px rgba(0, 0, 0, .2);
|
||||
}
|
||||
.main_armor *> .banner__img {
|
||||
width: 65%;
|
||||
margin: 0 auto;
|
||||
transform: scaleX(-1);
|
||||
}
|
104
LabWork_04-05/src/pages/css/ContactUsPage.css
Normal file
@ -0,0 +1,104 @@
|
||||
/* Contact us page */
|
||||
/* Контактная форма */
|
||||
.form {
|
||||
width: 28%;
|
||||
max-width: 550px;
|
||||
margin-right: 50px;
|
||||
border: solid 5px #a721fa;
|
||||
border-radius: 70px;
|
||||
color: #fff;
|
||||
background-color: #2c2a2a;
|
||||
}
|
||||
.form__header {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
padding-top: 30px;
|
||||
}
|
||||
.fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 11% 15%;
|
||||
gap: 20px;
|
||||
}
|
||||
.fields__name,
|
||||
.fields__email {
|
||||
padding: 15px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
.fields__message {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 200px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
.fields__submit {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 30px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background-color: #a721fa;
|
||||
}
|
||||
.fields__submit:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.fields__submit:active {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Контактная информация */
|
||||
.info_table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 50px;
|
||||
}
|
||||
.info_table__row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 50px;
|
||||
}
|
||||
.banner__contacts {
|
||||
margin: 25% 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.main_contact *> .banner__square2 {
|
||||
box-shadow: 5px -5px 10px 19px rgba(0, 0, 0, .2);
|
||||
}
|
||||
.banner__header {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
.banner__info {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
white-space: normal;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Адаптив Contact us page */
|
||||
@media (max-width: 1180px) {
|
||||
.form {
|
||||
width: 100%;
|
||||
margin-right: 0px;
|
||||
margin-bottom: 50px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.info_table {
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.info_table__row {
|
||||
gap: 25px;
|
||||
}
|
||||
}
|
40
LabWork_04-05/src/pages/css/ErrorPage.css
Normal file
@ -0,0 +1,40 @@
|
||||
.container {
|
||||
width: 30%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-top: 7%;
|
||||
background-color: #2c2a2a;
|
||||
border: solid 3px #a721fa;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
margin: 30px;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
background-color: #363434;
|
||||
border: solid 3px #a721fa;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 30px;
|
||||
padding: 5px 20px;
|
||||
background-color: #a721fa;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
border-radius: 30px;
|
||||
}
|
||||
button:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
button:active {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
75
LabWork_04-05/src/pages/css/HomePage.css
Normal file
@ -0,0 +1,75 @@
|
||||
/* Home page (index) */
|
||||
/* Информация */
|
||||
.main__product {
|
||||
width: 45%;
|
||||
margin-right: 300px;
|
||||
}
|
||||
.info__title {
|
||||
color: #a721fa;
|
||||
font-size: 90px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.info__text {
|
||||
width: 85%;
|
||||
margin-top: 30px;
|
||||
color: #fff;
|
||||
font-size: 25px;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* Кнопка */
|
||||
.main__button {
|
||||
width: 263px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #a721fa;
|
||||
margin-top: 50px;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
border-radius: 30px;
|
||||
}
|
||||
.main__button:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.main__button:active {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Баннер */
|
||||
.main_index *> .banner__square2 {
|
||||
box-shadow: 10px -10px 10px 10px rgba(0, 0, 0, .2);
|
||||
}
|
||||
.main_index *> .banner__loop {
|
||||
position: absolute;
|
||||
right: 7%;
|
||||
top: 67%;
|
||||
max-width: 45%;
|
||||
}
|
||||
|
||||
/* Адаптив главной страницы */
|
||||
@media (max-width: 1240px) {
|
||||
.main__button {
|
||||
margin: 50px auto;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.main__product {
|
||||
width: 100%;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.info__title {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.info__text {
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
13
LabWork_04-05/src/pages/css/ShopPage.css
Normal file
@ -0,0 +1,13 @@
|
||||
.main_shop *> .banner__square1 {
|
||||
left: 0;
|
||||
}
|
||||
.main_shop *> .banner__square2 {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
box-shadow: -10px -10px 10px 10px rgba(0, 0, 0, .2);
|
||||
}
|
||||
.main_shop *> .banner__img {
|
||||
width: 100%;
|
||||
margin: 3% 0% 0% -3%;
|
||||
transform: scaleX(-1);
|
||||
}
|
12
LabWork_04-05/vite.config.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
sourcemap: true,
|
||||
chunkSizeWarningLimit: 1024,
|
||||
emptyOutDir: true,
|
||||
},
|
||||
});
|