LabWork04-05 Additions

This commit is contained in:
parent ec5535386d
commit 0031acb601
57 changed files with 1184 additions and 60 deletions

Binary file not shown.

View File

@ -22,20 +22,6 @@
} }
], ],
"lines": [ "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", "typeId": "5",
"price": "10.00", "price": "10.00",
@ -112,13 +98,112 @@
"amount": "1", "amount": "1",
"total": "10.00", "total": "10.00",
"id": 35 "id": 35
}
],
"messages": [
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "Я устал",
"date": "2024-01-05",
"flag": "true",
"id": 1
}, },
{ {
"typeId": "2", "name": "Максим",
"price": "30.00", "email": "masenkin73@gmail.com",
"amount": "3", "text": "Я мухожук",
"total": "90.00", "date": "2024-01-05",
"id": 36 "flag": "false",
"id": 2
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "С Новым годом",
"date": "2024-01-06",
"flag": "true",
"id": 3
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "С наступающим Рождеством",
"date": "2024-01-06",
"flag": "false",
"id": 4
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "С 23 февраля",
"date": "2024-01-06",
"flag": "true",
"id": 5
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "С 8 марта",
"date": "2024-01-06",
"flag": "false",
"id": 6
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "С Пасхой",
"date": "2024-01-06",
"flag": "true",
"id": 7
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "Христос Воскрес",
"date": "2024-01-06",
"flag": "false",
"id": 8
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "Воистину Воскрес",
"date": "2024-01-06",
"flag": "true",
"id": 9
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "С Днём рождения",
"date": "2024-01-06",
"flag": "false",
"id": 10
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "Счастья здоровья",
"date": "2024-01-06",
"flag": "true",
"id": 11
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "Здоровья погибшим",
"date": "2024-01-06",
"flag": "false",
"id": 12
},
{
"name": "Максим",
"email": "masenkin73@gmail.com",
"text": "В ходе выполнения лабораторной работы пострадал один человек",
"date": "2024-01-06",
"flag": "true",
"id": 13
} }
] ]
} }

View File

@ -7,13 +7,13 @@ import Footer from './components/footer/Footer.jsx';
const App = ({ routes }) => { const App = ({ routes }) => {
return ( return (
<> <div className='wrapper'>
<Header routes={routes} /> <Header routes={routes} />
<Container className='wrapper' as='main' fluid> <Container as='main' fluid>
<Outlet /> <Outlet />
</Container> </Container>
<Footer /> <Footer />
</> </div>
); );
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 B

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,7 +1,7 @@
/* Футер */ /* Футер */
/* Обертка футера */ /* Обертка футера */
.footer { .footer {
position: absolute; position: fixed;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
padding: 30px 0px; padding: 30px 0px;

View File

@ -13,6 +13,9 @@ const Footer = () => {
<li className="nav__item"> <li className="nav__item">
<Link to="/contact_us" className="nav__link">Contact us</Link> <Link to="/contact_us" className="nav__link">Contact us</Link>
</li> </li>
<li className="nav__item">
<Link to="/admin_messages" className="nav__link">Admin messages</Link>
</li>
</ul> </ul>
</nav> </nav>
<div className="footer__privacy"> <div className="footer__privacy">

View File

@ -37,7 +37,7 @@
.fields__price, .fields__price,
.fields__amount, .fields__amount,
.fields__name, .fields__name,
.fields__email .fields__email,
.fields__number, .fields__number,
.fields__password, .fields__password,
.fields__password_repeat { .fields__password_repeat {

View File

@ -0,0 +1,44 @@
.message__card {
width: 40%;
display: flex;
padding: 10px;
margin-bottom: 30px;
margin-right: 30px;
border: solid 3px #a721fa;
border-radius: 10px;
color: #fff;
background-color: #2c2a2a;
}
.message__avatar {
width: 100px;
height: 100px;
border-radius: 10px;
margin-right: 15px;
}
.message__content {
flex: 1;
position: relative;
}
.message__username {
font-size: 25px;
font-weight: 700;
margin-bottom: 5px;
color: #a721fa;
}
.message__text {
font-size: 16px;
}
.message__date {
position: absolute;
font-size: 10px;
font-weight: 600;
color: #666;
bottom: 0;
right: 0;
}
@media (max-width: 768px) {
.message__card {
flex-direction: column;
}
}

View File

@ -0,0 +1,26 @@
import PropTypes from 'prop-types';
import './MessageCard.css';
const MessageCard = ({
avatar, username, text, date,
}) => {
return (
<div className='message__card'>
<img className='message__avatar' src={avatar} alt="avatar.png" />
<div className='message__content'>
<div className='message__username'>{username}</div>
<div className='message__text'>{text}</div>
<div className='message__date'>{date}</div>
</div>
</div>
);
};
MessageCard.propTypes = {
avatar: PropTypes.object,
username: PropTypes.string,
text: PropTypes.string,
date: PropTypes.string,
};
export default MessageCard;

View File

@ -0,0 +1,11 @@
.messages__column {
display: flex;
justify-content: center;
width: 1060px;
flex-wrap: wrap;
}
@media (max-width: 768px) {
.messages__column {
width: 100%;
}
}

View File

@ -0,0 +1,35 @@
import MessageCard from './MessageCard';
import useMessages from '../hooks/MessagesHook';
import Steve from '../../../assets/steve.png';
import Creeper from '../../../assets/creeper.png';
import Zombie from '../../../assets/zombie.png';
import Skeleton from '../../../assets/skeleton.png';
import Slime from '../../../assets/slime.png';
import './MessageTable.css';
const MessagesTable = () => {
const { messages } = useMessages('true');
const avatars = [Steve, Creeper, Zombie, Skeleton, Slime];
const getRandomIndex = () => {
const index = Math.floor(Math.random() * avatars.length);
return index;
}
return (
<div className="messages__table">
<div className="messages__column">
{messages.map((message, index) => (
<MessageCard key={index}
avatar={avatars[getRandomIndex()]}
username={message.name}
text={message.text}
date={message.date} />
))}
</div>
</div>
);
};
export default MessagesTable;

View File

@ -0,0 +1,71 @@
.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__input_checkbox {
display: flex;
justify-content: center;
padding-top: 15px;
}
.fields__checkbox {
width: 15px;
height: 15px;
border-radius: 3px;
}
.fields__label {
padding-left: 10px;
font-weight: 600;
}
.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);
}
@media (max-width: 1180px) {
.form {
width: 100%;
margin-right: 0px;
margin-bottom: 50px;
border-radius: 30px;
}
}

View File

@ -0,0 +1,30 @@
import PropTypes from 'prop-types';
import { useState } from 'react';
import Input from '../../input/Input.jsx';
import './Form.css';
const MessagesItemEditForm = ({ message, handleChange }) => {
const [isChecked, setIsChecked] = useState(message.flag);
const handleCheckboxChange = (event) => {
setIsChecked(event.target.checked);
handleChange(event);
};
return (
<>
<Input name='text' placeholder='Enter yor message' value={message.text} onChange={handleChange} className='fields__message' type='text' required />
<div className='fields__input_checkbox'>
<input name='flag' value={isChecked.toString()} onChange={handleCheckboxChange} className='fields__checkbox' type='checkbox' checked={isChecked || message.flag == 'true'}/>
<label className='fields__label' htmlFor="flag">Publish</label>
</div>
</>
);
};
MessagesItemEditForm.propTypes = {
message: PropTypes.object,
handleChange: PropTypes.func,
};
export default MessagesItemEditForm;

View File

@ -0,0 +1,45 @@
import PropTypes from 'prop-types';
import { Button, Form } from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
import useMessagesItemForm from '../hooks/MessagesItemFormHook';
import MessagesItemForm from './MessagesItemForm.jsx';
import './Form.css';
const MessagesForm = ({ id }) => {
const navigate = useNavigate();
const {
message,
validated,
handleSubmit,
handleChange,
} = useMessagesItemForm(id);
const onSubmit = async (event) => {
if (await handleSubmit(event)) {
navigate('/messages');
}
};
return (
<>
<Form className='main__form form' noValidate validated={validated} onSubmit={onSubmit}>
<h2 className="form__header">Contact us</h2>
<div className='form__fields fields'>
<MessagesItemForm message={message} handleChange={handleChange} />
<Form.Group className='row justify-content-center m-0 mt-3'>
<Button className='fields__submit' type='submit' variant='primary'>
Send message
</Button>
</Form.Group>
</div>
</Form>
</>
);
};
MessagesForm.propTypes = {
id: PropTypes.string,
};
export default MessagesForm;

View File

@ -0,0 +1,21 @@
import PropTypes from 'prop-types';
import Input from '../../input/Input.jsx';
import './Form.css';
const MessagesItemForm = ({ message, handleChange }) => {
return (
<>
<Input name ='name' placeholder='Enter your name' value={message.name} onChange={handleChange} className='fields__name'type='text' required />
<Input name='email' placeholder='Enter your valid email' value={message.email} onChange={handleChange} className='fields__email' type='email' required />
<Input name='text' placeholder='Enter yor message' value={message.text} onChange={handleChange} className='fields__message' type='text' required />
</>
);
};
MessagesItemForm.propTypes = {
message: PropTypes.object,
handleChange: PropTypes.func,
};
export default MessagesItemForm;

View File

@ -0,0 +1,34 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import useModal from '../../messages/modal/ModalHook';
import MessagesApiService from '../service/MessagesApiService';
const useMessagesDeleteModal = (messagesChangeHandle) => {
const { isModalShow, showModal, hideModal } = useModal();
const [currentId, setCurrentId] = useState(0);
const showModalDialog = (id) => {
showModal();
setCurrentId(id);
};
const onClose = () => {
hideModal();
};
const onDelete = async () => {
await MessagesApiService.delete(currentId);
messagesChangeHandle();
toast.success('Сообщение успешно удалено', { id: 'MessagesTable' });
onClose();
};
return {
isDeleteModalShow: isModalShow,
showDeleteModal: showModalDialog,
handleDeleteConfirm: onDelete,
handleDeleteCancel: onClose,
};
};
export default useMessagesDeleteModal;

View File

@ -0,0 +1,45 @@
import { useState } from 'react';
import useModal from '../../messages/modal/ModalHook';
import useMessagesItemForm from './MessagesItemFormHook';
const useMessagesFormModal = (messagesChangeHandle) => {
const { isModalShow, showModal, hideModal } = useModal();
const [currentId, setCurrentId] = useState(0);
const {
message,
validated,
handleSubmit,
handleChange,
resetValidity,
} = useMessagesItemForm(currentId, messagesChangeHandle);
const showModalDialog = (id) => {
setCurrentId(id);
resetValidity();
showModal();
};
const onClose = () => {
setCurrentId(-1);
hideModal();
};
const onSubmit = async (event) => {
if (await handleSubmit(event)) {
onClose();
}
};
return {
isFormModalShow: isModalShow,
isFormValidated: validated,
showFormModal: showModalDialog,
currentMessage: message,
handleMessageChange: handleChange,
handleFormSubmit: onSubmit,
handleFormClose: onClose,
};
};
export default useMessagesFormModal;

View File

@ -0,0 +1,29 @@
import { useEffect, useState } from 'react';
import MessagesApiService from '../service/MessagesApiService';
const useMessages = (typeFilter) => {
const [messagesRefresh, setMessagesRefresh] = useState(false);
const [messages, setMessages] = useState([]);
const handleMessagesChange = () => setMessagesRefresh(!messagesRefresh);
const getMessages = async () => {
let expand = '';
if (typeFilter) {
expand = `${expand}?flag=${typeFilter}`
}
const data = await MessagesApiService.getAll(expand);
setMessages(data ?? []);
};
useEffect(() => {
getMessages();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [messagesRefresh, typeFilter]);
return {
messages,
handleMessagesChange,
};
};
export default useMessages;

View File

@ -0,0 +1,67 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import MessagesApiService from '../service/MessagesApiService';
import useMessagesItem from './MessagesitemHook';
const useMessagesItemForm = (id, messagesChangeHandle) => {
const { message, setMessage } = useMessagesItem(id);
const [validated, setValidated] = useState(false);
const resetValidity = () => {
setValidated(false);
};
const getMessageObject = (formData) => {
const name = formData.name;
const email = formData.email;
const text = formData.text;
const date = formData.date;
const flag = Boolean(formData.flag);
return {
name: name.toString(),
email: email.toString(),
text: text.toString(),
date: date.toString(),
flag: flag.toString(),
};
};
const handleChange = (event) => {
const inputName = event.target.name;
const inputValue = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
setMessage({
...message,
[inputName]: inputValue,
});
};
const handleSubmit = async (event) => {
const form = event.currentTarget;
event.preventDefault();
event.stopPropagation();
const body = getMessageObject(message);
if (form.checkValidity()) {
if (id === undefined) {
await MessagesApiService.create(body);
} else {
await MessagesApiService.update(id, body);
}
if (messagesChangeHandle) messagesChangeHandle();
toast.success('Комментарий успешно добавлен', { id: 'MessagesTable' });
return true;
}
setValidated(true);
return false;
};
return {
message,
validated,
handleSubmit,
handleChange,
resetValidity,
};
};
export default useMessagesItemForm;

View File

@ -0,0 +1,37 @@
import { useEffect, useState } from 'react';
import MessagesApiService from '../service/MessagesApiService';
const useMessagesItem = (id) => {
const currentDate = new Date().toISOString().split('T')[0];
const emptyMessage = {
id: '',
name: '',
email: '',
text: '',
date: currentDate,
flag: false,
};
const [message, setMessage] = useState({ ...emptyMessage });
const getMessage = async (messageId = undefined) => {
if (messageId && messageId > 0) {
const data = await MessagesApiService.get(messageId);
setMessage(data);
} else {
setMessage({ ...emptyMessage });
}
};
useEffect(() => {
getMessage(id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);
return {
message,
setMessage,
};
};
export default useMessagesItem;

View File

@ -0,0 +1,41 @@
import PropTypes from 'prop-types';
import { Button, Modal } from 'react-bootstrap';
import { createPortal } from 'react-dom';
const ModalConfirm = ({
show, title, message, onConfirm, onClose,
}) => {
return createPortal(
<Modal show={show} backdrop='static' onHide={() => onClose()}>
<Modal.Header className='pt-2 pb-2 ps-3 pe-3' closeButton>
<Modal.Title>{title}</Modal.Title>
</Modal.Header>
<Modal.Body>
{message}
</Modal.Body>
<Modal.Footer className='m-0 pt-2 pb-2 ps-3 pe-3 row justify-content-center'>
<Button variant='secondary' className='col-5 m-0 me-2'
onClick={() => onClose()}>
No
</Button>
<Button variant='primary' className='col-5 m-0 ms-2'
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;

View File

@ -0,0 +1,42 @@
import PropTypes from 'prop-types';
import { Button, Form, Modal } from 'react-bootstrap';
import { createPortal } from 'react-dom';
const ModalForm = ({
show, title, validated, onSubmit, onClose, children,
}) => {
return createPortal(
<Modal show={show} backdrop='static' onHide={() => onClose()}>
<Modal.Header className='pt-2 pb-2 ps-3 pe-3' closeButton>
<Modal.Title>{title}</Modal.Title>
</Modal.Header>
<Form className='m-0' noValidate validated={validated} onSubmit={onSubmit}>
<Modal.Body>
{children}
</Modal.Body>
<Modal.Footer className='m-0 pt-2 pb-2 ps-3 pe-3 row justify-content-center'>
<Button variant='secondary' className='col-5 m-0 me-2'
onClick={() => onClose()}>
Cancel
</Button>
<Button variant='primary' className='col-5 m-0 ms-2' type='submit'>
Save
</Button>
</Modal.Footer>
</Form>
</Modal>,
document.body,
);
};
ModalForm.propTypes = {
show: PropTypes.bool,
title: PropTypes.string,
validated: PropTypes.bool,
onSubmit: PropTypes.func,
onClose: PropTypes.func,
children: PropTypes.node,
};
export default ModalForm;

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

View File

@ -0,0 +1,5 @@
import ApiService from '../../api/ApiService';
const MessagesApiService = new ApiService('messages');
export default MessagesApiService;

View File

@ -0,0 +1,62 @@
.admin_messages_table {
width: 100%;
min-height: 600px;
padding: 3% 3%;
border: solid 5px #a721fa;
border-radius: 70px;
color: #fff;
background-color: #2c2a2a;
}
.table__messages {
max-height: 550px;
display: flex;
flex-direction: column;
overflow-y: auto;
gap: 10px;
}
.table__row_header {
width: 100%;
display: flex;
justify-content: center;
flex-wrap: wrap;
font-size: 24px;
font-weight: 700;
}
.table_messages__row {
display: flex;
justify-content: center;
flex-wrap: wrap;
font-size: 16px;
font-weight: 600;
border-radius: 30px;
background-color: #363434;
}
.table__column_index,
.table__column_name,
.table__column_email,
.table__column_text,
.table__column_date,
.table__column_flag,
.table__column_actions {
display: flex;
justify-content: center;
align-items: center;
min-height: 50px;
width: calc(100% / 7);
word-wrap: break-word;
text-align: center;
}
@media (max-width: 1240px) {
.table {
margin-bottom: 50px;
border-radius: 30px;
}
.table__row_header {
font-size: 16px;
}
.table__row {
font-size: 12px;
}
}

View File

@ -0,0 +1,58 @@
import ModalConfirm from '../../messages/modal/ModalConfirm.jsx';
import ModalForm from '../../messages/modal/ModalForm.jsx';
import useMessagesDeleteModal from '../hooks/MessagesDeleteModalHook';
import useMessagesFormModal from '../hooks/MessagesFormModalHook';
import useMessages from '../hooks/MessagesHook';
import MessagesTable from './MessagesTable.jsx';
import MessagesTableRow from './MessagesTableRow.jsx';
import MessagesItemEditForm from '../form/MessageItemEditForm.jsx';
import './Messages.css';
const Messages = () => {
const { messages, handleMessagesChange } = useMessages();
const {
isDeleteModalShow,
showDeleteModal,
handleDeleteConfirm,
handleDeleteCancel,
} = useMessagesDeleteModal(handleMessagesChange);
const {
isFormModalShow,
isFormValidated,
showFormModal,
currentMessage,
handleMessageChange,
handleFormSubmit,
handleFormClose,
} = useMessagesFormModal(handleMessagesChange);
return (
<>
<MessagesTable>
{
messages.map((message, index) =>
<MessagesTableRow key={message.id}
index={index}
message={message}
onDelete={() => showDeleteModal(message.id)}
onEdit={() => showFormModal(message.id)}
/>)
}
</MessagesTable>
<ModalConfirm show={isDeleteModalShow}
onConfirm={handleDeleteConfirm} onClose={handleDeleteCancel}
title='Delete' message='Delete message?' />
<ModalForm show={isFormModalShow} validated={isFormValidated}
onSubmit={handleFormSubmit} onClose={handleFormClose}
title='Edit message'>
<MessagesItemEditForm message={currentMessage} handleChange={handleMessageChange} />
</ModalForm>
</>
);
};
export default Messages;

View File

@ -0,0 +1,30 @@
import PropTypes from 'prop-types';
const MessagesTable = ({ children }) => {
return (
<div className='admin_messages_table'>
<table>
<thead className='table__row_header'>
<tr className='table__row_header'>
<th className='table__column_index' scope='col'></th>
<th className='table__column_name' scope='col'>Name</th>
<th className='table__column_email' scope='col'>Email</th>
<th className='table__column_text' scope='col'>Text</th>
<th className='table__column_date' scope='col'>Date</th>
<th className='table__column_flag' scope='col'>Flag</th>
<th className='table__column_actions' scope='col'>Actions</th>
</tr>
</thead>
<tbody className='table__messages'>
{children}
</tbody>
</table>
</div>
);
};
MessagesTable.propTypes = {
children: PropTypes.node,
};
export default MessagesTable;

View File

@ -0,0 +1,35 @@
import PropTypes from 'prop-types';
import { PencilSquare, Trash3 } from 'react-bootstrap-icons';
const MessagesTableRow = ({
index, message, onDelete, onEdit,
}) => {
const handleAnchorClick = (event, action) => {
event.preventDefault();
action();
};
return (
<tr className='table_messages__row'>
<th className='table__column_index' scope="row">{index + 1}</th>
<td className='table__column_name'>{message.name}</td>
<td className='table__column_email'>{message.email}</td>
<td className='table__column_text'>{message.text}</td>
<td className='table__column_date'>{message.date}</td>
<td className='table__column_flag'>{message.flag}</td>
<td className='table__column_actions'>
<a href="#" onClick={(event) => handleAnchorClick(event, onEdit)}><PencilSquare /></a>
<a href="#" onClick={(event) => handleAnchorClick(event, onDelete)}><Trash3 /></a>
</td>
</tr>
);
};
MessagesTableRow.propTypes = {
index: PropTypes.number,
message: PropTypes.object,
onDelete: PropTypes.func,
onEdit: PropTypes.func,
};
export default MessagesTableRow;

View File

@ -6,7 +6,7 @@
} }
.header *> .nav__item { .header *> .nav__item {
display: inline-block;; display: inline-block;;
width: calc(1/6 * 100%); width: calc(1/7 * 100%);
} }
.nav__item:not(:last-child) { .nav__item:not(:last-child) {
margin-right: 20px; margin-right: 20px;

View File

@ -10,8 +10,8 @@ const Navigation = ({ routes }) => {
return ( return (
<Navbar className='header__nav nav'> <Navbar className='header__nav nav'>
<Container fluid> <Container fluid>
<Navbar.Collapse id='main-navbar'> <Navbar.Collapse id='main-navbar' expand='md'>
<Nav activeKey={location.pathname}> <Nav activeKey={location.pathname} className='nav__list'>
{pages.map((page) => ( {pages.map((page) => (
<Nav.Item className='nav__item' key={page.path}> <Nav.Item className='nav__item' key={page.path}>
<Nav.Link as={Link} className="nav__link" to={page.path ?? '/'}> <Nav.Link as={Link} className="nav__link" to={page.path ?? '/'}>

View File

@ -1,18 +1,24 @@
import PropTypes from 'prop-types';
import Row from '../shopTable/Row.jsx'; import Row from '../shopTable/Row.jsx';
import './Table.css'; import './Table.css';
const Table = () => { const Table = ({ data }) => {
return ( return (
<div className='main__table table'> <div className='main__table table'>
<Row name="protection" labels={['Protection 4', 'Protection 3', 'Protection 2', 'Protection 1']} /> {data.map((rowData, index) => (
<Row name="unbreaking" labels={['Unbreaking 3', 'Unbreaking 2', 'Unbreaking 1']} /> <Row key={index} name={rowData.name} labels={rowData.labels} />
<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> </div>
); );
}; };
Table.propTypes = {
data: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
labels: PropTypes.arrayOf(PropTypes.string).isRequired,
})
).isRequired,
};
export default Table; export default Table;

View File

@ -101,7 +101,6 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100%; min-height: 100%;
overflow: hidden;
} }
._container { ._container {
max-width: 1680px; max-width: 1680px;

View File

@ -7,10 +7,16 @@ import './index.css'
import ErrorPage from "./pages/ErrorPage.jsx"; import ErrorPage from "./pages/ErrorPage.jsx";
import HomePage from './pages/HomePage.jsx'; import HomePage from './pages/HomePage.jsx';
import ArmorPage from './pages/ArmorPage.jsx'; import ArmorPage from './pages/ArmorPage.jsx';
import ToolsPage from './pages/ToolsPage.jsx';
import SwordPage from './pages/SwordPage.jsx';
import BowPage from './pages/BowPage.jsx';
import TridentPage from './pages/TridentPage.jsx';
import AccountPage from './pages/AccountPage.jsx'; import AccountPage from './pages/AccountPage.jsx';
import ShopPage from './pages/ShopPage.jsx'; import ShopPage from './pages/ShopPage.jsx';
import AboutUsPage from './pages/AbousUsPage.jsx'; import AboutUsPage from './pages/AbousUsPage.jsx';
import ContactUsPage from './pages/ContactUsPage.jsx'; import ContactUsPage from './pages/ContactUsPage.jsx';
import AdminMessagesPage from './pages/AdminMessagesPage.jsx';
import MessagesPage from './pages/MessagesPage.jsx';
const routes = [ const routes = [
{ {
@ -25,23 +31,23 @@ const routes = [
title: 'Armor', title: 'Armor',
}, },
{ {
path: '/error', path: '/tools',
element: <ErrorPage />, element: <ToolsPage />,
title: 'Tools', title: 'Tools',
}, },
{ {
path: '/error', path: '/sword',
element: <ErrorPage />, element: <SwordPage />,
title: 'Sword', title: 'Sword',
}, },
{ {
path: '/error', path: '/bow',
element: <ErrorPage />, element: <BowPage />,
title: 'Bow', title: 'Bow',
}, },
{ {
path: '/error', path: '/trident',
element: <ErrorPage />, element: <TridentPage />,
title: 'Trident', title: 'Trident',
}, },
{ {
@ -64,6 +70,15 @@ const routes = [
path: '/shop/:id?', path: '/shop/:id?',
element: <ShopPage />, element: <ShopPage />,
}, },
{
path: '/admin_messages',
element: <AdminMessagesPage />,
},
{
path: '/messages',
element: <MessagesPage />,
title: 'Messages',
},
]; ];
const router = createBrowserRouter([ const router = createBrowserRouter([

View File

@ -0,0 +1,14 @@
import Messages from "../components/messages/table/Messages";
import './css/AdminMessagesPage.css';
const AdminMessagesPage = () => {
return (
<main className="main__admin_messages">
<div className="main__container _container">
<Messages />
</div>
</main>
);
};
export default AdminMessagesPage;

View File

@ -3,6 +3,16 @@ import Table from '../components/shopTable/Table';
import './css/ArmorPage.css'; import './css/ArmorPage.css';
const ArmorPage = () => { const ArmorPage = () => {
const tableData = [
{ name: "protection", labels: ['Protection 4', 'Protection 3', 'Protection 2', 'Protection 1'] },
{ name: "unbreaking", labels: ['Unbreaking 3', 'Unbreaking 2', 'Unbreaking 1'] },
{ name: "mending", labels: ['Mending'] },
{ name: "thorns", labels: ['Thorns 3', 'Thorns 2', 'Thorns 1'] },
{ name: "swift_sneak", labels: ['Swift Sneak 2', 'Swift Sneak 1'] },
{ name: "feather_falling", labels: ['Feather Falling 4', 'Feather Falling 3', 'Feather Falling 2', 'Feather Falling 1'] },
{ name: "soul_speed", labels: ['Soul Speed 3', 'Soul Speed 2', 'Soul Speed 1'] },
];
return ( return (
<main className="main_armor"> <main className="main_armor">
<div className="main__container _container"> <div className="main__container _container">
@ -14,7 +24,7 @@ const ArmorPage = () => {
</div> </div>
</div> </div>
</div> </div>
<Table /> <Table data={tableData} />
</div> </div>
</main> </main>
); );

View File

@ -0,0 +1,33 @@
import Bow from '../assets/bow.png';
import Table from '../components/shopTable/Table';
import './css/BowPage.css';
const BowPage = () => {
const tableData = [
{ name: "protection", labels: ['Protection 4', 'Protection 3', 'Protection 2', 'Protection 1'] },
{ name: "unbreaking", labels: ['Unbreaking 3', 'Unbreaking 2', 'Unbreaking 1'] },
{ name: "mending", labels: ['Mending'] },
{ name: "thorns", labels: ['Thorns 3', 'Thorns 2', 'Thorns 1'] },
{ name: "swift_sneak", labels: ['Swift Sneak 2', 'Swift Sneak 1'] },
{ name: "feather_falling", labels: ['Feather Falling 4', 'Feather Falling 3', 'Feather Falling 2', 'Feather Falling 1'] },
{ name: "soul_speed", labels: ['Soul Speed 3', 'Soul Speed 2', 'Soul Speed 1'] },
];
return (
<main className="main_bow">
<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={Bow} alt="bow.png" />
</div>
</div>
</div>
<Table data={tableData} />
</div>
</main>
);
};
export default BowPage;

View File

@ -1,20 +1,14 @@
import Input from '../components/input/Input.jsx'; import { useParams } from 'react-router-dom';
import { Form } from 'react-bootstrap'; import MessagesForm from '../components/messages/form/MessagesForm.jsx';
import './css/ContactUsPage.css'; import './css/ContactUsPage.css';
const ContactUsPage = () => { const ContactUsPage = () => {
const { id } = useParams();
return ( return (
<main className="main_contact"> <main className="main_contact">
<div className="main__container _container"> <div className="main__container _container">
<Form className="main__form form needs-validation" action="contact_us.html" method="get" noValidate> <MessagesForm id={id} />
<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="main__info info_table">
<div className="info_table__row"> <div className="info_table__row">
<div className="info_table__column"> <div className="info_table__column">

View File

@ -7,14 +7,16 @@ const ErrorPage = () => {
const error = useRouteError(); const error = useRouteError();
return ( return (
<Container fluid className='container'> <div className="main__container">
<Container fluid className='container'>
<Alert className='alert' variant='danger'> <Alert className='alert' variant='danger'>
{error?.message ?? 'Page not found 404'} {error?.message ?? 'Page not found 404'}
</Alert> </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> <iframe className='video' src="https://www.youtube.com/embed/dQw4w9WgXcQ" title="Never Gonna Give You Up" ></iframe>
<Button className='button' variant='primary' <Button className='button' variant='primary'
onClick={() => navigate(-1)}>Back</Button> onClick={() => navigate(-1)}>Back</Button>
</Container> </Container>
</div>
); );
}; };

View File

@ -0,0 +1,14 @@
import MessagesTable from "../components/messages/card/MessageTable.jsx";
import './css/MessagesPage.css';
const MessagesPage = () => {
return (
<main className="main__messages">
<div className="main__container _container">
<MessagesTable />
</div>
</main>
);
};
export default MessagesPage;

View File

@ -0,0 +1,33 @@
import Sword from '../assets/sword.png';
import Table from '../components/shopTable/Table';
import './css/SwordPage.css';
const SwordPage = () => {
const tableData = [
{ name: "protection", labels: ['Protection 4', 'Protection 3', 'Protection 2', 'Protection 1'] },
{ name: "unbreaking", labels: ['Unbreaking 3', 'Unbreaking 2', 'Unbreaking 1'] },
{ name: "mending", labels: ['Mending'] },
{ name: "thorns", labels: ['Thorns 3', 'Thorns 2', 'Thorns 1'] },
{ name: "swift_sneak", labels: ['Swift Sneak 2', 'Swift Sneak 1'] },
{ name: "feather_falling", labels: ['Feather Falling 4', 'Feather Falling 3', 'Feather Falling 2', 'Feather Falling 1'] },
{ name: "soul_speed", labels: ['Soul Speed 3', 'Soul Speed 2', 'Soul Speed 1'] },
];
return (
<main className="main_sword">
<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={Sword} alt="sword.png" />
</div>
</div>
</div>
<Table data={tableData} />
</div>
</main>
);
};
export default SwordPage;

View File

@ -0,0 +1,33 @@
import Pickaxe from '../assets/pickaxe.png';
import Table from '../components/shopTable/Table';
import './css/ToolsPage.css';
const ToolsPage = () => {
const tableData = [
{ name: "protection", labels: ['Protection 4', 'Protection 3', 'Protection 2', 'Protection 1'] },
{ name: "unbreaking", labels: ['Unbreaking 3', 'Unbreaking 2', 'Unbreaking 1'] },
{ name: "mending", labels: ['Mending'] },
{ name: "thorns", labels: ['Thorns 3', 'Thorns 2', 'Thorns 1'] },
{ name: "swift_sneak", labels: ['Swift Sneak 2', 'Swift Sneak 1'] },
{ name: "feather_falling", labels: ['Feather Falling 4', 'Feather Falling 3', 'Feather Falling 2', 'Feather Falling 1'] },
{ name: "soul_speed", labels: ['Soul Speed 3', 'Soul Speed 2', 'Soul Speed 1'] },
];
return (
<main className="main_tools">
<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={Pickaxe} alt="pickaxe.png" />
</div>
</div>
</div>
<Table data={tableData} />
</div>
</main>
);
};
export default ToolsPage;

View File

@ -0,0 +1,33 @@
import Trident from '../assets/trident.png';
import Table from '../components/shopTable/Table';
import './css/TridentPage.css';
const TridentPage = () => {
const tableData = [
{ name: "protection", labels: ['Protection 4', 'Protection 3', 'Protection 2', 'Protection 1'] },
{ name: "unbreaking", labels: ['Unbreaking 3', 'Unbreaking 2', 'Unbreaking 1'] },
{ name: "mending", labels: ['Mending'] },
{ name: "thorns", labels: ['Thorns 3', 'Thorns 2', 'Thorns 1'] },
{ name: "swift_sneak", labels: ['Swift Sneak 2', 'Swift Sneak 1'] },
{ name: "feather_falling", labels: ['Feather Falling 4', 'Feather Falling 3', 'Feather Falling 2', 'Feather Falling 1'] },
{ name: "soul_speed", labels: ['Soul Speed 3', 'Soul Speed 2', 'Soul Speed 1'] },
];
return (
<main className="main_trident">
<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={Trident} alt="trident.png" />
</div>
</div>
</div>
<Table data={tableData} />
</div>
</main>
);
};
export default TridentPage;

View File

@ -0,0 +1,14 @@
/* Bow page */
/* Баннер */
.main_bow *> .banner__square1 {
left: 0;
}
.main_bow *> .banner__square2 {
right: 0;
bottom: 0;
box-shadow: -10px -10px 10px 10px rgba(0, 0, 0, .2);
}
.main_bow *> .banner__img {
width: 100%;
margin: 0 auto;
}

View File

@ -1,10 +1,12 @@
.main__container {
padding-top: 75px;
}
.container { .container {
width: 30%; width: 30%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
margin-top: 7%;
background-color: #2c2a2a; background-color: #2c2a2a;
border: solid 3px #a721fa; border: solid 3px #a721fa;
border-radius: 30px; border-radius: 30px;

View File

@ -0,0 +1,3 @@
.main__messages ._container {
justify-content: center;
}

View File

@ -0,0 +1,14 @@
/* Sword page */
/* Баннер */
.main_sword *> .banner__square1 {
left: 0;
}
.main_sword *> .banner__square2 {
right: 0;
bottom: 0;
box-shadow: -10px -10px 10px 10px rgba(0, 0, 0, .2);
}
.main_sword *> .banner__img {
width: 100%;
margin: 0 auto;
}

View File

@ -0,0 +1,14 @@
/* Tools page */
/* Баннер */
.main_tools *> .banner__square1 {
left: 0;
}
.main_tools *> .banner__square2 {
right: 0;
bottom: 0;
box-shadow: -10px -10px 10px 10px rgba(0, 0, 0, .2);
}
.main_tools *> .banner__img img {
width: 150%;
margin: 0 auto;
}

View File

@ -0,0 +1,14 @@
/* Trident page */
/* Баннер */
.main_trident *> .banner__square1 {
left: 0;
}
.main_trident *> .banner__square2 {
right: 0;
bottom: 0;
box-shadow: -10px -10px 10px 10px rgba(0, 0, 0, .2);
}
.main_trident *> .banner__img img {
width: 100%;
margin: 0 auto;
}