Compare commits

...

No commits in common. "Code5" and "main" have entirely different histories.
Code5 ... main

76 changed files with 0 additions and 8559 deletions

View File

@ -1,25 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'airbnb-base',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 12, 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',
'linebreak-style': 'off',
},
}

24
.gitignore vendored
View File

@ -1,24 +0,0 @@
# 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
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,5 +0,0 @@
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/../untitled.iml" filepath="$PROJECT_DIR$/../untitled.iml" />
</modules>
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

110
data.json

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<title>Тарелька</title>
</head>
<body>
<div id="root" class="h-100 d-flex flex-column"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@ -1,14 +0,0 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Node",
"target": "ES2020",
"jsx": "react",
"strictNullChecks": true,
"strictFunctionTypes": true
},
"exclude": [
"node_modules",
"**/node_modules/*"
]
}

6007
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +0,0 @@
{
"name": "lec4",
"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",
"dev": "npm-run-all --parallel rest vite",
"prod": "npm-run-all lint 'vite build' --parallel rest 'vite preview'"
},
"dependencies": {
"axios": "^1.6.2",
"bootstrap": "^5.3.2",
"fast-levenshtein": "^3.0.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-bootstrap": "^2.9.1",
"react-bootstrap-icons": "^1.10.3",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-router-dom": "^6.18.0"
},
"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"
}
}

View File

@ -1 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"><path d="M418.1,450.121H93.9c-44.333,0-80.4-36.067-80.4-80.4V142.279c0-44.333,36.067-80.4,80.4-80.4h324.2 c44.333,0,80.4,36.067,80.4,80.4v227.442C498.5,414.054,462.433,450.121,418.1,450.121z M93.9,86.879 c-30.548,0-55.4,24.852-55.4,55.4v227.442c0,30.548,24.852,55.4,55.4,55.4h324.2c30.548,0,55.4-24.852,55.4-55.4V142.279 c0-30.548-24.853-55.4-55.4-55.4H93.9z M209.777,358.423c-13.954,0-26.284-5.86-34.718-16.501 c-8.042-10.147-12.293-24.283-12.293-40.878v-90.089c0-16.594,4.251-30.729,12.294-40.877c8.435-10.641,20.764-16.502,34.717-16.502 c11.222,0,23.006,3.64,35.026,10.817l72.299,43.175c20.72,12.374,32.132,29.573,32.132,48.431s-11.412,36.057-32.132,48.431 l-72.299,43.175C232.784,354.784,221,358.423,209.777,358.423z M209.777,178.577c-19.866,0-22.011,22.644-22.011,32.379v90.089 c0,9.735,2.145,32.379,22.011,32.379c6.552,0,14.231-2.518,22.208-7.281l72.298-43.175c12.865-7.683,19.95-17.259,19.95-26.967 s-7.085-19.284-19.95-26.967l-72.298-43.175C224.009,181.095,216.33,178.577,209.777,178.577z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,29 +0,0 @@
#### Окружение:
- nodejs 18;
- VSCode;
- ESLint плагин для VSCode;
- для отладки необходимы бразузеры Chrome или Edge.
#### Создание пустого проекта:
```commandline
npm create vite@latest ./ -- --template react
```
#### Установка зависимостей:
```commandline
npm install
```
#### Запуск проекта:
```commandline
npm run dev
```
#### Сборка проекта:
```commandline
npm run build
```

View File

View File

@ -1,22 +0,0 @@
import PropTypes from 'prop-types';
import { Container } from 'react-bootstrap';
import { Outlet } from 'react-router-dom';
import './App.css';
import Navigation from './components/navigation/Navigation.jsx';
const App = ({ routes }) => {
return (
<>
<Navigation routes={routes}></Navigation>
<Container className='p-2' as="main" fluid>
<Outlet />
</Container>
</>
);
};
App.propTypes = {
routes: PropTypes.array,
};
export default App;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1,23 +0,0 @@
import PropTypes from 'prop-types';
import { useNavigate } from 'react-router-dom';
function Button({ line }) {
const navigator = useNavigate();
const RedirectVideo = () => {
navigator(`/Watch/${line.id}`);
};
return (
<>
<button onClick={RedirectVideo} className="vid">
<img src={line.image} style={{ maxWidth: '100%', maxHeight: '50%' }} />
<h1 className="text-center">{line.title}</h1>
</button>
</>
);
}
Button.propTypes = {
line: PropTypes.object,
};
export default Button;

View File

@ -1,25 +0,0 @@
import PropTypes from 'prop-types';
import { useNavigate } from 'react-router-dom';
function Button100({ line }) {
const navigator = useNavigate();
const RedirectVideo = () => {
navigator(`/Watch/${line.id}`);
};
return (
<>
<button onClick={RedirectVideo} className="vid" style={{ width: '100%' }}>
<img src={line.image} style={{ maxWidth: '100%', maxHeight: '50%' }} />
<h1 className="text-center">{line.title}</h1>
</button>
</>
);
}
Button100.propTypes = {
line: PropTypes.object,
};
export default Button100;

View File

@ -1,26 +0,0 @@
import PropTypes from 'prop-types';
import { useNavigate } from 'react-router-dom';
function ButtonSearch({ line }) {
const navigator = useNavigate();
const RedirectVideo = () => {
navigator(`/Watch/${line.id}`);
};
return (
<>
<button className="vid-row" onClick={RedirectVideo}>
<img src={line.image} style={{ maxWidth: '50%' }} />
<div className="columns" style={{ width: '100%' }}>
<div className="vid-search-text-big">{line.title}</div>
<div className="vid-search-text-small">{line.description}</div>
</div>
</button>
</>
);
}
ButtonSearch.propTypes = {
line: PropTypes.object,
};
export default ButtonSearch;

View File

@ -1,22 +0,0 @@
import PropTypes from 'prop-types';
import { Person } from 'react-bootstrap-icons';
import { useNavigate } from 'react-router-dom';
function ChanelDop({ chanel }) {
const navigator = useNavigate();
const RedirectChanel = () => {
navigator(`/chanel/${chanel.id}`);
};
return (
<>
<div className="row m-3"> <button onClick={RedirectChanel} className="subscribed" >{chanel.name}<Person style={{ float: 'right' }}></Person></button></div>
</>
);
}
ChanelDop.propTypes = {
chanel: PropTypes.object,
};
export default ChanelDop;

View File

@ -1,40 +0,0 @@
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: '10000',
headers: {
Accept: 'application/json',
},
});
ApiClient.interceptors.response.use(responseHandler, responseErrorHandler);

View File

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

View File

@ -1,23 +0,0 @@
import PropTypes from 'prop-types';
import { Form } from 'react-bootstrap';
const Input = ({
name, label, value, onChange, className, ...rest
}) => {
return (
<Form.Group className={`mb-2 ${className || ''}`} controlId={name}>
<Form.Label>{label}</Form.Label>
<Form.Control name={name || ''} value={value || ''} onChange={onChange} {...rest} />
</Form.Group>
);
};
Input.propTypes = {
name: PropTypes.string,
label: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func,
className: PropTypes.string,
};
export default Input;

View File

@ -1,29 +0,0 @@
import PropTypes from 'prop-types';
import { Form } from 'react-bootstrap';
const Select = ({
values, name, label, value, onChange, className, ...rest
}) => {
return (
<Form.Group className={`mb-2 ${className || ''}`} controlId={name}>
<Form.Label className='form-label'>{label}</Form.Label>
<Form.Select name={name || ''} value={value || ''} onChange={onChange} {...rest}>
<option value=''>Выберите значение</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;

View File

@ -1,55 +0,0 @@
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';
const LinesForm = ({ id }) => {
const navigate = useNavigate();
let userId = '';
try {
userId = localStorage.getItem('EnabledUser');
} catch (error) {
userId = null;
}
const {
item,
validated,
handleSubmit,
handleChange,
} = useLinesItemForm(id);
const onBack = () => {
navigate(-1);
};
const onSubmit = async (event) => {
if (await handleSubmit(event)) {
onBack();
}
};
return (
<>
<Form className='m-0 p-2' noValidate validated={validated} onSubmit={onSubmit}>
<LinesItemForm item={item} handleChange={handleChange} user={userId} />
<Form.Group className='row justify-content-center m-0 mt-3'>
<Button className='col-5 col-lg-2 m-0 me-2' variant='secondary' onClick={() => onBack()}>
Назад
</Button>
<Button className='col-5 col-lg-2 m-0 ms-2' type='submit' variant='primary' >
Сохранить
</Button>
</Form.Group>
</Form>
</>
);
};
LinesForm.propTypes = {
id: PropTypes.string,
};
export default LinesForm;

View File

@ -1,3 +0,0 @@
#image-preview {
width: 200px;
}

View File

@ -1,55 +0,0 @@
import PropTypes from 'prop-types';
import imgPlaceholder from '../../../assets/200.png';
import Input from '../../input/Input.jsx';
import Select from '../../input/Select.jsx';
import useTypes from '../../types/hooks/TypesHook';
import useType from '../../types/hooks/TypeHook';
import './LinesItemForm.css';
const LinesItemForm = ({ item, handleChange, user }) => {
const { types } = useTypes();
const user1 = useType(user);
if (user === '1') {
return (
<>
<div className='text-center'>
<img id='image-preview' className='rounded' alt='placeholder'
src={item.image || imgPlaceholder} />
</div>
<Select values={types} name='typeId' label='Автор' value={item.typeId} onChange={handleChange} required />
<Input name='title' label='Название' value={item.title} type='text' onChange={handleChange} required />
<Input name='description' label='Описание' value={item.description} type='text' onChange={handleChange} />
<Input name='image' label='Изображение' onChange={handleChange} type='file' accept='image/*'/>
<Input name='video' label='Видео' onChange={handleChange} type='file' accept='video/*'/>
</>
);
}
let id = '';
try {
id = user1.type.id.toString();
} catch (error) {
id = null;
}
const doNothing = () => {};
return (
<>
<div className='text-center'>
<img id='image-preview' className='rounded' alt='placeholder'
src={item.image || imgPlaceholder} />
</div>
<Select values={types} name='typeId' value={id} readOnly onChange={() => doNothing()} required style={{ display: 'none' }}/>
<Input name='title' label='Название' value={item.title} type='text' onChange={handleChange} required />
<Input name='description' label='Описание' value={item.description} type='text' onChange={handleChange} />
<Input name='image' label='Изображение' onChange={handleChange} type='file' accept='image/*'/>
<Input name='video' label='Видео' onChange={handleChange} type='file' accept='video/*'/>
</>
);
};
LinesItemForm.propTypes = {
item: PropTypes.object,
handleChange: PropTypes.func,
user: PropTypes.string,
};
export default LinesItemForm;

View File

@ -1,34 +0,0 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import LinesApiService from '../service/LinesApiService';
import useModal from '../../modal/ModalHook';
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;

View File

@ -1,29 +0,0 @@
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}&title_like=${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;

View File

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

View File

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

View File

@ -1,94 +0,0 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import getBase64FromFile from '../utils/Base64';
import LinesApiService from '../service/LinesApiService';
import useLinesItem from './LinesItemHook';
const useLinesItemForm = (id, linesChangeHandle) => {
const { item, setItem } = useLinesItem(id);
const [validated, setValidated] = useState(false);
const [setMessage] = useState();
const resetValidity = () => {
setValidated(false);
};
const getLineObject = (formData) => {
const typeId = parseInt(formData.typeId, 10);
const title = formData.title.toString();
const description = formData.description.toString();
const image = formData.image.startsWith('data:image') ? formData.image : '';
const video = formData.video.startsWith('data:video') ? formData.video : '';
return {
typeId: typeId.toString(),
title,
description,
image,
video,
};
};
const handleImageChange = async (event) => {
const { files } = event.target;
const file = await getBase64FromFile(files.item(0));
if (event.target.name === 'image') {
setItem({
...item,
image: file,
});
} else {
setItem({
...item,
video: file,
});
}
};
const handleChange = (event) => {
if (event.target.type === 'file') {
handleImageChange(event);
return;
}
const inputName = event.target.name;
const inputValue = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
setItem({
...item,
[inputName]: inputValue,
});
};
const handleMessageChange = async (event) => {
setMessage(event.target.value);
};
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,
handleMessageChange,
};
};
export default useLinesItemForm;

View File

@ -1,34 +0,0 @@
import { useEffect, useState } from 'react';
import LinesApiService from '../service/LinesApiService';
const useLinesItem = (id) => {
const emptyItem = {
typeId: '',
title: '',
description: '',
image: '',
video: '',
};
const [item, setItem] = useState({ ...emptyItem });
const getItem = async (typeId = undefined) => {
if (typeId && typeId > 0) {
const data = await LinesApiService.get(typeId);
setItem(data);
} else {
setItem({ ...emptyItem });
}
};
useEffect(() => {
getItem(id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);
return {
item,
setItem,
};
};
export default useLinesItem;

View File

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

View File

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

View File

@ -1,55 +0,0 @@
import { Button, ButtonGroup } from 'react-bootstrap';
import { Link, useNavigate } from 'react-router-dom';
import Select from '../../input/Select.jsx';
import useTypeFilter from '../hooks/LinesFilterHook';
import useLines from '../hooks/LinesHook';
import LinesTable from './LinesTable.jsx';
import LinesTableRow from './LinesTableRow.jsx';
import ModalConfirm from '../../modal/ModalConfirm.jsx';
import useLinesDeleteModal from '../hooks/LinesDeleteModalHook';
const Lines = () => {
const { types, currentFilter, handleFilterChange } = useTypeFilter();
const { lines, handleLinesChange } = useLines(currentFilter);
const {
isDeleteModalShow,
showDeleteModal,
handleDeleteConfirm,
handleDeleteCancel,
} = useLinesDeleteModal(handleLinesChange);
const navigate = useNavigate();
const showEditPage = (id) => {
navigate(`/redact/${id}`);
};
return (
<>
<ButtonGroup>
<Button as={Link} to='/redact' variant='success'>
Добавить видео (страница)
</Button>
</ButtonGroup>
<Select className='mt-2' values={types} label='Фильтр по авторам'
value={currentFilter} onChange={handleFilterChange} />
<LinesTable>
{
lines.map((line, index) =>
<LinesTableRow key={line.id}
index={index} line={line}
onEditInPage={() => showEditPage(line.id)}
onDelete={() => showDeleteModal(line.id)}
/>)
}
</LinesTable>
<ModalConfirm show={isDeleteModalShow}
onConfirm={handleDeleteConfirm} onClose={handleDeleteCancel}
title='Удаление' message='Удалить видео?' />
</>
);
};
export default Lines;

View File

@ -1,27 +0,0 @@
import PropTypes from 'prop-types';
import { Table } from 'react-bootstrap';
const LinesTable = ({ children }) => {
return (
<Table className='mt-2' striped responsive>
<thead>
<tr>
<th scope='col'></th>
<th scope='col' className='w-50'>Автор</th>
<th scope='col' className='w-50'>Название</th>
<th scope='col'></th>
<th scope='col'></th>
</tr>
</thead>
<tbody>
{children}
</tbody >
</Table >
);
};
LinesTable.propTypes = {
children: PropTypes.node,
};
export default LinesTable;

View File

@ -1,36 +0,0 @@
import PropTypes from 'prop-types';
import { PencilSquare, Trash3 } from 'react-bootstrap-icons';
import { useNavigate } from 'react-router-dom';
const LinesTableRow = ({
index, line, onDelete, onEditInPage,
}) => {
const handleAnchorClick = (event, action) => {
event.preventDefault();
action();
};
const navigator = useNavigate();
const video = (id) => {
navigator(`/watch/${id}`);
};
return (
<tr>
<th scope="row"><a onClick={() => video(line.id)} >{index + 1}</a></th>
<td>{line.type.name}</td>
<td>{line.title}</td>
<td><a href="#" onClick={(event) => handleAnchorClick(event, onEditInPage)}><PencilSquare /></a></td>
<td><a href="#" onClick={(event) => handleAnchorClick(event, onDelete)}><Trash3 /></a></td>
</tr>
);
};
LinesTableRow.propTypes = {
index: PropTypes.number,
line: PropTypes.object,
onDelete: PropTypes.func,
onEdit: PropTypes.func,
onEditInPage: PropTypes.func,
};
export default LinesTableRow;

View File

@ -1,55 +0,0 @@
import PropTypes from 'prop-types';
import { Button, ButtonGroup } from 'react-bootstrap';
import { Link, useNavigate } from 'react-router-dom';
import useLines from '../hooks/LinesHook';
import LinesTable from './LinesTable.jsx';
import LinesTableRow from './LinesTableRow.jsx';
import ModalConfirm from '../../modal/ModalConfirm.jsx';
import useLinesDeleteModal from '../hooks/LinesDeleteModalHook';
const LinesUser = () => {
const idUser = localStorage.getItem('EnabledUser');
const { lines, handleLinesChange } = useLines(idUser);
const {
isDeleteModalShow,
showDeleteModal,
handleDeleteConfirm,
handleDeleteCancel,
} = useLinesDeleteModal(handleLinesChange);
const navigate = useNavigate();
const showEditPage = (id) => {
navigate(`/redact/${id}`);
};
return (
<>
<ButtonGroup>
<Button as={Link} to='/redact' variant='success'>
Добавить видео (страница)
</Button>
</ButtonGroup>
<LinesTable>
{
lines.map((line, index) =>
<LinesTableRow key={line.id}
index={index} line={line}
onEditInPage={() => showEditPage(line.id)}
onDelete={() => showDeleteModal(line.id)}
/>)
}
</LinesTable>
<ModalConfirm show={isDeleteModalShow}
onConfirm={handleDeleteConfirm} onClose={handleDeleteCancel}
title='Удаление' message='Удалить видео?' />
</>
);
};
LinesUser.propTypes = {
idUser: PropTypes.string,
};
export default LinesUser;

View File

@ -1,15 +0,0 @@
const getBase64FromFile = async (file) => {
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onloadend = () => {
const fileContent = reader.result;
resolve(fileContent);
};
reader.onerror = () => {
reject(new Error('Oops, something went wrong with the file reader.'));
};
reader.readAsDataURL(file);
});
};
export default getBase64FromFile;

View File

@ -1,3 +0,0 @@
.modal-title {
font-size: 1.2rem;
}

View File

@ -1,42 +0,0 @@
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 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()}>
Нет
</Button>
<Button variant='primary' className='col-5 m-0 ms-2'
onClick={() => onConfirm()}>
Да
</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

@ -1,43 +0,0 @@
import PropTypes from 'prop-types';
import { Button, Form, Modal } from 'react-bootstrap';
import { createPortal } from 'react-dom';
import './Modal.css';
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()}>
Отмена
</Button>
<Button variant='primary' className='col-5 m-0 ms-2' type='submit'>
Сохранить
</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

@ -1,21 +0,0 @@
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

@ -1,64 +0,0 @@
import PropTypes from 'prop-types';
import { Container, Navbar, Nav } from 'react-bootstrap';
import { CameraVideo, Search, Person } from 'react-bootstrap-icons';
import { Link, useNavigate } from 'react-router-dom';
const Navigation = ({ routes }) => {
const navigate = useNavigate();
const indexPageLink = routes.filter((route) => route.index === false).shift();
let userId = localStorage.getItem('EnabledUser');
const RedirectChanel = () => {
userId = localStorage.getItem('EnabledUser');
if (userId !== null) {
navigate('/Admin');
}
};
const RedirectSearch = (event) => {
event.preventDefault();
const searchText = document.getElementById('search').value;
navigate(`/search?find=${encodeURIComponent(searchText)}`);
};
const RedirectLogin = () => {
userId = localStorage.getItem('EnabledUser');
if (userId !== null) {
navigate('/account');
} else {
navigate('/login');
}
};
return (
<header>
<Navbar expand='md' className='my-navbar'>
<Container fluid>
<Navbar.Brand as={Link} to={indexPageLink?.path ?? '/'}>
<CameraVideo className='d-inline-block align-top me-1 logo' />
Тарелька
</Navbar.Brand>
<Navbar.Toggle aria-controls='main-navbar' />
<Navbar.Collapse id='main-navbar' style={{ flexGrow: '0' }}>
<Nav className="navbar-nav">
<div className="row" style={{ flexWrap: 'nowrap', margin: '5px 20px 5px 0px' }}>
<input className="form-control me-1 nav-link" type="search" id="search" style={{ width: '90%' }} placeholder="Search" aria-label="Search" />
<button onClick={RedirectSearch} className="back" type="submit" >
<Search className="fa-solid" />
</button>
</div>
<div className="row" style={{ flexWrap: 'nowrap', margin: '5px 20px 5px 0px' }}>
<button onClick={RedirectLogin} className="login nav-link me-1" style={{ width: '90%' }} >Войти/Аккаунт</button>
<button onClick={RedirectChanel} className="user nav-link" type="submit">
<Person className="fa-solid fa-user" />
</button>
</div>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar >
</header>
);
};
Navigation.propTypes = {
routes: PropTypes.array,
};
export default Navigation;

View File

@ -1,33 +0,0 @@
import { useEffect, useState } from 'react';
import TypesApiService from '../service/TypesApiService';
const usePersonL = (loginEnter) => {
let login = loginEnter;
const [user, setUser] = useState(null);
const getUser = async () => {
const data = await TypesApiService.getAll(`?mail=${login}`);
if (data && data.length > 0) {
setUser(data[0]);
} else {
setUser(null);
}
};
useEffect(() => {
getUser(login);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handlerLoginChanged = (newLogin) => {
login = newLogin;
getUser();
};
return {
user,
handlerLoginChanged,
};
};
export default usePersonL;

View File

@ -1,22 +0,0 @@
import { useLocation } from 'react-router-dom';
import useLines from '../../lines/hooks/LinesFilterHOOOK';
const useQuery = () => {
return new URLSearchParams(useLocation().search);
};
const SearchVideos = () => {
const query = useQuery();
const searchText = query.get('find')?.toLowerCase() || '';
const { lines, handleLinesChange } = useLines(searchText);
const files = lines.filter((line) => (
line.title.toLowerCase().includes(searchText.toLowerCase())
));
return {
files,
handleLinesChange,
};
};
export default SearchVideos;

View File

@ -1,32 +0,0 @@
import { useEffect, useState } from 'react';
import TypesApiService from '../service/TypesApiService';
const useType = (id) => {
const emptyItem = {
name: '',
password: '',
mail: '',
};
const [type, setType] = useState({ ...emptyItem });
const getType = async (Tid = undefined) => {
if (Tid && Tid > 0) {
const data = await TypesApiService.get(Tid);
setType(data);
} else {
setType({ ...emptyItem });
}
};
useEffect(() => {
getType(id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);
return {
type,
setType,
};
};
export default useType;

View File

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

View File

@ -1,67 +0,0 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import LinesApiService from '../service/TypesApiService';
import useLinesItem from './TypeHook';
const useTypesItemForm = (id, linesChangeHandle) => {
const { type, setType } = useLinesItem(id);
const [validated, setValidated] = useState(false);
const [setMessage] = useState();
const resetValidity = () => {
setValidated(false);
};
const getTypeObject = (formData) => {
const name = formData.name.toString();
const mail = formData.mail.toString();
const password = formData.password.toString();
return {
name,
mail,
password,
};
};
const handleChange = (event) => {
const inputName = event.target.name;
const inputValue = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
setType({
...type,
[inputName]: inputValue,
});
};
const handleMessageChange = async (event) => {
setMessage(event.target.value);
};
const handleSubmit = async (event) => {
const form = event.currentTarget;
event.preventDefault();
event.stopPropagation();
const body = getTypeObject(type);
if (form.checkValidity()) {
if (id === undefined) {
await LinesApiService.create(body);
}
if (linesChangeHandle) linesChangeHandle();
toast.success('Элемент успешно сохранен', { id: 'LinesTable' });
return true;
}
setValidated(true);
return false;
};
return {
type,
validated,
handleSubmit,
handleChange,
resetValidity,
handleMessageChange,
};
};
export default useTypesItemForm;

View File

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

View File

@ -1,43 +0,0 @@
import PropTypes from 'prop-types';
import { Button, Form } from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
import TypesItemForm from './TypesItemForm.jsx';
import useTypesItemForm from '../types/hooks/TypesItemFormHook';
const TypesForm = ({ id }) => {
const navigate = useNavigate();
const {
type,
validated,
handleSubmit,
handleChange,
} = useTypesItemForm(id);
const onBack = () => {
navigate(-1);
};
const onSubmit = async (event) => {
if (await handleSubmit(event)) {
onBack();
}
};
return (
<>
<Form className='container-fluid w-2 register-table text-center' noValidate validated={validated} onSubmit={onSubmit}>
<TypesItemForm item={type} handleChange={handleChange} />
<Button type='submit' variant='primary'>
Сохранить
</Button>
</Form>
</>
);
};
TypesForm.propTypes = {
id: PropTypes.string,
};
export default TypesForm;

View File

@ -1,19 +0,0 @@
import PropTypes from 'prop-types';
import Input from '../input/Input.jsx';
const TypesItemForm = ({ item, handleChange }) => {
return (
<>
<Input name='name' label='Имя' value={item.name} type='text' onChange={handleChange} required />
<Input name='mail' label='Почта' value={item.mail} type='mail' onChange={handleChange} required />
<Input name='password' label='Пароль' value={item.password} onChange={handleChange} type='password' required />
</>
);
};
TypesItemForm.propTypes = {
item: PropTypes.object,
handleChange: PropTypes.func,
};
export default TypesItemForm;

View File

@ -1,622 +0,0 @@
html, body {
padding: 0;
margin: 0;
font-family: sans-serif;
line-height: 1.15;
height: 100%;
}
header {
background-color: #87B650;
color: #ffffff;
padding: 0.5em;
display: flex;
align-items: center;
}
header nav {
justify-content: flex-end !important;
display: flex !important;
flex-grow: 1;
}
header a {
color: #ffffff;
text-decoration: none;
margin: 0 0.5em;
}
header a:hover {
text-decoration: underline;
}
#logo {
margin-left: 0.5em;
}
article {
padding: 0.5em;
display: flex;
flex-direction: column;
align-items: stretch;
min-height: calc(100% - 64px - 32px - 0.5em * 6);
}
.form-item {
margin-bottom: 1em;
}
.form-item > label {
display: inline-block;
margin-bottom: .3rem;
}
.form-control, .form-select {
display: block;
width: 100%;
font-size: 1rem;
line-height: 1.5;
color: #212529;
background-color: #fff;
border: 1px solid #ced4da;
border-radius: .375em;
padding: .375rem .75rem;
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
.form-select {
padding: .375rem 2.25rem .375rem .75rem;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right .75rem center;
background-size: 16px 12px;
appearance: none;
}
button:not(:disabled) {
cursor: pointer;
}
.btn {
display: inline-block;
font-size: 1rem;
line-height: 1.5;
text-align: center;
text-decoration: none;
vertical-align: middle;
color: #fff;
border-radius: .375em;
border: 1px solid #336fab;
padding: .375rem .75rem;
background-color: #336fab;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
appearance: none;
}
.login{
display: inline-flex;
font-size: 1rem;
color: black;
border-radius: .5em;
border: 1px solid #d9d9d9;
padding: .375rem .75rem;
}
.back {
border: 2px solid #ffa367;
border-radius: 50%;
padding: 10px;
width: 40px;
background-color: #ffa367;
background-origin:content-box;
}
.user {
font-size: 1rem;
width: 40px;
border-radius: 50%;
border: 1px solid #ffa367;
background-color: #ffa367;
padding: 10px;
background-origin: content-box;
}
.left-blank
{
height: 60%;
width: 20%;
top: 20%;
overflow: auto;
position: fixed;
float:left;
background-color: #ffa367;
}
.right-blank
{
width: 80%;
left:22%;
right:2%;
overflow: auto;
float: right;
display: flex;
}
.subscribed
{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
position: relative;
top:16%;
bottom: 16%;
left: 5%;
width: 85%;
font-size: 1rem;
line-height: 1.5;
text-align: center;
vertical-align: middle;
color: #fff;
border-radius: .5em;
border: 1px solid #F45546;
padding: .375rem .75rem;
background-color: #F45546;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
appearance: none;
}
.subscribed_text
{
position: relative;
}
.vid_cont
{
display: grid;
grid-template-columns: repeat(auto-fit, auto);
grid-gap: 20px;
justify-content: space-between;
resize: horizontal;
overflow: hidden;
}
.vid
{
width: 30%;
height: 30%;
}
.rows
{
display: flex;
flex-direction: row;
}
.columns
{
display: flex;
flex-direction: column;
}
.images
{
justify-content: space-around;
}
@media (max-width: 2000px) {
.images
{
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.vid
{
width: 30%;
height: 30%;
}
}
@media (max-width: 1000px) {
.images
{
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.vid
{
width: 50%;
height: 50%;
}
}
.mobdel
{
display: flex;
flex-direction: column;
}
@media (max-width: 768px) {
.images
{
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.vid
{
width: 100%;
height: 100%;
}
.mobdel
{
display: none;
}
.right-blank
{
width: 100%;
overflow: auto;
display: flex;
}
}
/* Тут код на окно регистрации/логина/восстановления */
.orange
{
background-color: #FFA367;
}
@media (max-width: 500px) {
.register-table
{
height: 50%;
width:70%;
top: 20%;
left: 20%;
overflow: auto;
position: fixed;
background-color: #87B650;
}
.register-table-text
{
margin-top: 2%;
margin-left: 1%;
width: 60%;
color: white;
position: fixed;
}
.register-table-column
{
top:30%;
display: flex;
flex-direction: column;
}
.register-table-button
{
width: 60%;
top:60%;
position: fixed;
}
.register-table-input1
{
top: 15%;
width: 60%;
position: fixed;
}
.register-table-input2
{
top: 25%;
width: 60%;
position: fixed;
}
.register-table-input3
{
top: 35%;
width: 60%;
position: fixed;
}
.register-table-input4
{
top: 45%;
width: 60%;
position: fixed;
}
}
@media (min-width: 500px) {
.register-table
{
height: 400px;
width: 300px;
top:20%;
left: 35%;
overflow: auto;
position: fixed;
background-color: #87B650;
}
.register-table-text
{
margin-top: 30px;
margin-left: 35px;
color: white;
position: fixed;
}
.register-table-column
{
top:30%;
display: flex;
flex-direction: column;
}
.register-table-button
{
width: 277px;
margin-top:300px;
position: fixed;
}
.register-table-input1
{
margin-top: 100px;
width: 277px;
position: fixed;
}
.register-table-input2
{
margin-top: 150px;
width: 277px;
position: fixed;
}
.register-table-input3
{
margin-top: 200px;
width: 277px;
position: fixed;
}
.register-table-input4
{
margin-top: 250px;
width: 277px;
position: fixed;
}
}
.reg_button{
display: inline-flex;
font-size: 1rem;
color: black;
border-radius: .5em;
border: 1px solid #d9d9d9;
padding: .375rem .75rem;
width: 80%;
margin-top: 10px;
}
/*Главный вид канала*/
.chanel
{
width: 100%;
background-color: #87B650;
overflow: auto;
display: flex;
}
@media (max-width: 768px) {
.chanel-user {
margin: 40px 0 0 5%;
font-size: 1rem;
height: 50px;
width:80px;
border-radius: 50%;
border: 1px solid #ffa367;
background-color: #ffa367;
padding: 10px;
background-origin: content-box;
}
.chanel-text
{
margin: 40px 0 0 5%;
width: 80%;
height: 100%;
font-size: 20px;
color: white;
}
.chanel-subscribe
{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
position: relative;
margin: 30px 20px 0 auto;
height: 50px;
text-align: center;
vertical-align: middle;
color: #fff;
border-radius: .5em;
border: 1px solid #F45546;
padding: .375rem .75rem;
background-color: #F45546;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
appearance: none;
}
}
@media (min-width: 768px) {
.chanel-user {
top:10%;
margin: 20px 20px 20px 20px;
font-size: 1rem;
height: 70px;
width:95px;
border-radius: 50%;
border: 1px solid #ffa367;
background-color: #ffa367;
padding: 10px;
background-origin: content-box;
}
.chanel-text
{
margin: 40px 0 0 100px;
width: 100%;
height: 100%;
font-size: 40px;
color: white;
}
.chanel-subscribe {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
position: relative;
margin: 40px 20px 10px auto;
height: 30%;
text-align: center;
vertical-align: middle;
color: #fff;
border-radius: .5em;
border: 1px solid #F45546;
padding: .375rem .75rem;
background-color: #F45546;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
appearance: none;
}
.vid
{
margin: auto auto auto auto;
}
}
/*Видеоплеер*/
@media (min-width: 768px) {
.vid-text-big
{
margin: 5px 0 0 8%;
width: 90%;
word-break: break-word;
font-size: 35px;
color: black;
}
.vid-text-small
{
margin: 10px 0 0 8%;
width: 70%;
word-break: break-word;
}
.vid-player
{
margin: 30px 0 0 8%;
}
.vid-column
{
width: 100%;
}
.vid-worker-small
{
display: none;
}
.vid-worker-big
{
display: flex;
}
.vid-row
{
display: flex;
flex-direction: row;
margin: 10px 0 0 10px;
}
.vid-search-text-big
{
margin: 0 0 0 5%;
font-size: 27px;
width: 95%;
word-break: break-word;
text-align:justify;
}
.vid-search-text-small
{
margin: 5% 0 0 5%;
font-size: 20px;
width: 95%;
word-break: break-word;
text-align:justify;
}
}
@media (max-width: 768px) {
.vid-text-big
{
margin: 5px 0 0 5%;
width: 100%;
word-break: break-word;
font-size: 35px;
color: black;
}
.vid-text-small
{
margin: 10px 0 0 5%;
width: 70%;
word-break: break-word;
}
.vid-player
{
margin: 30px 0 0 5%;
width: 90%;
}
.vid-column
{
width: 100%;
}
.vid-worker-small
{
display: flex;
}
.vid-worker-big
{
display: none;
}
.vid-row {
display: flex;
flex-direction: row;
margin: 10px 0 0 10px;
}
.vid-search-text-big
{
margin: 0 0 0 5%;
font-size: 15px;
width: 95%;
word-break: break-word;
text-align:justify;
}
.vid-search-text-small
{
margin: 5% 0 0 5%;
font-size: 12px;
width: 95%;
word-break: break-word;
text-align:justify;
}
}
.vid-admin
{
display: flex;
flex-direction: row;
width: 100%;
height: 30%;
}
#image-preview {
width: 300px;
}

View File

@ -1,79 +0,0 @@
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 MainPage from './pages/MainPage.jsx';
import Chanel from './pages/Chanel.jsx';
import Login from './pages/Login.jsx';
import Register from './pages/Register.jsx';
import Admin from './pages/Admin.jsx';
import Repair from './pages/Repair.jsx';
import Searching from './pages/Search.jsx';
import Watch from './pages/Watch.jsx';
import Redact from './pages/Redact.jsx';
const routes = [
{
index: true,
path: '/',
element: <MainPage />,
title: 'Главная страница',
},
{
path: '/chanel/:id?',
element: <Chanel />,
title: 'Канал',
},
{
path: '/login',
element: <Login />,
title: 'Вход',
},
{
path: '/regist',
element: <Register />,
title: 'Регистрация',
},
{
path: '/admin',
element: <Admin />,
title: 'Админ Панель',
},
{
path: '/account',
element: <Repair />,
title: 'Аккаунт',
},
{
path: '/search',
element: <Searching />,
title: 'Поиск',
},
{
path: '/watch/:id?',
element: <Watch />,
title: 'Видео',
},
{
path: '/redact/:id?',
element: <Redact />,
},
];
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>,
);

View File

@ -1,47 +0,0 @@
import { Person } from 'react-bootstrap-icons';
import Prev from '../assets/fon.jpg';
import Lines from '../components/lines/table/Lines.jsx';
import useType from '../components/types/hooks/TypeHook';
import LinesUser from '../components/lines/table/LinesUser.jsx';
const Admin = () => {
const id = localStorage.getItem('EnabledUser');
const { type } = useType(id);
if (localStorage.getItem('EnabledUser') === '1') {
return (
<>
<div style={{ width: '100%', height: '20%' }}>
<img src={Prev} style={{ width: '100%', height: '100%' }} />
</div>
<div className="chanel rows" style={{ bsGutterX: '0' }}>
<div className="chanel-user">
<Person style={{ margin: '35% 0 0 35%' }} />
</div>
<div className="chanel-text column">
<div>{type.name}</div>
</div>
</div>
<Lines />
</>
);
}
return (
<>
<div style={{ width: '100%', height: '20%' }}>
<img src={Prev} style={{ width: '100%', height: '100%' }} />
</div>
<div className="chanel rows" style={{ bsGutterX: '0' }}>
<div className="chanel-user">
<Person style={{ margin: '35% 0 0 35%' }} />
</div>
<div className="chanel-text column">
<div>{type.name}</div>
</div>
</div>
<LinesUser idUser={id} />
</>
);
};
export default Admin;

View File

@ -1,44 +0,0 @@
import { useParams } from 'react-router-dom';
import { Person } from 'react-bootstrap-icons';
import PropTypes from 'prop-types';
import Prev from '../assets/fon.jpg';
import Button from '../components/Button/Button.jsx';
import useLines from '../components/lines/hooks/LinesHook';
import useType from '../components/types/hooks/TypeHook';
const Chanel = () => {
const { id } = useParams();
const { lines } = useLines(id);
const { type } = useType(id);
return (
<>
<div style={{ width: '100%' }}>
<img src={Prev} style={{ width: '100%' }} />
</div>
<div className="chanel rows" style={{ bsGutterX: '0' }}>
<div className="chanel-user">
<Person style={{ margin: '35% 0 0 35%' }} />
</div>
<div className="chanel-text">
{type.name}
</div>
<button className="chanel-subscribe">
Подписаться
</button>
</div>
<div className="rows images">
{
lines.map((line) =>
<Button key={line.id} line={line} />)
}
</div>
</>
);
};
Chanel.propTypes = {
type: PropTypes.object,
Button: PropTypes.func,
};
export default Chanel;

View File

@ -1,19 +0,0 @@
import { Alert, Button, Container } from 'react-bootstrap';
import { useNavigate } from 'react-router-dom';
const ErrorPage = () => {
const navigate = useNavigate();
return (
<Container fluid className="p-2 row justify-content-center">
<Container className='col-md-6'>
<Alert variant="danger">
Страница не найдена
</Alert>
<Button className="w-25 mt-2" variant="primary" onClick={() => navigate(-1)}>Назад</Button>
</Container>
</Container>
);
};
export default ErrorPage;

View File

@ -1,71 +0,0 @@
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';
import { Button, Form } from 'react-bootstrap';
import usePersonL from '../components/types/hooks/LoginHooks';
const Login = () => {
const navigator = useNavigate();
const RedirectRegist = () => {
navigator('/regist');
};
const [validated, setValidated] = useState(false);
const [formData, setFormData] = useState({
email: '',
password: '',
});
const { user, handlerLoginChanged } = usePersonL(formData.email);
const handleSubmit = (event) => {
event.preventDefault();
event.stopPropagation();
if (user === null) {
return;
}
if (user.password === formData.password) {
const ind = user.id;
localStorage.setItem('EnabledUser', JSON.stringify(user.id));
navigator(`/Chanel/${ind}`);
}
setValidated(true);
};
const handleChange = (event) => {
const inputName = event.target.name;
const inputValue = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
setFormData({
...formData,
[inputName]: inputValue,
});
if (inputName === 'email') {
handlerLoginChanged(inputValue);
}
};
return (
<>
<Form className='container-fluid w-2 register-table text-center col' noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className='mb-2' controlId='email'>
<Form.Label>E-mail</Form.Label>
<Form.Control type='email' name='email' placeholder='name@example.ru' required
value={formData.email} onChange={handleChange} />
</Form.Group>
<Form.Group className='mb-2' controlId='password'>
<Form.Label>Пароль</Form.Label>
<Form.Control type='password' name='password' required
value={formData.password} onChange={handleChange} />
</Form.Group>
<div className='text-center'>
<Button className='w-50' variant='primary' type='submit'>Войти</Button>
</div>
<div style={{ height: '5px' }}></div>
<div className='text-center'>
<Button className='w-50' variant='primary' onClick={RedirectRegist} >Регистрация</Button>
</div>
</Form>
</>
);
};
export default Login;

View File

@ -1,39 +0,0 @@
import PropTypes from 'prop-types';
import useLines from '../components/lines/hooks/LinesHook';
import useTypeFilter from '../components/lines/hooks/LinesFilterHook';
import ChanelDop from '../components/Button/ChanelDop.jsx';
import Button from '../components/Button/Button.jsx';
import useTypes from '../components/types/hooks/TypesHook';
const MainPage = () => {
const { currentFilter } = useTypeFilter();
const { lines } = useLines(currentFilter);
const { types } = useTypes();
return (
<>
<div className="container-fluid w-100" style={{ bsGutterX: '0' }}>
<div className="container-fluid w-2 left-blank text-center column mobdel">
{
types.map((type) =>
<ChanelDop key={type.id} chanel={type} />)
}
</div>
<div className="right-blank row images">
{
lines.map((line) =>
<Button key={line.id} line={line} />)
}
</div>
</div>
</>
);
};
MainPage.propTypes = {
Button: PropTypes.func,
ChanelDop: PropTypes.func,
};
export default MainPage;

View File

@ -1,11 +0,0 @@
import { useParams } from 'react-router-dom';
import LinesForm from '../components/lines/form/LinesForm.jsx';
const Redact = () => {
const { id } = useParams();
return (
<LinesForm id={id} />
);
};
export default Redact;

View File

@ -1,14 +0,0 @@
import { useParams } from 'react-router-dom';
import TypesForm from '../components/user/TypesForm.jsx';
const Register = () => {
const { id } = useParams();
return (
<>
<TypesForm id={id} />
</>
);
};
export default Register;

View File

@ -1,28 +0,0 @@
import { useNavigate } from 'react-router-dom';
import useType from '../components/types/hooks/TypeHook';
const Repair = () => {
const navigator = useNavigate();
const RedirectChanel = () => {
localStorage.removeItem('EnabledUser');
navigator('/');
};
const id = localStorage.getItem('EnabledUser');
const { type } = useType(id);
return (
<>
<div className="container-fluid w-2 register-table text-center col">
<div className="register-table-text">
Информация об аккаунте
</div>
<div className="text-center register-table-input1">{type.name}</div>
<div className="text-center register-table-input2">{type.mail}</div>
<div className="register-table-button">
<button onClick={RedirectChanel} className="reg_button">Выйти из аккаунта</button>
</div>
</div>
</>
);
};
export default Repair;

View File

@ -1,19 +0,0 @@
import ButtonSearch from '../components/Button/ButtonSearch.jsx';
import SearchVideos from '../components/types/hooks/SearchHook';
const Search = () => {
const { files } = SearchVideos();
return (
<>
<div className="columns" style={{ margin: '5% 5% 5% 5%' }}>
{
files.map((line) =>
<ButtonSearch key={line.id} line={line} />)
}
</div>
</>
);
};
export default Search;

View File

@ -1,48 +0,0 @@
import { useParams } from 'react-router-dom';
import Button100 from '../components/Button/Button100.jsx';
import useLinesItemForm from '../components/lines/hooks/LinesItemFormHook';
import useTypeFilter from '../components/lines/hooks/LinesFilterHook';
import useLines from '../components/lines/hooks/LinesHook';
const Watch = () => {
const { id } = useParams();
const {
item,
} = useLinesItemForm(id);
const { currentFilter } = useTypeFilter();
const { lines } = useLines(currentFilter);
return (
<>
<div className="w-100 rows">
<div className="w-75 columns vid-worker-big">
<video controls src={item.video} className="vid-player"></video>
<div className="vid-text-big">{item.title}</div>
<div className="vid-text-small">{item.description}</div>
</div>
<div className="w-25 columns vid-worker-big">
{
lines.map((line) =>
<Button100 key={line.id} line={line} />)
}
</div>
<div className="w-100 columns vid-worker-small">
<div>
<video controls src={item.video} className="vid-player"></video>
<div className="vid-text-big">{item.title}</div>
<div className="vid-text-small">{item.description}</div>
</div>
{
lines.map((line) =>
<Button100 key={line.id} line={line} />)
}
</div>
</div>
</>
);
};
export default Watch;

View File

@ -1,13 +0,0 @@
/* eslint-disable import/no-extraneous-dependencies */
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
sourcemap: true,
chunkSizeWarningLimit: 1024,
emptyOutDir: true,
},
});

BIN
Отчет1.docx Normal file

Binary file not shown.

BIN
Отчет2.docx Normal file

Binary file not shown.

BIN
Отчет3.docx Normal file

Binary file not shown.

BIN
Отчет4.docx Normal file

Binary file not shown.

BIN
Отчет5.docx Normal file

Binary file not shown.