This commit is contained in:
Gerimovich_Ilya 2024-01-12 13:32:52 +04:00
parent dabcb6d568
commit eea4e8d8c6
25 changed files with 681 additions and 53 deletions

View File

@ -12,7 +12,7 @@
"bootstrap": "^5.3.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-bootstrap": "^2.9.1",
"react-bootstrap": "^2.9.2",
"react-bootstrap-icons": "^1.10.3",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
@ -4699,9 +4699,9 @@
}
},
"node_modules/react-bootstrap": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.9.1.tgz",
"integrity": "sha512-ezgmh/ARCYp18LbZEqPp0ppvy+ytCmycDORqc8vXSKYV3cer4VH7OReV8uMOoKXmYzivJTxgzGHalGrHamryHA==",
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.9.2.tgz",
"integrity": "sha512-a36B+EHsAI/aH+ZhXNILBFnqscE3zr10dWmjBmfhIb2QR7KSXJiGzYd6Faf/25G8G7/CP9TCL2B0WhUBOD2UBQ==",
"dependencies": {
"@babel/runtime": "^7.22.5",
"@restart/hooks": "^0.4.9",

View File

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

View File

@ -1,24 +1,33 @@
import PropTypes from 'prop-types';
import { Container } from 'react-bootstrap';
// import { Container } from 'react-bootstrap';
import { Outlet } from 'react-router-dom';
import './App.css';
import Footer from './components/footer/Footer.jsx';
import Navigation from './components/navigation/Navigation.jsx';
import Sidebar from './components/sidebar/Sidebar.jsx';
import { AuthProvider } from './components/logins/login/context/AuthContext.jsx';
import { CartProvider } from './components/cart/CartContext.jsx';
const App = ({ routes }) => {
return (
<>
<AuthProvider>
<CartProvider>
<body>
<Navigation routes={routes}></Navigation>
<Container className='flex-container container-fluid' as="main" fluid>
<main className='flex-container container-fluid'>
<Sidebar />
<Container className='' as="main" fluid>
<main className='flex-container full-page-div'>
<Outlet />
</Container>
</main>
</main>
</Container>
<Footer />
</body>
</CartProvider>
</AuthProvider>
</>
);
};
@ -29,6 +38,20 @@ App.propTypes = {
export default App;
{/* <Navigation routes={routes}></Navigation>
<Container className='flex-container container-fluid' as="main" fluid>
<Sidebar />
<Container className='container-fluid full-page-div' as="main" fluid>
<Sidebar />
<Outlet />
</Container>
</Container>
<Footer /> */}
////////////////////////////////////////////////////
{/* <div className="app-container">
<Navigation routes={routes}></Navigation>

View File

@ -0,0 +1,8 @@
.cart-image {
width: 3.1rem;
padding: .25rem;
}
.cart-item {
height: auto;
}

View File

@ -0,0 +1,59 @@
import { Button, ButtonGroup, Card } from 'react-bootstrap';
import { DashLg, PlusLg, XLg } from 'react-bootstrap-icons';
import imgPlaceholder from '../../assets/200.png';
import './Cart.css';
import useCart from './CartHook';
const Cart = () => {
const {
cart,
getCartSum,
addToCart,
removeFromCart,
clearCart,
} = useCart();
return (
<div className='d-flex flex-column align-items-center'>
<div className='mb-2 col-12 col-md-8 col-lg-6 d-flex align-items-center'>
<strong className='flex-fill'>Корзина</strong>
<Button variant='danger' onClick={() => clearCart()}>
<XLg /> Очистить
</Button>
</div>
{
cart.map((cartItem) =>
<Card key={cartItem.id} className='mb-2 col-12 col-md-8 col-lg-6'>
<Card.Body className='p-2 d-flex flex-column flex-sm-row align-items-center'>
<div className='cart-item flex-fill'>
<img className='cart-image' src={cartItem.image || imgPlaceholder} alt="Cart Image" />
{cartItem.type.name}
</div>
<div className='cart-item mt-2 mt-sm-0 d-flex flex-column align-items-center align-items-sm-end'>
<div>
{cartItem.price}
{' * '}
{cartItem.count}
{' = '}
{parseFloat(cartItem.price * cartItem.count).toFixed(2)}
</div>
<ButtonGroup className='mt-2 mt-sm-1' aria-label="Cart counter">
<Button variant="primary" onClick={() => addToCart(cartItem)}>
<PlusLg />
</Button>
<Button variant="danger" onClick={() => removeFromCart(cartItem)}>
<DashLg />
</Button>
</ButtonGroup>
</div>
</Card.Body>
</Card>)
}
<div className='mb-2 col-12 col-md-8 col-lg-6 d-flex justify-content-end'>
<strong>Итого: {getCartSum()} &#8381;</strong>
</div>
</div>
);
};
export default Cart;

View File

@ -0,0 +1,29 @@
import PropTypes from 'prop-types';
import {
createContext,
useEffect,
useReducer,
} from 'react';
import { cartReducer, loadCart, saveCart } from './CartReducer';
const CartContext = createContext(null);
export const CartProvider = ({ children }) => {
const [cart, dispatch] = useReducer(cartReducer, [], loadCart);
useEffect(() => {
saveCart(cart || []);
}, [cart]);
return (
<CartContext.Provider value={{ cart, dispatch }}>
{children}
</CartContext.Provider>
);
};
CartProvider.propTypes = {
children: PropTypes.node,
};
export default CartContext;

View File

@ -0,0 +1,26 @@
import { useContext } from 'react';
import CartContext from './CartContext.jsx';
import { cartAdd, cartClear, cartRemove } from './CartReducer';
const useCart = () => {
const { cart, dispatch } = useContext(CartContext);
const cartSum = () => {
return parseFloat(
cart?.reduce((sum, cartItem) => {
return sum + (cartItem.price * cartItem.count);
}, 0)
?? 0,
).toFixed(2);
};
return {
cart,
getCartSum: () => cartSum(),
addToCart: (item) => dispatch(cartAdd(item)),
removeFromCart: (item) => dispatch(cartRemove(item)),
clearCart: () => dispatch(cartClear()),
};
};
export default useCart;

View File

@ -0,0 +1,71 @@
const setCartCount = (cart, item, value) => {
return cart.map((cartItem) => {
if (cartItem.id === item.id) {
return { ...cartItem, count: cartItem.count + value };
}
return cartItem;
});
};
const addToCart = (cart, item) => {
const existsItem = cart.find((cartItem) => cartItem.id === item.id);
if (existsItem !== undefined) {
return setCartCount(cart, item, 1);
}
return [...cart, { ...item, count: 1 }];
};
const removeFromCart = (cart, item) => {
const existsItem = cart.find((cartItem) => cartItem.id === item.id);
if (existsItem !== undefined && existsItem.count > 1) {
return setCartCount(cart, item, -1);
}
return cart.filter((cartItem) => cartItem.id !== item.id);
};
const CART_KEY = 'localCart';
const CART_ADD = 'cart/add';
const CART_REMOVE = 'cart/remove';
const CART_CLEAR = 'cart/clear';
export const saveCart = (cart) => {
localStorage.setItem(CART_KEY, JSON.stringify(cart));
};
export const loadCart = (initialValue = []) => {
const cartData = localStorage.getItem(CART_KEY);
if (cartData) {
return JSON.parse(cartData);
}
return initialValue;
};
export const cartReducer = (cart, action) => {
const { item } = action;
switch (action.type) {
case CART_ADD: {
return addToCart(cart, item);
}
case CART_REMOVE: {
return removeFromCart(cart, item);
}
case CART_CLEAR: {
return [];
}
default: {
throw Error(`Unknown action: ${action.type}`);
}
}
};
export const cartAdd = (item) => ({
type: CART_ADD, item,
});
export const cartRemove = (item) => ({
type: CART_REMOVE, item,
});
export const cartClear = () => ({
type: CART_CLEAR,
});

View File

@ -24,14 +24,31 @@ const UpdateVideo1 = ({
};
return (
<div className="col-md-4 mb-4">
<div className="rectNews d-flex flex-column">
<img src={item.image} width="100%" alt={item.name} />
<div className="rectNewsTextBox text-center">
<span className="rectNewsText">
<div className="card mb-4 shadow-sm">
<img className="card-img-top" src={item.image} width="100%" />
<div className="card-body">
<div className="container">
<div className="row">
<div className='col-md-4'>
<p className='h-4 m-0 text-nowrap font-italic font-weight-bold'>
{item.name}
</p>
<p className='fs-5 text-nowrap text-secondary'>
{item.description}
</p>
<a href="#" onClick={(event) => handleAnchorClick(event, onEdit)}><PencilFill /></a>
<a href="#" onClick={(event) => handleAnchorClick(event, onDelete)}><Trash3 /></a>
{item.description}
</span>
</div>
<div className='col-md-4'>
</div>
<div className='col-md-4'>
<a className='btn btn-secondary ms-auto' href='./channel'></a>
</div>
</div>
</div>
</div>
</div>
</div>
@ -68,11 +85,11 @@ const UpdateVideos = () => {
<>
<Container className="col text-center">
<span className="mainSt">
<b>Новости</b>
<b>Видео</b>
</span>
<div className="text-center">
<Button variant='info' onClick={() => showFormModal()}>
Добавить товар</Button>
Добавить видео</Button>
</div>
<div className="mainDiv col-md-9 ml-sm-auto col-lg-10 px-">
<div className='mainRow row'>

View File

@ -0,0 +1,33 @@
import { useEffect, useState } from 'react';
import EntrysDataApiService from '../service/EntrysDataApiService';
const useEntysDataItem = (id) => {
const emptyItem = {
id: '',
login: '',
password: '',
role: '',
};
const [item, setItem] = useState({ ...emptyItem });
const getItem = async (itemId = undefined) => {
if (itemId && itemId > 0) {
const data = await EntrysDataApiService.get(itemId);
setItem(data);
} else {
setItem({ ...emptyItem });
}
};
useEffect(() => {
getItem(id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);
return {
item,
setItem,
};
};
export default useEntysDataItem;

View File

@ -0,0 +1,22 @@
import { useEffect, useState } from 'react';
import EntrysDataApiService from '../service/EntrysDataApiService';
const useEntrysData = (login, password, expand) => {
const [entrys, setEntrys] = useState([]);
const getEntrysData = async () => {
const data = await EntrysDataApiService.getAll(expand);
setEntrys(data ?? []);
};
useEffect(() => {
getEntrysData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [login, password]);
return {
entrys,
};
};
export default useEntrysData;

View File

@ -0,0 +1,60 @@
import { useState } from 'react';
import EntrysDataApiService from '../service/EntrysDataApiService';
import useEntysDataItem from './DataItemHook';
const useEntrysItemForm = (id) => {
const { item, setItem } = useEntysDataItem(id);
const [validated, setValidated] = useState(false);
const resetValidity = () => {
setValidated(false);
};
const getLineObject = (formData) => {
const Login = formData.login.toString();
const Password = formData.password.toString();
const Role = 'user';
return {
login: Login,
password: Password,
role: Role,
};
};
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 EntrysDataApiService.create(body);
} else {
await EntrysDataApiService.update(id, body);
}
return true;
}
setValidated(true);
return false;
};
return {
item,
validated,
UseHandleSubmit: handleSubmit,
UseHandleChange: handleChange,
resetValidity,
};
};
export default useEntrysItemForm;

View File

@ -0,0 +1,58 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import toast from 'react-hot-toast';
import { Button, Form } from 'react-bootstrap';
import Input from '../../input/Input.jsx';
import useEntrysData from '../hooks/EntrysDataHook';
const Entry = () => {
const [validated, setValidated] = useState(false);
const [login, setLogin] = useState('');
const [password, setPassword] = useState('');
const isLoginValid = (value) => /^[a-zA-Z]+$/.test(value);
const { entrys } = useEntrysData(login, password);
const handleSubmit = (event) => {
const form = event.currentTarget;
event.preventDefault();
event.stopPropagation();
if (form.checkValidity() !== false) {
if (isLoginValid(login)) {
if (entrys.length === 0) {
toast.error('Аккаунт не найден');
} else {
setValidated(true);
toast.success('Был произведён вход');
}
} else {
toast.error('Логин должен быть введён латинскими символами');
}
}
setValidated(true);
};
return (
<body className="container-fluid text-center">
<span className="mainSt">
<b>Личный кабинет</b>
</span>
<div className="rectpage4 d-flex row justify-content-center">
<span className="EntrysSt">
<b>Вход</b>
</span>
<Form className="col-md-4 m-0 w-auto" onSubmit={ handleSubmit } noValidate validated={validated}>
<label className="form-label"><b>Логин</b></label>
<Input name="login" value = { login } onChange={(e) => setLogin(e.target.value)}
placeholder="dyctator" type="text" required />
<label className="form-label"><b>Пароль</b></label>
<Input name="password" value = { password } onChange={(e) => setPassword(e.target.value)}
type="password" required />
<Button className="btn btn-primary w-auto" type="submit" >Войти</Button>
<Button as={Link} to='/registrPage' variant = "danger" className="btn btn-primary w-auto" >Регистрация</Button>
</Form>
</div>
</body>
);
};
export default Entry;

View File

@ -0,0 +1,87 @@
import { useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import toast from 'react-hot-toast';
import { Button, Form } from 'react-bootstrap';
import Input from '../../../input/Input.jsx';
import useEntrysData from '../../hooks/EntrysDataHook';
import { AuthContext } from '../context/AuthContext.jsx';
const Entry = () => {
const [validated, setValidated] = useState(false);
const [login, setLogin] = useState('');
const [password, setPassword] = useState('');
const isLoginValid = (value) => /^[a-zA-Z0-9]+$/.test(value);
const { entrys } = useEntrysData(login, password, `?login=${login}&password=${password}`);
const { state, dispatch } = useContext(AuthContext);
const handleSubmit = (event) => {
const form = event.currentTarget;
event.preventDefault();
event.stopPropagation();
if (form.checkValidity() !== false) {
if (isLoginValid(login)) {
if (entrys.length === 0) {
toast.error('Аккаунт не найден');
} else {
setValidated(true);
dispatch({
type: 'LOGIN',
payload: entrys,
});
toast.success(`Был произведён вход: ${login}`);
}
} else {
toast.error('Логин должен быть введён латинскими символами');
}
}
setValidated(true);
};
const handleLogOut = () => {
dispatch({
type: 'LOGOUT',
payload: entrys,
});
};
return (
<main className="container-fluid text-center">
<span className="mainSt">
<b>Личный кабинет</b>
</span>
<div className="rectpage4 d-flex row justify-content-center">
{state.user === null ? (
<>
<span className="EntrysSt">
<b>Вход</b>
</span>
<Form className="col-md-4 m-0 w-auto" onSubmit={ handleSubmit } noValidate validated={validated}>
<label className="form-label"><b>Логин</b></label>
<Input name="login" value = { login } onChange={(e) => setLogin(e.target.value)}
placeholder="dyctator" type="text" required />
<label className="form-label"><b>Пароль</b></label>
<Input name="password" value = { password } onChange={(e) => setPassword(e.target.value)}
type="password" required />
<Button className="btn btn-primary w-auto" type="submit" >Войти</Button>
<Button as={Link} to='/registrPage' variant = "danger" className="btn btn-primary w-auto" >Регистрация</Button>
</Form>
</>
) : (
<>
<p className = 'EntrysSt'>Welcome, {state.user.map((item) => item.login)}</p>
<Button className="btn btn-primary w-25 h-25" variant = "danger" onClick = {handleLogOut}>
Выйти
</Button>
</>
)}
</div>
</main>
);
};
Entry.propTypes = {
item: PropTypes.object,
};
export default Entry;

View File

@ -0,0 +1,73 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import toast from 'react-hot-toast';
import { Button, Form } from 'react-bootstrap';
import Input from '../../input/Input.jsx';
import useEntrysData from '../hooks/EntrysDataHook';
import useEntrysItemForm from '../hooks/EntrysDataItemHook';
const Entry = () => {
const [validated, setValidated] = useState(false);
const [login, setLogin] = useState('');
const [password, setPassword] = useState('');
const isLoginValid = (value) => /^[a-zA-Z0-9]+$/.test(value);
const { entrys } = useEntrysData(login, password, `?login=${login}`);
const { UseHandleSubmit, UseHandleChange } = useEntrysItemForm();
const handleSubmit = (event) => {
const form = event.currentTarget;
event.preventDefault();
event.stopPropagation();
if (form.checkValidity() !== false) {
if (isLoginValid(login)) {
const isLoginExists = entrys.some((item) => item.login === login);
if (isLoginExists) {
toast.error('Такой аккаунт уже создан');
} else {
UseHandleSubmit(event);
toast.success('Ваш аккаунт успешно создан');
}
} else {
toast.error('Логин должен быть введён латинскими символами');
return;
}
}
setValidated(true);
};
const handleChangeLogin = (event) => {
setLogin(event.target.value);
UseHandleChange(event);
};
const handleChangePassword = (event) => {
setPassword(event.target.value);
UseHandleChange(event);
};
return (
<main className="container-fluid text-center">
<span className="mainSt">
<b>Личный кабинет</b>
</span>
<div className="rectpage4 d-flex row justify-content-center">
<span className="EntrysSt">
<b>Регистрация</b>
</span>
<Form className="col-md-4 m-0 w-auto" onSubmit={ handleSubmit } noValidate validated={validated}>
<label className="form-label"><b>Логин</b></label>
<Input name="login" value = { login } onChange={ handleChangeLogin }
placeholder="dyctator" type="text" required />
<label className="form-label"><b>Пароль</b></label>
<Input name="password" value = { password } onChange={ handleChangePassword }
type="password" required />
<Button as={Link} to='/page4' className = "btn btn-info">Назад</Button>
<Button className="btn btn-primary w-auto" type="submit" >Создать</Button>
</Form>
</div>
</main>
);
};
export default Entry;

View File

@ -0,0 +1,66 @@
import { createContext, useReducer, useEffect } from 'react';
import PropTypes from 'prop-types';
// Функция для сохранения состояния user в localStorage
const saveToLocalstorage = (user) => {
if (user) {
localStorage.setItem('authState', JSON.stringify(user));
} else {
localStorage.removeItem('authState');
}
};
// Функция для восстановления состояния user из localStorage
const getInitialState = () => {
const userStorage = localStorage.getItem('authState');
if (userStorage) {
return { user: JSON.parse(userStorage) };
}
return { user: null };
};
const AuthReducer = (state, action) => {
switch (action.type) {
case 'LOGIN':
saveToLocalstorage(action.payload);
return {
...state,
user: action.payload,
};
case 'LOGOUT':
saveToLocalstorage(null);
return {
...state,
user: null,
};
default:
return state;
}
};
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, getInitialState, getInitialState);
// При инициализации компонента проверим localStorage
useEffect(() => {
const user = localStorage.getItem('authState');
if (user) {
dispatch({
type: 'LOGIN',
payload: JSON.parse(user),
});
}
}, []);
return (
<AuthContext.Provider value={{ state, dispatch }}>
{children}
</AuthContext.Provider>
);
};
AuthProvider.propTypes = {
children: PropTypes.node.isRequired,
};

View File

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

View File

@ -3,13 +3,13 @@ import TableDirect from './TableDirect.jsx';
import Search from './Search.jsx';
import useSearching from '../hooks/SearchHooks.js';
import Input from '../../input/Input.jsx';
import useTypeFilter from '../../lines/hooks/LinesFilterHook.js';
// import useTypeFilter from '../../lines/hooks/LinesFilterHook.js';
const Searching = () => {
const { type, currentFilter } = useTypeFilter;
// const { type, currentFilter } = useTypeFilter;
const { searching } = useSearching();
const [searchValue, setSearchValue] = useState('');
const {lines} =useLines(currentFilter);
// const {lines} =useLines(currentFilter);
return (
<>

View File

@ -50,7 +50,7 @@ h4 {
height: 500px;
}*/
/* .container-fluid{
height: 100vh;
height: 100%;
}
.full-page-div {
width: 100%;
@ -109,23 +109,9 @@ font-size: 13px;
}
/* .sidebar {
.sidebar {
background-color: #f8f9fa;
height: 100vh;
} */
.video-thumbnail {
/* width: 1920px;
height: 1080px; */
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
}
.embed-responsive{
max-width: 100%;
max-height: 100%;
width: 1600px;
height: 800px;
}
img,

View File

@ -1,8 +1,9 @@
// import React from 'react';
import './Sidebar.css';
import '../sidebar/Sidebar.css';
const Sidebar = () => {
return (
<body>
<div className="container-fluid ">
<div className="row">
@ -78,6 +79,7 @@ const Sidebar = () => {
</nav>
</div>
</div>
</body>
);
};

View File

@ -12,7 +12,6 @@ const useTypes = () => {
useEffect(() => {
getTypes();
}, []);
return {
types,
};

View File

@ -1,3 +1,4 @@
import Entry from '../components/logins/login/Entry/Entry.jsx';
const AdminPage = () => {
@ -15,6 +16,7 @@ const AdminPage = () => {
<>
<main role="main" className="col-md-9 ml-sm-auto col-lg-10 px-4">
<div className="full-page-div">
<Entry />
<div className="btn-group" role="group">
<button id="items-add" className="btn btn-info">
Добавить видео (диалог)

View File

@ -16,7 +16,7 @@ html, body {
}
}
.full-page-div {
background-color: #ececec; /* Цвет фона */
background-color:#ececec; /* Цвет фона #ececec */
padding: 1%; /* Внутренний отступ */
margin: 1%; /* Внешний отступ */
}

View File

@ -1,5 +1,7 @@
import { Link } from 'react-router-dom';
import UpdateVideos from '../components/lines/videos/UpdateVideos.jsx';
import '../pages/Pages.css';
const StartPage = () => {
return (
@ -16,7 +18,7 @@ const StartPage = () => {
<div className="p-5 text-bg-dark rounded-3">
<h2>Популярные трансляции</h2>
<div className="row">
<UpdateVideos />
<UpdateVideos/>
</div>
</div>