Globally refactored project structure, added login and upload document forms

This commit is contained in:
Dm1tr1y147 2020-09-20 01:32:48 +05:00
parent c50ad9f2b9
commit 101f8c4bac
No known key found for this signature in database
GPG Key ID: D76D70029F55183E
36 changed files with 256 additions and 23 deletions

View File

@ -2,13 +2,15 @@ import React, { useEffect, useRef, useState } from 'react';
import { Switch, Route, useHistory } from 'react-router-dom';
import './App.css';
import Header from './Header';
import Home from './Home';
import Navbar from './Navbar';
import { queryIsEmpty } from './Navbar/utils';
import SubjectList from './SubjectList';
import Header from './components/navigation/Header';
import Home from './views/Home';
import Navbar from './components/navigation/Navbar';
import { queryIsEmpty } from './components/navigation/Navbar/utils';
import SubjectList from './views/SubjectList';
import { ILoadingState, IFilterQuery } from './types';
import NothingFound from './NothingFound';
import NothingFound from './views/NothingFound';
import UploadForm from './views/Admin/UploadForm';
import LogInForm from './views/Admin/LogInForm';
const useDidUpdate: typeof useEffect = (func, dependencies) => {
const didMountRef = useRef(false);
@ -28,6 +30,8 @@ const useDidUpdate: typeof useEffect = (func, dependencies) => {
const App = () => {
const history = useHistory();
const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
const [loading, setLoading] = useState<ILoadingState>({
fetching: true,
error: ''
@ -49,7 +53,11 @@ const App = () => {
return (
<>
<Header query={searchQuery} loading={loading} setSearchQuery={setSearchQuery} />
<Header
query={searchQuery}
loading={loading}
setSearchQuery={setSearchQuery}
/>
<Navbar query={searchQuery} setSearchQuery={setSearchQuery} />
<Switch>
<Route exact path="/">
@ -64,6 +72,16 @@ const App = () => {
setLoading={setLoading}
/>
</Route>
<Route path="/u">
<UploadForm setLoading={setLoading} token={token} setToken={setToken} />
</Route>
<Route path="/l">
<LogInForm
setLoading={setLoading}
token={token}
setToken={setToken}
/>
</Route>
<Route path="*">
<NothingFound setLoading={setLoading} />
</Route>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { IData } from '../types';
import { IData } from '../../../types';
import './main.css';

View File

@ -2,7 +2,7 @@ import React, { Dispatch, SetStateAction } from 'react';
import { motion } from 'framer-motion';
import { useLocation } from 'react-router-dom';
import { IFilterQuery, ILoadingState } from '../types';
import { IFilterQuery, ILoadingState } from '../../../types';
import './main.css';
import Logotype from '../Logotype';
import { genName } from './utils';

View File

@ -1,4 +1,4 @@
import { IFilterQuery } from '../types';
import { IFilterQuery } from '../../../types';
const genName = (
searchQuery: IFilterQuery,
@ -8,6 +8,15 @@ const genName = (
if (error) {
return error.toString();
}
if (path === '/u') {
return 'Upload form';
}
if (path === '/l') {
return 'login';
}
if (path === '/list' && searchQuery) {
let result = '';

View File

@ -2,7 +2,7 @@ import React, { Dispatch, SetStateAction } from 'react';
import { Link } from 'react-router-dom';
import { emptyQuery } from '../Navbar/utils';
import { IFilterQuery } from '../types';
import { IFilterQuery } from '../../../types';
import LogoImage from './logo.png';
import './main.css';

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,5 +1,5 @@
import { Dispatch, RefObject, SetStateAction } from 'react';
import { IFilterQuery } from '../types';
import { IFilterQuery } from '../../../types';
const handleFiltersButton = (
filtersCollapsed: boolean,

View File

@ -10,8 +10,8 @@ import { motion } from 'framer-motion';
import './main.css';
import FilterIcon from './filter.svg';
import SearchIcon from './search.svg';
import { IFilterQuery } from '../types';
import { useFocus } from '../utils';
import { IFilterQuery } from '../../../types';
import { useFocus } from '../../../utils';
import Logotype from '../Logotype';
import {
filtersVariants,

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
import { IFilterQuery } from '../types';
import { IFilterQuery } from '../../../types';
const queryIsEmpty = (q: IFilterQuery): boolean => {
for (const value of Object.values(q)) {

View File

@ -0,0 +1,25 @@
import React from 'react';
type props = {
label: string;
name: string;
options: string[];
};
const Select: React.FC<props> = ({ label, name, options }) => {
return (
<>
<label htmlFor={name}>{label}</label>
<select name={name} id={name}>
<option defaultValue={undefined} value="">
-
</option>
{options.map((val, index) => (
<option key={index} value={val}>{val}</option>
))}
</select>
</>
);
};
export default Select;

View File

@ -8,4 +8,27 @@ const useFocus = (focusRef: React.RefObject<HTMLInputElement>) => {
return setFocus;
};
export { useFocus };
const handleFormSubmit = (
e: React.FormEvent<HTMLFormElement>,
uri: string,
callBack?: (res: Response) => void,
headers?: Headers | string[][] | Record<string, string> | undefined
) => {
e.preventDefault();
const data = new FormData(e.currentTarget);
const options: RequestInit = {
method: 'POST',
body: data,
headers: headers
};
fetch('https://upml-bank.dmitriy.icu/' + uri, options)
.then((res) => {
if (!res.ok) throw res.statusText;
if (callBack) callBack(res);
})
.catch((err) => console.log(err));
};
export { useFocus, handleFormSubmit };

View File

@ -0,0 +1,13 @@
import { Dispatch, SetStateAction } from 'react';
const handleSuccessfulLogin = (
res: Response,
setToken: Dispatch<SetStateAction<string | null>>
) => {
res.json().then(({ token }) => {
localStorage.setItem('token', token);
setToken(token);
});
};
export { handleSuccessfulLogin };

View File

@ -0,0 +1,40 @@
import React, { Dispatch, SetStateAction, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { ILoadingState } from '../../../types';
import { handleFormSubmit } from '../../../utils';
import { handleSuccessfulLogin } from './handlers';
type props = {
setLoading: Dispatch<SetStateAction<ILoadingState>>;
token: string | null;
setToken: Dispatch<SetStateAction<string | null>>;
};
const LogInForm: React.FC<props> = ({ setLoading, token, setToken }) => {
const { push: historyPush } = useHistory();
useEffect(() => {
setLoading({ fetching: false, error: '' });
if (token) {
historyPush('/u');
}
}, [setLoading, historyPush, token]);
return (
<form
onSubmit={(e) =>
handleFormSubmit(e, 'api/login', (res: Response) => handleSuccessfulLogin(res, setToken))
}
>
<label htmlFor="">Имя пользователя</label>
<input type="text" name="username" />
<label htmlFor="password">Пароль</label>
<input type="password" name="password" />
<input type="submit" value="Вход" />
</form>
);
};
export default LogInForm;

View File

View File

@ -0,0 +1,78 @@
import React, { Dispatch, SetStateAction, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import Select from '../../../components/uploadForm/Select';
import { ILoadingState } from '../../../types';
import { handleFormSubmit } from '../../../utils';
import selectOptions from './selectOptions.json';
type props = {
setLoading: Dispatch<SetStateAction<ILoadingState>>;
token: string | null;
setToken: Dispatch<SetStateAction<string | null>>;
};
const UploadForm: React.FC<props> = ({ setLoading, token, setToken }) => {
const { push: historyPush } = useHistory();
useEffect(() => {
setLoading({ fetching: false, error: '' });
if (!token) {
historyPush('/l');
}
}, [setLoading, historyPush, token]);
return (
<>
<form
onSubmit={(e) =>
handleFormSubmit(e, 'api/card/create', undefined, {
Authorization: `Token ${localStorage.token}`
})
}
>
<label htmlFor="title">Название</label>
<input type="text" name="title" />
<Select
label="Преподаватель"
name="teacher"
options={selectOptions.teacher}
/>
<Select
label="Тип задания"
name="type_num"
options={selectOptions.type_num}
/>
<Select
label="Класс"
name="class_num"
options={selectOptions.class_num}
/>
<Select
label="Предмет"
name="predmet_type"
options={selectOptions.predmet_type}
/>
<label htmlFor="image">Файл</label>
<input type="file" name="image" id="image" />
<input type="submit" value="Отправить" />
</form>
<button
onClick={() => {
localStorage.removeItem('token');
setToken(null);
}}
>
Выход
</button>
</>
);
};
export default UploadForm;

View File

View File

@ -0,0 +1,20 @@
{
"teacher": [
"Попов Д.А",
"Ильин А.Б",
"Пачин И.М",
"Николаева Л.Н",
"Ню В.В",
"Вишневская Е.А",
"Некрасов М.В",
"Попова Н.А",
"Пачин М.Ф",
"Керамов Н.Д",
"Новожилова В.И",
"Шпехт А.Ю",
"Конкина Н.В"
],
"type_num": ["Семестровки", "Семинары", "Потоковые"],
"class_num": ["10", "11"],
"predmet_type": ["Математика", "Физика", "Информатика"]
}

View File

@ -0,0 +1,7 @@
import React from 'react';
const Admin: React.FC = () => {
return <div></div>;
};
export default Admin;

0
src/views/Admin/main.css Normal file
View File

View File

@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
import { IFilterQuery } from '../types';
import { IFilterQuery } from '../../types';
const handleShowMore = (
predmet_type: string,

View File

@ -1,9 +1,9 @@
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import Card from '../Card';
import Card from '../../components/lists/Card';
import NothingFound from '../NothingFound';
import { IData, IFilterQuery, ILoadingState } from '../types';
import { IData, IFilterQuery, ILoadingState } from '../../types';
import { handleShowMore } from './handlers';
import './main.css';

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -2,7 +2,7 @@ import React, { useEffect, Dispatch, SetStateAction } from 'react';
import './main.css';
import icon from './icon.svg';
import { ILoadingState } from '../types';
import { ILoadingState } from '../../types';
type props = {
setLoading?: Dispatch<SetStateAction<ILoadingState>>;

View File

@ -1,9 +1,9 @@
import React, { useEffect, useState, Dispatch, SetStateAction } from 'react';
import { useHistory } from 'react-router-dom';
import { IData, ILoadingState, IFilterQuery } from '../types';
import { queryIsEmpty } from '../Navbar/utils';
import Card from '../Card';
import { IData, ILoadingState, IFilterQuery } from '../../types';
import { queryIsEmpty } from '../../components/navigation/Navbar/utils';
import Card from '../../components/lists/Card';
import './main.css';
import NothingFound from '../NothingFound';