Compare commits
No commits in common. "main" and "Code5" have entirely different histories.
25
.eslintrc.cjs
Normal file
25
.eslintrc.cjs
Normal file
@ -0,0 +1,25 @@
|
||||
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
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
5
.idea/misc.xml
Normal file
5
.idea/misc.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?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>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
15
index.html
Normal file
15
index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<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>
|
14
jsconfig.json
Normal file
14
jsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2020",
|
||||
"jsx": "react",
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
]
|
||||
}
|
6007
package-lock.json
generated
Normal file
6007
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
package.json
Normal file
39
package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
1
public/favicon.svg
Normal file
1
public/favicon.svg
Normal file
@ -0,0 +1 @@
|
||||
<?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>
|
After Width: | Height: | Size: 1.3 KiB |
29
readme.md
Normal file
29
readme.md
Normal file
@ -0,0 +1,29 @@
|
||||
#### Окружение:
|
||||
- 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
|
||||
```
|
0
src/App.css
Normal file
0
src/App.css
Normal file
22
src/App.jsx
Normal file
22
src/App.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
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;
|
BIN
src/assets/200.png
Normal file
BIN
src/assets/200.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/Rick.mp4
Normal file
BIN
src/assets/Rick.mp4
Normal file
Binary file not shown.
BIN
src/assets/fon.jpg
Normal file
BIN
src/assets/fon.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 429 KiB |
BIN
src/assets/maxresdefault.jpg
Normal file
BIN
src/assets/maxresdefault.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
1
src/assets/react.svg
Normal file
1
src/assets/react.svg
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
After Width: | Height: | Size: 4.0 KiB |
23
src/components/Button/Button.jsx
Normal file
23
src/components/Button/Button.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
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;
|
25
src/components/Button/Button100.jsx
Normal file
25
src/components/Button/Button100.jsx
Normal file
@ -0,0 +1,25 @@
|
||||
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;
|
26
src/components/Button/ButtonSearch.jsx
Normal file
26
src/components/Button/ButtonSearch.jsx
Normal file
@ -0,0 +1,26 @@
|
||||
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;
|
22
src/components/Button/ChanelDop.jsx
Normal file
22
src/components/Button/ChanelDop.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
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;
|
0
src/components/Button/MaxVideo.jsx
Normal file
0
src/components/Button/MaxVideo.jsx
Normal file
40
src/components/api/ApiClient.js
Normal file
40
src/components/api/ApiClient.js
Normal file
@ -0,0 +1,40 @@
|
||||
import axios from 'axios';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
export class HttpError extends Error {
|
||||
constructor(message = '') {
|
||||
super(message);
|
||||
this.name = 'HttpError';
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
toast.error(message, { id: 'HttpError' });
|
||||
}
|
||||
}
|
||||
|
||||
function responseHandler(response) {
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
const data = response?.data;
|
||||
if (!data) {
|
||||
throw new HttpError('API Error. No data!');
|
||||
}
|
||||
return data;
|
||||
}
|
||||
throw new HttpError(`API Error! Invalid status code ${response.status}!`);
|
||||
}
|
||||
|
||||
function responseErrorHandler(error) {
|
||||
if (error === null) {
|
||||
throw new Error('Unrecoverable error!! Error is null!');
|
||||
}
|
||||
toast.error(error.message, { id: 'AxiosError' });
|
||||
return Promise.reject(error.message);
|
||||
}
|
||||
|
||||
export const ApiClient = axios.create({
|
||||
baseURL: 'http://localhost:3000/',
|
||||
timeout: '10000',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
ApiClient.interceptors.response.use(responseHandler, responseErrorHandler);
|
29
src/components/api/ApiService.js
Normal file
29
src/components/api/ApiService.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { ApiClient } from './ApiClient';
|
||||
|
||||
class ApiService {
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
async getAll(expand) {
|
||||
return ApiClient.get(`${this.url}${expand || ''}`);
|
||||
}
|
||||
|
||||
async get(id, expand) {
|
||||
return ApiClient.get(`${this.url}/${id}${expand || ''}`);
|
||||
}
|
||||
|
||||
async create(body) {
|
||||
return ApiClient.post(this.url, body);
|
||||
}
|
||||
|
||||
async update(id, body) {
|
||||
return ApiClient.put(`${this.url}/${id}`, body);
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
return ApiClient.delete(`${this.url}/${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiService;
|
23
src/components/input/Input.jsx
Normal file
23
src/components/input/Input.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
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;
|
29
src/components/input/Select.jsx
Normal file
29
src/components/input/Select.jsx
Normal file
@ -0,0 +1,29 @@
|
||||
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;
|
55
src/components/lines/form/LinesForm.jsx
Normal file
55
src/components/lines/form/LinesForm.jsx
Normal file
@ -0,0 +1,55 @@
|
||||
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;
|
3
src/components/lines/form/LinesItemForm.css
Normal file
3
src/components/lines/form/LinesItemForm.css
Normal file
@ -0,0 +1,3 @@
|
||||
#image-preview {
|
||||
width: 200px;
|
||||
}
|
55
src/components/lines/form/LinesItemForm.jsx
Normal file
55
src/components/lines/form/LinesItemForm.jsx
Normal file
@ -0,0 +1,55 @@
|
||||
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;
|
34
src/components/lines/hooks/LinesDeleteModalHook.js
Normal file
34
src/components/lines/hooks/LinesDeleteModalHook.js
Normal file
@ -0,0 +1,34 @@
|
||||
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;
|
29
src/components/lines/hooks/LinesFilterHOOOK.js
Normal file
29
src/components/lines/hooks/LinesFilterHOOOK.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import LinesApiService from '../service/LinesApiService';
|
||||
|
||||
const useLines = (typeFilter) => {
|
||||
const [linesRefresh, setLinesRefresh] = useState(false);
|
||||
const [lines, setLines] = useState([]);
|
||||
const handleLinesChange = () => setLinesRefresh(!linesRefresh);
|
||||
|
||||
const getLines = async () => {
|
||||
let expand = '?_expand=type';
|
||||
if (typeFilter) {
|
||||
expand = `${expand}&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;
|
28
src/components/lines/hooks/LinesFilterHook.js
Normal file
28
src/components/lines/hooks/LinesFilterHook.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import useTypes from '../../types/hooks/TypesHook';
|
||||
|
||||
const useTypeFilter = () => {
|
||||
const filterName = 'type';
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const { types } = useTypes();
|
||||
|
||||
const handleFilterChange = (event) => {
|
||||
const type = event.target.value;
|
||||
if (type) {
|
||||
searchParams.set(filterName, event.target.value);
|
||||
} else {
|
||||
searchParams.delete(filterName);
|
||||
}
|
||||
setSearchParams(searchParams);
|
||||
};
|
||||
|
||||
return {
|
||||
types,
|
||||
currentFilter: searchParams.get(filterName) || '',
|
||||
handleFilterChange,
|
||||
};
|
||||
};
|
||||
|
||||
export default useTypeFilter;
|
29
src/components/lines/hooks/LinesHook.js
Normal file
29
src/components/lines/hooks/LinesHook.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import LinesApiService from '../service/LinesApiService';
|
||||
|
||||
const useLines = (typeFilter) => {
|
||||
const [linesRefresh, setLinesRefresh] = useState(false);
|
||||
const [lines, setLines] = useState([]);
|
||||
const handleLinesChange = () => setLinesRefresh(!linesRefresh);
|
||||
|
||||
const getLines = async () => {
|
||||
let expand = '?_expand=type';
|
||||
if (typeFilter) {
|
||||
expand = `${expand}&typeId=${typeFilter}`;
|
||||
}
|
||||
const data = await LinesApiService.getAll(expand);
|
||||
setLines(data ?? []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getLines();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [linesRefresh, typeFilter]);
|
||||
|
||||
return {
|
||||
lines,
|
||||
handleLinesChange,
|
||||
};
|
||||
};
|
||||
|
||||
export default useLines;
|
94
src/components/lines/hooks/LinesItemFormHook.js
Normal file
94
src/components/lines/hooks/LinesItemFormHook.js
Normal file
@ -0,0 +1,94 @@
|
||||
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;
|
34
src/components/lines/hooks/LinesItemHook.js
Normal file
34
src/components/lines/hooks/LinesItemHook.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import LinesApiService from '../service/LinesApiService';
|
||||
|
||||
const useLinesItem = (id) => {
|
||||
const emptyItem = {
|
||||
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;
|
5
src/components/lines/service/LinesApiService.js
Normal file
5
src/components/lines/service/LinesApiService.js
Normal file
@ -0,0 +1,5 @@
|
||||
import ApiService from '../../api/ApiService';
|
||||
|
||||
const LinesApiService = new ApiService('lines');
|
||||
|
||||
export default LinesApiService;
|
5
src/components/lines/service/TypesApiService.js
Normal file
5
src/components/lines/service/TypesApiService.js
Normal file
@ -0,0 +1,5 @@
|
||||
import ApiService from '../../api/ApiService';
|
||||
|
||||
const TypesApiService = new ApiService('types');
|
||||
|
||||
export default TypesApiService;
|
55
src/components/lines/table/Lines.jsx
Normal file
55
src/components/lines/table/Lines.jsx
Normal file
@ -0,0 +1,55 @@
|
||||
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;
|
27
src/components/lines/table/LinesTable.jsx
Normal file
27
src/components/lines/table/LinesTable.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
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;
|
36
src/components/lines/table/LinesTableRow.jsx
Normal file
36
src/components/lines/table/LinesTableRow.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
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;
|
55
src/components/lines/table/LinesUser.jsx
Normal file
55
src/components/lines/table/LinesUser.jsx
Normal file
@ -0,0 +1,55 @@
|
||||
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;
|
15
src/components/lines/utils/Base64.js
Normal file
15
src/components/lines/utils/Base64.js
Normal file
@ -0,0 +1,15 @@
|
||||
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;
|
3
src/components/modal/Modal.css
Normal file
3
src/components/modal/Modal.css
Normal file
@ -0,0 +1,3 @@
|
||||
.modal-title {
|
||||
font-size: 1.2rem;
|
||||
}
|
42
src/components/modal/ModalConfirm.jsx
Normal file
42
src/components/modal/ModalConfirm.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Modal } from 'react-bootstrap';
|
||||
import { createPortal } from 'react-dom';
|
||||
import './Modal.css';
|
||||
|
||||
const ModalConfirm = ({
|
||||
show, title, message, onConfirm, onClose,
|
||||
}) => {
|
||||
return createPortal(
|
||||
<Modal 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;
|
43
src/components/modal/ModalForm.jsx
Normal file
43
src/components/modal/ModalForm.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
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;
|
21
src/components/modal/ModalHook.js
Normal file
21
src/components/modal/ModalHook.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
const useModal = () => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const showModalDialog = () => {
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const hideModalDialog = () => {
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
return {
|
||||
isModalShow: showModal,
|
||||
showModal: showModalDialog,
|
||||
hideModal: hideModalDialog,
|
||||
};
|
||||
};
|
||||
|
||||
export default useModal;
|
64
src/components/navigation/Navigation.jsx
Normal file
64
src/components/navigation/Navigation.jsx
Normal file
@ -0,0 +1,64 @@
|
||||
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;
|
33
src/components/types/hooks/LoginHooks.js
Normal file
33
src/components/types/hooks/LoginHooks.js
Normal file
@ -0,0 +1,33 @@
|
||||
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;
|
22
src/components/types/hooks/SearchHook.js
Normal file
22
src/components/types/hooks/SearchHook.js
Normal file
@ -0,0 +1,22 @@
|
||||
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;
|
32
src/components/types/hooks/TypeHook.js
Normal file
32
src/components/types/hooks/TypeHook.js
Normal file
@ -0,0 +1,32 @@
|
||||
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;
|
21
src/components/types/hooks/TypesHook.js
Normal file
21
src/components/types/hooks/TypesHook.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import TypesApiService from '../service/TypesApiService';
|
||||
|
||||
const useTypes = () => {
|
||||
const [types, setTypes] = useState([]);
|
||||
|
||||
const getTypes = async () => {
|
||||
const data = await TypesApiService.getAll();
|
||||
setTypes(data ?? []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getTypes();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
types,
|
||||
};
|
||||
};
|
||||
|
||||
export default useTypes;
|
67
src/components/types/hooks/TypesItemFormHook.js
Normal file
67
src/components/types/hooks/TypesItemFormHook.js
Normal file
@ -0,0 +1,67 @@
|
||||
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;
|
5
src/components/types/service/TypesApiService.js
Normal file
5
src/components/types/service/TypesApiService.js
Normal file
@ -0,0 +1,5 @@
|
||||
import ApiService from '../../api/ApiService';
|
||||
|
||||
const TypesApiService = new ApiService('types');
|
||||
|
||||
export default TypesApiService;
|
43
src/components/user/TypesForm.jsx
Normal file
43
src/components/user/TypesForm.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
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;
|
19
src/components/user/TypesItemForm.jsx
Normal file
19
src/components/user/TypesItemForm.jsx
Normal file
@ -0,0 +1,19 @@
|
||||
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;
|
622
src/index.css
Normal file
622
src/index.css
Normal file
@ -0,0 +1,622 @@
|
||||
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;
|
||||
}
|
79
src/main.jsx
Normal file
79
src/main.jsx
Normal file
@ -0,0 +1,79 @@
|
||||
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>,
|
||||
);
|
47
src/pages/Admin.jsx
Normal file
47
src/pages/Admin.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
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;
|
44
src/pages/Chanel.jsx
Normal file
44
src/pages/Chanel.jsx
Normal file
@ -0,0 +1,44 @@
|
||||
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;
|
19
src/pages/ErrorPage.jsx
Normal file
19
src/pages/ErrorPage.jsx
Normal file
@ -0,0 +1,19 @@
|
||||
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;
|
71
src/pages/Login.jsx
Normal file
71
src/pages/Login.jsx
Normal file
@ -0,0 +1,71 @@
|
||||
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;
|
39
src/pages/MainPage.jsx
Normal file
39
src/pages/MainPage.jsx
Normal file
@ -0,0 +1,39 @@
|
||||
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;
|
11
src/pages/Redact.jsx
Normal file
11
src/pages/Redact.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
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;
|
14
src/pages/Register.jsx
Normal file
14
src/pages/Register.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
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;
|
28
src/pages/Repair.jsx
Normal file
28
src/pages/Repair.jsx
Normal file
@ -0,0 +1,28 @@
|
||||
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;
|
19
src/pages/Search.jsx
Normal file
19
src/pages/Search.jsx
Normal file
@ -0,0 +1,19 @@
|
||||
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;
|
48
src/pages/Watch.jsx
Normal file
48
src/pages/Watch.jsx
Normal file
@ -0,0 +1,48 @@
|
||||
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;
|
13
vite.config.js
Normal file
13
vite.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
/* 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
BIN
Отчет1.docx
Binary file not shown.
BIN
Отчет2.docx
BIN
Отчет2.docx
Binary file not shown.
BIN
Отчет3.docx
BIN
Отчет3.docx
Binary file not shown.
BIN
Отчет4.docx
BIN
Отчет4.docx
Binary file not shown.
BIN
Отчет5.docx
BIN
Отчет5.docx
Binary file not shown.
Loading…
Reference in New Issue
Block a user