From 9ff47797201a1116d032e330ffcc4d0b7c1b8ff5 Mon Sep 17 00:00:00 2001 From: Nix13 Date: Wed, 29 Apr 2020 02:50:07 +0300 Subject: [PATCH] Many fixes --- package-lock.json | 44 +++++++++ package.json | 2 + src/api/users.ts | 6 +- src/components/AdminPanel/AdminPanel.sass | 19 ++++ src/components/AdminPanel/AdminPanel.tsx | 61 ++++++++++++ src/components/AdminPanel/Main/AdminPanel.tsx | 30 ------ .../AdminPanel/Novels/AdminPanelNovels.tsx | 24 +++++ src/components/AdminPanel/Users/APUser.sass | 27 ++++++ .../AdminPanel/Users/AdminPanelUsers.tsx | 49 ++++++++++ .../AdminPanel/Users/ModifyUser.tsx | 92 +++++++++++++++++++ src/components/App/App.sass | 18 +++- src/components/App/App.tsx | 14 ++- src/components/Comment/Comment.tsx | 12 +-- src/components/Comment/Comments.tsx | 56 +++++++---- src/components/Constants.sass | 28 +++--- src/components/Main/Main.sass | 3 +- src/components/Main/News.tsx | 4 +- src/components/Navbar/Navbar.sass | 69 ++++++++------ src/components/Navbar/Navbar.tsx | 88 +++++++++++------- src/components/News/AllNews.tsx | 31 ++++++- src/components/News/Post.sass | 6 +- src/components/News/Post.tsx | 3 +- src/components/Notification/Notification.sass | 8 +- src/components/Notification/Notification.tsx | 20 ++-- src/components/Novel/Novel.sass | 3 + src/components/Novel/Novel.tsx | 87 ++++++++++-------- src/components/NovelItem/NovelItem.sass | 61 ++++++------ src/components/NovelItem/NovelItem.tsx | 25 +++-- src/components/Spoiler/Spoiler.sass | 5 +- src/components/User/Novels/UserNovels.sass | 48 +++++----- src/components/User/User.sass | 40 ++++++-- src/components/User/User.tsx | 54 ++++++++--- src/types.d.ts | 2 + src/utils.ts | 6 +- 34 files changed, 764 insertions(+), 281 deletions(-) create mode 100644 src/components/AdminPanel/AdminPanel.sass create mode 100644 src/components/AdminPanel/AdminPanel.tsx delete mode 100644 src/components/AdminPanel/Main/AdminPanel.tsx create mode 100644 src/components/AdminPanel/Novels/AdminPanelNovels.tsx create mode 100644 src/components/AdminPanel/Users/APUser.sass create mode 100644 src/components/AdminPanel/Users/AdminPanelUsers.tsx create mode 100644 src/components/AdminPanel/Users/ModifyUser.tsx diff --git a/package-lock.json b/package-lock.json index 2c28971..6a41a91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1857,6 +1857,15 @@ "@types/react-router": "*" } }, + "@types/react-transition-group": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.4.tgz", + "integrity": "sha512-8DMUaDqh0S70TjkqU0DxOu80tFUiiaS9rxkWip/nb7gtvAsbqOXm02UCmR8zdcjWujgeYPiPNTVpVpKzUDotwA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -4649,6 +4658,30 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", + "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^2.6.7" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + } + } + }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -12456,6 +12489,17 @@ } } }, + "react-transition-group": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", + "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", diff --git a/package.json b/package.json index 175feff..fe20942 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "react-recaptcha": "^2.3.10", "react-redux": "^7.1.3", "react-router-dom": "^5.1.2", + "react-transition-group": "^4.3.0", "redux": "^4.0.5" }, "devDependencies": { @@ -26,6 +27,7 @@ "@types/react-dom": "^16.9.5", "@types/react-redux": "^7.1.7", "@types/react-router-dom": "^5.1.3", + "@types/react-transition-group": "^4.2.4", "node-sass": "^4.13.1", "redux-devtools-extension": "^2.13.8", "typescript": "^3.8.2", diff --git a/src/api/users.ts b/src/api/users.ts index 18647d8..e482788 100644 --- a/src/api/users.ts +++ b/src/api/users.ts @@ -1,6 +1,10 @@ -import {get, post} from "./api"; +import {get, post, put} from "./api"; + +export const fetchUsers = () => get('/users'); export const fetchUser = (id: number) => get(`users/${id}`); +export const updateUser = (user: UserType) => put(`users/${user.id}`, { user }) + export const fetchCurrentUser = () => get('users/current'); export const fetchUserNovels = (id: number) => get(`users/${id}/novels`); diff --git a/src/components/AdminPanel/AdminPanel.sass b/src/components/AdminPanel/AdminPanel.sass new file mode 100644 index 0000000..4bda701 --- /dev/null +++ b/src/components/AdminPanel/AdminPanel.sass @@ -0,0 +1,19 @@ +@import "../Constants" + +.AdminPanel + background-color: var(--sec-bg-color) + padding: .5rem + + &__Title + font-size: 1.5rem + font-weight: 600 + text-align: center + + &__Router + display: flex + flex-wrap: wrap + border-bottom: 1px solid white + + a + margin: 0 .2rem + padding: .1rem \ No newline at end of file diff --git a/src/components/AdminPanel/AdminPanel.tsx b/src/components/AdminPanel/AdminPanel.tsx new file mode 100644 index 0000000..4265dee --- /dev/null +++ b/src/components/AdminPanel/AdminPanel.tsx @@ -0,0 +1,61 @@ +import {connect} from "react-redux"; +import React from "react"; +import NotFoundError from "../NotFoundError"; +import {hasAccessToAdminPanel} from "../../utils"; +import './AdminPanel.sass'; +import {Link} from "react-router-dom"; +import AdminPanelUsers from "./Users/AdminPanelUsers"; +import AdminPanelNovels from "./Novels/AdminPanelNovels"; +import ModifyUser from "./Users/ModifyUser"; + +type Props = { + user: UserType + match: { path: string } + location: { pathname: string } +} + +type State = { + +} + +class AdminPanel extends React.Component { + render() { + const { user, match: { path }, location: { pathname } } = this.props; + if(!user || !user.group) return ; + if(!hasAccessToAdminPanel(user)) return ; + const subPath = pathname.slice(path.length+1); + let component; + switch(subPath) { + case 'users': component = ; break; + case 'novels': component = ; break; + case 'news': component =
Новости
; break; + default: { + const users = /users\/(\d+)/; + + const userMatch = subPath.match(users); + + if(userMatch) + component = ; + } + } + + return ( +
+
Админ панель (в разработке)
+
+ Главная + Пользователи + Новелы + Новости +
+ {component} +
+ ); + } +} + +export default connect( + (state: StoreState) => ({ + user: state.currentUser + }) +)(AdminPanel); \ No newline at end of file diff --git a/src/components/AdminPanel/Main/AdminPanel.tsx b/src/components/AdminPanel/Main/AdminPanel.tsx deleted file mode 100644 index 22b1002..0000000 --- a/src/components/AdminPanel/Main/AdminPanel.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import {connect} from "react-redux"; -import React from "react"; -import NotFoundError from "../../NotFoundError"; -import {hasPermission} from "../../../utils"; - -type Props = { - user: UserType -} - -type State = { - -} - -class AdminPanel extends React.Component { - render() { - const { user } = this.props; - if(!user || !user.group) return ; - if(!user.is_superuser && !user.group.is_superuser && !hasPermission(user, 'admin_panel')) return ; - - return ( -
Админ панель, ёпта
- ) - } -} - -export default connect( - (state: StoreState) => ({ - user: state.currentUser - }) -)(AdminPanel); \ No newline at end of file diff --git a/src/components/AdminPanel/Novels/AdminPanelNovels.tsx b/src/components/AdminPanel/Novels/AdminPanelNovels.tsx new file mode 100644 index 0000000..03de972 --- /dev/null +++ b/src/components/AdminPanel/Novels/AdminPanelNovels.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import {connect} from "react-redux"; + +type Props = { + +}; + +type State = { + +}; + +class AdminPanelNovels extends React.Component { + render() { + return ( +
Novels
+ ) + } +} + +export default connect( + (state: StoreState) => ({ + user: state.currentUser + }) +)(AdminPanelNovels); \ No newline at end of file diff --git a/src/components/AdminPanel/Users/APUser.sass b/src/components/AdminPanel/Users/APUser.sass new file mode 100644 index 0000000..c9c08f2 --- /dev/null +++ b/src/components/AdminPanel/Users/APUser.sass @@ -0,0 +1,27 @@ +.APUser__Modify + &_Title + text-align: center + font-weight: 600 + font-size: 1.5rem + + &_Form + display: flex + flex-direction: column + + button + font-size: 1.5rem + + label + display: flex + flex-wrap: nowrap + white-space: nowrap + align-items: center + font-size: 1.2rem + + input + margin: .5rem 0 .5rem .5rem + font-size: 1.2rem + width: 100% + + &[type='checkbox'] + width: 0 diff --git a/src/components/AdminPanel/Users/AdminPanelUsers.tsx b/src/components/AdminPanel/Users/AdminPanelUsers.tsx new file mode 100644 index 0000000..d4d4cb2 --- /dev/null +++ b/src/components/AdminPanel/Users/AdminPanelUsers.tsx @@ -0,0 +1,49 @@ +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 { + state: State = { + users: [] + } + + componentDidMount() { + fetchUsers().then(users => this.setState({ users })) + } + + render() { + const { users } = this.state; + const { path } = this.props; + + return ( +
+
Пользователи
+
+ {users.sort((a, b) => a.id - b.id).map(user => ( + +
+
{user.id}
+
{user.username}
+
+ + ))} +
+
+ ) + } +} + +export default connect( + (state: StoreState) => ({ + user: state.currentUser + }) +)(AdminPanelUsers); \ No newline at end of file diff --git a/src/components/AdminPanel/Users/ModifyUser.tsx b/src/components/AdminPanel/Users/ModifyUser.tsx new file mode 100644 index 0000000..2f0b43e --- /dev/null +++ b/src/components/AdminPanel/Users/ModifyUser.tsx @@ -0,0 +1,92 @@ +import React, {ChangeEvent, FormEvent} from "react"; +import {connect} from "react-redux"; +import {fetchUser, updateUser} from "../../../api/users"; +import Loading from "../../Loading"; +import './APUser.sass'; +import {addNotification} from "../../../store/actions"; +import Notification from "../../Notification/Notification"; + +type Props = { + userId: number + addNotification: (notification: React.ReactNode) => void +}; + +type State = { + user: UserType | null + username: string +}; + +class ModifyUser extends React.Component { + state: State = { + user: null, username: '' + } + + componentDidMount() { + const { userId } = this.props; + fetchUser(userId).then((user: UserType) => { + this.setState({ user, username: user.username }); + document.title = `${user.username} / Админ-панель`; + }) + } + + onChange = (event: ChangeEvent) => { + const user = Object.assign({}, this.state.user); + const target = event.target; + // @ts-ignore + user[target.name] = target.type === 'checkbox' ? target.checked : target.value; + this.setState({ user }) + } + + onSubmit = (event: FormEvent) => { + event.preventDefault(); + const { user } = this.state; + const { addNotification } = this.props; + + if(user === null) { + console.error('Пользователь - null!'); + return; + } + updateUser(user).then(() => { + const notification = ( + + Пользователь успешно изменен! + + ) + addNotification(notification) + }); + } + + render() { + const { user, username } = this.state; + + if(!user) return ; + return ( +
+
{username}
+
+ + + + + + + +
+ +
+
+
+ ) + } +} + +export default connect( + (state: StoreState) => ({ + user: state.currentUser + }), + (dispatch) => ({ + addNotification: (notification: React.ReactNode) => { + dispatch(addNotification(notification)) + } + }) +)(ModifyUser); \ No newline at end of file diff --git a/src/components/App/App.sass b/src/components/App/App.sass index 85b84ba..4e36141 100644 --- a/src/components/App/App.sass +++ b/src/components/App/App.sass @@ -5,11 +5,14 @@ html, body, #root width: 100% height: 100% +body, pre, textarea + font-family: 'Roboto', sans-serif + +h1, h2, h3, h4, h5, h6, p, body, pre + margin: 0 body - margin: 0 padding: 0 - font-family: 'Roboto', sans-serif --main-color: #{$mainColor} --sec-color: #{$secondaryColor} @@ -28,8 +31,6 @@ body background: var(--main-bg-color) pre - font-family: 'Roboto', sans-serif - margin: 0 white-space: pre-wrap body[theme='red'] @@ -64,6 +65,9 @@ body[theme='light'] --block-sec-color: #{$blockSecondaryBackgroundColorLight} --nav-bg-color: #{$navbarBackgroundColorLight} + color: var(--main-color) + background: var(--main-bg-color) + a color: var(--main-color) text-decoration: none @@ -74,8 +78,12 @@ a input, button, textarea color: var(--main-color) + &:disabled + color: var(--sec-color) + button cursor: pointer + padding: .2rem border-radius: $borderRadius background-color: var(--block-color) border: 1px solid var(--main-color) @@ -97,7 +105,7 @@ input .Container max-width: 1200px - min-height: calc( 100% - 4rem ) + min-height: calc(100% - 4rem) margin: .5rem auto .Notifications diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 337b717..626b8c2 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Switch, Route } from "react-router-dom"; +import {Switch, Route} from "react-router-dom"; import './App.sass'; import Navbar from "../Navbar/Navbar"; import Novel from "../Novel/Novel"; @@ -22,7 +22,7 @@ import ColorTag from "../Tags/ColorTag"; import ActivateAccount from "../ActivateAccount"; import AllNews from "../News/AllNews"; import Post from "../News/Post"; -import AdminPanel from "../AdminPanel/Main/AdminPanel"; +import AdminPanel from "../AdminPanel/AdminPanel"; type Props = { notifications: React.ReactNode[] @@ -47,7 +47,9 @@ class App extends React.Component { }; const { setUser } = this.props; - fetchCurrentUser().then(currentUser => setUser(currentUser)).catch(console.error); + fetchCurrentUser().then(currentUser => setUser(currentUser)).catch((err: ApiError) => { + if(err.code !== 3) console.error(err.text); + }); const theme = localStorage.getItem('theme'); if(theme) @@ -91,7 +93,9 @@ class App extends React.Component { - + + {/**/} + @@ -105,7 +109,7 @@ class App extends React.Component { } export default connect( - (state: { notifications: React.ReactNode[] }) => ({ + (state: StoreState) => ({ notifications: state.notifications }), dispatch => ({ diff --git a/src/components/Comment/Comment.tsx b/src/components/Comment/Comment.tsx index 0acf668..266ca92 100644 --- a/src/components/Comment/Comment.tsx +++ b/src/components/Comment/Comment.tsx @@ -9,18 +9,10 @@ type Props = { user: UserType | undefined text: string } -type State = { - user: UserType | null -} - -class Comment extends React.Component { - state: State = { - user: null - }; +class Comment extends React.Component { render() { - const { user } = this.props; - const { text } = this.props; + const { user, text } = this.props; if(!user) return ; return (user && diff --git a/src/components/Comment/Comments.tsx b/src/components/Comment/Comments.tsx index d8a3b0e..9b8c3d8 100644 --- a/src/components/Comment/Comments.tsx +++ b/src/components/Comment/Comments.tsx @@ -5,6 +5,9 @@ import Comment from './Comment'; import './Comments.sass'; import Loading from "../Loading"; import {fetchUser} from "../../api/users"; +import {connect} from "react-redux"; +import {addNotification} from "../../store/actions"; +import Notification from "../Notification/Notification"; type Props = { comments: CommentType[] @@ -12,6 +15,7 @@ type Props = { user: UserType loadMore: () => void sendComment: (text: string) => void + addNotification: (notification: React.ReactNode) => void }; type State = { @@ -41,35 +45,43 @@ class Comments extends React.Component { sendComment = (event: React.FormEvent) => { event.preventDefault(); - const { sendComment } = this.props; + const { sendComment, addNotification } = this.props; const { commentText } = this.state; - if(commentText === '') - console.log('empty'); - else { + if(commentText === '') { + const notification = ( + + Пустой комментарий + + ); + addNotification(notification); + } else { sendComment(commentText); this.setState({ commentText: '' }); } }; componentDidUpdate(prevProps: Readonly) { - if(prevProps.comments.length !== this.props.comments.length) { - let map: Map = new Map(); - this.props.comments.forEach(comment => { - if(!map.has(comment.user_id)) { - fetchUser(comment.user_id).then(user => { - map = map.set(comment.user_id, user) - }); + const { comments } = this.props; + const { comments: prevComments } = prevProps; + + if(prevComments.length !== comments.length) { + let { users } = this.state; + + comments.forEach(async comment => { + if(!users.has(comment.user_id)) { + const user = await fetchUser(comment.user_id); + users = users.set(comment.user_id, user); + this.setState({ users }); } }); - console.log(map) } } render() { const { user, comments, hasMore, loadMore } = this.props; const { users } = this.state; - if(!user || !comments) return ; + if(!user) return ; return (
@@ -86,15 +98,21 @@ class Comments extends React.Component {