Added even more TypeScript, Header, Logotype and NothingFound components, some code refactors
This commit is contained in:
parent
f6c9dd293c
commit
64340c4ef0
42
src/App.tsx
42
src/App.tsx
@ -2,41 +2,13 @@ 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 { ILoadingState, IFilterQuery } from './types';
|
||||
|
||||
const genName = (searchQuery: IFilterQuery, path: string): string => {
|
||||
if (path === '/list' && searchQuery) {
|
||||
let result = '';
|
||||
|
||||
if (searchQuery.class_num) {
|
||||
result = result + searchQuery.class_num + ' класс';
|
||||
}
|
||||
|
||||
if (searchQuery.predmet_type) {
|
||||
result = result + ' ' + searchQuery.predmet_type;
|
||||
}
|
||||
|
||||
if (searchQuery.teacher) {
|
||||
result = result + ' ' + searchQuery.teacher;
|
||||
}
|
||||
|
||||
if (searchQuery.search) {
|
||||
result = result + ' поиск по "' + searchQuery.search + '"';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (path === '/') {
|
||||
return 'Банк семинаров';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
import NothingFound from './NothingFound';
|
||||
|
||||
const useDidUpdate: typeof useEffect = (func, dependencies) => {
|
||||
const didMountRef = useRef(false);
|
||||
@ -76,10 +48,8 @@ const App = () => {
|
||||
}, [searchQuery]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<header id="name">
|
||||
<h1>{genName(searchQuery, history.location.pathname)}</h1>
|
||||
</header>
|
||||
<>
|
||||
<Header query={searchQuery} loading={loading} setSearchQuery={setSearchQuery} />
|
||||
<Navbar query={searchQuery} setSearchQuery={setSearchQuery} />
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
@ -95,10 +65,10 @@ const App = () => {
|
||||
/>
|
||||
</Route>
|
||||
<Route path="*">
|
||||
<h1>404</h1>
|
||||
<NothingFound setLoading={setLoading} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
103
src/Header/index.tsx
Normal file
103
src/Header/index.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import React, { Dispatch, SetStateAction, useEffect } from 'react';
|
||||
import { motion, Transition } from 'framer-motion';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { IFilterQuery, ILoadingState } from '../types';
|
||||
import './main.css';
|
||||
import Logotype from '../Logotype';
|
||||
|
||||
const genName = (
|
||||
searchQuery: IFilterQuery,
|
||||
path: string,
|
||||
error: string
|
||||
): string => {
|
||||
if (error) {
|
||||
return error.toString();
|
||||
}
|
||||
if (path === '/list' && searchQuery) {
|
||||
let result = '';
|
||||
|
||||
if (searchQuery.class_num) {
|
||||
result = result + searchQuery.class_num + ' класс';
|
||||
}
|
||||
|
||||
if (searchQuery.predmet_type) {
|
||||
result = result + ' ' + searchQuery.predmet_type;
|
||||
}
|
||||
|
||||
if (searchQuery.teacher) {
|
||||
result = result + ' ' + searchQuery.teacher;
|
||||
}
|
||||
|
||||
if (searchQuery.search) {
|
||||
result = result + ' поиск по "' + searchQuery.search + '"';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (path === '/') {
|
||||
return 'Банк семинаров';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const Header = ({
|
||||
query,
|
||||
loading,
|
||||
setSearchQuery
|
||||
}: {
|
||||
query: IFilterQuery;
|
||||
loading: ILoadingState;
|
||||
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
|
||||
}) => {
|
||||
/*
|
||||
* Hooks definitions
|
||||
*/
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {}, [loading]);
|
||||
|
||||
/*
|
||||
* Animations
|
||||
*/
|
||||
const headerVariants = {
|
||||
expanded: {
|
||||
height: '100vh'
|
||||
},
|
||||
collapsed: {
|
||||
height: '10vh'
|
||||
}
|
||||
};
|
||||
|
||||
const transition: Transition = {
|
||||
duration: 0.5,
|
||||
ease: 'linear'
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
id="name"
|
||||
variants={headerVariants}
|
||||
transition={transition}
|
||||
animate={loading.fetching ? 'expanded' : 'collapsed'}
|
||||
initial="expanded"
|
||||
>
|
||||
<h1>{genName(query, history.location.pathname, loading.error)}</h1>
|
||||
{loading.fetching ? (
|
||||
<motion.div
|
||||
id="loadingLogo"
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ loop: Infinity, ease: 'linear', duration: 3 }}
|
||||
>
|
||||
<Logotype setSearchQuery={setSearchQuery} />
|
||||
</motion.div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</motion.header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
40
src/Header/main.css
Normal file
40
src/Header/main.css
Normal file
@ -0,0 +1,40 @@
|
||||
#name {
|
||||
background-color: rgb(54, 54, 69);
|
||||
padding: 2vh;
|
||||
color: #ffffff;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
height: 10vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#name h1 {
|
||||
text-align: center;
|
||||
line-height: 6vh;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#loadingLogo {
|
||||
position: absolute;
|
||||
width: 75vw;
|
||||
height: 75vw;
|
||||
left: 13.5vw;
|
||||
top: calc((100vh - 75vw) / 2);
|
||||
z-index: 1000;
|
||||
background-color: white;
|
||||
border-radius: 100%;
|
||||
padding: 1vh;
|
||||
}
|
||||
|
||||
@media (orientation: landscape) {
|
||||
#loadingLogo {
|
||||
top: 25vh;
|
||||
left: calc((100vw - 50vh) / 2);
|
||||
width: 50vh;
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
[{
|
||||
"title": "Семинар 10",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "1",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Математика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 3
|
||||
},
|
||||
{
|
||||
"title": "Семинар 10",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "1",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Физика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 3
|
||||
},
|
||||
{
|
||||
"title": "Семинар 10",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "1",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Математика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 1
|
||||
},
|
||||
{
|
||||
"title": "Very long text, too long to show it full",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "2",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Математика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 2
|
||||
},
|
||||
{
|
||||
"title": "Семинар 10",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "2",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Математика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 3
|
||||
},
|
||||
{
|
||||
"title": "Семинар 10",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "3",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Математика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 1
|
||||
},
|
||||
{
|
||||
"title": "Very long text, too long to show it full",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "3",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Математика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 2
|
||||
},
|
||||
{
|
||||
"title": "Семинар 10",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "11",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Математика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 3
|
||||
},
|
||||
{
|
||||
"title": "Семинар 10",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "10",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Математика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 1
|
||||
},
|
||||
{
|
||||
"title": "Very long text, too long to show it full",
|
||||
"type_num": "Семинары",
|
||||
"class_num": "11",
|
||||
"post_date": "2020-09-15",
|
||||
"predmet_type": "Математика",
|
||||
"teacher": "Ню В.В",
|
||||
"image": "/code/media/card_img/bxd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd0xb0xd1x80_10_xd0xa1xd0xb5xd0xbcxd0xb8xd0xbdxd_lN2D1k2.jpg",
|
||||
"slug": "card-3-11-2020-09-15",
|
||||
"card_id": 2
|
||||
}
|
||||
]
|
@ -2,6 +2,7 @@ import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Card from '../Card';
|
||||
import NothingFound from '../NothingFound';
|
||||
import { IData, IFilterQuery, ILoadingState } from '../types';
|
||||
import './main.css';
|
||||
|
||||
@ -38,60 +39,75 @@ const Home = ({
|
||||
];
|
||||
|
||||
const handleShowMore = (predmet_type: string, class_num: number) => {
|
||||
setSearchQuery((prev): IFilterQuery => {
|
||||
return { ...prev, predmet_type, class_num: class_num.toString() };
|
||||
});
|
||||
setSearchQuery(
|
||||
(prev): IFilterQuery => {
|
||||
return {
|
||||
...prev,
|
||||
predmet_type,
|
||||
class_num: class_num.toString()
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="homeContainer">
|
||||
{classes.map((class_num, index) => (
|
||||
<div key={index} className="classContainer">
|
||||
<h1>{class_num} класс</h1>
|
||||
{subjects.map((subject, jndex) =>
|
||||
data.filter(
|
||||
(el) =>
|
||||
parseInt(el.class_num) === class_num &&
|
||||
el.predmet_type === subject
|
||||
).length ? (
|
||||
<div key={jndex} className="subjectContainer">
|
||||
<h2>{subject}</h2>
|
||||
<div className="carousel">
|
||||
<div className="carouselInner">
|
||||
{data
|
||||
.filter(
|
||||
(el) =>
|
||||
parseInt(el.class_num) ===
|
||||
class_num &&
|
||||
el.predmet_type === subject
|
||||
)
|
||||
.map((el, kndex) => (
|
||||
<Card key={kndex} data={el} />
|
||||
))}
|
||||
</div>
|
||||
<div className="showMore">
|
||||
<Link
|
||||
onClick={() =>
|
||||
handleShowMore(
|
||||
subject,
|
||||
class_num
|
||||
{classes.length ? (
|
||||
classes.map((class_num, index) => (
|
||||
<div key={index} className="classContainer">
|
||||
<h1>{class_num} класс</h1>
|
||||
{subjects.map((subject, jndex) =>
|
||||
data.filter(
|
||||
(el) =>
|
||||
parseInt(el.class_num) === class_num &&
|
||||
el.predmet_type === subject
|
||||
).length ? (
|
||||
<div key={jndex} className="subjectContainer">
|
||||
<h2>{subject}</h2>
|
||||
<div className="carousel">
|
||||
<div className="carouselInner">
|
||||
{data
|
||||
.filter(
|
||||
(el) =>
|
||||
parseInt(
|
||||
el.class_num
|
||||
) === class_num &&
|
||||
el.predmet_type ===
|
||||
subject
|
||||
)
|
||||
}
|
||||
to={'/list'}
|
||||
>
|
||||
Больше →
|
||||
</Link>
|
||||
.map((el, kndex) => (
|
||||
<Card
|
||||
key={kndex}
|
||||
data={el}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="showMore">
|
||||
<Link
|
||||
onClick={() =>
|
||||
handleShowMore(
|
||||
subject,
|
||||
class_num
|
||||
)
|
||||
}
|
||||
to={'/list'}
|
||||
>
|
||||
Больше →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
)}
|
||||
) : (
|
||||
''
|
||||
)
|
||||
)}
|
||||
|
||||
<div className="curve"></div>
|
||||
</div>
|
||||
))}
|
||||
<div className="curve"></div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<NothingFound />
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
@ -7,6 +7,7 @@
|
||||
padding-top: calc(20px + 2vh);
|
||||
margin-top: -20px;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.subjectContainer {
|
||||
@ -69,11 +70,11 @@
|
||||
}
|
||||
|
||||
.classContainer:nth-child(odd) .showMore {
|
||||
background: linear-gradient(to right, rgba(244, 244, 244, 0) 25%, rgb(244, 244, 244) 70%);
|
||||
background: linear-gradient( to right, rgba(244, 244, 244, 0) 25%, rgb(244, 244, 244) 70%);
|
||||
}
|
||||
|
||||
.classContainer:nth-child(even) .showMore {
|
||||
background: linear-gradient(to right, rgba(255, 255, 255, 0) 25%, rgb(255, 255, 255) 70%);
|
||||
background: linear-gradient( to right, rgba(255, 255, 255, 0) 25%, rgb(255, 255, 255) 70%);
|
||||
}
|
||||
|
||||
|
||||
@ -84,4 +85,14 @@
|
||||
.showMore a {
|
||||
color: rgb(54, 54, 69);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (orientation: landscape) {
|
||||
.classContainer {
|
||||
padding-left: 20vw;
|
||||
padding-right: 20vw;
|
||||
}
|
||||
.classContainer .curve {
|
||||
left: 2vh;
|
||||
}
|
||||
}
|
26
src/Logotype/index.tsx
Normal file
26
src/Logotype/index.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { Dispatch, SetStateAction } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { emptyQuery } from '../Navbar/utils';
|
||||
import { IFilterQuery } from '../types';
|
||||
import LogoImage from './logo.png';
|
||||
import './main.css'
|
||||
|
||||
const Logotype = ({
|
||||
setSearchQuery
|
||||
}: {
|
||||
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
|
||||
}) => {
|
||||
return (
|
||||
<Link
|
||||
to="/"
|
||||
onClick={() => {
|
||||
emptyQuery(setSearchQuery);
|
||||
}}
|
||||
>
|
||||
<img id="logo" src={LogoImage} alt="Логотип ЮФМЛ" />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logotype;
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
21
src/Logotype/main.css
Normal file
21
src/Logotype/main.css
Normal file
@ -0,0 +1,21 @@
|
||||
#logo {
|
||||
height: 6vh;
|
||||
width: 6vh;
|
||||
background-color: white;
|
||||
border-radius: 100%;
|
||||
padding: 1vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#loadingLogo #logo {
|
||||
height: calc(75vw - 2vh);
|
||||
width: calc(75vw - 2vh);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (orientation: landscape) {
|
||||
#loadingLogo #logo {
|
||||
width: 48vh;
|
||||
height: 48vh;
|
||||
}
|
||||
}
|
@ -7,16 +7,13 @@ import React, {
|
||||
useState
|
||||
} from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import './main.css';
|
||||
|
||||
import LogoImage from './logo.png';
|
||||
import FilterIcon from './filter.svg';
|
||||
import SearchIcon from './search.svg';
|
||||
import { IFilterQuery } from '../types';
|
||||
import { emptyQuery } from './utils';
|
||||
import { useFocus } from '../utils';
|
||||
import Logotype from '../Logotype';
|
||||
|
||||
const Navbar = ({
|
||||
setSearchQuery,
|
||||
@ -42,7 +39,6 @@ const Navbar = ({
|
||||
useEffect(() => {
|
||||
if (formRef.current) {
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
console.log(key, value);
|
||||
if (formRef.current.elements.namedItem(key)) {
|
||||
(formRef.current.elements.namedItem(
|
||||
key
|
||||
@ -143,14 +139,7 @@ const Navbar = ({
|
||||
animate={filtersCollapsed ? 'closed' : 'open'}
|
||||
>
|
||||
<nav>
|
||||
<Link
|
||||
to="/"
|
||||
onClick={() => {
|
||||
emptyQuery(setSearchQuery);
|
||||
}}
|
||||
>
|
||||
<img id="logo" src={LogoImage} alt="Логотип ЮФМЛ" />
|
||||
</Link>
|
||||
<Logotype setSearchQuery={setSearchQuery} />
|
||||
|
||||
<div id="spacing"></div>
|
||||
|
||||
@ -175,6 +164,11 @@ const Navbar = ({
|
||||
transition={transition}
|
||||
aria-label="Поиск"
|
||||
ref={searchInput}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSearchButton();
|
||||
}
|
||||
}}
|
||||
type="search"
|
||||
name="search"
|
||||
id="searchInput"
|
||||
|
@ -1,7 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.6 KiB |
@ -20,15 +20,6 @@
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#logo {
|
||||
height: 6vh;
|
||||
width: 6vh;
|
||||
background-color: white;
|
||||
border-radius: 100%;
|
||||
padding: 1vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#spacing {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { Dispatch, SetStateAction } from 'react';
|
||||
import { IFilterQuery } from '../types';
|
||||
|
||||
const queryIsEmpty = (q: IFilterQuery): boolean => {
|
||||
for (const [_, value] of Object.entries(q)) {
|
||||
for (const value of Object.values(q)) {
|
||||
if (value) {
|
||||
return false;
|
||||
}
|
||||
|
83
src/NothingFound/icon.svg
Normal file
83
src/NothingFound/icon.svg
Normal file
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 466.482 466.482" style="enable-background:new 0 0 466.482 466.482;" xml:space="preserve">
|
||||
<g>
|
||||
<path style="fill:#363645;" d="M233.824,148.642L126.673,119.94c-4.365-1.187-8.795,1.422-9.958,5.747
|
||||
c-1.162,4.333,1.414,8.795,5.747,9.957l107.151,28.702c0.707,0.187,1.414,0.276,2.105,0.276c3.593,0,6.877-2.398,7.844-6.023
|
||||
C240.741,154.259,238.164,149.796,233.824,148.642z" />
|
||||
<path style="fill:#363645;" d="M257.795,160.591c0.301-0.423,0.553-0.886,0.935-1.244
|
||||
C258.356,159.713,258.104,160.168,257.795,160.591z" />
|
||||
<path style="fill:#363645;" d="M256.933,161.867c-0.187,0.415-0.236,0.878-0.35,1.333
|
||||
C256.697,162.753,256.746,162.29,256.933,161.867z" />
|
||||
<path style="fill:#363645;" d="M261.42,157.665c-0.585,0.219-1.065,0.585-1.569,0.927
|
||||
C260.355,158.258,260.835,157.892,261.42,157.665z" />
|
||||
<path style="fill:#363645;" d="M256.616,398.588c-1.447-4.251,0.821-8.868,5.072-10.315l10.754-3.666V170.996l-5.462,1.894
|
||||
c-4.259,1.479-8.876-0.772-10.34-5.015c-0.333-0.967-0.431-1.951-0.398-2.918c0,0.089-0.049,0.171-0.049,0.26v220.293
|
||||
L86.729,342.265v-220.91l-3.967,1.301l-12.29,11.055v214.863c0,3.715,2.52,6.95,6.121,7.877l185.722,47.39
|
||||
c0.667,0.171,1.341,0.252,2.008,0.252C260.924,404.099,257.762,401.969,256.616,398.588z" />
|
||||
<path style="fill:#363645;" d="M264.306,404.099c0.902,0,1.788-0.146,2.634-0.439h-0.008
|
||||
C266.062,403.961,265.176,404.099,264.306,404.099z" />
|
||||
<path style="fill:#363645;" d="M74.032,108.918c0.553-0.382,1.105-0.772,1.74-1.008C75.121,108.145,74.585,108.528,74.032,108.918z
|
||||
" />
|
||||
<path style="fill:#363645;" d="M414.823,111.429c-0.098-0.171-0.268-0.268-0.374-0.423c0.797,1.187,1.357,2.544,1.439,4.048
|
||||
c0.187,3.609-2.04,6.917-5.454,8.096l-6.373,2.211l42.374,74.003l-116.19,40.245l-32.474-46.829
|
||||
c-2.561-3.674-7.633-4.609-11.315-2.04c-3.69,2.552-4.601,7.625-2.048,11.315l35.985,51.893c1.553,2.227,4.072,3.495,6.682,3.495
|
||||
c0.886,0,1.788-0.138,2.658-0.447l65.411-22.662v-11.421c0-4.495,3.642-8.129,8.129-8.129s8.129,3.633,8.129,8.129v5.788
|
||||
l49.617-17.184c2.284-0.797,4.105-2.561,4.95-4.836c0.845-2.26,0.642-4.788-0.561-6.893L414.823,111.429z" />
|
||||
<path style="fill:#363645;" d="M412.384,108.845c0.276,0.195,0.504,0.431,0.748,0.65
|
||||
C412.88,109.275,412.652,109.031,412.384,108.845z" />
|
||||
<path style="fill:#363645;" d="M409.775,107.617c0.244,0.065,0.455,0.203,0.691,0.285
|
||||
C410.222,107.82,410.019,107.682,409.775,107.617z" />
|
||||
<path style="fill:#363645;" d="M395.151,342.761l-122.717,41.846v11.356c0,2.512-1.162,4.885-3.146,6.422
|
||||
c-0.715,0.553-1.512,0.975-2.349,1.268l138.958-47.39c3.292-1.122,5.503-4.219,5.503-7.69V228.7l-16.257,5.633v108.427H395.151z" />
|
||||
<path style="fill:#363645;" d="M272.434,395.97v-11.356l-10.754,3.666c-4.251,1.447-6.519,6.064-5.072,10.315
|
||||
c1.154,3.382,4.308,5.511,7.69,5.511c0.87,0,1.756-0.138,2.626-0.439h0.008c0.837-0.284,1.634-0.707,2.349-1.268
|
||||
C271.272,400.847,272.434,398.474,272.434,395.97z" />
|
||||
<path style="fill:#363645;" d="M403.28,214.784c-4.487,0-8.129,3.633-8.129,8.129v11.421l16.257-5.633v-5.788
|
||||
C411.409,218.418,407.767,214.784,403.28,214.784z" />
|
||||
<path style="fill:#363645;" d="M413.132,109.503c0.488,0.439,0.935,0.935,1.317,1.504
|
||||
C414.067,110.438,413.636,109.95,413.132,109.503z" />
|
||||
<path style="fill:#363645;" d="M74.032,108.918c-0.285,0.195-0.626,0.284-0.878,0.512L2.696,172.808
|
||||
c-2.902,2.601-3.544,6.917-1.52,10.25c1.512,2.495,4.178,3.918,6.958,3.918c0.943,0,1.902-0.171,2.837-0.504l43.09-16.046
|
||||
c4.211-1.569,6.348-6.243,4.78-10.453c-1.569-4.219-6.251-6.332-10.453-4.788l-3.048,1.138l25.134-22.606v-18.249
|
||||
C70.455,112.706,71.927,110.381,74.032,108.918z" />
|
||||
<path style="fill:#363645;" d="M410.466,107.902c0.675,0.236,1.325,0.528,1.918,0.943
|
||||
C411.799,108.438,411.148,108.145,410.466,107.902z" />
|
||||
<path style="fill:#363645;" d="M86.713,115.469v5.893l129.293-42.431l162.571,38.058L261.64,157.543
|
||||
c-0.081,0.024-0.138,0.098-0.219,0.13c0.902-0.341,1.861-0.577,2.878-0.577c4.487,0,8.129,3.633,8.129,8.129v5.779l131.626-45.642
|
||||
l-3.349-5.844c-2.227-3.894-0.878-8.868,3.016-11.095c1.91-1.081,4.072-1.292,6.048-0.797c-0.057-0.016-0.098-0.049-0.154-0.065
|
||||
L217.485,62.585c-1.455-0.333-2.975-0.268-4.389,0.187L76.048,107.747c-0.106,0.033-0.179,0.122-0.285,0.163
|
||||
c0.886-0.333,1.821-0.569,2.821-0.569C83.079,107.341,86.713,110.974,86.713,115.469z" />
|
||||
<path style="fill:#363645;" d="M258.73,159.347c0.325-0.309,0.748-0.504,1.122-0.756
|
||||
C259.477,158.843,259.055,159.038,258.73,159.347z" />
|
||||
<path style="fill:#363645;" d="M256.226,164.956c0.016-0.618,0.203-1.179,0.358-1.756
|
||||
C256.429,163.786,256.25,164.346,256.226,164.956z" />
|
||||
<path style="fill:#363645;" d="M257.795,160.591c-0.301,0.423-0.65,0.805-0.862,1.276
|
||||
C257.153,161.396,257.502,161.014,257.795,160.591z" />
|
||||
<path style="fill:#363645;" d="M86.713,121.354v-5.893c0-4.495-3.642-8.129-8.129-8.129c-1,0-1.935,0.236-2.821,0.569
|
||||
c-0.634,0.236-1.187,0.626-1.74,1.008c-2.105,1.471-3.568,3.788-3.568,6.552v18.249l12.29-11.055L86.713,121.354z" />
|
||||
<path style="fill:#363645;" d="M264.306,157.088c-1.024,0-1.983,0.236-2.878,0.577c-0.585,0.228-1.073,0.585-1.569,0.927
|
||||
c-0.374,0.252-0.788,0.447-1.122,0.756c-0.382,0.366-0.634,0.821-0.935,1.244s-0.642,0.805-0.862,1.276
|
||||
c-0.187,0.423-0.236,0.886-0.35,1.333c-0.154,0.577-0.333,1.138-0.358,1.756c-0.033,0.967,0.057,1.951,0.398,2.918
|
||||
c1.471,4.243,6.088,6.495,10.34,5.015l5.462-1.894v-5.779C272.434,160.721,268.793,157.088,264.306,157.088z" />
|
||||
<path style="fill:#363645;" d="M403.727,108.414c-3.894,2.227-5.243,7.202-3.016,11.096l3.349,5.844l6.373-2.211
|
||||
c3.414-1.187,5.641-4.487,5.454-8.096c-0.081-1.504-0.642-2.853-1.439-4.048c-0.382-0.569-0.821-1.065-1.317-1.504
|
||||
c-0.244-0.228-0.48-0.463-0.748-0.65c-0.593-0.406-1.244-0.707-1.918-0.943c-0.236-0.081-0.439-0.219-0.691-0.285
|
||||
C407.799,107.113,405.637,107.324,403.727,108.414z" />
|
||||
</g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.1 KiB |
25
src/NothingFound/index.tsx
Normal file
25
src/NothingFound/index.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React, { useEffect, Dispatch, SetStateAction } from 'react';
|
||||
|
||||
import './main.css';
|
||||
import icon from './icon.svg';
|
||||
import { ILoadingState } from '../types';
|
||||
|
||||
const NothingFound = ({
|
||||
setLoading
|
||||
}: {
|
||||
setLoading?: Dispatch<SetStateAction<ILoadingState>>;
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
if (!setLoading) return;
|
||||
|
||||
setLoading({ error: '404', fetching: false });
|
||||
}, [setLoading]);
|
||||
return (
|
||||
<div id="nothingFound">
|
||||
<img src={icon} alt="" />
|
||||
<h1>Ничего не найдено</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NothingFound;
|
13
src/NothingFound/main.css
Normal file
13
src/NothingFound/main.css
Normal file
@ -0,0 +1,13 @@
|
||||
#nothingFound {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
height: 75vh;
|
||||
text-align: center;
|
||||
margin-top: -1vh;
|
||||
}
|
||||
|
||||
#nothingFound img {
|
||||
max-width: 40vmin;
|
||||
margin: 0 auto;
|
||||
}
|
@ -1,25 +1,26 @@
|
||||
import React, { useEffect, useState, Dispatch } from 'react';
|
||||
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 './main.css';
|
||||
import NothingFound from '../NothingFound';
|
||||
|
||||
const SubjectList = ({
|
||||
setLoading,
|
||||
searchQuery
|
||||
}: {
|
||||
setLoading: Dispatch<ILoadingState>;
|
||||
setLoading: Dispatch<SetStateAction<ILoadingState>>;
|
||||
searchQuery: IFilterQuery;
|
||||
}) => {
|
||||
const [data, setData] = useState<IData[]>([]);
|
||||
|
||||
const history = useHistory();
|
||||
const { push: historyPush } = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
if (queryIsEmpty(searchQuery)) {
|
||||
history.push('/');
|
||||
historyPush('/');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -28,22 +29,27 @@ const SubjectList = ({
|
||||
'https://upml-bank.dmitriy.icu/api/cards?' +
|
||||
new URLSearchParams({ ...searchQuery }).toString();
|
||||
fetch(fetchURL)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
if (!res.ok) throw res.statusText;
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
setData(data);
|
||||
setLoading({ fetching: false, error: '' });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
setLoading({ fetching: false, error: err });
|
||||
console.log(err);
|
||||
});
|
||||
}, [setLoading, searchQuery]);
|
||||
}, [setLoading, searchQuery, historyPush]);
|
||||
|
||||
return (
|
||||
<main className="subjectList">
|
||||
{data.map((el, index) => (
|
||||
<Card key={index} data={el} />
|
||||
))}
|
||||
{data.length ? (
|
||||
data.map((el, index) => <Card key={index} data={el} />)
|
||||
) : (
|
||||
<NothingFound />
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
@ -4,4 +4,10 @@
|
||||
gap: 1.5vh;
|
||||
padding: 2vh;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
@media (orientation: landscape) {
|
||||
.subjectList {
|
||||
padding: 2vh 20vw;
|
||||
}
|
||||
}
|
@ -21,23 +21,4 @@ body {
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
#name {
|
||||
background-color: rgb(54, 54, 69);
|
||||
padding: 2vh;
|
||||
color: #ffffff;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
height: 10vh;
|
||||
}
|
||||
|
||||
#name h1 {
|
||||
text-align: center;
|
||||
line-height: 6vh;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
</React.StrictMode>,
|
||||
document.getElementById("root")
|
||||
);
|
14
src/index.tsx
Normal file
14
src/index.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
Loading…
x
Reference in New Issue
Block a user