something new

This commit is contained in:
2020-06-11 05:08:58 +03:00
parent 54b36e7981
commit 6e40dfc101
22 changed files with 164 additions and 99 deletions
+2 -27
View File
@@ -1,10 +1,3 @@
import axios, { AxiosError } from 'axios';
export const http = axios.create({
baseURL: process.env.NODE_ENV === "development" ? 'http://localhost:8080/v1' : 'https://api.unmei.nix13.pw/v1',
withCredentials: true
});
const baseUrl = process.env.NODE_ENV === "development" ? 'http://localhost:8080/v1' : 'https://api.unmei.nix13.pw/v1'; const baseUrl = process.env.NODE_ENV === "development" ? 'http://localhost:8080/v1' : 'https://api.unmei.nix13.pw/v1';
export const response = async (url: string, method: string, body?: any) => { export const response = async (url: string, method: string, body?: any) => {
@@ -28,31 +21,11 @@ export const response = async (url: string, method: string, body?: any) => {
return Promise.resolve(json.data); return Promise.resolve(json.data);
} }
http.interceptors.response.use(undefined, (error: AxiosError) => {
if(error.response) {
if(error.response?.status === 429) {
return new Promise(res => {
setTimeout(res.bind(null, http.request(error.config)), 1000)
});
} else {
if(error.response.data.error_data)
return Promise.reject(error.response.data.error_data);
}
} else {
console.error(error);
}
});
const get = (url: string, data?: object) => response(url, 'GET', data); const get = (url: string, data?: object) => response(url, 'GET', data);
const post = (url: string, data?: object) => response(url, 'POST', data); const post = (url: string, data?: object) => response(url, 'POST', data);
const put = (url: string, data?: object) => response(url, 'PUT', data); const put = (url: string, data?: object) => response(url, 'PUT', data);
const del = (url: string, data?: object) => response(url, 'DELETE', data); const del = (url: string, data?: object) => response(url, 'DELETE', data);
// const get = (url: string, data?: object) => http.get(url, data).then(r => r?.data?.data);
// const post = (url: string, data?: object) => http.post(url, data).then(r => r?.data.data);
// const put = (url: string, data?: object) => http.put(url, data).then(r => r?.data.data);
// const del = (url: string, data?: object) => http.delete(url, data).then(r => r?.data.data);
const TranslateStatus: { [id: string]: string } = { const TranslateStatus: { [id: string]: string } = {
planned: 'Запланировано', planned: 'Запланировано',
completed: 'Пройдено', completed: 'Пройдено',
@@ -61,4 +34,6 @@ const TranslateStatus: { [id: string]: string } = {
deferred: 'Отложено' deferred: 'Отложено'
}; };
export const getVersion = () => get('version');
export { get, post, put, del, TranslateStatus }; export { get, post, put, del, TranslateStatus };
+3 -8
View File
@@ -1,9 +1,4 @@
import {http} from "./api"; import {get} from "./api";
export const fetchChar = (id: number) => export const fetchChar = (id: number) => get(`characters/${id}`);
http.get(`characters/${id}`) export const fetchCharNovels = (id: number) => get(`characters/${id}/novels`);
.then(res => res.data.data);
export const fetchCharNovels = (id: number) =>
http.get(`characters/${id}/novels`)
.then(res => res.data.data);
+1
View File
@@ -5,6 +5,7 @@ const errors: { [key: number]: string } = {
5: 'Аккаунт уже активирован! Вы будете перенаправлены на главную через 3 секунды.', 5: 'Аккаунт уже активирован! Вы будете перенаправлены на главную через 3 секунды.',
6: 'Токен истек!', 6: 'Токен истек!',
8: 'Новый и старый пароли совпадают!', 8: 'Новый и старый пароли совпадают!',
9: 'Необходима проверка ReCaptcha',
100: 'Не найдено!' 100: 'Не найдено!'
}; };
+3
View File
@@ -3,9 +3,12 @@ import { get, post, put, del } from "./api";
export const fetchNovels = (orderBy?: string) => get(orderBy ? `novels?sort=${orderBy}` : 'novels'); export const fetchNovels = (orderBy?: string) => get(orderBy ? `novels?sort=${orderBy}` : 'novels');
export const createNovel = (novel: NovelType) => post('novels', { ...novel }); export const createNovel = (novel: NovelType) => post('novels', { ...novel });
export const fetchNovel = (id: number) => get(`novels/${id}`); export const fetchNovel = (id: number) => get(`novels/${id}`);
export const updateNovel = (id: number, novel: NovelType) => put(`novels/${id}`, { ...novel }); export const updateNovel = (id: number, novel: NovelType) => put(`novels/${id}`, { ...novel });
export const fetchNovelCharacters = (id: number) => get(`novels/${id}/characters`); export const fetchNovelCharacters = (id: number) => get(`novels/${id}/characters`);
export const fetchNovelGenres = (id: number) => get(`novels/${id}/genres`); export const fetchNovelGenres = (id: number) => get(`novels/${id}/genres`);
export const fetchNovelComments = (id: number, offset=0, count=5) => get(`novels/${id}/comments?offset=${offset}&count=${count}`); export const fetchNovelComments = (id: number, offset=0, count=5) => get(`novels/${id}/comments?offset=${offset}&count=${count}`);
+1 -1
View File
@@ -9,7 +9,7 @@ export const fetchCurrentUser = () => get('users/current');
export const fetchUserNovels = (id: number) => get(`users/${id}/novels`); export const fetchUserNovels = (id: number) => get(`users/${id}/novels`);
export const registerUser = (login: string, password: string, email: string, recaptcha: string) => post('auth/register', { login, password, email, recaptcha }); export const registerUser = (login: string, password: string, email: string, recaptcha: string) => post('auth/register', { login, password, email, recaptcha });
export const login = (login: string, password: string) => post('auth/login', { login, password }); export const login = (login: string, password: string, recaptcha: string) => post('auth/login', { login, password, recaptcha });
export const userLogout = () => post('auth/logout'); export const userLogout = () => post('auth/logout');
export const activateAccount = (token: string) => post('auth/activate', { token }); export const activateAccount = (token: string) => post('auth/activate', { token });
@@ -4,7 +4,7 @@ import {fetchUser, updateUser} from "../../../api/users";
import Loading from "../../Loading"; import Loading from "../../Loading";
import './APUser.sass'; import './APUser.sass';
import {addNotification} from "../../../store/actions"; import {addNotification} from "../../../store/actions";
import Notification from "../../Notification/Notification"; import NotificationMessage from "../../Notifications/NotificationMessage";
type Props = { type Props = {
userId: number userId: number
@@ -48,9 +48,9 @@ class ModifyUser extends React.Component<Props, State> {
} }
updateUser(user).then(() => { updateUser(user).then(() => {
const notification = ( const notification = (
<Notification level={"success"}> <NotificationMessage level={"success"}>
Пользователь успешно изменен! Пользователь успешно изменен!
</Notification> </NotificationMessage>
) )
addNotification(notification) addNotification(notification)
}); });
+3
View File
@@ -26,6 +26,7 @@ import AdminPanel from "../AdminPanel/AdminPanel";
import PasswordRestoreGenerate from "../PasswordRestore/PasswordRestoreGenerate"; import PasswordRestoreGenerate from "../PasswordRestore/PasswordRestoreGenerate";
import PasswordRestore from "../PasswordRestore/PasswordRestore"; import PasswordRestore from "../PasswordRestore/PasswordRestore";
import Users from "../Users/Users"; import Users from "../Users/Users";
import {getVersion} from "../../api/api";
type Props = { type Props = {
notifications: React.ReactNode[] notifications: React.ReactNode[]
@@ -60,6 +61,8 @@ class App extends React.Component<Props, State> {
parser.registerTag('spoiler', SpoilerTag); parser.registerTag('spoiler', SpoilerTag);
parser.registerTag('color', ColorTag); parser.registerTag('color', ColorTag);
getVersion().then(res => console.debug(`Current back-end version: ${res.version}`))
} }
showLoginModal = () => this.setState({ loginModalVisible: true, registerModalVisible: false }); showLoginModal = () => this.setState({ loginModalVisible: true, registerModalVisible: false });
+13 -10
View File
@@ -5,32 +5,35 @@ import './Character.sass'
import parser from 'bbcode-to-react'; import parser from 'bbcode-to-react';
import NovelItem from "../NovelItem/NovelItem"; import NovelItem from "../NovelItem/NovelItem";
import Loading from "../Loading"; import Loading from "../Loading";
import NotFoundError from "../NotFoundError";
type Props = { type Props = {
match: { params: { id: number } } match: { params: { charId: number } }
} }
type State = { type State = {
char: CharacterType | null char: CharacterType | null
novels: NovelType[] | null novels: NovelType[]
errorCode: number
}; };
class Character extends React.Component<Props, State> { class Character extends React.Component<Props, State> {
state: State = { state: State = {
char: null, novels: null char: null, novels: [], errorCode: 0
}; };
componentDidMount = () => { componentDidMount = () => {
const { id } = this.props.match.params; const { charId } = this.props.match.params;
fetchChar(id).then(char => { fetchChar(charId).then(char => {
this.setState({ char }); this.setState({ char });
document.title = char.localized_name ? `${char.original_name} / ${char.localized_name}` : char.original_name; document.title = char.localized_name ? `${char.original_name} / ${char.localized_name}` : char.original_name;
}); }).catch(r => this.setState({ errorCode: r.code }));
fetchCharNovels(id).then(novels => this.setState({ novels })); fetchCharNovels(charId).then(novels => this.setState({ novels }));
}; };
render() { render() {
const { char, novels } = this.state; const { char, novels, errorCode } = this.state;
if(errorCode === 100) return <NotFoundError/>;
if(!char) return <Loading/>; if(!char) return <Loading/>;
return ( return (
<div className={'Character'}> <div className={'Character'}>
@@ -45,9 +48,9 @@ class Character extends React.Component<Props, State> {
<pre className="Character__Info_Description">{parser.toReact(char.description)}</pre> <pre className="Character__Info_Description">{parser.toReact(char.description)}</pre>
</div> </div>
</div> </div>
{novels && {novels.length > 0 &&
<div className="Character__Novels"> <div className="Character__Novels">
{novels.map(novel => <NovelItem {...novel} key={novel.id} />)} {novels.map(novel => <NovelItem {...novel} key={novel.id} viewType='grid' />)}
</div>} </div>}
</div> </div>
); );
+3 -3
View File
@@ -7,7 +7,7 @@ import Loading from "../Loading";
import {fetchUser} from "../../api/users"; import {fetchUser} from "../../api/users";
import {connect} from "react-redux"; import {connect} from "react-redux";
import {addNotification} from "../../store/actions"; import {addNotification} from "../../store/actions";
import Notification from "../Notification/Notification"; import NotificationMessage from "../Notifications/NotificationMessage";
type Props = { type Props = {
comments: CommentType[] comments: CommentType[]
@@ -50,9 +50,9 @@ class Comments extends React.Component<Props, State> {
if(commentText === '') { if(commentText === '') {
const notification = ( const notification = (
<Notification level={"error"}> <NotificationMessage level={"error"}>
Пустой комментарий Пустой комментарий
</Notification> </NotificationMessage>
); );
addNotification(notification); addNotification(notification);
} else { } else {
+23 -11
View File
@@ -6,9 +6,8 @@ import {addNotification, setUser} from "../../store/actions";
import './LoginModal.sass' import './LoginModal.sass'
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import errors from "../../api/errors"; import errors from "../../api/errors";
import Notification from "../Notification/Notification"; import NotificationMessage from "../Notifications/NotificationMessage";
// @ts-ignore import Recaptcha from '../Recaptcha/Recaptcha'
import Recaptcha from 'react-recaptcha'
type Props = { type Props = {
setUser: (user: UserType) => void setUser: (user: UserType) => void
@@ -21,39 +20,46 @@ type State = {
error: string error: string
login: string login: string
password: string password: string
recaptcha: string
recaptchaNeeded: boolean
}; };
class LoginModal extends React.Component<Props, State> { class LoginModal extends React.Component<Props, State> {
state: State = { state: State = {
error: '', login: '', password: '' error: '', login: '', password: '', recaptcha: '', recaptchaNeeded: false
}; };
loginAndFetchToken = (event: FormEvent) => { loginAndFetchToken = (event: FormEvent) => {
event.preventDefault(); event.preventDefault();
const { setUser, hideModal, addNotification } = this.props; const { setUser, hideModal, addNotification } = this.props;
login(this.state.login, this.state.password).then(user => { const { login: log, password, recaptcha } = this.state;
login(log, password, recaptcha).then(user => {
if(!user) return; if(!user) return;
setUser(user); setUser(user);
const successful = ( const successful = (
<Notification level={"success"}> <NotificationMessage level={"success"}>
Ты успешно вошел! Ты успешно вошел!
</Notification> </NotificationMessage>
); );
addNotification(successful); addNotification(successful);
hideModal(); hideModal();
}) })
.catch((r: ApiError) => this.setState({ error: errors[r.code] })); .catch((r: ApiError) => {
this.setState({ error: errors[r.code] });
if(r.code === 9) this.setState({ recaptchaNeeded: true });
});
}; };
handlePasswordChange = (event: ChangeEvent) => this.setState({ password: (event.target as HTMLInputElement).value }); handlePasswordChange = (event: ChangeEvent) => this.setState({ password: (event.target as HTMLInputElement).value });
handleLoginChange = (event: ChangeEvent) => this.setState({ login: (event.target as HTMLInputElement).value }); handleLoginChange = (event: ChangeEvent) => this.setState({ login: (event.target as HTMLInputElement).value });
render() { render() {
const { login, password, error, recaptchaNeeded } = this.state;
return ( return (
<Modal onCloseRequest={this.props.hideModal} className={'LoginModal'}> <Modal onCloseRequest={this.props.hideModal} className={'LoginModal'}>
<h1>Авторизация</h1> <h1>Авторизация</h1>
{this.state.error && <div className="LoginModal__Error">{this.state.error}</div>} {error && <div className="LoginModal__Error">{error}</div>}
<form className={'LoginModal__Form'} onSubmit={this.loginAndFetchToken}> <form className={'LoginModal__Form'} onSubmit={this.loginAndFetchToken}>
<input <input
type="text" type="text"
@@ -61,7 +67,7 @@ class LoginModal extends React.Component<Props, State> {
placeholder={'Логин'} placeholder={'Логин'}
className={'LoginModal__Field'} className={'LoginModal__Field'}
onChange={this.handleLoginChange} onChange={this.handleLoginChange}
value={this.state.login} value={login}
/> />
<input <input
type="password" type="password"
@@ -69,8 +75,14 @@ class LoginModal extends React.Component<Props, State> {
placeholder={'Пароль'} placeholder={'Пароль'}
className={'LoginModal__Field'} className={'LoginModal__Field'}
onChange={this.handlePasswordChange} onChange={this.handlePasswordChange}
value={this.state.password} value={password}
/> />
{recaptchaNeeded &&
<Recaptcha
onVerify={(res: any) => this.setState({ recaptcha: res })}
theme={'dark'}
sitekey={'6LfnDsMUAAAAAEDfD5ubCdFQbNUnKxxJlMWeUMzN'}
/>}
<button className={'LoginModal__Submit'} type={"submit"}>Войти</button> <button className={'LoginModal__Submit'} type={"submit"}>Войти</button>
</form> </form>
<div className="LoginModal__Links"> <div className="LoginModal__Links">
+3 -3
View File
@@ -5,7 +5,7 @@ import './RegisterModal.sass'
import {registerUser} from "../../api/users"; import {registerUser} from "../../api/users";
import {connect} from "react-redux"; import {connect} from "react-redux";
import {addNotification} from "../../store/actions"; import {addNotification} from "../../store/actions";
import Notification from "../Notification/Notification"; import NotificationMessage from "../Notifications/NotificationMessage";
type Props = { type Props = {
addNotification: (notification: React.ReactNode) => void addNotification: (notification: React.ReactNode) => void
@@ -50,10 +50,10 @@ class RegisterModal extends React.Component<Props, State> {
else { else {
registerUser(login, password1, email, recaptcha).then(() => { registerUser(login, password1, email, recaptcha).then(() => {
const notification = ( const notification = (
<Notification level={"success"}> <NotificationMessage level={"success"}>
Аккаунт успешно создан! Аккаунт успешно создан!
Проверьте почту <strong>{email}</strong>. На неё была отправлена ссылка для подтверждения аккаунта. Проверьте почту <strong>{email}</strong>. На неё была отправлена ссылка для подтверждения аккаунта.
</Notification> </NotificationMessage>
); );
const { addNotification, hideModal } = this.props; const { addNotification, hideModal } = this.props;
addNotification(notification); addNotification(notification);
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import './Notification.sass' import './NotificationMessage.sass'
import {capitalize} from "../../utils"; import {capitalize} from "../../utils";
type Props = { type Props = {
@@ -12,7 +12,7 @@ type State = {
paused: boolean paused: boolean
}; };
class Notification extends React.Component<Props, State> { class NotificationMessage extends React.Component<Props, State> {
timer: NodeJS.Timeout | undefined; timer: NodeJS.Timeout | undefined;
state = { state = {
@@ -68,4 +68,4 @@ class Notification extends React.Component<Props, State> {
} }
} }
export default Notification; export default NotificationMessage;
+20
View File
@@ -12,9 +12,29 @@
width: 100% width: 100%
display: flex display: flex
&.Grid
flex-direction: column flex-direction: column
align-items: center align-items: center
&.Table
width: 100%
padding: .2rem
box-sizing: border-box
flex-direction: row
justify-content: space-between
align-items: center
&:not(:last-child)
margin: 0 0 .5rem
&:last-child
margin: 0
.NovelItem__Status
white-space: nowrap
&__Image &__Image
border-radius: $borderRadius $borderRadius 0 0 border-radius: $borderRadius $borderRadius 0 0
background-size: cover background-size: cover
+16 -2
View File
@@ -2,18 +2,22 @@ import React from 'react';
import { TranslateStatus } from '../../api/api'; import { TranslateStatus } from '../../api/api';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import './NovelItem.sass'; import './NovelItem.sass';
import {capitalize, generateClassName} from "../../utils";
type Props = { type Props = {
id: number id: number
original_name: string original_name: string
viewType: string
key?: number
localized_name?: string localized_name?: string
image?: string image?: string
status?: string status?: string
mark?: number mark?: number
} }
const NovelItem = ({ id, original_name, localized_name, image, status, mark }: Props) => ( const NovelItem = ({ id, original_name, localized_name, image, status, mark, viewType, key }: Props) => (
<Link to={`/novels/${id}`} className="NovelItem"> <Link to={`/novels/${id}`} className={generateClassName("NovelItem", capitalize(viewType) || 'Grid')}>
{viewType === 'grid' && (<>
<div className='NovelItem__Image' style={{ backgroundImage: `url(${image || '/static/img/no-image.png'})` }}/> <div className='NovelItem__Image' style={{ backgroundImage: `url(${image || '/static/img/no-image.png'})` }}/>
<div className="NovelItem__Title"> <div className="NovelItem__Title">
{localized_name || original_name} {localized_name || original_name}
@@ -21,6 +25,16 @@ const NovelItem = ({ id, original_name, localized_name, image, status, mark }: P
{status && <div className="NovelItem__Status"> {status && <div className="NovelItem__Status">
{TranslateStatus[status]} {mark !== undefined && mark > 0 && mark} {TranslateStatus[status]} {mark !== undefined && mark > 0 && mark}
</div>} </div>}
</>)}
{viewType === 'table' && (<>
<div className="NovelItem__Component">{key || ''}</div>
<div className="NovelItem__Title">
{localized_name || original_name}
</div>
{status && <div className="NovelItem__Status">
{TranslateStatus[status]} {mark !== undefined && mark > 0 && mark}
</div>}
</>)}
</Link> </Link>
); );
+1 -1
View File
@@ -52,7 +52,7 @@ class Novels extends React.Component<{}, State> {
return ( return (
<div className={'Novels'}> <div className={'Novels'}>
<div className="Novels__List"> <div className="Novels__List">
{novels.map((novel: NovelType) => <NovelItem {...novel} key={novel.id} />)} {novels.map((novel: NovelType) => <NovelItem {...novel} key={novel.id} viewType={'grid'} />)}
</div> </div>
{/*<div className="Novels__Filters"> {/*<div className="Novels__Filters">
<div className="Novels__Filters_Genre"> <div className="Novels__Filters_Genre">
+15 -1
View File
@@ -9,9 +9,17 @@
font-weight: 600 font-weight: 600
text-align: center text-align: center
.svg-inline--fa
margin: 0 .5rem
cursor: pointer
&:hover
color: var(--link-color)
&__List &__List
display: flex display: flex
flex-direction: column flex-direction: column
width: 100%
&:not(:last-child) &:not(:last-child)
border-bottom: var(--link-color) 1px solid border-bottom: var(--link-color) 1px solid
@@ -28,6 +36,12 @@
&_Novels &_Novels
display: flex display: flex
justify-content: center
&.Grid
flex-direction: row flex-direction: row
flex-wrap: wrap flex-wrap: wrap
justify-content: center
&.Table
flex-direction: column
width: 100%
+29 -9
View File
@@ -3,7 +3,9 @@ import './UserNovels.sass';
import { fetchUserNovels, fetchUser } from '../../../api/users'; import { fetchUserNovels, fetchUser } from '../../../api/users';
import Loading from '../../Loading'; import Loading from '../../Loading';
import NovelItem from '../../NovelItem/NovelItem'; import NovelItem from '../../NovelItem/NovelItem';
import {getRandomInt} from "../../../utils"; import {capitalize, generateClassName, getRandomInt} from "../../../utils";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faAlignJustify, faThLarge} from "@fortawesome/free-solid-svg-icons";
type Props = { type Props = {
match: { params: { userId: number } } match: { params: { userId: number } }
@@ -12,11 +14,12 @@ type Props = {
type State = { type State = {
novels: NovelType[] novels: NovelType[]
user: UserType | null user: UserType | null
viewType: string
}; };
class UserNovels extends React.Component<Props, State> { class UserNovels extends React.Component<Props, State> {
state: State = { state: State = {
novels: [], user: null novels: [], user: null, viewType: 'grid'
}; };
getRandomNovel = () => { getRandomNovel = () => {
@@ -34,10 +37,25 @@ class UserNovels extends React.Component<Props, State> {
.then(novels => this.setState({ novels })); .then(novels => this.setState({ novels }));
fetchUser(userId) fetchUser(userId)
.then(user => this.setState({ user })); .then(user => this.setState({ user }));
const viewType = localStorage.getItem('viewType');
if(viewType) {
if(viewType !== 'grid' && viewType !== 'table') {
console.warn('Тип был неизвестен... Сбрасываю...');
localStorage.setItem('viewType', 'grid')
} else {
this.setState({ viewType });
}
}
};
changeViewType = (type: 'grid' | 'table') => {
this.setState({ viewType: type });
localStorage.setItem('viewType', type);
} }
render() { render() {
const { novels, user } = this.state; const { novels, user, viewType } = this.state;
if(!novels || !user) return <Loading/>; if(!novels || !user) return <Loading/>;
const planned = novels.filter(n => n.status === 'planned'); const planned = novels.filter(n => n.status === 'planned');
@@ -47,24 +65,26 @@ class UserNovels extends React.Component<Props, State> {
<div className='UserNovels'> <div className='UserNovels'>
<div className="UserNovels__Title"> <div className="UserNovels__Title">
Новеллы пользователя {user.username} Новеллы пользователя {user.username}
<FontAwesomeIcon icon={faThLarge} onClick={() => this.changeViewType('grid')}/>
<FontAwesomeIcon icon={faAlignJustify} onClick={() => this.changeViewType('table')}/>
</div> </div>
{planned.length > 0 &&<div className="UserNovels__List"> {planned.length > 0 && <div className={'UserNovels__List'}>
<div className="UserNovels__List_Title"> <div className="UserNovels__List_Title">
<div style={{ marginRight: '1rem' }}>Запланированные</div> <div style={{ marginRight: '1rem' }}>Запланированные</div>
<button onClick={this.getRandomNovel}>Рандомная новелла</button> <button onClick={this.getRandomNovel}>Рандомная новелла</button>
</div> </div>
<div className="UserNovels__List_Novels"> <div className={generateClassName("UserNovels__List_Novels", capitalize(viewType) || 'Grid')}>
{planned.map(novel => <NovelItem {...novel} key={novel.id}/>)} {planned.map(novel => <NovelItem {...novel} key={novel.id} viewType={viewType}/>)}
</div> </div>
</div>} </div>}
{completed.length > 0 &&<div className="UserNovels__List"> {completed.length > 0 && <div className={'UserNovels__List'}>
<div className="UserNovels__List_Title"> <div className="UserNovels__List_Title">
Пройденные Пройденные
</div> </div>
<div className="UserNovels__List_Novels"> <div className={generateClassName("UserNovels__List_Novels", capitalize(viewType) || 'Grid')}>
{completed.map(novel => <NovelItem {...novel} key={novel.id}/>)} {completed.map(novel => <NovelItem {...novel} key={novel.id} viewType={viewType}/>)}
</div> </div>
</div>} </div>}
</div> </div>
+1 -1
View File
@@ -91,7 +91,7 @@ class User extends React.Component<Props, State> {
</div> </div>
<div className="User__List_Items"> <div className="User__List_Items">
{novels.length > 0 ? {novels.length > 0 ?
novels.slice(0, 4).map((novel) => <NovelItem {...novel} key={novel.id} /> novels.slice(0, 4).map((novel) => <NovelItem {...novel} key={novel.id} viewType={'grid'} />
) : ( ) : (
<div>У этого пользователя нет новелл!</div> <div>У этого пользователя нет новелл!</div>
)} )}
+1 -1
View File
@@ -5,7 +5,7 @@ const initialState: React.ReactNode[] = [];
type Action = { type Action = {
type: string type: string
notification: React.ReactNode[] notification: React.ReactNode
} }
export default (state = initialState, action: Action) => { export default (state = initialState, action: Action) => {
+6 -1
View File
@@ -15,4 +15,9 @@ const hasPermission = (user: UserType, permission: string) => {
const hasAccessToAdminPanel = (user: UserType) => user.is_superuser || user.group.is_superuser || hasPermission(user, 'admin_panel') const hasAccessToAdminPanel = (user: UserType) => user.is_superuser || user.group.is_superuser || hasPermission(user, 'admin_panel')
export { capitalize, getRandomInt, hasPermission, hasAccessToAdminPanel }; const generateClassName = (baseClassName: string, ...classNames: string[]) => {
if(classNames.length === 0) return baseClassName;
else return `${baseClassName} ${classNames.join(" ")}`
}
export { capitalize, getRandomInt, hasPermission, hasAccessToAdminPanel, generateClassName };