v1.1.2; bugfixes; admin panel improvement; small folder re-struct
This commit is contained in:
+16
-17
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.32",
|
"@fortawesome/fontawesome-svg-core": "1.2.34",
|
||||||
"@fortawesome/free-brands-svg-icons": "5.15.1",
|
"@fortawesome/free-brands-svg-icons": "5.15.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.1",
|
"@fortawesome/free-solid-svg-icons": "5.15.2",
|
||||||
"@fortawesome/react-fontawesome": "0.1.13",
|
"@fortawesome/react-fontawesome": "0.1.14",
|
||||||
"@sentry/react": "5.28.0",
|
"@sentry/react": "6.2.0",
|
||||||
"bbcode-to-react": "0.2.9",
|
"bbcode-to-react": "0.2.9",
|
||||||
"eruda": "2.4.1",
|
"eruda": "2.4.1",
|
||||||
"eruda-dom": "2.0.0",
|
"eruda-dom": "2.0.0",
|
||||||
@@ -15,23 +14,23 @@
|
|||||||
"eruda-memory": "2.0.0",
|
"eruda-memory": "2.0.0",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-dom": "17.0.1",
|
"react-dom": "17.0.1",
|
||||||
"react-loader-spinner": "3.1.14",
|
"react-loader-spinner": "4.0.0",
|
||||||
"react-redux": "7.2.2",
|
"react-redux": "7.2.2",
|
||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
"react-snowfall": "^1.0.2",
|
"react-snowfall": "1.0.2",
|
||||||
"redux": "4.0.5"
|
"redux": "4.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "14.14.9",
|
"@types/node": "14.14.31",
|
||||||
"@types/react": "17.0.0",
|
"@types/react": "17.0.2",
|
||||||
"@types/react-dom": "17.0.0",
|
"@types/react-dom": "17.0.1",
|
||||||
"@types/react-redux": "7.1.11",
|
"@types/react-redux": "7.1.16",
|
||||||
"@types/react-router-dom": "5.1.6",
|
"@types/react-router-dom": "5.1.7",
|
||||||
"@types/react-transition-group": "4.4.0",
|
"@types/react-transition-group": "4.4.1",
|
||||||
"react-scripts": "3.4.4",
|
"react-scripts": "4.0.2",
|
||||||
"redux-devtools-extension": "2.13.8",
|
"redux-devtools-extension": "2.13.8",
|
||||||
"sass": "1.29.0",
|
"sass": "1.32.8",
|
||||||
"typescript": "4.1.2"
|
"typescript": "4.1.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
const baseUrl = process.env.NODE_ENV === "development" ? 'http://localhost:8080/v1' : 'https://api.unmei.space/v1';
|
const baseUrl = process.env.NODE_ENV === "development" ? 'http://localhost:8080/v1' : 'https://api.unmei.space/v1';
|
||||||
|
|
||||||
|
// function loadRequestFromCache<T>(url: string): T | null {
|
||||||
|
// let cached = sessionStorage.getItem(url);
|
||||||
|
// if(!cached) return null;
|
||||||
|
// return JSON.parse(cached) as T;
|
||||||
|
// }
|
||||||
|
|
||||||
async function request<T>(url: string, method: string, body?: string | FormData): Promise<T> {
|
async function request<T>(url: string, method: string, body?: string | FormData): Promise<T> {
|
||||||
let bUrl = baseUrl;
|
let bUrl = baseUrl;
|
||||||
if(baseUrl.endsWith('/'))
|
if(baseUrl.endsWith('/'))
|
||||||
@@ -7,6 +13,12 @@ async function request<T>(url: string, method: string, body?: string | FormData)
|
|||||||
if(url.startsWith('/'))
|
if(url.startsWith('/'))
|
||||||
url = url.slice(1, baseUrl.length);
|
url = url.slice(1, baseUrl.length);
|
||||||
|
|
||||||
|
// const cached = method.toLowerCase() === 'get' ? loadRequestFromCache<T>(url) : null;
|
||||||
|
// if(cached) {
|
||||||
|
// console.debug(`${baseUrl}/${url} loaded from session cache!`);
|
||||||
|
// return Promise.resolve(cached);
|
||||||
|
// }
|
||||||
|
|
||||||
const res = await fetch(`${bUrl}/${url}`, {
|
const res = await fetch(`${bUrl}/${url}`, {
|
||||||
method, body,
|
method, body,
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
@@ -28,6 +40,7 @@ async function request<T>(url: string, method: string, body?: string | FormData)
|
|||||||
if(json?.error) return Promise.reject(json.error_data);
|
if(json?.error) return Promise.reject(json.error_data);
|
||||||
if(!res.ok) return Promise.reject(res.statusText);
|
if(!res.ok) return Promise.reject(res.statusText);
|
||||||
if(!json.data) return Promise.reject(null);
|
if(!json.data) return Promise.reject(null);
|
||||||
|
sessionStorage.setItem(url, JSON.stringify(json.data));
|
||||||
return Promise.resolve(json.data);
|
return Promise.resolve(json.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
import { del, get, put } from './api';
|
import { del, get, post, put } from './api';
|
||||||
|
|
||||||
export const fetchNews = () => get<PostType[]>('news');
|
export const fetchNews = () => get<PostType[]>('news');
|
||||||
|
export const createPost = (p: PostType) => post<PostType>('news', p);
|
||||||
|
|
||||||
export const fetchPost = (id: number) => get<PostType>(`news/${id}`);
|
export const fetchPost = (id: number) => get<PostType>(`news/${id}`);
|
||||||
export const updatePost = (post: PostType) => put<PostType>(`news/${post.id}`, post);
|
export const updatePost = (post: PostType) => put<PostType>(`news/${post.id}`, post);
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { Link } from "react-router-dom";
|
|||||||
import { hasAccessToAdminPanel } from "../../utils";
|
import { hasAccessToAdminPanel } from "../../utils";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faBars } from '@fortawesome/free-solid-svg-icons';
|
import { faBars } from '@fortawesome/free-solid-svg-icons';
|
||||||
import RegisterModal from "../Modals/RegisterModal";
|
import RegisterModal from "../../pages/Modals/RegisterModal";
|
||||||
import LoginModal from "../Modals/LoginModal";
|
import LoginModal from "../../pages/Modals/LoginModal";
|
||||||
import { logout } from "../../store/ducks/currentUser";
|
import { logout } from "../../store/ducks/currentUser";
|
||||||
import { setModal } from "../../store/ducks/modal";
|
import { setModal } from "../../store/ducks/modal";
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import { activateAccount } from "../api/users";
|
import { activateAccount } from "../api/users";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import errors from "../api/errors";
|
import errors from "../api/errors";
|
||||||
import Loading from "../ui/Loading";
|
import Loading from "../components/Loading";
|
||||||
import { addNotification } from "../store/ducks/notifications";
|
import { addNotification } from "../store/ducks/notifications";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -1,51 +1,28 @@
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import NotFoundError from "../NotFoundError";
|
import NotFoundError from "../../components/NotFoundError";
|
||||||
import { hasAccessToAdminPanel } from "../../utils";
|
import { hasAccessToAdminPanel } from "../../utils";
|
||||||
import './AdminPanel.sass';
|
import './AdminPanel.sass';
|
||||||
import { Link } from "react-router-dom";
|
import { Link, Route, Switch } from "react-router-dom";
|
||||||
import AdminPanelUsers from "./Users/AdminPanelUsers";
|
|
||||||
import AdminPanelNovels from "./Novels/AdminPanelNovels";
|
|
||||||
import ModifyUser from "./Users/ModifyUser";
|
|
||||||
import APNews from "./News/APNews";
|
import APNews from "./News/APNews";
|
||||||
import APModifyPost from "./News/APModifyPost";
|
import APModifyPost from "./News/APModifyPost";
|
||||||
|
import APAddPost from "./News/APAddPost";
|
||||||
|
import APNovels from "./Novels/APNovels";
|
||||||
|
import APUsers from "./Users/APUsers";
|
||||||
|
import APModifyUser from "./Users/APModifyUser";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: UserType
|
user: UserType
|
||||||
match: { path: string }
|
match: { path: string }
|
||||||
location: { pathname: string }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {}
|
type State = {}
|
||||||
|
|
||||||
class AdminPanel extends React.Component<Props, State> {
|
class AdminPanel extends React.Component<Props, State> {
|
||||||
render() {
|
render() {
|
||||||
const { user, match: { path }, location: { pathname } } = this.props;
|
const { user, match: { path } } = this.props;
|
||||||
if(!user || !user.group) return <NotFoundError/>;
|
if(!user || !user.group) return <NotFoundError/>;
|
||||||
if(!hasAccessToAdminPanel(user)) return <NotFoundError/>;
|
if(!hasAccessToAdminPanel(user)) return <NotFoundError/>;
|
||||||
const subPath = pathname.slice(path.length + 1);
|
|
||||||
let component;
|
|
||||||
switch(subPath) {
|
|
||||||
case 'users':
|
|
||||||
component = <AdminPanelUsers path={`${path}/${subPath}`}/>;
|
|
||||||
break;
|
|
||||||
case 'novels':
|
|
||||||
component = <AdminPanelNovels/>;
|
|
||||||
break;
|
|
||||||
case 'news':
|
|
||||||
component = <APNews path={`${path}/${subPath}`}/>;
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
const users = /users\/(\d+)/;
|
|
||||||
const news = /news\/(\d+)/;
|
|
||||||
|
|
||||||
const userMatch = subPath.match(users);
|
|
||||||
const newsMatch = subPath.match(news);
|
|
||||||
|
|
||||||
if(userMatch) component = <ModifyUser userId={parseInt(userMatch[1])}/>;
|
|
||||||
if(newsMatch) component = <APModifyPost postId={parseInt(newsMatch[1])}/>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'AdminPanel'}>
|
<div className={'AdminPanel'}>
|
||||||
@@ -57,7 +34,20 @@ class AdminPanel extends React.Component<Props, State> {
|
|||||||
<Link to={`${path}/novels`}>Новелы</Link>
|
<Link to={`${path}/novels`}>Новелы</Link>
|
||||||
<Link to={`${path}/news`}>Новости</Link>
|
<Link to={`${path}/news`}>Новости</Link>
|
||||||
</div>
|
</div>
|
||||||
{component}
|
|
||||||
|
<Switch>
|
||||||
|
<Route exact path={`${path}/news`} component={APNews}/>
|
||||||
|
<Route exact path={`${path}/news/new`} component={APAddPost}/>
|
||||||
|
<Route exact path={`${path}/news/:id`} component={APModifyPost}/>
|
||||||
|
|
||||||
|
<Route exact path={`${path}/novels`} component={APNovels}/>
|
||||||
|
<Route exact path={`${path}/novels/new`} component={NotFoundError}/>
|
||||||
|
<Route exact path={`${path}/novels/:id`} component={NotFoundError}/>
|
||||||
|
|
||||||
|
<Route exact path={`${path}/users`} component={APUsers}/>
|
||||||
|
<Route exact path={`${path}/users/new`} component={NotFoundError}/>
|
||||||
|
<Route exact path={`${path}/users/:id`} component={APModifyUser}/>
|
||||||
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import React, { ChangeEvent, FormEvent } from "react";
|
||||||
|
import Group from "../../../ui/Group/Group";
|
||||||
|
import { createPost } from "../../../api/news";
|
||||||
|
import './APNews.sass';
|
||||||
|
import '../../../ui/TextField/TextField.sass'
|
||||||
|
import Input from "../../../ui/Input/Input";
|
||||||
|
import Button from "../../../ui/Button/Button";
|
||||||
|
import BBEditor from "../../../ui/BBEditor/BBEditor";
|
||||||
|
// @ts-ignore
|
||||||
|
import parser from 'bbcode-to-react';
|
||||||
|
import Title from "../../../ui/Title/Title";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import NotificationMessage from "../../../ui/Notifications/NotificationMessage";
|
||||||
|
import { setModal } from "../../../store/ducks/modal";
|
||||||
|
import { addNotification } from "../../../store/ducks/notifications";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
match: { path: string }
|
||||||
|
history: { push: (path: string) => void }
|
||||||
|
setPopout: (modal: React.ReactNode | null) => void
|
||||||
|
addNotification: (notification: React.ReactNode) => void
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
post: PostType
|
||||||
|
title: string
|
||||||
|
|
||||||
|
previewShort: string
|
||||||
|
previewFull: string
|
||||||
|
};
|
||||||
|
|
||||||
|
class APModifyPost extends React.Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
post: {} as PostType, title: '',
|
||||||
|
previewShort: '', previewFull: ''
|
||||||
|
}
|
||||||
|
private shortPost = React.createRef<HTMLTextAreaElement>();
|
||||||
|
private fullPost = React.createRef<HTMLTextAreaElement>();
|
||||||
|
|
||||||
|
savePost = async(event: FormEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const { post } = this.state;
|
||||||
|
if(!this.shortPost.current || !this.fullPost.current)
|
||||||
|
return;
|
||||||
|
|
||||||
|
post.short_post = this.shortPost.current.value;
|
||||||
|
post.full_post = this.fullPost.current.value;
|
||||||
|
const { addNotification } = this.props;
|
||||||
|
|
||||||
|
createPost(post).then(createdPost => {
|
||||||
|
const notification = (
|
||||||
|
<NotificationMessage level={"success"}>
|
||||||
|
Успешно создана новость с ID {createdPost.id}!
|
||||||
|
</NotificationMessage>
|
||||||
|
);
|
||||||
|
addNotification(notification);
|
||||||
|
const path = this.props.match.path.split('/');
|
||||||
|
this.props.history.push(`/${path[1]}/${path[2]}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
previewShort = () => {
|
||||||
|
if(!this.shortPost.current) return;
|
||||||
|
this.setState({ previewShort: parser.toReact(this.shortPost.current.value) })
|
||||||
|
}
|
||||||
|
|
||||||
|
previewFull = () => {
|
||||||
|
if(!this.fullPost.current) return;
|
||||||
|
this.setState({ previewFull: parser.toReact(this.fullPost.current.value) })
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTitle = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { post } = this.state;
|
||||||
|
post.title = event.target.value;
|
||||||
|
document.title = event.target.value || 'Unmei';
|
||||||
|
this.setState({ post });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { post, title, previewShort, previewFull } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group title={title}>
|
||||||
|
<form onSubmit={this.savePost} className={'APPost'}>
|
||||||
|
<Input type="text" placeholder={'Заголовок'} value={post.title ?? ''} onChange={this.changeTitle}/>
|
||||||
|
|
||||||
|
<div className={'APPost__Short'}>
|
||||||
|
<Title>Короткая новость</Title>
|
||||||
|
<BBEditor inputRef={this.shortPost} placeholder={'Короткая новость'}/>
|
||||||
|
{previewShort.length > 0 && (<>
|
||||||
|
<Title>Предпросмотр</Title>
|
||||||
|
<div className={'APPost__Short_Preview TextField'}>{previewShort}</div>
|
||||||
|
</>)}
|
||||||
|
<Button type={"button"} onClick={this.previewShort}>Предпросмотр</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'APPost__Full'}>
|
||||||
|
<Title>Полная новость</Title>
|
||||||
|
<BBEditor inputRef={this.fullPost} placeholder={'Полная новость'}/>
|
||||||
|
{previewFull.length > 0 && (<>
|
||||||
|
<Title>Предпросмотр</Title>
|
||||||
|
<div className={'APPost__Full_Preview TextField'}>{previewFull}</div>
|
||||||
|
</>)}
|
||||||
|
<Button type={"button"} onClick={this.previewFull}>Предпросмотр</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button style={{ marginRight: '1rem' }}>Сохранить</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null,
|
||||||
|
dispatch => ({
|
||||||
|
setPopout: (modal: React.ReactNode | null) => dispatch(setModal(modal)),
|
||||||
|
addNotification: (notification: React.ReactNode) => dispatch(addNotification(notification))
|
||||||
|
})
|
||||||
|
)(APModifyPost);
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { ChangeEvent, FormEvent } from "react";
|
import React, { ChangeEvent, FormEvent } from "react";
|
||||||
import Group from "../../../ui/Group/Group";
|
import Group from "../../../ui/Group/Group";
|
||||||
import NotFoundError from "../../NotFoundError";
|
import NotFoundError from "../../../components/NotFoundError";
|
||||||
import { deletePost, fetchPost, updatePost } from "../../../api/news";
|
import { deletePost, fetchPost, updatePost } from "../../../api/news";
|
||||||
import './APNews.sass';
|
import './APNews.sass';
|
||||||
import '../../../ui/TextField/TextField.sass'
|
|
||||||
import Input from "../../../ui/Input/Input";
|
import Input from "../../../ui/Input/Input";
|
||||||
import Button from "../../../ui/Button/Button";
|
import Button from "../../../ui/Button/Button";
|
||||||
import BBEditor from "../../../ui/BBEditor/BBEditor";
|
import BBEditor from "../../../ui/BBEditor/BBEditor";
|
||||||
@@ -17,7 +16,8 @@ import { setModal } from "../../../store/ducks/modal";
|
|||||||
import { addNotification } from "../../../store/ducks/notifications";
|
import { addNotification } from "../../../store/ducks/notifications";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
postId: number
|
match: { params: { id: number }, path: string }
|
||||||
|
history: { push: (path: string) => void }
|
||||||
setPopout: (modal: React.ReactNode | null) => void
|
setPopout: (modal: React.ReactNode | null) => void
|
||||||
addNotification: (notification: React.ReactNode) => void
|
addNotification: (notification: React.ReactNode) => void
|
||||||
};
|
};
|
||||||
@@ -39,8 +39,9 @@ class APModifyPost extends React.Component<Props, State> {
|
|||||||
private fullPost = React.createRef<HTMLTextAreaElement>();
|
private fullPost = React.createRef<HTMLTextAreaElement>();
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { postId } = this.props;
|
const { match } = this.props;
|
||||||
fetchPost(postId).then((post: PostType) => {
|
|
||||||
|
fetchPost(match.params.id).then((post: PostType) => {
|
||||||
this.setState({ post, title: post.title });
|
this.setState({ post, title: post.title });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -61,7 +62,7 @@ class APModifyPost extends React.Component<Props, State> {
|
|||||||
<NotificationMessage level={"success"}>
|
<NotificationMessage level={"success"}>
|
||||||
Успешно!
|
Успешно!
|
||||||
</NotificationMessage>
|
</NotificationMessage>
|
||||||
)
|
);
|
||||||
addNotification(notification)
|
addNotification(notification)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -88,14 +89,21 @@ class APModifyPost extends React.Component<Props, State> {
|
|||||||
const post = this.state.post;
|
const post = this.state.post;
|
||||||
if(!post) return;
|
if(!post) return;
|
||||||
post.title = event.target.value;
|
post.title = event.target.value;
|
||||||
|
document.title = event.target.value || 'Unmei';
|
||||||
this.setState({ post });
|
this.setState({ post });
|
||||||
}
|
}
|
||||||
|
|
||||||
deletePost = () => {
|
deletePost = async (postId: number) => {
|
||||||
const { postId, setPopout } = this.props;
|
await deletePost(postId);
|
||||||
|
const path = this.props.match.path.split('/')
|
||||||
|
this.props.history.push(`/${path[1]}/${path[2]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_deletePost = () => {
|
||||||
|
const { match: { params: { id} }, setPopout } = this.props;
|
||||||
|
|
||||||
const popout = (
|
const popout = (
|
||||||
<ConfirmPopout onConfirm={() => deletePost(postId)}>
|
<ConfirmPopout onConfirm={() => this.deletePost(id)}>
|
||||||
Вы уверены?
|
Вы уверены?
|
||||||
</ConfirmPopout>
|
</ConfirmPopout>
|
||||||
);
|
);
|
||||||
@@ -135,7 +143,7 @@ class APModifyPost extends React.Component<Props, State> {
|
|||||||
<div>
|
<div>
|
||||||
<Button style={{ marginRight: '1rem' }}>Сохранить</Button>
|
<Button style={{ marginRight: '1rem' }}>Сохранить</Button>
|
||||||
<Button type={"button"} style={{ color: 'var(--error-bg-color)' }}
|
<Button type={"button"} style={{ color: 'var(--error-bg-color)' }}
|
||||||
onClick={this.deletePost}>Удалить</Button>
|
onClick={this._deletePost}>Удалить</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Link } from "react-router-dom";
|
|||||||
import Button from "../../../ui/Button/Button";
|
import Button from "../../../ui/Button/Button";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
path: string
|
match: { path: string }
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -23,11 +23,13 @@ class APNews extends React.Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { news } = this.state;
|
const { news } = this.state;
|
||||||
const { path } = this.props;
|
const { match: { path } } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group title={'Новости'}>
|
<Group title={'Новости'}>
|
||||||
<Button>Добавить новость</Button>
|
<Link to={`${path}/new`}>
|
||||||
|
<Button>Добавить новость</Button>
|
||||||
|
</Link>
|
||||||
{news.map(post => (
|
{news.map(post => (
|
||||||
<Link to={`${path}/${post.id}`} key={post.id}>
|
<Link to={`${path}/${post.id}`} key={post.id}>
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ type Props = {};
|
|||||||
|
|
||||||
type State = {};
|
type State = {};
|
||||||
|
|
||||||
class AdminPanelNovels extends React.Component<Props, State> {
|
class APNovels extends React.Component<Props, State> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>Novels</div>
|
<div>Novels</div>
|
||||||
@@ -17,4 +17,4 @@ export default connect(
|
|||||||
(state: StoreState) => ({
|
(state: StoreState) => ({
|
||||||
user: state.currentUser
|
user: state.currentUser
|
||||||
})
|
})
|
||||||
)(AdminPanelNovels);
|
)(APNovels);
|
||||||
+6
-6
@@ -1,14 +1,14 @@
|
|||||||
import React, { ChangeEvent, FormEvent } from "react";
|
import React, { ChangeEvent, FormEvent } from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { fetchUser, updateUser } from "../../../api/users";
|
import { fetchUser, updateUser } from "../../../api/users";
|
||||||
import Loading from "../../../ui/Loading";
|
import Loading from "../../../components/Loading";
|
||||||
import './APUser.sass';
|
import './APUser.sass';
|
||||||
import NotificationMessage from "../../../ui/Notifications/NotificationMessage";
|
import NotificationMessage from "../../../ui/Notifications/NotificationMessage";
|
||||||
import Button from "../../../ui/Button/Button";
|
import Button from "../../../ui/Button/Button";
|
||||||
import { addNotification } from "../../../store/ducks/notifications";
|
import { addNotification } from "../../../store/ducks/notifications";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
userId: number
|
match: { params: { id: number } }
|
||||||
addNotification: (notification: React.ReactNode) => void
|
addNotification: (notification: React.ReactNode) => void
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,14 +17,14 @@ type State = {
|
|||||||
username: string
|
username: string
|
||||||
};
|
};
|
||||||
|
|
||||||
class ModifyUser extends React.Component<Props, State> {
|
class APModifyUser extends React.Component<Props, State> {
|
||||||
state: State = {
|
state: State = {
|
||||||
user: null, username: ''
|
user: null, username: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { userId } = this.props;
|
const { match: { params: { id } } } = this.props;
|
||||||
fetchUser(userId).then((user: UserType) => {
|
fetchUser(id).then((user: UserType) => {
|
||||||
this.setState({ user, username: user.username });
|
this.setState({ user, username: user.username });
|
||||||
document.title = `${user.username} / Админ-панель`;
|
document.title = `${user.username} / Админ-панель`;
|
||||||
})
|
})
|
||||||
@@ -95,4 +95,4 @@ export default connect(
|
|||||||
dispatch(addNotification(notification))
|
dispatch(addNotification(notification))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)(ModifyUser);
|
)(APModifyUser);
|
||||||
@@ -24,4 +24,4 @@
|
|||||||
width: 100%
|
width: 100%
|
||||||
|
|
||||||
&[type='checkbox']
|
&[type='checkbox']
|
||||||
width: 0
|
width: 16px
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { fetchUsers } from "../../../api/users";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import Group from "../../../ui/Group/Group";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
match: { path: string }
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
users: UserType[]
|
||||||
|
};
|
||||||
|
|
||||||
|
class APUsers extends React.Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
users: []
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
fetchUsers().then(users => this.setState({ users }));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { users } = this.state;
|
||||||
|
const { match: { path } } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group title={'Пользователи'}>
|
||||||
|
{users.sort((a, b) => a.id - b.id).map(user => (
|
||||||
|
<Link to={`${path}/${user.id}`} key={user.id}>
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<div style={{ marginRight: '.5rem', fontWeight: 600 }}>{user.id}</div>
|
||||||
|
<div>{user.username}</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
(state: StoreState) => ({
|
||||||
|
user: state.currentUser
|
||||||
|
})
|
||||||
|
)(APUsers);
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { fetchUsers } from "../../../api/users";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
path: string
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
users: UserType[]
|
|
||||||
};
|
|
||||||
|
|
||||||
class AdminPanelUsers extends React.Component<Props, State> {
|
|
||||||
state: State = {
|
|
||||||
users: []
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
fetchUsers().then(users => this.setState({ users }));
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { users } = this.state;
|
|
||||||
const { path } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div style={{ textAlign: "center", fontWeight: 600, fontSize: '1.5rem' }}>Пользователи</div>
|
|
||||||
<div>
|
|
||||||
{users.sort((a, b) => a.id - b.id).map(user => (
|
|
||||||
<Link to={`${path}/${user.id}`} key={user.id}>
|
|
||||||
<div style={{ display: "flex" }}>
|
|
||||||
<div style={{ marginRight: '.5rem', fontWeight: 600 }}>{user.id}</div>
|
|
||||||
<div>{user.username}</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
(state: StoreState) => ({
|
|
||||||
user: state.currentUser
|
|
||||||
})
|
|
||||||
)(AdminPanelUsers);
|
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route, Switch } from "react-router-dom";
|
import { Route, Switch } from "react-router-dom";
|
||||||
import './App.sass';
|
import './App.sass';
|
||||||
import Navbar from "../Navbar/Navbar";
|
import Navbar from "../../components/Navbar/Navbar";
|
||||||
import Novel from "../Novel/Novel";
|
import Novel from "../Novel/Novel";
|
||||||
import User from "../User/User";
|
import User from "../User/User";
|
||||||
import Main from "../Main/Main"
|
import Main from "../Main/Main"
|
||||||
import NotFoundError from "../NotFoundError";
|
import NotFoundError from "../../components/NotFoundError";
|
||||||
import { fetchCurrentUser, fetchUserSettings } from "../../api/users";
|
import { fetchCurrentUser, fetchUserSettings } from "../../api/users";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import Character from "../Character/Character";
|
import Character from "../Character/Character";
|
||||||
import SpoilerTag from "../../ui/Spoiler/SpoilerTag";
|
import SpoilerTag from "../../ui/Spoiler/SpoilerTag";
|
||||||
import Novels from "../Novels/Novels";
|
import Novels from "../Novels/Novels";
|
||||||
import Footer from '../Footer/Footer';
|
import Footer from '../../components/Footer/Footer';
|
||||||
import UserNovels from '../User/Novels/UserNovels';
|
import UserNovels from '../User/Novels/UserNovels';
|
||||||
import ColorTag from "../../ui/Tags/ColorTag";
|
import ColorTag from "../../ui/Tags/ColorTag";
|
||||||
import ActivateAccount from "../ActivateAccount";
|
import ActivateAccount from "../ActivateAccount";
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import './Character.sass'
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import parser from 'bbcode-to-react';
|
import parser from 'bbcode-to-react';
|
||||||
import NovelItem from "../../ui/NovelItem/NovelItem";
|
import NovelItem from "../../ui/NovelItem/NovelItem";
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import NotFoundError from "../NotFoundError";
|
import NotFoundError from "../../components/NotFoundError";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
match: { params: { charId: number } }
|
match: { params: { charId: number } }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { getClub } from "../../api/clubs";
|
import { getClub } from "../../api/clubs";
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import Group from "../../ui/Group/Group";
|
import Group from "../../ui/Group/Group";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import Comment from '../../ui/Comment/Comment';
|
import Comment from '../../ui/Comment/Comment';
|
||||||
import './Comments.sass';
|
import './Comments.sass';
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import { fetchUser } from "../../api/users";
|
import { fetchUser } from "../../api/users";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import NotificationMessage from "../../ui/Notifications/NotificationMessage";
|
import NotificationMessage from "../../ui/Notifications/NotificationMessage";
|
||||||
@@ -71,9 +71,8 @@ class Comments extends React.Component<Props, State> {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<Props>) {
|
componentDidUpdate(prevProps: Readonly<Props>) {
|
||||||
const { comments } = this.props;
|
const { comments } = this.props;
|
||||||
const { comments: prevComments } = prevProps;
|
|
||||||
|
|
||||||
if(prevComments.length !== comments.length) {
|
if(prevProps.comments.length !== comments.length) {
|
||||||
let { users } = this.state;
|
let { users } = this.state;
|
||||||
|
|
||||||
comments.forEach(async comment => {
|
comments.forEach(async comment => {
|
||||||
@@ -89,7 +88,8 @@ class Comments extends React.Component<Props, State> {
|
|||||||
render() {
|
render() {
|
||||||
const { user, comments, hasMore, loadMore } = this.props;
|
const { user, comments, hasMore, loadMore } = this.props;
|
||||||
const { users, commentText } = this.state;
|
const { users, commentText } = this.state;
|
||||||
if(!user) return <Loading/>;
|
if(!user)
|
||||||
|
return <Loading/>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Comments">
|
<div className="Comments">
|
||||||
@@ -120,12 +120,13 @@ class Comments extends React.Component<Props, State> {
|
|||||||
{hasMore && <Button onClick={loadMore}>Загрузить ещё</Button>}
|
{hasMore && <Button onClick={loadMore}>Загрузить ещё</Button>}
|
||||||
</div>
|
</div>
|
||||||
</form>}
|
</form>}
|
||||||
{!comments && (
|
{comments.length === 0 && (
|
||||||
<Loading/>
|
<Loading/>
|
||||||
)}
|
)}
|
||||||
{comments && comments.length > 0 ? (
|
{comments.length > 0 ? (
|
||||||
comments.map(comment => <Comment text={comment.text} key={comment.id}
|
comments.map(comment => (
|
||||||
user={users.get(comment.user_id)}/>)
|
<Comment text={comment.text} key={comment.id} user={users.get(comment.user_id)}/>
|
||||||
|
))
|
||||||
) : (
|
) : (
|
||||||
'Никто не оставил комментарий к этой новелле... :c'
|
'Никто не оставил комментарий к этой новелле... :c'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { fetchNews } from "../../api/news";
|
import { fetchNews } from "../../api/news";
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import NewsPost from "../../ui/NewsPost/NewsPost";
|
import NewsPost from "../../ui/NewsPost/NewsPost";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import { fetchNews } from "../../api/news";
|
import { fetchNews } from "../../api/news";
|
||||||
import NewsPost from "../../ui/NewsPost/NewsPost";
|
import NewsPost from "../../ui/NewsPost/NewsPost";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { fetchPost } from "../../api/news";
|
import { fetchPost } from "../../api/news";
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import NotFoundError from "../NotFoundError";
|
import NotFoundError from "../../components/NotFoundError";
|
||||||
import './Post.sass'
|
import './Post.sass'
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import './Novel.sass'
|
import './Novel.sass'
|
||||||
import NotFoundError from "../NotFoundError";
|
import NotFoundError from "../../components/NotFoundError";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {
|
import {
|
||||||
createUserNovel,
|
createUserNovel,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
updateUserNovel
|
updateUserNovel
|
||||||
} from "../../api/novels";
|
} from "../../api/novels";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import { TranslateExitStatus, TranslatePlatform, TranslateStatus } from "../../api/api";
|
import { TranslateExitStatus, TranslatePlatform, TranslateStatus } from "../../api/api";
|
||||||
import Comments from "../Comments/Comments";
|
import Comments from "../Comments/Comments";
|
||||||
import LoadingModal from "../Modals/LoadingModal";
|
import LoadingModal from "../Modals/LoadingModal";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import './Novels.sass'
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { fetchNovels } from "../../api/novels";
|
import { fetchNovels } from "../../api/novels";
|
||||||
import NovelItem from '../../ui/NovelItem/NovelItem';
|
import NovelItem from '../../ui/NovelItem/NovelItem';
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
novels: NovelType[] | null
|
novels: NovelType[] | null
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './UserNovels.sass';
|
import './UserNovels.sass';
|
||||||
import { fetchUser, fetchUserNovels } from '../../../api/users';
|
import { fetchUser, fetchUserNovels } from '../../../api/users';
|
||||||
import Loading from '../../../ui/Loading';
|
import Loading from '../../../components/Loading';
|
||||||
import NovelItem from '../../../ui/NovelItem/NovelItem';
|
import NovelItem from '../../../ui/NovelItem/NovelItem';
|
||||||
import { capitalize, generateClassName, getRandomInt } from "../../../utils";
|
import { capitalize, generateClassName, getRandomInt } from "../../../utils";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Group from "../../../ui/Group/Group";
|
import Group from "../../../ui/Group/Group";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import NotFoundError from "../../NotFoundError";
|
import NotFoundError from "../../../components/NotFoundError";
|
||||||
import './Settings.sass';
|
import './Settings.sass';
|
||||||
import Tabs from "../../../ui/Tabs/Tabs";
|
import Tabs from "../../../ui/Tabs/Tabs";
|
||||||
import TabItem from "../../../ui/Tabs/TabItem";
|
import TabItem from "../../../ui/Tabs/TabItem";
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { CSSProperties } from "react";
|
import React, { CSSProperties } from "react";
|
||||||
import { fetchUser, fetchUserNovels, generateActivateLink } from "../../api/users";
|
import { fetchUser, fetchUserNovels, generateActivateLink } from "../../api/users";
|
||||||
import './User.sass'
|
import './User.sass'
|
||||||
import NotFoundError from "../NotFoundError";
|
import NotFoundError from "../../components/NotFoundError";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import NovelItem from "../../ui/NovelItem/NovelItem";
|
import NovelItem from "../../ui/NovelItem/NovelItem";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import Button from "../../ui/Button/Button";
|
import Button from "../../ui/Button/Button";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { fetchUsers } from "../../api/users";
|
import { fetchUsers } from "../../api/users";
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../components/Loading";
|
||||||
import './Users.sass';
|
import './Users.sass';
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import './Comment.sass'
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import parser from 'bbcode-to-react';
|
import parser from 'bbcode-to-react';
|
||||||
import Loading from "../Loading";
|
import Loading from "../../components/Loading";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: UserType | undefined
|
user: UserType | undefined
|
||||||
|
|||||||
+3
-2
@@ -16,8 +16,9 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react",
|
"jsx": "react-jsx",
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src"
|
||||||
|
|||||||
Reference in New Issue
Block a user