лаба 5, в процессе

This commit is contained in:
a.puchkina 2024-01-11 17:04:57 +04:00
parent 77f57f9576
commit 8bf96a862c
16 changed files with 307 additions and 201 deletions

File diff suppressed because one or more lines are too long

View File

@ -15,6 +15,7 @@
"react-bootstrap": "^2.9.1", "react-bootstrap": "^2.9.1",
"react-bootstrap-icons": "^1.10.3", "react-bootstrap-icons": "^1.10.3",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hook-form": "^7.49.3",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-router-dom": "^6.18.0" "react-router-dom": "^6.18.0"
}, },
@ -2791,9 +2792,9 @@
"dev": true "dev": true
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.3", "version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -2880,7 +2881,7 @@
}, },
"node_modules/function.prototype.name": { "node_modules/function.prototype.name": {
"version": "1.1.6", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", "resolved": "https://registry.npmjs.org/function.prototype.name/-/page4.prototype.name-1.1.6.tgz",
"integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
@ -4750,6 +4751,22 @@
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/react-hook-form": {
"version": "7.49.3",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz",
"integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==",
"engines": {
"node": ">=18",
"pnpm": "8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/react-hot-toast": { "node_modules/react-hot-toast": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz",
@ -5669,9 +5686,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "4.5.0", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
"integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.18.10", "esbuild": "^0.18.10",

View File

@ -11,15 +11,16 @@
"prod": "npm-run-all lint 'vite build' --parallel rest 'vite preview'" "prod": "npm-run-all lint 'vite build' --parallel rest 'vite preview'"
}, },
"dependencies": { "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", "axios": "^1.6.1",
"bootstrap": "^5.3.2", "bootstrap": "^5.3.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-bootstrap": "^2.9.1", "react-bootstrap": "^2.9.1",
"react-bootstrap-icons": "^1.10.3", "react-bootstrap-icons": "^1.10.3",
"prop-types": "^15.8.1" "react-dom": "^18.2.0",
"react-hook-form": "^7.49.3",
"react-hot-toast": "^2.4.1",
"react-router-dom": "^6.18.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.15", "@types/react": "^18.2.15",

View File

@ -5,10 +5,9 @@ import PropTypes from 'prop-types';
import useCart from './cart/CartHook'; import useCart from './cart/CartHook';
import './Card.css'; import './Card.css';
import '../pages/Pages.css'; import '../pages/Pages.css';
import '../pages/Page3.css';
const CardOfRoom = ({ const CardOfRoom = ({
product, price, image, card, product, price, image, card, size,
}) => { }) => {
const { addToCart } = useCart(); const { addToCart } = useCart();
const handleAnchorClick = (event, action) => { const handleAnchorClick = (event, action) => {
@ -16,13 +15,14 @@ const CardOfRoom = ({
action(); action();
}; };
return ( return (
<Card className='card_catalog'> <Card className='card_catalog p-0'>
<Link to="/Page6" > <img src={image} className="card-img-top" alt="..." /></Link> <Link to="" > <img src={image} className="card-img-top" alt="..." /></Link>
<div className="card-body"> <div className="card-body">
<p className="card-title card_name">{product}</p> <p className="card-title card_name">{product}</p>
<div className="caption_basket_price"> <div className="caption_basket_price">
<p className="card-title card_price"> { `${parseInt(price, 10)}` }</p> <p className="card-title card_price"> { `${parseInt(price, 10)}` }</p>
</div> </div>
<p className="card_size"> Размер: {size} </p>
<div className="d-flex justify-content-center"> <div className="d-flex justify-content-center">
<Button type="submit" variant="outline-secondary" onClick={ (event) => handleAnchorClick(event, () => addToCart(card))}> Добавить в корзину</Button> <Button type="submit" variant="outline-secondary" onClick={ (event) => handleAnchorClick(event, () => addToCart(card))}> Добавить в корзину</Button>
</div> </div>

View File

@ -0,0 +1,171 @@
/* eslint-disable linebreak-style */
import { useRef, useState, useEffect } from 'react';
import axios from './api/axios';
const USER_REGEX = /^[A-z][A-z0-9-_]{3,23}$/;
const PWD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%]).{8,24}$/;
const REGISTER_URL = '/register';
const Register = () => {
const userRef = useRef();
const errRef = useRef();
const [user, setUser] = useState('');
const [validName, setValidName] = useState(false);
const [userFocus, setUserFocus] = useState(false);
const [pwd, setPwd] = useState('');
const [validPwd, setValidPwd] = useState(false);
const [pwdFocus, setPwdFocus] = useState(false);
const [matchPwd, setMatchPwd] = useState('');
const [validMatch, setValidMatch] = useState(false);
const [matchFocus, setMatchFocus] = useState(false);
const [errMsg, setErrMsg] = useState('');
const [success, setSuccess] = useState(false);
useEffect(() => {
userRef.current.focus();
}, []);
useEffect(() => {
setValidName(USER_REGEX.test(user));
}, [user]);
useEffect(() => {
setValidPwd(PWD_REGEX.test(pwd));
setValidMatch(pwd === matchPwd);
}, [pwd, matchPwd]);
useEffect(() => {
setErrMsg('');
}, [user, pwd, matchPwd]);
const handleSubmit = async (e) => {
e.preventDefault();
// if button enabled with JS hack
const v1 = USER_REGEX.test(user);
const v2 = PWD_REGEX.test(pwd);
if (!v1 || !v2) {
setErrMsg('Invalid Entry');
return;
}
try {
const response = await axios.post(
REGISTER_URL,
JSON.stringify({ user, pwd }),
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true,
},
);
console.log(response?.data);
console.log(response?.accessToken);
console.log(JSON.stringify(response));
setSuccess(true);
setUser('');
setPwd('');
setMatchPwd('');
} catch (err) {
if (!err?.response) {
setErrMsg('No Server Response');
} else if (err.response?.status === 409) {
setErrMsg('Username Taken');
} else {
setErrMsg('Registration Failed');
}
errRef.current.focus();
}
};
return (
<>
{success ? (
<section>
<h1>Success!</h1>
<p>
<a href="#">Sign In</a>
</p>
</section>
) : (
<section>
<p ref={errRef} className={errMsg ? 'errmsg' : 'offscreen'} aria-live="assertive">{errMsg}</p>
<h1>Register</h1>
<form onSubmit={handleSubmit}>
<label htmlFor="username">
Username:
</label>
<input
type="text"
id="username"
ref={userRef}
autoComplete="off"
onChange={(e) => setUser(e.target.value)}
value={user}
required
aria-invalid={validName ? 'false' : 'true'}
aria-describedby="uidnote"
onFocus={() => setUserFocus(true)}
onBlur={() => setUserFocus(false)}
/>
<p id="uidnote" className={userFocus && user && !validName ? 'instructions' : 'offscreen'}>
4 to 24 characters.<br />
Must begin with a letter.<br />
Letters, numbers, underscores, hyphens allowed.
</p>
<label htmlFor="password">
Password:
</label>
<input
type="password"
id="password"
onChange={(e) => setPwd(e.target.value)}
value={pwd}
required
aria-invalid={validPwd ? 'false' : 'true'}
aria-describedby="pwdnote"
onFocus={() => setPwdFocus(true)}
onBlur={() => setPwdFocus(false)}
/>
<p id="pwdnote" className={pwdFocus && !validPwd ? 'instructions' : 'offscreen'}>
Allowed special characters: <span aria-label="exclamation mark">!</span>
<span aria-label="at symbol">@</span> <span aria-label="hashtag">#
</span> <span aria-label="dollar sign">$</span> <span aria-label="percent">%</span>
</p>
<label htmlFor="confirm_pwd">
Confirm Password:
</label>
<input
type="password"
id="confirm_pwd"
onChange={(e) => setMatchPwd(e.target.value)}
value={matchPwd}
required
aria-invalid={validMatch ? 'false' : 'true'}
aria-describedby="confirmnote"
onFocus={() => setMatchFocus(true)}
onBlur={() => setMatchFocus(false)}
/>
<p id="confirmnote" className={matchFocus && !validMatch ? 'instructions' : 'offscreen'}>
Must match the first password input field.
</p>
<button disabled={!!(!validName || !validPwd
|| !validMatch)}>Sign Up</button>
</form>
<p>
Already registered?<br />
<span className="line">
<a href="#">Sign In</a>
</span>
</p>
</section>
)}
</>
);
};
export default Register;

View File

@ -0,0 +1,5 @@
import axios from 'axios';
export default axios.create({
baseURL: 'http://localhost:3500',
});

View File

@ -3,53 +3,10 @@
.cart{ .cart{
font-family: "Montserrat Alternates"; font-family: "Montserrat Alternates";
} }
.cart .container-products {
background-color: rgba(210, 202, 188, 0.64);
position: relative;
margin-bottom: 3%;
border-radius: 10px;
}
.container-products .title-product,
.container-products .price,
.container-products .articuL,
.container-products a {
margin-left: 30%;
position: absolute;
padding: 3%;
}
.container-products .title-product {
top: 0;
left: 0;
}
.container-products .price {
top: 0;
right: 0;
}
.container-products .title-product,
.container-products .price {
font-weight: 700;
}
.container-products .articul {
bottom: 0;
left: 0;
padding-bottom: 3%;
}
.container-products a {
width: 20%;
bottom: 0;
right: 0;
margin-bottom: 3%;
}
.cart .card{ .cart .card{
background-color: rgba(191, 180, 161, 1);
margin-bottom: 30px; margin-bottom: 30px;
} border-width: 1px;
.cart u { border-color: #533908;
padding-left: 4%;
text-align: left;
margin-left: 3%;
font-weight: 500;
padding-bottom: 10%;
} }
.cart .btn{ .cart .btn{
font-weight: 100; font-weight: 100;
@ -59,44 +16,6 @@
background-color: #533908; background-color: #533908;
border: #533908 3px solid; border: #533908 3px solid;
} }
.cart .card-text{
font-weight: 500;
padding: 5px;
font-size: 18px;
text-align: center;
color:#533908;
}
.cart .card-title{
font-weight: 600;
font-size: 23px;
color:#533908;
padding: 8px;
margin-bottom: 5%;
text-align: center;
}
.cart .price-text{
font-weight: 300;
padding: 5px;
font-size: 18px;
text-align: center;
color:#533908;
}
.cart .price-count{
font-weight: 500;
padding: 5px;
text-align: center;
color:#533908;
}
.cart .imageOfProduct{
padding: 10px;
width: 30%;
border-radius: 20px;
}
.cart-image { .cart-image {
width: 12em; width: 12em;
} }
.cart-item {
height: auto;
}

View File

@ -22,7 +22,7 @@ const LinesItemForm = ({ item, handleChange }) => {
type='number' min='1000.0' step='0.50' required /> type='number' min='1000.0' step='0.50' required />
<Input name='count' label='Количество' value={item.count} onChange={handleChange} <Input name='count' label='Количество' value={item.count} onChange={handleChange}
type='number' min='1' step='1' required /> type='number' min='1' step='1' required />
<Input name='color' label='Цвет' value={item.color} onChange={handleChange} required /> <Input name='color' label='Цвет' value={item.color} onChange={handleChange} type='color' required />
<Input name='size' label='Размер' value={item.size} onChange={handleChange} required /> <Input name='size' label='Размер' value={item.size} onChange={handleChange} required />
<Input name='image' label='Изображение' onChange={handleChange} <Input name='image' label='Изображение' onChange={handleChange}
type='file' accept='image/*' /> type='file' accept='image/*' />

View File

@ -1,4 +1,3 @@
/* eslint-disable no-undef */
import { useState } from 'react'; import { useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import getBase64FromFile from '../../utils/Base64'; import getBase64FromFile from '../../utils/Base64';

View File

@ -1,5 +1,4 @@
import { Button, ButtonGroup } from 'react-bootstrap'; import { Button } from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
import useCart from '../../cart/CartHook'; import useCart from '../../cart/CartHook';
import Select from '../../input/Select.jsx'; import Select from '../../input/Select.jsx';
import ModalConfirm from '../../modal/ModalConfirm.jsx'; import ModalConfirm from '../../modal/ModalConfirm.jsx';
@ -34,22 +33,14 @@ const Lines = () => {
handleFormClose, handleFormClose,
} = useLinesFormModal(handleLinesChange); } = useLinesFormModal(handleLinesChange);
const navigate = useNavigate();
const showEditPage = (id) => {
navigate(`/page-edit/${id}`);
};
const { addToCart } = useCart(); const { addToCart } = useCart();
return ( return (
<> <>
<ButtonGroup> <Button variant='secondary' className='mt-4 mx-4' onClick={() => showFormModal()}>
<Button variant='secondary' onClick={() => showFormModal()}> Добавить товар
Добавить товар (диалог)
</Button> </Button>
</ButtonGroup> <Select className='mt-3 mx-4' values={types} label='Фильтр по товарам'
<Select className='mt-2' values={types} label='Фильтр по товарам'
value={currentFilter} onChange={handleFilterChange} /> value={currentFilter} onChange={handleFilterChange} />
<LinesTable> <LinesTable>
{ {
@ -59,7 +50,6 @@ const Lines = () => {
onAddCart={() => addToCart(line)} onAddCart={() => addToCart(line)}
onDelete={() => showDeleteModal(line.id)} onDelete={() => showDeleteModal(line.id)}
onEdit={() => showFormModal(line.id)} onEdit={() => showFormModal(line.id)}
onEditInPage={() => showEditPage(line.id)}
/>) />)
} }
</LinesTable> </LinesTable>

View File

@ -2,19 +2,25 @@ import PropTypes from 'prop-types';
import { Row } from 'react-bootstrap'; import { Row } from 'react-bootstrap';
import CardOfRoom from '../../Card.jsx'; import CardOfRoom from '../../Card.jsx';
import useLines from '../hooks/LinesHook'; import useLines from '../hooks/LinesHook';
import Select from '../../input/Select.jsx';
import useTypeFilter from '../hooks/LinesFilterHook';
const LinesTableRow = () => { const LinesTableRow = () => {
const { lines } = useLines(); const { types, currentFilter, handleFilterChange } = useTypeFilter();
const { lines } = useLines(currentFilter);
function allCards() { function allCards() {
return lines.map((card) => return lines.map((card) =>
<CardOfRoom key={card.id} <CardOfRoom key={card.id}
product={card.product} product={card.product}
size = {card.size}
price={card.price} image={card.image} card={card} />); price={card.price} image={card.image} card={card} />);
} }
return ( return (
<> <>
<Select className='col-2 mt-0 mb-4 mx-4 ms-auto' values={types} label='Фильтр по товарам'
value={currentFilter} onChange={handleFilterChange} />
<Row xs={1} md={2} className="g-4 justify-content-center"> <Row xs={1} md={2} className="g-4 justify-content-center">
{allCards()} {allCards()}
</Row> </Row>

View File

@ -8,9 +8,8 @@ import CartPage from './pages/CartPage.jsx';
import ErrorPage from './pages/ErrorPage.jsx'; import ErrorPage from './pages/ErrorPage.jsx';
import Page1 from './pages/Page1.jsx'; import Page1 from './pages/Page1.jsx';
import Page2 from './pages/Page2.jsx'; import Page2 from './pages/Page2.jsx';
import Page3 from './pages/Page3.jsx';
import Page4 from './pages/Page4.jsx'; import Page4 from './pages/Page4.jsx';
import Page5 from './pages/Page5.jsx';
import Page6 from './pages/Page6.jsx';
import PageEdit from './pages/PageEdit.jsx'; import PageEdit from './pages/PageEdit.jsx';
const routes = [ const routes = [
@ -24,18 +23,14 @@ const routes = [
element: <Page2 />, // Страница каталога element: <Page2 />, // Страница каталога
}, },
{ {
path: '/page4', path: '/page3',
element: <Page4 />, element: <Page3 />,
title: 'Аккаунт', title: 'Аккаунт',
}, },
{ {
path: '/page5', path: '/page4',
element: <Page5 />, element: <Page4 />,
title: 'Табличка', // Таблица с товарами title: 'Таблица товаров', // Таблица с товарами
},
{
path: '/page6',
element: <Page6 />, // Страница товара
}, },
{ {
path: '/page-edit/:id?', path: '/page-edit/:id?',

59
laba5/src/pages/Page3.jsx Normal file
View File

@ -0,0 +1,59 @@
import { Link } from 'react-router-dom';
import {
Container, Col, Row, Form, Button,
} from 'react-bootstrap';
// import Register from '../components/Register';
import './Page3.css';
const Page3 = () => {
return (
// <Register />
<>
<div className="accountPage">
<Container>
<Row>
<Col className="mx-auto">
<Form.Text className='text-center'>
<h1>АВТОРИЗАЦИЯ</h1>
</Form.Text>
<Form method="post" >
<Form.Group>
<Form.Label htmlFor="login">Логин</Form.Label>
<Form.Control type="text" className="form-control"id="exampleInputLogin" />
</Form.Group>
<Form.Group>
<Form.Label htmlFor="password">Пароль</Form.Label>
<Form.Control type="password" className="form-control"
id="exampleInputPassword" />
</Form.Group>
<Button className='my-3' type="submit" variant="secondary"
as={Link} to="/page4">Войти</Button>
</Form>
</Col>
<Col className="mx-auto">
<Form.Text className='text-center'>
<h1>РЕГИСТРАЦИЯ</h1>
</Form.Text>
<Form method="post" >
<Form.Group>
<Form.Label htmlFor="login">Логин</Form.Label>
<Form.Control type="text" className="form-control"
id="exampleInputLogin" />
</Form.Group>
<Form.Group>
<Form.Label htmlFor="password">Пароль</Form.Label>
<Form.Control type="password" className="form-control"
id="exampleInputPassword" />
</Form.Group>
<Button className='my-3' type="submit" variant="outline-secondary">
Зарегистрироваться</Button>
</Form>
</Col>
</Row>
</Container>
</div>
</>
);
};
export default Page3;

View File

@ -1,55 +1,8 @@
import { Link } from 'react-router-dom'; import Lines from '../components/lines/table/Lines.jsx';
import {
Container, Col, Row, Form, Button,
} from 'react-bootstrap';
import './Page4.css';
const Page4 = () => { const Page4 = () => {
return ( return (
<> <Lines />
<div className="accountPage">
<Container>
<Row>
<Col className="mx-auto">
<Form.Text className='text-center'>
<h1>АВТОРИЗАЦИЯ</h1>
</Form.Text>
<Form method="post">
<Form.Group>
<Form.Label htmlFor="email">E-mail</Form.Label>
<Form.Control type="email" name="email" required />
</Form.Group>
<Form.Group>
<Form.Label htmlFor="password">Пароль</Form.Label>
<Form.Control type="password" name="password" required />
</Form.Group>
<Button className='my-3' type="submit" variant="secondary" as={Link} to="/page5">Войти</Button>
</Form>
</Col>
<Col className="mx-auto">
<Form.Text className='text-center'>
<h1>РЕГИСТРАЦИЯ</h1>
</Form.Text>
<Form method="post">
<Form.Group>
<Form.Label htmlFor="email">E-mail</Form.Label>
<Form.Control type="email" name="email" required />
</Form.Group>
<Form.Group>
<Form.Label htmlFor="password">Пароль</Form.Label>
<Form.Control type="password" name="password" required />
</Form.Group>
<Form.Group>
<Form.Label htmlFor="password">Повторите пароль</Form.Label>
<Form.Control type="password" name="password" required />
</Form.Group>
<Button className='my-3' type="submit" variant="outline-secondary">Зарегистрироваться</Button>
</Form>
</Col>
</Row>
</Container>
</div>
</>
); );
}; };

View File

@ -1,9 +0,0 @@
import Lines from '../components/lines/table/Lines.jsx';
const Page5 = () => {
return (
<Lines />
);
};
export default Page5;