18 次代碼提交 3e19118406 ... 69dd8e18f4

作者 SHA1 備註 提交日期
  makstravm 69dd8e18f4 fix render posts, added scroll end conditions. и шоб не було скучно, начинаем секас с Сагой 2 年之前
  makstravm f5d05e2a58 refactoring, added scroll in profile page, added limit scroll. 2 年之前
  makstravm 70289f6eb9 added posts in Prifile Page 2 年之前
  makstravm 9796a08187 added subscrie/unsubscribe 2 年之前
  makstravm 92b4eb06eb added action setAvatar, upload image 2 年之前
  makstravm c8aa80b872 added top part page profile, modal followind and followers 2 年之前
  makstravm 2e8683e6fa added drop menu user (profile, setings, logout) 2 年之前
  makstravm a7eaa7c8a9 added users search by login 2 年之前
  makstravm 0beb7070df added infinity scroll 2 年之前
  makstravm af43b3bd3f finish render post, add comments, fix render and thunk added/ remove Likes Post 2 年之前
  makstravm a028f7e359 added postFeed reducer, add change like 2 年之前
  makstravm e03b723ca3 fix slider 2 年之前
  makstravm 9b995ac184 carusel infinite=false 2 年之前
  makstravm 2d34613784 added image, slider block in the component Post 2 年之前
  makstravm 3663e1add4 style header, and create top part Post 2 年之前
  makstravm 4a982a9ea3 remember token in sessionStorage and edit authReducer and function qetGqL 2 年之前
  makstravm 6d81cad6ac added page register and checkbox rembmer token in LocalStorage 2 年之前
  makstravm 41dc998a8e added Ant Design in project and page Log In 2 年之前

+ 3 - 0
package.json

@@ -3,15 +3,18 @@
     "version": "0.1.0",
     "private": true,
     "dependencies": {
+        "@ant-design/icons": "^4.7.0",
         "@testing-library/jest-dom": "^5.16.1",
         "@testing-library/react": "^12.1.2",
         "@testing-library/user-event": "^13.5.0",
+        "antd": "^4.18.2",
         "react": "^17.0.2",
         "react-dom": "^17.0.2",
         "react-redux": "^7.2.6",
         "react-router-dom": "^5.3.0",
         "react-scripts": "5.0.0",
         "redux": "^4.1.2",
+        "redux-saga": "^1.1.3",
         "redux-thunk": "^2.4.1",
         "sass": "^1.45.0",
         "web-vitals": "^2.1.2"

+ 32 - 13
src/App.js

@@ -1,35 +1,54 @@
+import 'antd/dist/antd.min.css'
 import './App.scss';
 import { Router, Route, Switch, Redirect } from 'react-router-dom';
 import createHistory from "history/createBrowserHistory";
 import { connect, Provider } from 'react-redux';
-import { store } from './redux/redux-store';
-import { Authorization } from './pages/Authorization';
-import { Content } from './pages/Content';
-
-
-
+import  store  from './redux/redux-store';
+import { Authorization } from './components/Authorization';
+import { Content, Main } from './pages/Content';
+import HeaderComponent from './components/header/Header';
+import { CMainPostsFeed } from './components/main/MainPostsFeed';
+import { CProfilePage } from './components/main/Profile';
+import { CAdd } from './components/main/Add';
 
 export const history = createHistory()
 
 
+const Aside = () =>
+    <div>sdfsdgsgsdg</div>
 const AppContent = ({ isToken }) =>
     <Router history={history}>
         {!isToken
             ?
             <Switch>
-                <Route path='/login' component={Authorization} />
-                <Redirect from='/*' to='/login' />
+                <Route path='/auth/:_id'
+                    component={Authorization} />
+                <Redirect from='/*' to='/auth/login' />
             </Switch>
             :
-            <Switch>
-                <Route path='/' component={Content} exact />
-                {/* <Redirect from='/*' to='/' /> */}
-            </Switch>
+
+            <Content>
+                <HeaderComponent />
+                <Main>
+                    <Switch>
+                        <Route path='/' component={CMainPostsFeed} exact />
+                        <Route path='/profile/:_id' component={CProfilePage} />
+                        <Route path='/message' component={Aside} />
+                        <Route path='/add' component={CAdd} />
+                        <Redirect from='/*' to='/profile/614c8ef4f9fc3a5e42bddb28' />
+                    </Switch>
+                </Main>
+            </Content >
+
+
+            // <Switch>
+            //     <Route path='/' component={Content} exact />
+            //     <Redirect from='/auth/*' to='/' />
+            // </Switch>
         }
     </Router >
 
 const CAppContent = connect(state => ({ isToken: state.auth?.token }))(AppContent)
-
 store.subscribe(() => console.log(store.getState()))
 
 function App() {

+ 255 - 9
src/App.scss

@@ -49,21 +49,267 @@ select {
     font: inherit;
 }
 
-//
 .Header {
     display: flex;
     background-color: #ececec;
     justify-content: space-between;
     align-items: center;
+    .Logo {
+        display: block;
+    }
+    &__inner {
+        max-width: 1250px;
+        margin: 0 auto;
+    }
+    &__search {
+        display: block;
+        padding: 0 10px;
+    }
+    &__search-link {
+        display: flex;
+        align-items: center;
+        padding: 5px 20px;
+        transition: all 0.3s;
+        strong {
+            padding-left: 15px;
+            color: #000;
+        }
+        &:hover {
+            background-color: #0057ff1f;
+        }
+    }
+    &__search-drop {
+        max-width: 500px;
+        min-width: 320px;
+        max-height: 500px;
+        padding: 16px 0;
+        overflow: auto;
+    }
+    &__userNav {
+        div {
+            padding-left: 20px;
+        }
+        a {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+        svg {
+            width: 24px;
+            height: 24px;
+        }
+    }
 }
-.UserNav {
-    display: flex;
-    width: 25vw;
-    align-items: center;
+
+.ant-popover {
+    padding-top: 12px;
+    &-arrow {
+        width: 14px;
+        height: 19px;
+        &-content {
+            width: 14px;
+            height: 14px;
+        }
+    }
+    &-inner-content {
+        padding: 2px 0 0;
+    }
+}
+
+.dropMenu {
+    max-width: 200px;
+    min-width: 150px;
+    .anticon.anticon-user,
+    li {
+        transition: all 0.3s;
+        &:hover {
+            background-color: rgba($color: #0057ff, $alpha: 0.12);
+        }
+    }
+    a {
+        transition: all 0.3s;
+    }
+    button {
+        cursor: pointer;
+        text-align-last: left;
+        width: 100%;
+        background-color: transparent;
+        border: none;
+        padding: 0;
+        margin: 0;
+        transition: all 0.3s;
+    }
+}
+
+.Authorization {
+    height: 100%;
+    background-position: right top;
+    background-repeat: no-repeat;
+    background-size: cover;
+    a {
+        font-size: 1.2em;
+    }
+    .active {
+        color: red;
+    }
+    .ant-form-item {
+        margin-bottom: 10px;
+    }
+    &__form {
+        height: 100vh;
+        padding-right: calc(10vw + 25px);
+    }
+    .login-form {
+        text-align: center;
+        button {
+            width: 75%;
+        }
+    }
+    .ant-divider {
+        margin-bottom: 0;
+    }
+}
+
+.ant-layout-header {
+    height: 58px;
+    line-height: 58px;
+    padding: 0 20px;
+    background-color: #fff;
+    box-shadow: 0 0 9px 5px rgba($color: #001529, $alpha: 0.4);
+}
+
+.Main {
+    padding-top: 58px;
+    &__inner {
+        padding-top: 15px;
+    }
+}
+
+.Post {
+    padding: 10px 0;
+    position: relative;
     img {
-        width: 100px;
-        border-radius: 50%;
-        overflow: hidden;
-        border: 1px solid #000;
+        margin: 0 auto;
+        padding: 1px;
+    }
+    &__dots.slick-dots {
+        bottom: -14px;
+        li {
+            border-radius: 50%;
+            background-color: rgba($color: #1890ff, $alpha: 0.5);
+            width: 10px;
+            height: 10px;
+            overflow: hidden;
+            &.slick-active {
+                border-radius: 5px;
+                width: 20px;
+                button {
+                    background-color: #1890ff;
+                }
+            }
+            button {
+                height: 10px;
+            }
+        }
+    }
+    .ant-empty-image {
+        height: 300px;
+        img {
+            margin: 0 auto;
+        }
+    }
+    &__btn {
+        border: none;
+        position: absolute;
+        padding: 0;
+        background-color: rgba(0, 0, 0, 0.165);
+        bottom: 1px;
+        top: 1px;
+        width: 50px;
+        transition: 0.4s;
+        cursor: pointer;
+        svg {
+            fill: #fff;
+            opacity: 0.5;
+            width: 50px;
+            height: 30px;
+            transition: 0.4s;
+        }
+    }
+    &__prev {
+        left: 0px;
+        opacity: 0;
+        &.--active {
+            opacity: 1;
+        }
+    }
+    &__next {
+        right: 0px;
+        opacity: 0;
+        &.--active {
+            opacity: 1;
+        }
+    }
+    &__heart {
+        button {
+            min-width: auto;
+            width: 30px;
+            height: 30px;
+            border: none;
+            padding: 0;
+            box-shadow: none;
+        }
+        strong {
+            display: block;
+        }
+    }
+    &__comments {
+        a {
+            font-size: 1.1em;
+            color: #000;
+            font-weight: 500;
+        }
+    }
+    &__send-comment {
+        button {
+            border: none;
+            box-shadow: none;
+        }
+    }
+}
+.Modal {
+    .ant-modal-body {
+        padding-left: 0;
+        padding-right: 0;
+    }
+    &__inner {
+        overflow: auto;
+        max-height: 400px;
+    }
+    li {
+        padding-left: 35px;
+    }
+}
+.Profile {
+    width: 100%;
+    h1 {
+        line-height: 1;
+    }
+    &__login {
+        color: #8d8d8d;
+    }
+    &__count {
+        strong {
+            font-size: 1.2em;
+            padding-right: 5px;
+        }
+        span {
+            font-size: 1.2em;
+        }
+    }
+    button {
+        color: #000;
+        // padding: 0;
+        // border: none;
     }
 }

+ 195 - 43
src/actions/index.js

@@ -1,62 +1,214 @@
-import { actionAuthLogin } from "../redux/auth-reducer";
 import { gql } from "../helpers";
+import { actionPromise } from "../redux/redux-thunk";
 
 
 
-const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
-const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
-const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
+export const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
+export const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
+export const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
 
-export const actionPromise = (name, promise) =>
-    async dispatch => {
-        dispatch(actionPending(name))
-        try {
-            let data = await promise
-            dispatch(actionResolved(name, data))
-            return data
-        }
-        catch (error) {
-            dispatch(actionRejected(name, error))
-        }
-    }
+export const actionAuthLogin = (token, remember) => ({ type: 'AUTH_LOGIN', token, remember })
+export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
+
+export const actionAboutMeAC = (data) => ({ type: 'ABOUTME-DATA-ADD', data })
+export const actionUpdateMyAvatart = (data) => ({ type: 'ABOUTME-UPDATE-AVATAR', data })
+export const actionAddPostsFeedAC = (count, newResult, userData) => ({ type: 'ADD-POSTS-FEED', newResult, userData, count })
+export const actionRemovePostsFeedAC = () => ({ type: 'REMOVE-POSTS-FEED' })
+
+export const actionAddLikePostAC = (postId, newResult) => ({ type: 'ADD-POST-LIKE', postId, newResult })
+export const actionRemoveLikePostAC = (postId, newResult) => ({ type: 'REMOVE-POST-LIKE', postId, newResult })
+export const actionAddCommentAC = (postId, newResult) => ({ type: 'ADD-COMMENT', postId, newResult })
 
+export const actionUpdateFollowersAC = (newResult) => ({ type: 'UPDATE-FOLLOWERS', newResult })
+
+//****************---Action Authirization ---*************************//
 
 export const actionLogin = (login, password) =>
     actionPromise('login', gql(`query login($login:String!, $password:String!){
             login(login:$login, password:$password)
         }`, { login, password }))
 
-export const actionFullLogin = (login, password) =>
-    async dispatch => {
-        let token = await dispatch(actionLogin(login, password))
-        if (token) {
-            dispatch(actionAuthLogin(token))
-        }
-    }
+export const actionRegister = (login, password) =>
+    actionPromise('register', gql(`mutation rega ($login:String!, $password:String!){
+                                    createUser(login: $login, password: $password){
+                                        _id login
+                                    }
+                                }`, { login, password }))
 
-export const actionProfilData = (_id) =>
-    actionPromise('dataProfileAuth', gql(`query userOned($id:String!){
-                        UserFindOne(query: $id){
-                            _id  login avatar{ _id url }
-    }
-}`, { id: JSON.stringify([{ _id }]) }))
-
-export const myFolowingPosts = () =>
-    actionPromise('followingPosts', gql(`query allposts($query: String!){
-        PostFind(query: $query){
-            _id, text, title,
-            owner{_id, nick, login, avatar {url}
-            }, 
-            images{url},
-            comments{text},
+export const actionAboutMe = (id) =>
+    actionPromise('aboutMe', gql(`query userOned($myID:String!){
+                        UserFindOne(query: $myID){
+                            _id  login nick
+                            avatar { _id url }
+                            following{ _id}
+                        }
+                }`, { myID: JSON.stringify([{ ___owner: id }]) }))
+
+
+
+
+export const actionMyFolowingPosts = (skip, myFollowing) =>
+    actionPromise('followingPosts',
+        gql(`query allposts($query: String!){
+        PostFind(query:$query){
+            _id, text, title
+            owner{_id, nick, login, avatar {url}}
+            likes { _id owner {_id}}   
+            images{url _id}
+            comments{_id text owner{_id nick login} likes{_id}}
             createdAt
         }
     }`, {
-        query: JSON.stringify([{},
+            query: JSON.stringify([{ ___owner: { $in: myFollowing } },
+            {
+                sort: [{ _id: -1 }],
+                skip: [skip || 0],
+                limit: [10]
+            }])
+        }))
+
+
+// 
+
+
+//****************---Action FindUsers ---*************************//
+
+export const actionFindUsers = (value) =>
+    actionPromise('findUsersAll', gql(`query findUsersAll($query:String!) {
+                                UserFind(query: $query) {
+                                    _id login nick 
+                                    avatar { _id url } 
+                                }
+    }`, {
+        query: JSON.stringify([{
+            $or: [{ nick: `/${value}/` }, { login: `/${value}/` }]
+        },
+        {
+            sort: [{ login: 1 }]
+        },
+        ])
+    }))
+
+//****************---Action Like ---*************************//
+
+export const actionRemoveLikePost = (_id) =>
+    actionPromise('removelikePost', gql(`mutation LikeRemove($like:LikeInput){
+        LikeDelete(like:$like){
+            _id
+        }
+    }`, { like: { _id } }))
+
+export const actionAddLikePost = (_id) =>
+    actionPromise('likePost', gql(`mutation LikePost($like:LikeInput){
+        LikeUpsert(like:$like){
+            _id
+        }
+    }`, { like: { post: { _id } } }))
+
+export const actionMyLikePost = (postId) =>
+    actionPromise('myLikes', gql(`query likeFindPost ($id:String!){
+        PostFindOne(query:$id){
+        likes { _id owner {_id}} 
+        }
+    }`, { id: JSON.stringify([{ _id: postId }]) }))
+
+
+//****************---Action Comment ---*************************//
+
+export const actionAddComment = (postId, text) =>
+    actionPromise('addcomment', gql(`mutation addcomment($comment: CommentInput ){
+        CommentUpsert(comment:$comment){
+            _id text
+        }
+    }`, { comment: { post: { _id: postId }, text } }))
+
+export const actionFindComment = (postId) =>
+    actionPromise('findCommentPost', gql(`query commentFindPost ($id:String!){
+        PostFindOne(query:$id){
+         comments{_id text owner{_id nick login} likes{_id}}
+        }
+    }`, { id: JSON.stringify([{ _id: postId }]) }))
+
+//****************---Action ProfileData ---*************************//
+
+export const actionProfilePageData = (_id) =>
+    actionPromise('userOneData', gql(` query userOned($id:String!){
+                        UserFindOne(query: $id){
+                            _id  login nick
+                            avatar { _id url }     
+                            createdAt
+                            followers {_id nick login}
+                            following {_id nick login}
+                }
+            } `, { id: JSON.stringify([{ _id }]) }))
+
+export const actionProfilePagePost = (_id, skip) =>
+    actionPromise('userOneDataPosts', gql(` query userOned($id:String!){
+                PostFind(query:$id){
+                    _id   images{url _id}
+                }
+                }`, {
+        id: JSON.stringify([{
+            ___owner: _id
+        },
         {
             sort: [{ _id: -1 }],
-            skip: [3],
-            limit: [150],
+            skip: [skip || 0],
+            limit: [10]
+        }])
+    }))
+
+export const actionProfilePostCount = (_id) =>
+    actionPromise('userPostsCount', gql(` query userPostsCount($id:String!){
+                PostCount(query:$id)
+                }`, { id: JSON.stringify([{ ___owner: { $in: _id } }]) }))
+
+//****************---Action ProfileData ---*************************//
+//  
+export const actionUpdateMyFollowing = (_id) =>
+    actionPromise('upDateFollowing', gql(` query followers($id:String!){
+        UserFindOne(query: $id){
+                            following {_id nick login}
         }
-        ])
-    }))
+    }`, { id: JSON.stringify([{ _id }]) }))
+
+
+export const actionUpdateFollowers = (_id) =>
+    actionPromise('upDateFollowers', gql(` query followers($id:String!){
+        UserFindOne(query: $id){
+                            followers {_id nick login}
+        }
+    }`, { id: JSON.stringify([{ _id }]) }))
+
+export const actionSubscribe = (myID, myFollowing, userId) =>
+    actionPromise('subscribe', gql(`mutation following($user:UserInput){
+        UserUpsert( user:$user){
+            following{_id}
+        }
+      }`, { user: { _id: myID, following: [...myFollowing || [], { _id: userId }] } }))
+
+export const actionUnSubscribe = (myID, myFollowing) =>
+    actionPromise('unSubscribe', gql(`mutation followingUn($user:UserInput){
+        UserUpsert( user:$user){
+            following{_id}
+        }
+      }`, { user: { _id: myID, following: [...myFollowing] } }))
+
+
+
+//****************---Action Upload Images ---*************************//
+
+
+export const actionSetAvatar = (file, id) =>
+    actionPromise('uploadPhoto', gql(`mutation avaUpsert($ava: UserInput){
+                UserUpsert(user: $ava){
+                    _id avatar {_id}
+                }
+              }`, { ava: { _id: id, avatar: { _id: file._id } } })
+    )
+export const actionGetAvatar = (id) =>
+    actionPromise('uploadPhoto', gql(`query userOned($myID: String!){
+        UserFindOne(query: $myID) {
+                            avatar { _id url }
+        }
+    }`, { myID: JSON.stringify([{ ___owner: id }]) }))

+ 89 - 0
src/components/Authorization.jsx

@@ -0,0 +1,89 @@
+import React  from 'react'
+import authBg from '../images/authBg.png'
+import { connect } from 'react-redux'
+import { NavLink} from 'react-router-dom'
+
+import { Form, Input, Button, Row, Col, Card, Divider, Checkbox } from 'antd';
+import { UserOutlined, LockOutlined } from '@ant-design/icons';
+import { actionFullLogin, actionFullRegister } from '../redux/redux-thunk';
+
+const FormInput = ({ buttonTitle, onSignIn }) => {
+    const onFinish = ({ login, password, remember }) => {
+        onSignIn(login, password, remember)
+    };
+    return (
+        <Form
+            name="normal_login"
+            className="login-form"
+            initialValues={{
+                remember: true,
+            }}
+            labelCol={{ flex: '25px' }}
+            layout={'vertical'}
+            size='middle'
+            onFinish={onFinish}
+        >
+            <Form.Item
+                name="login"
+                label='Login'
+                rules={[
+                    {
+                        required: true,
+                        message: 'Please input your Username!',
+                    },
+                ]}
+            >
+                <Input prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Username" />
+            </Form.Item>
+            <Form.Item
+                name="password"
+                label='Password'
+                rules={[
+                    {
+                        required: true,
+                        message: 'Please input your Password!',
+                    },
+                ]}
+            >
+                <Input.Password
+                    prefix={<LockOutlined className="site-form-item-icon" />}
+                    type="password"
+                    placeholder="Password"
+                />
+            </Form.Item>
+            <Form.Item
+                name="remember"
+                valuePropName="checked"
+            >
+                <Checkbox>Remember me</Checkbox>
+            </Form.Item>
+            <Form.Item >
+                <Button type="primary" className="login-form-button" htmlType="submit">
+                    {buttonTitle}
+                </Button>
+            </Form.Item>
+        </Form>
+    )
+}
+const CLoginForm = connect(null, { onSignIn: actionFullLogin})(FormInput)
+const CRegisterForm = connect(null, { onSignIn: actionFullRegister})(FormInput)
+
+export const Authorization = ({ match: { params: { _id } } }) => {
+    return (
+        <div className='Authorization' style={{ backgroundImage: `url(${authBg})` }}>
+            <Row justify="end" align="middle" className='Authorization__form'>
+                <Col >
+                    <Card style={{ width: 380 }} >
+                        <NavLink activeClassName='active' to={`/auth/login`}><span>Log In</span></NavLink>
+                        <Divider type="vertical" />
+                        <NavLink activeClassName='active' to={'/auth/registration'}>Registration</NavLink>
+                        <Divider>{_id === 'login' ? 'Log in' : 'Registration'}</Divider >
+                        {_id === 'login' ? <CLoginForm buttonTitle={'Sign In'} /> : <CRegisterForm buttonTitle={'Sign up'} />}
+                    </Card>
+                </Col>
+            </Row >
+        </div>
+    )
+}
+
+

File diff suppressed because it is too large
+ 83 - 29
src/components/header/Header.jsx


+ 41 - 5
src/components/header/Search.jsx

@@ -1,10 +1,46 @@
+import { Empty, Input, Popover } from 'antd'
 import React from 'react'
+import { connect } from 'react-redux';
+import { Link } from 'react-router-dom';
+import { actionFindUsers } from '../../actions';
+import { UserAvatar } from './Header';
 
-export const Search = () => {
+const { Search } = Input;
+const FindUsersResult = ({ usersRes }) => {
+    return <div className='Header__search-drop' >
+        {
+            usersRes.length === 0 ?
+                <Empty /> :
+                usersRes.map(u => {
+                    return (<Link
+                        className='Header__search-link'
+                        key={u._id}
+                        to={`/profile/${u._id}`} >
+                        <UserAvatar avatar={u.avatar} login={u.login} nick={u.nick} avatarSize={'40px'} />
+                        <strong>{u?.nick || u?.login || 'User'}</strong>
+                    </Link>)
+                })
+        }
+    </div >
+}
+
+export const FieldSearch = ({ usersRes, findUsers }) => {
     return (
-        <div className='Search'>
-            <input />
-            <button>Lupa</button>
-        </div>
+        <>
+            <Popover placement="bottom"
+                content={<FindUsersResult usersRes={usersRes} />}
+                destroyTooltipOnHide={true}
+                trigger="focus">
+                <></>
+                <Search className='Header__search'
+                    onSearch={value => findUsers(value)}
+                    placeholder="Search users"
+                    allowClear
+                    enterButton="Search"
+                    enterButton />
+            </Popover>
+        </>
     )
 }
+
+export const CFieldSearch = connect(state => ({ usersRes: state.promise?.findUsersAll?.payload || [] }), { findUsers: actionFindUsers })(FieldSearch) 

+ 59 - 0
src/components/main/Add.js

@@ -0,0 +1,59 @@
+import { useState } from 'react';
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
+import { connect } from 'react-redux';
+import { Upload, message } from 'antd';
+import { backURL, gql } from '../../helpers';
+import { actionSetAvatar } from '../../actions';
+import { actionFullSetAvatar } from '../../redux/redux-thunk';
+import { Loo } from './Loo';
+
+const Add = ({ imageUrl, onUploadFile }) => {
+    const [loading, setLoading] = useState(false)
+    const [imageLoad, setImageLoad] = useState(false)
+    const props = {
+        name: 'photo',
+        action: `${backURL}/upload`,
+        headers: localStorage.authToken || sessionStorage.authToken ? { Authorization: 'Bearer ' + (localStorage.authToken || sessionStorage.authToken) } : {}
+    }
+
+    const handleChange = async ({ file }) => {
+
+        if (file.status === 'uploading') {
+            setLoading(true)
+        }
+        if (file.status === 'done') {
+            message.success(`${file.name} file uploaded successfully`);
+            await onUploadFile(file.response)
+            console.log(file);
+            setImageLoad(true)
+            setLoading(false)
+        } else if (file.status === 'error') {
+            message.error(`${file.name} file upload failed.`);
+        }
+    }
+
+    return (
+        <>
+            <Upload {...props}
+                listType="picture-card"
+                showUploadList={false}
+                onChange={handleChange}
+                className="avatar-uploader">
+                {imageLoad ?
+                    <img src={`${backURL + '/' + imageUrl}`} alt="avatar" style={{ width: '100%' }} /> :
+                    <div>
+                        {loading ? <LoadingOutlined /> : <PlusOutlined />}
+                        <div style={{ marginTop: 8 }}>Upload</div>
+                    </div>}
+            </Upload>
+            <hr />
+            <hr />
+            <hr />
+            <hr />
+            <Loo />
+        </>
+
+    )
+}
+
+export const CAdd = connect(state => ({ imageUrl: state?.myData?.avatar?.url }), { onUploadFile: actionFullSetAvatar })(Add)

+ 109 - 0
src/components/main/Loo.js

@@ -0,0 +1,109 @@
+import { Upload, Modal } from 'antd';
+import { PlusOutlined } from '@ant-design/icons';
+import React from 'react';
+import { backURL } from '../../helpers';
+
+function getBase64(file) {
+    return new Promise((resolve, reject) => {
+   
+        const reader = new FileReader();
+        reader.readAsDataURL(file);
+        reader.onload = () => resolve(reader.result);
+        reader.onerror = error => reject(error);
+    });
+}
+
+export class Loo extends React.Component {
+    state = {
+        previewVisible: false,
+        previewImage: '',
+        previewTitle: '',
+        fileList: [
+            {
+                uid: '-1',
+                name: 'image.png',
+                status: 'done',
+                url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+            },
+            {
+                uid: '-2',
+                name: 'image.png',
+                status: 'done',
+                url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+            },
+            {
+                uid: '-3',
+                name: 'image.png',
+                status: 'done',
+                url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+            },
+            {
+                uid: '-4',
+                name: 'image.png',
+                status: 'done',
+                url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+            },
+            {
+                uid: '-xxx',
+                percent: 50,
+                name: 'image.png',
+                status: 'uploading',
+                url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+            },
+            {
+                uid: '-5',
+                name: 'image.png',
+                status: 'error',
+            },
+        ],
+    };
+
+    handleCancel = () => this.setState({ previewVisible: false });
+
+    handlePreview = async file => {
+        if (!file.url && !file.preview) {
+            file.preview = await getBase64(file.originFileObj);
+        }
+
+        this.setState({
+            previewImage: file.url || file.preview,
+            previewVisible: true,
+            previewTitle: file.name || file.url.substring(file.url.lastIndexOf('/') + 1),
+        });
+    };
+
+    handleChange = ({ fileList }) => this.setState({ fileList });
+
+    render() {
+        const { previewVisible, previewImage, fileList, previewTitle } = this.state;
+        const uploadButton = (
+            <div>
+                <PlusOutlined />
+                <div style={{ marginTop: 8 }}>Upload</div>
+            </div>
+        );
+        return (
+            <>
+                <Upload
+                name='photo'
+                    action={`${backURL}/upload`}
+                    listType="picture-card"
+                    fileList={fileList}
+                    onPreview={this.handlePreview}
+                    onChange={this.handleChange}
+                >
+                    {fileList.length >= 8 ? null : uploadButton}
+                </Upload>
+                <Modal
+                    visible={previewVisible}
+                    title={previewTitle}
+                    footer={null}
+                    onCancel={this.handleCancel}
+                >
+                    <img alt="example" style={{ width: '100%' }} src={previewImage} />
+                </Modal>
+            </>
+        );
+    }
+}
+

+ 0 - 52
src/components/main/MainContent.js

@@ -1,52 +0,0 @@
-import React, { useEffect } from 'react'
-import { connect } from 'react-redux'
-import { Link } from 'react-router-dom'
-import { myFolowingPosts } from '../../actions'
-import { backURL } from '../../helpers'
-
-const Post = ({ postData: { text, title, owner, images, createdAt, comments } }) => {
-    const date = new Date(createdAt * 1)
-    const resultDate = new Intl.DateTimeFormat('default').format(date)
-    return (
-        <div style={{ padding: '50px '}}>
-            <a href='/asd'>asd</a>
-            <Link to={`/${owner?._id}`} className='owner'>
-                {owner?.avatar?.url && <img src={backURL + '/' + owner.avatar.url} alt='avatar' />}
-                <span>{owner?.login}</span>
-            </Link>
-            {images && images[0] && images[0].url && < img src={backURL + '/' + images[0].url} alt='post' />}
-            <div>
-                <span>
-                    {resultDate}
-                </span>
-                <span>
-                    {title}
-                </span>
-                <span>
-                    {text}
-                </span>
-            </div>
-            {comments ? 'yes' : 'no'}
-        </div>
-    )
-
-}
-
-
-
-
-const MainContent = ({ posts, postsFollowing }) => {
-
-    useEffect(() => {
-        postsFollowing()
-    }, [])
-
-    console.log(posts);
-    return (
-        <div>
-            {posts.map(p => <Post key={p._id} postData={p} />)}
-        </div>
-    )
-}
-
-export const CMainContent = connect(state => ({ posts: state.promise?.followingPosts?.payload || [] }), { postsFollowing: myFolowingPosts })(MainContent)

+ 266 - 0
src/components/main/MainPostsFeed.js

@@ -0,0 +1,266 @@
+import { Card, Col, Row, Carousel, Empty, Button } from 'antd'
+import React, { createRef, useEffect, useState } from 'react'
+import { connect } from 'react-redux'
+import { Link } from 'react-router-dom'
+import { backURL } from '../../helpers'
+import { UserAvatar } from '../header/Header'
+import nodata from '../../images/nodata.png'
+import { HeartFilled, HeartOutlined, LeftCircleOutlined, RightCircleOutlined, SendOutlined, } from '@ant-design/icons'
+import Paragraph from 'antd/lib/typography/Paragraph'
+import Text from 'antd/lib/typography/Text'
+import TextArea from 'antd/lib/input/TextArea'
+import { actionAddPostsFeed, actionFullAddComment, actionFullAddLikePost, actionFullRemoveLikePost } from '../../redux/redux-thunk'
+import { actionRemovePostsFeedAC } from '../../actions'
+
+const PostTitle = ({ owner }) =>
+    <Link to={`/profile/${owner?._id}`} className='owner'>
+        <Row justify="start" align='middle'>
+            <Col >
+                <UserAvatar avatar={owner?.avatar} login={owner?.login} avatarSize={'45px'} nick={owner?.nick} />
+            </Col>
+            <Col offset={1}>
+                <span>{owner?.nick ? owner.nick : owner?.login ? owner.login : 'Null'}</span>
+            </Col>
+        </Row>
+    </Link >
+
+class PostImage extends React.Component {
+    constructor(props) {
+        super(props);
+        this.carouselRef = createRef();
+        this.state = {
+            movePrev: false,
+            moveNext: false
+        }
+    }
+
+    handleNext = () => this.carouselRef.current.next(this);
+
+    handlePrev = () => this.carouselRef.current.prev(this);
+
+    moveOnDivArray = (length, index) => {
+        if (length === 1) {
+            this.setState({ movePrev: false, moveNext: false })
+        } else if (index === 0) {
+            this.setState({ movePrev: false, moveNext: true })
+        } else if (index === length - 1 && length > 1) {
+            this.setState({ movePrev: true, moveNext: false })
+        } else {
+            this.setState({ movePrev: true, moveNext: true })
+        }
+    }
+
+    downOnDivArray = () => this.setState({ movePrev: false, moveNext: false })
+
+    render() {
+
+        const { images } = this.props
+        return (
+            <Carousel ref={this.carouselRef}
+                effect="fade"
+                infinite={false}
+                dots={{ className: 'Post__dots' }
+                }>
+                {!!images ?
+                    images.map((i, index) => i?.url ? <div key={i._id}
+                        onMouseEnter={() => this.moveOnDivArray(images.length, index)}
+                        onMouseLeave={this.downOnDivArray}>
+                        <button onClick={() => this.handlePrev()}
+                            className={`Post__prev Post__btn ${this.state.movePrev ? '--active' : ''}`}><LeftCircleOutlined /></button>
+                        <button onClick={() => this.handleNext()}
+                            className={`Post__next Post__btn ${this.state.moveNext ? '--active' : ''}`}><RightCircleOutlined /></button>
+                        <img src={backURL + '/' + i.url} />
+                    </div> :
+                        <Empty key={i._id} image={nodata} description={false} />) :
+                    <Empty image={nodata} description={false} />
+                }
+            </Carousel >
+        );
+    }
+}
+
+const HeartLike = ({ styleFontSize, likeStatus, changeLike }) =>
+    <Button
+        onClick={() => changeLike()}
+        type="none"
+        shape="circle"
+        icon={
+            likeStatus ?
+                <HeartFilled style={{ color: '#ff6969', fontSize: `${styleFontSize}` }} /> :
+                <HeartOutlined style={{ color: '#1890ff', fontSize: `${styleFontSize}` }} />}
+    />
+
+const PostUserPanel = ({ myID, postId, likes = [], addLikePost, removeLikePost }) => {
+    let likeStatus
+    let likeId
+    likes.find(l => {
+        if (l.owner._id === myID) {
+            likeStatus = true
+            likeId = l._id
+        } else {
+            likeStatus = false
+        }
+    })
+
+    const changeLike = () => likeStatus ? removeLikePost(likeId, postId) : addLikePost(postId)
+    const styleFontSize = '1.7em'
+
+    return (
+        <>
+            <Row className="Post__panel-btn">
+                <Col className='Post__heart'>
+                    <HeartLike
+                        changeLike={changeLike}
+                        likeStatus={likeStatus}
+                        styleFontSize={styleFontSize} />
+                </Col>
+                <Col>
+                </Col>
+            </Row>
+            {!!likes.length && <strong>Likes: {likes.length}</strong>}
+        </>
+    )
+}
+
+const CPostUserPanel = connect(state => ({
+    myID: state.auth.payload.sub.id || '',
+    myLikes: state?.promise?.myLikes?.payload || [],
+}), {
+    addLikePost: actionFullAddLikePost,
+    removeLikePost: actionFullRemoveLikePost,
+})(PostUserPanel)
+
+const PostDescription = ({ title, description, date }) =>
+    <>
+        <Row justify='space-between'>
+            <Col >
+                {!!title && <Text level={3} strong>{title}</Text>}
+            </Col>
+            <Col >
+                <Text type='secondary'>{date}</Text>
+            </Col>
+        </Row>
+        <Paragraph ellipsis={true ? { rows: 1, expandable: true, symbol: 'more' } : false}>
+            {description}
+        </Paragraph>
+    </>
+
+const Comments = ({ comments }) =>
+    <>
+        {comments && comments.length > 2 &&
+            <Link to={`/#`}>
+                <Text type={'secondary'} level={3}>{`Посмотреть все ${comments.length} комментария`}</Text>
+            </Link>}
+        {comments && <div>
+            <div className='Post__comments'>
+                <Link to={`/#`}>{comments[comments?.length - 2]?.owner?.nick || comments[comments?.length - 2]?.owner?.login}: </Link>
+                <span>{comments[comments?.length - 2]?.text}</span>
+            </div>
+            <div className='Post__comments'>
+                <Link to={`/#`}>{comments[comments?.length - 1]?.owner?.login || comments[comments?.length - 1]?.owner?.login}: </Link>
+                <span>{comments[comments?.length - 1]?.text}</span>
+            </div>
+        </div>}
+    </>
+
+
+const FieldCommentSend = ({ postId, sentComment }) => {
+    const [commentValue, setCommentValue] = useState('')
+    const [error, setError] = useState(false)
+
+    const changeComentTextarea = (e) => {
+        setCommentValue(e.currentTarget.value)
+        setError(false)
+    }
+    const sendCommentValid = (value) => {
+        if (value.trim() !== '') {
+            sentComment(postId, value.trim())
+            setCommentValue('')
+        } else {
+            setError(true)
+        }
+    }
+    return (
+        <>
+            {error && <Text type='danger'>Field is required</Text>}
+            <Row align='middle' className='Post__send-comment'>
+                <Col flex='auto' offset={1}>
+                    <TextArea value={commentValue}
+                        placeholder="Add a comment ..."
+                        autoSize={{ minRows: 1, maxRows: 2 }}
+                        onChange={changeComentTextarea}
+                        bordered={false}
+                    />
+                </Col>
+                <Col span={2}>
+                    <Button
+                        onClick={() => sendCommentValid(commentValue)}
+                        icon={< SendOutlined
+                            style={{ fontSize: '1.2em', opacity: .6 }} />} />
+                </Col>
+            </Row>
+        </>
+    )
+}
+
+const CFieldCommentSend = connect(null, { sentComment: actionFullAddComment })(FieldCommentSend)
+
+const Post = ({ postData: { _id, text, title, owner, images, createdAt = '', comments, likes } }) => {
+    const date = new Date(createdAt * 1)
+    const resultDate = new Intl.DateTimeFormat('default').format(date)
+    return (
+        <div className='Post'>
+            <Card
+                title={<PostTitle owner={owner} />}
+                cover={<PostImage images={images} />}
+                actions={[<CFieldCommentSend postId={_id} />]}
+            >
+                <CPostUserPanel postId={_id} likes={likes} />
+                <PostDescription title={title} description={text} date={resultDate} />
+                <Comments comments={comments} />
+            </Card>
+        </div>
+    )
+}
+
+const MainPostsFeed = ({ posts, countPosts, postsFollowing, postsFollowingRemove, following }) => {
+    const [checkScroll, setCheckScroll] = useState(true)
+
+    useEffect(async () => {
+        if (checkScroll && following.length !== 0) {
+            await postsFollowing(following)
+            setCheckScroll(false)
+        }
+    }, [checkScroll, following])
+
+
+
+    useEffect(() => {
+        document.addEventListener('scroll', scrollHandler)
+        return () => {
+            document.removeEventListener('scroll', scrollHandler)
+            postsFollowingRemove()
+            console.log(posts.length);
+        }
+    }, [])
+
+    const scrollHandler = (e) => {
+        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 500) {
+            setCheckScroll(true)
+        }
+    }
+    return (
+        <>
+            {posts.map(p => <Post key={p._id} postData={p} />)}
+        </>
+    )
+}
+
+export const CMainPostsFeed = connect(state => ({
+    countPosts: state?.postsFeed?.count || 1,
+    posts: state?.postsFeed?.posts || [],
+    following: state?.myData.following || []
+}), {
+    postsFollowing: actionAddPostsFeed,
+    postsFollowingRemove: actionRemovePostsFeedAC,
+})(MainPostsFeed)

+ 160 - 0
src/components/main/Profile.js

@@ -0,0 +1,160 @@
+import { Button, Col, List, Row } from 'antd'
+import Modal from 'antd/lib/modal/Modal'
+import React, { useEffect, useState } from 'react'
+import { connect } from 'react-redux'
+import { Link } from 'react-router-dom'
+import { actionRemovePostsFeedAC } from '../../actions'
+import { backURL } from '../../helpers'
+import { actionFullProfilePageData, actionFullSubscribe, actionFullUnSubscribe } from '../../redux/redux-thunk'
+import { UserAvatar } from '../header/Header'
+
+const ModalFolower = ({ statusModal, data, title }) => {
+
+    const handleCancel = () => statusModal(false);
+
+    return (
+        <Modal className='Modal'
+            title={title}
+            visible={true}
+            destroyOnClose={true}
+            footer={null}
+            onCancel={handleCancel}>
+            <></>
+            <List className='Modal__inner'
+                itemLayout="horizontal"
+                dataSource={data}
+                renderItem={item => (
+                    <List.Item >
+                        <Link to={`/profile/${item._id}`} style={{ width: '100%' }} onClick={handleCancel}>
+                            <Row align='middle' >
+                                <Col>
+                                    <UserAvatar avatar={item.avatar} avatarSize={'40px'} />
+                                </Col>
+                                <Col offset={2}>{item.nick || item.login || 'No Name'}</Col>
+                            </Row>
+                        </Link>
+                    </List.Item>
+                )}
+            />
+        </Modal>
+    )
+}
+
+const CModalFollowers = connect(state => ({ data: state?.postsFeed?.userData?.followers || [] }))(ModalFolower)
+const CModalFollowing = connect(state => ({ data: state?.postsFeed?.userData?.following || [] }))(ModalFolower)
+
+
+const ProfileSetting = ({ myID, userId, followers, onSubsuscribe, onUnSubsuscribe }) => {
+    const followCheck = followers.find(f => f._id === myID && true)
+    return (
+        <Col className='Profile__seting' offset={4}>
+            {!!followCheck ?
+                <Button onClick={() => onUnSubsuscribe(userId)}>UnSubscribe</Button> :
+                <Button onClick={() => onSubsuscribe(userId)} type="primary">Subscribe</Button>}
+        </Col>
+    )
+}
+
+const CProfileSetting = connect(state => ({
+    myID: state?.auth?.payload?.sub.id,
+    followers: state?.postsFeed?.userData?.followers || []
+}), { onSubsuscribe: actionFullSubscribe, onUnSubsuscribe: actionFullUnSubscribe })(ProfileSetting)
+
+const ProfilePageData = ({ data: { _id, avatar, login, nick, followers, following }, count, setFollowing, setFollowers }) => {
+
+    return (
+        <Row className='Profile' align='middle'>
+            <Col span={8}>
+                <UserAvatar avatarSize={'150px'} avatar={avatar} />
+            </Col>
+            <Col span={14} offset={1}>
+                <Row align='top'>
+                    <Col>
+                        <h1>{nick || login || 'No Name'}</h1>
+                        <span className='Profile__login'>{login || '----'}</span>
+                    </Col>
+                    < CProfileSetting userId={_id} />
+                </Row>
+                <Row className='Profile__count' align='middle' justify='space-between'>
+                    <Col >
+                        <strong>{count || '0'}</strong>
+                        <span>Posts</span>
+                    </Col>
+                    <Col >
+                        <Button type="link" onClick={() => setFollowers(true)}>
+                            <strong>{followers?.length || '0'}</strong>
+                            <span>Followers</span>
+                        </Button>
+                    </Col>
+                    <Col >
+                        <Button type="link" onClick={() => setFollowing(true)}>
+                            <strong>{following?.length || '0'}</strong>
+                            <span>Following</span>
+                        </Button>
+                    </Col>
+                </Row>
+            </Col >
+        </Row >
+    )
+}
+
+const CProfilePageData = connect(state => ({
+    data: state?.postsFeed?.userData || {},
+    count: state?.postsFeed?.count || null
+}))(ProfilePageData)
+
+
+const ProfilePagePosts = ({ posts }) => {
+    return (
+        <Row>
+            {posts.map(p => <Col key={p._id}>
+                {p.images && p.images[0] && p.images[0].url && <img src={(backURL + '/' + p?.images[0].url)} alt='post Img' />}
+            </Col>)
+            }
+        </Row >
+    )
+
+}
+
+export const CProfilePagePosts = connect(state => ({ posts: state.postsFeed?.posts || [] }))(ProfilePagePosts)
+
+const ProfilePage = ({ match: { params: { _id } }, posts, countPost, getProfileUser, clearDataProfile }) => {
+    const [followers, setFollowers] = useState(false)
+    const [following, setFollowing] = useState(false)
+    const [checkScroll, setCheckScroll] = useState(true)
+
+    useEffect(() => {
+        document.addEventListener('scroll', scrollHandler)
+        return async () => {
+            document.removeEventListener('scroll', scrollHandler)
+            setCheckScroll(true)
+            await clearDataProfile()
+            console.log(posts.length);
+        }
+    }, [_id])
+    useEffect(async () => {
+        if (checkScroll) {
+            await getProfileUser(_id)
+            setCheckScroll(false)
+        }
+    }, [_id, checkScroll])
+
+    const scrollHandler = (e) => {
+        if (e.target.documentElement.scrollHeight - (e.target.documentElement.scrollTop + window.innerHeight) < 500) {
+            setCheckScroll(true)
+        }
+    }
+
+    return (
+        <>
+            <CProfilePageData setFollowing={setFollowing} setFollowers={setFollowers} />
+            {followers && < CModalFollowers statusModal={setFollowers} title={'Followers'} />}
+            {following && < CModalFollowing statusModal={setFollowing} title={'Following'} />}
+            <CProfilePagePosts />
+        </>
+    )
+}
+
+export const CProfilePage = connect(state => ({
+    posts: state?.postsFeed?.posts || []
+}), { getProfileUser: actionFullProfilePageData, clearDataProfile: actionRemovePostsFeedAC })(ProfilePage)

+ 2 - 1
src/helpers/index.js

@@ -18,7 +18,8 @@ const getGQL = url =>
             method: 'POST',
             headers: {
                 "Content-Type": "application/json",
-                Authorization: localStorage.authToken ? 'Bearer ' + localStorage.authToken : {},
+                ...(localStorage.authToken ? { Authorization: 'Bearer ' + localStorage.authToken } :
+                    sessionStorage.authToken ? { Authorization: 'Bearer ' + sessionStorage.authToken } : {})
             },
             body: JSON.stringify({ query, variables })
         })

二進制
src/images/authBg.png


二進制
src/images/noAva.png


二進制
src/images/nodata.png


File diff suppressed because it is too large
+ 6 - 1
src/logo.svg


+ 0 - 65
src/pages/Authorization.jsx

@@ -1,65 +0,0 @@
-import React, { useState } from 'react'
-import { connect } from 'react-redux'
-import { Link } from 'react-router-dom'
-import { actionFullLogin } from '../actions'
-
-const LogIn = ({ logInput, setLogInput, pasInput, setPasInput , onLogIn}) => {
-    return (
-        <div className='Form'>
-            <div className="LoginForm__inner">
-                <h4>Login</h4>
-                <label >
-                    Login:
-                    <input value={logInput}
-                        onChange={e => setLogInput(e.currentTarget.value)}
-                        placeholder='login' />
-                </label>
-                <label>
-                    Password:
-                    <input value={pasInput}
-                        onChange={e => setPasInput(e.currentTarget.value)}
-                        placeholder='password' />
-                </label>
-                <button onClick={()=>onLogIn(logInput, pasInput )}
-                >Login
-                </button>
-                <Link to={'/registration'}>Registration</Link>
-            </div >
-        </div >
-    )
-}
-
-
-const Form = ({ auth, onLogIn }) => {
-    const [logInput, setLogInput] = useState('')
-    const [pasInput, setPasInput] = useState('')
-
-    return (
-        <div className='UserPanel'>
-            <LogIn
-                logInput={logInput}
-                setLogInput={setLogInput}
-                pasInput={pasInput}
-                setPasInput={setPasInput}
-                onLogIn={onLogIn}
-            />
-
-        </div>
-    )
-}
-
-const CForm = connect(state => ({ auth: state.auth }), { onLogIn: actionFullLogin })(Form)
-
-
-
-
-export const Authorization = () => {
-    return (
-        <div className='Authorization'>
-            asjgsdg
-            <CForm />
-        </div>
-    )
-}
-
-

+ 13 - 19
src/pages/Content.js

@@ -1,27 +1,21 @@
-import React, { useEffect } from 'react'
-import { connect } from 'react-redux'
-import { myFolowingPosts } from '../actions'
-import Header from '../components/header/Header'
-import { CMainContent } from '../components/main/MainContent'
+import { Col, Row } from 'antd'
+import React from 'react'
 
 
 
-const Main = ({ children }) =>
-    <>{children}</>
+
+export const Main = ({ children }) =>
+    <Row justify='center' className='Main'>
+        <Col xs={{ span: 24 }} sm={{ span: 20 }} md={{ span: 16 }} lg={{ span: 14 }} xl={{span:12}}className='Main__inner'>
+            {children}
+        </Col>
+    </Row>
 
 
-export const CMain = connect(null, { postsFollowing: myFolowingPosts })(Main)
 
-export const Content = () => {
-    return (
-        <>
-            <Header />
-            <Main>
-                <CMainContent />
-                {/* <Aside /> */}
-            </Main>
-        </>
-    )
-}
+// export const CMain = connect(null, { postsFollowing: myFolowingPosts })(Main)
 
 
+export const Content = ({ children }) =>
+    <>{children}</>
+

+ 7 - 33
src/redux/auth-reducer.js

@@ -1,17 +1,18 @@
 import { jwtDecode } from '../helpers'
 
 
-export const authReducer = (state, { type, token }) => {
+export const authReducer = (state, { type, token, remember }) => {
     if (!state) {
-        if (localStorage.authToken) {
+        if (localStorage.authToken || sessionStorage.authToken) {
             type = 'AUTH_LOGIN'
-            token = localStorage.authToken
+            token = localStorage.authToken || sessionStorage.authToken
         } else state = {}
     }
 
     if (type === 'AUTH_LOGIN') {
-        localStorage.setItem('authToken', token)
-
+        remember ?
+            localStorage.setItem('authToken', token) :
+            sessionStorage.setItem('authToken', token)
         let payload = jwtDecode(token)
         if (typeof payload === 'object') {
             return {
@@ -23,38 +24,11 @@ export const authReducer = (state, { type, token }) => {
     }
     if (type === 'AUTH_LOGOUT') {
         localStorage.removeItem('authToken')
+        sessionStorage.removeItem('authToken')
         return {}
     }
     return state
 }
 
-export const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token })
-
-export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' })
-
-
-
-// const actionRegister = (login, password) =>
-//     actionPromise('register', gql(`
-//                 mutation reg($login:String, $password:String){
-//                 UserUpsert(user:{
-//                     login:$login,
-//                         password:$password,
-//                         nick:$login}){
-//                 _id login
-//                 }
-//             }
-//             `, { login, password }))
-
-// export const actionFullRegister = (login, password) =>
-//     async dispatch => {
-//         await actionRegister(login, password)
-//         let token = await dispatch(actionLogin(login, password))
-//         if (token) {
-//             dispatch(actionAuthLogin(token))
-//         }
-//     }
-
-
 
 

+ 17 - 0
src/redux/myProfile-reducer.js

@@ -0,0 +1,17 @@
+import React from 'react'
+
+export const myProfileReducer = (state = {}, { type, data }) => {
+    const types = {
+        'ABOUTME-DATA-ADD': () => {
+            return { ...state, ...data }
+        },
+        'ABOUTME-UPDATE-AVATAR': () => {
+            return { ...state, avatar: { ...data } }
+        },
+      
+    }
+    if (type in types) {
+        return types[type]()
+    }
+    return state
+}

+ 56 - 0
src/redux/postFeed-reducer.js

@@ -0,0 +1,56 @@
+import React from 'react'
+
+export const postsFeedReducer = (state = {}, { type, postId, newResult, userData = {}, count = null }) => {
+    const { posts } = state
+    const types = {
+        'ADD-POSTS-FEED': () => {
+            return {
+                ...state,
+                posts: !!posts ? [...posts, ...newResult] : [...newResult],
+                userData: { ...userData },
+                count
+            }
+        },
+        'REMOVE-POSTS-FEED': () => {
+            return {
+                ...state,
+                posts: [],
+                userData: {},
+                count: 0
+            }
+        },
+        'ADD-POST-LIKE': () => {
+            return {
+                ...state,
+                posts: posts.map(p => p._id === postId ? p = { ...p, likes: [...newResult] } : p),
+            }
+        },
+        'REMOVE-POST-LIKE': () => {
+            return {
+                ...state,
+                posts: posts.map(p => p._id === postId ? p = { ...p, likes: [...newResult] } : p),
+            }
+        },
+        'ADD-COMMENT': () => {
+            return {
+                ...state,
+                posts: posts.map(p => p._id === postId ? { ...p, comments: [...newResult] } : p)
+            }
+        },
+        'UPDATE-FOLLOWERS': () => {
+            return {
+                ...state,
+                userData: { ...state.userData, followers: [...newResult] }
+            }
+        }
+    }
+    if (type in types) {
+        return types[type]()
+    }
+    return state
+}
+
+
+
+
+

+ 14 - 3
src/redux/redux-store.js

@@ -1,10 +1,21 @@
 import { createStore, combineReducers, applyMiddleware } from 'redux';
 import thunk from 'redux-thunk';
 import { authReducer } from './auth-reducer';
+import { myProfileReducer } from './myProfile-reducer';
+import { postsFeedReducer } from './postFeed-reducer';
 import { promiseReducer } from './promise-reducer';
+import { actionFullAboutMe } from './redux-thunk';
 
-export const store = createStore(combineReducers({
-    promise: promiseReducer,
+
+
+const store = createStore(combineReducers({
     auth: authReducer,
+    promise: promiseReducer,
+    myData: myProfileReducer,
+    postsFeed: postsFeedReducer,
 }),
-    applyMiddleware(thunk))
+    applyMiddleware(thunk))
+
+store.dispatch(actionFullAboutMe())
+
+export default store;

File diff suppressed because it is too large
+ 133 - 0
src/redux/redux-thunk.js


+ 594 - 3
yarn.lock

@@ -2,6 +2,40 @@
 # yarn lockfile v1
 
 
+"@ant-design/colors@^6.0.0":
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-6.0.0.tgz#9b9366257cffcc47db42b9d0203bb592c13c0298"
+  integrity sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==
+  dependencies:
+    "@ctrl/tinycolor" "^3.4.0"
+
+"@ant-design/icons-svg@^4.2.1":
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a"
+  integrity sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==
+
+"@ant-design/icons@^4.7.0":
+  version "4.7.0"
+  resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.7.0.tgz#8c3cbe0a556ba92af5dc7d1e70c0b25b5179af0f"
+  integrity sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==
+  dependencies:
+    "@ant-design/colors" "^6.0.0"
+    "@ant-design/icons-svg" "^4.2.1"
+    "@babel/runtime" "^7.11.2"
+    classnames "^2.2.6"
+    rc-util "^5.9.4"
+
+"@ant-design/react-slick@~0.28.1":
+  version "0.28.4"
+  resolved "https://registry.yarnpkg.com/@ant-design/react-slick/-/react-slick-0.28.4.tgz#8b296b87ad7c7ae877f2a527b81b7eebd9dd29a9"
+  integrity sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==
+  dependencies:
+    "@babel/runtime" "^7.10.4"
+    classnames "^2.2.5"
+    json2mq "^0.2.0"
+    lodash "^4.17.21"
+    resize-observer-polyfill "^1.5.0"
+
 "@apideck/better-ajv-errors@^0.3.1":
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.2.tgz#cd6d3814eda8aee38ee2e3fa6457be43af4f8361"
@@ -1015,7 +1049,7 @@
     core-js-pure "^3.19.0"
     regenerator-runtime "^0.13.4"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
   integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
@@ -1065,6 +1099,11 @@
   resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4"
   integrity sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==
 
+"@ctrl/tinycolor@^3.4.0":
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f"
+  integrity sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ==
+
 "@eslint/eslintrc@^1.0.5":
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318"
@@ -1315,6 +1354,50 @@
     schema-utils "^3.0.0"
     source-map "^0.7.3"
 
+"@redux-saga/core@^1.1.3":
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.1.3.tgz#3085097b57a4ea8db5528d58673f20ce0950f6a4"
+  integrity sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg==
+  dependencies:
+    "@babel/runtime" "^7.6.3"
+    "@redux-saga/deferred" "^1.1.2"
+    "@redux-saga/delay-p" "^1.1.2"
+    "@redux-saga/is" "^1.1.2"
+    "@redux-saga/symbols" "^1.1.2"
+    "@redux-saga/types" "^1.1.0"
+    redux "^4.0.4"
+    typescript-tuple "^2.2.1"
+
+"@redux-saga/deferred@^1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@redux-saga/deferred/-/deferred-1.1.2.tgz#59937a0eba71fff289f1310233bc518117a71888"
+  integrity sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ==
+
+"@redux-saga/delay-p@^1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@redux-saga/delay-p/-/delay-p-1.1.2.tgz#8f515f4b009b05b02a37a7c3d0ca9ddc157bb355"
+  integrity sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g==
+  dependencies:
+    "@redux-saga/symbols" "^1.1.2"
+
+"@redux-saga/is@^1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@redux-saga/is/-/is-1.1.2.tgz#ae6c8421f58fcba80faf7cadb7d65b303b97e58e"
+  integrity sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w==
+  dependencies:
+    "@redux-saga/symbols" "^1.1.2"
+    "@redux-saga/types" "^1.1.0"
+
+"@redux-saga/symbols@^1.1.2":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@redux-saga/symbols/-/symbols-1.1.2.tgz#216a672a487fc256872b8034835afc22a2d0595d"
+  integrity sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ==
+
+"@redux-saga/types@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204"
+  integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==
+
 "@rollup/plugin-babel@^5.2.0":
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
@@ -2231,6 +2314,55 @@ ansi-styles@^5.0.0:
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
   integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
 
+antd@^4.18.2:
+  version "4.18.2"
+  resolved "https://registry.yarnpkg.com/antd/-/antd-4.18.2.tgz#e4d03b2229b1f4b3b204e5e5539693aa653d3b7e"
+  integrity sha512-omezo7dtskrfGoSOoHYbA2jyaSVDd2UsxS5WM8GWyuIDtguMqlTDFKF/5204s3IjXnSUiJLOuDOWSLPMyVwHrA==
+  dependencies:
+    "@ant-design/colors" "^6.0.0"
+    "@ant-design/icons" "^4.7.0"
+    "@ant-design/react-slick" "~0.28.1"
+    "@babel/runtime" "^7.12.5"
+    "@ctrl/tinycolor" "^3.4.0"
+    array-tree-filter "^2.1.0"
+    classnames "^2.2.6"
+    copy-to-clipboard "^3.2.0"
+    lodash "^4.17.21"
+    memoize-one "^6.0.0"
+    moment "^2.25.3"
+    rc-cascader "~3.0.0-alpha.3"
+    rc-checkbox "~2.3.0"
+    rc-collapse "~3.1.0"
+    rc-dialog "~8.6.0"
+    rc-drawer "~4.4.2"
+    rc-dropdown "~3.2.0"
+    rc-field-form "~1.22.0-2"
+    rc-image "~5.2.5"
+    rc-input-number "~7.3.0"
+    rc-mentions "~1.6.1"
+    rc-menu "~9.1.1"
+    rc-motion "^2.4.4"
+    rc-notification "~4.5.7"
+    rc-pagination "~3.1.9"
+    rc-picker "~2.5.17"
+    rc-progress "~3.2.1"
+    rc-rate "~2.9.0"
+    rc-resize-observer "^1.1.2"
+    rc-select "~14.0.0-alpha.15"
+    rc-slider "~9.7.4"
+    rc-steps "~4.1.0"
+    rc-switch "~3.2.0"
+    rc-table "~7.21.0"
+    rc-tabs "~11.10.0"
+    rc-textarea "~0.3.0"
+    rc-tooltip "~5.1.1"
+    rc-tree "~5.3.5"
+    rc-tree-select "~5.0.0-alpha.2"
+    rc-trigger "^5.2.10"
+    rc-upload "~4.3.0"
+    rc-util "^5.14.0"
+    scroll-into-view-if-needed "^2.2.25"
+
 anymatch@^3.0.3, anymatch@~3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@@ -2290,6 +2422,11 @@ array-includes@^3.1.3, array-includes@^3.1.4:
     get-intrinsic "^1.1.1"
     is-string "^1.0.7"
 
+array-tree-filter@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190"
+  integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==
+
 array-union@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
@@ -2323,6 +2460,11 @@ ast-types-flow@^0.0.7:
   resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
   integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
 
+async-validator@^4.0.2:
+  version "4.0.7"
+  resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-4.0.7.tgz#034a0fd2103a6b2ebf010da75183bec299247afe"
+  integrity sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ==
+
 async@0.9.x:
   version "0.9.2"
   resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
@@ -2772,6 +2914,11 @@ cjs-module-lexer@^1.0.0:
   resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
   integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
 
+classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
+  integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
+
 clean-css@^5.2.2:
   version "5.2.2"
   resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.2.tgz#d3a7c6ee2511011e051719838bdcf8314dc4548d"
@@ -2903,6 +3050,11 @@ compression@^1.7.4:
     safe-buffer "5.1.2"
     vary "~1.1.2"
 
+compute-scroll-into-view@^1.0.17:
+  version "1.0.17"
+  resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab"
+  integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==
+
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -2947,6 +3099,13 @@ cookie@0.4.1:
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
   integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
 
+copy-to-clipboard@^3.2.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
+  integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
+  dependencies:
+    toggle-selection "^1.0.6"
+
 core-js-compat@^3.18.0, core-js-compat@^3.19.1:
   version "3.20.2"
   resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.20.2.tgz#d1ff6936c7330959b46b2e08b122a8b14e26140b"
@@ -3226,6 +3385,16 @@ data-urls@^2.0.0:
     whatwg-mimetype "^2.3.0"
     whatwg-url "^8.0.0"
 
+date-fns@2.x:
+  version "2.28.0"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
+  integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
+
+dayjs@1.x:
+  version "1.10.7"
+  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
+  integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
+
 debug@2.6.9, debug@^2.6.0, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -3425,6 +3594,11 @@ dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9:
   resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz#caa6d08f60388d0bb4539dd75fe458a9a1d0014c"
   integrity sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g==
 
+dom-align@^1.7.0:
+  version "1.12.2"
+  resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.2.tgz#0f8164ebd0c9c21b0c790310493cd855892acd4b"
+  integrity sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==
+
 dom-converter@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
@@ -5492,6 +5666,13 @@ json-stable-stringify-without-jsonify@^1.0.1:
   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
   integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
 
+json2mq@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a"
+  integrity sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=
+  dependencies:
+    string-convert "^0.2.0"
+
 json5@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
@@ -5743,6 +5924,11 @@ memfs@^3.1.2, memfs@^3.2.2:
   dependencies:
     fs-monkey "1.0.3"
 
+memoize-one@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
+  integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
+
 merge-descriptors@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -5837,6 +6023,11 @@ mkdirp@^0.5.5, mkdirp@~0.5.1:
   dependencies:
     minimist "^1.2.5"
 
+moment@^2.24.0, moment@^2.25.3:
+  version "2.29.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
+  integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
+
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -6958,6 +7149,353 @@ raw-body@2.4.2:
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
+rc-align@^4.0.0:
+  version "4.0.11"
+  resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-4.0.11.tgz#8198c62db266bc1b8ef05e56c13275bf72628a5e"
+  integrity sha512-n9mQfIYQbbNTbefyQnRHZPWuTEwG1rY4a9yKlIWHSTbgwI+XUMGRYd0uJ5pE2UbrNX0WvnMBA1zJ3Lrecpra/A==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "2.x"
+    dom-align "^1.7.0"
+    lodash "^4.17.21"
+    rc-util "^5.3.0"
+    resize-observer-polyfill "^1.5.1"
+
+rc-cascader@~3.0.0-alpha.3:
+  version "3.0.0-alpha.6"
+  resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.0.0-alpha.6.tgz#62cba394310bfd8844fa1a96cdf117bdffd8f911"
+  integrity sha512-DdMtH7KO5qvNoKl1gVo2I/5or6xBmPYWxVgd22HuhHemZcCSiSXutKCSAkr2A2R0td8moQYSySmgAGrHJdmbDQ==
+  dependencies:
+    "@babel/runtime" "^7.12.5"
+    array-tree-filter "^2.1.0"
+    classnames "^2.3.1"
+    rc-select "~14.0.0-alpha.8"
+    rc-tree "~5.3.4"
+    rc-util "^5.6.1"
+
+rc-checkbox@~2.3.0:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/rc-checkbox/-/rc-checkbox-2.3.2.tgz#f91b3678c7edb2baa8121c9483c664fa6f0aefc1"
+  integrity sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.1"
+
+rc-collapse@~3.1.0:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-3.1.2.tgz#76028a811b845d03d9460ccc409c7ea8ad09db14"
+  integrity sha512-HujcKq7mghk/gVKeI6EjzTbb8e19XUZpakrYazu1MblEZ3Hu3WBMSN4A3QmvbF6n1g7x6lUlZvsHZ5shABWYOQ==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "2.x"
+    rc-motion "^2.3.4"
+    rc-util "^5.2.1"
+    shallowequal "^1.1.0"
+
+rc-dialog@~8.6.0:
+  version "8.6.0"
+  resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-8.6.0.tgz#3b228dac085de5eed8c6237f31162104687442e7"
+  integrity sha512-GSbkfqjqxpZC5/zc+8H332+q5l/DKUhpQr0vdX2uDsxo5K0PhvaMEVjyoJUTkZ3+JstEADQji1PVLVb/2bJeOQ==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.6"
+    rc-motion "^2.3.0"
+    rc-util "^5.6.1"
+
+rc-drawer@~4.4.2:
+  version "4.4.3"
+  resolved "https://registry.yarnpkg.com/rc-drawer/-/rc-drawer-4.4.3.tgz#2094937a844e55dc9644236a2d9fba79c344e321"
+  integrity sha512-FYztwRs3uXnFOIf1hLvFxIQP9MiZJA+0w+Os8dfDh/90X7z/HqP/Yg+noLCIeHEbKln1Tqelv8ymCAN24zPcfQ==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.6"
+    rc-util "^5.7.0"
+
+rc-dropdown@^3.2.0, rc-dropdown@~3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/rc-dropdown/-/rc-dropdown-3.2.0.tgz#da6c2ada403842baee3a9e909a0b1a91ba3e1090"
+  integrity sha512-j1HSw+/QqlhxyTEF6BArVZnTmezw2LnSmRk6I9W7BCqNCKaRwleRmMMs1PHbuaG8dKHVqP6e21RQ7vPBLVnnNw==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.6"
+    rc-trigger "^5.0.4"
+
+rc-field-form@~1.22.0-2:
+  version "1.22.1"
+  resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.22.1.tgz#0bd2f4e730ff2f071529d00bef28e062362890f5"
+  integrity sha512-LweU7nBeqmC5r3HDUjRprcOXXobHXp/TGIxD7ppBq5FX6Iptt3ibdpRVg4RSyNulBNGHOuknHlRcguuIpvVMVg==
+  dependencies:
+    "@babel/runtime" "^7.8.4"
+    async-validator "^4.0.2"
+    rc-util "^5.8.0"
+
+rc-image@~5.2.5:
+  version "5.2.5"
+  resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-5.2.5.tgz#44e6ffc842626827960e7ab72e1c0d6f3a8ce440"
+  integrity sha512-qUfZjYIODxO0c8a8P5GeuclYXZjzW4hV/5hyo27XqSFo1DmTCs2HkVeQObkcIk5kNsJtgsj1KoPThVsSc/PXOw==
+  dependencies:
+    "@babel/runtime" "^7.11.2"
+    classnames "^2.2.6"
+    rc-dialog "~8.6.0"
+    rc-util "^5.0.6"
+
+rc-input-number@~7.3.0:
+  version "7.3.4"
+  resolved "https://registry.yarnpkg.com/rc-input-number/-/rc-input-number-7.3.4.tgz#674aea98260250287d36e330a7e065b174486e9d"
+  integrity sha512-W9uqSzuvJUnz8H8vsVY4kx+yK51SsAxNTwr8SNH4G3XqQNocLVmKIibKFRjocnYX1RDHMND9FFbgj2h7E7nvGA==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.5"
+    rc-util "^5.9.8"
+
+rc-mentions@~1.6.1:
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/rc-mentions/-/rc-mentions-1.6.1.tgz#46035027d64aa33ef840ba0fbd411871e34617ae"
+  integrity sha512-LDzGI8jJVGnkhpTZxZuYBhMz3avcZZqPGejikchh97xPni/g4ht714Flh7DVvuzHQ+BoKHhIjobHnw1rcP8erg==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.6"
+    rc-menu "^9.0.0"
+    rc-textarea "^0.3.0"
+    rc-trigger "^5.0.4"
+    rc-util "^5.0.1"
+
+rc-menu@^9.0.0, rc-menu@~9.1.1:
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/rc-menu/-/rc-menu-9.1.1.tgz#c44b9d8a9af6d1c26cf9cfeb9776b216bcee060d"
+  integrity sha512-yavNNsTgWOlUYwOrJtO8s1Hn0haUvc/x5ozx9KA/H0VspOksIFeWOp7lsEQ3juWyBI2VltDxWQ2DHc65OhZ5pg==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "2.x"
+    rc-motion "^2.4.3"
+    rc-overflow "^1.2.0"
+    rc-trigger "^5.1.2"
+    rc-util "^5.12.0"
+    shallowequal "^1.1.0"
+
+rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.2.0, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4:
+  version "2.4.4"
+  resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.4.4.tgz#e995d5fa24fc93065c24f714857cf2677d655bb0"
+  integrity sha512-ms7n1+/TZQBS0Ydd2Q5P4+wJTSOrhIrwNxLXCZpR7Fa3/oac7Yi803HDALc2hLAKaCTQtw9LmQeB58zcwOsqlQ==
+  dependencies:
+    "@babel/runtime" "^7.11.1"
+    classnames "^2.2.1"
+    rc-util "^5.2.1"
+
+rc-notification@~4.5.7:
+  version "4.5.7"
+  resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-4.5.7.tgz#265e6e6a0c1a0fac63d6abd4d832eb8ff31522f1"
+  integrity sha512-zhTGUjBIItbx96SiRu3KVURcLOydLUHZCPpYEn1zvh+re//Tnq/wSxN4FKgp38n4HOgHSVxcLEeSxBMTeBBDdw==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "2.x"
+    rc-motion "^2.2.0"
+    rc-util "^5.0.1"
+
+rc-overflow@^1.0.0, rc-overflow@^1.2.0:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.2.2.tgz#95b0222016c0cdbdc0db85f569c262e7706a5f22"
+  integrity sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==
+  dependencies:
+    "@babel/runtime" "^7.11.1"
+    classnames "^2.2.1"
+    rc-resize-observer "^1.0.0"
+    rc-util "^5.5.1"
+
+rc-pagination@~3.1.9:
+  version "3.1.15"
+  resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.15.tgz#e05eddf4c15717a5858290bed0857e27e2f957ff"
+  integrity sha512-4L3fot8g4E+PjWEgoVGX0noFCg+8ZFZmeLH4vsnZpB3O2T2zThtakjNxG+YvSaYtyMVT4B+GLayjKrKbXQpdAg==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.1"
+
+rc-picker@~2.5.17:
+  version "2.5.19"
+  resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.5.19.tgz#73d07546fac3992f0bfabf2789654acada39e46f"
+  integrity sha512-u6myoCu/qiQ0vLbNzSzNrzTQhs7mldArCpPHrEI6OUiifs+IPXmbesqSm0zilJjfzrZJLgYeyyOMSznSlh0GKA==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.1"
+    date-fns "2.x"
+    dayjs "1.x"
+    moment "^2.24.0"
+    rc-trigger "^5.0.4"
+    rc-util "^5.4.0"
+    shallowequal "^1.1.0"
+
+rc-progress@~3.2.1:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/rc-progress/-/rc-progress-3.2.2.tgz#319afe140f7e25cde2c1378b236a76905b551cf1"
+  integrity sha512-hvYqiFxFQeDGzY8AuARqp4vEGSD54W0KMg8cCcLFyT2tRJnxQyND/9vyUzVMYuaHexou06QsvLoqyBc3BPDVbg==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.6"
+    rc-util "^5.16.1"
+
+rc-rate@~2.9.0:
+  version "2.9.1"
+  resolved "https://registry.yarnpkg.com/rc-rate/-/rc-rate-2.9.1.tgz#e43cb95c4eb90a2c1e0b16ec6614d8c43530a731"
+  integrity sha512-MmIU7FT8W4LYRRHJD1sgG366qKtSaKb67D0/vVvJYR0lrCuRrCiVQ5qhfT5ghVO4wuVIORGpZs7ZKaYu+KMUzA==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.5"
+    rc-util "^5.0.1"
+
+rc-resize-observer@^1.0.0, rc-resize-observer@^1.1.0, rc-resize-observer@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.1.2.tgz#214bc5d0de19e0a5fcd7d8352d9c1560dd7531b7"
+  integrity sha512-Qp+1x6D88FxyWBFRYP95IV9A1o0xlkC6qhiTX3YakE+o86QH9IzC7UVnltwmm4Q8uYH+E3F/HRmEiuxXJECdSw==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.1"
+    rc-util "^5.15.0"
+    resize-observer-polyfill "^1.5.1"
+
+rc-select@~14.0.0-alpha.15, rc-select@~14.0.0-alpha.8:
+  version "14.0.0-alpha.19"
+  resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.0.0-alpha.19.tgz#74196366a7769cf1932d426b0b805e7ac7899b65"
+  integrity sha512-It7e5bQHxkx7Y9ZiKjvLOm208aJYccsDBvUhWMRYtcYMXDrnoVPXdE8WLB+buzVCrjawcTxDv7ZK92ewuavG5A==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "2.x"
+    rc-motion "^2.0.1"
+    rc-overflow "^1.0.0"
+    rc-trigger "^5.0.4"
+    rc-util "^5.16.1"
+    rc-virtual-list "^3.2.0"
+
+rc-slider@~9.7.4:
+  version "9.7.5"
+  resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-9.7.5.tgz#193141c68e99b1dc3b746daeb6bf852946f5b7f4"
+  integrity sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.5"
+    rc-tooltip "^5.0.1"
+    rc-util "^5.16.1"
+    shallowequal "^1.1.0"
+
+rc-steps@~4.1.0:
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/rc-steps/-/rc-steps-4.1.4.tgz#0ba82db202d59ca52d0693dc9880dd145b19dc23"
+  integrity sha512-qoCqKZWSpkh/b03ASGx1WhpKnuZcRWmvuW+ZUu4mvMdfvFzVxblTwUM+9aBd0mlEUFmt6GW8FXhMpHkK3Uzp3w==
+  dependencies:
+    "@babel/runtime" "^7.10.2"
+    classnames "^2.2.3"
+    rc-util "^5.0.1"
+
+rc-switch@~3.2.0:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/rc-switch/-/rc-switch-3.2.2.tgz#d001f77f12664d52595b4f6fb425dd9e66fba8e8"
+  integrity sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.1"
+    rc-util "^5.0.1"
+
+rc-table@~7.21.0:
+  version "7.21.1"
+  resolved "https://registry.yarnpkg.com/rc-table/-/rc-table-7.21.1.tgz#37db960c9d8cb68f11b7ae0d4a9cb8428e527225"
+  integrity sha512-b9CdrJR1k4mJoddPMmeyAe6YOFpYAPWAEzAG4O4tFOpIGB7mnvwlNhZT2qnjdixPioQ1w6IHiu7RGaPPz+wjRA==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.5"
+    rc-resize-observer "^1.1.0"
+    rc-util "^5.14.0"
+    shallowequal "^1.1.0"
+
+rc-tabs@~11.10.0:
+  version "11.10.5"
+  resolved "https://registry.yarnpkg.com/rc-tabs/-/rc-tabs-11.10.5.tgz#53bbb642d04b307f8ce86e318ab99d519507b29b"
+  integrity sha512-DDuUdV6b9zGRYLtjI5hyejWLKoz1QiLWNgMeBzc3aMeQylZFhTYnFGdDc6HRqj5IYearNTsFPVSA+6VIT8g5cg==
+  dependencies:
+    "@babel/runtime" "^7.11.2"
+    classnames "2.x"
+    rc-dropdown "^3.2.0"
+    rc-menu "^9.0.0"
+    rc-resize-observer "^1.0.0"
+    rc-util "^5.5.0"
+
+rc-textarea@^0.3.0, rc-textarea@~0.3.0:
+  version "0.3.7"
+  resolved "https://registry.yarnpkg.com/rc-textarea/-/rc-textarea-0.3.7.tgz#987142891efdedb774883c07e2f51b318fde5a11"
+  integrity sha512-yCdZ6binKmAQB13hc/oehh0E/QRwoPP1pjF21aHBxlgXO3RzPF6dUu4LG2R4FZ1zx/fQd2L1faktulrXOM/2rw==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.1"
+    rc-resize-observer "^1.0.0"
+    rc-util "^5.7.0"
+    shallowequal "^1.1.0"
+
+rc-tooltip@^5.0.1, rc-tooltip@~5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.1.1.tgz#94178ed162d0252bc4993b725f5dc2ac0fccf154"
+  integrity sha512-alt8eGMJulio6+4/uDm7nvV+rJq9bsfxFDCI0ljPdbuoygUscbsMYb6EQgwib/uqsXQUvzk+S7A59uYHmEgmDA==
+  dependencies:
+    "@babel/runtime" "^7.11.2"
+    rc-trigger "^5.0.0"
+
+rc-tree-select@~5.0.0-alpha.2:
+  version "5.0.0-alpha.3"
+  resolved "https://registry.yarnpkg.com/rc-tree-select/-/rc-tree-select-5.0.0-alpha.3.tgz#88c2015ef194d2e3418fe135b2f8a68edd5655f5"
+  integrity sha512-lsbWdnUZRas7FCfg4r6FX0EJ8srjLGTWmvCrnFqiKFiDD5tAFqxCB1wY7OT02GDsW+GFVrx/4wCljrstVSSRmg==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "2.x"
+    rc-select "~14.0.0-alpha.8"
+    rc-tree "~5.3.3"
+    rc-util "^5.16.1"
+
+rc-tree@~5.3.3, rc-tree@~5.3.4, rc-tree@~5.3.5:
+  version "5.3.7"
+  resolved "https://registry.yarnpkg.com/rc-tree/-/rc-tree-5.3.7.tgz#556c1e265cb426108fa6bd961b72ec8c26b558cb"
+  integrity sha512-H3KLD/bvqaBqqQ6zNgP5/ostffkSi4wuMSEYqhqFheJWPDveNrML10m3mvc6hZB39cRrp1YSQANaIQ1VcLYiXQ==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "2.x"
+    rc-motion "^2.0.1"
+    rc-util "^5.16.1"
+    rc-virtual-list "^3.4.1"
+
+rc-trigger@^5.0.0, rc-trigger@^5.0.4, rc-trigger@^5.1.2, rc-trigger@^5.2.10:
+  version "5.2.10"
+  resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.2.10.tgz#8a0057a940b1b9027eaa33beec8a6ecd85cce2b1"
+  integrity sha512-FkUf4H9BOFDaIwu42fvRycXMAvkttph9AlbCZXssZDVzz2L+QZ0ERvfB/4nX3ZFPh1Zd+uVGr1DEDeXxq4J1TA==
+  dependencies:
+    "@babel/runtime" "^7.11.2"
+    classnames "^2.2.6"
+    rc-align "^4.0.0"
+    rc-motion "^2.0.0"
+    rc-util "^5.5.0"
+
+rc-upload@~4.3.0:
+  version "4.3.3"
+  resolved "https://registry.yarnpkg.com/rc-upload/-/rc-upload-4.3.3.tgz#e237aa525e5313fa16f4d04d27f53c2f0e157bb8"
+  integrity sha512-YoJ0phCRenMj1nzwalXzciKZ9/FAaCrFu84dS5pphwucTC8GUWClcDID/WWNGsLFcM97NqIboDqrV82rVRhW/w==
+  dependencies:
+    "@babel/runtime" "^7.10.1"
+    classnames "^2.2.5"
+    rc-util "^5.2.0"
+
+rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.0.7, rc-util@^5.12.0, rc-util@^5.14.0, rc-util@^5.15.0, rc-util@^5.16.1, rc-util@^5.2.0, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.4.0, rc-util@^5.5.0, rc-util@^5.5.1, rc-util@^5.6.1, rc-util@^5.7.0, rc-util@^5.8.0, rc-util@^5.9.4, rc-util@^5.9.8:
+  version "5.16.1"
+  resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.16.1.tgz#374db7cb735512f05165ddc3d6b2c61c21b8b4e3"
+  integrity sha512-kSCyytvdb3aRxQacS/71ta6c+kBWvM1v8/2h9d/HaNWauc3qB8pLnF20PJ8NajkNN8gb+rR1l0eWO+D4Pz+LLQ==
+  dependencies:
+    "@babel/runtime" "^7.12.5"
+    react-is "^16.12.0"
+    shallowequal "^1.1.0"
+
+rc-virtual-list@^3.2.0, rc-virtual-list@^3.4.1:
+  version "3.4.2"
+  resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz#1078327aa7230b5e456d679ed2ce99f3c036ebd1"
+  integrity sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==
+  dependencies:
+    classnames "^2.2.6"
+    rc-resize-observer "^1.0.0"
+    rc-util "^5.0.7"
+
 react-app-polyfill@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz#95221e0a9bd259e5ca6b177c7bb1cb6768f68fd7"
@@ -7014,7 +7552,7 @@ react-error-overlay@^6.0.10:
   resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
   integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==
 
-react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
+react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -7177,12 +7715,19 @@ redent@^3.0.0:
     indent-string "^4.0.0"
     strip-indent "^3.0.0"
 
+redux-saga@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.1.3.tgz#9f3e6aebd3c994bbc0f6901a625f9a42b51d1112"
+  integrity sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw==
+  dependencies:
+    "@redux-saga/core" "^1.1.3"
+
 redux-thunk@^2.4.1:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
   integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==
 
-redux@^4.0.0, redux@^4.1.2:
+redux@^4.0.0, redux@^4.0.4, redux@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"
   integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==
@@ -7286,6 +7831,11 @@ requires-port@^1.0.0:
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
   integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
 
+resize-observer-polyfill@^1.5.0, resize-observer-polyfill@^1.5.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
+  integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
+
 resolve-cwd@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@@ -7475,6 +8025,13 @@ schema-utils@^4.0.0:
     ajv-formats "^2.1.1"
     ajv-keywords "^5.0.0"
 
+scroll-into-view-if-needed@^2.2.25:
+  version "2.2.28"
+  resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz#5a15b2f58a52642c88c8eca584644e01703d645a"
+  integrity sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==
+  dependencies:
+    compute-scroll-into-view "^1.0.17"
+
 select-hose@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -7570,6 +8127,11 @@ setprototypeof@1.2.0:
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
   integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
 
+shallowequal@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
+  integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
+
 shebang-command@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@@ -7747,6 +8309,11 @@ stackframe@^1.1.1:
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
   integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
 
+string-convert@^0.2.0:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
+  integrity sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c=
+
 string-length@^4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
@@ -8103,6 +8670,11 @@ to-regex-range@^5.0.1:
   dependencies:
     is-number "^7.0.0"
 
+toggle-selection@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
+  integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
+
 toidentifier@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
@@ -8212,6 +8784,25 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
+typescript-compare@^0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425"
+  integrity sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==
+  dependencies:
+    typescript-logic "^0.0.0"
+
+typescript-logic@^0.0.0:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/typescript-logic/-/typescript-logic-0.0.0.tgz#66ebd82a2548f2b444a43667bec120b496890196"
+  integrity sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==
+
+typescript-tuple@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/typescript-tuple/-/typescript-tuple-2.2.1.tgz#7d9813fb4b355f69ac55032e0363e8bb0f04dad2"
+  integrity sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==
+  dependencies:
+    typescript-compare "^0.0.2"
+
 unbox-primitive@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"